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 }