* Added max multiplicity checking. Properties with a setter method can only be set once, whereas properties with an adder method can be set an unlimited number of times. * Resolves properties in reference ids. e.g <javac classpath-ref="${my-classpath-id-name}"/> * Ignores String adder and setter methods, if other methods exist. Longer term, the type should be able to specify exactly which method to use. * Moved all per-object state behind the ConfigurationState interface. The ObjectConfigurer is now responsible for state-based validation. * Tidied-up error messages. More context info is available in error messages, to make figuring out the problem easier. Error messages still need work. git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@270822 13f79535-47bb-0310-9956-ffa450edef68master
@@ -0,0 +1,22 @@ | |||||
/* | |||||
* 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; | |||||
/** | |||||
* A marker interface that represents the state of an object while it is being | |||||
* configured. | |||||
* | |||||
* @author Adam Murdoch | |||||
*/ | |||||
public interface ConfigurationState | |||||
{ | |||||
/** | |||||
* Returns the configurer being used to configure the object. | |||||
*/ | |||||
ObjectConfigurer getConfigurer(); | |||||
} |
@@ -0,0 +1,73 @@ | |||||
/* | |||||
* 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; | |||||
/** | |||||
* A default configuration state implementation. Keeps track of which | |||||
* of the object's properties have been set. Also keeps track of the | |||||
* objects created by the creator methods, but not yet set by the adder | |||||
* methods. | |||||
* | |||||
* @author Adam Murdoch | |||||
*/ | |||||
public class DefaultConfigurationState | |||||
implements ConfigurationState | |||||
{ | |||||
final private int[] m_propCount; | |||||
final private Object[] m_createdObjects; | |||||
final private ObjectConfigurer m_configurer; | |||||
final private Object m_object; | |||||
public DefaultConfigurationState( final ObjectConfigurer configurer, | |||||
final Object object, | |||||
final int numProps ) | |||||
{ | |||||
m_configurer = configurer; | |||||
m_object = object; | |||||
m_propCount = new int[ numProps ]; | |||||
m_createdObjects = new Object[ numProps ]; | |||||
} | |||||
/** | |||||
* Returns the configurer being used to configure the object. | |||||
*/ | |||||
public ObjectConfigurer getConfigurer() | |||||
{ | |||||
return m_configurer; | |||||
} | |||||
/** Returns the object being configured. */ | |||||
public Object getObject() | |||||
{ | |||||
return m_object; | |||||
} | |||||
/** Returns a property count. */ | |||||
public int getPropCount( final int propIndex ) | |||||
{ | |||||
return m_propCount[ propIndex ]; | |||||
} | |||||
/** Increments a property count. */ | |||||
public void incPropCount( final int propIndex ) | |||||
{ | |||||
m_propCount[ propIndex ]++; | |||||
} | |||||
/** Returns a property's pending objects. */ | |||||
public Object getCreatedObject( final int propIndex ) | |||||
{ | |||||
return m_createdObjects[ propIndex ]; | |||||
} | |||||
/** Sets a property's pending objects. */ | |||||
public void setCreatedObject( final int propIndex, final Object object ) | |||||
{ | |||||
m_createdObjects[ propIndex ] = object; | |||||
} | |||||
} |
@@ -7,11 +7,13 @@ | |||||
*/ | */ | ||||
package org.apache.myrmidon.components.configurer; | package org.apache.myrmidon.components.configurer; | ||||
import java.lang.reflect.InvocationTargetException; | |||||
import java.util.HashMap; | import java.util.HashMap; | ||||
import java.util.Map; | import java.util.Map; | ||||
import org.apache.avalon.excalibur.i18n.ResourceManager; | import org.apache.avalon.excalibur.i18n.ResourceManager; | ||||
import org.apache.avalon.excalibur.i18n.Resources; | import org.apache.avalon.excalibur.i18n.Resources; | ||||
import org.apache.avalon.excalibur.property.PropertyUtil; | import org.apache.avalon.excalibur.property.PropertyUtil; | ||||
import org.apache.avalon.framework.CascadingException; | |||||
import org.apache.avalon.framework.component.ComponentException; | import org.apache.avalon.framework.component.ComponentException; | ||||
import org.apache.avalon.framework.component.ComponentManager; | import org.apache.avalon.framework.component.ComponentManager; | ||||
import org.apache.avalon.framework.component.Composable; | import org.apache.avalon.framework.component.Composable; | ||||
@@ -37,9 +39,6 @@ public class DefaultConfigurer | |||||
private final static Resources REZ = | private final static Resources REZ = | ||||
ResourceManager.getPackageResources( DefaultConfigurer.class ); | ResourceManager.getPackageResources( DefaultConfigurer.class ); | ||||
///Compile time constant to turn on extreme debugging | |||||
private final static boolean DEBUG = false; | |||||
///Converter to use for converting between values | ///Converter to use for converting between values | ||||
private MasterConverter m_converter; | private MasterConverter m_converter; | ||||
@@ -71,50 +70,88 @@ public class DefaultConfigurer | |||||
final Context context ) | final Context context ) | ||||
throws ConfigurationException | throws ConfigurationException | ||||
{ | { | ||||
if( DEBUG ) | |||||
try | |||||
{ | |||||
configureObject( object, configuration, context ); | |||||
} | |||||
catch( InvocationTargetException ite ) | |||||
{ | { | ||||
final String message = REZ.getString( "configuring-object.notice", object ); | |||||
getLogger().debug( message ); | |||||
// A configuration exception thrown from a nested object. Unpack | |||||
// and re-throw | |||||
throw (ConfigurationException)ite.getTargetException(); | |||||
} | } | ||||
} | |||||
/** | |||||
* Does the work of configuring an object. | |||||
*/ | |||||
private void configureObject( final Object object, | |||||
final Configuration configuration, | |||||
final Context context ) | |||||
throws ConfigurationException, InvocationTargetException | |||||
{ | |||||
if( object instanceof Configurable ) | if( object instanceof Configurable ) | ||||
{ | { | ||||
if( DEBUG ) | |||||
{ | |||||
final String message = REZ.getString( "configurable.notice" ); | |||||
getLogger().debug( message ); | |||||
} | |||||
// Let the object configure itself | // Let the object configure itself | ||||
( (Configurable)object ).configure( configuration ); | ( (Configurable)object ).configure( configuration ); | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
if( DEBUG ) | |||||
{ | |||||
final String message = REZ.getString( "reflection.notice" ); | |||||
getLogger().debug( message ); | |||||
} | |||||
final String elemName = configuration.getName(); | |||||
// Locate the configurer for this object | // Locate the configurer for this object | ||||
final ObjectConfigurer configurer = getConfigurer( object.getClass() ); | final ObjectConfigurer configurer = getConfigurer( object.getClass() ); | ||||
// Start configuring this object | |||||
final ConfigurationState state = configurer.startConfiguration( object ); | |||||
// Set each of the attributes | // Set each of the attributes | ||||
final String[] attributes = configuration.getAttributeNames(); | final String[] attributes = configuration.getAttributeNames(); | ||||
for( int i = 0; i < attributes.length; i++ ) | for( int i = 0; i < attributes.length; i++ ) | ||||
{ | { | ||||
final String name = attributes[ i ]; | final String name = attributes[ i ]; | ||||
final String value = configuration.getAttribute( name ); | |||||
// Set the attribute | |||||
setAttribute( configurer, object, name, value, context ); | |||||
try | |||||
{ | |||||
// Set the attribute | |||||
final String value = configuration.getAttribute( name ); | |||||
setAttribute( state, name, value, context ); | |||||
} | |||||
catch( final NoSuchPropertyException nspe ) | |||||
{ | |||||
final String message = | |||||
REZ.getString( "no-such-attribute.error", elemName, name ); | |||||
throw new ConfigurationException( message, nspe ); | |||||
} | |||||
catch( final CascadingException ce ) | |||||
{ | |||||
final String message = | |||||
REZ.getString( "bad-set-attribute.error", elemName, name ); | |||||
throw new ConfigurationException( message, ce ); | |||||
} | |||||
} | } | ||||
// Set the text content | // Set the text content | ||||
final String content = configuration.getValue( null ); | final String content = configuration.getValue( null ); | ||||
if( null != content && content.length() > 0 ) | if( null != content && content.length() > 0 ) | ||||
{ | { | ||||
setContent( configurer, object, content, context ); | |||||
try | |||||
{ | |||||
// Set the content | |||||
final PropertyConfigurer contentConfigurer = state.getConfigurer().getContentConfigurer(); | |||||
setValue( contentConfigurer, state, content, context ); | |||||
} | |||||
catch( final NoSuchPropertyException nspe ) | |||||
{ | |||||
final String message = | |||||
REZ.getString( "no-content.error", elemName ); | |||||
throw new ConfigurationException( message, nspe ); | |||||
} | |||||
catch( final CascadingException ce ) | |||||
{ | |||||
final String message = | |||||
REZ.getString( "bad-set-content.error", elemName ); | |||||
throw new ConfigurationException( message, ce ); | |||||
} | |||||
} | } | ||||
// Create and configure each of the child elements | // Create and configure each of the child elements | ||||
@@ -122,8 +159,27 @@ public class DefaultConfigurer | |||||
for( int i = 0; i < children.length; i++ ) | for( int i = 0; i < children.length; i++ ) | ||||
{ | { | ||||
final Configuration childConfig = children[ i ]; | final Configuration childConfig = children[ i ]; | ||||
configureElement( configurer, object, childConfig, context ); | |||||
final String name = childConfig.getName(); | |||||
try | |||||
{ | |||||
configureElement( state, childConfig, context ); | |||||
} | |||||
catch( final NoSuchPropertyException nspe ) | |||||
{ | |||||
final String message = | |||||
REZ.getString( "no-such-element.error", elemName, name ); | |||||
throw new ConfigurationException( message, nspe ); | |||||
} | |||||
catch( final CascadingException ce ) | |||||
{ | |||||
final String message = | |||||
REZ.getString( "bad-set-element.error", name ); | |||||
throw new ConfigurationException( message, ce ); | |||||
} | |||||
} | } | ||||
// Finish configuring the object | |||||
configurer.finishConfiguration( state ); | |||||
} | } | ||||
} | } | ||||
@@ -147,121 +203,103 @@ public class DefaultConfigurer | |||||
// Locate the configurer for this object | // Locate the configurer for this object | ||||
final ObjectConfigurer configurer = getConfigurer( object.getClass() ); | final ObjectConfigurer configurer = getConfigurer( object.getClass() ); | ||||
// Set the attribute value | |||||
setAttribute( configurer, object, name, value, context ); | |||||
} | |||||
/** | |||||
* Sets the text content of an object. | |||||
*/ | |||||
private void setContent( final ObjectConfigurer configurer, | |||||
final Object object, | |||||
final String content, | |||||
final Context context ) | |||||
throws ConfigurationException | |||||
{ | |||||
if( DEBUG ) | |||||
{ | |||||
final String message = | |||||
REZ.getString( "configure-content.notice", content ); | |||||
getLogger().debug( message ); | |||||
} | |||||
// Set the content | |||||
final PropertyConfigurer contentConfigurer = configurer.getContentConfigurer(); | |||||
if( null == contentConfigurer ) | |||||
{ | |||||
final String message = REZ.getString( "content-not-supported.error" ); | |||||
throw new ConfigurationException( message ); | |||||
} | |||||
// TODO - this ain't right, the validation is going to be screwed up | |||||
final ConfigurationState state = configurer.startConfiguration( object ); | |||||
// Set the attribute value | |||||
try | try | ||||
{ | { | ||||
setValue( contentConfigurer, object, content, context ); | |||||
setAttribute( state, name, value, context ); | |||||
} | } | ||||
catch( final Exception e ) | |||||
catch( final CascadingException ce ) | |||||
{ | { | ||||
final String message = REZ.getString( "bad-set-content.error" ); | |||||
throw new ConfigurationException( message, e ); | |||||
final String message = | |||||
REZ.getString( "bad-set-class-attribute.error", | |||||
name, | |||||
object.getClass().getName() ); | |||||
throw new ConfigurationException( message, ce ); | |||||
} | } | ||||
// Finish up | |||||
configurer.finishConfiguration( state ); | |||||
} | } | ||||
/** | /** | ||||
* Configures a property from a nested element. | * Configures a property from a nested element. | ||||
*/ | */ | ||||
private void configureElement( final ObjectConfigurer configurer, | |||||
final Object object, | |||||
private void configureElement( final ConfigurationState state, | |||||
final Configuration element, | final Configuration element, | ||||
final Context context ) | final Context context ) | ||||
throws ConfigurationException | |||||
throws CascadingException, InvocationTargetException | |||||
{ | { | ||||
final String elementName = element.getName(); | final String elementName = element.getName(); | ||||
if( DEBUG ) | |||||
{ | |||||
final String message = | |||||
REZ.getString( "configure-subelement.notice", elementName ); | |||||
getLogger().debug( message ); | |||||
} | |||||
if( elementName.endsWith( "-ref" ) ) | |||||
if( elementName.toLowerCase().endsWith( "-ref" ) ) | |||||
{ | { | ||||
// A reference | // A reference | ||||
configureReference( configurer, object, element, context ); | |||||
configureReference( state, element, context ); | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
// An inline object | // An inline object | ||||
configureInline( configurer, object, element, context ); | |||||
configureInline( state, element, context ); | |||||
} | } | ||||
} | } | ||||
/** | /** | ||||
* Configure a property from an inline object. | * Configure a property from an inline object. | ||||
*/ | */ | ||||
private void configureInline( final ObjectConfigurer configurer, | |||||
final Object object, | |||||
private void configureInline( final ConfigurationState state, | |||||
final Configuration element, | final Configuration element, | ||||
final Context context ) | final Context context ) | ||||
throws ConfigurationException | |||||
throws CascadingException, InvocationTargetException | |||||
{ | { | ||||
final String elementName = element.getName(); | final String elementName = element.getName(); | ||||
// Locate the configurer for the child element | // Locate the configurer for the child element | ||||
final PropertyConfigurer childConfigurer = configurer.getProperty( elementName ); | |||||
if( null == childConfigurer ) | |||||
final PropertyConfigurer childConfigurer = state.getConfigurer().getProperty( elementName ); | |||||
// Create the child element | |||||
Object child = childConfigurer.createValue( state ); | |||||
if( child == null ) | |||||
{ | { | ||||
final String message = REZ.getString( "unknown-property.error", elementName ); | |||||
throw new ConfigurationException( message ); | |||||
// Create an instance using the default constructor | |||||
try | |||||
{ | |||||
child = childConfigurer.getType().newInstance(); | |||||
} | |||||
catch( final Exception e ) | |||||
{ | |||||
final String message = | |||||
REZ.getString( "create-object.error", | |||||
childConfigurer.getType().getName() ); | |||||
throw new ConfigurationException( message, e ); | |||||
} | |||||
} | } | ||||
// Configure the child element | |||||
try | try | ||||
{ | { | ||||
// Create the child element | |||||
final Object child = childConfigurer.createValue( object ); | |||||
// Configure the child element | |||||
configure( child, element, context ); | |||||
// Set the child element | |||||
childConfigurer.setValue( object, child ); | |||||
configureObject( child, element, context ); | |||||
} | } | ||||
catch( final ConfigurationException ce ) | catch( final ConfigurationException ce ) | ||||
{ | { | ||||
final String message = | |||||
REZ.getString( "bad-set-property.error", elementName ); | |||||
throw new ConfigurationException( message, ce ); | |||||
// Nasty hack-o-rama, used to get this exception up through | |||||
// the stack of doConfigure() calls. This is unpacked by the | |||||
// top-most configure() call, and rethrown. | |||||
throw new InvocationTargetException( ce ); | |||||
} | } | ||||
// Set the child element | |||||
childConfigurer.addValue( state, child ); | |||||
} | } | ||||
/** | /** | ||||
* Configures a property from a reference. | * Configures a property from a reference. | ||||
*/ | */ | ||||
private void configureReference( final ObjectConfigurer configurer, | |||||
final Object object, | |||||
private void configureReference( final ConfigurationState state, | |||||
final Configuration element, | final Configuration element, | ||||
final Context context ) | final Context context ) | ||||
throws ConfigurationException | |||||
throws CascadingException | |||||
{ | { | ||||
// Adjust the name | // Adjust the name | ||||
final String elementName = element.getName(); | final String elementName = element.getName(); | ||||
@@ -277,33 +315,23 @@ public class DefaultConfigurer | |||||
} | } | ||||
// Set the property | // Set the property | ||||
setReference( configurer, object, name, id, context ); | |||||
setReference( state, name, id, context ); | |||||
} | } | ||||
/** | /** | ||||
* Sets a property using a reference. | * Sets a property using a reference. | ||||
*/ | */ | ||||
private void setReference( final ObjectConfigurer configurer, | |||||
final Object object, | |||||
private void setReference( final ConfigurationState state, | |||||
final String name, | final String name, | ||||
final String id, | |||||
final String unresolvedId, | |||||
final Context context ) | final Context context ) | ||||
throws ConfigurationException | |||||
throws CascadingException | |||||
{ | { | ||||
// Locate the configurer for the child element | // Locate the configurer for the child element | ||||
final PropertyConfigurer childConfigurer = configurer.getProperty( name ); | |||||
if( null == childConfigurer ) | |||||
{ | |||||
final String message = REZ.getString( "unknown-property.error", name ); | |||||
throw new ConfigurationException( message ); | |||||
} | |||||
final PropertyConfigurer childConfigurer = state.getConfigurer().getProperty( name ); | |||||
// Check if the creator method must be used | |||||
if( childConfigurer.useCreator() ) | |||||
{ | |||||
final String message = REZ.getString( "must-be-element.error" ); | |||||
throw new ConfigurationException( message ); | |||||
} | |||||
// Resolve any props in the id | |||||
Object id = PropertyUtil.resolveProperty( unresolvedId, context, false ); | |||||
// Locate the referenced object | // Locate the referenced object | ||||
Object ref = null; | Object ref = null; | ||||
@@ -311,77 +339,45 @@ public class DefaultConfigurer | |||||
{ | { | ||||
ref = context.get( id ); | ref = context.get( id ); | ||||
} | } | ||||
catch( final ContextException ce ) | |||||
catch( final ContextException exc ) | |||||
{ | { | ||||
final String message = REZ.getString( "get-ref.error", id, name ); | |||||
throw new ConfigurationException( message, ce ); | |||||
final String message = REZ.getString( "get-ref.error", id ); | |||||
throw new ConfigurationException( message, exc ); | |||||
} | } | ||||
// Check the types | // Check the types | ||||
final Class type = childConfigurer.getType(); | final Class type = childConfigurer.getType(); | ||||
if( !type.isInstance( ref ) ) | if( !type.isInstance( ref ) ) | ||||
{ | { | ||||
final String message = REZ.getString( "mismatch-ref-types.error", id, name ); | |||||
final String message = REZ.getString( "mismatch-ref-types.error", id, type.getName(), ref.getClass().getName() ); | |||||
throw new ConfigurationException( message ); | throw new ConfigurationException( message ); | ||||
} | } | ||||
// Set the child element | // Set the child element | ||||
try | |||||
{ | |||||
childConfigurer.setValue( object, ref ); | |||||
} | |||||
catch( final ConfigurationException ce ) | |||||
{ | |||||
final String message = | |||||
REZ.getString( "bad-set-property.error", name ); | |||||
throw new ConfigurationException( message, ce ); | |||||
} | |||||
childConfigurer.addValue( state, ref ); | |||||
} | } | ||||
/** | /** | ||||
* Sets an attribute value. | * Sets an attribute value. | ||||
*/ | */ | ||||
private void setAttribute( final ObjectConfigurer configurer, | |||||
final Object object, | |||||
private void setAttribute( final ConfigurationState state, | |||||
final String name, | final String name, | ||||
final String value, | final String value, | ||||
final Context context ) | final Context context ) | ||||
throws ConfigurationException | |||||
throws CascadingException | |||||
{ | { | ||||
if( DEBUG ) | |||||
{ | |||||
final String message = REZ.getString( "configure-attribute.notice", | |||||
name, | |||||
value ); | |||||
getLogger().debug( message ); | |||||
} | |||||
if( name.endsWith( "-ref" ) ) | |||||
if( name.toLowerCase().endsWith( "-ref" ) ) | |||||
{ | { | ||||
// A reference | // A reference | ||||
final String refName = name.substring( 0, name.length() - 4 ); | final String refName = name.substring( 0, name.length() - 4 ); | ||||
setReference( configurer, object, refName, value, context ); | |||||
setReference( state, refName, value, context ); | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
// Locate the configurer for this attribute | |||||
final PropertyConfigurer propConfigurer = configurer.getProperty( name ); | |||||
if( null == propConfigurer ) | |||||
{ | |||||
final String message = REZ.getString( "unknown-property.error", name ); | |||||
throw new ConfigurationException( message ); | |||||
} | |||||
// Set the value | // Set the value | ||||
try | |||||
{ | |||||
setValue( propConfigurer, object, value, context ); | |||||
} | |||||
catch( final Exception e ) | |||||
{ | |||||
final String message = REZ.getString( "bad-set-property.error", name ); | |||||
throw new ConfigurationException( message, e ); | |||||
} | |||||
final PropertyConfigurer propConfigurer = | |||||
state.getConfigurer().getProperty( name ); | |||||
setValue( propConfigurer, state, value, context ); | |||||
} | } | ||||
} | } | ||||
@@ -389,18 +385,11 @@ public class DefaultConfigurer | |||||
* Sets an attribute value, or an element's text content. | * Sets an attribute value, or an element's text content. | ||||
*/ | */ | ||||
private void setValue( final PropertyConfigurer setter, | private void setValue( final PropertyConfigurer setter, | ||||
final Object object, | |||||
final ConfigurationState state, | |||||
final String value, | final String value, | ||||
final Context context ) | final Context context ) | ||||
throws Exception | |||||
throws CascadingException | |||||
{ | { | ||||
// Check if the creator method must be used | |||||
if( setter.useCreator() ) | |||||
{ | |||||
final String message = REZ.getString( "must-be-element.error" ); | |||||
throw new ConfigurationException( message ); | |||||
} | |||||
// Resolve property references in the attribute value | // Resolve property references in the attribute value | ||||
Object objValue = PropertyUtil.resolveProperty( value, context, false ); | Object objValue = PropertyUtil.resolveProperty( value, context, false ); | ||||
@@ -409,7 +398,7 @@ public class DefaultConfigurer | |||||
objValue = m_converter.convert( clazz, objValue, context ); | objValue = m_converter.convert( clazz, objValue, context ); | ||||
// Set the value | // Set the value | ||||
setter.setValue( object, objValue ); | |||||
setter.addValue( state, objValue ); | |||||
} | } | ||||
/** | /** | ||||
@@ -7,20 +7,19 @@ | |||||
*/ | */ | ||||
package org.apache.myrmidon.components.configurer; | package org.apache.myrmidon.components.configurer; | ||||
import org.apache.avalon.excalibur.i18n.ResourceManager; | |||||
import org.apache.avalon.excalibur.i18n.Resources; | |||||
import org.apache.avalon.framework.configuration.ConfigurationException; | |||||
import java.lang.reflect.Method; | import java.lang.reflect.Method; | ||||
import java.lang.reflect.Modifier; | import java.lang.reflect.Modifier; | ||||
import java.util.Map; | |||||
import java.util.ArrayList; | |||||
import java.util.Collection; | |||||
import java.util.HashMap; | import java.util.HashMap; | ||||
import java.util.Set; | |||||
import java.util.HashSet; | import java.util.HashSet; | ||||
import java.util.Iterator; | import java.util.Iterator; | ||||
import java.util.List; | import java.util.List; | ||||
import java.util.ArrayList; | |||||
import java.util.Collection; | |||||
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 properties | * An object configurer which uses reflection to determine the properties | ||||
@@ -42,6 +41,11 @@ public class DefaultObjectConfigurer | |||||
*/ | */ | ||||
private final Map m_props = new HashMap(); | private final Map m_props = new HashMap(); | ||||
/** | |||||
* All property configurers. | |||||
*/ | |||||
private final List m_allProps = new ArrayList(); | |||||
/** | /** | ||||
* Content configurer. | * Content configurer. | ||||
*/ | */ | ||||
@@ -101,8 +105,8 @@ public class DefaultObjectConfigurer | |||||
{ | { | ||||
final String message = | final String message = | ||||
REZ.getString( "incompatible-element-types.error", | REZ.getString( "incompatible-element-types.error", | ||||
propName, | |||||
m_class.getName() ); | |||||
m_class.getName(), | |||||
propName ); | |||||
throw new ConfigurationException( message ); | throw new ConfigurationException( message ); | ||||
} | } | ||||
} | } | ||||
@@ -115,9 +119,21 @@ public class DefaultObjectConfigurer | |||||
type = addMethod.getParameterTypes()[ 0 ]; | type = addMethod.getParameterTypes()[ 0 ]; | ||||
} | } | ||||
// Determine the max count for the property | |||||
int maxCount = Integer.MAX_VALUE; | |||||
if( addMethod != null && addMethod.getName().startsWith( "set" ) ) | |||||
{ | |||||
maxCount = 1; | |||||
} | |||||
final DefaultPropertyConfigurer configurer = | final DefaultPropertyConfigurer configurer = | ||||
new DefaultPropertyConfigurer( type, createMethod, addMethod ); | |||||
new DefaultPropertyConfigurer( m_allProps.size(), | |||||
type, | |||||
createMethod, | |||||
addMethod, | |||||
maxCount ); | |||||
m_props.put( propName, configurer ); | m_props.put( propName, configurer ); | ||||
m_allProps.add( configurer ); | |||||
} | } | ||||
} | } | ||||
@@ -138,8 +154,8 @@ public class DefaultObjectConfigurer | |||||
{ | { | ||||
final Method method = (Method)iterator.next(); | final Method method = (Method)iterator.next(); | ||||
final String methodName = method.getName(); | final String methodName = method.getName(); | ||||
if( method.getReturnType() != Void.TYPE || | |||||
method.getParameterTypes().length != 1 ) | |||||
if( method.getReturnType() != Void.TYPE | |||||
|| method.getParameterTypes().length != 1 ) | |||||
{ | { | ||||
continue; | continue; | ||||
} | } | ||||
@@ -150,19 +166,37 @@ public class DefaultObjectConfigurer | |||||
continue; | continue; | ||||
} | } | ||||
// Extract element name | |||||
final String elemName = extractName( 3, methodName ); | |||||
// Extract property name | |||||
final String propName = extractName( 3, methodName ); | |||||
final Class type = method.getParameterTypes()[ 0 ]; | |||||
// Add to the adders map | // Add to the adders map | ||||
if( adders.containsKey( elemName ) ) | |||||
if( adders.containsKey( propName ) ) | |||||
{ | { | ||||
final String message = | |||||
REZ.getString( "multiple-adder-methods-for-element.error", | |||||
m_class.getName(), | |||||
elemName ); | |||||
throw new ConfigurationException( message ); | |||||
final Class currentType = ( (Method)adders.get( propName ) ).getParameterTypes()[ 0 ]; | |||||
// Ditch the string version, if any | |||||
if( currentType != String.class && type == String.class ) | |||||
{ | |||||
// New type is string, and current type is not. Ignore | |||||
// the new method | |||||
continue; | |||||
} | |||||
if( currentType != String.class || type == String.class ) | |||||
{ | |||||
// Both are string, or both are not string | |||||
final String message = | |||||
REZ.getString( "multiple-adder-methods-for-element.error", | |||||
m_class.getName(), | |||||
propName ); | |||||
throw new ConfigurationException( message ); | |||||
} | |||||
// Else, current type is string, and new type is not, so | |||||
// continue below, and overwrite the current method | |||||
} | } | ||||
adders.put( elemName, method ); | |||||
adders.put( propName, method ); | |||||
} | } | ||||
return adders; | return adders; | ||||
} | } | ||||
@@ -235,8 +269,14 @@ public class DefaultObjectConfigurer | |||||
throw new ConfigurationException( message ); | throw new ConfigurationException( message ); | ||||
} | } | ||||
Class type = method.getParameterTypes()[0]; | |||||
m_contentConfigurer = new DefaultPropertyConfigurer( type, null, method ); | |||||
final Class type = method.getParameterTypes()[ 0 ]; | |||||
m_contentConfigurer = | |||||
new DefaultPropertyConfigurer( m_allProps.size(), | |||||
type, | |||||
null, | |||||
method, | |||||
1 ); | |||||
m_allProps.add( m_contentConfigurer ); | |||||
} | } | ||||
} | } | ||||
@@ -252,27 +292,64 @@ public class DefaultObjectConfigurer | |||||
} | } | ||||
/** | /** | ||||
* Returns the class. | |||||
* Starts the configuration of an object. | |||||
*/ | */ | ||||
public Class getType() | |||||
public ConfigurationState startConfiguration( Object object ) | |||||
throws ConfigurationException | |||||
{ | { | ||||
return m_class; | |||||
return new DefaultConfigurationState( this, object, m_allProps.size() ); | |||||
} | |||||
/** | |||||
* Finishes the configuration of an object, performing any final | |||||
* validation and type conversion. | |||||
*/ | |||||
public Object finishConfiguration( final ConfigurationState state ) | |||||
throws ConfigurationException | |||||
{ | |||||
// Make sure there are no pending created objects | |||||
final DefaultConfigurationState defState = (DefaultConfigurationState)state; | |||||
for( int i = 0; i < m_allProps.size(); i++ ) | |||||
{ | |||||
if( defState.getCreatedObject( i ) != null ) | |||||
{ | |||||
final String message = REZ.getString( "pending-property-value.error" ); | |||||
throw new ConfigurationException( message ); | |||||
} | |||||
} | |||||
return defState.getObject(); | |||||
} | } | ||||
/** | /** | ||||
* Returns a configurer for an element of this class. | * Returns a configurer for an element of this class. | ||||
*/ | */ | ||||
public PropertyConfigurer getProperty( final String name ) | |||||
public PropertyConfigurer getProperty( final String name ) throws NoSuchPropertyException | |||||
{ | { | ||||
return (PropertyConfigurer)m_props.get( name ); | |||||
final PropertyConfigurer prop = (PropertyConfigurer)m_props.get( name ); | |||||
if( prop != null ) | |||||
{ | |||||
return prop; | |||||
} | |||||
// Unknown property | |||||
final String message = REZ.getString( "unknown-property.error", m_class.getName(), name ); | |||||
throw new NoSuchPropertyException( message ); | |||||
} | } | ||||
/** | /** | ||||
* Returns a configurer for the content of this class. | * Returns a configurer for the content of this class. | ||||
*/ | */ | ||||
public PropertyConfigurer getContentConfigurer() | |||||
public PropertyConfigurer getContentConfigurer() throws NoSuchPropertyException | |||||
{ | { | ||||
return m_contentConfigurer; | |||||
if( m_contentConfigurer != null ) | |||||
{ | |||||
return m_contentConfigurer; | |||||
} | |||||
// Does not handle content | |||||
final String message = REZ.getString( "content-unsupported.error", m_class.getName() ); | |||||
throw new NoSuchPropertyException( message ); | |||||
} | } | ||||
/** | /** | ||||
@@ -7,13 +7,12 @@ | |||||
*/ | */ | ||||
package org.apache.myrmidon.components.configurer; | 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.ResourceManager; | ||||
import org.apache.avalon.excalibur.i18n.Resources; | import org.apache.avalon.excalibur.i18n.Resources; | ||||
import org.apache.avalon.framework.configuration.ConfigurationException; | import org.apache.avalon.framework.configuration.ConfigurationException; | ||||
import java.lang.reflect.InvocationTargetException; | |||||
import java.lang.reflect.Method; | |||||
/** | /** | ||||
* The default property configurer implementation, which uses reflection to | * The default property configurer implementation, which uses reflection to | ||||
* create and set property values. | * create and set property values. | ||||
@@ -27,21 +26,30 @@ class DefaultPropertyConfigurer | |||||
private final static Resources REZ = | private final static Resources REZ = | ||||
ResourceManager.getPackageResources( DefaultPropertyConfigurer.class ); | ResourceManager.getPackageResources( DefaultPropertyConfigurer.class ); | ||||
private final int m_propIndex; | |||||
private final Class m_type; | private final Class m_type; | ||||
private final Method m_createMethod; | private final Method m_createMethod; | ||||
private final Method m_addMethod; | private final Method m_addMethod; | ||||
private final int m_maxCount; | |||||
public DefaultPropertyConfigurer( Class type, | |||||
Method createMethod, | |||||
Method addMethod ) | |||||
public DefaultPropertyConfigurer( final int propIndex, | |||||
final Class type, | |||||
final Method createMethod, | |||||
final Method addMethod, | |||||
final int maxCount ) | |||||
{ | { | ||||
m_propIndex = propIndex; | |||||
if ( type.isPrimitive() ) | if ( type.isPrimitive() ) | ||||
{ | { | ||||
type = getComplexTypeFor(type); | |||||
m_type = getComplexTypeFor(type); | |||||
} | |||||
else | |||||
{ | |||||
m_type = type; | |||||
} | } | ||||
m_type = type; | |||||
m_createMethod = createMethod; | m_createMethod = createMethod; | ||||
m_addMethod = addMethod; | m_addMethod = addMethod; | ||||
m_maxCount = maxCount; | |||||
} | } | ||||
/** | /** | ||||
@@ -53,29 +61,31 @@ class DefaultPropertyConfigurer | |||||
} | } | ||||
/** | /** | ||||
* Determines if the property value must be created via {@link #createValue}. | |||||
*/ | |||||
public boolean useCreator() | |||||
{ | |||||
return (m_createMethod != null); | |||||
} | |||||
/** | |||||
* Creates a nested element. | |||||
* Creates a default value for this property. | |||||
*/ | */ | ||||
public Object createValue( final Object parent ) | |||||
public Object createValue( ConfigurationState state ) | |||||
throws ConfigurationException | throws ConfigurationException | ||||
{ | { | ||||
if( null == m_createMethod ) | |||||
{ | |||||
return null; | |||||
} | |||||
final DefaultConfigurationState defState = (DefaultConfigurationState)state; | |||||
// Make sure there isn't a pending object for this property | |||||
if( defState.getCreatedObject( m_propIndex ) != null ) | |||||
{ | |||||
final String message = REZ.getString( "pending-property-value.error" ); | |||||
throw new ConfigurationException( message ); | |||||
} | |||||
try | try | ||||
{ | { | ||||
if( null != m_createMethod ) | |||||
{ | |||||
return m_createMethod.invoke( parent, null ); | |||||
} | |||||
else | |||||
{ | |||||
return m_type.newInstance(); | |||||
} | |||||
// Create the value | |||||
final Object object = m_createMethod.invoke( defState.getObject(), null ); | |||||
defState.setCreatedObject( m_propIndex, object ); | |||||
return object; | |||||
} | } | ||||
catch( final InvocationTargetException ite ) | catch( final InvocationTargetException ite ) | ||||
{ | { | ||||
@@ -89,16 +99,42 @@ class DefaultPropertyConfigurer | |||||
} | } | ||||
/** | /** | ||||
* Sets the nested element, after it has been configured. | |||||
* Adds a value for this property, to an object. | |||||
*/ | */ | ||||
public void setValue( final Object parent, final Object child ) | |||||
public void addValue( ConfigurationState state, Object value ) | |||||
throws ConfigurationException | throws ConfigurationException | ||||
{ | { | ||||
final DefaultConfigurationState defState = (DefaultConfigurationState)state; | |||||
// Make sure the supplied object is the pending object | |||||
final Object pending = defState.getCreatedObject( m_propIndex ); | |||||
if( pending != null && pending != value ) | |||||
{ | |||||
} | |||||
// Make sure the creator method was called, if necessary | |||||
if( pending == null && m_createMethod != null ) | |||||
{ | |||||
final String message = REZ.getString( "must-be-element.error" ); | |||||
throw new ConfigurationException( message ); | |||||
} | |||||
defState.setCreatedObject( m_propIndex, null ); | |||||
// Check the property count | |||||
if( defState.getPropCount( m_propIndex ) >= m_maxCount ) | |||||
{ | |||||
final String message = REZ.getString( "too-many-values.error" ); | |||||
throw new ConfigurationException( message ); | |||||
} | |||||
defState.incPropCount( m_propIndex ); | |||||
try | try | ||||
{ | { | ||||
// Add the value | |||||
if( null != m_addMethod ) | if( null != m_addMethod ) | ||||
{ | { | ||||
m_addMethod.invoke( parent, new Object[]{child} ); | |||||
m_addMethod.invoke( defState.getObject(), new Object[]{ value } ); | |||||
} | } | ||||
} | } | ||||
catch( final InvocationTargetException ite ) | catch( final InvocationTargetException ite ) | ||||
@@ -0,0 +1,26 @@ | |||||
/* | |||||
* 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.CascadingException; | |||||
/** | |||||
* An exception thrown when an unknown property is encountered. | |||||
* | |||||
* TODO - this should extend ConfigurationException, however | |||||
* ConfigurationException is final. | |||||
* | |||||
* @author Adam Murdoch | |||||
*/ | |||||
public class NoSuchPropertyException extends CascadingException | |||||
{ | |||||
public NoSuchPropertyException( String message ) | |||||
{ | |||||
super( message ); | |||||
} | |||||
} |
@@ -7,6 +7,8 @@ | |||||
*/ | */ | ||||
package org.apache.myrmidon.components.configurer; | package org.apache.myrmidon.components.configurer; | ||||
import org.apache.avalon.framework.configuration.ConfigurationException; | |||||
/** | /** | ||||
* Configures objects of a particular class. | * Configures objects of a particular class. | ||||
* | * | ||||
@@ -16,24 +18,44 @@ package org.apache.myrmidon.components.configurer; | |||||
public interface ObjectConfigurer | public interface ObjectConfigurer | ||||
{ | { | ||||
/** | /** | ||||
* Returns the class. | |||||
* Starts the configuration of an object. | |||||
* | |||||
* @param object The object about to be configured. | |||||
* @return The state object, used to track type-specific state during | |||||
* configuration. | |||||
* @throws ConfigurationException On error starting the configuration. | |||||
*/ | |||||
ConfigurationState startConfiguration( Object object ) | |||||
throws ConfigurationException; | |||||
/** | |||||
* Finishes the configuration of an object, performing any final | |||||
* validation and type conversion. | |||||
* | |||||
* @param state The state object. | |||||
* @return The configured object. | |||||
* @throws ConfigurationException On error finishing the configurtion. | |||||
*/ | */ | ||||
Class getType(); | |||||
Object finishConfiguration( ConfigurationState state ) | |||||
throws ConfigurationException; | |||||
/** | /** | ||||
* Returns a configurer for a property of this class. | * Returns a configurer for a property of this class. | ||||
* | * | ||||
* @param name The element name. | |||||
* @return A configurer for the property. Returns null if the property | |||||
* is not valid for this class. | |||||
* @param name The element name. Property names are case-insensitive. | |||||
* @return A configurer for the property. | |||||
* @throws NoSuchPropertyException If the property is not valid for this | |||||
* class | |||||
*/ | */ | ||||
PropertyConfigurer getProperty( String name ); | |||||
PropertyConfigurer getProperty( String name ) | |||||
throws NoSuchPropertyException; | |||||
/** | /** | ||||
* Returns a configurer for the content of this class. | * 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. | |||||
* @return A configurer for the content. | |||||
* @throws NoSuchPropertyException If the class does not handle content. | |||||
*/ | */ | ||||
PropertyConfigurer getContentConfigurer(); | |||||
PropertyConfigurer getContentConfigurer() | |||||
throws NoSuchPropertyException; | |||||
} | } |
@@ -19,36 +19,32 @@ import org.apache.avalon.framework.configuration.ConfigurationException; | |||||
public interface PropertyConfigurer | public interface PropertyConfigurer | ||||
{ | { | ||||
/** | /** | ||||
* Returns the type of the property. | |||||
* Returns the type of this property. | |||||
*/ | */ | ||||
Class getType(); | Class getType(); | ||||
/** | /** | ||||
* Determines if the property value must be created via {@link #createValue}. | |||||
*/ | |||||
boolean useCreator(); | |||||
/** | |||||
* Creates a default value for the property. This value must be configured, | |||||
* Creates a default value for this property. This value must be configured, | |||||
* and then attached to the object using {@link #setValue}. This | * and then attached to the object using {@link #setValue}. This | ||||
* method must be called if {@link #useCreator} returns true. | * method must be called if {@link #useCreator} returns true. | ||||
* | * | ||||
* @param parent The parent object. | |||||
* @param state The state object, representing the object being configured. | |||||
* @return An object which is assignable to the type returned by | * @return An object which is assignable to the type returned by | ||||
* {@link #getType}. | |||||
* {@link #getType}. Returns null if this property does not | |||||
* need a default value. | |||||
* @throws ConfigurationException If the object cannot be created. | * @throws ConfigurationException If the object cannot be created. | ||||
*/ | */ | ||||
Object createValue( Object parent ) | |||||
Object createValue( ConfigurationState state ) | |||||
throws ConfigurationException; | throws ConfigurationException; | ||||
/** | /** | ||||
* Sets a property value for an object. | |||||
* Adds a value for this property, to an object. | |||||
* | * | ||||
* @param object The object to set the property of. | |||||
* @param state The state object, representing the object being configured. | |||||
* @param value The property value. This must be assignable to the type | * @param value The property value. This must be assignable to the type | ||||
* returned by {@link #getType}. | * returned by {@link #getType}. | ||||
* @throws ConfigurationException If the property cannot be set. | * @throws ConfigurationException If the property cannot be set. | ||||
*/ | */ | ||||
void setValue( Object object, Object value ) | |||||
void addValue( ConfigurationState state, Object value ) | |||||
throws ConfigurationException; | throws ConfigurationException; | ||||
} | } |
@@ -1,20 +1,21 @@ | |||||
configuring-object.notice=Configuring {0}. | |||||
configurable.notice=Configuring object via Configurable interface. | |||||
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}". | |||||
content-not-supported.error=Text content is not supported for this element. | |||||
bad-set-content.error=Could not set text content. | |||||
unknown-property.error=Unknown property "{0}". | |||||
bad-set-property.error=Could not set property "{0}". | |||||
no-complex-type.error=Can not get complex type for non-primitive type {0}. | |||||
create-object.error=Could not create an object of class {0}. | |||||
extra-config-for-ref.error=A reference element can only include an "id" attribute. | extra-config-for-ref.error=A reference element can only include an "id" attribute. | ||||
get-ref.error=Could not locate reference "{0}" for element "{1}". | |||||
mismatch-ref-types.error=Mismatched type for reference "{0}" for element "{1}". | |||||
multiple-creator-methods-for-element.error=Multiple creator methods found in class {0} for property "{0}". | |||||
multiple-adder-methods-for-element.error=Multiple adder/setter methods found in class {0} for property "{0}". | |||||
incompatible-element-types.error=Incompatible creator and adder/setter types for property "{0}" of class {1}. | |||||
get-ref.error=Could not locate reference "{0}". | |||||
mismatch-ref-types.error=Mismatched type for reference "{0}". Was expecting an object of type {1}, instead found an object of type {2}. | |||||
incompatible-element-types.error=Incompatible creator and adder/setter methods found in class {0} for property "{1}". | |||||
multiple-adder-methods-for-element.error=Multiple adder/setter methods found in class {0} for property "{1}". | |||||
multiple-creator-methods-for-element.error=Multiple creator methods found in class {0} for property "{1}". | |||||
multiple-content-setter-methods.error=Multiple content setter methods found in class {0}. | multiple-content-setter-methods.error=Multiple content setter methods found in class {0}. | ||||
must-be-element.error=This property must be configured using a nested element. | |||||
pending-property-value.error=An object created using the creator method has not been set using the adder/setter method. | |||||
unknown-property.error=Class {0} does not have a "{1}" property. | |||||
content-not-supported.error=Class {0} does not support text content. | |||||
must-be-element.error=This property must be configured using a nested element. | |||||
too-many-values.error=Too many values for this property. | |||||
no-complex-type.error=Can not get complex type for non-primitive type {0}. | |||||
no-such-attribute.error=Attribute "{1}" is not allowed for element <{0}>. | |||||
bad-set-attribute.error=Could not set attribute "{1}" for element <{0}>. | |||||
bad-set-class-attribute.error=Could not set attribute "{0}" for object of class {1}. | |||||
no-such-element.error=Nested <{1}> elements are not allowed for element <{0}>. | |||||
bad-set-element.error=Could not handle element <{1}>, nested in element <{0}>. | |||||
no-content.error=Text content is not allowed for element <{0}>. | |||||
bad-set-content.error=Could not set text content for element <{0}>. |