Automatically-added singletons
The registry can maintain a set of classes that contain annotated singletons corresponding to the components.
This commit is contained in:
parent
5032e44182
commit
3dc2f5b8d1
3 changed files with 123 additions and 8 deletions
1
TODO
1
TODO
|
@ -3,7 +3,6 @@ To Do:
|
|||
* Registry tests
|
||||
* Registry doc
|
||||
* General usage documentation
|
||||
* Automatically-updated singletons
|
||||
* Document exceptions
|
||||
|
||||
Other ideas (maybe later if needed):
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
package info.ebenoit.ebul.cmp;
|
||||
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -34,20 +37,63 @@ public class ComponentRegistry
|
|||
|
||||
}
|
||||
|
||||
private ArrayList< ComponentState > components;
|
||||
private HashMap< String , ComponentState > byName;
|
||||
private HashMap< Class< ? > , ArrayList< ComponentState > > byType;
|
||||
private final class SingletonClass
|
||||
{
|
||||
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 initialised;
|
||||
private boolean active;
|
||||
|
||||
|
||||
public ComponentRegistry( )
|
||||
public void addSingletonClass( Class< ? > klass )
|
||||
throws ComponentManagementException , AmbiguousComponentException
|
||||
{
|
||||
this.components = new ArrayList< >( );
|
||||
this.byName = new HashMap< >( );
|
||||
this.byType = new HashMap< >( );
|
||||
if ( !this.singletonClasses.containsKey( klass ) ) {
|
||||
SingletonClass info = new SingletonClass( klass );
|
||||
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.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
|
||||
if ( this.initialised ) {
|
||||
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 ,
|
||||
ArrayList< ComponentState > nComponents , HashMap< String , ComponentState > byName ,
|
||||
HashMap< Class< ? > , ArrayList< ComponentState > > byType )
|
||||
|
|
27
src/main/java/info/ebenoit/ebul/cmp/ComponentSingleton.java
Normal file
27
src/main/java/info/ebenoit/ebul/cmp/ComponentSingleton.java
Normal 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 "";
|
||||
|
||||
}
|
Loading…
Reference in a new issue