hlship 2004/06/05 17:29:25
Modified: framework/src/java/org/apache/hivemind/service/impl
ClassFactoryImpl.java ServiceMessages.java
ClassFactoryClassLoader.java ClassFabImpl.java
MethodFabImpl.java
. status.xml
framework/src/java/org/apache/hivemind/service ClassFab.java
Added: framework/src/java/org/apache/hivemind/service/impl
CtClassSource.java
framework/src/test/hivemind/test/services
AbstractIntWrapper.java TestClassFab.java
FailService.java
Log:
Refactor ClassFab for reuse outside of HiveMind and add new tests.
Revision Changes Path
1.4 +9 -53
jakarta-hivemind/framework/src/java/org/apache/hivemind/service/impl/ClassFactoryImpl.java
Index: ClassFactoryImpl.java
===================================================================
RCS file:
/home/cvs/jakarta-hivemind/framework/src/java/org/apache/hivemind/service/impl/ClassFactoryImpl.java,v
retrieving revision 1.3
retrieving revision 1.4
diff -u -r1.3 -r1.4
--- ClassFactoryImpl.java 5 Jun 2004 19:09:13 -0000 1.3
+++ ClassFactoryImpl.java 6 Jun 2004 00:29:25 -0000 1.4
@@ -34,39 +34,23 @@
* Implementation of [EMAIL PROTECTED]
org.apache.hivemind.service.ClassFactory}.
*
* @author Howard Lewis Ship
- * @version $Id$
*/
public class ClassFactoryImpl implements ClassFactory
{
/**
- * Map of ModuleData, keyed on module id.
+ * Map of CtClassSource, keyed on module id.
*/
- private Map _moduleDataMap = new HashMap();
-
- /**
- * Stores the Javassist ClassPool and the special ClassFactoryClassLoader
- * for a module.
- */
- private static class ModuleData
- {
- ClassPool _pool;
- ClassFactoryClassLoader _loader;
- }
+ private Map _ctClassSourceMap = new HashMap();
public ClassFab newClass(String name, Class superClass, Module module)
{
- ModuleData data = findModuleData(module);
-
- ClassPool pool = data._pool;
- ClassFactoryClassLoader loader = data._loader;
-
- CtClass ctSuperClass = getClass(pool, superClass);
+ CtClassSource source = findCtClassSource(module);
try
{
- CtClass ctNewClass = pool.makeClass(name, ctSuperClass);
+ CtClass ctNewClass = source.newClass(name, superClass);
- return new ClassFabImpl(this, ctNewClass, loader);
+ return new ClassFabImpl(source, ctNewClass);
}
catch (Exception ex)
{
@@ -77,45 +61,17 @@
}
- public CtClass getClass(ClassPool pool, Class inputClass)
- {
- String name = ClassFabUtils.getJavaClassName(inputClass);
-
- try
- {
- return pool.get(name);
- }
- catch (NotFoundException ex)
- {
- throw new ApplicationRuntimeException(
- ServiceMessages.unableToLookupClass(name, ex),
- ex);
- }
- }
-
- private synchronized ModuleData findModuleData(Module module)
+ private synchronized CtClassSource findCtClassSource(Module module)
{
String id = module.getModuleId();
- ModuleData result = (ModuleData) _moduleDataMap.get(id);
+ CtClassSource result = (CtClassSource) _ctClassSourceMap.get(id);
if (result == null)
{
- ClassPool pool = new ClassPool(null);
-
- ClassLoader moduleLoader =
module.getClassResolver().getClassLoader();
- ClassPath path = new LoaderClassPath(moduleLoader);
-
- pool.appendClassPath(path);
-
- ClassFactoryClassLoader loader = new
ClassFactoryClassLoader(moduleLoader);
-
- result = new ModuleData();
-
- result._pool = pool;
- result._loader = loader;
+ result = new
CtClassSource(module.getClassResolver().getClassLoader());
- _moduleDataMap.put(id, result);
+ _ctClassSourceMap.put(id, result);
}
return result;
1.2 +10 -0
jakarta-hivemind/framework/src/java/org/apache/hivemind/service/impl/ServiceMessages.java
Index: ServiceMessages.java
===================================================================
RCS file:
/home/cvs/jakarta-hivemind/framework/src/java/org/apache/hivemind/service/impl/ServiceMessages.java,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -r1.1 -r1.2
--- ServiceMessages.java 5 Jun 2004 19:09:13 -0000 1.1
+++ ServiceMessages.java 6 Jun 2004 00:29:25 -0000 1.2
@@ -17,6 +17,7 @@
import java.beans.EventSetDescriptor;
import javassist.CtClass;
+import javassist.CtMethod;
import org.apache.hivemind.HiveMind;
import org.apache.hivemind.InterceptorStack;
@@ -163,6 +164,15 @@
return HiveMind.format(
"EventLinkImpl.unable-to-introspect-class",
targetClass.getName(),
+ cause.getMessage());
+ }
+
+ public static String unableToAddCatch(Class exceptionClass, CtMethod
method, Throwable cause)
+ {
+ return HiveMind.format(
+ "MethodFabImpl.unable-to-add-catch",
+ exceptionClass.getName(),
+ method.getDeclaringClass().getName(),
cause.getMessage());
}
}
1.2 +0 -1
jakarta-hivemind/framework/src/java/org/apache/hivemind/service/impl/ClassFactoryClassLoader.java
Index: ClassFactoryClassLoader.java
===================================================================
RCS file:
/home/cvs/jakarta-hivemind/framework/src/java/org/apache/hivemind/service/impl/ClassFactoryClassLoader.java,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -r1.1 -r1.2
--- ClassFactoryClassLoader.java 26 Feb 2004 23:07:45 -0000 1.1
+++ ClassFactoryClassLoader.java 6 Jun 2004 00:29:25 -0000 1.2
@@ -18,7 +18,6 @@
* ClassLoader used to properly instantiate newly created classes.
*
* @author Howard Lewis Ship / Essl Christian
- * @version $Id$
*/
class ClassFactoryClassLoader extends ClassLoader
{
1.4 +11 -32
jakarta-hivemind/framework/src/java/org/apache/hivemind/service/impl/ClassFabImpl.java
Index: ClassFabImpl.java
===================================================================
RCS file:
/home/cvs/jakarta-hivemind/framework/src/java/org/apache/hivemind/service/impl/ClassFabImpl.java,v
retrieving revision 1.3
retrieving revision 1.4
diff -u -r1.3 -r1.4
--- ClassFabImpl.java 5 Jun 2004 19:09:13 -0000 1.3
+++ ClassFabImpl.java 6 Jun 2004 00:29:25 -0000 1.4
@@ -34,33 +34,27 @@
*
* @author Howard Lewis Ship
*/
-class ClassFabImpl implements ClassFab
+public class ClassFabImpl implements ClassFab
{
- private ClassFactoryImpl _factory;
private CtClass _ctClass;
- private ClassPool _pool;
- private ClassFactoryClassLoader _loader;
+ private CtClassSource _source;
- private static final boolean WRITE_CLASS =
Boolean.getBoolean("hivemind.write-class");
-
- public ClassFabImpl(ClassFactoryImpl factory, CtClass ctClass,
ClassFactoryClassLoader loader)
+ public ClassFabImpl(CtClassSource source, CtClass ctClass)
{
- _factory = factory;
+ _source = source;
_ctClass = ctClass;
- _pool = ctClass.getClassPool();
- _loader = loader;
}
public void addInterface(Class interfaceClass)
{
- CtClass ctInterfaceClass = _factory.getClass(_pool, interfaceClass);
+ CtClass ctInterfaceClass = _source.getCtClass(interfaceClass);
_ctClass.addInterface(ctInterfaceClass);
}
public void addField(String name, Class type)
{
- CtClass ctType = _factory.getClass(_pool, type);
+ CtClass ctType = _source.getCtClass(type);
try
{
@@ -85,7 +79,8 @@
Class[] exceptions,
String body)
{
- CtClass ctReturnType = _factory.getClass(_pool, returnType);
+ CtClass ctReturnType = _source.getCtClass(returnType);
+
CtClass[] ctParameters = convertClasses(parameterTypes);
CtClass[] ctExceptions = convertClasses(exceptions);
@@ -108,7 +103,7 @@
// Return a MethodFab so the caller can add catches.
- return new MethodFabImpl(_factory, _pool, method);
+ return new MethodFabImpl(_source, method);
}
@@ -143,7 +138,7 @@
for (int i = 0; i < count; i++)
{
- CtClass ctClass = _factory.getClass(_pool, inputClasses[i]);
+ CtClass ctClass = _source.getCtClass(inputClasses[i]);
result[i] = ctClass;
}
@@ -153,23 +148,7 @@
public Class createClass()
{
- String className = _ctClass.getName();
-
- try
- {
- if (WRITE_CLASS)
- _ctClass.writeFile();
-
- byte[] bytecode = _pool.write(className);
-
- return _loader.loadClass(className, bytecode);
- }
- catch (Throwable ex)
- {
- throw new ApplicationRuntimeException(
- ServiceMessages.unableToWriteClass(_ctClass, ex),
- ex);
- }
+ return _source.createClass(_ctClass);
}
}
1.3 +5 -12
jakarta-hivemind/framework/src/java/org/apache/hivemind/service/impl/MethodFabImpl.java
Index: MethodFabImpl.java
===================================================================
RCS file:
/home/cvs/jakarta-hivemind/framework/src/java/org/apache/hivemind/service/impl/MethodFabImpl.java,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -r1.2 -r1.3
--- MethodFabImpl.java 13 May 2004 11:04:46 -0000 1.2
+++ MethodFabImpl.java 6 Jun 2004 00:29:25 -0000 1.3
@@ -29,24 +29,21 @@
* so that additional exception handlers may be attached to the added method.
*
* @author Howard Lewis Ship
- * @version $Id$
*/
class MethodFabImpl implements MethodFab
{
- private ClassFactoryImpl _factory;
- private ClassPool _pool;
+ private CtClassSource _source;
private CtMethod _method;
- public MethodFabImpl(ClassFactoryImpl factory, ClassPool pool, CtMethod
method)
+ public MethodFabImpl(CtClassSource source, CtMethod method)
{
- _factory = factory;
- _pool = pool;
+ _source = source;
_method = method;
}
public void addCatch(Class exceptionClass, String catchBody)
{
- CtClass ctException = _factory.getClass(_pool, exceptionClass);
+ CtClass ctException = _source.getCtClass(exceptionClass);
try
{
@@ -55,11 +52,7 @@
catch (Exception ex)
{
throw new ApplicationRuntimeException(
- HiveMind.format(
- "MethodFabImpl.unable-to-add-catch",
- exceptionClass.getName(),
- _method.getDeclaringClass().getName(),
- ex.getMessage()),
+ ServiceMessages.unableToAddCatch(exceptionClass, _method,
ex),
ex);
}
}
1.1
jakarta-hivemind/framework/src/java/org/apache/hivemind/service/impl/CtClassSource.java
Index: CtClassSource.java
===================================================================
// Copyright 2004 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package org.apache.hivemind.service.impl;
import javassist.ClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.LoaderClassPath;
import javassist.NotFoundException;
import org.apache.hivemind.ApplicationRuntimeException;
import org.apache.hivemind.service.ClassFabUtils;
/**
* Wrapper around Javassist's [EMAIL PROTECTED] javassist.ClassPool} and
* our own [EMAIL PROTECTED]
org.apache.hivemind.service.impl.ClassFactoryClassLoader}
* that manages the creation of new instance of [EMAIL PROTECTED]
javassist.CtClass}
* and converts finished CtClass's into instantiable Classes.
*
* @author Howard Lewis Ship
*/
public class CtClassSource
{
private ClassPool _pool;
private ClassFactoryClassLoader _loader;
public CtClassSource(ClassLoader parentLoader)
{
_pool = new ClassPool(null);
ClassPath path = new LoaderClassPath(parentLoader);
_pool.appendClassPath(path);
_loader = new ClassFactoryClassLoader(parentLoader);
}
public CtClass getCtClass(Class searchClass)
{
String name = ClassFabUtils.getJavaClassName(searchClass);
try
{
return _pool.get(name);
}
catch (NotFoundException ex)
{
throw new ApplicationRuntimeException(
ServiceMessages.unableToLookupClass(name, ex),
ex);
}
}
public CtClass newClass(String name, Class superClass)
{
CtClass ctSuperClass = getCtClass(superClass);
return _pool.makeClass(name, ctSuperClass);
}
public Class createClass(CtClass ctClass)
{
String className = ctClass.getName();
try
{
_pool.write(className);
byte[] bytecode = _pool.write(className);
return _loader.loadClass(className, bytecode);
}
catch (Throwable ex)
{
throw new ApplicationRuntimeException(
ServiceMessages.unableToWriteClass(ctClass, ex),
ex);
}
}
}
1.1
jakarta-hivemind/framework/src/test/hivemind/test/services/AbstractIntWrapper.java
Index: AbstractIntWrapper.java
===================================================================
// Copyright 2004 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hivemind.test.services;
import org.apache.hivemind.impl.BaseLocatable;
/**
* Part of the [EMAIL PROTECTED] hivemind.test.services.TestClassFab} test
case.
*
* @author Howard Lewis Ship
*/
public abstract class AbstractIntWrapper extends BaseLocatable
{
public abstract int getIntValue();
}
1.1
jakarta-hivemind/framework/src/test/hivemind/test/services/TestClassFab.java
Index: TestClassFab.java
===================================================================
// Copyright 2004 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hivemind.test.services;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.util.List;
import javassist.CtClass;
import org.apache.hivemind.ApplicationRuntimeException;
import org.apache.hivemind.service.ClassFab;
import org.apache.hivemind.service.MethodFab;
import org.apache.hivemind.service.impl.ClassFabImpl;
import org.apache.hivemind.service.impl.CtClassSource;
import org.apache.hivemind.test.HiveMindTestCase;
import org.apache.hivemind.util.PropertyUtils;
/**
* Tests related to [EMAIL PROTECTED]
org.apache.hivemind.service.impl.ClassFabImpl},
* [EMAIL PROTECTED] org.apache.hivemind.service.impl.CtClassSource}, etc.
*
* @author Howard Lewis Ship
*/
public class TestClassFab extends HiveMindTestCase
{
private CtClassSource _source =
new CtClassSource(Thread.currentThread().getContextClassLoader());
private ClassFab newClassFab(String className, Class superClass)
{
CtClass ctClass = _source.newClass(className, superClass);
return new ClassFabImpl(_source, ctClass);
}
public void testCreateBean() throws Exception
{
ClassFab cf = newClassFab("TargetBean", Object.class);
cf.addField("_stringValue", String.class);
cf.addMethod(
Modifier.PUBLIC,
"setStringValue",
void.class,
new Class[] { String.class },
null,
"_stringValue = $1;");
cf.addMethod(
Modifier.PUBLIC,
"getStringValue",
String.class,
null,
null,
"return _stringValue;");
Class targetClass = cf.createClass();
Object targetBean = targetClass.newInstance();
PropertyUtils.write(targetBean, "stringValue", "Fred", null);
String actual = (String) PropertyUtils.read(targetBean,
"stringValue", null);
assertEquals("Fred", actual);
}
public void testConstructor() throws Exception
{
ClassFab cf = newClassFab("ConstructableBean", Object.class);
cf.addField("_stringValue", String.class);
cf.addConstructor(new Class[] { String.class }, null, "{ _stringValue
= $1; }");
cf.addMethod(
Modifier.PUBLIC,
"getStringValue",
String.class,
null,
null,
"return _stringValue;");
Class targetClass = cf.createClass();
try
{
targetClass.newInstance();
unreachable();
}
catch (InstantiationException ex)
{
}
Constructor c = targetClass.getConstructors()[0];
Object targetBean = c.newInstance(new Object[] { "Buffy" });
String actual = (String) PropertyUtils.read(targetBean,
"stringValue", null);
assertEquals("Buffy", actual);
}
public void testConstructorFromBaseClass() throws Exception
{
ClassFab cf = newClassFab("MyIntHolder", AbstractIntWrapper.class);
cf.addField("_intValue", int.class);
cf.addConstructor(new Class[] { int.class }, null, "{ _intValue = $1;
}");
cf.addMethod(Modifier.PUBLIC, "getIntValue", int.class, null, null,
"return _intValue;");
Class targetClass = cf.createClass();
Constructor c = targetClass.getConstructors()[0];
AbstractIntWrapper targetBean =
(AbstractIntWrapper) c.newInstance(new Object[] { new
Integer(137)});
assertEquals(137, targetBean.getIntValue());
}
public void testInvalidSuperClass() throws Exception
{
ClassFab cf = newClassFab("InvalidSuperClass", List.class);
try
{
cf.createClass();
unreachable();
}
catch (ApplicationRuntimeException ex)
{
assertExceptionSubstring(ex, "Unable to create class
InvalidSuperClass");
}
}
public void testAddInterface() throws Exception
{
ClassFab cf = newClassFab("SimpleService", Object.class);
cf.addInterface(SimpleService.class);
cf.addMethod(
Modifier.PUBLIC,
"add",
int.class,
new Class[] { int.class, int.class },
null,
"return $1 + $2;");
Class targetClass = cf.createClass();
SimpleService s = (SimpleService) targetClass.newInstance();
assertEquals(207, s.add(99, 108));
}
public void testSubclassFromFinal() throws Exception
{
ClassFab cf = newClassFab("StringSubclass", String.class);
try
{
cf.createClass();
}
catch (ApplicationRuntimeException ex)
{
assertEquals(
"Unable to create class StringSubclass: Cannot inherit from
final class",
ex.getMessage());
}
}
public void testInPackage() throws Exception
{
ClassFab cf = newClassFab("org.apache.hivemind.InPackage",
Object.class);
Class c = cf.createClass();
Object o = c.newInstance();
assertEquals("org.apache.hivemind.InPackage", o.getClass().getName());
}
public void testBadMethodBody() throws Exception
{
ClassFab cf = newClassFab("BadMethodBody", Object.class);
cf.addInterface(Runnable.class);
try
{
cf.addMethod(Modifier.PUBLIC, "run", void.class, null, null,
"fail;");
}
catch (ApplicationRuntimeException ex)
{
assertExceptionSubstring(ex, "Unable to add method run to class
BadMethod");
}
}
public void testBadConstructor() throws Exception
{
ClassFab cf = newClassFab("BadConstructor", Object.class);
try
{
cf.addConstructor(null, null, " woops!");
}
catch (ApplicationRuntimeException ex)
{
assertExceptionSubstring(ex, "Unable to add constructor to class
BadConstructor");
}
}
public void testCatchException() throws Exception
{
ClassFab cf = newClassFab("Fail", Object.class);
cf.addInterface(FailService.class);
MethodFab mf =
cf.addMethod(
Modifier.PUBLIC,
"fail",
void.class,
null,
null,
"throw new java.lang.RuntimeException(\"Ouch!\");");
mf.addCatch(RuntimeException.class, "throw new
java.io.IOException($e.getMessage());");
Class targetClass = cf.createClass();
FailService fs = (FailService) targetClass.newInstance();
try
{
fs.fail();
unreachable();
}
catch (IOException ex)
{
assertEquals("Ouch!", ex.getMessage());
}
}
public void testBadCatch() throws Exception
{
ClassFab cf = newClassFab("BadCatch", Object.class);
cf.addInterface(Runnable.class);
MethodFab mf = cf.addMethod(Modifier.PUBLIC, "run", void.class, null,
null, "return;");
try
{
mf.addCatch(RuntimeException.class, "woops!");
unreachable();
}
catch (ApplicationRuntimeException ex)
{
assertExceptionSubstring(
ex,
"Unable to add catch block for exception
java.lang.RuntimeException to class BadCatch");
}
}
public void testInvalidField() throws Exception
{
ClassFab cf = newClassFab("InvalidField", Object.class);
cf.addField(".", String.class);
cf.addField("buffy", int.class);
cf.addField("", int.class);
try
{
cf.createClass();
unreachable();
}
catch (ApplicationRuntimeException ex)
{
assertExceptionSubstring(ex, "Unable to create class
InvalidField");
}
// Javassist lets us down here; I can't think of a way to get
addField() to actually
// fail.
}
}
1.1
jakarta-hivemind/framework/src/test/hivemind/test/services/FailService.java
Index: FailService.java
===================================================================
// Copyright 2004 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hivemind.test.services;
import java.io.IOException;
/**
* Used by [EMAIL PROTECTED] hivemind.test.services.TestClassFab}.
*
* @author Howard Lewis Ship
*/
public interface FailService
{
public void fail() throws IOException;
}
1.2 +4 -1 jakarta-hivemind/status.xml
Index: status.xml
===================================================================
RCS file: /home/cvs/jakarta-hivemind/status.xml,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -r1.1 -r1.2
--- status.xml 5 Jun 2004 19:36:37 -0000 1.1
+++ status.xml 6 Jun 2004 00:29:25 -0000 1.2
@@ -17,7 +17,7 @@
<status>
<developers>
- <person name="Howard M. Lewis Ship" email="[EMAIL PROTECTED]"
id="HLS" />
+ <person name="Howard M. Lewis Ship" email="[EMAIL PROTECTED]" id="HLS"
/>
</developers>
@@ -32,6 +32,9 @@
<changes>
<release version="1.0-beta-1" date="unreleased">
<action type="update" dev="HLS">Added change log.
</action>
+ <action type="update" dev="HLS">Refactored ClassFab and
related classes
+ for easier reuse outside of HiveMind. Added a
new suite of tests
+ related to ClassFab.</action>
</release>
</changes>
</status>
1.2 +1 -2
jakarta-hivemind/framework/src/java/org/apache/hivemind/service/ClassFab.java
Index: ClassFab.java
===================================================================
RCS file:
/home/cvs/jakarta-hivemind/framework/src/java/org/apache/hivemind/service/ClassFab.java,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -r1.1 -r1.2
--- ClassFab.java 26 Feb 2004 23:07:49 -0000 1.1
+++ ClassFab.java 6 Jun 2004 00:29:25 -0000 1.2
@@ -19,7 +19,6 @@
* the Javassist library.
*
* @author Howard Lewis Ship
- * @version $Id$
*/
public interface ClassFab
{
@@ -47,7 +46,7 @@
*/
public MethodFab addMethod(
- int modifiers,
+ int modifiers,
String name,
Class returnType,
Class[] parameterTypes,
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]