diff --git a/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/ConfigurationState.java b/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/ConfigurationState.java new file mode 100644 index 000000000..cac90958c --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/ConfigurationState.java @@ -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(); +} diff --git a/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/DefaultConfigurationState.java b/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/DefaultConfigurationState.java new file mode 100644 index 000000000..c2c35d0f1 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/DefaultConfigurationState.java @@ -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; + } +} diff --git a/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/DefaultConfigurer.java b/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/DefaultConfigurer.java index e74989820..4203b7bd8 100644 --- a/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/DefaultConfigurer.java +++ b/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/DefaultConfigurer.java @@ -7,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 ); } /** diff --git a/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/DefaultObjectConfigurer.java b/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/DefaultObjectConfigurer.java index c4cf8306d..947084982 100644 --- a/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/DefaultObjectConfigurer.java +++ b/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/DefaultObjectConfigurer.java @@ -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 ); } /** diff --git a/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/DefaultPropertyConfigurer.java b/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/DefaultPropertyConfigurer.java index 55e44bcee..77e94f771 100644 --- a/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/DefaultPropertyConfigurer.java +++ b/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/DefaultPropertyConfigurer.java @@ -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 ) diff --git a/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/NoSuchPropertyException.java b/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/NoSuchPropertyException.java new file mode 100644 index 000000000..d52e3a627 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/NoSuchPropertyException.java @@ -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 ); + } +} diff --git a/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/ObjectConfigurer.java b/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/ObjectConfigurer.java index 02c25d265..539ef9589 100644 --- a/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/ObjectConfigurer.java +++ b/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/ObjectConfigurer.java @@ -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; } diff --git a/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/PropertyConfigurer.java b/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/PropertyConfigurer.java index 2879d726e..4e8db70be 100644 --- a/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/PropertyConfigurer.java +++ b/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/PropertyConfigurer.java @@ -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; } diff --git a/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/Resources.properties b/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/Resources.properties index 56f9859d6..2584209f5 100644 --- a/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/Resources.properties +++ b/proposal/myrmidon/src/java/org/apache/myrmidon/components/configurer/Resources.properties @@ -1,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. \ No newline at end of file +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}>.