Importing some code
* Various annotations * New component information class
This commit is contained in:
parent
48466ed511
commit
e2de94eb7b
14 changed files with 826 additions and 0 deletions
22
src/main/java/info/ebenoit/ebul/cmp/Autostart.java
Normal file
22
src/main/java/info/ebenoit/ebul/cmp/Autostart.java
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package info.ebenoit.ebul.cmp;
|
||||||
|
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This annotation indicates that a component will be started automatically, even if it is not needed by other
|
||||||
|
* components.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:ebenoit@ebenoit.info">E. Benoît</a>
|
||||||
|
*/
|
||||||
|
@Retention( RetentionPolicy.RUNTIME )
|
||||||
|
@Target( ElementType.TYPE )
|
||||||
|
public @interface Autostart
|
||||||
|
{
|
||||||
|
// EMPTY
|
||||||
|
}
|
25
src/main/java/info/ebenoit/ebul/cmp/Component.java
Normal file
25
src/main/java/info/ebenoit/ebul/cmp/Component.java
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
package info.ebenoit.ebul.cmp;
|
||||||
|
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This annotation indicates that a class will be considered as a component. The optional value specifies the
|
||||||
|
* component's name if it differs from the class' name.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:ebenoit@ebenoit.info">E. Benoît</a>
|
||||||
|
*/
|
||||||
|
@Retention( RetentionPolicy.RUNTIME )
|
||||||
|
@Target( ElementType.TYPE )
|
||||||
|
public @interface Component
|
||||||
|
{
|
||||||
|
|
||||||
|
/** Name of the component (blank to use the class' name) */
|
||||||
|
public String value( ) default "";
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package info.ebenoit.ebul.cmp;
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings( "serial" )
|
||||||
|
public class ComponentCreationException
|
||||||
|
extends ComponentException
|
||||||
|
{
|
||||||
|
|
||||||
|
public ComponentCreationException( final Throwable cause )
|
||||||
|
{
|
||||||
|
super( cause );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package info.ebenoit.ebul.cmp;
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings( "serial" )
|
||||||
|
public class ComponentDefinitionException
|
||||||
|
extends ComponentException
|
||||||
|
{
|
||||||
|
|
||||||
|
public ComponentDefinitionException( )
|
||||||
|
{
|
||||||
|
super( );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ComponentDefinitionException( String message )
|
||||||
|
{
|
||||||
|
super( message );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ComponentDefinitionException( Throwable cause )
|
||||||
|
{
|
||||||
|
super( cause );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ComponentDefinitionException( String message , Throwable cause )
|
||||||
|
{
|
||||||
|
super( message , cause );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
32
src/main/java/info/ebenoit/ebul/cmp/ComponentException.java
Normal file
32
src/main/java/info/ebenoit/ebul/cmp/ComponentException.java
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
package info.ebenoit.ebul.cmp;
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings( "serial" )
|
||||||
|
public class ComponentException
|
||||||
|
extends RuntimeException
|
||||||
|
{
|
||||||
|
|
||||||
|
public ComponentException( )
|
||||||
|
{
|
||||||
|
super( );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ComponentException( String message )
|
||||||
|
{
|
||||||
|
super( message );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ComponentException( Throwable cause )
|
||||||
|
{
|
||||||
|
super( cause );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ComponentException( String message , Throwable cause )
|
||||||
|
{
|
||||||
|
super( message , cause );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
26
src/main/java/info/ebenoit/ebul/cmp/Dependencies.java
Normal file
26
src/main/java/info/ebenoit/ebul/cmp/Dependencies.java
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package info.ebenoit.ebul.cmp;
|
||||||
|
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
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}).
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:ebenoit@ebenoit.info">E. Benoît</a>
|
||||||
|
*/
|
||||||
|
@Retention( RetentionPolicy.RUNTIME )
|
||||||
|
@Target( ElementType.TYPE )
|
||||||
|
public @interface Dependencies
|
||||||
|
{
|
||||||
|
|
||||||
|
/** List the component's dependencies */
|
||||||
|
public String[]value( );
|
||||||
|
|
||||||
|
}
|
113
src/main/java/info/ebenoit/ebul/cmp/DependencyInfo.java
Normal file
113
src/main/java/info/ebenoit/ebul/cmp/DependencyInfo.java
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
package info.ebenoit.ebul.cmp;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class carries information about a dependency's target. Dependencies may be specified by name or by type.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:ebenoit@ebenoit.info">E. Benoît</a>
|
||||||
|
*/
|
||||||
|
public class DependencyInfo
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the component being depended upon, or <code>null</code> if the dependency information is based on
|
||||||
|
* type.
|
||||||
|
*/
|
||||||
|
private final String name;
|
||||||
|
/**
|
||||||
|
* The type of the component being depended upon, or <code>null</code> if the dependency information is based on the
|
||||||
|
* name.
|
||||||
|
*/
|
||||||
|
private final Class< ? > klass;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a name-based dependency information record
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
* the name of the component being depended upon
|
||||||
|
*/
|
||||||
|
public DependencyInfo( final String name )
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
this.klass = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a type-based dependency information record
|
||||||
|
*
|
||||||
|
* @param klass
|
||||||
|
* the type of the component being depended upon
|
||||||
|
* @throws ComponentDefinitionException
|
||||||
|
* if the specified type is not supported (e.g. primitives or arrays)
|
||||||
|
*/
|
||||||
|
public DependencyInfo( final Class< ? > klass )
|
||||||
|
throws ComponentDefinitionException
|
||||||
|
{
|
||||||
|
this.name = null;
|
||||||
|
this.klass = klass;
|
||||||
|
if ( klass.isPrimitive( ) ) {
|
||||||
|
throw new ComponentDefinitionException( "specified dependency is a primitive type" );
|
||||||
|
}
|
||||||
|
if ( klass.isArray( ) ) {
|
||||||
|
throw new ComponentDefinitionException( "specified dependency is an array type" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode( )
|
||||||
|
{
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + ( this.klass == null ? 0 : this.klass.hashCode( ) );
|
||||||
|
result = prime * result + ( this.name == null ? 0 : this.name.hashCode( ) );
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals( final Object obj )
|
||||||
|
{
|
||||||
|
if ( this == obj ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ( obj == null ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ( this.getClass( ) != obj.getClass( ) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final DependencyInfo other = (DependencyInfo) obj;
|
||||||
|
if ( this.klass == null ) {
|
||||||
|
if ( other.klass != null ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this.name.equals( other.name );
|
||||||
|
} else if ( this.klass != other.klass ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the name of the component being depended upon, or <code>null</code> if the dependency information is
|
||||||
|
* based on type.
|
||||||
|
*/
|
||||||
|
public String getTargetName( )
|
||||||
|
{
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the type of the component being depended upon, or <code>null</code> if the dependency information is
|
||||||
|
* based on the name.
|
||||||
|
*/
|
||||||
|
public Class< ? > getTargetClass( )
|
||||||
|
{
|
||||||
|
return this.klass;
|
||||||
|
}
|
||||||
|
}
|
26
src/main/java/info/ebenoit/ebul/cmp/DriverFor.java
Normal file
26
src/main/java/info/ebenoit/ebul/cmp/DriverFor.java
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package info.ebenoit.ebul.cmp;
|
||||||
|
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Inherited;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This annotation is meant to indicate that a component acts as a driver for another component.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:ebenoit@ebenoit.info">E. Benoît</a>
|
||||||
|
*/
|
||||||
|
@Inherited
|
||||||
|
@Retention( RetentionPolicy.RUNTIME )
|
||||||
|
@Target( ElementType.TYPE )
|
||||||
|
public @interface DriverFor
|
||||||
|
{
|
||||||
|
|
||||||
|
/** Name of the component to act as a driver for. Implies a dependency. */
|
||||||
|
public String value( );
|
||||||
|
|
||||||
|
}
|
24
src/main/java/info/ebenoit/ebul/cmp/LifecycleMethod.java
Normal file
24
src/main/java/info/ebenoit/ebul/cmp/LifecycleMethod.java
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
package info.ebenoit.ebul.cmp;
|
||||||
|
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This annotation can be used to indicate that a method should be called as part of the component's lifecycle.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:ebenoit@ebenoit.info">E. Benoît</a>
|
||||||
|
*/
|
||||||
|
@Retention( RetentionPolicy.RUNTIME )
|
||||||
|
@Target( ElementType.METHOD )
|
||||||
|
public @interface LifecycleMethod
|
||||||
|
{
|
||||||
|
|
||||||
|
/** The lifecycle stage to which the method corresponds. */
|
||||||
|
public LifecycleStage value( );
|
||||||
|
|
||||||
|
}
|
20
src/main/java/info/ebenoit/ebul/cmp/LifecycleStage.java
Normal file
20
src/main/java/info/ebenoit/ebul/cmp/LifecycleStage.java
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
package info.ebenoit.ebul.cmp;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This enumaration represents the 4 stages of a component's lifecycle.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:ebenoit@ebenoit.info">E. Benoît</a>
|
||||||
|
*/
|
||||||
|
public enum LifecycleStage {
|
||||||
|
|
||||||
|
/** Initialisation stage */
|
||||||
|
INITIALISE ,
|
||||||
|
/** Startup stage */
|
||||||
|
START ,
|
||||||
|
/** Shutdown stage */
|
||||||
|
STOP ,
|
||||||
|
/** Destruction stage */
|
||||||
|
DESTROY
|
||||||
|
|
||||||
|
}
|
440
src/main/java/info/ebenoit/ebul/cmp/NewComponentInfo.java
Normal file
440
src/main/java/info/ebenoit/ebul/cmp/NewComponentInfo.java
Normal file
|
@ -0,0 +1,440 @@
|
||||||
|
package info.ebenoit.ebul.cmp;
|
||||||
|
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.EnumMap;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
import info.ebenoit.ebul.func.ThrowingBiConsumer;
|
||||||
|
import info.ebenoit.ebul.func.ThrowingConsumer;
|
||||||
|
import info.ebenoit.ebul.func.ThrowingSupplier;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class carries information about a component to be added to the registry. Instances can be fed directly to the
|
||||||
|
* registry, or generated from a class' annotations.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:ebenoit@ebenoit.info">E. Benoît</a>
|
||||||
|
*/
|
||||||
|
public final class NewComponentInfo< T >
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Creates a new component information record based on a component's annotated class.
|
||||||
|
*
|
||||||
|
* @param klass
|
||||||
|
* the class to extract the information from
|
||||||
|
* @return the generated information record
|
||||||
|
*/
|
||||||
|
public static < T > NewComponentInfo< T > fromClass( final Class< T > klass )
|
||||||
|
{
|
||||||
|
final NewComponentInfo< T > info = new NewComponentInfo< >( ( ) -> klass.newInstance( ) );
|
||||||
|
|
||||||
|
// Sets the new component's name
|
||||||
|
final Component aComp = klass.getAnnotation( Component.class );
|
||||||
|
if ( aComp != null && !"".equals( aComp.value( ) ) ) {
|
||||||
|
info.setName( aComp.value( ) );
|
||||||
|
} else if ( !ParametricComponent.class.isAssignableFrom( klass ) ) {
|
||||||
|
info.setName( klass.getSimpleName( ) );
|
||||||
|
} else {
|
||||||
|
info.setName( null );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Autostart flag
|
||||||
|
info.setAutostart( klass.isAnnotationPresent( Autostart.class ) );
|
||||||
|
|
||||||
|
// Driver for other component?
|
||||||
|
final DriverFor driverFor = klass.getAnnotation( DriverFor.class );
|
||||||
|
if ( driverFor != null ) {
|
||||||
|
info.setDriverFor( driverFor.value( ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Explicit dependencies
|
||||||
|
final Dependencies dependencies = klass.getAnnotation( Dependencies.class );
|
||||||
|
if ( dependencies != null ) {
|
||||||
|
for ( final String dependency : dependencies.value( ) ) {
|
||||||
|
info.addDependency( dependency );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find lifecycle methods and dependency injections
|
||||||
|
NewComponentInfo.findMemberAnnotations( info , klass , klass );
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds annotations from the members of a component's class and of its ancestors
|
||||||
|
*
|
||||||
|
* @param info
|
||||||
|
* the information record being built
|
||||||
|
* @param klass
|
||||||
|
* the class of the component
|
||||||
|
* @param current
|
||||||
|
* the class being examined
|
||||||
|
*
|
||||||
|
* @throws ComponentDefinitionException
|
||||||
|
* if errors are found while reading the annotations
|
||||||
|
*/
|
||||||
|
private static < T > void findMemberAnnotations( final NewComponentInfo< T > info , final Class< T > klass ,
|
||||||
|
final Class< ? super T > current )
|
||||||
|
throws ComponentDefinitionException
|
||||||
|
{
|
||||||
|
if ( current == Object.class ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
NewComponentInfo.findMemberAnnotations( info , klass , current.getSuperclass( ) );
|
||||||
|
NewComponentInfo.addLifecycleActions( info , current );
|
||||||
|
NewComponentInfo.addDependencyInjectors( info , current );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds component lifecycle actions to an information record by looking for annotated methods
|
||||||
|
*
|
||||||
|
* @param info
|
||||||
|
* the information record to be updated
|
||||||
|
* @param current
|
||||||
|
* the class being examined
|
||||||
|
*/
|
||||||
|
private static < T > void addLifecycleActions( final NewComponentInfo< T > info , final Class< ? super T > current )
|
||||||
|
{
|
||||||
|
final boolean found[] = {
|
||||||
|
false , false , false , false
|
||||||
|
};
|
||||||
|
for ( final Method method : current.getDeclaredMethods( ) ) {
|
||||||
|
final LifecycleMethod lcm = method.getAnnotation( LifecycleMethod.class );
|
||||||
|
if ( lcm == null ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for duplicates
|
||||||
|
final int order = lcm.value( ).ordinal( );
|
||||||
|
if ( found[ order ] ) {
|
||||||
|
throw new ComponentDefinitionException(
|
||||||
|
"in class " + current + ": duplicate lifecycle action for stage " + lcm.value( ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check method
|
||||||
|
if ( method.getParameterCount( ) != 0 ) {
|
||||||
|
throw new ComponentDefinitionException( "in class " + current + ": lifecycle action method for stage "
|
||||||
|
+ lcm.value( ) + " has arguments" );
|
||||||
|
}
|
||||||
|
if ( ( method.getModifiers( ) & Modifier.STATIC ) != 0 ) {
|
||||||
|
throw new ComponentDefinitionException(
|
||||||
|
"in class " + current + ": lifecycle action method for stage " + lcm.value( ) + " is static" );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add it
|
||||||
|
found[ order ] = true;
|
||||||
|
info.setLifecycleAction( lcm.value( ) , o -> {
|
||||||
|
method.setAccessible( true );
|
||||||
|
method.invoke( o );
|
||||||
|
method.setAccessible( false );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds fields that require dependency injection in a component's class and adds them to the information record
|
||||||
|
*
|
||||||
|
* @param info
|
||||||
|
* the information record being updated
|
||||||
|
* @param current
|
||||||
|
* the class being examined
|
||||||
|
*/
|
||||||
|
private static < T > void addDependencyInjectors( final NewComponentInfo< T > info ,
|
||||||
|
final Class< ? super T > current )
|
||||||
|
{
|
||||||
|
for ( final Field field : current.getDeclaredFields( ) ) {
|
||||||
|
final UseComponent uc = field.getAnnotation( UseComponent.class );
|
||||||
|
if ( uc == null ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the field
|
||||||
|
final int mod = field.getModifiers( );
|
||||||
|
if ( ( mod & Modifier.STATIC ) != 0 ) {
|
||||||
|
throw new ComponentDefinitionException(
|
||||||
|
"in class " + current + ": field " + field.getName( ) + " is static" );
|
||||||
|
}
|
||||||
|
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 );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 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 */
|
||||||
|
private final ThrowingSupplier< T > supplier;
|
||||||
|
|
||||||
|
/** The component's name, or <code>null</code> if it must be guessed */
|
||||||
|
private String name;
|
||||||
|
/**
|
||||||
|
* The name of a component this new component acts as a driver for, or <code>null</code> if this component is not a
|
||||||
|
* driver.
|
||||||
|
*/
|
||||||
|
private String driverFor;
|
||||||
|
/** The set of dependency names for the new component */
|
||||||
|
private final HashSet< DependencyInfo > dependencies;
|
||||||
|
|
||||||
|
/** 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;
|
||||||
|
|
||||||
|
/** Whether the component should be activated automatically */
|
||||||
|
private boolean autostart;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialises a component based on a existing instance
|
||||||
|
*
|
||||||
|
* @param component
|
||||||
|
* the component's instance
|
||||||
|
*/
|
||||||
|
public NewComponentInfo( final T component )
|
||||||
|
{
|
||||||
|
this.component = component;
|
||||||
|
this.supplier = null;
|
||||||
|
this.dependencies = new HashSet< >( );
|
||||||
|
this.lcActions = new EnumMap< >( LifecycleStage.class );
|
||||||
|
this.injectors = new HashMap< >( );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialises a component based on a component supplier
|
||||||
|
*
|
||||||
|
* @param supplier
|
||||||
|
* the supplier to obtain the component from
|
||||||
|
*/
|
||||||
|
public NewComponentInfo( final ThrowingSupplier< T > supplier )
|
||||||
|
{
|
||||||
|
this.component = null;
|
||||||
|
this.supplier = supplier;
|
||||||
|
this.dependencies = new HashSet< >( );
|
||||||
|
this.lcActions = new EnumMap< >( LifecycleStage.class );
|
||||||
|
this.injectors = new HashMap< >( );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the component, or <code>null</code> if a supplier is in use
|
||||||
|
*/
|
||||||
|
public T getComponent( )
|
||||||
|
{
|
||||||
|
return this.component;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the component supplier, or <code>null</code> if an instance was provided
|
||||||
|
*/
|
||||||
|
public ThrowingSupplier< T > getSupplier( )
|
||||||
|
{
|
||||||
|
return this.supplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the new component's name
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
* the new component's name
|
||||||
|
* @return the current object
|
||||||
|
*/
|
||||||
|
public NewComponentInfo< T > setName( final String name )
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the name of the new component
|
||||||
|
*/
|
||||||
|
public String getName( )
|
||||||
|
{
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the name of the component for which the current component acts as a driver for
|
||||||
|
*/
|
||||||
|
public String getDriverFor( )
|
||||||
|
{
|
||||||
|
return this.driverFor;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the name of the component for which the current component acts as a driver for
|
||||||
|
*
|
||||||
|
* @param driverFor
|
||||||
|
* the name of the main component
|
||||||
|
* @return the current object
|
||||||
|
*/
|
||||||
|
public NewComponentInfo< T > setDriverFor( final String driverFor )
|
||||||
|
{
|
||||||
|
this.driverFor = driverFor;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the action for one of the component's lifecycle stages
|
||||||
|
*
|
||||||
|
* @param stage
|
||||||
|
* the lifecyle stage for which the action is being set
|
||||||
|
* @param action
|
||||||
|
* the action to perform
|
||||||
|
* @return the current object
|
||||||
|
*/
|
||||||
|
public NewComponentInfo< T > setLifecycleAction( final LifecycleStage stage ,
|
||||||
|
final ThrowingConsumer< ? super T > action )
|
||||||
|
{
|
||||||
|
this.lcActions.put( stage , action );
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the action for one of the component's lifecycle stages
|
||||||
|
*
|
||||||
|
* @param stage
|
||||||
|
* the lifecycle stage for which the action must be returned
|
||||||
|
* @return the action corresponding to the specified lifecycle stage
|
||||||
|
*/
|
||||||
|
public ThrowingConsumer< ? > getLifecycleAction( final LifecycleStage stage )
|
||||||
|
{
|
||||||
|
return this.lcActions.get( stage );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the modifiable set of dependencies
|
||||||
|
*/
|
||||||
|
public HashSet< DependencyInfo > getDependencies( )
|
||||||
|
{
|
||||||
|
return this.dependencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a dependency to the component
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
* the dependency's name
|
||||||
|
* @return the current object
|
||||||
|
*/
|
||||||
|
public NewComponentInfo< T > addDependency( final String name )
|
||||||
|
{
|
||||||
|
this.dependencies.add( new DependencyInfo( name ) );
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a dependency to the component
|
||||||
|
*
|
||||||
|
* @param klass
|
||||||
|
* the dependency's type
|
||||||
|
* @return the current object
|
||||||
|
*/
|
||||||
|
public NewComponentInfo< T > addDependency( final Class< ? > klass )
|
||||||
|
{
|
||||||
|
this.dependencies.add( new DependencyInfo( klass ) );
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return <code>true</code> if the component is supposed to start automatically
|
||||||
|
*/
|
||||||
|
public boolean getAutostart( )
|
||||||
|
{
|
||||||
|
return this.autostart;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the autostart flag
|
||||||
|
*
|
||||||
|
* @param autostart
|
||||||
|
* a flag indicating whether the component should be started automatically
|
||||||
|
* @return the current object
|
||||||
|
*/
|
||||||
|
public NewComponentInfo< T > setAutostart( final boolean autostart )
|
||||||
|
{
|
||||||
|
this.autostart = autostart;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accesses the map of dependency injectors. Note that any direct change should be reflected on the dependencies
|
||||||
|
* manually.
|
||||||
|
*
|
||||||
|
* @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( )
|
||||||
|
{
|
||||||
|
return this.injectors;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a dependency injector and registers the new dependency.
|
||||||
|
*
|
||||||
|
* @param dependency
|
||||||
|
* the name of the component being depended upon
|
||||||
|
* @param injector
|
||||||
|
* a function that accepts two parameters: the instance into which the dependency is being injected, and
|
||||||
|
* the instance of the component being injected
|
||||||
|
* @return the current object
|
||||||
|
*/
|
||||||
|
public NewComponentInfo< T > addDependencyInjector( final DependencyInfo dependency ,
|
||||||
|
final ThrowingBiConsumer< ? super T , ? > injector )
|
||||||
|
{
|
||||||
|
ArrayList< ThrowingBiConsumer< ? super T , ? > > injectors = this.injectors.get( dependency );
|
||||||
|
if ( injectors == null ) {
|
||||||
|
injectors = new ArrayList< >( );
|
||||||
|
this.injectors.put( dependency , injectors );
|
||||||
|
}
|
||||||
|
injectors.add( injector );
|
||||||
|
this.dependencies.add( dependency );
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
20
src/main/java/info/ebenoit/ebul/cmp/ParametricComponent.java
Normal file
20
src/main/java/info/ebenoit/ebul/cmp/ParametricComponent.java
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
package info.ebenoit.ebul.cmp;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface must be implemented by components which are loaded dynamically depending on configuration (for example
|
||||||
|
* VFS drivers). Note that such components need not be annotated with {@link Component}, and that they never start
|
||||||
|
* automatically.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:ebenoit@ebenoit.info">E. Benoît</a>
|
||||||
|
*/
|
||||||
|
public interface ParametricComponent
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine the name of the current instance of this component.
|
||||||
|
*
|
||||||
|
* @return the name of the component
|
||||||
|
*/
|
||||||
|
public String getComponentName( );
|
||||||
|
|
||||||
|
}
|
26
src/main/java/info/ebenoit/ebul/cmp/UseComponent.java
Normal file
26
src/main/java/info/ebenoit/ebul/cmp/UseComponent.java
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package info.ebenoit.ebul.cmp;
|
||||||
|
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
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.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:ebenoit@ebenoit.info">E. Benoît</a>
|
||||||
|
*/
|
||||||
|
@Retention( RetentionPolicy.RUNTIME )
|
||||||
|
@Target( ElementType.FIELD )
|
||||||
|
public @interface UseComponent
|
||||||
|
{
|
||||||
|
|
||||||
|
/** The injected component's name, or blank if it is to be guessed from the field's type */
|
||||||
|
public String value( ) default "";
|
||||||
|
|
||||||
|
}
|
6
src/main/java/info/ebenoit/ebul/cmp/package-info.java
Normal file
6
src/main/java/info/ebenoit/ebul/cmp/package-info.java
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
/**
|
||||||
|
* Component management library and related annotations.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:ebenoit@ebenoit.info">E. Benoît</a>
|
||||||
|
*/
|
||||||
|
package info.ebenoit.ebul.cmp;
|
Loading…
Reference in a new issue