org.refcodes.decoupling.Reactor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of refcodes-decoupling Show documentation
Show all versions of refcodes-decoupling Show documentation
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.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.function.Function;
import org.refcodes.factory.Factory;
/**
* A {@link Reactor}'s instance is used for breaking up dependencies between a
* Java software system's components or modules, which enables them to be
* developed and tested independently (dependency injection and inversion of
* control). The reactor wires components or modules together to form an
* interacting application. The Reactor uses dependency injection and inversion
* of control, which are two techniques that help achieve decoupling by allowing
* components to depend on abstractions instead of concrete implementations.
*/
public class Reactor {
// /////////////////////////////////////////////////////////////////////////
// VARIABLES:
// /////////////////////////////////////////////////////////////////////////
protected List> _dependencies = new ArrayList<>();
protected List _interceptors = new ArrayList<>();
// /////////////////////////////////////////////////////////////////////////
// METHODS:
// /////////////////////////////////////////////////////////////////////////
/**
* Adds a {@link Dependency} to the {@link Reactor} which's
* {@link DependencyBuilder} is returned for fluently configuring your
* {@link Dependency}.
*
* @param The type of the {@link Dependency}.
*
* @param aType The type of the {@link Dependency}.
*
* @return An instance of the {@link DependencyBuilder} class which is used
* to fluently configure your {@link Dependency}.
*/
public DependencyBuilder addDependency( Class aType ) {
final DependencyBuilder theBuilder = new DependencyBuilder<>( aType );
_dependencies.add( theBuilder );
return theBuilder;
}
/**
* Adds a {@link Dependency} to the {@link Reactor} which's
* {@link DependencyBuilder} is returned for fluently configuring your
* {@link Dependency}.
*
* @param The type of the {@link Dependency}.
*
* @param aInstance The instance of the {@link Dependency}.
*
* @return An instance of the {@link DependencyBuilder} class which is used
* to fluently configure your {@link Dependency}.
*/
public DependencyBuilder addDependency( T aInstance ) {
final DependencyBuilder theBuilder = new DependencyBuilder<>( aInstance ).withInstanceMetrics( InstanceMode.SINGLETON_IS_MANDATORY );
_dependencies.add( theBuilder );
return theBuilder;
}
/**
* Adds a {@link Dependency} fabricated by a factory.
*
* @param The type of the factory producing the {@link Dependency}.
* @param The type of the {@link Dependency} itself.
* @param aType The type of the {@link Dependency} to be fabricated by the
* factory.
* @param aFactory The {@link Factory} instance producing the according
* {@link Dependency} declaration(s).
*
* @return An instance of the {@link DependencyBuilder} class which is used
* to fluently configure your {@link Dependency}.
*/
public , T> DependencyBuilder addDependency( Class aType, F aFactory ) {
final DependencyBuilder theContext = new DependencyBuilder<>( aFactory ).withAlias( UUID.randomUUID().toString() );
_dependencies.add( theContext );
final DependencyBuilder theFactory = new DependencyBuilder<>( aType ).withFactory( theContext.getType(), ( f ) -> aFactory.create(), theContext.getAlias() );
_dependencies.add( theFactory );
return theFactory;
}
/**
* Adds a {@link Dependency} fabricated by a factory.
*
* @param The type of the factory producing the {@link Dependency}.
* @param The type of the {@link Dependency} itself.
* @param aType The type of the {@link Dependency} to be fabricated by the
* factory.
* @param aFactory The type of the {@link Factory} producing the according
* {@link Dependency} declaration(s).
*
* @return An instance of the {@link DependencyBuilder} class which is used
* to fluently configure your {@link Dependency}.
*/
public , T> DependencyBuilder addDependency( Class aType, Class aFactory ) {
return addDependency( aType, aFactory, Factory::create );
}
/**
* Adds a {@link Dependency} fabricated from factory's context
* ({@link Dependency}).
*
* @param The type of the factory's context ({@link Dependency}).
* @param The type of the {@link Dependency} itself.
* @param aType The type of the {@link Dependency} to be fabricated by the
* factory.
* @param aDependency The context ({@link Dependency}) used by the factory
* to produce the resulting {@link Dependency}.
* @param aFactory The factory {@link Function} using the context to produce
* the according {@link Dependency} declaration(s).
*
* @return An instance of the {@link DependencyBuilder} class which is used
* to fluently configure your {@link Dependency}.
*/
public DependencyBuilder addDependency( Class aType, Class aDependency, Function aFactory ) {
final DependencyBuilder theContext = new DependencyBuilder<>( aDependency ).withAlias( UUID.randomUUID().toString() );
_dependencies.add( theContext );
final DependencyBuilder theFactory = new DependencyBuilderFactory<>( aType, theContext ).withFactory( theContext.getType(), aFactory::apply, theContext.getAlias() );
_dependencies.add( theFactory );
return theFactory;
}
/**
* Tests whether the given {@link DependencyInterceptor} instance has been
* added.
*
* @param aInterceptor The {@link DependencyInterceptor} instance for which
* to test if it has been added.
*
* @return True if the given {@link DependencyInterceptor} instance has been
* added already.
*/
public boolean hasInterceptor( DependencyInterceptor aInterceptor ) {
return _interceptors.contains( aInterceptor );
}
/**
* Adds the given {@link DependencyInterceptor} instance. The
* {@link DependencyInterceptor} instance itself acts as the handle which is
* used when removing the given {@link DependencyInterceptor} instance
* later.
*
* @param aInterceptor The {@link DependencyInterceptor} instance which is
* to be added.
*
* @return True if the {@link DependencyInterceptor} instance has been added
* successfully. If the {@link DependencyInterceptor} instance has
* already been added, false is returned.
*/
public boolean addInterceptor( DependencyInterceptor aInterceptor ) {
if ( !_interceptors.contains( aInterceptor ) ) {
return _interceptors.add( aInterceptor );
}
return false;
}
/**
* Removes the {@link DependencyInterceptor} instance. In case the
* {@link DependencyInterceptor} instance has not been added before, then
* false is returned.
*
* @param aInterceptor The {@link DependencyInterceptor} instance which is
* to be removed.
*
* @return True if the {@link DependencyInterceptor} instance has been
* removed successfully. If there was none such
* {@link DependencyInterceptor} instance or if the
* {@link DependencyInterceptor} instance has already been removed,
* then false is returned.
*/
public boolean removeInterceptor( DependencyInterceptor aInterceptor ) {
return _interceptors.remove( aInterceptor );
}
/**
* Creates a {@link Context} which describes the components and modules
* wired together by this {@link Reactor}.
*
* @return The accordingly wired {@link Context} instance.
*
* @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 CircularDependencyException thrown in case there is some
* (transitive) circular dependency between two {@link Dependency}
* instances.
* @throws DuplicateDependencyException is thrown in case there are multiple
* identical dependencies applicable by the {@link Reactor}.
* @throws DuplicateClaimException is thrown in case there are multiple
* identical claims declared by a {@link Dependency}.
* @throws UnsatisfiedClaimException is thrown in case a {@link Claim}
* instance cannot be satisfied by any known {@link Dependency}
* instance.
* @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 UnsatisfiedInitializerException is thrown in case a
* {@link InitializerClaim} instance cannot be satisfied by any
* known {@link Dependency} declaration.
* @throws AmbigousFactoryException is thrown in case one
* {@link FactoryClaim} instance can be matched by multiple
* dependencies.
* @throws UnsatisfiedFactoryException is thrown in case a
* {@link FactoryClaim} instance cannot be satisfied by any known
* {@link Dependency} declaration.
* @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).
*/
public Context createContext() throws AmbigousDependencyException, UnsatisfiedDependencyException, CircularDependencyException, DuplicateDependencyException, DuplicateClaimException, UnsatisfiedClaimException, AmbigousClaimException, AmbigousInitializerException, UnsatisfiedInitializerException, AmbigousFactoryException, UnsatisfiedFactoryException, InstallDependencyException {
return createContext( new Object[] {} );
}
/**
* Creates a {@link Context}, regarding the provided profiles, which
* describes the components and modules wired together by this
* {@link Reactor}.
*
* @param aProfiles The profiles to take into account when creating the
* {@link Context} instance.
*
* @return The accordingly wired {@link Context} instance, as of the
* provided profiles.
*
* @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 CircularDependencyException thrown in case there is some
* (transitive) circular dependency between two {@link Dependency}
* instances.
* @throws DuplicateDependencyException is thrown in case there are multiple
* identical dependencies applicable by the {@link Reactor}.
* @throws DuplicateClaimException is thrown in case there are multiple
* identical claims declared by a {@link Dependency}.
* @throws UnsatisfiedClaimException is thrown in case a {@link Claim}
* instance cannot be satisfied by any known {@link Dependency}
* instance.
* @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 UnsatisfiedInitializerException is thrown in case a
* {@link InitializerClaim} instance cannot be satisfied by any
* known {@link Dependency} declaration.
* @throws AmbigousFactoryException is thrown in case one
* {@link FactoryClaim} instance can be matched by multiple
* dependencies.
* @throws UnsatisfiedFactoryException is thrown in case a
* {@link FactoryClaim} instance cannot be satisfied by any known
* {@link Dependency} declaration.
* @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).
*/
public Context createContext( String... aProfiles ) throws AmbigousDependencyException, UnsatisfiedDependencyException, CircularDependencyException, DuplicateDependencyException, DuplicateClaimException, UnsatisfiedClaimException, AmbigousClaimException, AmbigousInitializerException, UnsatisfiedInitializerException, AmbigousFactoryException, UnsatisfiedFactoryException, InstallDependencyException {
return createContext( (Object[]) aProfiles );
}
/**
* Creates a {@link Context}, regarding the provided profiles, which
* describes the components and modules wired together by this
* {@link Reactor}.
*
* @param aProfiles The profiles to take into account when creating the
* {@link Context} instance.
*
* @return The accordingly wired {@link Context} instance, as of the
* provided profiles.
*
* @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 CircularDependencyException thrown in case there is some
* (transitive) circular dependency between two {@link Dependency}
* instances.
* @throws DuplicateDependencyException is thrown in case there are multiple
* identical dependencies applicable by the {@link Reactor}.
* @throws DuplicateClaimException is thrown in case there are multiple
* identical claims declared by a {@link Dependency}.
* @throws UnsatisfiedClaimException is thrown in case a {@link Claim}
* instance cannot be satisfied by any known {@link Dependency}
* instance.
* @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 UnsatisfiedInitializerException is thrown in case a
* {@link InitializerClaim} instance cannot be satisfied by any
* known {@link Dependency} declaration.
* @throws AmbigousFactoryException is thrown in case one
* {@link FactoryClaim} instance can be matched by multiple
* dependencies.
* @throws UnsatisfiedFactoryException is thrown in case a
* {@link FactoryClaim} instance cannot be satisfied by any known
* {@link Dependency} declaration.
* @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).
*/
public Context createContext( Object... aProfiles ) throws AmbigousDependencyException, UnsatisfiedDependencyException, CircularDependencyException, DuplicateDependencyException, DuplicateClaimException, UnsatisfiedClaimException, AmbigousClaimException, AmbigousInitializerException, UnsatisfiedInitializerException, AmbigousFactoryException, UnsatisfiedFactoryException, InstallDependencyException {
final Context theCtx = toContext( aProfiles );
final Dependency>[] theDependencies = toDependencies( aProfiles );
toDependencies( theDependencies );
for ( Dependency> eDependency : theDependencies ) {
try {
if ( eDependency.getInstanceMetrics().isMandatory() ) {
eDependency.toInstance( theDependencies, aProfiles );
}
}
catch ( AmbigousDependencyException e ) {
throw new AmbigousDependencyException( "Cannot create context as of an ambiguous <{0}> dependency!", e.getDependency(), e.getDependencies(), e );
}
catch ( UnsatisfiedDependencyException e ) {
throw new UnsatisfiedDependencyException( "Cannot create context as of an unsatisfied <{0}> dependency!", e.getDependency(), e.getDependencies(), e );
}
}
theCtx.initialize( theDependencies );
return theCtx;
}
// /////////////////////////////////////////////////////////////////////////
// HOOKS:
// /////////////////////////////////////////////////////////////////////////
/**
* Hook method to create (and prepare) an empty {@link Context} (sub-class).
*
* @param aProfiles The profiles being applied when creating the
* {@link Context}.
*
* @return The according {@link Context} (sub-class) instance.
*/
protected Context toContext( Object[] aProfiles ) {
return new Context( aProfiles );
}
/**
* Invokes all registered {@link DependencyInterceptor} instances for the
* given {@link Dependency} declaration's instance for intercepting
* purposes.
*
* @param The type of the {@link Dependency}
* @param aInstance The instance created for the provided {@link Dependency}
* declaration.
* @param aDependency The {@link Dependency} declaration.
*
* @return The intercepted instance.
*/
@SuppressWarnings("unchecked")
protected T intercept( T aInstance, Dependency aDependency ) {
if ( aInstance == null ) {
throw new NullPointerException( "The provided instance must not be !" );
}
for ( DependencyInterceptor eInterceptor : _interceptors ) {
aInstance = (T) eInterceptor.intercept( aInstance, aDependency );
if ( aInstance == null ) {
throw new NullPointerException( "The intercepted work piece must not be !" );
}
}
return aInstance;
}
// /////////////////////////////////////////////////////////////////////////
// TEST HOOKS:
// /////////////////////////////////////////////////////////////////////////
/**
* Returns a list of {@link Dependency} declarations matching the provided
* profiles.
*
* @param aProfiles The profiles with which to match.
*
* @return The according list of {@link Dependency} declarations.
*/
Dependency>[] toDependencies( Object... aProfiles ) {
final List> theDependencies = new ArrayList<>();
for ( DependencyBuilder> eDependency : _dependencies ) {
if ( eDependency.hasProfile( aProfiles ) ) {
theDependencies.add( new Dependency<>( eDependency, this ) );
}
}
return theDependencies.toArray( new Dependency>[theDependencies.size()] );
}
// /////////////////////////////////////////////////////////////////////////
// HELPER:
// /////////////////////////////////////////////////////////////////////////
private void toDependencies( Dependency>[] theDependencies ) throws DuplicateClaimException, UnsatisfiedInitializerException, UnsatisfiedFactoryException, UnsatisfiedClaimException, DuplicateDependencyException {
Claim[] eClaims;
for ( int i = 0; i < theDependencies.length; i++ ) {
// Duplicate claims:
eClaims = theDependencies[i].getClaims();
if ( theDependencies[i].getSetup() != null ) {
eClaims = Arrays.copyOf( eClaims, eClaims.length + 1 );
eClaims[eClaims.length - 1] = theDependencies[i].getSetup();
}
if ( theDependencies[i].getFactory() != null ) {
eClaims = Arrays.copyOf( eClaims, eClaims.length + 1 );
eClaims[eClaims.length - 1] = theDependencies[i].getFactory();
}
for ( int a = 0; a < eClaims.length; a++ ) {
for ( int b = a + 1; b < eClaims.length; b++ ) {
if ( b != a && eClaims[b].equals( eClaims[a] ) ) {
throw new DuplicateClaimException( "An identical claim <{0}> for dependency <{2}> already exists!", eClaims[a], eClaims, theDependencies[i] );
}
}
// Unsatisfied claims:
out: {
for ( Dependency> eDependency : theDependencies ) {
if ( eDependency != theDependencies[i] ) {
if ( eClaims[a].isClaim( eDependency ) ) {
break out;
}
}
}
if ( eClaims[a] instanceof InitializerClaim, ?> ) {
throw new UnsatisfiedInitializerException( "The initializer <{0}> for dependency <{1}> cannot be satisfied by any of the provided dependencies <{2}>!", (InitializerClaim, ?>) eClaims[a], theDependencies[i], theDependencies );
}
if ( eClaims[a] instanceof FactoryClaim, ?> ) {
throw new UnsatisfiedFactoryException( "The initializer <{0}> for dependency <{1}> cannot be satisfied by any of the provided dependencies <{2}>!", (FactoryClaim, ?>) eClaims[a], theDependencies[i], theDependencies );
}
throw new UnsatisfiedClaimException( "The initializer <{0}> for dependency <{1}> cannot be satisfied by any of the provided dependencies <{2}>!", eClaims[a], theDependencies[i], theDependencies );
}
}
// Duplicate dependencies:
for ( int j = i + 1; j < theDependencies.length; j++ ) {
if ( theDependencies[j].equals( theDependencies[i] ) ) {
throw new DuplicateDependencyException( "An identical dependency <{0}> already exists as of the provided dependencies <{1}>!", theDependencies[i], theDependencies );
}
}
}
}
// /////////////////////////////////////////////////////////////////////////
// INNER CLASSES:
// /////////////////////////////////////////////////////////////////////////
private static class DependencyBuilderFactory extends DependencyBuilder {
private final DependencyBuilder> _dependencyBuilder;
DependencyBuilderFactory( Class aType, DependencyBuilder> aDependencyBuilder ) {
super( aType );
_dependencyBuilder = aDependencyBuilder;
}
@Override
public void setInstanceMetrics( InstanceMetrics aInstanceMetrics ) {
_dependencyBuilder.setInstanceMetrics( toInstanceMetrics( aInstanceMetrics ) );
super.setInstanceMetrics( aInstanceMetrics );
};
@Override
public void setInstanceMetrics( InstanceMode aInstanceMode ) {
_dependencyBuilder.setInstanceMetrics( toInstanceMetrics( aInstanceMode ) );
super.setInstanceMetrics( aInstanceMode );
};
@Override
public DependencyBuilder withInstanceMetrics( InstanceMetrics aInstanceMetrics ) {
_dependencyBuilder.setInstanceMetrics( toInstanceMetrics( aInstanceMetrics ) );
return super.withInstanceMetrics( aInstanceMetrics );
};
@Override
public DependencyBuilder withInstanceMetrics( InstanceMode aInstanceMode ) {
_dependencyBuilder.setInstanceMetrics( toInstanceMetrics( aInstanceMode ) );
return super.withInstanceMetrics( aInstanceMode );
}
private InstanceMetrics toInstanceMetrics( InstanceMetrics aInstanceMode ) {
return aInstanceMode.isSingleton() ? aInstanceMode : new InstanceMetrics() {
@Override
public boolean isSingleton() {
return aInstanceMode.isSingleton();
}
@Override
public boolean isMandatory() {
return false;
}
};
};
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy