Browse Source

Refactor the Configurer so that it is much much faster and caches the reflection information. Make it much easier to support pluggable configurors in the future and alternative configuration mechanisms.

Submitted by: "Adam Murdoch" <adammurdoch_ml@yahoo.com>


git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@270491 13f79535-47bb-0310-9956-ffa450edef68
master
Peter Donald 23 years ago
parent
commit
010a31e844
8 changed files with 860 additions and 339 deletions
  1. +35
    -0
      proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/AttributeSetter.java
  2. +66
    -0
      proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/DefaultAttributeSetter.java
  3. +167
    -325
      proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/DefaultConfigurer.java
  4. +101
    -0
      proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/DefaultElementConfigurer.java
  5. +380
    -0
      proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/DefaultObjectConfigurer.java
  6. +47
    -0
      proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/ElementConfigurer.java
  7. +48
    -0
      proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/ObjectConfigurer.java
  8. +16
    -14
      proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/Resources.properties

+ 35
- 0
proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/AttributeSetter.java View File

@@ -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 <a href="mailto:adammurdoch_ml@yahoo.com">Adam Murdoch</a>
* @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;
}

+ 66
- 0
proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/DefaultAttributeSetter.java View File

@@ -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 <a href="mailto:adammurdoch_ml@yahoo.com">Adam Murdoch</a>
* @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 );
}
}
}

+ 167
- 325
proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/DefaultConfigurer.java View File

@@ -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 );
}
}
}

+ 101
- 0
proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/DefaultElementConfigurer.java View File

@@ -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 <a href="mailto:adammurdoch_ml@yahoo.com">Adam Murdoch</a>
* @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 );
}
}
}

+ 380
- 0
proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/DefaultObjectConfigurer.java View File

@@ -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 <a href="mailto:adammurdoch_ml@yahoo.com">Adam Murdoch</a>
* @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 <code>enable</code> 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 );
}
}
}

+ 47
- 0
proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/ElementConfigurer.java View File

@@ -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 <a href="mailto:adammurdoch_ml@yahoo.com">Adam Murdoch</a>
* @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;
}

+ 48
- 0
proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/ObjectConfigurer.java View File

@@ -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 <a href="mailto:adammurdoch_ml@yahoo.com">Adam Murdoch</a>
* @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();
}

+ 16
- 14
proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/Resources.properties View File

@@ -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}".

Loading…
Cancel
Save