Components registry - Component registration

* Incomplete class for component state tracking
* Incomplete components registry class
* Various exceptions for problems that can occur during component
registration
This commit is contained in:
Emmanuel BENOîT 2015-09-15 11:20:59 +02:00
parent 5dc745670b
commit 59f28483dd
7 changed files with 422 additions and 0 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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