Component registration info - Rewrote dependency injector finder
Use the MemberFinder. Dependencies can be injected using methods.
This commit is contained in:
parent
a1ddf5f63a
commit
9f5fbcbf3d
3 changed files with 102 additions and 63 deletions
|
@ -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>
|
||||
*/
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
||||
|
|
Loading…
Reference in a new issue