donaldp 2002/07/10 20:41:23
Modified: container/src/conf ant-services.xml
container/src/java/org/apache/myrmidon/components/workspace
DefaultTaskContext.java
container/src/test/org/apache/myrmidon/components
AbstractComponentTest.java
container/src/test/org/apache/myrmidon/components/property/test
AbstractPropertyResolverTestCase.java
ClassicPropertyResolverTestCase.java
DefaultPropertyResolverTestCase.java
Added: container/src/java/org/apache/myrmidon/components/property
ClassicPropertyEvaluator.java
DefaultPropertyEvaluator.java
XPathPropertyEvaluator.java
container/src/java/org/apache/myrmidon/interfaces/property
PropertyEvaluator.java
Removed: container/src/java/org/apache/myrmidon/components/property
ClassicPropertyResolver.java
DefaultPropertyResolver.java
XPathPropertyResolver.java
container/src/java/org/apache/myrmidon/interfaces/property
PropertyResolver.java
Log:
Renamed PropertyResolver to PropertyEvaluator
Revision Changes Path
1.9 +2 -2 jakarta-ant-myrmidon/container/src/conf/ant-services.xml
Index: ant-services.xml
===================================================================
RCS file: /home/cvs/jakarta-ant-myrmidon/container/src/conf/ant-services.xml,v
retrieving revision 1.8
retrieving revision 1.9
diff -u -r1.8 -r1.9
--- ant-services.xml 2 Jul 2002 09:56:26 -0000 1.8
+++ ant-services.xml 11 Jul 2002 03:41:22 -0000 1.9
@@ -30,8 +30,8 @@
<role
name="org.apache.myrmidon.interfaces.converter.ConverterRegistry"/>
<role name="org.apache.excalibur.converter.Converter"/>
</service>
- <service
classname="org.apache.myrmidon.components.property.XPathPropertyResolver">
- <role
name="org.apache.myrmidon.interfaces.property.PropertyResolver"/>
+ <service
classname="org.apache.myrmidon.components.property.XPathPropertyEvaluator">
+ <role
name="org.apache.myrmidon.interfaces.property.PropertyEvaluator"/>
</service>
<service
classname="org.apache.myrmidon.components.builder.DefaultModelBuilder">
<role name="org.apache.myrmidon.interfaces.builder.ModelBuilder"/>
1.1
jakarta-ant-myrmidon/container/src/java/org/apache/myrmidon/components/property/ClassicPropertyEvaluator.java
Index: ClassicPropertyEvaluator.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE.txt file.
*/
package org.apache.myrmidon.components.property;
import org.apache.myrmidon.api.TaskContext;
import org.apache.myrmidon.api.TaskException;
import org.apache.myrmidon.interfaces.property.PropertyEvaluator;
/**
* A [EMAIL PROTECTED] PropertyEvaluator} implementation which resolves
properties
* as per Ant1, ignoring undefined properties.
*
* @author <a href="mailto:[EMAIL PROTECTED]">Darrell DeBoer</a>
* @version $Revision: 1.1 $ $Date: 2002/07/11 03:41:22 $
*
* @ant.type type="property-resolver" name="classic"
*/
public class ClassicPropertyEvaluator
extends DefaultPropertyEvaluator
implements PropertyEvaluator
{
/**
* Evaluates an expression.
*
* @param expression the name of the property to retrieve
* @param context the set of known properties
*/
protected Object evaluateExpression( final String expression,
final TaskContext context )
throws TaskException
{
final Object value = context.getProperty( expression );
if( value != null )
{
return value;
}
else
{
return "${" + expression + "}";
}
}
}
1.1
jakarta-ant-myrmidon/container/src/java/org/apache/myrmidon/components/property/DefaultPropertyEvaluator.java
Index: DefaultPropertyEvaluator.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE.txt file.
*/
package org.apache.myrmidon.components.property;
import org.apache.avalon.excalibur.i18n.ResourceManager;
import org.apache.avalon.excalibur.i18n.Resources;
import org.apache.avalon.framework.service.ServiceException;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.avalon.framework.service.Serviceable;
import org.apache.excalibur.converter.Converter;
import org.apache.excalibur.converter.ConverterException;
import org.apache.myrmidon.api.TaskContext;
import org.apache.myrmidon.api.TaskException;
import org.apache.myrmidon.interfaces.property.PropertyEvaluator;
/**
* Base class for PropertyEvaluator implementations.
*
* @author <a href="mailto:[EMAIL PROTECTED]">Peter Donald</a>
* @author <a href="mailto:[EMAIL PROTECTED]">Darrell DeBoer</a>
* @version $Revision: 1.1 $ $Date: 2002/07/11 03:41:22 $
*
* @ant.type type="property-resolver" name="default"
* @avalon.component name="default"
* @avalon.service interface="PropertyEvaluator"
*/
public class DefaultPropertyEvaluator
implements PropertyEvaluator, Serviceable
{
private static final Resources REZ =
ResourceManager.getPackageResources( DefaultPropertyEvaluator.class );
private Converter m_converter;
/**
* @avalon.dependency interface="Converter"
*/
public void service( final ServiceManager serviceManager ) throws
ServiceException
{
m_converter = (Converter)serviceManager.lookup( Converter.ROLE );
}
/**
* Resolve a string property. This evaluates all property
* substitutions based on specified context.
*
* If the content contains a single property reference, then the property
value
* <code>Object</code> itself is returned.
* Otherwise, a <code>String</code> is returned, comprising the supplied
* content, with all property references replaced with the result of
* <code>toString()</code> called on the property value.
*
* @param content the property to resolve
* @param context the context in which to resolve property
* @return the reolved property
* @throws TaskException if an error occurs
*/
public Object resolveProperties( final String content,
final TaskContext context )
throws TaskException
{
int start = findNextProperty( content, 0 );
if( -1 == start )
{
return content;
}
int end = findEnding( content, start );
final int length = content.length();
if( 0 == start && end == ( length - 1 ) )
{
return evaluateExpression( content.substring( start + 2, end ),
context );
}
final StringBuffer sb = new StringBuffer( length * 2 );
int lastPlace = 0;
while( true )
{
final String propertyValue =
getPropertyStringValue( content.substring( start + 2, end ),
context );
sb.append( content.substring( lastPlace, start ) );
sb.append( propertyValue );
lastPlace = end + 1;
start = findNextProperty( content, lastPlace );
if( -1 == start )
{
break;
}
end = findEnding( content, start );
}
sb.append( content.substring( lastPlace, length ) );
return sb.toString();
}
/**
* Resolve a string property. This recursively evaluates all property
* substitutions based on specified context.
*
* @param content the property to resolve
* @param context the context in which to resolve property
* @return the reolved property
* @throws TaskException if an error occurs
*/
protected Object recursiveResolveProperty( final String content,
final TaskContext context )
throws TaskException
{
int start = findNextProperty( content, 0 );
if( -1 == start )
{
return content;
}
int end = findNestedEnding( content, start );
final int length = content.length();
if( 0 == start && end == ( length - 1 ) )
{
final String propertyName = content.substring( start + 2, end );
final Object key = recursiveResolveProperty( propertyName,
context );
return evaluateExpression( key.toString(), context );
}
final StringBuffer sb = new StringBuffer( length * 2 );
int lastPlace = 0;
while( true )
{
final String propertyName = content.substring( start + 2, end );
final Object key = recursiveResolveProperty( propertyName,
context );
final String value = getPropertyStringValue( key.toString(),
context );
sb.append( content.substring( lastPlace, start ) );
sb.append( value );
lastPlace = end + 1;
start = findNextProperty( content, lastPlace );
if( -1 == start )
{
break;
}
end = findNestedEnding( content, start );
}
sb.append( content.substring( lastPlace, length ) );
return sb.toString();
}
/**
* Finds the next occurrance of the start of a Property identifier.
* @param content the String to search
* @param currentPosition start location of the search
* @return the position of the next occurrence, or <code>-1</code> if none
* was found.
*/
private int findNextProperty( final String content, final int
currentPosition )
{
//TODO: Check if it is commented out
return content.indexOf( "${", currentPosition );
}
/**
* Finds the next occurrence of the end of a Property identifier.
* @param property the String to search
* @param currentPosition start location of the search
* @return the position of the next occurrence
* @throws TaskException if no end was found
*/
private int findEnding( final String property, final int currentPosition )
throws TaskException
{
//TODO: Check if it is commented out
final int index = property.indexOf( '}', currentPosition );
if( -1 == index )
{
final String message = REZ.getString(
"prop.mismatched-braces.error" );
throw new TaskException( message );
}
return index;
}
/**
* Finds the end of the property identifier at the currentPosition,
* taking into account nested property identifiers.
* @param property the String to search
* @param currentPosition location of the property
* @return the position of the propery ending.
* @throws TaskException if the property is not properly ended.
*/
private int findNestedEnding( final String property, final int
currentPosition )
throws TaskException
{
final int length = property.length();
final int start = currentPosition + 2;
int weight = 1;
for( int i = start; ( weight > 0 ) && ( i < length ); i++ )
{
final char ch = property.charAt( i );
switch( ch )
{
case '}':
//TODO: Check if it is commented out
weight--;
if( weight == 0 )
{
return i;
}
break;
case '$':
{
//TODO: Check if it is commented out
final int next = i + 1;
if( next < length && '{' == property.charAt( next ) )
{
weight++;
}
}
break;
}
}
final String message = REZ.getString( "prop.mismatched-braces.error"
);
throw new TaskException( message );
}
/**
* Returns a property's value, converted to a String.
*/
private String getPropertyStringValue( final String expression,
final TaskContext context )
throws TaskException
{
final Object value = evaluateExpression( expression, context );
if( value == null )
{
final String message = REZ.getString( "prop.missing-value.error",
expression );
throw new TaskException( message );
}
if( value instanceof String )
{
return (String)value;
}
try
{
return (String)m_converter.convert( String.class, value, context
);
}
catch( final ConverterException e )
{
throw new TaskException( e.getMessage(), e );
}
}
/**
* Evaluates an expression.
*
* @param expression the expression to evaluate
* @param context the set of known properties
* @return the object retrieved from context
*/
protected Object evaluateExpression( final String expression,
final TaskContext context )
throws TaskException
{
final Object value = context.getProperty( expression );
if( value != null )
{
return value;
}
final String message = REZ.getString( "prop.missing-value.error",
expression );
throw new TaskException( message );
}
}
1.1
jakarta-ant-myrmidon/container/src/java/org/apache/myrmidon/components/property/XPathPropertyEvaluator.java
Index: XPathPropertyEvaluator.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE.txt file.
*/
package org.apache.myrmidon.components.property;
import java.util.Collections;
import java.util.HashMap;
import org.apache.avalon.framework.activity.Initializable;
import org.apache.avalon.framework.service.ServiceException;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.commons.jxpath.JXPathContext;
import org.apache.commons.jxpath.JXPathIntrospector;
import org.apache.commons.jxpath.MapDynamicPropertyHandler;
import org.apache.myrmidon.api.TaskContext;
import org.apache.myrmidon.api.TaskException;
/**
* A property resolver that uses JXPath to resolve values.
*
* @author <a href="mailto:[EMAIL PROTECTED]">Adam Murdoch</a>
* @version $Revision: 1.1 $ $Date: 2002/07/11 03:41:22 $
* @avalon.component name="xpath"
* @avalon.service interface="PropertyEvaluator"
*/
public class XPathPropertyEvaluator
extends DefaultPropertyEvaluator
implements Initializable
{
/**
* @avalon.dependency interface="Converter"
*/
public void service( ServiceManager serviceManager ) throws
ServiceException
{
super.service( serviceManager );
}
public void initialize()
throws Exception
{
//We need to explicitly register this class
//or else JXPath will not be able to understand that
//instances are a Map and not a bean.
final Class clazz = Collections.unmodifiableMap( new HashMap()
).getClass();
JXPathIntrospector.registerDynamicClass( clazz,
MapDynamicPropertyHandler.class );
}
/**
* Evaluates an expression.
*
* @param expression the expression to evaluate
* @param context the set of known properties
* @return the object retrieved from context
*
* @todo - If more than one value, return a List or Map of them.
* @todo - For some reason DOM nodes get converted to String. Need to
* fix this so that the Node object is returned.
*/
protected Object evaluateExpression( final String expression,
final TaskContext context )
throws TaskException
{
final JXPathContext jxContext = JXPathContext.newContext(
context.getProperties() );
return jxContext.getValue( expression );
}
}
1.65 +4 -4
jakarta-ant-myrmidon/container/src/java/org/apache/myrmidon/components/workspace/DefaultTaskContext.java
Index: DefaultTaskContext.java
===================================================================
RCS file:
/home/cvs/jakarta-ant-myrmidon/container/src/java/org/apache/myrmidon/components/workspace/DefaultTaskContext.java,v
retrieving revision 1.64
retrieving revision 1.65
diff -u -r1.64 -r1.65
--- DefaultTaskContext.java 2 Jul 2002 09:56:26 -0000 1.64
+++ DefaultTaskContext.java 11 Jul 2002 03:41:22 -0000 1.65
@@ -18,7 +18,7 @@
import org.apache.myrmidon.api.event.LogLevel;
import org.apache.myrmidon.api.event.TaskEvent;
import org.apache.myrmidon.interfaces.event.TaskEventManager;
-import org.apache.myrmidon.interfaces.property.PropertyResolver;
+import org.apache.myrmidon.interfaces.property.PropertyEvaluator;
import org.apache.myrmidon.interfaces.property.PropertyStore;
import org.apache.myrmidon.interfaces.service.AntServiceKernel;
@@ -209,8 +209,8 @@
{
try
{
- final PropertyResolver propertyResolver =
- (PropertyResolver)getService( PropertyResolver.class );
+ final PropertyEvaluator propertyResolver =
+ (PropertyEvaluator)getService( PropertyEvaluator.class );
final Object object = propertyResolver.resolveProperties( value,
this );
if( null == object )
{
1.1
jakarta-ant-myrmidon/container/src/java/org/apache/myrmidon/interfaces/property/PropertyEvaluator.java
Index: PropertyEvaluator.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE.txt file.
*/
package org.apache.myrmidon.interfaces.property;
import org.apache.myrmidon.api.TaskContext;
import org.apache.myrmidon.api.TaskException;
/**
*
* Provides a service for the resolution of property identifiers within
* String content.
*
* @author <a href="mailto:[EMAIL PROTECTED]">Darrell DeBoer</a>
* @version $Revision: 1.1 $ $Date: 2002/07/11 03:41:22 $
*
* @ant.role name="property-resolver"
*/
public interface PropertyEvaluator
{
/** Role name for this interface. */
String ROLE = PropertyEvaluator.class.getName();
/**
* Resolve a string property. This evaluates all property
* substitutions based on specified contex.
* Rules used for property resolution are implementation dependent.
*
* @param value the value to resolve, which may contain property
identifiers
* @param context the set of properties to resolve against.
* @return the resolved content
* @throws TaskException if an error occurs
*/
Object resolveProperties( final String value,
final TaskContext context )
throws TaskException;
}
1.56 +6 -6
jakarta-ant-myrmidon/container/src/test/org/apache/myrmidon/components/AbstractComponentTest.java
Index: AbstractComponentTest.java
===================================================================
RCS file:
/home/cvs/jakarta-ant-myrmidon/container/src/test/org/apache/myrmidon/components/AbstractComponentTest.java,v
retrieving revision 1.55
retrieving revision 1.56
diff -u -r1.55 -r1.56
--- AbstractComponentTest.java 5 Jul 2002 03:21:58 -0000 1.55
+++ AbstractComponentTest.java 11 Jul 2002 03:41:23 -0000 1.56
@@ -33,7 +33,7 @@
import org.apache.myrmidon.components.extensions.DefaultExtensionManager;
import org.apache.myrmidon.components.library.DefaultLibraryManager;
import org.apache.myrmidon.components.property.DefaultNameValidatorManager;
-import org.apache.myrmidon.components.property.DefaultPropertyResolver;
+import org.apache.myrmidon.components.property.DefaultPropertyEvaluator;
import org.apache.myrmidon.components.property.DefaultPropertyStore;
import org.apache.myrmidon.components.service.DefaultAntServiceKernel;
import org.apache.myrmidon.components.type.DefaultTypeManager;
@@ -47,7 +47,7 @@
import org.apache.myrmidon.interfaces.extensions.ExtensionManager;
import org.apache.myrmidon.interfaces.library.LibraryManager;
import org.apache.myrmidon.interfaces.property.NameValidatorManager;
-import org.apache.myrmidon.interfaces.property.PropertyResolver;
+import org.apache.myrmidon.interfaces.property.PropertyEvaluator;
import org.apache.myrmidon.interfaces.property.PropertyStore;
import org.apache.myrmidon.interfaces.role.RoleInfo;
import org.apache.myrmidon.interfaces.role.RoleManager;
@@ -136,8 +136,8 @@
m_serviceKernel.registerService( ExtensionManager.ROLE,
component );
components.add( component );
- component = createComponent( PropertyResolver.ROLE,
DefaultPropertyResolver.class );
- m_serviceKernel.registerService( PropertyResolver.ROLE,
component );
+ component = createComponent( PropertyEvaluator.ROLE,
DefaultPropertyEvaluator.class );
+ m_serviceKernel.registerService( PropertyEvaluator.ROLE,
component );
components.add( component );
component = createComponent( TaskEventManager.ROLE,
DefaultTaskEventManager.class );
@@ -189,7 +189,7 @@
info = new RoleInfo( "service-factory", ServiceFactory.class );
roleRegistry.addRole( info );
- info = new RoleInfo( "property-resolver", PropertyResolver.class
);
+ info = new RoleInfo( "property-resolver",
PropertyEvaluator.class );
roleRegistry.addRole( info );
}
1.15 +8 -8
jakarta-ant-myrmidon/container/src/test/org/apache/myrmidon/components/property/test/AbstractPropertyResolverTestCase.java
Index: AbstractPropertyResolverTestCase.java
===================================================================
RCS file:
/home/cvs/jakarta-ant-myrmidon/container/src/test/org/apache/myrmidon/components/property/test/AbstractPropertyResolverTestCase.java,v
retrieving revision 1.14
retrieving revision 1.15
diff -u -r1.14 -r1.15
--- AbstractPropertyResolverTestCase.java 5 Jul 2002 03:21:58 -0000
1.14
+++ AbstractPropertyResolverTestCase.java 11 Jul 2002 03:41:23 -0000
1.15
@@ -14,8 +14,8 @@
import org.apache.myrmidon.api.TaskContext;
import org.apache.myrmidon.api.TaskException;
import org.apache.myrmidon.components.AbstractComponentTest;
-import org.apache.myrmidon.components.property.DefaultPropertyResolver;
-import org.apache.myrmidon.interfaces.property.PropertyResolver;
+import org.apache.myrmidon.components.property.DefaultPropertyEvaluator;
+import org.apache.myrmidon.interfaces.property.PropertyEvaluator;
/**
* General-purpose property resolver test cases.
@@ -26,7 +26,7 @@
public abstract class AbstractPropertyResolverTestCase
extends AbstractComponentTest
{
- protected PropertyResolver m_resolver;
+ protected PropertyEvaluator m_resolver;
protected TaskContext m_context;
public AbstractPropertyResolverTestCase( final String name )
@@ -38,7 +38,7 @@
{
super.setUp();
- m_resolver = (PropertyResolver)lookup( PropertyResolver.ROLE );
+ m_resolver = (PropertyEvaluator)lookup( PropertyEvaluator.ROLE );
m_context = createContext();
m_context.setProperty( "intProp", new Integer( 333 ) );
@@ -54,7 +54,7 @@
protected Object createComponent( String role, Class defaultImpl )
throws Exception
{
- if( role.equals( PropertyResolver.ROLE ) )
+ if( role.equals( PropertyEvaluator.ROLE ) )
{
return createResolver();
}
@@ -67,7 +67,7 @@
/**
* Creates the resolver to test.
*/
- protected abstract PropertyResolver createResolver();
+ protected abstract PropertyEvaluator createResolver();
/**
* Test property resolution with various different typed properties.
@@ -119,7 +119,7 @@
*/
public void testInvalidTypeDeclarations() throws Exception
{
- final Resources rez = getResourcesForTested(
DefaultPropertyResolver.class );
+ final Resources rez = getResourcesForTested(
DefaultPropertyEvaluator.class );
doTestFailure( "${unclosed",
rez.getString( "prop.mismatched-braces.error" ),
m_context );
1.5 +6 -6
jakarta-ant-myrmidon/container/src/test/org/apache/myrmidon/components/property/test/ClassicPropertyResolverTestCase.java
Index: ClassicPropertyResolverTestCase.java
===================================================================
RCS file:
/home/cvs/jakarta-ant-myrmidon/container/src/test/org/apache/myrmidon/components/property/test/ClassicPropertyResolverTestCase.java,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -r1.4 -r1.5
--- ClassicPropertyResolverTestCase.java 5 Jul 2002 03:21:58 -0000
1.4
+++ ClassicPropertyResolverTestCase.java 11 Jul 2002 03:41:23 -0000
1.5
@@ -7,11 +7,11 @@
*/
package org.apache.myrmidon.components.property.test;
-import org.apache.myrmidon.components.property.ClassicPropertyResolver;
-import org.apache.myrmidon.interfaces.property.PropertyResolver;
+import org.apache.myrmidon.components.property.ClassicPropertyEvaluator;
+import org.apache.myrmidon.interfaces.property.PropertyEvaluator;
/**
- * A test for [EMAIL PROTECTED]
org.apache.myrmidon.components.property.ClassicPropertyResolver}.
+ * A test for [EMAIL PROTECTED]
org.apache.myrmidon.components.property.ClassicPropertyEvaluator}.
*
* @author <a href="mailto:[EMAIL PROTECTED]">Darrell DeBoer</a>
* @version $Revision$ $Date$
@@ -24,9 +24,9 @@
super( name );
}
- protected PropertyResolver createResolver()
+ protected PropertyEvaluator createResolver()
{
- return new ClassicPropertyResolver();
+ return new ClassicPropertyEvaluator();
}
/**
1.6 +6 -6
jakarta-ant-myrmidon/container/src/test/org/apache/myrmidon/components/property/test/DefaultPropertyResolverTestCase.java
Index: DefaultPropertyResolverTestCase.java
===================================================================
RCS file:
/home/cvs/jakarta-ant-myrmidon/container/src/test/org/apache/myrmidon/components/property/test/DefaultPropertyResolverTestCase.java,v
retrieving revision 1.5
retrieving revision 1.6
diff -u -r1.5 -r1.6
--- DefaultPropertyResolverTestCase.java 5 Jul 2002 03:21:58 -0000
1.5
+++ DefaultPropertyResolverTestCase.java 11 Jul 2002 03:41:23 -0000
1.6
@@ -8,12 +8,12 @@
package org.apache.myrmidon.components.property.test;
import org.apache.avalon.excalibur.i18n.Resources;
-import org.apache.myrmidon.components.property.DefaultPropertyResolver;
+import org.apache.myrmidon.components.property.DefaultPropertyEvaluator;
import org.apache.myrmidon.components.property.DefaultPropertyStore;
-import org.apache.myrmidon.interfaces.property.PropertyResolver;
+import org.apache.myrmidon.interfaces.property.PropertyEvaluator;
/**
- * Functional tests for [EMAIL PROTECTED]
org.apache.myrmidon.components.property.DefaultPropertyResolver}.
+ * Functional tests for [EMAIL PROTECTED]
org.apache.myrmidon.components.property.DefaultPropertyEvaluator}.
*
* @author <a href="mailto:[EMAIL PROTECTED]">Darrell DeBoer</a>
* @version $Revision$ $Date$
@@ -26,9 +26,9 @@
super( name );
}
- protected PropertyResolver createResolver()
+ protected PropertyEvaluator createResolver()
{
- return new DefaultPropertyResolver();
+ return new DefaultPropertyEvaluator();
}
/**
--
To unsubscribe, e-mail: <mailto:[EMAIL PROTECTED]>
For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>