org.easymock.internal.ClassProxyFactory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of easymock Show documentation
Show all versions of easymock Show documentation
EasyMock provides an easy way to create Mock Objects for interfaces and classes generating them on the fly
/**
* Copyright 2001-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.easymock.internal;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.*;
import java.lang.reflect.InvocationHandler;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import net.sf.cglib.core.*;
import net.sf.cglib.proxy.*;
import org.easymock.ConstructorArgs;
/**
* Factory generating a mock for a class.
*
* @param
* type of the proxy created
*
* @author Henri Tremblay
*/
public class ClassProxyFactory implements IProxyFactory {
public static class MockMethodInterceptor implements MethodInterceptor, Serializable {
private static final long serialVersionUID = -9054190871232972342L;
private final InvocationHandler handler;
private transient Set mockedMethods;
public MockMethodInterceptor(final InvocationHandler handler) {
this.handler = handler;
}
public Object intercept(final Object obj, Method method, final Object[] args, final MethodProxy proxy)
throws Throwable {
// We conveniently mock abstract methods be default
if (Modifier.isAbstract(method.getModifiers())) {
return handler.invoke(obj, method, args);
}
// Here I need to check if the fillInStackTrace was called by EasyMock inner code
// If yes, invoke super. Otherwise, just behave normally
if (obj instanceof Throwable && method.getName().equals("fillInStackTrace")) {
final Exception e = new Exception();
final StackTraceElement[] elements = e.getStackTrace();
// ///CLOVER:OFF
if (elements.length > 2) {
// ///CLOVER:ON
final StackTraceElement element = elements[2];
if (element.getClassName().equals("org.easymock.internal.MockInvocationHandler")
&& element.getMethodName().equals("invoke")) {
return proxy.invokeSuper(obj, args);
}
}
}
// Bridges should delegate to their bridged method. It should be done before
// checking for mocked methods because only unbridged method are mocked
// It also make sure the method passed to the handler is not the bridge. Normally it
// shouldn't be necessary because bridges are never mocked so are never in the mockedMethods
// map. So the normal case is that is will call invokeSuper which will call the interceptor for
// the bridged method. The problem is that it doesn't happen. It looks like a cglib bug. For
// package scoped bridges (see GenericTest), the interceptor is not called for the bridged
// method. Not normal from my point of view.
if (method.isBridge()) {
method = BridgeMethodResolver.findBridgedMethod(method);
}
if (mockedMethods != null && !mockedMethods.contains(method)) {
return proxy.invokeSuper(obj, args);
}
return handler.invoke(obj, method, args);
}
public InvocationHandler getHandler() {
return handler;
}
public void setMockedMethods(final Method... mockedMethods) {
this.mockedMethods = new HashSet(Arrays.asList(mockedMethods));
}
@SuppressWarnings("unchecked")
private void readObject(final java.io.ObjectInputStream stream) throws IOException,
ClassNotFoundException {
stream.defaultReadObject();
final Set methods = (Set) stream
.readObject();
if (methods == null) {
return;
}
mockedMethods = new HashSet(methods.size());
for (final MethodSerializationWrapper m : methods) {
try {
mockedMethods.add(m.getMethod());
} catch (final NoSuchMethodException e) {
// ///CLOVER:OFF
throw new IOException(e.toString());
// ///CLOVER:ON
}
}
}
private void writeObject(final java.io.ObjectOutputStream stream) throws IOException {
stream.defaultWriteObject();
if (mockedMethods == null) {
stream.writeObject(null);
return;
}
final Set methods = new HashSet(
mockedMethods.size());
for (final Method m : mockedMethods) {
methods.add(new MethodSerializationWrapper(m));
}
stream.writeObject(methods);
}
}
// ///CLOVER:OFF (I don't know how to test it automatically yet)
private static final NamingPolicy ALLOWS_MOCKING_CLASSES_IN_SIGNED_PACKAGES = new DefaultNamingPolicy() {
@Override
public String getClassName(final String prefix, final String source, final Object key,
final Predicate names) {
return "codegen." + super.getClassName(prefix, source, key, names);
}
};
// ///CLOVER:ON
@SuppressWarnings("unchecked")
public T createProxy(final Class toMock, final InvocationHandler handler) {
final Enhancer enhancer = createEnhancer(toMock);
final MethodInterceptor interceptor = new MockMethodInterceptor(handler);
enhancer.setCallbackType(interceptor.getClass());
Class mockClass;
try {
mockClass = enhancer.createClass();
} catch (final CodeGenerationException e) {
// ///CLOVER:OFF (don't know how to test it automatically)
// Probably caused by a NoClassDefFoundError, let's try EasyMock class loader
// instead of the default one (which is the class to mock one
// This is required by Eclipse Plug-ins, the mock class loader doesn't see
// cglib most of the time. Using EasyMock class loader solves this
// See issue ID: 2994002
enhancer.setClassLoader(getClass().getClassLoader());
mockClass = enhancer.createClass();
// ///CLOVER:ON
}
try {
Enhancer.registerCallbacks(mockClass, new Callback[] { interceptor });
if (ClassExtensionHelper.getCurrentConstructorArgs() != null) {
// Really instantiate the class
final ConstructorArgs args = ClassExtensionHelper.getCurrentConstructorArgs();
Constructor cstr;
try {
// Get the constructor with the same params
cstr = mockClass.getDeclaredConstructor(args.getConstructor().getParameterTypes());
} catch (final NoSuchMethodException e) {
// Shouldn't happen, constructor is checked when ConstructorArgs is instantiated
// ///CLOVER:OFF
throw new RuntimeException("Fail to find constructor for param types", e);
// ///CLOVER:ON
}
T mock;
try {
cstr.setAccessible(true); // So we can call a protected
// constructor
mock = (T) cstr.newInstance(args.getInitArgs());
} catch (final InstantiationException e) {
// ///CLOVER:OFF
throw new RuntimeException("Failed to instantiate mock calling constructor", e);
// ///CLOVER:ON
} catch (final IllegalAccessException e) {
// ///CLOVER:OFF
throw new RuntimeException("Failed to instantiate mock calling constructor", e);
// ///CLOVER:ON
} catch (final InvocationTargetException e) {
throw new RuntimeException(
"Failed to instantiate mock calling constructor: Exception in constructor", e
.getTargetException());
}
return mock;
} else {
// Do not call any constructor
Factory mock;
try {
mock = (Factory) ClassInstantiatorFactory.getInstantiator().newInstance(mockClass);
} catch (final InstantiationException e) {
// ///CLOVER:OFF
throw new RuntimeException("Fail to instantiate mock for " + toMock + " on "
+ ClassInstantiatorFactory.getJVM() + " JVM");
// ///CLOVER:ON
}
// This call is required. CGlib has some "magic code" making sure a
// callback is used by only one instance of a given class. So only
// the
// instance created right after registering the callback will get
// it.
// However, this is done in the constructor which I'm bypassing to
// allow class instantiation without calling a constructor.
// Fortunately, the "magic code" is also called in getCallback which
// is
// why I'm calling it here mock.getCallback(0);
mock.getCallback(0);
return (T) mock;
}
} finally {
// To avoid CGLib out of memory issues
Enhancer.registerCallbacks(mockClass, null);
}
}
private Enhancer createEnhancer(final Class toMock) {
// Create the mock
final Enhancer enhancer = new Enhancer() {
/**
* Filter all private constructors but do not check that there are
* some left
*/
@SuppressWarnings("unchecked")
@Override
protected void filterConstructors(final Class sc, final List constructors) {
CollectionUtils.filter(constructors, new VisibilityPredicate(sc, true));
}
};
enhancer.setSuperclass(toMock);
// ///CLOVER:OFF (I don't know how to test it automatically yet)
// See issue ID: 2994002
if (toMock.getSigners() != null) {
enhancer.setNamingPolicy(ALLOWS_MOCKING_CLASSES_IN_SIGNED_PACKAGES);
}
// ///CLOVER:ON
return enhancer;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy