* 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-ffa450edef68
master
| @@ -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; | |||
| import java.lang.reflect.InvocationTargetException; | |||
| 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.PropertyUtil; | |||
| import org.apache.avalon.framework.CascadingException; | |||
| import org.apache.avalon.framework.component.ComponentException; | |||
| import org.apache.avalon.framework.component.ComponentManager; | |||
| import org.apache.avalon.framework.component.Composable; | |||
| @@ -37,9 +39,6 @@ public class DefaultConfigurer | |||
| private final static Resources REZ = | |||
| 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 | |||
| private MasterConverter m_converter; | |||
| @@ -71,50 +70,88 @@ public class DefaultConfigurer | |||
| final Context context ) | |||
| 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( DEBUG ) | |||
| { | |||
| final String message = REZ.getString( "configurable.notice" ); | |||
| getLogger().debug( message ); | |||
| } | |||
| // Let the object configure itself | |||
| ( (Configurable)object ).configure( configuration ); | |||
| } | |||
| else | |||
| { | |||
| if( DEBUG ) | |||
| { | |||
| final String message = REZ.getString( "reflection.notice" ); | |||
| getLogger().debug( message ); | |||
| } | |||
| final String elemName = configuration.getName(); | |||
| // Locate the configurer for this object | |||
| final ObjectConfigurer configurer = getConfigurer( object.getClass() ); | |||
| // Start configuring this object | |||
| final ConfigurationState state = configurer.startConfiguration( object ); | |||
| // 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 ); | |||
| // 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 | |||
| final String content = configuration.getValue( null ); | |||
| 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 | |||
| @@ -122,8 +159,27 @@ public class DefaultConfigurer | |||
| for( int i = 0; i < children.length; 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 | |||
| 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 | |||
| { | |||
| 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. | |||
| */ | |||
| private void configureElement( final ObjectConfigurer configurer, | |||
| final Object object, | |||
| private void configureElement( final ConfigurationState state, | |||
| final Configuration element, | |||
| final Context context ) | |||
| throws ConfigurationException | |||
| throws CascadingException, InvocationTargetException | |||
| { | |||
| 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 | |||
| configureReference( configurer, object, element, context ); | |||
| configureReference( state, element, context ); | |||
| } | |||
| else | |||
| { | |||
| // An inline object | |||
| configureInline( configurer, object, element, context ); | |||
| configureInline( state, element, context ); | |||
| } | |||
| } | |||
| /** | |||
| * 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 Context context ) | |||
| throws ConfigurationException | |||
| throws CascadingException, InvocationTargetException | |||
| { | |||
| final String elementName = element.getName(); | |||
| // 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 | |||
| { | |||
| // 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 ) | |||
| { | |||
| 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. | |||
| */ | |||
| private void configureReference( final ObjectConfigurer configurer, | |||
| final Object object, | |||
| private void configureReference( final ConfigurationState state, | |||
| final Configuration element, | |||
| final Context context ) | |||
| throws ConfigurationException | |||
| throws CascadingException | |||
| { | |||
| // Adjust the name | |||
| final String elementName = element.getName(); | |||
| @@ -277,33 +315,23 @@ public class DefaultConfigurer | |||
| } | |||
| // Set the property | |||
| setReference( configurer, object, name, id, context ); | |||
| setReference( state, name, id, context ); | |||
| } | |||
| /** | |||
| * 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 id, | |||
| final String unresolvedId, | |||
| final Context context ) | |||
| throws ConfigurationException | |||
| throws CascadingException | |||
| { | |||
| // 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 | |||
| Object ref = null; | |||
| @@ -311,77 +339,45 @@ public class DefaultConfigurer | |||
| { | |||
| 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 | |||
| final Class type = childConfigurer.getType(); | |||
| 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 ); | |||
| } | |||
| // 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. | |||
| */ | |||
| private void setAttribute( final ObjectConfigurer configurer, | |||
| final Object object, | |||
| private void setAttribute( final ConfigurationState state, | |||
| final String name, | |||
| final String value, | |||
| 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 | |||
| final String refName = name.substring( 0, name.length() - 4 ); | |||
| setReference( configurer, object, refName, value, context ); | |||
| setReference( state, refName, value, context ); | |||
| } | |||
| 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 | |||
| 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. | |||
| */ | |||
| private void setValue( final PropertyConfigurer setter, | |||
| final Object object, | |||
| final ConfigurationState state, | |||
| final String value, | |||
| 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 | |||
| Object objValue = PropertyUtil.resolveProperty( value, context, false ); | |||
| @@ -409,7 +398,7 @@ public class DefaultConfigurer | |||
| objValue = m_converter.convert( clazz, objValue, context ); | |||
| // Set the value | |||
| setter.setValue( object, objValue ); | |||
| setter.addValue( state, objValue ); | |||
| } | |||
| /** | |||
| @@ -7,20 +7,19 @@ | |||
| */ | |||
| 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.Modifier; | |||
| import java.util.Map; | |||
| import java.util.ArrayList; | |||
| import java.util.Collection; | |||
| import java.util.HashMap; | |||
| import java.util.Set; | |||
| import java.util.HashSet; | |||
| import java.util.Iterator; | |||
| 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 | |||
| @@ -42,6 +41,11 @@ public class DefaultObjectConfigurer | |||
| */ | |||
| private final Map m_props = new HashMap(); | |||
| /** | |||
| * All property configurers. | |||
| */ | |||
| private final List m_allProps = new ArrayList(); | |||
| /** | |||
| * Content configurer. | |||
| */ | |||
| @@ -101,8 +105,8 @@ public class DefaultObjectConfigurer | |||
| { | |||
| final String message = | |||
| REZ.getString( "incompatible-element-types.error", | |||
| propName, | |||
| m_class.getName() ); | |||
| m_class.getName(), | |||
| propName ); | |||
| throw new ConfigurationException( message ); | |||
| } | |||
| } | |||
| @@ -115,9 +119,21 @@ public class DefaultObjectConfigurer | |||
| 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 = | |||
| new DefaultPropertyConfigurer( type, createMethod, addMethod ); | |||
| new DefaultPropertyConfigurer( m_allProps.size(), | |||
| type, | |||
| createMethod, | |||
| addMethod, | |||
| maxCount ); | |||
| m_props.put( propName, configurer ); | |||
| m_allProps.add( configurer ); | |||
| } | |||
| } | |||
| @@ -138,8 +154,8 @@ public class DefaultObjectConfigurer | |||
| { | |||
| final Method method = (Method)iterator.next(); | |||
| final String methodName = method.getName(); | |||
| if( method.getReturnType() != Void.TYPE || | |||
| method.getParameterTypes().length != 1 ) | |||
| if( method.getReturnType() != Void.TYPE | |||
| || method.getParameterTypes().length != 1 ) | |||
| { | |||
| continue; | |||
| } | |||
| @@ -150,19 +166,37 @@ public class DefaultObjectConfigurer | |||
| 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 | |||
| 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; | |||
| } | |||
| @@ -235,8 +269,14 @@ public class DefaultObjectConfigurer | |||
| 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. | |||
| */ | |||
| 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. | |||
| */ | |||
| 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; | |||
| 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; | |||
| import java.lang.reflect.InvocationTargetException; | |||
| import java.lang.reflect.Method; | |||
| /** | |||
| * The default property configurer implementation, which uses reflection to | |||
| * create and set property values. | |||
| @@ -27,21 +26,30 @@ class DefaultPropertyConfigurer | |||
| private final static Resources REZ = | |||
| ResourceManager.getPackageResources( DefaultPropertyConfigurer.class ); | |||
| private final int m_propIndex; | |||
| private final Class m_type; | |||
| private final Method m_createMethod; | |||
| 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() ) | |||
| { | |||
| type = getComplexTypeFor(type); | |||
| m_type = getComplexTypeFor(type); | |||
| } | |||
| else | |||
| { | |||
| m_type = type; | |||
| } | |||
| m_type = type; | |||
| m_createMethod = createMethod; | |||
| 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 | |||
| { | |||
| 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 | |||
| { | |||
| 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 ) | |||
| { | |||
| @@ -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 | |||
| { | |||
| 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 | |||
| { | |||
| // Add the value | |||
| if( null != m_addMethod ) | |||
| { | |||
| m_addMethod.invoke( parent, new Object[]{child} ); | |||
| m_addMethod.invoke( defState.getObject(), new Object[]{ value } ); | |||
| } | |||
| } | |||
| 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; | |||
| import org.apache.avalon.framework.configuration.ConfigurationException; | |||
| /** | |||
| * Configures objects of a particular class. | |||
| * | |||
| @@ -16,24 +18,44 @@ package org.apache.myrmidon.components.configurer; | |||
| 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. | |||
| * | |||
| * @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. | |||
| * | |||
| * @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 | |||
| { | |||
| /** | |||
| * Returns the type of the property. | |||
| * Returns the type of this property. | |||
| */ | |||
| 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 | |||
| * 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 | |||
| * {@link #getType}. | |||
| * {@link #getType}. Returns null if this property does not | |||
| * need a default value. | |||
| * @throws ConfigurationException If the object cannot be created. | |||
| */ | |||
| Object createValue( Object parent ) | |||
| Object createValue( ConfigurationState state ) | |||
| 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 | |||
| * returned by {@link #getType}. | |||
| * @throws ConfigurationException If the property cannot be set. | |||
| */ | |||
| void setValue( Object object, Object value ) | |||
| void addValue( ConfigurationState state, Object value ) | |||
| 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. | |||
| 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}. | |||
| 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}>. | |||