481 lines
No EOL
11 KiB
Java
481 lines
No EOL
11 KiB
Java
package info.ebenoit.ebul.cmp;
|
|
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
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 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( ComponentRegistry.this , info );
|
|
this.deps = new HashMap< >( );
|
|
}
|
|
|
|
}
|
|
|
|
private ArrayList< ComponentState > components;
|
|
private HashMap< String , ComponentState > byName;
|
|
private HashMap< Class< ? > , ArrayList< ComponentState > > byType;
|
|
|
|
private boolean failed;
|
|
private boolean initialised;
|
|
private boolean active;
|
|
|
|
|
|
public ComponentRegistry( )
|
|
{
|
|
this.components = new ArrayList< >( );
|
|
this.byName = new HashMap< >( );
|
|
this.byType = new HashMap< >( );
|
|
}
|
|
|
|
|
|
public boolean hasFailed( )
|
|
{
|
|
return failed;
|
|
}
|
|
|
|
|
|
public boolean isInitialised( )
|
|
{
|
|
return initialised;
|
|
}
|
|
|
|
|
|
public boolean isActive( )
|
|
{
|
|
return active;
|
|
}
|
|
|
|
|
|
public ComponentState getState( String name )
|
|
{
|
|
return this.byName.get( name );
|
|
}
|
|
|
|
|
|
public ComponentState getState( Class< ? > type )
|
|
throws AmbiguousComponentException
|
|
{
|
|
ArrayList< ComponentState > l = this.byType.get( type );
|
|
if ( l == null ) {
|
|
return null;
|
|
}
|
|
if ( l.size( ) == 1 ) {
|
|
return l.get( 0 );
|
|
}
|
|
throw new AmbiguousComponentException( type , l.size( ) );
|
|
}
|
|
|
|
|
|
public List< ComponentState > getStates( Class< ? > type )
|
|
throws AmbiguousComponentException
|
|
{
|
|
ArrayList< ComponentState > l = this.byType.get( type );
|
|
if ( l == null ) {
|
|
return Collections.emptyList( );
|
|
}
|
|
return Collections.unmodifiableList( l );
|
|
}
|
|
|
|
|
|
public void getStates( Class< ? > type , Collection< ComponentState > states )
|
|
{
|
|
ArrayList< ComponentState > l = this.byType.get( type );
|
|
if ( l != null ) {
|
|
int nFound = l.size( );
|
|
for ( int i = 0 ; i < nFound ; i++ ) {
|
|
states.add( l.get( i ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public List< ComponentState > getAllStates( )
|
|
{
|
|
return Collections.unmodifiableList( components );
|
|
}
|
|
|
|
|
|
public void getAllStates( Collection< ComponentState > result )
|
|
{
|
|
result.addAll( result );
|
|
}
|
|
|
|
|
|
public Object get( String name )
|
|
{
|
|
ComponentState cs = this.byName.get( name );
|
|
return cs == null ? null : cs.getComponent( );
|
|
}
|
|
|
|
|
|
public < T > T get( Class< T > type )
|
|
throws AmbiguousComponentException
|
|
{
|
|
ArrayList< ComponentState > l = this.byType.get( type );
|
|
if ( l == null ) {
|
|
return null;
|
|
}
|
|
if ( l.size( ) == 1 ) {
|
|
return type.cast( l.get( 0 ).getComponent( ) );
|
|
}
|
|
throw new AmbiguousComponentException( type , l.size( ) );
|
|
}
|
|
|
|
|
|
public < T > List< T > getAll( Class< T > type )
|
|
{
|
|
ArrayList< ComponentState > l = this.byType.get( type );
|
|
if ( l == null ) {
|
|
return Collections.emptyList( );
|
|
}
|
|
|
|
int nFound = l.size( );
|
|
ArrayList< T > result = new ArrayList< >( nFound );
|
|
for ( int i = 0 ; i < nFound ; i++ ) {
|
|
result.add( type.cast( l.get( i ).getComponent( ) ) );
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
public < T > void getAll( Class< T > type , Collection< T > result )
|
|
{
|
|
ArrayList< ComponentState > l = this.byType.get( type );
|
|
if ( l != null ) {
|
|
int nFound = l.size( );
|
|
for ( int i = 0 ; i < nFound ; i++ ) {
|
|
result.add( type.cast( l.get( i ).getComponent( ) ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public ComponentRegistry register( Collection< NewComponentInfo< ? > > components )
|
|
throws ComponentCreationException , DuplicateComponentException , RecursiveDependenciesException ,
|
|
DependencyInjectionException , AmbiguousComponentException , ComponentInitialisationException ,
|
|
ComponentStartupException
|
|
{
|
|
if ( this.failed ) {
|
|
throw new IllegalStateException( "register() called on failed registry" );
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// Set up driver components
|
|
for ( int i = 0 ; i < nAdd ; i++ ) {
|
|
TempBuildRec c = adding.get( i );
|
|
String df = c.info.getDriverFor( );
|
|
if ( df != null ) {
|
|
c.state.setDriverFor( byName.get( df ) );
|
|
}
|
|
}
|
|
|
|
// Inject and track dependencies
|
|
injectDependencies( adding );
|
|
|
|
// Replace registry contents
|
|
int nOld = this.components.size( );
|
|
this.components = nComponents;
|
|
this.byName = byName;
|
|
this.byType = byType;
|
|
|
|
// Initialise all new components if the registry has been initialised
|
|
if ( this.initialised ) {
|
|
for ( int i = nOld ; i < nOld + nAdd ; i++ ) {
|
|
initComponent( i );
|
|
}
|
|
}
|
|
|
|
// Activate autostart components
|
|
if ( this.active ) {
|
|
for ( int i = 0 ; i < nOld + nAdd ; i++ ) {
|
|
autostartComponent( i );
|
|
}
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
|
|
public void initialise( )
|
|
throws ComponentInitialisationException
|
|
{
|
|
if ( this.failed ) {
|
|
throw new IllegalStateException( "initialise() called on failed registry" );
|
|
}
|
|
if ( this.initialised ) {
|
|
return;
|
|
}
|
|
|
|
int nComponents = this.components.size( );
|
|
for ( int i = 0 ; i < nComponents ; i++ ) {
|
|
initComponent( i );
|
|
}
|
|
|
|
this.initialised = true;
|
|
}
|
|
|
|
|
|
public void destroy( )
|
|
throws IllegalStateException , ComponentShutdownException , ComponentDestructionException
|
|
{
|
|
if ( this.failed ) {
|
|
throw new IllegalStateException( "destroy() called on failed registry" );
|
|
}
|
|
if ( !this.initialised ) {
|
|
return;
|
|
}
|
|
|
|
this.stop( );
|
|
for ( int i = this.components.size( ) - 1 ; i >= 0 ; i-- ) {
|
|
destroyComponent( i );
|
|
}
|
|
this.initialised = false;
|
|
}
|
|
|
|
|
|
public void start( )
|
|
throws IllegalStateException , ComponentStartupException
|
|
{
|
|
if ( this.failed ) {
|
|
throw new IllegalStateException( "start() called on failed registry" );
|
|
}
|
|
if ( !this.initialised ) {
|
|
throw new IllegalStateException( "start() called on uninitialised registry" );
|
|
}
|
|
if ( this.active ) {
|
|
return;
|
|
}
|
|
|
|
int nComponents = this.components.size( );
|
|
for ( int i = 0 ; i < nComponents ; i++ ) {
|
|
autostartComponent( i );
|
|
}
|
|
this.initialised = true;
|
|
}
|
|
|
|
|
|
public void stop( )
|
|
throws IllegalStateException , ComponentShutdownException
|
|
{
|
|
if ( this.failed ) {
|
|
throw new IllegalStateException( "stop() called on failed registry" );
|
|
}
|
|
if ( !this.active ) {
|
|
return;
|
|
}
|
|
|
|
for ( int i = this.components.size( ) - 1 ; i >= 0 ; i-- ) {
|
|
stopComponent( i );
|
|
}
|
|
this.active = false;
|
|
}
|
|
|
|
|
|
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 );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
private void initComponent( int index )
|
|
throws ComponentInitialisationException
|
|
{
|
|
try {
|
|
this.components.get( index ).init( );
|
|
} catch ( RuntimeException e ) {
|
|
this.failed = true;
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
|
|
private void autostartComponent( int index )
|
|
throws ComponentStartupException
|
|
{
|
|
try {
|
|
ComponentState cmp = this.components.get( index );
|
|
if ( cmp.hasAutostart( ) ) {
|
|
cmp.start( );
|
|
}
|
|
} catch ( RuntimeException e ) {
|
|
this.failed = true;
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
|
|
private void stopComponent( int index )
|
|
throws ComponentDestructionException
|
|
{
|
|
try {
|
|
this.components.get( index ).init( );
|
|
} catch ( RuntimeException e ) {
|
|
this.failed = true;
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
|
|
private void destroyComponent( int index )
|
|
throws ComponentDestructionException
|
|
{
|
|
try {
|
|
this.components.get( index ).init( );
|
|
} catch ( RuntimeException e ) {
|
|
this.failed = true;
|
|
throw e;
|
|
}
|
|
}
|
|
} |