Importing some code

* Various annotations
* New component information class
This commit is contained in:
Emmanuel BENOîT 2015-09-13 14:46:11 +02:00
parent 48466ed511
commit e2de94eb7b
14 changed files with 826 additions and 0 deletions

View 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
}

View 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 "";
}

View file

@ -0,0 +1,14 @@
package info.ebenoit.ebul.cmp;
@SuppressWarnings( "serial" )
public class ComponentCreationException
extends ComponentException
{
public ComponentCreationException( final Throwable cause )
{
super( cause );
}
}

View file

@ -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 );
}
}

View 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 );
}
}

View 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( );
}

View 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;
}
}

View 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( );
}

View 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( );
}

View 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
}

View 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;
}
}

View 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( );
}

View 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 "";
}

View 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;