blob: 4c9b963863be2b52a1c580616abb6f3f20b63007 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010-present Sonatype, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Stuart McCulloch (Sonatype, Inc.) - initial API and implementation
*******************************************************************************/
package org.eclipse.sisu.plexus;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.security.AccessController;
import java.security.PrivilegedAction;
import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
import org.codehaus.plexus.component.configurator.ConfigurationListener;
import org.codehaus.plexus.component.configurator.converters.ConfigurationConverter;
import org.codehaus.plexus.component.configurator.converters.ParameterizedConfigurationConverter;
import org.codehaus.plexus.component.configurator.converters.lookup.ConverterLookup;
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
import org.codehaus.plexus.configuration.PlexusConfiguration;
import org.eclipse.sisu.bean.DeclaredMembers;
import org.eclipse.sisu.bean.DeclaredMembers.View;
import com.google.inject.TypeLiteral;
/**
* Helper class that implements low-level Plexus configuration of composite beans.
*/
public final class CompositeBeanHelper
{
// ----------------------------------------------------------------------
// Implementation fields
// ----------------------------------------------------------------------
private final ConverterLookup lookup;
private final ClassLoader loader;
private final ExpressionEvaluator evaluator;
private final ConfigurationListener listener;
// ----------------------------------------------------------------------
// Constructors
// ----------------------------------------------------------------------
public CompositeBeanHelper( final ConverterLookup lookup, final ClassLoader loader,
final ExpressionEvaluator evaluator, final ConfigurationListener listener )
{
this.lookup = lookup;
this.loader = loader;
this.evaluator = evaluator;
this.listener = listener;
}
// ----------------------------------------------------------------------
// Public methods
// ----------------------------------------------------------------------
/**
* Calls the default "set" method on the bean; re-converts the configuration if necessary.
*
* @param bean The bean being configured
* @param defaultValue The default value
* @param configuration The configuration
*/
public void setDefault( final Object bean, final Object defaultValue, final PlexusConfiguration configuration )
throws ComponentConfigurationException
{
final Class<?> beanType = bean.getClass();
final Type[] paramTypeHolder = new Type[1];
// ----------------------------------------------------------------------
final Method setter = findMethod( beanType, paramTypeHolder, "set" );
if ( null == setter )
{
throw new ComponentConfigurationException( configuration, "Cannot find default setter in " + beanType );
}
// ----------------------------------------------------------------------
Object value = defaultValue;
final TypeLiteral<?> paramType = TypeLiteral.get( paramTypeHolder[0] );
if ( !paramType.getRawType().isInstance( value ) )
{
if ( configuration.getChildCount() > 0 )
{
throw new ComponentConfigurationException( "Basic element '" + configuration.getName()
+ "' must not contain child elements" );
}
value = convertProperty( beanType, paramType.getRawType(), paramType.getType(), configuration );
}
// ----------------------------------------------------------------------
if ( null != value )
{
try
{
if ( null != listener )
{
listener.notifyFieldChangeUsingSetter( "", value, bean );
}
setter.invoke( bean, value );
}
catch ( final Exception e )
{
throw new ComponentConfigurationException( configuration, "Cannot set default", e );
}
catch ( final LinkageError e )
{
throw new ComponentConfigurationException( configuration, "Cannot set default", e );
}
}
}
/**
* Sets a property in the bean; looks for public setter/adder method before checking fields.
*
* @param bean The bean being configured
* @param propertyName The property name
* @param valueType The value type
* @param configuration The configuration
*/
public void setProperty( final Object bean, final String propertyName, final Class<?> valueType,
final PlexusConfiguration configuration )
throws ComponentConfigurationException
{
final Class<?> beanType = bean.getClass();
final Type[] paramTypeHolder = new Type[1];
// ----------------------------------------------------------------------
final String title = Character.toTitleCase( propertyName.charAt( 0 ) ) + propertyName.substring( 1 );
Method setter = findMethod( beanType, paramTypeHolder, "set" + title );
if ( null == setter )
{
setter = findMethod( beanType, paramTypeHolder, "add" + title );
}
// ----------------------------------------------------------------------
Throwable problem = null;
Object value = null;
if ( null != setter )
{
try
{
final TypeLiteral<?> paramType = TypeLiteral.get( paramTypeHolder[0] );
Class<?> rawPropertyType = paramType.getRawType();
if ( null != valueType && rawPropertyType.isAssignableFrom( valueType ) )
{
rawPropertyType = valueType; // pick more specific type
}
value = convertProperty( beanType, rawPropertyType, paramType.getType(), configuration );
if ( null != value )
{
if ( null != listener )
{
listener.notifyFieldChangeUsingSetter( propertyName, value, bean );
}
setter.invoke( bean, value );
return;
}
}
catch ( final Exception e )
{
problem = e;
}
catch ( final LinkageError e )
{
problem = e;
}
}
// ----------------------------------------------------------------------
final Field field = findField( beanType, propertyName );
if ( null != field )
{
try
{
final TypeLiteral<?> fieldType = TypeLiteral.get( field.getGenericType() );
Class<?> rawPropertyType = fieldType.getRawType();
if ( !rawPropertyType.isInstance( value ) ) // only re-convert if we must
{
if ( null != valueType && rawPropertyType.isAssignableFrom( valueType ) )
{
rawPropertyType = valueType; // pick more specific type
}
value = convertProperty( beanType, rawPropertyType, fieldType.getType(), configuration );
}
if ( null != value )
{
if ( null != listener )
{
listener.notifyFieldChangeUsingReflection( propertyName, value, bean );
}
setField( bean, field, value );
return;
}
}
catch ( final Exception e )
{
if ( null == problem )
{
problem = e;
}
}
catch ( final LinkageError e )
{
if ( null == problem )
{
problem = e;
}
}
}
// ----------------------------------------------------------------------
if ( problem instanceof ComponentConfigurationException )
{
throw (ComponentConfigurationException) problem;
}
if ( null != problem )
{
final String reason = "Cannot set '" + propertyName + "' in " + beanType;
throw new ComponentConfigurationException( configuration, reason, problem );
}
if ( null == setter && null == field )
{
final String reason = "Cannot find '" + propertyName + "' in " + beanType;
throw new ComponentConfigurationException( configuration, reason );
}
}
// ----------------------------------------------------------------------
// Implementation methods
// ----------------------------------------------------------------------
private Object convertProperty( final Class<?> beanType, final Class<?> rawPropertyType,
final Type genericPropertyType, final PlexusConfiguration configuration )
throws ComponentConfigurationException
{
final ConfigurationConverter converter = lookup.lookupConverterForType( rawPropertyType );
if ( !( genericPropertyType instanceof Class<?> ) && converter instanceof ParameterizedConfigurationConverter )
{
final Type[] propertyTypeArgs = TypeArguments.get( genericPropertyType );
return ( (ParameterizedConfigurationConverter) converter ).fromConfiguration( lookup, configuration,
rawPropertyType,
propertyTypeArgs, beanType,
loader, evaluator, listener );
}
return converter.fromConfiguration( lookup, configuration, rawPropertyType, beanType, loader, evaluator,
listener );
}
private static Method findMethod( final Class<?> beanType, final Type[] paramTypeHolder, final String methodName )
{
for ( final Method m : beanType.getMethods() )
{
if ( methodName.equals( m.getName() ) && !Modifier.isStatic( m.getModifiers() ) )
{
final Type[] paramTypes = m.getGenericParameterTypes();
if ( paramTypes.length == 1 )
{
paramTypeHolder[0] = paramTypes[0];
return m;
}
}
}
return null;
}
private static Field findField( final Class<?> beanType, final String fieldName )
{
for ( final Member f : new DeclaredMembers( beanType, View.FIELDS ) )
{
if ( fieldName.equals( f.getName() ) && !Modifier.isStatic( f.getModifiers() ) )
{
return (Field) f;
}
}
return null;
}
private static void setField( final Object bean, final Field field, final Object value )
throws Exception
{
if ( !field.isAccessible() )
{
AccessController.doPrivileged( new PrivilegedAction<Void>()
{
public Void run()
{
field.setAccessible( true );
return null;
}
} );
}
field.set( bean, value );
}
}