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-ffa450edef68master
@@ -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; | |||
} |
@@ -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 ); | |||
} | |||
} | |||
} |
@@ -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 ); | |||
} | |||
} | |||
} |
@@ -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 ); | |||
} | |||
} | |||
} |
@@ -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 ); | |||
} | |||
} | |||
} |
@@ -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; | |||
} |
@@ -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(); | |||
} |
@@ -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}". |