Component registration info - Rewrote dependency injector finder

Use the MemberFinder. Dependencies can be injected using methods.
This commit is contained in:
Emmanuel BENOîT 2015-09-14 10:56:19 +02:00
parent a1ddf5f63a
commit 9f5fbcbf3d
3 changed files with 102 additions and 63 deletions

View file

@ -11,7 +11,7 @@ import java.lang.annotation.Target;
/** /**
* This annotation lists dependencies for a component. If some components are injected as dependencies through the * This annotation lists dependencies for a component. If some components are injected as dependencies through the
* UseComponent annotation, they don't need to be listed here (same goes for the component indicated by * UseComponent annotation, they don't need to be listed here (same goes for the component indicated by
* {@link IsDriverFor}). * {@link DriverFor}).
* *
* @author <a href="mailto:ebenoit@ebenoit.info">E. Benoît</a> * @author <a href="mailto:ebenoit@ebenoit.info">E. Benoît</a>
*/ */

View file

@ -31,6 +31,10 @@ public final class NewComponentInfo< T >
private static final MemberFinder< Constructor< ? > > CONSTRUCTOR_FINDER; private static final MemberFinder< Constructor< ? > > CONSTRUCTOR_FINDER;
/** Member finder that looks for lifecycle methods */ /** Member finder that looks for lifecycle methods */
private static final MemberFinder< Method > LC_METHOD_FINDER; private static final MemberFinder< Method > LC_METHOD_FINDER;
/** Member finder that looks for dependency injections using fields */
private static final MemberFinder< Field > DI_FIELD_FINDER;
/** Member finder that looks for dependency injections using methods */
private static final MemberFinder< Method > DI_METHOD_FINDER;
static { static {
@ -42,6 +46,12 @@ public final class NewComponentInfo< T >
NewComponentInfo.LC_METHOD_FINDER.setClassesFirst( true ); NewComponentInfo.LC_METHOD_FINDER.setClassesFirst( true );
NewComponentInfo.LC_METHOD_FINDER NewComponentInfo.LC_METHOD_FINDER
.setMemberFilter( m -> m.getDeclaredAnnotation( LifecycleMethod.class ) != null ); .setMemberFilter( m -> m.getDeclaredAnnotation( LifecycleMethod.class ) != null );
DI_FIELD_FINDER = new MemberFinder< >( MemberFinder.FIELD_EXTRACTOR , false );
NewComponentInfo.DI_FIELD_FINDER.setMemberFilter( f -> f.getDeclaredAnnotation( UseComponent.class ) != null );
DI_METHOD_FINDER = new MemberFinder< >( MemberFinder.METHOD_EXTRACTOR , true );
NewComponentInfo.DI_METHOD_FINDER.setMemberFilter( m -> m.getDeclaredAnnotation( UseComponent.class ) != null );
} }
@ -95,7 +105,8 @@ public final class NewComponentInfo< T >
// Find lifecycle methods and dependency injections // Find lifecycle methods and dependency injections
NewComponentInfo.findLifecycleActions( info , klass ); NewComponentInfo.findLifecycleActions( info , klass );
NewComponentInfo.findMemberAnnotations( info , klass , klass ); NewComponentInfo.findDependencyInjectorFields( info , klass );
NewComponentInfo.findDependencyInjectorMethods( info , klass );
return info; return info;
} }
@ -175,81 +186,107 @@ public final class NewComponentInfo< T >
/** /**
* Finds annotations from the members of a component's class and of its ancestors * Finds fields annotated with {@link UseComponent} and add dependency injection actions setting these fields to the
* information structure.
* *
* @param info * @param info
* the information record being built * the information structure
* @param klass * @param klass
* the class of the component * the component class
* @param current
* the class being examined
*
* @throws ComponentDefinitionException * @throws ComponentDefinitionException
* if errors are found while reading the annotations * if a static or final field is annotated with {@link UseComponent}, or if the field's type is invalid
* (primitive or array type)
*/ */
private static < T > void findMemberAnnotations( final NewComponentInfo< T > info , final Class< T > klass , private static < T > void findDependencyInjectorFields( final NewComponentInfo< T > info , final Class< T > klass )
final Class< ? super T > current ) throws ComponentDefinitionException
throws ComponentDefinitionException
{ {
if ( current == Object.class ) { final ArrayList< Field > fields = new ArrayList< >( );
return; NewComponentInfo.DI_FIELD_FINDER.find( fields , klass );
for ( final Field f : fields ) {
if ( ( f.getModifiers( ) & Modifier.STATIC ) != 0 ) {
throw new ComponentDefinitionException( "in class " + f.getDeclaringClass( ) + ": static field "
+ f.getName( ) + " annotated with UseComponent" );
}
if ( ( f.getModifiers( ) & Modifier.FINAL ) != 0 ) {
throw new ComponentDefinitionException( "in class " + f.getDeclaringClass( ) + ": final field "
+ f.getName( ) + " annotated with UseComponent" );
}
DependencyInfo dep;
try {
dep = NewComponentInfo.depFrom( f.getType( ) , f.getDeclaredAnnotation( UseComponent.class ) );
} catch ( final ComponentDefinitionException e ) {
throw new ComponentDefinitionException(
"in class " + f.getDeclaringClass( ) + ", field " + f.getName( ) + ": " + e.getMessage( ) );
}
f.setAccessible( true );
info.addDependencyInjector( dep , ( o , d ) -> f.set( o , d ) );
} }
NewComponentInfo.findMemberAnnotations( info , klass , current.getSuperclass( ) );
NewComponentInfo.addDependencyInjectors( info , current );
} }
/** /**
* Finds fields that require dependency injection in a component's class and adds them to the information record * Finds methods annotated with {@link UseComponent} and add dependency injection actions calling these methods to
* the information structure.
* *
* @param info * @param info
* the information record being updated * the information structure
* @param current * @param klass
* the class being examined * the component class
* @throws ComponentDefinitionException
* if a static method or a method with more than one parameter is annotated with {@link UseComponent},
* or if the method parameter's type is invalid (primitive or array type)
*/ */
private static < T > void addDependencyInjectors( final NewComponentInfo< T > info , private static < T > void findDependencyInjectorMethods( final NewComponentInfo< T > info , final Class< T > klass )
final Class< ? super T > current ) throws ComponentDefinitionException
{ {
for ( final Field field : current.getDeclaredFields( ) ) { final ArrayList< Method > methods = new ArrayList< >( );
final UseComponent uc = field.getAnnotation( UseComponent.class ); NewComponentInfo.DI_METHOD_FINDER.find( methods , klass );
if ( uc == null ) { for ( final Method m : methods ) {
continue; if ( ( m.getModifiers( ) & Modifier.STATIC ) != 0 ) {
throw new ComponentDefinitionException( "in class " + m.getDeclaringClass( ) + ": static method "
+ m.getName( ) + " annotated with UseComponent" );
}
if ( m.getParameterCount( ) != 1 ) {
throw new ComponentDefinitionException( "in class " + m.getDeclaringClass( ) + ": method "
+ m.getName( ) + " has more than 1 parameter" );
} }
// Check the field DependencyInfo dep;
final int mod = field.getModifiers( ); try {
if ( ( mod & Modifier.STATIC ) != 0 ) { dep = NewComponentInfo.depFrom( m.getParameterTypes( )[ 0 ] ,
m.getDeclaredAnnotation( UseComponent.class ) );
} catch ( final ComponentDefinitionException e ) {
throw new ComponentDefinitionException( throw new ComponentDefinitionException(
"in class " + current + ": field " + field.getName( ) + " is static" ); "in class " + m.getDeclaringClass( ) + ", field " + m.getName( ) + ": " + e.getMessage( ) );
} }
if ( ( mod & Modifier.FINAL ) != 0 ) { m.setAccessible( true );
throw new ComponentDefinitionException( info.addDependencyInjector( dep , ( o , d ) -> m.invoke( o , d ) );
"in class " + current + ": field " + field.getName( ) + " is final" );
}
// Add the dependency
final String ucn = uc.value( );
final DependencyInfo di;
if ( "".equals( ucn ) ) {
try {
di = new DependencyInfo( field.getType( ) );
} catch ( final ComponentDefinitionException e ) {
throw new ComponentDefinitionException(
"in class " + current + ", field " + field.getName( ) + ": " + e.getMessage( ) );
}
} else {
di = new DependencyInfo( ucn );
}
// Add the injector
info.addDependencyInjector( di , ( c , d ) -> {
field.setAccessible( true );
field.set( c , d );
field.setAccessible( false );
} );
} }
} }
/**
* Create a dependency from an {@link UseComponent}-annotated field or method
*
* @param type
* the type of the field or of the method's parameter
* @param annotation
* the {@link UseComponent} annotation
* @return the dependency information record
* @throws ComponentDefinitionException
* if the type is invalid (primitive or array type)
*/
private static DependencyInfo depFrom( final Class< ? > type , final UseComponent annotation )
throws ComponentDefinitionException
{
if ( "".equals( annotation.value( ) ) ) {
return new DependencyInfo( type );
}
DependencyInfo.checkClassValidity( type );
return new DependencyInfo( annotation.value( ) );
}
/** The component's instance, or <code>null</code> if a supplier is being used */ /** The component's instance, or <code>null</code> if a supplier is being used */
private final T component; private final T component;
/** The component's supplier, or <code>null</code> if an instance has been supplied */ /** The component's supplier, or <code>null</code> if an instance has been supplied */
@ -268,7 +305,7 @@ public final class NewComponentInfo< T >
/** Lifecylce actions for the new component */ /** Lifecylce actions for the new component */
private final EnumMap< LifecycleStage , ThrowingConsumer< ? super T > > lcActions; private final EnumMap< LifecycleStage , ThrowingConsumer< ? super T > > lcActions;
/** Dependency injectors */ /** Dependency injectors */
private final HashMap< DependencyInfo , ArrayList< ThrowingBiConsumer< ? super T , ? > > > injectors; private final HashMap< DependencyInfo , ArrayList< ThrowingBiConsumer< ? super T , Object > > > injectors;
/** Whether the component should be activated automatically */ /** Whether the component should be activated automatically */
private boolean autostart; private boolean autostart;
@ -468,7 +505,7 @@ public final class NewComponentInfo< T >
* @return the modifiable map of dependency injectors, with the dependency information as the key and a list of * @return the modifiable map of dependency injectors, with the dependency information as the key and a list of
* functions that inject the dependency into the target component as the value. * functions that inject the dependency into the target component as the value.
*/ */
public HashMap< DependencyInfo , ArrayList< ThrowingBiConsumer< ? super T , ? > > > getInjectors( ) public HashMap< DependencyInfo , ArrayList< ThrowingBiConsumer< ? super T , Object > > > getInjectors( )
{ {
return this.injectors; return this.injectors;
} }
@ -485,9 +522,9 @@ public final class NewComponentInfo< T >
* @return the current object * @return the current object
*/ */
public NewComponentInfo< T > addDependencyInjector( final DependencyInfo dependency , public NewComponentInfo< T > addDependencyInjector( final DependencyInfo dependency ,
final ThrowingBiConsumer< ? super T , ? > injector ) final ThrowingBiConsumer< ? super T , Object > injector )
{ {
ArrayList< ThrowingBiConsumer< ? super T , ? > > injectors = this.injectors.get( dependency ); ArrayList< ThrowingBiConsumer< ? super T , Object > > injectors = this.injectors.get( dependency );
if ( injectors == null ) { if ( injectors == null ) {
injectors = new ArrayList< >( ); injectors = new ArrayList< >( );
this.injectors.put( dependency , injectors ); this.injectors.put( dependency , injectors );

View file

@ -9,14 +9,16 @@ import java.lang.annotation.Target;
/** /**
* This annotation indicates that a component's field is meant to receive the instance of another component (creating a * This annotation indicates that a component's field or method is meant to receive the instance of another component
* dependency implicitly). If no name is specified, the component to inject will be determined based on the type of the * (creating a dependency implicitly). If no name is specified, the component to inject will be determined based on the
* field. * type of the field.
* *
* @author <a href="mailto:ebenoit@ebenoit.info">E. Benoît</a> * @author <a href="mailto:ebenoit@ebenoit.info">E. Benoît</a>
*/ */
@Retention( RetentionPolicy.RUNTIME ) @Retention( RetentionPolicy.RUNTIME )
@Target( ElementType.FIELD ) @Target( {
ElementType.FIELD , ElementType.METHOD
} )
public @interface UseComponent public @interface UseComponent
{ {