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 net.bytebuddy.ByteBuddy;
      8 import net.bytebuddy.description.method.MethodDescription;
      9 import net.bytebuddy.description.modifier.SynchronizationState;
     10 import net.bytebuddy.description.modifier.Visibility;
     11 import net.bytebuddy.dynamic.DynamicType;
     12 import net.bytebuddy.dynamic.loading.MultipleParentClassLoader;
     13 import net.bytebuddy.dynamic.scaffold.TypeValidation;
     14 import net.bytebuddy.implementation.FieldAccessor;
     15 import net.bytebuddy.implementation.Implementation;
     16 import net.bytebuddy.matcher.ElementMatcher;
     17 import org.mockito.internal.creation.bytebuddy.ByteBuddyCrossClassLoaderSerializationSupport.CrossClassLoaderSerializableMock;
     18 import org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.DispatcherDefaultingToRealMethod;
     19 import org.mockito.mock.SerializableMode;
     20 
     21 import java.io.IOException;
     22 import java.io.ObjectInputStream;
     23 import java.lang.reflect.Type;
     24 import java.util.ArrayList;
     25 import java.util.Random;
     26 
     27 import static java.lang.Thread.currentThread;
     28 import static net.bytebuddy.description.modifier.Visibility.PRIVATE;
     29 import static net.bytebuddy.dynamic.Transformer.ForMethod.withModifiers;
     30 import static net.bytebuddy.implementation.MethodDelegation.to;
     31 import static net.bytebuddy.implementation.attribute.MethodAttributeAppender.ForInstrumentedMethod.INCLUDING_RECEIVER;
     32 import static net.bytebuddy.matcher.ElementMatchers.*;
     33 
     34 class SubclassBytecodeGenerator implements BytecodeGenerator {
     35 
     36     private final SubclassLoader loader;
     37 
     38     private final ByteBuddy byteBuddy;
     39     private final Random random;
     40 
     41     private final Implementation readReplace;
     42     private final ElementMatcher<? super MethodDescription> matcher;
     43 
     44     public SubclassBytecodeGenerator() {
     45         this(new SubclassInjectionLoader());
     46     }
     47 
     48     public SubclassBytecodeGenerator(SubclassLoader loader) {
     49         this(loader, null, any());
     50     }
     51 
     52     public SubclassBytecodeGenerator(Implementation readReplace, ElementMatcher<? super MethodDescription> matcher) {
     53         this(new SubclassInjectionLoader(), readReplace, matcher);
     54     }
     55 
     56     protected SubclassBytecodeGenerator(SubclassLoader loader, Implementation readReplace, ElementMatcher<? super MethodDescription> matcher) {
     57         this.loader = loader;
     58         this.readReplace = readReplace;
     59         this.matcher = matcher;
     60         byteBuddy = new ByteBuddy().with(TypeValidation.DISABLED);
     61         random = new Random();
     62     }
     63 
     64     @Override
     65     public <T> Class<? extends T> mockClass(MockFeatures<T> features) {
     66         DynamicType.Builder<T> builder =
     67                 byteBuddy.subclass(features.mockedType)
     68                          .name(nameFor(features.mockedType))
     69                          .ignoreAlso(isGroovyMethod())
     70                          .annotateType(features.mockedType.getAnnotations())
     71                          .implement(new ArrayList<Type>(features.interfaces))
     72                          .method(matcher)
     73                            .intercept(to(DispatcherDefaultingToRealMethod.class))
     74                            .transform(withModifiers(SynchronizationState.PLAIN))
     75                            .attribute(INCLUDING_RECEIVER)
     76                          .method(isHashCode())
     77                            .intercept(to(MockMethodInterceptor.ForHashCode.class))
     78                          .method(isEquals())
     79                            .intercept(to(MockMethodInterceptor.ForEquals.class))
     80                          .serialVersionUid(42L)
     81                          .defineField("mockitoInterceptor", MockMethodInterceptor.class, PRIVATE)
     82                          .implement(MockAccess.class)
     83                            .intercept(FieldAccessor.ofBeanProperty());
     84         if (features.serializableMode == SerializableMode.ACROSS_CLASSLOADERS) {
     85             builder = builder.implement(CrossClassLoaderSerializableMock.class)
     86                              .intercept(to(MockMethodInterceptor.ForWriteReplace.class));
     87         }
     88         if (readReplace != null) {
     89             builder = builder.defineMethod("readObject", void.class, Visibility.PRIVATE)
     90                     .withParameters(ObjectInputStream.class)
     91                     .throwing(ClassNotFoundException.class, IOException.class)
     92                     .intercept(readReplace);
     93         }
     94         return builder.make()
     95                       .load(new MultipleParentClassLoader.Builder()
     96                               .append(features.mockedType)
     97                               .append(features.interfaces)
     98                               .append(currentThread().getContextClassLoader())
     99                               .append(MockAccess.class, DispatcherDefaultingToRealMethod.class)
    100                               .append(MockMethodInterceptor.class,
    101                                       MockMethodInterceptor.ForHashCode.class,
    102                                       MockMethodInterceptor.ForEquals.class).build(MockMethodInterceptor.class.getClassLoader()),
    103                               loader.getStrategy(features.mockedType))
    104                       .getLoaded();
    105     }
    106 
    107     private static ElementMatcher<MethodDescription> isGroovyMethod() {
    108         return isDeclaredBy(named("groovy.lang.GroovyObjectSupport"));
    109     }
    110 
    111     // TODO inspect naming strategy (for OSGI, signed package, java.* (and bootstrap classes), etc...)
    112     private String nameFor(Class<?> type) {
    113         String typeName = type.getName();
    114         if (isComingFromJDK(type)
    115                 || isComingFromSignedJar(type)
    116                 || isComingFromSealedPackage(type)) {
    117             typeName = "codegen." + typeName;
    118         }
    119         return String.format("%s$%s$%d", typeName, "MockitoMock", Math.abs(random.nextInt()));
    120     }
    121 
    122     private boolean isComingFromJDK(Class<?> type) {
    123         // Comes from the manifest entry :
    124         // Implementation-Title: Java Runtime Environment
    125         // This entry is not necessarily present in every jar of the JDK
    126         return type.getPackage() != null && "Java Runtime Environment".equalsIgnoreCase(type.getPackage().getImplementationTitle())
    127                 || type.getName().startsWith("java.")
    128                 || type.getName().startsWith("javax.");
    129     }
    130 
    131     private boolean isComingFromSealedPackage(Class<?> type) {
    132         return type.getPackage() != null && type.getPackage().isSealed();
    133     }
    134 
    135     private boolean isComingFromSignedJar(Class<?> type) {
    136         return type.getSigners() != null;
    137     }
    138 }
    139