• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2007 Mockito contributors
3  * This program is made available under the terms of the MIT License.
4  */
5 package org.mockito.internal.creation.jmock;
6 
7 import org.mockito.cglib.core.CodeGenerationException;
8 import org.mockito.cglib.core.NamingPolicy;
9 import org.mockito.cglib.core.Predicate;
10 import org.mockito.cglib.proxy.*;
11 import org.mockito.exceptions.base.MockitoException;
12 import org.mockito.internal.configuration.GlobalConfiguration;
13 import org.mockito.internal.creation.cglib.MockitoNamingPolicy;
14 import org.objenesis.ObjenesisStd;
15 
16 import java.lang.reflect.Constructor;
17 import java.lang.reflect.Method;
18 import java.lang.reflect.Modifier;
19 import java.util.Collection;
20 import java.util.List;
21 
22 import static org.mockito.internal.util.StringJoiner.join;
23 
24 /**
25  * Thanks to jMock guys for this handy class that wraps all the cglib magic.
26  */
27 public class ClassImposterizer  {
28 
29     public static final ClassImposterizer INSTANCE = new ClassImposterizer();
30 
ClassImposterizer()31     private ClassImposterizer() {}
32 
33     //TODO: in order to provide decent exception message when objenesis is not found,
34     //have a constructor in this class that tries to instantiate ObjenesisStd and if it fails then show decent exception that dependency is missing
35     //TODO: for the same reason catch and give better feedback when hamcrest core is not found.
36     private ObjenesisStd objenesis = new ObjenesisStd(new GlobalConfiguration().enableClassCache());
37 
38     private static final NamingPolicy NAMING_POLICY_THAT_ALLOWS_IMPOSTERISATION_OF_CLASSES_IN_SIGNED_PACKAGES = new MockitoNamingPolicy() {
39         @Override
40         public String getClassName(String prefix, String source, Object key, Predicate names) {
41             return "codegen." + super.getClassName(prefix, source, key, names);
42         }
43     };
44 
45     private static final CallbackFilter IGNORE_BRIDGE_METHODS = new CallbackFilter() {
46         public int accept(Method method, List<Method> allMethods) {
47             return method.isBridge() ? 1 : 0;
48         }
49     };
50 
imposterise(final MethodInterceptor interceptor, Class<T> mockedType, Collection<Class> ancillaryTypes)51     public <T> T imposterise(final MethodInterceptor interceptor, Class<T> mockedType, Collection<Class> ancillaryTypes) {
52         return imposterise(interceptor, mockedType, ancillaryTypes.toArray(new Class[ancillaryTypes.size()]));
53     }
54 
imposterise(final MethodInterceptor interceptor, Class<T> mockedType, Class<?>... ancillaryTypes)55     public <T> T imposterise(final MethodInterceptor interceptor, Class<T> mockedType, Class<?>... ancillaryTypes) {
56         Class<?> proxyClass = null;
57         Object proxyInstance = null;
58         try {
59             setConstructorsAccessible(mockedType, true);
60             proxyClass = createProxyClass(mockedType, ancillaryTypes);
61             proxyInstance = createProxy(proxyClass, interceptor);
62             return mockedType.cast(proxyInstance);
63         } catch (ClassCastException cce) {
64             // NPE unlikely to happen because CCE will only happen on the cast statement
65             throw new MockitoException(join(
66                 "ClassCastException occurred while creating the mockito proxy :",
67                 "  class to imposterize : '" + mockedType.getCanonicalName() + "', loaded by classloader : '" + mockedType.getClassLoader() + "'",
68                 "  imposterizing class : '" + proxyClass.getCanonicalName() + "', loaded by classloader : '" + proxyClass.getClassLoader() + "'",
69                 "  proxy instance class : '" + proxyInstance.getClass().getCanonicalName() + "', loaded by classloader : '" + proxyInstance.getClass().getClassLoader() + "'",
70                 "",
71                 "You might experience classloading issues, disabling the Objenesis cache *might* help (see MockitoConfiguration)"
72             ), cce);
73         } finally {
74             setConstructorsAccessible(mockedType, false);
75         }
76     }
77 
setConstructorsAccessible(Class<?> mockedType, boolean accessible)78     public void setConstructorsAccessible(Class<?> mockedType, boolean accessible) {
79         for (Constructor<?> constructor : mockedType.getDeclaredConstructors()) {
80             constructor.setAccessible(accessible);
81         }
82     }
83 
createProxyClass(Class<?> mockedType, Class<?>... interfaces)84     public Class<?> createProxyClass(Class<?> mockedType, Class<?>... interfaces) {
85         if (mockedType == Object.class) {
86             mockedType = ClassWithSuperclassToWorkAroundCglibBug.class;
87         }
88 
89         Enhancer enhancer = new Enhancer() {
90             @Override
91             @SuppressWarnings("unchecked")
92             protected void filterConstructors(Class sc, List constructors) {
93                 // Don't filter
94             }
95         };
96         enhancer.setClassLoader(SearchingClassLoader.combineLoadersOf(mockedType));
97         enhancer.setUseFactory(true);
98         if (mockedType.isInterface()) {
99             enhancer.setSuperclass(Object.class);
100             enhancer.setInterfaces(prepend(mockedType, interfaces));
101         } else {
102             enhancer.setSuperclass(mockedType);
103             enhancer.setInterfaces(interfaces);
104         }
105         enhancer.setCallbackTypes(new Class[]{MethodInterceptor.class, NoOp.class});
106         enhancer.setCallbackFilter(IGNORE_BRIDGE_METHODS);
107         if (mockedType.getSigners() != null) {
108             enhancer.setNamingPolicy(NAMING_POLICY_THAT_ALLOWS_IMPOSTERISATION_OF_CLASSES_IN_SIGNED_PACKAGES);
109         } else {
110             enhancer.setNamingPolicy(MockitoNamingPolicy.INSTANCE);
111         }
112 
113         enhancer.setSerialVersionUID(42L);
114 
115         try {
116             return enhancer.createClass();
117         } catch (CodeGenerationException e) {
118             if (Modifier.isPrivate(mockedType.getModifiers())) {
119                 throw new MockitoException("\n"
120                         + "Mockito cannot mock this class: " + mockedType
121                         + ".\n"
122                         + "Most likely it is a private class that is not visible by Mockito");
123             }
124             throw new MockitoException("\n"
125                     + "Mockito cannot mock this class: " + mockedType
126                     + "\n"
127                     + "Mockito can only mock visible & non-final classes."
128                     + "\n"
129                     + "If you're not sure why you're getting this error, please report to the mailing list.", e);
130         }
131     }
132 
createProxy(Class<?> proxyClass, final MethodInterceptor interceptor)133     private Object createProxy(Class<?> proxyClass, final MethodInterceptor interceptor) {
134         Factory proxy = (Factory) objenesis.newInstance(proxyClass);
135         proxy.setCallbacks(new Callback[] {interceptor, SerializableNoOp.SERIALIZABLE_INSTANCE });
136         return proxy;
137     }
138 
prepend(Class<?> first, Class<?>... rest)139     private Class<?>[] prepend(Class<?> first, Class<?>... rest) {
140         Class<?>[] all = new Class<?>[rest.length+1];
141         all[0] = first;
142         System.arraycopy(rest, 0, all, 1, rest.length);
143         return all;
144     }
145 
146     public static class ClassWithSuperclassToWorkAroundCglibBug {}
147 
148 }