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 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):
|
||||||
|
|
|
@ -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 )
|
||||||
|
|
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