From 59f28483dd765c7867d946d8c065cf7cbb6addae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= Date: Tue, 15 Sep 2015 11:20:59 +0200 Subject: [PATCH] Components registry - Component registration * Incomplete class for component state tracking * Incomplete components registry class * Various exceptions for problems that can occur during component registration --- .../ebul/cmp/AmbiguousComponentException.java | 15 ++ .../cmp/ComponentManagementException.java | 34 +++ .../ebenoit/ebul/cmp/ComponentRegistry.java | 203 ++++++++++++++++++ .../info/ebenoit/ebul/cmp/ComponentState.java | 101 +++++++++ .../cmp/DependencyInjectionException.java | 15 ++ .../ebul/cmp/DuplicateComponentException.java | 34 +++ .../cmp/RecursiveDependenciesException.java | 20 ++ 7 files changed, 422 insertions(+) create mode 100644 src/main/java/info/ebenoit/ebul/cmp/AmbiguousComponentException.java create mode 100644 src/main/java/info/ebenoit/ebul/cmp/ComponentManagementException.java create mode 100644 src/main/java/info/ebenoit/ebul/cmp/ComponentRegistry.java create mode 100644 src/main/java/info/ebenoit/ebul/cmp/ComponentState.java create mode 100644 src/main/java/info/ebenoit/ebul/cmp/DependencyInjectionException.java create mode 100644 src/main/java/info/ebenoit/ebul/cmp/DuplicateComponentException.java create mode 100644 src/main/java/info/ebenoit/ebul/cmp/RecursiveDependenciesException.java diff --git a/src/main/java/info/ebenoit/ebul/cmp/AmbiguousComponentException.java b/src/main/java/info/ebenoit/ebul/cmp/AmbiguousComponentException.java new file mode 100644 index 0000000..e7e9ebf --- /dev/null +++ b/src/main/java/info/ebenoit/ebul/cmp/AmbiguousComponentException.java @@ -0,0 +1,15 @@ +package info.ebenoit.ebul.cmp; + + +public class AmbiguousComponentException + extends ComponentManagementException +{ + private static final long serialVersionUID = 7687715816342008691L; + + + public AmbiguousComponentException( Class< ? > requested , int matches ) + { + super( Integer.toString( matches ) + " components match type " + requested.getCanonicalName( ) ); + } + +} diff --git a/src/main/java/info/ebenoit/ebul/cmp/ComponentManagementException.java b/src/main/java/info/ebenoit/ebul/cmp/ComponentManagementException.java new file mode 100644 index 0000000..9b9bc04 --- /dev/null +++ b/src/main/java/info/ebenoit/ebul/cmp/ComponentManagementException.java @@ -0,0 +1,34 @@ +package info.ebenoit.ebul.cmp; + + +public class ComponentManagementException + extends ComponentException +{ + + private static final long serialVersionUID = -6185870047722371385L; + + + public ComponentManagementException( ) + { + super( ); + } + + + public ComponentManagementException( String message , Throwable cause ) + { + super( message , cause ); + } + + + public ComponentManagementException( String message ) + { + super( message ); + } + + + public ComponentManagementException( Throwable cause ) + { + super( cause ); + } + +} diff --git a/src/main/java/info/ebenoit/ebul/cmp/ComponentRegistry.java b/src/main/java/info/ebenoit/ebul/cmp/ComponentRegistry.java new file mode 100644 index 0000000..c9fa38c --- /dev/null +++ b/src/main/java/info/ebenoit/ebul/cmp/ComponentRegistry.java @@ -0,0 +1,203 @@ +package info.ebenoit.ebul.cmp; + + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +import info.ebenoit.ebul.func.FunctionException; +import info.ebenoit.ebul.func.ThrowingBiConsumer; +import info.ebenoit.ebul.reflection.Classes; + + + +public class ComponentRegistry +{ + private static final class TempBuildRec + { + private final NewComponentInfo< ? > info; + private final ComponentState state; + private final HashMap< DependencyInfo , ComponentState > deps; + private boolean added; + + + private TempBuildRec( NewComponentInfo< ? > info ) + { + this.info = info; + this.state = new ComponentState( info ); + this.deps = new HashMap< >( ); + } + + } + + private ArrayList< ComponentState > components; + private HashMap< String , ComponentState > byName; + private HashMap< Class< ? > , ArrayList< ComponentState > > byType; + + + // private boolean initialised; + // private boolean active; + + public ComponentRegistry( ) + { + this.components = new ArrayList< >( ); + this.byName = new HashMap< >( ); + this.byType = new HashMap< >( ); + } + + + public ComponentRegistry register( Collection< NewComponentInfo< ? > > components ) + throws ComponentCreationException , DuplicateComponentException , RecursiveDependenciesException , + DependencyInjectionException , AmbiguousComponentException + { + // Create "raw" state records for the new components + int nAdd = components.size( ); + int nLeft = nAdd; + ArrayList< TempBuildRec > adding = new ArrayList< >( nAdd ); + for ( NewComponentInfo< ? > nci : components ) { + adding.add( new TempBuildRec( nci ) ); + } + + // Create the registry's new state + ArrayList< ComponentState > nComponents = new ArrayList< >( this.components ); + HashMap< String , ComponentState > byName = new HashMap< >( this.byName ); + HashMap< Class< ? > , ArrayList< ComponentState > > byType = new HashMap< >( this.byType ); + + // Add all new components whose dependencies are satisfied. Stop when there are no new components left. If there + // are components left in the input list but no component can be added, it means there are some circular + // dependencies. + while ( nLeft > 0 ) { + int found = resolveNewDependencies( adding , nComponents , byName , byType ); + if ( found == 0 ) { + throw new RecursiveDependenciesException( adding.stream( ) // + .filter( r -> !r.added ) // + .map( r -> r.state.toString( ) ) // + .collect( Collectors.toList( ) ) ); + } + nLeft -= found; + } + + // Inject and track dependencies + injectDependencies( adding ); + + // Replace registry contents + this.components = nComponents; + this.byName = byName; + this.byType = byType; + + // TODO: handle initialisation and activation + + return this; + } + + + private static int resolveNewDependencies( ArrayList< TempBuildRec > adding , + ArrayList< ComponentState > nComponents , HashMap< String , ComponentState > byName , + HashMap< Class< ? > , ArrayList< ComponentState > > byType ) + throws AmbiguousComponentException , DuplicateComponentException + { + int size = adding.size( ); + int found = 0; + + for ( int i = 0 ; i < size ; i++ ) { + TempBuildRec rec = adding.get( i ); + if ( rec.added || !checkDependencies( byName , byType , rec ) ) { + continue; + } + + // Add the component + found++; + nComponents.add( rec.state ); + String name = rec.state.getName( ); + if ( name != null ) { + if ( byName.put( name , rec.state ) != null ) { + throw new DuplicateComponentException( name ); + } + } + for ( Class< ? > cls : Classes.getAllTypes( rec.state.getComponent( ).getClass( ) ) ) { + if ( cls == Object.class ) { + continue; + } + ArrayList< ComponentState > l = byType.get( cls ); + if ( l == null ) { + l = new ArrayList< >( ); + byType.put( cls , l ); + } + l.add( rec.state ); + } + rec.added = true; + } + + return found; + } + + + private static boolean checkDependencies( HashMap< String , ComponentState > byName , + HashMap< Class< ? > , ArrayList< ComponentState > > byType , TempBuildRec rec ) + throws AmbiguousComponentException + { + boolean depsOk = true; + for ( DependencyInfo dep : rec.info.getDependencies( ) ) { + if ( rec.deps.containsKey( dep ) ) { + continue; + } + + ComponentState cs; + if ( dep.getTargetClass( ) == null ) { + cs = byName.get( dep.getTargetName( ) ); + continue; + } else { + ArrayList< ComponentState > lt = byType.get( dep.getTargetClass( ) ); + if ( lt == null ) { + cs = null; + } else if ( lt.size( ) == 1 ) { + cs = lt.get( 0 ); + } else { + throw new AmbiguousComponentException( dep.getTargetClass( ) , lt.size( ) ); + } + } + + if ( cs == null ) { + depsOk = false; + } else { + rec.deps.put( dep , cs ); + } + } + return depsOk; + } + + + private static void injectDependencies( ArrayList< TempBuildRec > adding ) + throws DependencyInjectionException + { + int size = adding.size( ); + + for ( int i = 0 ; i < size ; i++ ) { + TempBuildRec rec = adding.get( i ); + Object cmp = rec.state.getComponent( ); + for ( Map.Entry< DependencyInfo , ArrayList< ThrowingBiConsumer< Object , Object > > > di : rec.info + .getInjectors( ).entrySet( ) ) { + ComponentState toInject = rec.deps.get( di.getKey( ) ); + assert toInject != null; + for ( ThrowingBiConsumer< Object , Object > action : di.getValue( ) ) { + try { + action.accept( cmp , toInject.getComponent( ) ); + } catch ( FunctionException e ) { + throw new DependencyInjectionException( rec.state , toInject , e.getCause( ) ); + } catch ( RuntimeException e ) { + throw new DependencyInjectionException( rec.state , toInject , e ); + } + } + } + } + + for ( int i = 0 ; i < size ; i++ ) { + TempBuildRec rec = adding.get( i ); + for ( ComponentState dep : rec.deps.values( ) ) { + rec.state.addDependency( dep ); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/info/ebenoit/ebul/cmp/ComponentState.java b/src/main/java/info/ebenoit/ebul/cmp/ComponentState.java new file mode 100644 index 0000000..d72cd63 --- /dev/null +++ b/src/main/java/info/ebenoit/ebul/cmp/ComponentState.java @@ -0,0 +1,101 @@ +package info.ebenoit.ebul.cmp; + + +import java.util.HashSet; + +import info.ebenoit.ebul.func.FunctionException; + + + +public final class ComponentState +{ + private final Object component; + private final String name; + + private final boolean autostart; + private boolean initialised; + private boolean active; + + private final HashSet< ComponentState > dependencies = new HashSet< >( ); + private final HashSet< ComponentState > reverseDependencies = new HashSet< >( ); + + + ComponentState( final NewComponentInfo< ? > ci ) + throws ComponentCreationException + { + Object component = ci.getComponent( ); + if ( component == null ) { + try { + component = ci.getSupplier( ).get( ); + } catch ( final FunctionException e ) { + throw new ComponentCreationException( e.getCause( ) ); + } + } + this.component = component; + + String name; + if ( component instanceof ParametricComponent ) { + name = ( (ParametricComponent) component ).getComponentName( ); + } else { + name = ci.getName( ); + if ( name == null ) { + name = component.getClass( ).getSimpleName( ); + } + } + this.name = name; + + this.autostart = ci.getAutostart( ); + } + + + @Override + public String toString( ) + { + StringBuilder sb = new StringBuilder( ); + if ( this.name == null ) { + sb.append( "anonymous component " ); + } else { + sb.append( "component '" ).append( this.name ).append( "' " ); + } + sb.append( " of type " ).append( this.component.getClass( ).getCanonicalName( ) ); + return super.toString( ); + } + + + public Object getComponent( ) + { + return this.component; + } + + + public String getName( ) + { + return this.name; + } + + + public boolean hasAutostart( ) + { + return this.autostart; + } + + + public boolean isInitialised( ) + { + return this.initialised; + } + + + public boolean isActive( ) + { + return this.active; + } + + + void addDependency( ComponentState dep ) + { + this.dependencies.add( dep ); + dep.reverseDependencies.add( this ); + } + +} diff --git a/src/main/java/info/ebenoit/ebul/cmp/DependencyInjectionException.java b/src/main/java/info/ebenoit/ebul/cmp/DependencyInjectionException.java new file mode 100644 index 0000000..ea50c7d --- /dev/null +++ b/src/main/java/info/ebenoit/ebul/cmp/DependencyInjectionException.java @@ -0,0 +1,15 @@ +package info.ebenoit.ebul.cmp; + + +public class DependencyInjectionException + extends ComponentManagementException +{ + + private static final long serialVersionUID = -7253816819524841154L; + + + public DependencyInjectionException( ComponentState target , ComponentState toInject , Throwable cause ) + { + super( "could not inject " + toInject + " into " + target , cause ); + } +} diff --git a/src/main/java/info/ebenoit/ebul/cmp/DuplicateComponentException.java b/src/main/java/info/ebenoit/ebul/cmp/DuplicateComponentException.java new file mode 100644 index 0000000..60fb534 --- /dev/null +++ b/src/main/java/info/ebenoit/ebul/cmp/DuplicateComponentException.java @@ -0,0 +1,34 @@ +package info.ebenoit.ebul.cmp; + + +public class DuplicateComponentException + extends ComponentManagementException +{ + + private static final long serialVersionUID = -9081860105587395537L; + + + public DuplicateComponentException( ) + { + super( ); + } + + + public DuplicateComponentException( String message , Throwable cause ) + { + super( message , cause ); + } + + + public DuplicateComponentException( String message ) + { + super( message ); + } + + + public DuplicateComponentException( Throwable cause ) + { + super( cause ); + } + +} diff --git a/src/main/java/info/ebenoit/ebul/cmp/RecursiveDependenciesException.java b/src/main/java/info/ebenoit/ebul/cmp/RecursiveDependenciesException.java new file mode 100644 index 0000000..5f5d53a --- /dev/null +++ b/src/main/java/info/ebenoit/ebul/cmp/RecursiveDependenciesException.java @@ -0,0 +1,20 @@ +package info.ebenoit.ebul.cmp; + + +import java.util.List; + + + +public class RecursiveDependenciesException + extends ComponentManagementException +{ + + private static final long serialVersionUID = -7548835178440134836L; + + + public RecursiveDependenciesException( final List< String > componentsLeft ) + { + super( "remaining components: " + String.join( ", " , componentsLeft ) ); + } + +}