Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.refcodes.decoupling.Dependency Maven / Gradle / Ivy
Go to download
Artifact for breaking up dependencies between components or modules of
a software system (dependency injection and inversion of control).
// /////////////////////////////////////////////////////////////////////////////
// REFCODES.ORG
// /////////////////////////////////////////////////////////////////////////////
// This code is copyright (c) by Siegfried Steiner, Munich, Germany and licensed
// under the following (see "http://en.wikipedia.org/wiki/Multi-licensing")
// licenses:
// -----------------------------------------------------------------------------
// GNU General Public License, v3.0 ("http://www.gnu.org/licenses/gpl-3.0.html")
// -----------------------------------------------------------------------------
// Apache License, v2.0 ("http://www.apache.org/licenses/LICENSE-2.0")
// -----------------------------------------------------------------------------
// Please contact the copyright holding author(s) of the software artifacts in
// question for licensing issues not being covered by the above listed licenses,
// also regarding commercial licensing models or regarding the compatibility
// with other open source licenses.
// /////////////////////////////////////////////////////////////////////////////
package org.refcodes.decoupling;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.refcodes.data.Delimiter;
import org.refcodes.mixin.AliasAccessor;
import org.refcodes.mixin.Schemable;
import org.refcodes.mixin.TypeAccessor;
import org.refcodes.textual.CaseStyleBuilder;
import org.refcodes.textual.VerboseTextBuilder;
/**
* A {@link Dependency} describes a component wired together by the
* {@link Reactor} with other components also defined by {@link Dependency}
* instances.
*
* @param the generic type of the {@link Dependency}.
*/
public class Dependency implements Schemable, AliasAccessor, TypeAccessor, ProfilesAccessor, TagsAccessor, InstanceMetricsAccessor, ClaimsAccessor, Comparable> {
// /////////////////////////////////////////////////////////////////////////
// STATICS:
// /////////////////////////////////////////////////////////////////////////
private static final InstanceMetrics DEFAULT_INSTANCE_MODE = InstanceMode.SINGLETON_IS_MANDATORY;
// /////////////////////////////////////////////////////////////////////////
// CONSTANTS:
// /////////////////////////////////////////////////////////////////////////
private static final Logger LOGGER = Logger.getLogger( Dependency.class.getName() );
// /////////////////////////////////////////////////////////////////////////
// VARIABLES:
// /////////////////////////////////////////////////////////////////////////
private Constructor> _constructor;
private T _dangling; // Instance has been created but not (yet) injected!
private boolean _isSetupSingletonFromInstance = false;
protected T _singleton = null;
protected String _alias;
protected Set _claims = new HashSet<>();
protected Dependency>[] _dependencies;
protected InstanceMetrics _instanceMetrics;
protected Set _instances = new HashSet<>();
protected Set _profiles;
protected Set _tags;
protected Class _type;
protected InitializerClaim, T> _initializer;
protected FactoryClaim, T> _factory;
protected Reactor _reactor;
// /////////////////////////////////////////////////////////////////////////
// CONSTRUCTORS:
// /////////////////////////////////////////////////////////////////////////
/**
* Instantiates a new {@link Dependency}.
*/
protected Dependency() {}
/**
* Instantiates a new {@link Dependency} from the given
* {@link DependencyBuilder} instance.
*
* @param aDependency the {@link DependencyBuilder} from which to
* initialize.
*
* @param aReactor The {@link Reactor} creating this {@link Dependency}.
*/
Dependency( DependencyBuilder aDependency, Reactor aReactor ) {
this( aDependency.getType(), aDependency.getInstance(), aDependency.getInstanceMetrics(), aDependency.getAlias(), aDependency.getTags(), aDependency.getProfiles(), aDependency.getClaims(), aDependency.getSetup(), aDependency.getFactory(), aReactor );
}
/**
* Constructs a {@link Dependency} for the given type (the alias is derived
* from type).
*
* @param aType The type of the dependency.
*/
Dependency( Class aType ) {
this( aType, (T) null, null, (String) null, (Collection) null, (Collection) null, null, (InitializerClaim, T>) null, (FactoryClaim, T>) null, null );
}
/**
* Constructs a {@link Dependency} for the given instance (the type is
* derived from the instance, the alias is derived from type).
*
* @param aInstance The instance of the dependency.
*/
Dependency( T aInstance ) {
this( (Class) null, aInstance, (InstanceMetrics) null, (String) null, (Collection) null, (Collection) null, null, (InitializerClaim, T>) null, (FactoryClaim, T>) null, null );
}
private Dependency( Class aType, T aInstance, InstanceMetrics aInstanceMetrics, String aAlias, Object[] aTags, Object[] aProfiles, Claim[] aClaims, InitializerClaim, T> aInitializerClaim, FactoryClaim, T> aFactory, Reactor aReactor ) {
this( aType, aInstance, aInstanceMetrics, aAlias, aTags != null && aTags.length != 0 ? Arrays.asList( aTags ) : null, aProfiles != null && aProfiles.length != 0 ? Arrays.asList( aProfiles ) : null, aClaims != null && aClaims.length != 0 ? Arrays.asList( aClaims ) : null, aInitializerClaim, aFactory, aReactor );
}
@SuppressWarnings("unchecked")
private Dependency( Class aType, T aInstance, InstanceMetrics aInstanceMetrics, String aAlias, Collection aTags, Collection aProfiles, Collection aClaims, InitializerClaim, T> aInitializerClaim, FactoryClaim, T> aFactory, Reactor aReactor ) {
_type = aInstance != null && aType == null ? (Class) aInstance.getClass() : aType;
_alias = ( aAlias == null || aAlias.isEmpty() ) ? CaseStyleBuilder.asCamelCase( toAlias( _type ) ) : aAlias;
_tags = aTags != null && aTags.size() != 0 ? new HashSet<>( aTags ) : new HashSet<>();
_profiles = aProfiles != null && aProfiles.size() != 0 ? new HashSet<>( aProfiles ) : new HashSet<>();
_instanceMetrics = aInstanceMetrics != null ? aInstanceMetrics : DEFAULT_INSTANCE_MODE;
_claims = aClaims != null && aClaims.size() != 0 ? new HashSet<>( aClaims ) : new HashSet<>();
_initializer = aInitializerClaim;
_factory = aFactory;
_reactor = aReactor;
if ( aInstance == null && aType == null ) {
throw new IllegalArgumentException( "At least an or a must be provided, but neither a nor an has been provided!" );
}
if ( aInstance != null && aFactory != null ) {
throw new IllegalArgumentException( "Either an or a must be provided, but not an instance <" + aInstance + "> and a factory <" + aFactory + "> at the same time!" );
}
if ( aInstance != null ) {
_instances.add( aInstance );
// An instance must be a singleton (we don't clone!) and is already there (cannot be omitted) |-->
if ( _instanceMetrics.isSingleton() && _instanceMetrics.isMandatory() ) {
_singleton = aInstance;
_isSetupSingletonFromInstance = true;
}
// An instance must be a singleton (we don't clone!) and is already there (cannot be omitted) <--|
else {
throw new IllegalArgumentException( "The instance <" + aInstance + "> may only be provided in case the dependency <" + this + "> instance metrics denotes singleton (we only have that single instance) and mandatory (as it has already been created)!" );
}
}
}
// /////////////////////////////////////////////////////////////////////////
// METHODS:
// /////////////////////////////////////////////////////////////////////////
/**
* {@inheritDoc}
*/
@Override
public boolean equals( Object obj ) {
if ( this == obj ) {
return true;
}
if ( obj == null ) {
return false;
}
if ( getClass() != obj.getClass() ) {
return false;
}
final Dependency> other = (Dependency>) obj;
return Objects.equals( _alias, other._alias ) && Objects.equals( _profiles, other._profiles ) && Objects.equals( _tags, other._tags ) && Objects.equals( _type, other._type );
}
/**
* {@inheritDoc}
*/
@Override
public String getAlias() {
return _alias;
}
/**
* {@inheritDoc}
*/
@Override
public Claim[] getClaims() {
return _claims.toArray( new Claim[_claims.size()] );
}
/**
* Retrieves the {@link InitializerClaim}, being a {@link Claim} dedicated
* to initialize the dependency after instantiation.
*
* @return The {@link InitializerClaim} being set for this
* {@link Dependency}.
*/
public InitializerClaim, T> getSetup() {
return _initializer;
}
/**
* Retrieves the {@link FactoryClaim}, being a {@link Claim} dedicated to
* fabricate (create) the dependency.
*
* @return The {@link FactoryClaim} being set for this {@link Dependency}.
*/
public FactoryClaim, T> getFactory() {
return _factory;
}
/**
* Retrieves the {@link InstanceMetrics} which describes how an instance for
* a {@link Dependency} is managed.
*
* @return The according {@link InstanceMetrics}.
*/
@Override
public InstanceMetrics getInstanceMetrics() {
return _instanceMetrics;
}
/**
* Retrieves the instances produced by this {@link Dependency} (as of the
* {@link Dependency}'s {@link InstanceMetrics} configuration).
*
* @return The instances fabricated by this {@link Dependency} declaration.
*/
@SuppressWarnings("unchecked")
public T[] getInstances() {
return _instances.toArray( (T[]) Array.newInstance( _type, _instances.size() ) );
}
/**
* Retrieves the profiles assigned to the {@link Dependency} declaration.
*
* @return The assigned profiles.
*/
@Override
public Object[] getProfiles() {
return _profiles.toArray();
}
/**
* Retrieves the tags assigned to the {@link Dependency} declaration.
*
* @return The assigned tags.
*/
@Override
public Object[] getTags() {
if ( _tags == null ) {
_tags = new HashSet<>();
}
return _tags.toArray();
}
/**
* {@inheritDoc}
*/
@Override
public Class getType() {
return _type;
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
return Objects.hash( _alias, _profiles, _tags, _type );
}
/**
* Determines weather this {@link Dependency} element has at least one
* instance of its type {@link #getType()} being created.
*
* @return True in case instances for this {@link Dependency} have already
* been created.
*/
public boolean hasInstances() {
return !_instances.isEmpty();
}
/**
* Determines whether this {@link Dependency} element contains the provided
* instance.
*
* @param aInstance The instance which is to be tested.
*
* @return True in case this {@link Dependency} element contains the given
* instance, else false.
*/
public boolean hasInstance( Object aInstance ) {
for ( T eInnstance : getInstances() ) {
if ( eInnstance == aInstance ) {
return true;
}
}
return false;
}
/**
* Tests whether this {@link Dependency} matches any of the provided
* profiles.
*
* @param aProfiles The profiles (as provided by the {@link Reactor}) to
* match against.
*
* @return True in case this {@link Dependency} matches at least one of the
* provided profiles.
*/
public boolean hasProfile( Object... aProfiles ) {
if ( _profiles == null || _profiles.size() == 0 ) {
return true;
}
else {
if ( aProfiles == null || aProfiles.length == 0 ) {
return false;
}
for ( Object eProfile : aProfiles ) {
for ( Object eDependencyProfile : _profiles ) {
if ( eProfile.toString().equalsIgnoreCase( eDependencyProfile.toString() ) ) {
return true;
}
}
}
}
return false;
}
/**
* Creates the {@link Dependency}'s instance using the prepared
* {@link Dependency} declarations (the {@link Dependency} declarations are
* prepared by the {@link Reactor}'s {@link Reactor#createContext()} and the
* like methods and in turn by the underlying {@link #toInstance()}
* methods).
*
* @return The instance retrieved from the {@link Dependency}.
*
* @throws DependencyInstanciationException the dependency instantiation
* exception
*/
@SuppressWarnings("unchecked")
public T toInstance() throws DependencyInstanciationException {
if ( _singleton != null ) {
return _singleton;
}
final Object[] theArgs = new Object[_dependencies.length];
for ( int i = 0; i < _dependencies.length; i++ ) {
theArgs[i] = _dependencies[i].toInstance();
}
try {
final T theInstance = (T) _constructor.newInstance( theArgs );
if ( _instanceMetrics.isSingleton() ) {
_singleton = theInstance;
_isSetupSingletonFromInstance = true;
}
_instances.add( theInstance );
return theInstance;
}
catch ( Exception e ) {
throw new DependencyInstanciationException( "Cannot instanciate dependency <{0}> using provided dependencies <{1}>!", this, _dependencies, e );
}
}
/**
* {@inheritDoc}
*/
@Override
public DependencySchema toSchema() {
DependencySchema[] theSchemas = null;
if ( _dependencies != null && _dependencies.length != 0 ) {
theSchemas = new DependencySchema[_dependencies.length];
for ( int i = 0; i < theSchemas.length; i++ ) {
theSchemas[i] = _dependencies[i].toSchema();
}
}
if ( getFactory() != null ) {
if ( theSchemas == null ) {
theSchemas = new DependencySchema[1];
}
else {
theSchemas = Arrays.copyOf( theSchemas, theSchemas.length + 1 );
}
theSchemas[theSchemas.length - 1] = getFactory().toSchema();
}
if ( getSetup() != null ) {
if ( theSchemas == null ) {
theSchemas = new DependencySchema[1];
}
else {
theSchemas = Arrays.copyOf( theSchemas, theSchemas.length + 1 );
}
theSchemas[theSchemas.length - 1] = getSetup().toSchema();
}
return new DependencySchema( this, theSchemas );
}
/**
* {@inheritDoc}
*/
@Override
public int compareTo( Dependency aDependency ) {
final String thisAlias = getAlias() != null ? getAlias() : "";
final String thatAlias = aDependency != null && aDependency.getAlias() != null ? aDependency.getAlias() : "";
return thisAlias.compareTo( thatAlias );
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return getClass().getSimpleName() + " [alias=" + _alias + ", tags=" + _tags + ", profiles=" + _profiles + ", type=" + _type + ", instances=" + _instances + ", instanceMetrics=" + _instanceMetrics + "]";
}
// /////////////////////////////////////////////////////////////////////////
// HOOKS:
// /////////////////////////////////////////////////////////////////////////
/**
* Sets the instance for the {@link Dependency} (the type is derived from
* the instance if not already set, the alias is derived from type if not
* already set).
*
* @param aInstance The instance of the dependency;
*/
@SuppressWarnings("unchecked")
protected void setInstance( T aInstance ) {
if ( aInstance == null ) {
throw new IllegalArgumentException( "The provided instance must not(!) be null!" );
}
_singleton = null;
_instances.clear();
if ( aInstance != null ) {
_instances.add( aInstance );
}
if ( _instanceMetrics.isSingleton() ) {
_singleton = aInstance;
_isSetupSingletonFromInstance = true;
}
if ( _type == null ) {
_type = (Class) aInstance.getClass();
}
if ( _alias == null && _alias.isEmpty() ) {
_alias = CaseStyleBuilder.asCamelCase( toAlias( _type ) );
}
}
/**
* Creates the {@link Dependency}'s instance as of its type using the
* provided {@link Dependency} declarations.
*
* @param aDependencies the {@link Dependency} declarations to use.
* @param aProfiles The profiles as provided by the {@link Reactor} to take
* into account.
*
* @return The instance retrieved from the {@link Dependency}.
*
* @throws CircularDependencyException thrown in case there is some
* (transitive) circular dependency between two {@link Dependency}
* instances.
* @throws AmbigousDependencyException thrown in case for a required
* {@link Dependency} there are more than one matching
* {@link Dependency} candidates in the {@link Reactor}.
* @throws UnsatisfiedDependencyException thrown in case for a required
* {@link Dependency} none matching {@link Dependency} candidates in
* the {@link Reactor} have been found (taking the profiles applied
* into account) for the constructors of the given
* {@link Dependency}.
* @throws AmbigousClaimException is thrown in case one {@link Claim} can be
* matched by multiple dependencies.
* @throws AmbigousInitializerException is thrown in case one
* {@link InitializerClaim} instance can be matched by multiple
* dependencies.
* @throws AmbigousFactoryException is thrown in case one
* {@link FactoryClaim} instance can be matched by multiple
* dependencies.
* @throws InstallDependencyException is thrown when trying to install a
* {@link Dependency} into the {@link Context}, though installing
* failed die to the dependency throwing an exception during the
* process of installing (e.g. instantiation).
*/
protected T toInstance( Dependency>[] aDependencies, Object... aProfiles ) throws CircularDependencyException, AmbigousDependencyException, UnsatisfiedDependencyException, AmbigousClaimException, AmbigousInitializerException, AmbigousFactoryException, InstallDependencyException {
return toInstance( aDependencies, new HashSet<>(), aProfiles );
}
/**
* Creates the {@link Dependency}'s instance as of its type using the
* provided {@link Dependency} declarations.
*
* @param aDependencies the {@link Dependency} declarations to use.
* @param aVistedDependencies The {@link Dependency} declarations already
* visited in order to detect a circular dependency situation.
* @param aProfiles The profiles as provided by the {@link Reactor} to take
* into account.
*
* @return The instance retrieved from the {@link Dependency}.
*
* @throws CircularDependencyException thrown in case there is some
* (transitive) circular dependency between two {@link Dependency}
* instances.
* @throws AmbigousDependencyException thrown in case for a required
* {@link Dependency} there are more than one matching
* {@link Dependency} candidates in the {@link Reactor}.
* @throws UnsatisfiedDependencyException thrown in case for a required
* {@link Dependency} none matching {@link Dependency} candidates in
* the {@link Reactor} have been found (taking the profiles applied
* into account) for the constructors of the given
* {@link Dependency}.
* @throws AmbigousClaimException is thrown in case one {@link Claim} can be
* matched by multiple dependencies.
* @throws AmbigousInitializerException is thrown in case one
* {@link InitializerClaim} instance can be matched by multiple
* dependencies.
* @throws AmbigousFactoryException is thrown in case one
* {@link FactoryClaim} instance can be matched by multiple
* dependencies.
* @throws InstallDependencyException is thrown when trying to install a
* {@link Dependency} into the {@link Context}, though installing
* failed die to the dependency throwing an exception during the
* process of installing (e.g. instantiation).
*/
@SuppressWarnings("unchecked")
protected T toInstance( Dependency>[] aDependencies, Set> aVistedDependencies, Object... aProfiles ) throws CircularDependencyException, AmbigousDependencyException, UnsatisfiedDependencyException, AmbigousClaimException, AmbigousInitializerException, AmbigousFactoryException, InstallDependencyException {
if ( !aVistedDependencies.add( this ) ) {
if ( _instances.isEmpty() ) {
throw new CircularDependencyException( "A circular dependency between this dependency <{0}> and one or more dependencies <{1}> has been detected, unable to resolve conflict (you may contribute and add some proxy mechanism here)!", this, aVistedDependencies.toArray( new Dependency[aVistedDependencies.size()] ) );
}
}
if ( _dangling != null ) {
return _dangling; // An instance has been requested and we still have a dangling one which we return
}
T theInstance = null;
if ( _singleton != null ) {
if ( !_isSetupSingletonFromInstance ) {
return _singleton;
}
theInstance = _singleton;
}
else if ( _factory != null ) {
final Dependency> theDependency = toMatchingDependencyByClaim( aDependencies, _factory );
final Object theFactory = theDependency.toInstance( aDependencies, aVistedDependencies, aProfiles );
theInstance = _factory.toInstance( theFactory );
}
else {
final Dependency>[] theDependencies = toMatchingDependenciesByClaims( aDependencies );
final Constructor>[] theCtors = toSuitableConstructors( theDependencies, aProfiles );
final List> eCtorDependencies = new ArrayList<>();
final List eCtorArgs = new ArrayList<>();
CircularDependencyException theCircularDependencyException = null;
InstallDependencyException theInstallDependencyException = null;
out: {
for ( Constructor> eCtor : theCtors ) {
try {
Dependency> eParamDependency;
for ( Parameter eParam : eCtor.getParameters() ) {
eParamDependency = toDependencyMatch( theDependencies, eParam.getType(), toParamAlias( eParam ), aProfiles );
eCtorArgs.add( eParamDependency.toInstance( aDependencies, aVistedDependencies, aProfiles ) );
eCtorDependencies.add( eParamDependency );
}
theInstance = (T) eCtor.newInstance( eCtorArgs.toArray() );
theInstance = intercept( theInstance );
for ( Dependency> eDependency : eCtorDependencies ) {
eDependency._dangling = null; // Dangling instances have been assigned to instance as above, they are not dangling any more
}
// if ( !_instanceMetrics.isSingleton() ) {
_dependencies = eCtorDependencies.toArray( new Dependency>[eCtorDependencies.size()] );
_constructor = eCtor;
// }
break out;
}
catch ( InstantiationException | IllegalAccessException | InvocationTargetException | CircularDependencyException e ) {
if ( e instanceof CircularDependencyException ) {
theCircularDependencyException = (CircularDependencyException) e;
}
else {
theInstallDependencyException = new InstallDependencyException( "Cannot install dependency <" + this + ">!", this, e );
}
LOGGER.log( Level.INFO, "Skipping dependnecy's <" + this + "> constructor with parameters <" + VerboseTextBuilder.asString( eCtor.getParameters() ) + "> as of: " + e.getMessage() );
}
eCtorDependencies.clear();
eCtorArgs.clear();
}
if ( theCircularDependencyException != null ) {
throw theCircularDependencyException;
}
if ( theInstallDependencyException != null ) {
throw theInstallDependencyException;
}
throw new UnsatisfiedDependencyException( "Cannot satisfy all required dependencies for dependency <{0}> for its constructors!", this, aDependencies );
}
}
if ( !_instanceMetrics.isSingleton() ) {
_dangling = theInstance; // We created an instance which is still dangling as some other dependency has to clear it's dangling status
}
if ( _initializer != null ) {
if ( _singleton == null || _isSetupSingletonFromInstance ) {
final Dependency> theDependency = toMatchingDependencyByClaim( aDependencies, _initializer );
final Object theSetup = theDependency.toInstance( aDependencies, aVistedDependencies, aProfiles );
theInstance = _initializer.toInstance( theSetup, theInstance );
_isSetupSingletonFromInstance = false;
}
}
if ( _instanceMetrics.isSingleton() && _singleton == null ) {
_singleton = theInstance;
}
_instances.add( theInstance );
return theInstance;
}
/**
* Hook method to be overwritten by sub-classes in order to decorate new
* aspects to the produced instance.
*
* @param aInstance The instance to be post processed.
*
* @return The (new) post processed instance.
*/
protected T intercept( T aInstance ) {
if ( _reactor != null ) {
aInstance = _reactor.intercept( aInstance, this );
}
return aInstance;
}
// /////////////////////////////////////////////////////////////////////////
// TEST HOOKS:
// /////////////////////////////////////////////////////////////////////////
/**
* Determines the constructors which can be satisfied by the
* {@link Dependency} declarations known by the {@link Reactor}.
*
* @param aDependencies The dependencies which to use for lookup.
* @param aProfiles The profiles as provided by the {@link Reactor} to take
* into account.
*
* @return An array with the suitable constructors or null if none was
* found.
*
* @throws AmbigousDependencyException thrown in case for a required
* {@link Dependency} there are more than one matching
* {@link Dependency} candidates in the {@link Reactor}.
* @throws UnsatisfiedDependencyException thrown in case for a required
* {@link Dependency} none matching {@link Dependency} candidates in
* the {@link Reactor} have been found (taking the profiles applied
* into account) for the constructors of the given
* {@link Dependency}.
*/
Constructor>[] toSuitableConstructors( Dependency>[] aDependencies, Object... aProfiles ) throws AmbigousDependencyException, UnsatisfiedDependencyException {
final Constructor>[] aConstructors = getType().getConstructors();
final List> theCtors = new ArrayList<>();
for ( Constructor> eCtr : aConstructors ) {
out: {
for ( Parameter eParam : eCtr.getParameters() ) {
if ( !hasDependencyMatch( aDependencies, eParam.getType(), toParamAlias( eParam ), aProfiles ) ) {
break out;
}
}
theCtors.add( eCtr );
}
}
if ( theCtors.size() == 0 ) {
throw new UnsatisfiedDependencyException( "Cannot find suitable constructor for dependency <" + this + "> as none constructor matches the provided dependencies <" + VerboseTextBuilder.asString( aDependencies ) + ">!", this, aDependencies );
}
return toOrderedConstructors( theCtors );
}
// /////////////////////////////////////////////////////////////////////////
// HELPER:
// /////////////////////////////////////////////////////////////////////////
/**
* Determines whether the given type can be satisfied by the provided
* dependencies taking the tags into account.
*
* @param aDependencies The dependencies which to use for testing.
* @param aType The type to be tested whether it can be satisfied by one of
* the provided dependencies.
* @param aAlias The alias assigned to the given type.
* @param aProfiles The profiles as provided by the {@link Reactor} to take
* into account.
*
* @return True in case the type can be satisfied by the provided
* dependencies.
*
* @throws AmbigousDependencyException thrown in case for a required
* {@link Dependency} there are more than one matching
* {@link Dependency} candidates in the {@link Reactor}.
*/
private boolean hasDependencyMatch( Dependency>[] aDependencies, Class> aType, String aAlias, Object... aProfiles ) throws AmbigousDependencyException {
final Set> theDependencies = toMatchingDependencies( aDependencies, aType, aAlias, aProfiles );
if ( theDependencies.size() > 1 ) {
throw new AmbigousDependencyException( "There are <" + theDependencies.size() + "> dependencies matching the type <" + aType.getName() + "> required by the dependency<" + this + ">: " + VerboseTextBuilder.asString( theDependencies ), this, theDependencies.toArray( new Dependency>[theDependencies.size()] ) );
}
return theDependencies.size() == 1;
}
/**
* Test whether the given {@link Dependency}'s tags matches at least one tag
* from this {@link Dependency}.
*
* @param aDependency The {@link Dependency} which's tags are to be tested.
*
* @return True in case we have a tag match.
*/
private boolean hasTagMatchWithDependency( Dependency> aDependency ) {
final Object[] theTagsA = getTags();
final Object[] theTagsB = aDependency.getTags();
if ( ( theTagsA == null || theTagsA.length == 0 ) && ( theTagsB == null || theTagsB.length == 0 ) ) {
return true;
}
if ( ( theTagsA != null && theTagsA.length != 0 ) && ( theTagsB == null || theTagsB.length == 0 ) ) {
return false;
}
if ( ( theTagsA == null || theTagsA.length == 0 ) && ( theTagsB != null && theTagsB.length != 0 ) ) {
return false;
}
for ( Object eObjA : theTagsA ) {
for ( Object eObjB : theTagsB ) {
if ( eObjA.toString().equalsIgnoreCase( eObjB.toString() ) ) {
return true;
}
}
}
return false;
}
/**
* Determines the {@link Dependency} matching the given type as of the
* provided {@link Dependency} declarations and their tags.
*
* @param aDependencies The {@link Dependency} declarations which to use for
* testing.
* @param aType The type for which to determine the matching
* {@link Dependency}.
* @param aAlias The alias assigned to the given type.
* @param aProfiles The profiles as provided by the {@link Reactor} to take
* into account.
*
* @return The matching {@link Dependency}.
*
* @throws AmbigousDependencyException thrown in case for a required
* {@link Dependency} there are more than one matching
* {@link Dependency} candidates in the {@link Reactor}.
*
* @throws UnsatisfiedDependencyException thrown in case for a required
* {@link Dependency} none matching {@link Dependency} candidates in
* the {@link Reactor} have been found (taking the profiles applied
* into account) for the constructors of the given
* {@link Dependency}.
*/
private Dependency> toDependencyMatch( Dependency>[] aDependencies, Class> aType, String aAlias, Object... aProfiles ) throws AmbigousDependencyException, UnsatisfiedDependencyException {
final Set> theDependencies = toMatchingDependencies( aDependencies, aType, aAlias, aProfiles );
if ( theDependencies.size() > 1 ) {
throw new AmbigousDependencyException( "There are <" + theDependencies.size() + "> dependencies matching the type <" + aType.getName() + "> required by the dependency<" + this + ">: " + VerboseTextBuilder.asString( theDependencies ), this, theDependencies.toArray( new Dependency>[theDependencies.size()] ) );
}
if ( theDependencies.isEmpty() ) {
throw new UnsatisfiedDependencyException( "There are no dependencies matching the type <" + aType.getName() + "> required by the dependency<" + this + ">: " + VerboseTextBuilder.asString( aDependencies ), this, aDependencies );
}
return theDependencies.iterator().next();
}
/**
* Determines those {@link Dependency} declarations which are ambiguous by
* considering their alias whether them are matching or not the target
* (parameter) alias.
*
* @param aDependencies The ambiguous {@link Dependency} declarations which
* tags are to be matched against the (parameter) alias.
*
* @param aAlias The alias to be matched.
*
* @return The (still) matching {@link Dependency} declarations.
*/
private Set> toMatchingAmbigousDependenciesByAlias( Set> aDependencies, String aAlias ) {
if ( aAlias == null || aAlias.isEmpty() ) {
return aDependencies;
}
final Set> theAmbigousDependencies = new HashSet<>( aDependencies );
final Iterator> e = aDependencies.iterator();
while ( e.hasNext() ) {
if ( !e.next().getAlias().equalsIgnoreCase( aAlias ) ) {
e.remove();
}
}
if ( aDependencies.size() == 0 ) {
return theAmbigousDependencies;
}
return aDependencies;
}
/**
* Determines those {@link Dependency} declarations which are ambiguous by
* considering their profiles whether them are matching or not the given
* profiles.
*
* @param aDependencies The ambiguous {@link Dependency} declarations which
* tags are to be matched against the (parameter) alias.
* @param aProfiles The profiles as provided by the {@link Reactor} to take
* into account.
*
* @return The (still) matching {@link Dependency} declarations.
*/
private Set> toMatchingAmbigousDependenciesByProfiles( Set> aDependencies, Object[] aProfiles ) {
if ( aProfiles == null || aProfiles.length == 0 ) {
return aDependencies;
}
final Set> theAmbigousDependencies = new HashSet<>( aDependencies );
final Iterator> e = aDependencies.iterator();
Dependency> eNext;
while ( e.hasNext() ) {
eNext = e.next();
if ( !eNext.hasProfile( aProfiles ) || eNext.getProfiles() == null || eNext.getProfiles().length == 0 ) {
e.remove();
}
}
if ( aDependencies.size() == 0 ) {
return theAmbigousDependencies;
}
return aDependencies;
}
/**
* Determines the {@link Dependency} declarations resulting form keeping
* only {@link Dependency} declarations not being ambiguous any more after
* probing against the {@link InitializerClaim} instance.
*
* @param aDependencies The dependencies which's ambiguousness is to be
* resolved by the {@link InitializerClaim} instance being declared.
*
* @return The {@link Dependency} declarations after removing ambiguousness.
*
* @throws AmbigousInitializerException is thrown in case one
* {@link InitializerClaim} instance can be matched by multiple
* dependencies.
* @throws AmbigousFactoryException is thrown in case one
* {@link FactoryClaim} instance can be matched by multiple
* dependencies.
* @throws AmbigousClaimException is thrown in case one {@link Claim}
* instance can be matched by multiple dependencies.
*
*/
private Dependency> toMatchingDependencyByClaim( Dependency>[] aDependencies, Claim aClaim ) throws AmbigousInitializerException, AmbigousFactoryException, AmbigousClaimException {
Dependency> theClaim = null;
if ( aClaim != null ) {
for ( Dependency> eDependency : aDependencies ) {
if ( aClaim.isClaim( eDependency ) ) {
if ( theClaim != null ) {
if ( aClaim instanceof InitializerClaim ) {
throw new AmbigousInitializerException( "The setup (claim) <" + aClaim + "> can be satisfied by more than one <" + theClaim + "> dependency <" + eDependency + ">: " + VerboseTextBuilder.asString( aDependencies ), (InitializerClaim, ?>) aClaim, this, aDependencies );
}
if ( aClaim instanceof FactoryClaim ) {
throw new AmbigousFactoryException( "The factory (claim) <" + aClaim + "> can be satisfied by more than one <" + theClaim + "> dependency <" + eDependency + ">: " + VerboseTextBuilder.asString( aDependencies ), (FactoryClaim, ?>) aClaim, this, aDependencies );
}
throw new AmbigousClaimException( "The claim <" + aClaim + "> can be satisfied by more than one <" + theClaim + "> dependency <" + eDependency + ">: " + VerboseTextBuilder.asString( aDependencies ), aClaim, this, aDependencies );
}
theClaim = eDependency;
continue;
}
}
}
return theClaim;
}
/**
* Determines the {@link Dependency} declarations resulting form keeping
* only {@link Dependency} declarations not being ambiguous any more after
* probing against the {@link Claim} instances or not being of interest for
* the {@link Claim} instances .
*
* @param aDependencies The dependencies which's ambiguousness is to be
* resolved by the {@link Claim} instances being declared.
*
* @return The {@link Dependency} declarations after removing ambiguousness.
*
* @throws AmbigousClaimException is thrown in case one {@link Claim} can be
* matched by multiple dependencies.
*/
private Dependency>[] toMatchingDependenciesByClaims( Dependency>[] aDependencies ) throws AmbigousClaimException {
final Set> theClaims = new HashSet<>();
boolean hasClaim;
for ( Claim eClaim : _claims ) {
hasClaim = false;
for ( Dependency> eDependency : aDependencies ) {
if ( eClaim.isClaim( eDependency ) ) {
if ( hasClaim ) {
throw new AmbigousClaimException( "The claim <" + eClaim + "> can be satisfied by more than one <" + eClaim + "> dependency <" + eDependency + ">: " + VerboseTextBuilder.asString( aDependencies ), eClaim, this, aDependencies );
}
theClaims.add( eDependency );
hasClaim = true;
}
}
}
final Set> theDependencies = new HashSet<>( Arrays.asList( aDependencies ) );
final Iterator> e = theDependencies.iterator();
Dependency> eDependency;
while ( e.hasNext() ) {
eDependency = e.next();
for ( Dependency> eClaim : theClaims ) {
if ( eClaim != eDependency && eClaim.getType().isAssignableFrom( eDependency.getType() ) ) {
e.remove();
continue;
}
}
}
return theDependencies.toArray( new Dependency>[theDependencies.size()] );
}
/**
* Determines those {@link Dependency} declarations which are ambiguous by
* considering their tags whether them are matching or not.
*
* @param aDependencies The ambiguous {@link Dependency} declarations which
* tags are to be matched against this {@link Dependency}.
*
* @return The (still) matching {@link Dependency} declarations.
*/
private Set> toMatchingAmbigousDependenciesByTag( Set> aDependencies ) {
final Set> theAmbigousDependencies = new HashSet<>( aDependencies );
final Iterator> e = aDependencies.iterator();
while ( e.hasNext() ) {
if ( !hasTagMatchWithDependency( e.next() ) ) {
e.remove();
}
}
if ( aDependencies.size() == 0 ) {
return theAmbigousDependencies;
}
return aDependencies;
}
/**
* Determines the dependencies matching the type and in case of ambiguous
* hits also by tag (the tags to test against are implicit as of this
* {@link Dependency}).
*
* @param aDependencies The {@link Dependency} declarations which to use for
* testing.
* @param aType The type for which to test the {@link Dependency}
* declarations against.
* @param aAlias The alias assigned to the given type.
* @param aProfiles The profiles as provided by the {@link Reactor} to take
* into account.
*
* @return A set with the determined {@link Dependency} declarations..
*/
private Set> toMatchingDependencies( Dependency>[] aDependencies, Class> aType, String aAlias, Object... aProfiles ) {
Set> theDependencies = toMatchingDependenciesByType( aDependencies, aType );
if ( theDependencies.size() > 1 ) {
theDependencies = toMatchingAmbigousDependenciesByTag( theDependencies );
}
if ( theDependencies.size() > 1 ) {
theDependencies = toMatchingAmbigousDependenciesByAlias( theDependencies, aAlias );
}
if ( theDependencies.size() > 1 ) {
theDependencies = toMatchingAmbigousDependenciesByProfiles( theDependencies, aProfiles );
}
return theDependencies;
}
/**
* Determines the matching {@link Dependency} declarations for the given
* type taking the tags into account.
*
* @param aDependencies The {@link Dependency} declarations which to use for
* testing.
* @param aType The type for which to retrieve the matching
* {@link Dependency} declarations.
*
* @return A set with the determined {@link Dependency} declarations..
*/
private Set> toMatchingDependenciesByType( Dependency>[] aDependencies, Class> aType ) {
final Set> theDependencies = new HashSet<>();
for ( Dependency> eDependency : aDependencies ) {
if ( eDependency != this ) {
if ( aType.isAssignableFrom( eDependency.getType() ) ) {
theDependencies.add( eDependency );
}
}
}
return theDependencies;
}
/**
* Sorts the provided constructors so that the one with the most arguments
* is first and the one with the least arguments is last. This is for making
* sure that a dependency gets as many dependency injected as possible as
* the fist constructor is tried out first, the second next, and so on.
*
* @param aCtors The constructors to be ordered accordingly.
*
* @return The accordingly ordered constructors.
*/
private Constructor>[] toOrderedConstructors( List> aCtors ) {
int i = 0;
final Constructor>[] theResult = new Constructor>[aCtors.size()];
while ( i < theResult.length ) {
Constructor> maxCtor = null;
for ( Constructor> eCtor : aCtors ) {
if ( maxCtor == null || maxCtor.getParameters().length < eCtor.getParameters().length ) {
maxCtor = eCtor;
}
}
aCtors.remove( maxCtor );
theResult[i] = maxCtor;
i++;
}
return theResult;
}
/**
* Retrieves the alias from the {@link Alias} annotation of a parameter.
*
* @param aParam The param from which to retrieve the alias.
*
* @return The according alias or null if none was detected.
*/
private String toParamAlias( Parameter aParam ) {
final Alias theAlias = aParam.getAnnotation( Alias.class );
return theAlias != null ? theAlias.value() : aParam.getName();
}
/**
* Determines an alias for the given type, taking enclosing classes into
* account.
*
* @param aType The type for which to get the alias.
*
* @return The according alias.
*/
private static String toAlias( Class> aType ) {
String theAlias = null;
if ( aType != null ) {
theAlias = aType.getSimpleName();
while ( ( aType = aType.getEnclosingClass() ) != null ) {
theAlias = aType.getSimpleName() + Delimiter.INNER_CLASS.getChar() + theAlias;
}
}
return theAlias;
}
}