diff --git a/src/main/java/info/ebenoit/ebul/cmp/Dependencies.java b/src/main/java/info/ebenoit/ebul/cmp/Dependencies.java
index 024915e..c0d969c 100644
--- a/src/main/java/info/ebenoit/ebul/cmp/Dependencies.java
+++ b/src/main/java/info/ebenoit/ebul/cmp/Dependencies.java
@@ -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 E. Benoît
*/
diff --git a/src/main/java/info/ebenoit/ebul/cmp/NewComponentInfo.java b/src/main/java/info/ebenoit/ebul/cmp/NewComponentInfo.java
index 0c2f765..3e8ca88 100644
--- a/src/main/java/info/ebenoit/ebul/cmp/NewComponentInfo.java
+++ b/src/main/java/info/ebenoit/ebul/cmp/NewComponentInfo.java
@@ -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 null
if a supplier is being used */
private final T component;
/** The component's supplier, or null
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 );
diff --git a/src/main/java/info/ebenoit/ebul/cmp/UseComponent.java b/src/main/java/info/ebenoit/ebul/cmp/UseComponent.java
index 091e8da..d19185d 100644
--- a/src/main/java/info/ebenoit/ebul/cmp/UseComponent.java
+++ b/src/main/java/info/ebenoit/ebul/cmp/UseComponent.java
@@ -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 E. Benoît
*/
@Retention( RetentionPolicy.RUNTIME )
-@Target( ElementType.FIELD )
+@Target( {
+ ElementType.FIELD , ElementType.METHOD
+} )
public @interface UseComponent
{