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 {