/*
* Copyright (c) 2006 Rogério Liesenfeld
* This file is subject to the terms of the MIT license (see LICENSE.txt).
*/
package mockit.internal.injection;
import java.lang.reflect.*;
import java.util.*;
import java.util.Map.*;
import java.util.concurrent.*;
import javax.annotation.*;
import javax.inject.*;
import javax.servlet.*;
import mockit.internal.expectations.mocking.*;
import mockit.internal.reflection.*;
import mockit.internal.state.*;
import static mockit.internal.injection.InjectionPoint.*;
import static mockit.internal.util.Utilities.getClassType;
/**
* Holds state used throughout the injection process while it's in progress for a given set of tested objects.
*/
public final class InjectionState implements BeanExporter
{
@Nonnull private static final Map<InjectionPoint, Object> globalDependencies =
new ConcurrentHashMap<InjectionPoint, Object>(2);
@Nonnull private final Map<InjectionPoint, Object> testedObjects;
@Nonnull private final Map<InjectionPoint, Object> instantiatedDependencies;
@Nonnull private List<MockedType> injectables;
@Nonnull private List<InjectionPointProvider> consumedInjectables;
@Nonnull public final LifecycleMethods lifecycleMethods;
private GenericTypeReflection testedTypeReflection;
private Object currentTestClassInstance;
private Type typeOfInjectionPoint;
InjectionState()
{
testedObjects = new HashMap<InjectionPoint, Object>();
instantiatedDependencies = new HashMap<InjectionPoint, Object>();
injectables = Collections.emptyList();
consumedInjectables = new ArrayList<InjectionPointProvider>();
lifecycleMethods = new LifecycleMethods();
}
void buildListsOfInjectables(@Nonnull Object testClassInstance, @Nonnull List<MockedType> injectables)
{
currentTestClassInstance = testClassInstance;
this.injectables = new ArrayList<MockedType>(injectables);
ParameterTypeRedefinitions paramTypeRedefs = TestRun.getExecutingTest().getParameterRedefinitions();
if (paramTypeRedefs != null) {
this.injectables.addAll(paramTypeRedefs.getInjectableParameters());
}
getServletConfigForInitMethodsIfAny(testClassInstance);
}
private void getServletConfigForInitMethodsIfAny(@Nonnull Object testClassInstance)
{
if (SERVLET_CLASS != null) {
for (InjectionPointProvider injectable : injectables) {
if (injectable.getDeclaredType() == ServletConfig.class) {
lifecycleMethods.servletConfig = injectable.getValue(testClassInstance);
break;
}
}
}
}
Object getCurrentTestClassInstance() { return currentTestClassInstance; }
void setTestedTypeReflection(@Nonnull GenericTypeReflection reflection) { testedTypeReflection = reflection; }
public void setTypeOfInjectionPoint(@Nonnull Type typeOfInjectionPoint)
{
this.typeOfInjectionPoint = typeOfInjectionPoint;
}
private boolean hasSameTypeAsInjectionPoint(@Nonnull InjectionPointProvider injectable)
{
Type declaredType = injectable.getDeclaredType();
return isSameTypeAsInjectionPoint(declaredType);
}
public boolean isSameTypeAsInjectionPoint(@Nonnull Type injectableType)
{
if (testedTypeReflection.areMatchingTypes(typeOfInjectionPoint, injectableType)) {
return true;
}
if (typeOfInjectionPoint instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) typeOfInjectionPoint;
Class<?> classOfInjectionPoint = (Class<?>) parameterizedType.getRawType();
if (
Iterable.class.isAssignableFrom(classOfInjectionPoint) ||
INJECT_CLASS != null && Provider.class.isAssignableFrom(classOfInjectionPoint)
) {
Type providedType = parameterizedType.getActualTypeArguments()[0];
if (providedType.equals(injectableType)) {
return true;
}
Class<?> injectableClass = getClassType(injectableType);
Class<?> providedClass = getClassType(providedType);
return providedClass.isAssignableFrom(injectableClass);
}
}
return false;
}
@Nullable
public MockedType findNextInjectableForInjectionPoint()
{
for (MockedType injectable : injectables) {
if (hasSameTypeAsInjectionPoint(injectable) && !consumedInjectables.contains(injectable)) {
return injectable;
}
}
return null;
}
@Nonnull
List<MockedType> findInjectablesByType()
{
List<MockedType> found = new ArrayList<MockedType>();
for (MockedType injectable : injectables) {
if (hasSameTypeAsInjectionPoint(injectable) && !consumedInjectables.contains(injectable)) {
found.add(injectable);
}
}
return found;
}
@Nullable
public InjectionPointProvider getProviderByTypeAndOptionallyName(@Nonnull String nameOfInjectionPoint)
{
Type elementTypeOfIterable = getElementTypeIfIterable(typeOfInjectionPoint);
if (elementTypeOfIterable != null) {
return findInjectablesByTypeOnly(elementTypeOfIterable);
}
return findInjectableByTypeAndOptionallyName(nameOfInjectionPoint);
}
@Nullable
public Object getTestedValueForConstructorParameter(@Nonnull String nameOfInjectionPoint)
{
InjectionPoint injectionPoint = new InjectionPoint(typeOfInjectionPoint, nameOfInjectionPoint);
return testedObjects.get(injectionPoint);
}
@Nullable
private static Type getElementTypeIfIterable(@Nonnull Type injectableType)
{
if (injectableType instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) injectableType;
Class<?> classOfInjectionPoint = (Class<?>) parameterizedType.getRawType();
if (Iterable.class.isAssignableFrom(classOfInjectionPoint)) {
return parameterizedType.getActualTypeArguments()[0];
}
}
return null;
}
@Nullable
private InjectionPointProvider findInjectablesByTypeOnly(@Nonnull Type elementType)
{
MultiValuedProvider found = null;
for (MockedType injectable : injectables) {
Type injectableType = injectable.getDeclaredType();
Type elementTypeOfIterable = getElementTypeIfIterable(injectableType);
if (
elementTypeOfIterable != null && testedTypeReflection.areMatchingTypes(elementType, elementTypeOfIterable)
) {
return injectable;
}
if (isSameTypeAsInjectionPoint(injectableType)) {
if (found == null) {
found = new MultiValuedProvider(elementType);
}
found.addInjectable(injectable);
}
}
return found;
}
@Nullable
private MockedType findInjectableByTypeAndOptionallyName(@Nonnull String nameOfInjectionPoint)
{
MockedType found = null;
for (MockedType injectable : injectables) {
if (hasSameTypeAsInjectionPoint(injectable)) {
if (nameOfInjectionPoint.equals(injectable.getName())) {
return injectable;
}
if (found == null) {
found = injectable;
}
}
}
return found;
}
@Nullable
public MockedType findInjectableByTypeAndName(@Nonnull String nameOfInjectionPoint)
{
for (MockedType injectable : injectables) {
if (hasSameTypeAsInjectionPoint(injectable) && nameOfInjectionPoint.equals(injectable.getName())) {
return injectable;
}
}
return null;
}
@Nullable
public Object getValueToInject(@Nonnull InjectionPointProvider injectable)
{
if (consumedInjectables.contains(injectable)) {
return null;
}
Object value = injectable.getValue(currentTestClassInstance);
if (value != null) {
consumedInjectables.add(injectable);
}
return value;
}
void resetConsumedInjectables()
{
consumedInjectables.clear();
}
@Nonnull
public List<InjectionPointProvider> saveConsumedInjectables()
{
List<InjectionPointProvider> previousConsumedInjectables = consumedInjectables;
consumedInjectables = new ArrayList<InjectionPointProvider>();
return previousConsumedInjectables;
}
public void restoreConsumedInjectables(@Nonnull List<InjectionPointProvider> previousConsumedInjectables)
{
consumedInjectables = previousConsumedInjectables;
}
void saveTestedObject(@Nonnull InjectionPoint key, @Nonnull Object testedObject)
{
testedObjects.put(key, testedObject);
}
@Nullable
Object getTestedInstance(@Nonnull Type testedType, @Nonnull String nameOfInjectionPoint)
{
Object testedInstance = instantiatedDependencies.isEmpty() ?
null : findPreviouslyInstantiatedDependency(testedType, nameOfInjectionPoint);
if (testedInstance == null) {
testedInstance = testedObjects.isEmpty() ?
null : getValueFromExistingTestedObject(testedType, nameOfInjectionPoint);
}
return testedInstance;
}
@Nullable
private Object findPreviouslyInstantiatedDependency(@Nonnull Type testedType, @Nonnull String nameOfInjectionPoint)
{
InjectionPoint injectionPoint = new InjectionPoint(testedType, nameOfInjectionPoint);
Object dependency = instantiatedDependencies.get(injectionPoint);
if (dependency == null) {
injectionPoint = new InjectionPoint(testedType);
dependency = instantiatedDependencies.get(injectionPoint);
if (dependency == null) {
dependency = findMatchingObject(instantiatedDependencies, null, injectionPoint);
}
}
return dependency;
}
@Nullable
private Object getValueFromExistingTestedObject(@Nonnull Type testedType, @Nonnull String nameOfInjectionPoint)
{
InjectionPoint injectionPoint = new InjectionPoint(testedType, nameOfInjectionPoint);
for (Object testedObject : testedObjects.values()) {
Object fieldValue = getValueFromFieldOfEquivalentTypeAndName(injectionPoint, testedObject);
if (fieldValue != null) {
return fieldValue;
}
}
return null;
}
@Nullable
private static Object getValueFromFieldOfEquivalentTypeAndName(
@Nonnull InjectionPoint injectionPoint, @Nonnull Object testedObject)
{
for (Field internalField : testedObject.getClass().getDeclaredFields()) {
Type fieldType = internalField.getGenericType();
String qualifiedName = getQualifiedName(internalField.getDeclaredAnnotations());
String fieldName = qualifiedName == null ? internalField.getName() : qualifiedName;
InjectionPoint internalInjectionPoint = new InjectionPoint(fieldType, fieldName);
if (internalInjectionPoint.equals(injectionPoint)) {
Object fieldValue = FieldReflection.getFieldValue(internalField, testedObject);
return fieldValue;
}
}
return null;
}
@Nullable @SuppressWarnings("unchecked")
public <D> D getGlobalDependency(@Nonnull InjectionPoint key) { return (D) globalDependencies.get(key); }
@Nullable
public Object getInstantiatedDependency(
@Nonnull TestedClass testedClass, @Nonnull InjectionPointProvider injectionProvider,
@Nonnull InjectionPoint dependencyKey)
{
Object dependency = testedObjects.get(dependencyKey);
if (dependency == null) {
dependency = findMatchingObject(testedObjects, testedClass.reflection, dependencyKey);
if (dependency == null) {
dependency = instantiatedDependencies.get(dependencyKey);
if (dependency == null) {
dependency = findMatchingObject(instantiatedDependencies, null, dependencyKey);
if (dependency == null) {
dependency = findMatchingObject(globalDependencies, null, dependencyKey);
}
}
}
}
return dependency;
}
@Nullable
private Object findMatchingObject(
@Nonnull Map<InjectionPoint, Object> availableObjects, @Nullable GenericTypeReflection reflection,
@Nonnull InjectionPoint injectionPoint)
{
Type dependencyType = injectionPoint.type;
for (Entry<InjectionPoint, Object> injectionPointAndObject : availableObjects.entrySet()) {
InjectionPoint dependencyIP = injectionPointAndObject.getKey();
Object dependencyObject = injectionPointAndObject.getValue();
if (
injectionPoint.equals(dependencyIP) ||
reflection != null && reflection.areMatchingTypes(dependencyType, dependencyIP.type)
) {
return dependencyObject;
}
}
return null;
}
public void saveInstantiatedDependency(@Nonnull InjectionPoint dependencyKey, @Nonnull Object dependency)
{
instantiatedDependencies.put(dependencyKey, dependency);
}
public void saveGlobalDependency(@Nonnull InjectionPoint dependencyKey, @Nonnull Object dependency)
{
globalDependencies.put(dependencyKey, dependency);
}
void clearTestedObjectsAndInstantiatedDependencies()
{
testedObjects.clear();
instantiatedDependencies.clear();
}
@Override
public Object getBean(@Nonnull String name)
{
for (InjectionPointProvider injectable : injectables) {
if (name.equals(injectable.getName())) {
return injectable.getValue(currentTestClassInstance);
}
}
Object bean = findByName(testedObjects, name);
if (bean == null) {
bean = findByName(instantiatedDependencies, name);
if (bean == null) {
bean = findByName(globalDependencies, name);
}
}
return bean;
}
@Nullable
private Object findByName(@Nonnull Map<InjectionPoint, Object> injectionPointsAndObjects, @Nonnull String name)
{
for (Entry<InjectionPoint, Object> injectionPointAndObject : injectionPointsAndObjects.entrySet()) {
InjectionPoint injectionPoint = injectionPointAndObject.getKey();
if (name.equals(injectionPoint.name)) {
return injectionPointAndObject.getValue();
}
}
return null;
}
}