ebul-cmp/src/main/java/info/ebenoit/ebul/cmp/ComponentRegistry.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;
}
}
}