/* * Copyright (c) 2006 Rogério Liesenfeld * This file is subject to the terms of the MIT license (see LICENSE.txt). */ package mockit.internal.expectations.mocking; import java.util.*; import javax.annotation.*; import mockit.external.asm.*; import mockit.internal.*; import mockit.internal.capturing.*; import mockit.internal.startup.*; import mockit.internal.state.*; import mockit.internal.util.*; import static mockit.external.asm.ClassReader.*; import static mockit.internal.reflection.FieldReflection.*; public class CaptureOfNewInstances extends CaptureOfImplementations<MockedType> { static final class Capture { @Nonnull final MockedType typeMetadata; @Nullable private Object originalMockInstance; @Nonnull private final List<Object> instancesCaptured; private Capture(@Nonnull MockedType typeMetadata, @Nullable Object originalMockInstance) { this.typeMetadata = typeMetadata; this.originalMockInstance = originalMockInstance; instancesCaptured = new ArrayList<Object>(4); } private boolean isInstanceAlreadyCaptured(@Nonnull Object mock) { return Utilities.containsReference(instancesCaptured, mock); } private boolean captureInstance(@Nullable Object fieldOwner, @Nonnull Object instance) { if (instancesCaptured.size() < typeMetadata.getMaxInstancesToCapture()) { if (fieldOwner != null && typeMetadata.field != null && originalMockInstance == null) { originalMockInstance = getFieldValue(typeMetadata.field, fieldOwner); } instancesCaptured.add(instance); return true; } return false; } void reset() { originalMockInstance = null; instancesCaptured.clear(); } } @Nonnull private final Map<Class<?>, List<Capture>> baseTypeToCaptures; @Nonnull private final List<Class<?>> partiallyMockedBaseTypes; CaptureOfNewInstances() { baseTypeToCaptures = new HashMap<Class<?>, List<Capture>>(); partiallyMockedBaseTypes = new ArrayList<Class<?>>(); } @Nonnull protected final Collection<List<Capture>> getCapturesForAllBaseTypes() { return baseTypeToCaptures.values(); } void useDynamicMocking(@Nonnull Class<?> baseType) { partiallyMockedBaseTypes.add(baseType); List<Class<?>> mockedClasses = TestRun.mockFixture().getMockedClasses(); for (Class<?> mockedClass : mockedClasses) { if (baseType.isAssignableFrom(mockedClass)) { if (mockedClass != baseType || !baseType.isInterface()) { redefineClassForDynamicPartialMocking(baseType, mockedClass); } } } } private static void redefineClassForDynamicPartialMocking(@Nonnull Class<?> baseType, @Nonnull Class<?> mockedClass) { ClassReader classReader = ClassFile.createReaderOrGetFromCache(mockedClass); ExpectationsModifier modifier = newModifier(mockedClass.getClassLoader(), classReader, baseType, null); modifier.useDynamicMocking(true); classReader.accept(modifier, SKIP_FRAMES); byte[] modifiedClassfile = modifier.toByteArray(); Startup.redefineMethods(mockedClass, modifiedClassfile); } @Nonnull private static ExpectationsModifier newModifier( @Nullable ClassLoader cl, @Nonnull ClassReader cr, @Nonnull Class<?> baseType, @Nullable MockedType typeMetadata) { ExpectationsModifier modifier = new ExpectationsModifier(cl, cr, typeMetadata); modifier.setClassNameForCapturedInstanceMethods(Type.getInternalName(baseType)); return modifier; } @Nonnull @Override protected final BaseClassModifier createModifier( @Nullable ClassLoader cl, @Nonnull ClassReader cr, @Nonnull Class<?> baseType, @Nullable MockedType typeMetadata) { ExpectationsModifier modifier = newModifier(cl, cr, baseType, typeMetadata); if (partiallyMockedBaseTypes.contains(baseType)) { modifier.useDynamicMocking(true); } return modifier; } @Override protected final void redefineClass(@Nonnull Class<?> realClass, @Nonnull byte[] modifiedClass) { new RedefinitionEngine(realClass).redefineMethodsWhileRegisteringTheClass(modifiedClass); } final void registerCaptureOfNewInstances(@Nonnull MockedType typeMetadata, @Nullable Object mockInstance) { Class<?> baseType = typeMetadata.getClassType(); if (!typeMetadata.isFinalFieldOrParameter()) { makeSureAllSubtypesAreModified(typeMetadata); } List<Capture> captures = baseTypeToCaptures.get(baseType); if (captures == null) { captures = new ArrayList<Capture>(); baseTypeToCaptures.put(baseType, captures); } captures.add(new Capture(typeMetadata, mockInstance)); } final void makeSureAllSubtypesAreModified(@Nonnull MockedType typeMetadata) { Class<?> baseType = typeMetadata.getClassType(); makeSureAllSubtypesAreModified(baseType, typeMetadata.fieldFromTestClass, typeMetadata); } public final boolean captureNewInstance(@Nullable Object fieldOwner, @Nonnull Object mock) { Class<?> mockedClass = mock.getClass(); List<Capture> captures = baseTypeToCaptures.get(mockedClass); boolean constructorModifiedForCaptureOnly = captures == null; if (constructorModifiedForCaptureOnly) { captures = findCaptures(mockedClass); if (captures == null) { return false; } } Capture captureFound = findCapture(fieldOwner, mock, captures); if (captureFound != null) { if (captureFound.typeMetadata.injectable) { TestRun.getExecutingTest().addCapturedInstanceForInjectableMock(captureFound.originalMockInstance, mock); constructorModifiedForCaptureOnly = true; } else { TestRun.getExecutingTest().addCapturedInstance(captureFound.originalMockInstance, mock); } } return constructorModifiedForCaptureOnly; } @Nullable private List<Capture> findCaptures(@Nonnull Class<?> mockedClass) { Class<?>[] interfaces = mockedClass.getInterfaces(); for (Class<?> anInterface : interfaces) { List<Capture> found = baseTypeToCaptures.get(anInterface); if (found != null) { return found; } } Class<?> superclass = mockedClass.getSuperclass(); if (superclass == Object.class) { return null; } List<Capture> found = baseTypeToCaptures.get(superclass); return found != null ? found : findCaptures(superclass); } @Nullable private static Capture findCapture( @Nullable Object fieldOwner, @Nonnull Object mock, @Nonnull List<Capture> captures) { for (Capture capture : captures) { if (capture.isInstanceAlreadyCaptured(mock)) { break; } else if (capture.captureInstance(fieldOwner, mock)) { return capture; } } return null; } public final void cleanUp() { baseTypeToCaptures.clear(); partiallyMockedBaseTypes.clear(); } }