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