* 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; | |||
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}>. |