/*
* Copyright (c) 2006 Rogério Liesenfeld
* This file is subject to the terms of the MIT license (see LICENSE.txt).
*/
package mockit.internal.injection.field;
import java.lang.reflect.*;
import java.util.*;
import java.util.regex.*;
import javax.annotation.*;
import javax.persistence.*;
import static java.lang.reflect.Modifier.*;
import static java.util.regex.Pattern.*;
import mockit.internal.injection.*;
import mockit.internal.injection.full.*;
import mockit.internal.reflection.*;
import mockit.internal.util.*;
import static mockit.internal.injection.InjectionPoint.*;
import static mockit.internal.injection.InjectionPointProvider.NULL;
public final class FieldInjection extends Injector
{
private static final Pattern TYPE_NAME = compile("class |interface |java\\.lang\\.");
boolean requireDIAnnotation;
@Nonnull Class<?> targetClass;
private Field targetField;
public FieldInjection(
@Nonnull InjectionState injectionState, @Nonnull TestedClass testedClass, @Nullable FullInjection fullInjection,
boolean requireDIAnnotation)
{
super(testedClass, injectionState, fullInjection);
this.requireDIAnnotation = requireDIAnnotation;
targetClass = testedClass.targetClass;
}
@Nonnull
public List<Field> findAllTargetInstanceFieldsInTestedClassHierarchy(@Nonnull Class<?> actualTestedClass)
{
requireDIAnnotation = false;
List<Field> targetFields = new ArrayList<Field>();
Class<?> classWithFields = actualTestedClass;
do {
addEligibleFields(targetFields, classWithFields);
classWithFields = classWithFields.getSuperclass();
}
while (testedClass.isClassFromSameModuleOrSystemAsTestedClass(classWithFields) || isServlet(classWithFields));
return targetFields;
}
private void addEligibleFields(@Nonnull List<Field> targetFields, @Nonnull Class<?> classWithFields)
{
Field[] fields = classWithFields.getDeclaredFields();
for (Field field : fields) {
if (isEligibleForInjection(field)) {
targetFields.add(field);
}
}
}
private boolean isEligibleForInjection(@Nonnull Field field)
{
int modifiers = field.getModifiers();
if (isFinal(modifiers)) {
return false;
}
if (kindOfInjectionPoint(field) != KindOfInjectionPoint.NotAnnotated) {
requireDIAnnotation = true;
return true;
}
//noinspection SimplifiableIfStatement
if (PERSISTENCE_UNIT_CLASS != null && field.getType().isAnnotationPresent(Entity.class)) {
return false;
}
return !isStatic(modifiers) && !isVolatile(modifiers);
}
public void injectIntoEligibleFields(@Nonnull List<Field> targetFields, @Nonnull Object testedObject)
{
targetClass = testedObject.getClass();
for (Field field : targetFields) {
targetField = field;
if (targetFieldWasNotAssignedByConstructor(testedObject)) {
Object injectableValue = getValueForFieldIfAvailable(targetFields);
if (injectableValue != null && injectableValue != NULL) {
injectableValue = wrapInProviderIfNeeded(field.getGenericType(), injectableValue);
FieldReflection.setFieldValue(field, testedObject, injectableValue);
}
}
}
}
private boolean targetFieldWasNotAssignedByConstructor(@Nonnull Object testedObject)
{
if (kindOfInjectionPoint(targetField) != KindOfInjectionPoint.NotAnnotated) {
return true;
}
Object fieldValue = FieldReflection.getFieldValue(targetField, testedObject);
if (fieldValue == null) {
return true;
}
Class<?> fieldType = targetField.getType();
if (!fieldType.isPrimitive()) {
return false;
}
Object defaultValue = DefaultValues.defaultValueForPrimitiveType(fieldType);
return fieldValue.equals(defaultValue);
}
@Nullable
private Object getValueForFieldIfAvailable(@Nonnull List<Field> targetFields)
{
String qualifiedFieldName = getQualifiedName(targetField.getDeclaredAnnotations());
InjectionPointProvider mockedType = findAvailableInjectableIfAny(targetFields, qualifiedFieldName);
if (mockedType != null) {
return injectionState.getValueToInject(mockedType);
}
KindOfInjectionPoint kindOfInjectionPoint = kindOfInjectionPoint(targetField);
if (fullInjection != null) {
InjectionPointProvider fieldToInject = new FieldToInject(targetField);
if (requireDIAnnotation && kindOfInjectionPoint == KindOfInjectionPoint.NotAnnotated) {
Object existingInstance = fullInjection.reuseInstance(testedClass, fieldToInject, qualifiedFieldName);
return existingInstance;
}
Object newInstance = fullInjection.createOrReuseInstance(this, fieldToInject, qualifiedFieldName);
if (newInstance != null) {
return newInstance;
}
}
if (kindOfInjectionPoint == KindOfInjectionPoint.WithValue) {
return getValueFromAnnotation(targetField);
}
throwExceptionIfUnableToInjectRequiredTargetField(kindOfInjectionPoint);
return null;
}
@Nullable
private InjectionPointProvider findAvailableInjectableIfAny(
@Nonnull List<Field> targetFields, @Nullable String qualifiedTargetFieldName)
{
injectionState.setTypeOfInjectionPoint(targetField.getGenericType());
if (qualifiedTargetFieldName != null && !qualifiedTargetFieldName.isEmpty()) {
String injectableName = convertToLegalJavaIdentifierIfNeeded(qualifiedTargetFieldName);
return injectionState.findInjectableByTypeAndName(injectableName);
}
String targetFieldName = targetField.getName();
return withMultipleTargetFieldsOfSameType(targetFields) ?
injectionState.findInjectableByTypeAndName(targetFieldName) :
injectionState.getProviderByTypeAndOptionallyName(targetFieldName);
}
private boolean withMultipleTargetFieldsOfSameType(@Nonnull List<Field> targetFields)
{
for (Field anotherTargetField : targetFields) {
if (
anotherTargetField != targetField &&
injectionState.isSameTypeAsInjectionPoint(anotherTargetField.getGenericType())
) {
return true;
}
}
return false;
}
private void throwExceptionIfUnableToInjectRequiredTargetField(@Nonnull KindOfInjectionPoint kindOfInjectionPoint)
{
if (kindOfInjectionPoint == KindOfInjectionPoint.Required) {
Type fieldType = targetField.getGenericType();
String fieldTypeName = fieldType.toString();
fieldTypeName = TYPE_NAME.matcher(fieldTypeName).replaceAll("");
String kindOfInjectable = "@Injectable";
if (fullInjection != null) {
if (targetField.getType().isInterface()) {
kindOfInjectable = "@Tested instance of an implementation class";
}
else {
kindOfInjectable = "@Tested object";
}
}
throw new IllegalStateException(
"Missing " + kindOfInjectable + " for field \"" + fieldTypeName + ' ' + targetField.getName() + "\" in " +
targetField.getDeclaringClass().getSimpleName());
}
}
@Override
public void fillOutDependenciesRecursively(@Nonnull Object dependency)
{
Class<?> dependencyClass = dependency.getClass();
boolean previousRequireDIAnnotation = requireDIAnnotation;
List<Field> targetFields = findAllTargetInstanceFieldsInTestedClassHierarchy(dependencyClass);
if (!targetFields.isEmpty()) {
List<InjectionPointProvider> currentlyConsumedInjectables = injectionState.saveConsumedInjectables();
injectIntoEligibleFields(targetFields, dependency);
injectionState.restoreConsumedInjectables(currentlyConsumedInjectables);
}
requireDIAnnotation = previousRequireDIAnnotation;
}
public boolean isDIAnnotationRequired() { return requireDIAnnotation; }
}