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