/*
*
* * Copyright 2010, Unitils.org
* *
* * 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.unitils.mock.core.proxy;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.objenesis.Objenesis;
import org.objenesis.ObjenesisStd;
import org.unitils.core.UnitilsException;
import java.util.HashSet;
import java.util.Set;
import static java.util.Arrays.asList;
import static org.unitils.util.ReflectionUtils.createInstanceOfType;
/**
* Utility class to create and work with proxy objects.
*
* @author Kenny Claes
* @author Filip Neven
* @author Tim Ducheyne
*/
public class ProxyFactory {
private static Log logger = LogFactory.getLog(ProxyFactory.class);
/**
* Creates a proxy object for the given type. All method invocations will be passed to the given invocation handler.
* If possible, the default constructor (can be private) will be used. If there is no default constructor,
* no constructor will be called.
*
* @param mockName The name of the mock, not null
* @param invocationHandler The handler that will handle the method invocations of the proxy, not null.
* @param proxiedClass The type to proxy, not null
* @param implementedInterfaces Additional interfaces that the proxy must implement
* @return The proxy object, not null
*/
public static <T> T createProxy(String mockName, ProxyInvocationHandler invocationHandler, Class<T> proxiedClass, Class<?>... implementedInterfaces) {
return createProxy(mockName, true, invocationHandler, proxiedClass, implementedInterfaces);
}
/**
* Creates a proxy object for the given type. All method invocations will be passed to the given invocation handler.
* No constructor or class-initialization will be called.
*
* @param mockName The name of the mock, not null
* @param invocationHandler The handler that will handle the method invocations of the proxy, not null.
* @param proxiedClass The type to proxy, not null
* @param implementedInterfaces Additional interfaces that the proxy must implement
* @return The proxy object, not null
*/
public static <T> T createUninitializedProxy(String mockName, ProxyInvocationHandler invocationHandler, Class<T> proxiedClass, Class<?>... implementedInterfaces) {
return createProxy(mockName, false, invocationHandler, proxiedClass, implementedInterfaces);
}
/**
* Creates a proxy object for the given type. All method invocations will be passed to the given invocation handler.
*
* @param mockName The name of the mock, not null
* @param initialize If possible, use the default constructor and initialize all fields
* @param implementedInterfaces Additional interfaces that the proxy must implement
* @param proxiedClass The type to proxy, not null
* @param invocationHandler The handler that will handle the method invocations of the proxy, not null.
* @return The proxy object, not null
*/
@SuppressWarnings({"unchecked"})
protected static <T> T createProxy(String mockName, boolean initialize, ProxyInvocationHandler invocationHandler, Class<T> proxiedClass, Class<?>... implementedInterfaces) {
Class<T> enhancedClass = createEnhancedClass(proxiedClass, implementedInterfaces);
Factory proxy;
if (initialize && !proxiedClass.isInterface()) {
proxy = (Factory) createInitializedOrUninitializedInstanceOfType(enhancedClass);
} else {
proxy = (Factory) createUninitializedInstanceOfType(enhancedClass);
}
proxy.setCallbacks(new Callback[]{new CglibProxyMethodInterceptor(mockName, proxiedClass, invocationHandler)});
return (T) proxy;
}
/**
* Creates an instance of the given type. First we try to create an instance using the default constructor.
* If this doesn't work, eg if there is no default constructor, we try using objenesis. This way the class doesn't
* have to offer an empty constructor in order for this method to succeed.
*
* @param <T> The type of the instance
* @param clazz The class for which an instance is requested
* @return An instance of the given class
*/
@SuppressWarnings("unchecked")
public static <T> T createInitializedOrUninitializedInstanceOfType(Class<T> clazz) {
try {
return createInstanceOfType(clazz, true);
} catch (UnitilsException e) {
logger.warn("Could not create initialized instance of type " + clazz.getSimpleName() + ". No no-arg constructor found. All fields in the instance will have the java default values. Add a default constructor (can be private) if the fields should be initialized. If this concerns an innerclass, make sure it is declared static. Partial mocking of non-static innerclasses is not supported.");
}
// unable to create type using regular constuctor, try objenesis
return createUninitializedInstanceOfType(clazz);
}
/**
* Creates an instance of the given type. First we try to create an instance using the default constructor.
* No constructor or class-initialization will be called.
*
* @param <T> The type of the instance
* @param clazz The class for which an instance is requested
* @return An instance of the given class
*/
@SuppressWarnings("unchecked")
public static <T> T createUninitializedInstanceOfType(Class<T> clazz) {
Objenesis objenesis = new ObjenesisStd();
return (T) objenesis.newInstance(clazz);
}
@SuppressWarnings("unchecked")
protected static <T> Class<T> createEnhancedClass(Class<T> proxiedClass, Class<?>... implementedInterfaces) {
Enhancer enhancer = new Enhancer();
Set<Class<?>> interfaces = new HashSet<Class<?>>();
if (proxiedClass.isInterface()) {
enhancer.setSuperclass(Object.class);
interfaces.add(proxiedClass);
} else {
enhancer.setSuperclass(proxiedClass);
}
if (implementedInterfaces != null && implementedInterfaces.length > 0) {
interfaces.addAll(asList(implementedInterfaces));
}
if (!interfaces.isEmpty()) {
enhancer.setInterfaces(interfaces.toArray(new Class<?>[interfaces.size()]));
}
enhancer.setCallbackType(MethodInterceptor.class);
enhancer.setUseFactory(true);
return enhancer.createClass();
}
}