Home | History | Annotate | Download | only in bytebuddy
      1 /*
      2  * Copyright (c) 2016 Mockito contributors
      3  * This program is made available under the terms of the MIT License.
      4  */
      5 package org.mockito.internal.creation.bytebuddy;
      6 
      7 import org.mockito.exceptions.base.MockitoException;
      8 import org.mockito.internal.configuration.plugins.Plugins;
      9 import org.mockito.creation.instance.Instantiator;
     10 import org.mockito.internal.util.Platform;
     11 import org.mockito.invocation.MockHandler;
     12 import org.mockito.mock.MockCreationSettings;
     13 
     14 import java.lang.reflect.Modifier;
     15 
     16 import static org.mockito.internal.util.StringUtil.join;
     17 
     18 /**
     19  * Subclass based mock maker.
     20  *
     21  * This mock maker tries to create a subclass to represent a mock. It uses the given mock settings, that contains
     22  * the type to mock, extra interfaces, and serialization support.
     23  *
     24  * <p>
     25  * The type to mock has to be not final and not part of the JDK. THe created mock will implement extra interfaces
     26  * if any. And will implement <code>Serializable</code> if this settings is explicitly set.
     27  */
     28 public class SubclassByteBuddyMockMaker implements ClassCreatingMockMaker {
     29 
     30     private final BytecodeGenerator cachingMockBytecodeGenerator;
     31 
     32     public SubclassByteBuddyMockMaker() {
     33         this(new SubclassInjectionLoader());
     34     }
     35 
     36     public SubclassByteBuddyMockMaker(SubclassLoader loader) {
     37         cachingMockBytecodeGenerator = new TypeCachingBytecodeGenerator(new SubclassBytecodeGenerator(loader), false);
     38     }
     39 
     40     @Override
     41     public <T> T createMock(MockCreationSettings<T> settings, MockHandler handler) {
     42         Class<? extends T> mockedProxyType = createMockType(settings);
     43 
     44         Instantiator instantiator = Plugins.getInstantiatorProvider().getInstantiator(settings);
     45         T mockInstance = null;
     46         try {
     47             mockInstance = instantiator.newInstance(mockedProxyType);
     48             MockAccess mockAccess = (MockAccess) mockInstance;
     49             mockAccess.setMockitoInterceptor(new MockMethodInterceptor(handler, settings));
     50 
     51             return ensureMockIsAssignableToMockedType(settings, mockInstance);
     52         } catch (ClassCastException cce) {
     53             throw new MockitoException(join(
     54                     "ClassCastException occurred while creating the mockito mock :",
     55                     "  class to mock : " + describeClass(settings.getTypeToMock()),
     56                     "  created class : " + describeClass(mockedProxyType),
     57                     "  proxy instance class : " + describeClass(mockInstance),
     58                     "  instance creation by : " + instantiator.getClass().getSimpleName(),
     59                     "",
     60                     "You might experience classloading issues, please ask the mockito mailing-list.",
     61                     ""
     62             ), cce);
     63         } catch (org.mockito.creation.instance.InstantiationException e) {
     64             throw new MockitoException("Unable to create mock instance of type '" + mockedProxyType.getSuperclass().getSimpleName() + "'", e);
     65         }
     66     }
     67 
     68     @Override
     69     public <T> Class<? extends T> createMockType(MockCreationSettings<T> settings) {
     70         try {
     71             return cachingMockBytecodeGenerator.mockClass(MockFeatures.withMockFeatures(
     72                     settings.getTypeToMock(),
     73                     settings.getExtraInterfaces(),
     74                     settings.getSerializableMode(),
     75                     settings.isStripAnnotations()
     76             ));
     77         } catch (Exception bytecodeGenerationFailed) {
     78             throw prettifyFailure(settings, bytecodeGenerationFailed);
     79         }
     80     }
     81 
     82     private static <T> T ensureMockIsAssignableToMockedType(MockCreationSettings<T> settings, T mock) {
     83         // Force explicit cast to mocked type here, instead of
     84         // relying on the JVM to implicitly cast on the client call site.
     85         // This allows us to catch earlier the ClassCastException earlier
     86         Class<T> typeToMock = settings.getTypeToMock();
     87         return typeToMock.cast(mock);
     88     }
     89 
     90     private <T> RuntimeException prettifyFailure(MockCreationSettings<T> mockFeatures, Exception generationFailed) {
     91         if (mockFeatures.getTypeToMock().isArray()) {
     92             throw new MockitoException(join(
     93                     "Mockito cannot mock arrays: " + mockFeatures.getTypeToMock() + ".",
     94                     ""
     95                     ), generationFailed);
     96         }
     97         if (Modifier.isPrivate(mockFeatures.getTypeToMock().getModifiers())) {
     98             throw new MockitoException(join(
     99                     "Mockito cannot mock this class: " + mockFeatures.getTypeToMock() + ".",
    100                     "Most likely it is due to mocking a private class that is not visible to Mockito",
    101                     ""
    102             ), generationFailed);
    103         }
    104         throw new MockitoException(join(
    105                 "Mockito cannot mock this class: " + mockFeatures.getTypeToMock() + ".",
    106                 "",
    107                 "Mockito can only mock non-private & non-final classes.",
    108                 "If you're not sure why you're getting this error, please report to the mailing list.",
    109                 "",
    110                 Platform.warnForVM(
    111                         "IBM J9 VM", "Early IBM virtual machine are known to have issues with Mockito, please upgrade to an up-to-date version.\n",
    112                         "Hotspot", Platform.isJava8BelowUpdate45() ? "Java 8 early builds have bugs that were addressed in Java 1.8.0_45, please update your JDK!\n" : ""
    113                 ),
    114                 Platform.describe(),
    115                 "",
    116                 "Underlying exception : " + generationFailed
    117         ), generationFailed);
    118     }
    119 
    120     private static String describeClass(Class<?> type) {
    121         return type == null ? "null" : "'" + type.getCanonicalName() + "', loaded by classloader : '" + type.getClassLoader() + "'";
    122     }
    123 
    124     private static String describeClass(Object instance) {
    125         return instance == null ? "null" : describeClass(instance.getClass());
    126     }
    127 
    128     @Override
    129     public MockHandler getHandler(Object mock) {
    130         if (!(mock instanceof MockAccess)) {
    131             return null;
    132         }
    133         return ((MockAccess) mock).getMockitoInterceptor().getMockHandler();
    134     }
    135 
    136     @Override
    137     public void resetMock(Object mock, MockHandler newHandler, MockCreationSettings settings) {
    138         ((MockAccess) mock).setMockitoInterceptor(
    139                 new MockMethodInterceptor(newHandler, settings)
    140         );
    141     }
    142 
    143     @Override
    144     public TypeMockability isTypeMockable(final Class<?> type) {
    145         return new TypeMockability() {
    146             @Override
    147             public boolean mockable() {
    148                 return !type.isPrimitive() && !Modifier.isFinal(type.getModifiers());
    149             }
    150 
    151             @Override
    152             public String nonMockableReason() {
    153                 if(mockable()) {
    154                     return "";
    155                 }
    156                 if (type.isPrimitive()) {
    157                     return "primitive type";
    158                 }
    159                 if (Modifier.isFinal(type.getModifiers())) {
    160                     return "final class";
    161                 }
    162                 return join("not handled type");
    163             }
    164         };
    165     }
    166 }
    167