diff --git a/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/AttributeSetter.java b/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/AttributeSetter.java
new file mode 100644
index 000000000..af0334785
--- /dev/null
+++ b/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/AttributeSetter.java
@@ -0,0 +1,35 @@
+/*
+ * 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.configurer;
+
+import org.apache.avalon.framework.configuration.ConfigurationException;
+
+/**
+ * Used to set an attribute or text content of an object.
+ *
+ * @author Adam Murdoch
+ * @version $Revision$ $Date$
+ */
+public interface AttributeSetter
+{
+ /**
+ * Returns the attribute type.
+ */
+ Class getType();
+
+ /**
+ * Sets the value of the attribute.
+ *
+ * @param object The object to set the attribute of.
+ * @param value The value of the attribute. Must be assignable to the class
+ * returned by {@link #getType}.
+ * @throw ConfigurationException If the value could not be set.
+ */
+ void setAttribute( Object object, Object value )
+ throws ConfigurationException;
+}
diff --git a/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/DefaultAttributeSetter.java b/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/DefaultAttributeSetter.java
new file mode 100644
index 000000000..5a9ec3d61
--- /dev/null
+++ b/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/DefaultAttributeSetter.java
@@ -0,0 +1,66 @@
+/*
+ * 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.configurer;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import org.apache.avalon.excalibur.i18n.ResourceManager;
+import org.apache.avalon.excalibur.i18n.Resources;
+import org.apache.avalon.framework.configuration.ConfigurationException;
+
+/**
+ * A default attribute setter implementation, which uses reflection to
+ * set the attribute value.
+ *
+ * @author Adam Murdoch
+ * @version $Revision$ $Date$
+ */
+class DefaultAttributeSetter
+ implements AttributeSetter
+{
+ private final Method m_method;
+ private final Class m_type;
+
+ private static final Resources REZ =
+ ResourceManager.getPackageResources( DefaultAttributeSetter.class );
+
+ public DefaultAttributeSetter( final Method method )
+ {
+ m_method = method;
+ m_type = method.getParameterTypes()[ 0 ];
+ }
+
+ /**
+ * Returns the attribute type.
+ */
+ public Class getType()
+ {
+ return m_type;
+ }
+
+ /**
+ * Sets the value of the attribute.
+ */
+ public void setAttribute( final Object object, final Object value )
+ throws ConfigurationException
+ {
+ try
+ {
+ m_method.invoke( object, new Object[]{value} );
+ }
+ catch( final InvocationTargetException ite )
+ {
+ final Throwable cause = ite.getTargetException();
+ throw new ConfigurationException( cause.getMessage(), cause );
+ }
+ catch( final Exception e )
+ {
+ throw new ConfigurationException( e.getMessage(), e );
+ }
+ }
+}
diff --git a/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/DefaultConfigurer.java b/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/DefaultConfigurer.java
index e36888e99..4564418ab 100644
--- a/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/DefaultConfigurer.java
+++ b/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/DefaultConfigurer.java
@@ -7,12 +7,10 @@
*/
package org.apache.myrmidon.components.configurer;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
import org.apache.avalon.excalibur.i18n.ResourceManager;
import org.apache.avalon.excalibur.i18n.Resources;
-import org.apache.avalon.excalibur.property.PropertyException;
import org.apache.avalon.excalibur.property.PropertyUtil;
import org.apache.avalon.framework.component.ComponentException;
import org.apache.avalon.framework.component.ComponentManager;
@@ -23,7 +21,6 @@ import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.context.Context;
import org.apache.avalon.framework.logger.AbstractLogEnabled;
import org.apache.avalon.framework.logger.LogEnabled;
-import org.apache.myrmidon.converter.ConverterException;
import org.apache.myrmidon.interfaces.configurer.Configurer;
import org.apache.myrmidon.interfaces.converter.MasterConverter;
@@ -42,18 +39,13 @@ public class DefaultConfigurer
///Compile time constant to turn on extreme debugging
private final static boolean DEBUG = false;
- /*
- * TODO: Should reserved names be "configurable" ?
- */
- ///Element names that are reserved
- private final static String[] RESERVED_ELEMENTS =
- {
- "content"
- };
-
///Converter to use for converting between values
private MasterConverter m_converter;
+ ///Cached object configurers. This is a map from Class to the
+ ///ObjectConfigurer for that class.
+ private Map m_configurerCache = new HashMap();
+
public void compose( final ComponentManager componentManager )
throws ComponentException
{
@@ -81,7 +73,7 @@ public class DefaultConfigurer
if( DEBUG )
{
final String message = REZ.getString( "configuring-object.notice", object );
- getLogger().debug( "Configuring " + object );
+ getLogger().debug( message );
}
if( object instanceof Configurable )
@@ -89,9 +81,10 @@ public class DefaultConfigurer
if( DEBUG )
{
final String message = REZ.getString( "configurable.notice" );
- getLogger().debug( "Configuring object via Configurable interface" );
+ getLogger().debug( message );
}
+ // Let the object configure itself
( (Configurable)object ).configure( configuration );
}
else
@@ -102,408 +95,257 @@ public class DefaultConfigurer
getLogger().debug( message );
}
+ // Locate the configurer for this object
+ final ObjectConfigurer configurer = getConfigurer( object.getClass() );
+
+ // Set each of the attributes
final String[] attributes = configuration.getAttributeNames();
for( int i = 0; i < attributes.length; i++ )
{
final String name = attributes[ i ];
final String value = configuration.getAttribute( name );
- if( DEBUG )
- {
- final String message = REZ.getString( "configure-attribute.notice", name, value );
- getLogger().debug( message );
- }
-
- configureAttribute( object, name, value, context );
+ // Set the attribute
+ setAttribute( configurer, object, name, value, context );
}
- final Configuration[] children = configuration.getChildren();
-
- for( int i = 0; i < children.length; i++ )
+ // Set the text content
+ final String content = configuration.getValue( null );
+ if( null != content && content.length() > 0 )
{
- final Configuration child = children[ i ];
-
- if( DEBUG )
- {
- final String message =
- REZ.getString( "configure-subelement.notice", child.getName() );
- getLogger().debug( message );
- }
-
- configureElement( object, child, context );
+ setContent( configurer, object, content, context );
}
- final String content = configuration.getValue( null );
- if( null != content )
+ // Create and configure each of the child elements
+ final Configuration[] children = configuration.getChildren();
+ for( int i = 0; i < children.length; i++ )
{
- if( !content.trim().equals( "" ) )
- {
- if( DEBUG )
- {
- final String message =
- REZ.getString( "configure-content.notice", content );
- getLogger().debug( message );
- }
-
- configureContent( object, content, context );
- }
+ final Configuration childConfig = children[ i ];
+ configureElement( configurer, object, childConfig, context );
}
}
}
/**
- * Configure named attribute of object in a particular context.
- * This configuring can be done in different ways for different
- * configurers.
- *
- * @param object the object
- * @param name the attribute name
- * @param value the attribute value
- * @param context the Context
- * @exception ConfigurationException if an error occurs
+ * Sets the text content of an object.
*/
- public void configure( final Object object,
- final String name,
- final String value,
- final Context context )
+ private void setContent( final ObjectConfigurer configurer,
+ final Object object,
+ final String content,
+ final Context context )
throws ConfigurationException
{
- configureAttribute( object, name, value, context );
+ if( DEBUG )
+ {
+ final String message =
+ REZ.getString( "configure-content.notice", content );
+ getLogger().debug( message );
+ }
+
+ // Set the content
+ final AttributeSetter setter = configurer.getContentSetter();
+ if( null == setter )
+ {
+ final String message = REZ.getString( "content-not-supported.error" );
+ throw new ConfigurationException( message );
+ }
+ try
+ {
+ setValue( setter, object, content, context );
+ }
+ catch( final Exception e )
+ {
+ final String message = REZ.getString( "bad-set-content.error" );
+ throw new ConfigurationException( message, e );
+ }
}
/**
- * Try to configure content of an object.
- *
- * @param object the object
- * @param content the content value to be set
- * @param context the Context
- * @exception ConfigurationException if an error occurs
+ * Creates and configures a nested element
*/
- private void configureContent( final Object object,
- final String content,
+ private void configureElement( final ObjectConfigurer configurer,
+ final Object object,
+ final Configuration childConfig,
final Context context )
throws ConfigurationException
{
- setValue( object, "addContent", content, context );
- }
+ final String childName = childConfig.getName();
- private void configureAttribute( final Object object,
- final String name,
- final String value,
- final Context context )
- throws ConfigurationException
- {
- final String methodName = getMethodNameFor( name );
- setValue( object, methodName, value, context );
- }
-
- private void setValue( final Object object,
- final String methodName,
- final String value,
- final Context context )
- throws ConfigurationException
- {
- // OMFG the rest of this is soooooooooooooooooooooooooooooooo
- // slow. Need to cache results per class etc.
-
- final Class clazz = object.getClass();
- final Method[] methods = getMethodsFor( clazz, methodName );
- if( 0 == methods.length )
+ if( DEBUG )
{
final String message =
- REZ.getString( "no-attribute-method.error", methodName );
- throw new ConfigurationException( message );
+ REZ.getString( "configure-subelement.notice", childName );
+ getLogger().debug( message );
}
- setValue( object, value, context, methods );
- }
+ // Locate the configurer for the child element
+ final ElementConfigurer childConfigurer = configurer.getElement( childName );
+ if( null == childConfigurer )
+ {
+ final String message = REZ.getString( "unknown-subelement.error", childName );
+ throw new ConfigurationException( message );
+ }
- private void setValue( final Object object,
- final String value,
- final Context context,
- final Method methods[] )
- throws ConfigurationException
- {
try
{
- final Object objectValue =
- PropertyUtil.resolveProperty( value, context, false );
+ // Create the child element
+ final Object child = childConfigurer.createElement( object );
- setValue( object, objectValue, methods, context );
+ // Configure the child element
+ configure( child, childConfig, context );
+
+ // Set the child element
+ childConfigurer.addElement( object, child );
}
- catch( final PropertyException pe )
+ catch( final ConfigurationException ce )
{
final String message =
- REZ.getString( "bad-property-resolve.error", value );
- throw new ConfigurationException( message, pe );
+ REZ.getString( "bad-configure-subelement.error", childName );
+ throw new ConfigurationException( message, ce );
}
}
- private void setValue( final Object object,
- Object value,
- final Method methods[],
+ /**
+ * Configure named attribute of object in a particular context.
+ * This configuring can be done in different ways for different
+ * configurers.
+ *
+ * @param object the object
+ * @param name the attribute name
+ * @param value the attribute value
+ * @param context the Context
+ * @exception ConfigurationException if an error occurs
+ */
+ public void configure( final Object object,
+ final String name,
+ final String value,
final Context context )
throws ConfigurationException
{
- final Class sourceClass = value.getClass();
- final String source = sourceClass.getName();
+ // Locate the configurer for this object
+ final ObjectConfigurer configurer = getConfigurer( object.getClass() );
- for( int i = 0; i < methods.length; i++ )
- {
- if( setValue( object, value, methods[ i ], sourceClass, source, context ) )
- {
- return;
- }
- }
-
- final String message =
- REZ.getString( "no-can-convert.error", methods[ 0 ].getName(), source );
- throw new ConfigurationException( message );
+ // Set the attribute value
+ setAttribute( configurer, object, name, value, context );
}
- private boolean setValue( final Object object,
- Object value,
- final Method method,
- final Class sourceClass,
- final String source,
- final Context context )
+ /**
+ * Sets an attribute value.
+ */
+ private void setAttribute( final ObjectConfigurer configurer,
+ final Object object,
+ final String name,
+ final String value,
+ final Context context )
throws ConfigurationException
{
- Class parameterType = method.getParameterTypes()[ 0 ];
- if( parameterType.isPrimitive() )
- {
- parameterType = getComplexTypeFor( parameterType );
- }
-
- try
+ if( DEBUG )
{
- value = m_converter.convert( parameterType, value, context );
+ final String message = REZ.getString( "configure-attribute.notice",
+ name,
+ value );
+ getLogger().debug( message );
}
- catch( final ConverterException ce )
- {
- if( DEBUG )
- {
- final String message = REZ.getString( "no-converter.error" );
- getLogger().debug( message, ce );
- }
- throw new ConfigurationException( ce.getMessage(), ce );
- }
- catch( final Exception e )
+ // Locate the setter for this attribute
+ final AttributeSetter setter = configurer.getAttributeSetter( name );
+ if( null == setter )
{
- final String message =
- REZ.getString( "bad-convert-for-attribute.error", method.getName() );
- throw new ConfigurationException( message, e );
- }
-
- if( null == value )
- {
- return false;
+ final String message = REZ.getString( "unknown-attribute.error", name );
+ throw new ConfigurationException( message );
}
+ // Set the value
try
{
- method.invoke( object, new Object[]{value} );
- }
- catch( final IllegalAccessException iae )
- {
- //should never happen ....
- final String message = REZ.getString( "illegal-access.error" );
- throw new ConfigurationException( message, iae );
- }
- catch( final InvocationTargetException ite )
- {
- final String message = REZ.getString( "invoke-target.error", method.getName() );
- throw new ConfigurationException( message, ite );
+ setValue( setter, object, value, context );
}
-
- return true;
- }
-
- private Class getComplexTypeFor( final Class clazz )
- {
- if( String.class == clazz )
- return String.class;
- else if( Integer.TYPE.equals( clazz ) )
- return Integer.class;
- else if( Long.TYPE.equals( clazz ) )
- return Long.class;
- else if( Short.TYPE.equals( clazz ) )
- return Short.class;
- else if( Byte.TYPE.equals( clazz ) )
- return Byte.class;
- else if( Boolean.TYPE.equals( clazz ) )
- return Boolean.class;
- else if( Float.TYPE.equals( clazz ) )
- return Float.class;
- else if( Double.TYPE.equals( clazz ) )
- return Double.class;
- else
+ catch( final Exception e )
{
- final String message = REZ.getString( "no-complex-type.error", clazz.getName() );
- throw new IllegalArgumentException( message );
+ final String message = REZ.getString( "bad-set-attribute.error", name );
+ throw new ConfigurationException( message, e );
}
}
- private Method[] getMethodsFor( final Class clazz, final String methodName )
+ /**
+ * Sets an attribute value, or an element's text content.
+ */
+ private void setValue( final AttributeSetter setter,
+ final Object object,
+ final String value,
+ final Context context )
+ throws Exception
{
- final Method methods[] = clazz.getMethods();
- final ArrayList matches = new ArrayList();
+ // Resolve property references in the attribute value
+ Object objValue = PropertyUtil.resolveProperty( value, context, false );
- for( int i = 0; i < methods.length; i++ )
+ // Convert the value to the appropriate type
+ Class clazz = setter.getType();
+ if( clazz.isPrimitive() )
{
- final Method method = methods[ i ];
- if( methodName.equals( method.getName() ) &&
- Method.PUBLIC == ( method.getModifiers() & Method.PUBLIC ) )
- {
- if( method.getReturnType().equals( Void.TYPE ) )
- {
- final Class parameters[] = method.getParameterTypes();
- if( 1 == parameters.length )
- {
- matches.add( method );
- }
- }
- }
+ clazz = getComplexTypeFor( clazz );
}
- return (Method[])matches.toArray( new Method[ 0 ] );
+ objValue = m_converter.convert( clazz, objValue, context );
+
+ // Set the value
+ setter.setAttribute( object, objValue );
}
- private Method[] getCreateMethodsFor( final Class clazz, final String methodName )
+ /**
+ * Locates the configurer for a particular class.
+ */
+ private ObjectConfigurer getConfigurer( final Class clazz )
+ throws ConfigurationException
{
- final Method methods[] = clazz.getMethods();
- final ArrayList matches = new ArrayList();
-
- for( int i = 0; i < methods.length; i++ )
+ ObjectConfigurer configurer =
+ (ObjectConfigurer)m_configurerCache.get( clazz );
+ if( null == configurer )
{
- final Method method = methods[ i ];
- if( methodName.equals( method.getName() ) &&
- Method.PUBLIC == ( method.getModifiers() & Method.PUBLIC ) )
- {
- final Class returnType = method.getReturnType();
- if( !returnType.equals( Void.TYPE ) &&
- !returnType.isPrimitive() )
- {
- final Class parameters[] = method.getParameterTypes();
- if( 0 == parameters.length )
- {
- matches.add( method );
- }
- }
- }
+ configurer = DefaultObjectConfigurer.getConfigurer( clazz );
+ m_configurerCache.put( clazz, configurer );
}
-
- return (Method[])matches.toArray( new Method[ 0 ] );
- }
-
- private String getMethodNameFor( final String attribute )
- {
- return "set" + getJavaNameFor( attribute.toLowerCase() );
+ return configurer;
}
- private String getJavaNameFor( final String name )
+ private Class getComplexTypeFor( final Class clazz )
{
- final StringBuffer sb = new StringBuffer();
-
- int index = name.indexOf( '-' );
- int last = 0;
-
- while( -1 != index )
+ if( String.class == clazz )
{
- final String word = name.substring( last, index ).toLowerCase();
- sb.append( Character.toUpperCase( word.charAt( 0 ) ) );
- sb.append( word.substring( 1, word.length() ) );
- last = index + 1;
- index = name.indexOf( '-', last );
+ return String.class;
}
-
- index = name.length();
- final String word = name.substring( last, index ).toLowerCase();
- sb.append( Character.toUpperCase( word.charAt( 0 ) ) );
- sb.append( word.substring( 1, word.length() ) );
-
- return sb.toString();
- }
-
- private void configureElement( final Object object,
- final Configuration configuration,
- final Context context )
- throws ConfigurationException
- {
- final String name = configuration.getName();
- final String javaName = getJavaNameFor( name );
-
- // OMFG the rest of this is soooooooooooooooooooooooooooooooo
- // slow. Need to cache results per class etc.
- final Class clazz = object.getClass();
- Method methods[] = getMethodsFor( clazz, "add" + javaName );
-
- if( 0 != methods.length )
+ else if( Integer.TYPE.equals( clazz ) )
{
- //guess it is first method ????
- addElement( object, methods[ 0 ], configuration, context );
+ return Integer.class;
}
- else
+ else if( Long.TYPE.equals( clazz ) )
{
- methods = getCreateMethodsFor( clazz, "create" + javaName );
-
- if( 0 == methods.length )
- {
- final String message =
- REZ.getString( "no-element-method.error", javaName );
- throw new ConfigurationException( message );
- }
-
- //guess it is first method ????
- createElement( object, methods[ 0 ], configuration, context );
+ return Long.class;
}
- }
-
- private void createElement( final Object object,
- final Method method,
- final Configuration configuration,
- final Context context )
- throws ConfigurationException
- {
- try
+ else if( Short.TYPE.equals( clazz ) )
{
- final Object created = method.invoke( object, new Object[ 0 ] );
- configure( created, configuration, context );
+ return Short.class;
}
- catch( final ConfigurationException ce )
+ else if( Byte.TYPE.equals( clazz ) )
{
- throw ce;
+ return Byte.class;
}
- catch( final Exception e )
+ else if( Boolean.TYPE.equals( clazz ) )
{
- final String message = REZ.getString( "subelement-create.error" );
- throw new ConfigurationException( message, e );
+ return Boolean.class;
}
- }
-
- private void addElement( final Object object,
- final Method method,
- final Configuration configuration,
- final Context context )
- throws ConfigurationException
- {
- try
+ else if( Float.TYPE.equals( clazz ) )
{
- final Class clazz = method.getParameterTypes()[ 0 ];
- final Object created = clazz.newInstance();
-
- configure( created, configuration, context );
- method.invoke( object, new Object[]{created} );
+ return Float.class;
}
- catch( final ConfigurationException ce )
+ else if( Double.TYPE.equals( clazz ) )
{
- throw ce;
+ return Double.class;
}
- catch( final Exception e )
+ else
{
- final String message = REZ.getString( "subelement-create.error" );
- throw new ConfigurationException( message, e );
+ final String message = REZ.getString( "no-complex-type.error", clazz.getName() );
+ throw new IllegalArgumentException( message );
}
}
}
diff --git a/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/DefaultElementConfigurer.java b/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/DefaultElementConfigurer.java
new file mode 100644
index 000000000..8e6f0d5f8
--- /dev/null
+++ b/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/DefaultElementConfigurer.java
@@ -0,0 +1,101 @@
+/*
+ * 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.configurer;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import org.apache.avalon.excalibur.i18n.ResourceManager;
+import org.apache.avalon.excalibur.i18n.Resources;
+import org.apache.avalon.framework.configuration.ConfigurationException;
+
+/**
+ * The default element configurer implementation, which uses reflection to
+ * create and/or add nested elements.
+ *
+ * @author Adam Murdoch
+ * @version $Revision$ $Date$
+ */
+class DefaultElementConfigurer
+ implements ElementConfigurer
+{
+ private static final Resources REZ =
+ ResourceManager.getPackageResources( DefaultElementConfigurer.class );
+
+ private final Class m_type;
+ private final Method m_createMethod;
+ private final Method m_addMethod;
+
+ public DefaultElementConfigurer( final Class type,
+ final Method createMethod,
+ final Method addMethod )
+ {
+ m_type = type;
+ m_createMethod = createMethod;
+ m_addMethod = addMethod;
+ }
+
+ /**
+ * Returns the type of the element.
+ */
+ public Class getType()
+ {
+ return m_type;
+ }
+
+ /**
+ * Creates a nested element.
+ */
+ public Object createElement( final Object parent )
+ throws ConfigurationException
+ {
+ try
+ {
+ if( null != m_createMethod )
+ {
+ return m_createMethod.invoke( parent, null );
+ }
+ else
+ {
+ return m_type.newInstance();
+ }
+ }
+ catch( final InvocationTargetException ite )
+ {
+ final Throwable cause = ite.getTargetException();
+ throw new ConfigurationException( cause.getMessage(), cause );
+ }
+ catch( final Exception e )
+ {
+ throw new ConfigurationException( e.getMessage(), e );
+ }
+ }
+
+ /**
+ * Sets the nested element, after it has been configured.
+ */
+ public void addElement( final Object parent, final Object child )
+ throws ConfigurationException
+ {
+ try
+ {
+ if( null != m_addMethod )
+ {
+ m_addMethod.invoke( parent, new Object[]{child} );
+ }
+ }
+ catch( final InvocationTargetException ite )
+ {
+ final Throwable cause = ite.getTargetException();
+ throw new ConfigurationException( cause.getMessage(), cause );
+ }
+ catch( final Exception e )
+ {
+ throw new ConfigurationException( e.getMessage(), e );
+ }
+ }
+}
diff --git a/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/DefaultObjectConfigurer.java b/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/DefaultObjectConfigurer.java
new file mode 100644
index 000000000..0172c89a2
--- /dev/null
+++ b/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/DefaultObjectConfigurer.java
@@ -0,0 +1,380 @@
+/*
+ * 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.configurer;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.avalon.excalibur.i18n.ResourceManager;
+import org.apache.avalon.excalibur.i18n.Resources;
+import org.apache.avalon.framework.configuration.ConfigurationException;
+
+/**
+ * An object configurer which uses reflection to determine the attributes
+ * and elements of a class.
+ *
+ * @author Adam Murdoch
+ * @version $Revision$ $Date$
+ */
+public class DefaultObjectConfigurer
+ implements ObjectConfigurer
+{
+ private static final Resources REZ =
+ ResourceManager.getPackageResources( DefaultObjectConfigurer.class );
+
+ private final Class m_class;
+
+ /**
+ * Map from lowercase attribute name -> AttributeSetter.
+ */
+ private final Map m_attrs = new HashMap();
+
+ /**
+ * Map from lowercase element name -> ElementSetter.
+ */
+ private final Map m_elements = new HashMap();
+
+ /**
+ * Content setter.
+ */
+ private AttributeSetter m_contentSetter;
+
+ /**
+ * Creates an object configurer for a particular class. The newly
+ * created configurer will not handle any attributes, elements, or content.
+ * Use the various enable
methods to enable handling of these.
+ */
+ public DefaultObjectConfigurer( final Class classInfo )
+ {
+ m_class = classInfo;
+ }
+
+ /**
+ * Enables all attributes, elements and content handling.
+ */
+ public void enableAll()
+ throws ConfigurationException
+ {
+ enableAttributes();
+ enableElements();
+ enableContent();
+ }
+
+ /**
+ * Enables all attributes.
+ */
+ public void enableAttributes()
+ throws ConfigurationException
+ {
+ // Find all 'set' methods which take a single parameter, and return void.
+ final List methods = new ArrayList();
+ findMethodsWithPrefix( "set", methods );
+ final Iterator iterator = methods.iterator();
+ while( iterator.hasNext() )
+ {
+ final Method method = (Method)iterator.next();
+ if( method.getReturnType() != Void.TYPE ||
+ method.getParameterTypes().length != 1 )
+ {
+ continue;
+ }
+
+ // Extract the attribute name
+ final String attrName = extractName( "set", method.getName() );
+
+ // Enable the attribute
+ enableAttribute( attrName, method );
+ }
+ }
+
+ /**
+ * Enables all elements.
+ */
+ public void enableElements()
+ throws ConfigurationException
+ {
+ final Map creators = findCreators();
+ final Map adders = findAdders();
+
+ // Add the elements
+ final Set elemNames = new HashSet();
+ elemNames.addAll( creators.keySet() );
+ elemNames.addAll( adders.keySet() );
+
+ final Iterator iterator = elemNames.iterator();
+ while( iterator.hasNext() )
+ {
+ final String elemName = (String)iterator.next();
+ final Method createMethod = (Method)creators.get( elemName );
+ final Method addMethod = (Method)adders.get( elemName );
+
+ // Determine and check the return type
+ Class type;
+ if( createMethod != null && addMethod != null )
+ {
+ // Make sure the add method is more general than the create
+ // method
+ type = createMethod.getReturnType();
+ final Class addType = addMethod.getParameterTypes()[ 0 ];
+ if( !addType.isAssignableFrom( type ) )
+ {
+ final String message =
+ REZ.getString( "incompatible-element-types.error",
+ elemName,
+ m_class.getName() );
+ throw new ConfigurationException( message );
+ }
+ }
+ else if( createMethod != null )
+ {
+ type = createMethod.getReturnType();
+ }
+ else
+ {
+ type = addMethod.getParameterTypes()[ 0 ];
+ }
+
+ final DefaultElementConfigurer configurer =
+ new DefaultElementConfigurer( type, createMethod, addMethod );
+ m_elements.put( elemName, configurer );
+ }
+ }
+
+ /**
+ * Locate all 'add' methods which return void, and take a non-primitive type
+ */
+ private Map findAdders()
+ throws ConfigurationException
+ {
+ final Map adders = new HashMap();
+ final List methodSet = new ArrayList();
+ findMethodsWithPrefix( "add", methodSet );
+
+ final Iterator iterator = methodSet.iterator();
+ while( iterator.hasNext() )
+ {
+ final Method method = (Method)iterator.next();
+ final String methodName = method.getName();
+ if( method.getReturnType() != Void.TYPE ||
+ method.getParameterTypes().length != 1 ||
+ method.getParameterTypes()[ 0 ].isPrimitive() )
+ {
+ continue;
+ }
+
+ // TODO - un-hard-code this
+ if( methodName.equals( "addContent" ) )
+ {
+ continue;
+ }
+
+ // Extract element name
+ final String elemName = extractName( "add", methodName );
+
+ // Add to the adders map
+ if( adders.containsKey( elemName ) )
+ {
+ final String message =
+ REZ.getString( "multiple-adder-methods-for-element.error",
+ m_class.getName(),
+ elemName );
+ throw new ConfigurationException( message );
+ }
+ adders.put( elemName, method );
+ }
+ return adders;
+ }
+
+ /**
+ * Find all 'create' methods, which return a non-primitive type,
+ * and take no parameters.
+ */
+ private Map findCreators()
+ throws ConfigurationException
+ {
+ final Map creators = new HashMap();
+ final List methodSet = new ArrayList();
+ findMethodsWithPrefix( "create", methodSet );
+
+ final Iterator iterator = methodSet.iterator();
+ while( iterator.hasNext() )
+ {
+ final Method method = (Method)iterator.next();
+ final String methodName = method.getName();
+ if( method.getReturnType().isPrimitive() ||
+ method.getParameterTypes().length != 0 )
+ {
+ continue;
+ }
+
+ // Extract element name
+ final String elemName = extractName( "create", methodName );
+
+ // Add to the creators map
+ if( creators.containsKey( elemName ) )
+ {
+ final String message =
+ REZ.getString( "multiple-creator-methods-for-element.error",
+ m_class.getName(),
+ elemName );
+ throw new ConfigurationException( message );
+ }
+ creators.put( elemName, method );
+ }
+ return creators;
+ }
+
+ /**
+ * Enables content.
+ */
+ public void enableContent()
+ throws ConfigurationException
+ {
+ // Locate any 'addContent' methods, which return void, and take
+ // a single parameter.
+ final Method[] methods = m_class.getMethods();
+ for( int i = 0; i < methods.length; i++ )
+ {
+ final Method method = methods[ i ];
+ final String methodName = method.getName();
+ if( Modifier.isStatic( method.getModifiers() ) ||
+ !methodName.equals( "addContent" ) ||
+ method.getReturnType() != Void.TYPE ||
+ method.getParameterTypes().length != 1 )
+ {
+ continue;
+ }
+
+ if( null != m_contentSetter )
+ {
+ final String message =
+ REZ.getString( "multiple-content-setter-methods.error", m_class.getName() );
+ throw new ConfigurationException( message );
+ }
+
+ m_contentSetter = new DefaultAttributeSetter( method );
+ }
+ }
+
+ /**
+ * Locates the configurer for a particular class.
+ */
+ public static ObjectConfigurer getConfigurer( final Class classInfo )
+ throws ConfigurationException
+ {
+ final DefaultObjectConfigurer configurer = new DefaultObjectConfigurer( classInfo );
+ configurer.enableAll();
+ return configurer;
+ }
+
+ /**
+ * Returns the class.
+ */
+ public Class getType()
+ {
+ return m_class;
+ }
+
+ /**
+ * Returns a configurer for an attribute of this class.
+ */
+ public AttributeSetter getAttributeSetter( final String name )
+ {
+ return (AttributeSetter)m_attrs.get( name );
+ }
+
+ /**
+ * Returns a configurer for an element of this class.
+ */
+ public ElementConfigurer getElement( final String name )
+ {
+ return (ElementConfigurer)m_elements.get( name );
+ }
+
+ /**
+ * Returns a configurer for the content of this class.
+ */
+ public AttributeSetter getContentSetter()
+ {
+ return m_contentSetter;
+ }
+
+ /**
+ * Enables an attribute.
+ */
+ private void enableAttribute( final String attrName,
+ final Method method )
+ throws ConfigurationException
+ {
+ if( m_attrs.containsKey( attrName ) )
+ {
+ final String message =
+ REZ.getString( "multiple-setter-methods-for-attribute.error",
+ m_class.getName(),
+ attrName );
+ throw new ConfigurationException( message );
+ }
+ final DefaultAttributeSetter setter = new DefaultAttributeSetter( method );
+ m_attrs.put( attrName, setter );
+ }
+
+ /**
+ * Extracts an attribute/element name from a Java method name.
+ * Removes the prefix, inserts '-' before each uppercase character
+ * (except the first), then converts all to lowercase.
+ */
+ private String extractName( final String prefix, final String methodName )
+ {
+ final StringBuffer sb = new StringBuffer( methodName );
+ sb.delete( 0, prefix.length() );
+ for( int i = 0; i < sb.length(); i++ )
+ {
+ char ch = sb.charAt( i );
+ if( Character.isUpperCase( ch ) )
+ {
+ if( i > 0 )
+ {
+ sb.insert( i, '-' );
+ i++;
+ }
+ sb.setCharAt( i, Character.toLowerCase( ch ) );
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Locates all non-static methods whose name starts with a particular
+ * prefix.
+ */
+ private void findMethodsWithPrefix( final String prefix, final Collection matches )
+ {
+ final int prefixLen = prefix.length();
+ final Method[] methods = m_class.getMethods();
+ for( int i = 0; i < methods.length; i++ )
+ {
+ final Method method = methods[ i ];
+ final String methodName = method.getName();
+ if( Modifier.isStatic( method.getModifiers() ) ||
+ methodName.length() <= prefixLen ||
+ !methodName.startsWith( prefix ) )
+ {
+ continue;
+ }
+
+ matches.add( method );
+ }
+ }
+}
diff --git a/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/ElementConfigurer.java b/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/ElementConfigurer.java
new file mode 100644
index 000000000..1cac23363
--- /dev/null
+++ b/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/ElementConfigurer.java
@@ -0,0 +1,47 @@
+/*
+ * 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.configurer;
+
+import org.apache.avalon.framework.configuration.ConfigurationException;
+
+/**
+ * Configures an element of an object.
+ *
+ * @author Adam Murdoch
+ * @version $Revision$ $Date$
+ */
+public interface ElementConfigurer
+{
+ /**
+ * Returns the type of the element.
+ */
+ Class getType();
+
+ /**
+ * Creates an object for an element.
+ *
+ * @param parent The parent object.
+ * @return An object which is assignable to the type returned by
+ * {@link #getType}.
+ * @throws ConfigurationException If the object cannot be created.
+ */
+ Object createElement( Object parent )
+ throws ConfigurationException;
+
+ /**
+ * Attaches an element object to its parent, after it has been configured.
+ *
+ * @param parent The parent object.
+ *
+ * @param child The element object. This must be assignable to the type
+ * returned by {@link #getType}.
+ * @throws ConfigurationException If the object cannot be attached.
+ */
+ void addElement( Object parent, Object child )
+ throws ConfigurationException;
+}
diff --git a/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/ObjectConfigurer.java b/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/ObjectConfigurer.java
new file mode 100644
index 000000000..945d90a73
--- /dev/null
+++ b/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/ObjectConfigurer.java
@@ -0,0 +1,48 @@
+/*
+ * 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.configurer;
+
+/**
+ * Configures objects of a particular class.
+ *
+ * @author Adam Murdoch
+ * @version $Revision$ $Date$
+ */
+public interface ObjectConfigurer
+{
+ /**
+ * Returns the class.
+ */
+ Class getType();
+
+ /**
+ * Returns a configurer for an attribute of this class.
+ *
+ * @param name The attribute name.
+ * @return A configurer for the attribute. Returns null if the attribute
+ * is not valid for this class.
+ */
+ AttributeSetter getAttributeSetter( String name );
+
+ /**
+ * Returns a configurer for an element of this class.
+ *
+ * @param name The element name.
+ * @return A configurer for the element. Returns null if the element
+ * is not valid for this class.
+ */
+ ElementConfigurer getElement( String name );
+
+ /**
+ * Returns a configurer for the content of this class.
+ *
+ * @return A configurer for the content. Returns null if the class does
+ * not allow text content.
+ */
+ AttributeSetter getContentSetter();
+}
diff --git a/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/Resources.properties b/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/Resources.properties
index ce049719f..750042a56 100644
--- a/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/Resources.properties
+++ b/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/Resources.properties
@@ -1,18 +1,20 @@
configuring-object.notice=Configuring {0}.
configurable.notice=Configuring object via Configurable interface.
-reflection.notice=Configuring object via Configurable reflection.
-configure-attribute.notice=Configuring attribute name={0} value={1}.
-configure-subelement.notice=Configuring subelement name={0}.
-configure-content.notice=Configuring content {0}.
+reflection.notice=Configuring object via ObjectConfigurer.
+configure-content.notice=Configuring content with "{0}".
+configure-subelement.notice=Configuring subelement "{0}".
+configure-attribute.notice=Configuring attribute name="{0}" value="{1}".
-reserved-attribute.error=Can not specify reserved attribute {0}.
-no-attribute-method.error=Unable to set attribute via {0} due to not finding any appropriate mutator method.
-bad-property-resolve.error=Error resolving property {0}.
-no-can-convert.error=Unable to set attribute via {0} as could not convert {1} to a matching type.
-no-converter.error=Failed to find converter.
-bad-convert-for-attribute.error=Error converting attribute for {0}.
-no-element-method.error=Unable to set element {0} due to not finding any appropriate mutator method.
-illegal-access.error=Error retrieving methods with correct access specifiers.
-invoke-target.error=Error calling method attribute {0}.
+content-not-supported.error=Text content is not supported for this element.
+bad-set-content.error=Could not set text content.
+unknown-subelement.error=Unknown element "{0}".
+bad-configure-subelement.error=Could not configure element "{0}".
+unknown-attribute.error=Unknown attribute "{0}".
+bad-set-attribute.error=Could not set attribute "{0}".
no-complex-type.error=Can not get complex type for non-primitive type {0}.
-subelement-create.error=Error creating sub-element.
+
+multiple-creator-methods-for-element.error=Multiple creator methods found in class {0} for element "{0}".
+multiple-adder-methods-for-element.error=Multiple adder methods found in class {0} for element "{0}".
+incompatible-element-types.error=Incompatible creator and adder types for element "{0}" of class {1}.
+multiple-content-setter-methods.error=Multiple content setter methods found in class {0}.
+multiple-setter-methods-for-attribute.error=Multiple setter methods found in class {0} for attribute "{1}".