/* * Copyright (c) 2006-2011 Rogério Liesenfeld * This file is subject to the terms of the MIT license (see LICENSE.txt). */ package mockit.internal.expectations.mocking; import java.lang.instrument.*; import java.lang.reflect.*; import java.lang.reflect.Type; import java.util.*; import static mockit.internal.util.Utilities.*; import mockit.*; import mockit.external.asm.*; import mockit.internal.*; public final class TestedClassRedefinitions { private final List<Field> testedFields; private final List<MockedType> mockedTypes; public TestedClassRedefinitions() { testedFields = new LinkedList<Field>(); mockedTypes = new ArrayList<MockedType>(); } public boolean redefineTestedClasses(Object objectWithTestedFields) { for (Field field: objectWithTestedFields.getClass().getDeclaredFields()) { if (field.isAnnotationPresent(Tested.class)) { testedFields.add(field); } else { MockedType mockedType = new MockedType(field, true); if (mockedType.isMockField()) { mockedTypes.add(mockedType); } } } for (Field testedField : testedFields) { redefineTestedClass(testedField.getType()); } return !testedFields.isEmpty(); } private void redefineTestedClass(Class<?> testedClass) { ClassReader cr = ClassFile.createClassFileReader(testedClass.getName()); TestedClassModifier modifier = new TestedClassModifier(cr, mockedTypes); cr.accept(modifier, false); byte[] modifiedClass = modifier.toByteArray(); ClassDefinition classDefinition = new ClassDefinition(testedClass, modifiedClass); RedefinitionEngine.redefineClasses(classDefinition); } public void assignNewInstancesToTestedFields(Object objectWithMockFields) { for (Field testedField : testedFields) { Object testedObject = getFieldValue(testedField, objectWithMockFields); if (testedObject == null) { Class<?> testedClass = testedField.getType(); Constructor<?>[] publicConstructors = testedClass.getConstructors(); if (publicConstructors.length == 1) { Object newTestedObject = instantiateWithPublicConstructor(objectWithMockFields, publicConstructors[0]); injectMocksIntoFieldsThatAreStillNull(objectWithMockFields, testedClass, newTestedObject); setFieldValue(testedField, objectWithMockFields, newTestedObject); } } } } private Object instantiateWithPublicConstructor(Object objectWithMockFields, Constructor<?> constructor) { Object[] mockArguments = obtainInjectableMocks(objectWithMockFields, constructor.getGenericParameterTypes()); return invoke(constructor, mockArguments); } private Object[] obtainInjectableMocks(Object parentObject, Type[] parameterTypes) { int n = parameterTypes.length; Object[] parameterValues = new Object[n]; for (int i = 0; i < n; i++) { parameterValues[i] = getRequiredMockObject(parentObject, parameterTypes[i]); } return parameterValues; } private Object getRequiredMockObject(Object parentObject, Type declaredType) { MockedType mockedType = findInjectableMockedType(declaredType); if (mockedType == null) { throw new IllegalArgumentException("No injectable mock field of " + declaredType); } Object mock = getFieldValue(mockedType.field, parentObject); if (mock == null) { throw new IllegalArgumentException("No injectable mock instance available of " + declaredType); } return mock; } private MockedType findInjectableMockedType(Type declaredType) { for (MockedType mockedType : mockedTypes) { if (mockedType.injectable && mockedType.declaredType == declaredType) { return mockedType; } } return null; } private void injectMocksIntoFieldsThatAreStillNull(Object objectWithMockFields, Class<?> testedClass, Object tested) { Class<?> superClass = testedClass.getSuperclass(); if (superClass != null && superClass.getProtectionDomain() == testedClass.getProtectionDomain()) { injectMocksIntoFieldsThatAreStillNull(objectWithMockFields, superClass, tested); } for (Field field : testedClass.getDeclaredFields()) { if (getFieldValue(field, tested) == null) { Object mock = getMockObjectIfAvailable(objectWithMockFields, field); setFieldValue(field, tested, mock); } } } private Object getMockObjectIfAvailable(Object parentObject, Field fieldToBeInjected) { MockedType mockedType = findInjectableMockedType(fieldToBeInjected); return mockedType == null ? null : getFieldValue(mockedType.field, parentObject); } private MockedType findInjectableMockedType(Field fieldToBeInjected) { Type declaredType = fieldToBeInjected.getGenericType(); String fieldName = fieldToBeInjected.getName(); boolean multipleFieldsOfSameTypeFound = false; MockedType found = null; for (MockedType mockedType : mockedTypes) { if (mockedType.injectable && mockedType.declaredType == declaredType) { if (found == null) { found = mockedType; } else { multipleFieldsOfSameTypeFound = true; if (fieldName.equals(mockedType.field.getName())) { return mockedType; } } } } if (multipleFieldsOfSameTypeFound && !fieldName.equals(found.field.getName())) { return null; } return found; } }