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:
parent
5dc745670b
commit
59f28483dd
7 changed files with 422 additions and 0 deletions
|
@ -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( ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
203
src/main/java/info/ebenoit/ebul/cmp/ComponentRegistry.java
Normal file
203
src/main/java/info/ebenoit/ebul/cmp/ComponentRegistry.java
Normal 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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
101
src/main/java/info/ebenoit/ebul/cmp/ComponentState.java
Normal file
101
src/main/java/info/ebenoit/ebul/cmp/ComponentState.java
Normal 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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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 );
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue