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
* 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>
*/

View file

@ -31,6 +31,10 @@ public final class NewComponentInfo< T >
private static final MemberFinder< Constructor< ? > > CONSTRUCTOR_FINDER;
/** Member finder that looks for lifecycle methods */
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 {
@ -42,6 +46,12 @@ public final class NewComponentInfo< T >
NewComponentInfo.LC_METHOD_FINDER.setClassesFirst( true );
NewComponentInfo.LC_METHOD_FINDER
.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
NewComponentInfo.findLifecycleActions( info , klass );
NewComponentInfo.findMemberAnnotations( info , klass , klass );
NewComponentInfo.findDependencyInjectorFields( info , klass );
NewComponentInfo.findDependencyInjectorMethods( info , klass );
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
* the information record being built
* the information structure
* @param klass
* the class of the component
* @param current
* the class being examined
*
* the component class
* @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 ,
final Class< ? super T > current )
throws ComponentDefinitionException
private static < T > void findDependencyInjectorFields( final NewComponentInfo< T > info , final Class< T > klass )
throws ComponentDefinitionException
{
if ( current == Object.class ) {
return;
final ArrayList< Field > fields = new ArrayList< >( );
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
* the information record being updated
* @param current
* the class being examined
* the information structure
* @param klass
* 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 ,
final Class< ? super T > current )
private static < T > void findDependencyInjectorMethods( final NewComponentInfo< T > info , final Class< T > klass )
throws ComponentDefinitionException
{
for ( final Field field : current.getDeclaredFields( ) ) {
final UseComponent uc = field.getAnnotation( UseComponent.class );
if ( uc == null ) {
continue;
final ArrayList< Method > methods = new ArrayList< >( );
NewComponentInfo.DI_METHOD_FINDER.find( methods , klass );
for ( final Method m : methods ) {
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
final int mod = field.getModifiers( );
if ( ( mod & Modifier.STATIC ) != 0 ) {
DependencyInfo dep;
try {
dep = NewComponentInfo.depFrom( m.getParameterTypes( )[ 0 ] ,
m.getDeclaredAnnotation( UseComponent.class ) );
} catch ( final ComponentDefinitionException e ) {
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 ) {
throw new ComponentDefinitionException(
"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 );
} );
m.setAccessible( true );
info.addDependencyInjector( dep , ( o , d ) -> m.invoke( o , d ) );
}
}
/**
* 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 */
private final T component;
/** 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 */
private final EnumMap< LifecycleStage , ThrowingConsumer< ? super T > > lcActions;
/** 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 */
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
* 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;
}
@ -485,9 +522,9 @@ public final class NewComponentInfo< T >
* @return the current object
*/
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 ) {
injectors = new ArrayList< >( );
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
* dependency implicitly). If no name is specified, the component to inject will be determined based on the type of the
* field.
* This annotation indicates that a component's field or method is meant to receive the instance of another component
* (creating a dependency implicitly). If no name is specified, the component to inject will be determined based on the
* type of the field.
*
* @author <a href="mailto:ebenoit@ebenoit.info">E. Benoît</a>
*/
@Retention( RetentionPolicy.RUNTIME )
@Target( ElementType.FIELD )
@Target( {
ElementType.FIELD , ElementType.METHOD
} )
public @interface UseComponent
{