Automatically-added singletons

The registry can maintain a set of classes that contain annotated
singletons corresponding to the components.
This commit is contained in:
Emmanuel BENOîT 2015-09-17 22:06:53 +02:00
parent 5032e44182
commit 3dc2f5b8d1
3 changed files with 123 additions and 8 deletions

1
TODO
View file

@ -3,7 +3,6 @@ To Do:
* Registry tests * Registry tests
* Registry doc * Registry doc
* General usage documentation * General usage documentation
* Automatically-updated singletons
* Document exceptions * Document exceptions
Other ideas (maybe later if needed): Other ideas (maybe later if needed):

View file

@ -1,10 +1,13 @@
package info.ebenoit.ebul.cmp; package info.ebenoit.ebul.cmp;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -34,20 +37,63 @@ public class ComponentRegistry
} }
private ArrayList< ComponentState > components; private final class SingletonClass
private HashMap< String , ComponentState > byName; {
private HashMap< Class< ? > , ArrayList< ComponentState > > byType; private final HashSet< Field > missingFields;
private final HashSet< Field > setFields;
private SingletonClass( Class< ? > klass )
{
this.missingFields = new HashSet< >( );
this.setFields = new HashSet< >( );
for ( Field fld : klass.getFields( ) ) {
if ( !fld.isAnnotationPresent( ComponentSingleton.class ) ) {
continue;
}
if ( ( fld.getModifiers( ) & Modifier.STATIC ) == 0 ) {
throw new ComponentManagementException( "singleton field " + fld.getName( ) + " in class "
+ klass.getCanonicalName( ) + " is not static" );
}
if ( ( fld.getModifiers( ) & Modifier.PUBLIC ) == 0 ) {
throw new ComponentManagementException( "singleton field " + fld.getName( ) + " in class "
+ klass.getCanonicalName( ) + " is not public" );
}
if ( ( fld.getModifiers( ) & Modifier.FINAL ) != 0 ) {
throw new ComponentManagementException( "singleton field " + fld.getName( ) + " in class "
+ klass.getCanonicalName( ) + " is final" );
}
missingFields.add( fld );
}
}
}
private final HashMap< Class< ? > , SingletonClass > singletonClasses = new HashMap< >( );
private ArrayList< ComponentState > components = new ArrayList< >( );
private HashMap< String , ComponentState > byName = new HashMap< >( );
private HashMap< Class< ? > , ArrayList< ComponentState > > byType = new HashMap< >( );
private boolean failed; private boolean failed;
private boolean initialised; private boolean initialised;
private boolean active; private boolean active;
public ComponentRegistry( ) public void addSingletonClass( Class< ? > klass )
throws ComponentManagementException , AmbiguousComponentException
{ {
this.components = new ArrayList< >( ); if ( !this.singletonClasses.containsKey( klass ) ) {
this.byName = new HashMap< >( ); SingletonClass info = new SingletonClass( klass );
this.byType = new HashMap< >( ); this.fillSingletonFields( info );
this.singletonClasses.put( klass , info );
}
}
public void removeSingletonClass( Class< ? > klass )
{
this.singletonClasses.remove( klass );
} }
@ -227,6 +273,16 @@ public class ComponentRegistry
this.byName = byName; this.byName = byName;
this.byType = byType; this.byType = byType;
// Update singleton classes
for ( SingletonClass sc : this.singletonClasses.values( ) ) {
try {
this.fillSingletonFields( sc );
} catch ( ComponentManagementException e ) {
this.failed = true;
throw e;
}
}
// Initialise all new components if the registry has been initialised // Initialise all new components if the registry has been initialised
if ( this.initialised ) { if ( this.initialised ) {
for ( int i = nOld ; i < nOld + nAdd ; i++ ) { for ( int i = nOld ; i < nOld + nAdd ; i++ ) {
@ -320,6 +376,39 @@ public class ComponentRegistry
} }
private void fillSingletonFields( SingletonClass info )
throws ComponentManagementException , AmbiguousComponentException
{
HashMap< Field , Object > matches = new HashMap< >( );
for ( Field fld : info.missingFields ) {
ComponentSingleton cs = fld.getAnnotation( ComponentSingleton.class );
ComponentState target;
if ( "".equals( cs.value( ) ) ) {
target = this.getState( fld.getType( ) );
} else {
target = this.getState( cs.value( ) );
}
if ( target != null ) {
matches.put( fld , target.getComponent( ) );
}
}
if ( matches.isEmpty( ) ) {
return;
}
for ( Map.Entry< Field , Object > entry : matches.entrySet( ) ) {
try {
entry.getKey( ).set( null , entry.getValue( ) );
} catch ( IllegalAccessException e ) {
throw new ComponentManagementException( "internal error while setting singleton" , e );
}
}
info.setFields.addAll( matches.keySet( ) );
info.missingFields.removeAll( matches.keySet( ) );
}
private static int resolveNewDependencies( ArrayList< TempBuildRec > adding , private static int resolveNewDependencies( ArrayList< TempBuildRec > adding ,
ArrayList< ComponentState > nComponents , HashMap< String , ComponentState > byName , ArrayList< ComponentState > nComponents , HashMap< String , ComponentState > byName ,
HashMap< Class< ? > , ArrayList< ComponentState > > byType ) HashMap< Class< ? > , ArrayList< ComponentState > > byType )

View file

@ -0,0 +1,27 @@
package info.ebenoit.ebul.cmp;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation can be used to create automatically filled singleton fields that correspond to components. It can
* only be used on static, public fields.
* <p>
* If a string is given, it will be used as the component's name. Otherwise the component to fetch will be determined
* using the field's type.
*
* @author <a href="mailto:ebenoit@ebenoit.info">E. Benoît</a>
*/
@Retention( RetentionPolicy.RUNTIME )
@Target( ElementType.METHOD )
public @interface ComponentSingleton
{
public String value( ) default "";
}