All Downloads are FREE. Search and download functionalities are using the official Maven repository.

mockit.internal.expectations.mocking.CaptureOfNewInstances Maven / Gradle / Ivy

Go to download

JMockit is a Java toolkit for automated developer testing. It contains APIs for the creation of the objects to be tested, for mocking dependencies, and for faking external APIs; JUnit (4 & 5) and TestNG test runners are supported. It also contains an advanced code coverage tool.

There is a newer version: 1.49
Show newest version
/*
 * 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.lang.instrument.*;
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.internal.reflection.FieldReflection.*;

public class CaptureOfNewInstances extends CaptureOfImplementations
{
   static final class Capture
   {
      @Nonnull final MockedType typeMetadata;
      @Nullable private Object originalMockInstance;
      @Nonnull private final List instancesCaptured;

      private Capture(@Nonnull MockedType typeMetadata, @Nullable Object originalMockInstance)
      {
         this.typeMetadata = typeMetadata;
         this.originalMockInstance = originalMockInstance;
         instancesCaptured = new ArrayList(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, List> baseTypeToCaptures;
   @Nonnull private final List> partiallyMockedBaseTypes;

   CaptureOfNewInstances()
   {
      baseTypeToCaptures = new HashMap, List>();
      partiallyMockedBaseTypes = new ArrayList>();
   }

   @Nonnull
   protected final Collection> getCapturesForAllBaseTypes() { return baseTypeToCaptures.values(); }

   void useDynamicMocking(@Nonnull Class baseType)
   {
      partiallyMockedBaseTypes.add(baseType);

      List> 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);

      MockedClassModifier modifier = newModifier(mockedClass.getClassLoader(), classReader, baseType, null);
      modifier.useDynamicMocking(true);
      classReader.accept(modifier);
      byte[] modifiedClassfile = modifier.toByteArray();

      Startup.redefineMethods(mockedClass, modifiedClassfile);
   }

   @Nonnull
   private static MockedClassModifier newModifier(
      @Nullable ClassLoader cl, @Nonnull ClassReader cr, @Nonnull Class baseType, @Nullable MockedType typeMetadata)
   {
      MockedClassModifier modifier = new MockedClassModifier(cl, cr, typeMetadata);
      String baseTypeDesc = JavaType.getInternalName(baseType);
      modifier.setClassNameForCapturedInstanceMethods(baseTypeDesc);
      return modifier;
   }

   @Nonnull @Override
   protected final BaseClassModifier createModifier(
      @Nullable ClassLoader cl, @Nonnull ClassReader cr, @Nonnull Class baseType, @Nullable MockedType typeMetadata)
   {
      MockedClassModifier 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[] modifiedClassfile)
   {
      ClassDefinition newClassDefinition = new ClassDefinition(realClass, modifiedClassfile);
      Startup.redefineMethods(newClassDefinition);

      MockFixture mockFixture = TestRun.mockFixture();
      mockFixture.addRedefinedClass(newClassDefinition);
      mockFixture.registerMockedClass(realClass);
   }

   final void registerCaptureOfNewInstances(@Nonnull MockedType typeMetadata, @Nullable Object mockInstance)
   {
      Class baseType = typeMetadata.getClassType();

      if (!typeMetadata.isFinalFieldOrParameter()) {
         makeSureAllSubtypesAreModified(typeMetadata);
      }

      List captures = baseTypeToCaptures.get(baseType);

      if (captures == null) {
         captures = new ArrayList();
         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 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 findCaptures(@Nonnull Class mockedClass)
   {
      Class[] interfaces = mockedClass.getInterfaces();

      for (Class anInterface : interfaces) {
         List found = baseTypeToCaptures.get(anInterface);

         if (found != null) {
            return found;
         }
      }

      Class superclass = mockedClass.getSuperclass();

      if (superclass == Object.class) {
         return null;
      }

      List found = baseTypeToCaptures.get(superclass);

      return found != null ? found : findCaptures(superclass);
   }

   @Nullable
   private static Capture findCapture(
      @Nullable Object fieldOwner, @Nonnull Object mock, @Nonnull List 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();
   }
}