
com.opsbears.webcomponents.dic.InjectorConfiguration Maven / Gradle / Ivy
package com.opsbears.webcomponents.dic;
import com.opsbears.webcomponents.immutable.ImmutableArrayList;
import com.opsbears.webcomponents.immutable.ImmutableHashMap;
import com.opsbears.webcomponents.immutable.ImmutableList;
import com.opsbears.webcomponents.immutable.ImmutableMap;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import javax.annotation.concurrent.Immutable;
import javax.inject.Provider;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
/**
* Contains the full configuration that is to be used for dependency injection.
*/
@Immutable
@ParametersAreNonnullByDefault
public class InjectorConfiguration {
private final ImmutableList scopes;
/**
* List of classes that are legal to instantiate.
*/
private final ImmutableScope>> definedClasses;
/**
* List of classes that should be instantiated using factories.
*/
private final ImmutableScope> factories;
/**
* List of classes that should be instantiated using factories.
*/
private final ImmutableScope>>> factoryClasses;
/**
* List of shared classes that should be cached on first instantiation.
*/
private final ImmutableScope> sharedClasses;
/**
* List of pre-instantiated shared objects.
*/
private final ImmutableMap sharedInstances;
/**
* List of interface/class aliases.
*/
private final ImmutableScope> aliases;
private final ImmutableScope>> collectedAliases;
/**
* List of named parameter values.
*/
private final ImmutableScope> namedParameterValues;
public InjectorConfiguration() {
scopes = new ImmutableArrayList<>();
definedClasses = new ImmutableScope<>(new ImmutableHashMap<>());
factories = new ImmutableScope<>(new ImmutableMapHierarchy<>());
factoryClasses = new ImmutableScope<>(new ImmutableMapHierarchy<>());
sharedClasses = new ImmutableScope<>(new ImmutableArrayList<>());
sharedInstances = new ImmutableHashMap<>();
aliases = new ImmutableScope<>(new ImmutableMapHierarchy<>());
collectedAliases = new ImmutableScope<>(new ImmutableHashMap<>());
namedParameterValues = new ImmutableScope<>(new ImmutableMapHierarchy<>());
}
private InjectorConfiguration(
ImmutableList scopes,
ImmutableScope>> definedClasses,
ImmutableScope> factories,
ImmutableScope>>> factoryClasses,
ImmutableScope> sharedClasses,
ImmutableMap sharedInstances,
ImmutableScope> aliases,
ImmutableScope>> collectedAliases,
ImmutableScope> namedParameterValues
) {
this.scopes = scopes;
this.definedClasses = definedClasses;
this.factories = factories;
this.factoryClasses = factoryClasses;
this.sharedClasses = sharedClasses;
this.sharedInstances = sharedInstances;
this.aliases = aliases;
this.collectedAliases = collectedAliases;
this.namedParameterValues = namedParameterValues;
}
private void checkScope(Class scope) {
if (!scopes.contains(scope)) {
throw new RuntimeException("BUG: " + scope.getSimpleName() + " has not been declared as a scope.");
}
}
public InjectorConfiguration withScope(Class scope) {
//noinspection unchecked
return new InjectorConfiguration(
scopes.withAdd(scope),
definedClasses.withScopedValue(scope, new ImmutableHashMap<>()),
factories.withScopedValue(scope, new ImmutableMapHierarchy<>()),
factoryClasses.withScopedValue(scope, new ImmutableMapHierarchy<>()),
sharedClasses.withScopedValue(scope, new ImmutableArrayList<>()),
sharedInstances,
aliases.withScopedValue(scope, new ImmutableMapHierarchy<>()),
collectedAliases.withScopedValue(scope, new ImmutableHashMap<>()),
namedParameterValues.withScopedValue(scope, new ImmutableMapHierarchy<>())
);
}
/**
* Marks a class as injectable on a global scope with all possible constructors.
*
* @param classDefinition the class to mark injectable
*
* @return a modified copy of this injection configuration.
*/
public InjectorConfiguration withDefined(Class classDefinition) {
//noinspection unchecked
return new InjectorConfiguration(
scopes, definedClasses.withModified(value -> value.withPut(
classDefinition,
new ImmutableArrayList<>(Arrays.asList(classDefinition.getConstructors()))
)),
factories,
factoryClasses, sharedClasses,
sharedInstances,
aliases,
collectedAliases, namedParameterValues
);
}
/**
* Marks a class as injectable on a global scope with all possible constructors.
*
* @param scope the scope to define this for.
* @param classDefinition the class to mark injectable
*
* @return a modified copy of this injection configuration.
*
* @throws ScopeNotFound if the specified scope was not found.
*/
public InjectorConfiguration withScopedDefined(Class scope, Class classDefinition) throws ScopeNotFound {
//noinspection unchecked
return new InjectorConfiguration(
scopes, definedClasses.withModified(scope, value -> value.withPut(
classDefinition,
new ImmutableArrayList<>(Arrays.asList(classDefinition.getConstructors()))
)),
factories,
factoryClasses, sharedClasses,
sharedInstances,
aliases,
collectedAliases, namedParameterValues
);
}
/**
* Marks a class as injectable on a global scope with one specific constructor.
*
* @param constructorDefinition the class to mark injectable
*
* @return a modified copy of this injection configuration.
*/
public InjectorConfiguration withDefined(Constructor constructorDefinition) {
//noinspection unchecked
return new InjectorConfiguration(
scopes, definedClasses.withModified(
scopeValue -> scopeValue
.withCompute(
constructorDefinition.getDeclaringClass(),
(key, currentValue) -> (
currentValue == null?
new ImmutableArrayList()
:
currentValue
).withAdd(constructorDefinition)
)
),
factories,
factoryClasses,
sharedClasses,
sharedInstances,
aliases,
collectedAliases,
namedParameterValues
);
}
/**
* Marks a class as injectable on a global scope with one specific constructor.
*
* @param scope the scope to define this for.
* @param constructorDefinition the class to mark injectable
*
* @return a modified copy of this injection configuration.
*
* @throws ScopeNotFound if the specified scope was not found.
*/
public InjectorConfiguration withScopedDefined(Class scope, Constructor constructorDefinition) {
//noinspection unchecked
return new InjectorConfiguration(
scopes, definedClasses.withModified(
scope,
scopeValue -> scopeValue
.withCompute(
constructorDefinition.getDeclaringClass(),
(key, currentValue) -> (
currentValue == null?
new ImmutableArrayList()
:
currentValue
).withAdd(constructorDefinition)
)
),
factories,
factoryClasses,
sharedClasses,
sharedInstances,
aliases,
collectedAliases,
namedParameterValues
);
}
/**
* Return a list of defined constructors for a certain class. May be empty.
*
* @param classDefinition the class that constructors are requested for.
*
* @return a list of constructors for the given class
*/
public ImmutableList getConstructors(Class classDefinition) {
if (definedClasses.getRootValue().containsKey(classDefinition)) {
return definedClasses.getRootValue().get(classDefinition);
}
return new ImmutableArrayList<>();
}
/**
* Return a list of defined constructors for a certain class. May be empty.
*
* @param scope the scope to return constructors for
* @param classDefinition the class that constructors are requested for.
*
* @return a list of constructors for the given class
*
* @throws ScopeNotFound if the scope was not defined
*/
public ImmutableList getScopedConstructors(Class scope, Class classDefinition) throws ScopeNotFound {
ImmutableList constructors = getConstructors(classDefinition);
ImmutableList scopedConstructors = definedClasses.getScope(scope).get(classDefinition);
if (scopedConstructors != null) {
constructors = constructors.withAddAll(scopedConstructors);
}
return constructors;
}
/**
* Specify that the given class should be created using the factory instance specified.
*
* @param classDefinition the class that should be produced using a factory.
* @param factory the factory class that creates the class definition
* @param type of the class
*
* @return a modified copy of this injection configuration.
*/
public InjectorConfiguration withFactory(
Class classDefinition,
Provider factory
) {
if (classDefinition.equals(Injector.class)) {
throw new DependencyInjectionFailedException("Cowardly refusing to define a global factory for Injector since that would lead to a Service Locator pattern. If you need the injector, please define it on a per-class or per-method basis.");
}
//noinspection unchecked
return new InjectorConfiguration(
scopes, definedClasses,
factories.withModified((value) -> value.with(classDefinition, factory)),
factoryClasses,
sharedClasses,
sharedInstances,
aliases,
collectedAliases,
namedParameterValues
);
}
/**
* Specify that the given class should be created using the factory instance specified.
*
* @param scope the scipe of this factory
* @param classDefinition the class that should be produced using a factory.
* @param factory the factory class that creates the class definition
* @param type of the class
*
* @return a modified copy of this injection configuration.
*/
public InjectorConfiguration withScopedFactory(
Class scope,
Class classDefinition,
Provider factory
) {
if (classDefinition.equals(Injector.class)) {
throw new DependencyInjectionFailedException("Cowardly refusing to define a global factory for Injector since that would lead to a Service Locator pattern. If you need the injector, please define it on a per-class or per-method basis.");
}
//noinspection unchecked
return new InjectorConfiguration(
scopes, definedClasses,
factories.withModified((value) -> value.with(classDefinition, factory)),
factoryClasses,
sharedClasses,
sharedInstances,
aliases,
collectedAliases,
namedParameterValues
);
}
/**
* Specify that the given class should be created using the factory class specified. The factory class MUST be
* instantiable via injection (e.g. defined or shared).
*
* @param classDefinition the class that should be produced using a factory.
* @param factory the factory class that creates the class definition
* @param type of the class
*
* @return a modified copy of this injection configuration.
*/
public InjectorConfiguration withFactory(
Class classDefinition,
Class extends Provider> factory
) {
if (classDefinition.equals(Injector.class)) {
throw new DependencyInjectionFailedException("Cowardly refusing to define a global factory for Injector since that would lead to a Service Locator pattern. If you need the injector, please define it on a per-class or per-method basis.");
}
//noinspection unchecked
return new InjectorConfiguration(
scopes, definedClasses,
factories,
factoryClasses.withModified((value) -> value.with(classDefinition, (Class) factory)),
sharedClasses,
sharedInstances,
aliases,
collectedAliases,
namedParameterValues
);
}
/**
* Specify that the given class should be created using the factory instance specified, for a single class
* instantiation only.
*
* @param scope the scope to apply this rule for
* @param forClass the class this rule should apply for
* @param classDefinition the class that should be produced using a factory.
* @param factory the factory class that creates the class definition
* @param type of the class
*
* @return a modified copy of this injection configuration.
*/
public InjectorConfiguration withScopedFactory(
Class scope,
Class forClass,
Class classDefinition,
Provider factory
) {
//noinspection unchecked
return new InjectorConfiguration(
scopes, definedClasses,
factories.withModified(scope, (value) -> value.with(forClass, classDefinition, factory)),
factoryClasses, sharedClasses,
sharedInstances,
aliases,
collectedAliases,
namedParameterValues
);
}
/**
* Specify that the given class should be created using the factory instance specified, for a single class
* instantiation only.
*
* @param forClass the class this rule should apply for
* @param classDefinition the class that should be produced using a factory.
* @param factory the factory class that creates the class definition
* @param type of the class
*
* @return a modified copy of this injection configuration.
*/
public InjectorConfiguration withFactory(
Class forClass,
Class classDefinition,
Provider factory
) {
//noinspection unchecked
return new InjectorConfiguration(
scopes, definedClasses,
factories.withModified((value) -> value.with(forClass, classDefinition, factory)),
factoryClasses,
sharedClasses,
sharedInstances,
aliases,
collectedAliases,
namedParameterValues
);
}
/**
* Specify that the given class should be created using the factory class specified, for a single class
* instantiation only. The factory class MUST be instantiable via injection (e.g. defined or shared).
*
* @param forClass the class this rule should apply for
* @param classDefinition the class that should be produced using a factory.
* @param factory the factory class that creates the class definition
* @param type of the class
*
* @return a modified copy of this injection configuration.
*/
public InjectorConfiguration withScopedFactory(
Class scope,
Class forClass,
Class classDefinition,
Class extends Provider> factory
) {
//noinspection unchecked
return new InjectorConfiguration(
scopes, definedClasses,
factories,
factoryClasses.withModified(
scope,
(value) -> value.with(
forClass,
classDefinition,
(Class) factory
)
),
sharedClasses,
sharedInstances,
aliases,
collectedAliases,
namedParameterValues
);
}
/**
* Specify that the given class should be created using the factory class specified, for a single class
* instantiation only. The factory class MUST be instantiable via injection (e.g. defined or shared).
*
* @param forClass the class this rule should apply for
* @param classDefinition the class that should be produced using a factory.
* @param factory the factory class that creates the class definition
* @param type of the class
*
* @return a modified copy of this injection configuration.
*/
public InjectorConfiguration withFactory(
Class forClass,
Class classDefinition,
Class extends Provider> factory
) {
//noinspection UnnecessaryLocalVariable
Class newFactory = factory;
//noinspection unchecked
return new InjectorConfiguration(
scopes, definedClasses,
factories,
factoryClasses.withModified(
value -> value.with(
forClass,
classDefinition,
(Class>)newFactory
)
),
sharedClasses,
sharedInstances,
aliases,
collectedAliases,
namedParameterValues
);
}
/**
* Specify that the given class should be created using the factory instance specified, for a single executable
* only.
*
* @param forExecutable the executable this rule should apply for.
* @param classDefinition the class that should be produced using a factory.
* @param factory the factory class that creates the class definition
* @param type of the class
*
* @return a modified copy of this injection configuration.
*/
public InjectorConfiguration withScopedFactory(
Class scope,
Executable forExecutable,
Class classDefinition,
Provider factory
) {
//noinspection unchecked
return new InjectorConfiguration(
scopes, definedClasses,
factories.withModified(scope, (value) -> value.with(forExecutable, classDefinition, factory)),
factoryClasses, sharedClasses,
sharedInstances,
aliases,
collectedAliases, namedParameterValues
);
}
/**
* Specify that the given class should be created using the factory instance specified, for a single executable
* only.
*
* @param forExecutable the executable this rule should apply for.
* @param classDefinition the class that should be produced using a factory.
* @param factory the factory class that creates the class definition
* @param type of the class
*
* @return a modified copy of this injection configuration.
*/
public InjectorConfiguration withFactory(
Executable forExecutable,
Class classDefinition,
Provider factory
) {
//noinspection unchecked
return new InjectorConfiguration(
scopes, definedClasses,
factories.withModified((value) -> value.with(forExecutable, classDefinition, factory)),
factoryClasses, sharedClasses,
sharedInstances,
aliases,
collectedAliases, namedParameterValues
);
}
/**
* Specify that the given class should be created using the factory class specified, for a single executable
* only. The factory class MUST be instantiable via injection (e.g. defined or shared).
*
* @param forExecutable the executable this rule should apply for.
* @param classDefinition the class that should be produced using a factory.
* @param factory the factory class that creates the class definition
* @param type of the class
*
* @return a modified copy of this injection configuration.
*/
public InjectorConfiguration withScopedFactory(
Class scope,
Executable forExecutable,
Class classDefinition,
Class extends Provider> factory
) {
//noinspection unchecked
return new InjectorConfiguration(
scopes, definedClasses,
factories,
factoryClasses.withModified(scope, (value) -> value.with(forExecutable, classDefinition, (Class)factory)),
sharedClasses,
sharedInstances,
aliases,
collectedAliases,
namedParameterValues
);
}
/**
* Specify that the given class should be created using the factory class specified, for a single executable
* only. The factory class MUST be instantiable via injection (e.g. defined or shared).
*
* @param forExecutable the executable this rule should apply for.
* @param classDefinition the class that should be produced using a factory.
* @param factory the factory class that creates the class definition
* @param type of the class
*
* @return a modified copy of this injection configuration.
*/
public InjectorConfiguration withFactory(
Executable forExecutable,
Class classDefinition,
Class extends Provider> factory
) {
Class newFactory = factory;
//noinspection unchecked
return new InjectorConfiguration(
scopes, definedClasses,
factories,
factoryClasses.withModified(
(value) -> value.with(
forExecutable,
classDefinition,
(Class>)newFactory
)
),
sharedClasses,
sharedInstances,
aliases,
collectedAliases,
namedParameterValues
);
}
/**
* Returns a factory instance to instantiate a certain class.
*
* @param classDefinition the class that is to be instantiated.
* @param the type of the class to be instantiated.
*
* @return a factory method for the specified class.
*/
@Nullable
public Provider getScopedFactory(Class scope, Class classDefinition) {
//noinspection unchecked
return factories.getScope(scope).get(classDefinition);
}
/**
* Returns a factory instance to instantiate a certain class.
*
* @param classDefinition the class that is to be instantiated.
* @param the type of the class to be instantiated.
*
* @return a factory method for the specified class.
*/
@Nullable
public Provider getFactory(Class classDefinition) {
//noinspection unchecked
return factories.getRootValue().get(classDefinition);
}
/**
* Returns a factory instance to instantiate a certain class.
*
* @param forClass the class this factory is needed for.
* @param classDefinition the class that is to be instantiated.
* @param the type of the class to be instantiated.
*
* @return a factory method for the specified class.
*/
@Nullable
public Provider getScopedFactory(Class scope, Class forClass, Class classDefinition) {
//noinspection unchecked
return factories.getScope(scope).get(forClass, classDefinition);
}
/**
* Returns a factory instance to instantiate a certain class.
*
* @param forClass the class this factory is needed for.
* @param classDefinition the class that is to be instantiated.
* @param the type of the class to be instantiated.
*
* @return a factory method for the specified class.
*/
@Nullable
public Provider getFactory(Class forClass, Class classDefinition) {
//noinspection unchecked
return factories.getRootValue().get(forClass, classDefinition);
}
/**
* Returns a factory instance to instantiate a certain class.
*
* @param forExecutable the executable this factory is needed for.
* @param classDefinition the class that is to be instantiated.
* @param the type of the class to be instantiated.
*
* @return a factory method for the specified class.
*/
@Nullable
public Provider getScopedFactory(Class scope, @Nullable Executable forExecutable, Class classDefinition) {
//noinspection unchecked
return factories.getScope(scope).get(forExecutable, classDefinition);
}
/**
* Returns a factory instance to instantiate a certain class.
*
* @param forExecutable the executable this factory is needed for.
* @param classDefinition the class that is to be instantiated.
* @param the type of the class to be instantiated.
*
* @return a factory method for the specified class.
*/
@Nullable
public Provider getFactory(@Nullable Executable forExecutable, Class classDefinition) {
//noinspection unchecked
return factories.getRootValue().get(forExecutable, classDefinition);
}
/**
* Returns a factory class to instantiate a certain class.
*
* @param classDefinition the class that is to be instantiated.
* @param the type of the class to be instantiated.
*
* @return a factory method for the specified class.
*/
@Nullable
public Class> getScopedFactoryClass(Class scope, Class classDefinition) {
//noinspection unchecked
return (Class)factoryClasses.getScope(scope).get(classDefinition);
}
/**
* Returns a factory class to instantiate a certain class.
*
* @param classDefinition the class that is to be instantiated.
* @param the type of the class to be instantiated.
*
* @return a factory method for the specified class.
*/
@Nullable
public Class> getFactoryClass(Class classDefinition) {
//noinspection unchecked
return (Class)factoryClasses.getRootValue().get(classDefinition);
}
/**
* Returns a factory class to instantiate a certain class.
*
* @param forClass the class this factory is needed for.
* @param classDefinition the class that is to be instantiated.
* @param the type of the class to be instantiated.
*
* @return a factory method for the specified class.
*/
@Nullable
public Class> getScopedFactoryClass(Class scope, Class forClass, Class classDefinition) {
//noinspection unchecked
return (Class)factoryClasses.getScope(scope).get(forClass, classDefinition);
}
/**
* Returns a factory class to instantiate a certain class.
*
* @param forClass the class this factory is needed for.
* @param classDefinition the class that is to be instantiated.
* @param the type of the class to be instantiated.
*
* @return a factory method for the specified class.
*/
@Nullable
public Class> getFactoryClass(Class forClass, Class classDefinition) {
//noinspection unchecked
return (Class)factoryClasses.getRootValue().get(forClass, classDefinition);
}
/**
* Returns a factory class to instantiate a certain class.
*
* @param forExecutable the executable this factory is needed for.
* @param classDefinition the class that is to be instantiated.
* @param the type of the class to be instantiated.
*
* @return a factory method for the specified class.
*/
@Nullable
public Class> getScopedFactoryClass(Class scope, @Nullable Executable forExecutable, Class classDefinition) {
//noinspection unchecked
return (Class)factoryClasses.getScope(scope).get(forExecutable, classDefinition);
}
/**
* Returns a factory class to instantiate a certain class.
*
* @param forExecutable the executable this factory is needed for.
* @param classDefinition the class that is to be instantiated.
* @param the type of the class to be instantiated.
*
* @return a factory method for the specified class.
*/
@Nullable
public Class> getFactoryClass(@Nullable Executable forExecutable, Class classDefinition) {
//noinspection unchecked
return (Class)factoryClasses.getRootValue().get(forExecutable, classDefinition);
}
/**
* Marks a class definition as injectable and shared on a global scope. the first time this class definition will
* be instantiated in an injector, it will be cached for subsequent calls. This comes with a couple of caveats:
*
* 1. the first instance of this class created, no matter in what specific context, may be cached.
* 2. it is not guaranteed that this class will only be instantiated once. The injection process may take place in
* parallel threads, so instantiation may happen multiple times. However, only a single instance will be
* returned and retained.
* 3. if you use multiple dependency injector copies, the shared instances may not be shared between them. To
* ensure singleton-ness, use the withShared method that accepts a pre-instantiated copy.
*
* @param classDefinition the class that should be shared.
*
* @return a modified copy of this injection configuration
*/
public InjectorConfiguration withScopeShared(
Class scope,
Class classDefinition
) {
//noinspection unchecked
return new InjectorConfiguration(
scopes, definedClasses,
factories,
factoryClasses,
sharedClasses.withModified(scope, (value) -> value.withAdd(classDefinition)),
sharedInstances,
aliases,
collectedAliases, namedParameterValues
);
}
/**
* Marks a class definition as injectable and shared on a global scope. the first time this class definition will
* be instantiated in an injector, it will be cached for subsequent calls. This comes with a couple of caveats:
*
* 1. the first instance of this class created, no matter in what specific context, may be cached.
* 2. it is not guaranteed that this class will only be instantiated once. The injection process may take place in
* parallel threads, so instantiation may happen multiple times. However, only a single instance will be
* returned and retained.
* 3. if you use multiple dependency injector copies, the shared instances may not be shared between them. To
* ensure singleton-ness, use the withShared method that accepts a pre-instantiated copy.
*
* @param classDefinition the class that should be shared.
*
* @return a modified copy of this injection configuration
*/
public InjectorConfiguration withShared(
Class classDefinition
) {
//noinspection unchecked
return new InjectorConfiguration(
scopes, definedClasses,
factories,
factoryClasses,
sharedClasses.withModified((value) -> value.withAdd(classDefinition)),
sharedInstances,
aliases,
collectedAliases, namedParameterValues
);
}
/**
* @return a list of classes that should be shared upon first instantiation.
*/
public ImmutableList getScopedSharedClasses(Class scope) {
return sharedClasses.getScope(scope);
}
/**
* @return a list of classes that should be shared upon first instantiation.
*/
public ImmutableList getSharedClasses() {
return sharedClasses.getRootValue();
}
/**
* Mark a class instance as shared. The type of the class will be marked as injectable and in all cases this
* class instance will be passed when requested. This holds true even with multiple injectors as long as they
* are created from the same configuration.
*
* @param instance class instance to share.
*
* @return a modified copy of this injection configuration.
*/
public InjectorConfiguration withShared(
T instance
) {
//noinspection unchecked
return new InjectorConfiguration(
scopes, definedClasses,
factories,
factoryClasses,
sharedClasses,
sharedInstances.withPut(instance.getClass(), instance),
aliases,
collectedAliases, namedParameterValues
);
}
/**
* @return a list of pre-initialized shared instances.
*/
public ImmutableMap getSharedInstances() {
return sharedInstances;
}
/**
* Defines that instead of the class/interface passed in abstractDefinition, the class specified in
* implementationDefinition should be used. The specified replacement class must be defined as injectable.
*
* @param abstractDefinition the abstract class or interface to replace.
* @param implementationDefinition the implementation class
* @param type of the abstract class/interface
* @param type if the implementation
*
* @return a modified copy of this injection configuration.
*/
public InjectorConfiguration withScopedAlias(
Class scope,
Class abstractDefinition,
Class implementationDefinition
) {
if (abstractDefinition.equals(Injector.class)) {
throw new DependencyInjectionFailedException("Cowardly refusing to define a global alias for Injector since that would lead to a Service Locator pattern. If you need the injector, please define it on a per-class or per-method basis.");
}
//noinspection unchecked
return new InjectorConfiguration(
scopes, definedClasses,
factories,
factoryClasses,
sharedClasses,
sharedInstances,
aliases.withModified(scope, (value) -> value.with(abstractDefinition, implementationDefinition)),
collectedAliases,
namedParameterValues
);
}
/**
* Defines that instead of the class/interface passed in abstractDefinition, the class specified in
* implementationDefinition should be used. The specified replacement class must be defined as injectable.
*
* @param abstractDefinition the abstract class or interface to replace.
* @param implementationDefinition the implementation class
* @param type of the abstract class/interface
* @param type if the implementation
*
* @return a modified copy of this injection configuration.
*/
public InjectorConfiguration withAlias(
Class abstractDefinition,
Class implementationDefinition
) {
if (abstractDefinition.equals(Injector.class)) {
throw new DependencyInjectionFailedException("Cowardly refusing to define a global alias for Injector since that would lead to a Service Locator pattern. If you need the injector, please define it on a per-class or per-method basis.");
}
//noinspection unchecked
return new InjectorConfiguration(
scopes, definedClasses,
factories,
factoryClasses,
sharedClasses,
sharedInstances,
aliases.withModified((value) -> value.with(abstractDefinition, implementationDefinition)),
collectedAliases,
namedParameterValues
);
}
/**
* Defines on a per-class basis that instead of the class defined in `abstractDefinition` the class defined in
* `implementationDefinition` should be used.
*
* @param forClass the class this alias should be used on.
* @param abstractDefinition the abstract class or interface to replace.
* @param implementationDefinition the implementation class
* @param type of the abstract class/interface
* @param type if the implementation
*
* @return a modified copy of this injection configuration.
*/
public InjectorConfiguration withScopedAlias(
Class scope,
Class forClass,
Class abstractDefinition,
Class implementationDefinition
) {
//noinspection unchecked
return new InjectorConfiguration(
scopes, definedClasses,
factories,
factoryClasses, sharedClasses,
sharedInstances,
aliases.withModified(scope, (value) -> value.with(forClass, abstractDefinition, implementationDefinition)),
collectedAliases,
namedParameterValues
);
}
/**
* Defines on a per-class basis that instead of the class defined in `abstractDefinition` the class defined in
* `implementationDefinition` should be used.
*
* @param forClass the class this alias should be used on.
* @param abstractDefinition the abstract class or interface to replace.
* @param implementationDefinition the implementation class
* @param type of the abstract class/interface
* @param type if the implementation
*
* @return a modified copy of this injection configuration.
*/
public InjectorConfiguration withAlias(
Class forClass,
Class abstractDefinition,
Class implementationDefinition
) {
//noinspection unchecked
return new InjectorConfiguration(
scopes, definedClasses,
factories,
factoryClasses, sharedClasses,
sharedInstances,
aliases.withModified((value) -> value.with(forClass, abstractDefinition, implementationDefinition)),
collectedAliases,
namedParameterValues
);
}
/**
* Defines on a per-method or per-constructor basis that instead of the class defined in `abstractDefinition` the
* class defined in `implementationDefinition` should be used.
*
* @param forExecutable the constructor or method this alias should be used on.
* @param abstractDefinition the abstract class or interface to replace.
* @param implementationDefinition the implementation class
* @param type of the abstract class/interface
* @param type if the implementation
*
* @return a modified copy of this injection configuration.
*/
public InjectorConfiguration withAlias(
Class scope,
Executable forExecutable,
Class abstractDefinition,
Class implementationDefinition
) {
//noinspection unchecked
return new InjectorConfiguration(
scopes, definedClasses,
factories,
factoryClasses, sharedClasses,
sharedInstances,
aliases.withModified(scope, (value) -> value.with(forExecutable, abstractDefinition, implementationDefinition)),
collectedAliases, namedParameterValues
);
}
/**
* Defines on a per-method or per-constructor basis that instead of the class defined in `abstractDefinition` the
* class defined in `implementationDefinition` should be used.
*
* @param forExecutable the constructor or method this alias should be used on.
* @param abstractDefinition the abstract class or interface to replace.
* @param implementationDefinition the implementation class
* @param type of the abstract class/interface
* @param type if the implementation
*
* @return a modified copy of this injection configuration.
*/
public InjectorConfiguration withAlias(
Executable forExecutable,
Class abstractDefinition,
Class implementationDefinition
) {
//noinspection unchecked
return new InjectorConfiguration(
scopes, definedClasses,
factories,
factoryClasses, sharedClasses,
sharedInstances,
aliases.withModified((value) -> value.with(forExecutable, abstractDefinition, implementationDefinition)),
collectedAliases, namedParameterValues
);
}
/**
* Returns an alias of abstractClass on a global scope, or null if no alias was found.
*
* @param abstractClass the abstraction that the
* @param type of the abstract class or interface
* @param type of the implementation
*
* @return the class definition of the implementation.
*/
@Nullable
public Class getScopedAlias(Class scope, Class abstractClass) {
//noinspection unchecked
return aliases.getScope(scope).get(abstractClass);
}
/**
* Returns an alias of abstractClass on a global scope, or null if no alias was found.
*
* @param abstractClass the abstraction that the
* @param type of the abstract class or interface
* @param type of the implementation
*
* @return the class definition of the implementation.
*/
@Nullable
public Class getAlias(Class abstractClass) {
//noinspection unchecked
return aliases.getRootValue().get(abstractClass);
}
/**
* Returns an alias of abstractClass on a class-scope, or null if no alias was found. The specified forClass is
* the class for scoping purposes.
*
* @param forClass the class this alias is needed for.
* @param abstractClass the abstraction that the
* @param type of the abstract class or interface
* @param type of the implementation
*
* @return the class definition of the implementation.
*/
@Nullable
public Class getScopedAlias(Class scope, Class forClass, Class abstractClass) {
//noinspection unchecked
return aliases.getScope(scope).get(forClass, abstractClass);
}
/**
* Returns an alias of abstractClass on a class-scope, or null if no alias was found. The specified forClass is
* the class for scoping purposes.
*
* @param forClass the class this alias is needed for.
* @param abstractClass the abstraction that the
* @param type of the abstract class or interface
* @param type of the implementation
*
* @return the class definition of the implementation.
*/
@Nullable
public Class getAlias(Class forClass, Class abstractClass) {
//noinspection unchecked
return aliases.getRootValue().get(forClass, abstractClass);
}
/**
* Returns an alias for `abstractClass` that should be used when executing the executable specified in `forExecutable`.
*
* @param forExecutable the executable this alias is needed for.
*
* @param abstractClass the abstraction that the
* @param type of the abstract class or interface
* @param type of the implementation
*
* @return the class definition of the implementation.
*/
@Nullable
public Class getScopedAlias(Class scope, @Nullable Executable forExecutable, Class abstractClass) {
//noinspection unchecked
return aliases.getScope(scope).get(forExecutable, abstractClass);
}
/**
* Returns an alias for `abstractClass` that should be used when executing the executable specified in `forExecutable`.
*
* @param forExecutable the executable this alias is needed for.
*
* @param abstractClass the abstraction that the
* @param type of the abstract class or interface
* @param type of the implementation
*
* @return the class definition of the implementation.
*/
@Nullable
public Class getAlias(@Nullable Executable forExecutable, Class abstractClass) {
//noinspection unchecked
return aliases.getRootValue().get(forExecutable, abstractClass);
}
/**
* This method allows for specifying one _possible_ alias. Whenever a collection (list, set, etc) of the interface
* is requested, the injector will instantiate all of these possibilities in order and pass the created list to the
* instance.
*
* This is especially useful when creating a plugin-type scenario where different modules can supply implementations
* to a specific interface.
*
* This method has no bearing on the scenario when a single instance is requested.
*
* @param abstractDefinition the abstraction to alias.
* @param implementationDefinition the actual implementation to collect.
* @param the abstraction type.
* @param the implementation type.
*
* @return a modified copy of this injection configuration.
*/
public InjectorConfiguration withScopedCollectedAlias(
Class scope,
Class abstractDefinition,
Class implementationDefinition
) {
//noinspection unchecked
ImmutableScope>> newCollectedAliases =
collectedAliases.withModified(
scope,
(value) -> {
value = value.withPutIfAbsent(abstractDefinition, new ImmutableArrayList<>());
return value
.withPut(
abstractDefinition,
value
.get(abstractDefinition)
.withAdd(implementationDefinition)
);
}
);
return new InjectorConfiguration(
scopes, definedClasses,
factories,
factoryClasses,
sharedClasses,
sharedInstances,
aliases,
newCollectedAliases,
namedParameterValues
);
}
/**
* This method allows for specifying one _possible_ alias. Whenever a collection (list, set, etc) of the interface
* is requested, the injector will instantiate all of these possibilities in order and pass the created list to the
* instance.
*
* This is especially useful when creating a plugin-type scenario where different modules can supply implementations
* to a specific interface.
*
* This method has no bearing on the scenario when a single instance is requested.
*
* @param abstractDefinition the abstraction to alias.
* @param implementationDefinition the actual implementation to collect.
* @param the abstraction type.
* @param the implementation type.
*
* @return a modified copy of this injection configuration.
*/
public InjectorConfiguration withCollectedAlias(
Class abstractDefinition,
Class implementationDefinition
) {
//noinspection unchecked
ImmutableScope>> newCollectedAliases =
collectedAliases.withModified(
(value) -> {
value = value.withPutIfAbsent(abstractDefinition, new ImmutableArrayList<>());
return value
.withPut(
abstractDefinition,
value
.get(abstractDefinition)
.withAdd(implementationDefinition)
);
}
);
return new InjectorConfiguration(
scopes, definedClasses,
factories,
factoryClasses,
sharedClasses,
sharedInstances,
aliases,
newCollectedAliases,
namedParameterValues
);
}
public ImmutableList getScopedCollectedAliases(Class scope, Class classDefinition) {
if (!collectedAliases.getScope(scope).containsKey(classDefinition)) {
return new ImmutableArrayList<>();
}
return collectedAliases.getScope(scope).get(classDefinition);
}
public ImmutableList getCollectedAliases(Class classDefinition) {
if (!collectedAliases.getRootValue().containsKey(classDefinition)) {
return new ImmutableArrayList<>();
}
return collectedAliases.getRootValue().get(classDefinition);
}
/**
* Set a certain value of a class parameter. When the specified class is instantiated and a parameter with the
* specified name is encountered in the constructor, the specified value will be used. This only works if the
* specified class was compiled with `-parameters` or the @javax.inject.Named annotation must be used.
*
* @param classDefinition The class that should receive the named parameter.
* @param parameterName Parameter to look for.
* @param value The exact value to pass.
* @param The value type.
*
* @return a modified copy of this injection configuration.
*/
public InjectorConfiguration withScopedNamedParameterValue(
Class scope,
Class classDefinition,
String parameterName,
T value
) throws MissingNamedParameterSupport {
//noinspection unchecked
NamedParameterSupportChecker.checkNamedParameterSupport(classDefinition);
//noinspection unchecked
return new InjectorConfiguration(
scopes, definedClasses,
factories,
factoryClasses,
sharedClasses,
sharedInstances,
aliases,
collectedAliases,
namedParameterValues.withModified(
scope,
(v) -> v.with(classDefinition, parameterName, value)
)
);
}
/**
* Set a certain value of a class parameter. When the specified class is instantiated and a parameter with the
* specified name is encountered in the constructor, the specified value will be used. This only works if the
* specified class was compiled with `-parameters` or the @javax.inject.Named annotation must be used.
*
* @param classDefinition The class that should receive the named parameter.
* @param parameterName Parameter to look for.
* @param value The exact value to pass.
* @param The value type.
*
* @return a modified copy of this injection configuration.
*/
public InjectorConfiguration withNamedParameterValue(
Class classDefinition,
String parameterName,
T value
) throws MissingNamedParameterSupport {
//noinspection unchecked
NamedParameterSupportChecker.checkNamedParameterSupport(classDefinition);
//noinspection unchecked
return new InjectorConfiguration(
scopes, definedClasses,
factories,
factoryClasses,
sharedClasses,
sharedInstances,
aliases,
collectedAliases,
namedParameterValues.withModified(
(v) -> v.with(classDefinition, parameterName, value)
)
);
}
/**
* Set a certain value of a constructor or method parameter. This only works if the target code was compiled with
* `-parameters` or the @javax.inject.Named annotation is used on the parameter. This overrides any possible
* aliases or other injection rules.
*
* @param executableDefinition The constructor that should receive the named parameter.
* @param parameterName Parameter to look for.
* @param value The exact value to pass.
* @param The value type.
*
* @return a modified copy of this injection configuration.
*/
public InjectorConfiguration withNamedParameterValue(
Class scope,
Executable executableDefinition,
String parameterName,
T value
) throws MissingNamedParameterSupport {
//noinspection unchecked
NamedParameterSupportChecker.checkNamedParameterSupport(executableDefinition);
//noinspection unchecked
return new InjectorConfiguration(
scopes, definedClasses,
factories,
factoryClasses, sharedClasses,
sharedInstances,
aliases,
collectedAliases,
namedParameterValues.withModified(scope, (v) -> v.with(executableDefinition, parameterName, value))
);
}
/**
* Set a certain value of a constructor or method parameter. This only works if the target code was compiled with
* `-parameters` or the @javax.inject.Named annotation is used on the parameter. This overrides any possible
* aliases or other injection rules.
*
* @param executableDefinition The constructor that should receive the named parameter.
* @param parameterName Parameter to look for.
* @param value The exact value to pass.
* @param The value type.
*
* @return a modified copy of this injection configuration.
*/
public InjectorConfiguration withNamedParameterValue(
Executable executableDefinition,
String parameterName,
T value
) throws MissingNamedParameterSupport {
//noinspection unchecked
NamedParameterSupportChecker.checkNamedParameterSupport(executableDefinition);
//noinspection unchecked
return new InjectorConfiguration(
scopes, definedClasses,
factories,
factoryClasses, sharedClasses,
sharedInstances,
aliases,
collectedAliases,
namedParameterValues.withModified((v) -> v.with(executableDefinition, parameterName, value))
);
}
@Nullable
public Object getScopedNamedParameterValue(Class scope, @Nullable Class classDefinition, String parameterName) {
return namedParameterValues.getScope(scope).get(classDefinition, parameterName);
}
@Nullable
public Object getNamedParameterValue(@Nullable Class classDefinition, String parameterName) {
return namedParameterValues.getRootValue().get(classDefinition, parameterName);
}
@Nullable
public Object getNamedParameterValue(Class scope, @Nullable Executable executable, String parameterName) {
return namedParameterValues.getScope(scope).get(executable, parameterName);
}
@Nullable
public Object getNamedParameterValue(@Nullable Executable executable, String parameterName) {
return namedParameterValues.getRootValue().get(executable, parameterName);
}
public List getScopes() {
return scopes;
}
private static class ImmutableScope {
private final V rootValue;
private final ImmutableMap scopedValues;
private ImmutableScope(
V rootValue
) {
this.rootValue = rootValue;
this.scopedValues = new ImmutableHashMap<>();
}
private ImmutableScope(
V rootValue,
ImmutableMap scopedValues
) {
this.rootValue = rootValue;
this.scopedValues = scopedValues;
}
public ImmutableScope withRootValue(V rootValue) {
return new ImmutableScope<>(
rootValue,
scopedValues
);
}
public ImmutableScope withScopedValue(K scope, V value) {
return new ImmutableScope<>(
rootValue,
scopedValues.withPut(scope, value)
);
}
public ImmutableScope withModified(Function modifier) {
return withRootValue(
modifier.apply(rootValue)
);
}
public ImmutableScope withModified(K scope, Function modifier) throws ScopeNotFound {
return withScopedValue(
scope,
modifier.apply(getScope(scope))
);
}
public V getRootValue() {
return rootValue;
}
public V getScope(K scope) throws ScopeNotFound {
if (scopedValues.containsKey(scope)) {
return scopedValues.get(scope);
}
throw new ScopeNotFound();
}
}
private static class ImmutableMapHierarchy {
private final ImmutableMap topLevel;
private final ImmutableMap> classLevel;
private final ImmutableMap> executableLevel;
ImmutableMapHierarchy() {
topLevel = new ImmutableHashMap<>();
classLevel = new ImmutableHashMap<>();
executableLevel = new ImmutableHashMap<>();
}
private ImmutableMapHierarchy(
ImmutableMap topLevel,
ImmutableMap> classLevel,
ImmutableMap> executableLevel
) {
this.topLevel = topLevel;
this.classLevel = classLevel;
this.executableLevel = executableLevel;
}
@Nullable
V get(K key) {
if (topLevel.containsKey(key)) {
return topLevel.get(key);
}
return null;
}
ImmutableMapHierarchy with(K key, V value) {
return new ImmutableMapHierarchy<>(
topLevel.withPut(key, value),
classLevel,
executableLevel
);
}
@Nullable
V get(@Nullable Class classDefinition, K key) {
if (classDefinition != null && classLevel.containsKey(classDefinition) && classLevel.get(classDefinition).containsKey(key)) {
return classLevel.get(classDefinition).get(key);
}
return get(key);
}
ImmutableMapHierarchy with(Class classDefinition, K key, V value) {
ImmutableMap> newClassLevel = classLevel.withPutIfAbsent(classDefinition, new ImmutableHashMap<>());
newClassLevel = newClassLevel.withPut(
classDefinition,
newClassLevel.get(classDefinition).withPut(key, value)
);
return new ImmutableMapHierarchy<>(
topLevel,
newClassLevel,
executableLevel
);
}
@Nullable
V get(@Nullable Executable executableDefinition, K key) {
if (executableDefinition != null && executableLevel.containsKey(executableDefinition) && executableLevel.get(executableDefinition).containsKey(key)) {
return executableLevel.get(executableDefinition).get(key);
}
return get(executableDefinition == null?null:executableDefinition.getDeclaringClass(), key);
}
ImmutableMapHierarchy with(Executable executableDefinition, K key, V value) {
ImmutableMap> newExecutableLevel = executableLevel.withPutIfAbsent(executableDefinition, new ImmutableHashMap<>());
newExecutableLevel = newExecutableLevel.withPut(
executableDefinition,
newExecutableLevel.get(executableDefinition).withPut(key, value)
);
return new ImmutableMapHierarchy<>(
topLevel,
classLevel,
newExecutableLevel
);
}
}
static class ScopeNotFound extends RuntimeException {
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy