package net.sourceforge.javautil.common.reflection.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.sourceforge.javautil.common.ReflectionUtil;
import net.sourceforge.javautil.common.exception.ThrowableManagerRegistry;
/**
* This is a standard implementation of {@link ReflectiveProxy}. It respects the use of {@link ReflectiveWrapper} and
* {@link ReflectiveConstructor} on proxy interfaces.
*
* @author elponderador
*/
public class ReflectiveObjectProxy extends ReflectiveProxy {
/**
* This will attempt to load the class via the {@link ReflectiveWrapper} annotation on the interface,
* and optionally and instance of it in order to facility declaration use.
*
* @param newInstance True if a new instance of the class should be created, otherwise this will be a static method interface using
* only the class
*
* @see #createProxy(ClassLoader, Class, Object)
*/
public static <T> T createProxy (ClassLoader cl, Class<T> iface, boolean newInstance) {
ReflectiveWrapper wrapper = iface.getAnnotation(ReflectiveWrapper.class);
if (wrapper != null) {
Class target = ReflectionUtil.getClass(wrapper.value(), cl);
return createProxy(cl, iface, newInstance ? ReflectionUtil.newInstance(target, new Class[0]) : target);
}
throw new IllegalArgumentException("Could not determine wrapped class from : " + iface);
}
/**
* @param <T> The type of Proxy to be returned.
* @param cl The {@link ClassLoader} to be used for boundary loading.
* @param iface The interface that defines the proxy.
* @param target The boundary object that will be the target of the real method invocations.
* @return A proxy that can be used to invoke boundary methods.
*/
public static <T> T createProxy (ClassLoader cl, Class<T> iface, Object target) {
return (T) Proxy.newProxyInstance(cl, new Class[] { iface }, new ReflectiveObjectProxy(target));
}
/**
* @param proxy The proxy object
* @return The actual target object, behind the proxy
*/
public static Object getTargetObject (Object proxy) {
InvocationHandler ih = Proxy.getInvocationHandler(proxy);
if (ih instanceof ReflectiveObjectProxy) {
return ((ReflectiveObjectProxy)ih).object;
} else
throw new IllegalArgumentException(proxy + " is not handled by ReflectiveObjectProxy");
}
/**
* This can be used in order to indirectly instantiate a boundary class and at the same time
* return a typed proxy for interacting with the new instance. Interfaces must use the {@link ReflectiveConstructor}
* for defining constructors.
*
* @param <T> The type of Proxy to be returned.
* @param proxyCL The {@link ClassLoader} for the proxy.
* @param cl The {@link ClassLoader} for boundary loading.
* @param iface The interface that defines the proxy.
* @param parameters The parameters to pass to the constructor call.
* @return A proxy that wraps the new instance of the boundary class.
*/
public static <T> T newInstance (ClassLoader proxyCL, ClassLoader cl, Class<T> iface, Object... parameters) {
try {
if (!iface.isInterface() || iface.getAnnotation(ReflectiveConstructor.class) == null)
throw new IllegalArgumentException(iface + " must be a reflective wrapper.");
ReflectiveConstructor c = iface.getAnnotation(ReflectiveConstructor.class);
if (c == null)
throw new IllegalArgumentException("This wrapper does not support new instance construction");
Class[] types = c.parameterTypes();
for (int s=0; s<types.length; s++) types[s] = translateParameterType(cl, types[s]);
Object[] args = new Object[parameters.length];
for (int o=0; o<parameters.length; o++) args[o] = translateArgument(parameters[o]);
return createProxy(proxyCL, iface, ReflectionUtil.newInstance(cl.loadClass( c.type() ), types, args));
} catch (ClassNotFoundException e) {
throw ThrowableManagerRegistry.caught(e);
}
}
/**
* This, utilizing {@link ReflectiveWrapper}, allows on to easily translate proxy types to boundary types.
*
* @param cl The {@link ClassLoader} to load boundary classes.
* @param parameterType The type to be passed, possibly a proxy type
* @return The boundary type class represented by the parameterType, or just passes on the parameterType
*/
public static Class translateParameterType(ClassLoader cl, Class parameterType) {
ReflectiveWrapper wrapper = (ReflectiveWrapper) parameterType.getAnnotation(ReflectiveWrapper.class);
return wrapper == null ? parameterType : ReflectionUtil.getClass(wrapper.value(), cl);
}
/**
* @param argument The regular or proxy object
* @return The possibly translated boundary object
*/
public static Object translateArgument (Object argument) {
if (argument != null && Proxy.isProxyClass(argument.getClass())) {
InvocationHandler ih = Proxy.getInvocationHandler(argument);
if (ih instanceof ReflectiveProxy) {
return ((ReflectiveProxy)ih).getTarget();
}
}
return argument;
}
private Object object;
private Class clazz;
private ClassLoader cl;
private Map<String, Class> interfaces = new HashMap<String, Class>();
/**
* @param object The object to wrap
*/
public ReflectiveObjectProxy(Object object) {
this.object = object;
this.clazz = object instanceof Class ? (Class) object : object.getClass();
this.cl = clazz.getClassLoader();
}
@Override protected Class getClassForMethod(Method method) { return clazz; }
@Override protected Object getObjectForMethod(Method method) { return object; }
@Override protected boolean isMethodStatic(Method method, Class[] realTypes) throws NoSuchMethodException {
if (object instanceof Class) return true;
return Modifier.isStatic( clazz.getMethod(method.getName(), realTypes).getModifiers() );
}
@Override protected Object createProxyForResult(Object result, Class returnType, Method method) {
// For collections, we need to get the parameterized type to see if any type of automatic mapping might be necesary.
if (returnType == List.class) {
Class clazz = (Class) ((ParameterizedType) method.getGenericReturnType()).getActualTypeArguments()[0];
if (clazz.isInterface() && clazz.getAnnotation(ReflectiveWrapper.class) != null) return new ReflectiveList( this, (List) result, clazz );
} else if (returnType == Map.class) {
Class clazz = (Class) ((ParameterizedType) method.getGenericReturnType()).getActualTypeArguments()[1];
if (clazz.isInterface() && clazz.getAnnotation(ReflectiveWrapper.class) != null) return new ReflectiveMap( (Map) result, this, clazz );
} else if (returnType == Set.class) {
Class clazz = (Class) ((ParameterizedType) method.getGenericReturnType()).getActualTypeArguments()[0];
if (clazz.isInterface() && clazz.getAnnotation(ReflectiveWrapper.class) != null) return new ReflectiveSet( (Set) result, this, clazz );
} else if (returnType == Collection.class) {
Class clazz = (Class) ((ParameterizedType) method.getGenericReturnType()).getActualTypeArguments()[0];
if (clazz.isInterface() && clazz.getAnnotation(ReflectiveWrapper.class) != null) return new ReflectiveCollection( (Collection) result, this, clazz );
} else if (returnType == Iterator.class) {
Class clazz = (Class) ((ParameterizedType) method.getGenericReturnType()).getActualTypeArguments()[0];
if (clazz.isInterface() && clazz.getAnnotation(ReflectiveWrapper.class) != null) return new ReflectiveIterator( this, clazz, (Iterator) result );
}
// We now try to get the interface automatically or from the manually mapped lookup
Class iface = returnType != null && returnType.isInterface() && returnType.getAnnotation(ReflectiveWrapper.class) != null ? returnType : this.interfaces.get(result.getClass());
return iface == null ? null : createProxy(Thread.currentThread().getContextClassLoader(), iface, result);
}
@Override protected Object mapArgument(Object argument, Method method) {
return translateArgument(argument);
}
@Override protected Class mapParameterType(Class parameterType, Method method) {
return translateParameterType(cl, parameterType);
}
@Override protected Object getTarget() { return object; }
/**
* @return The boundary class loader
*/
public ClassLoader getClassLoader () { return cl; }
/**
* @param cl The boundary class loader
*/
public void setClassLoader(ClassLoader cl) { this.cl = cl; }
/**
* This will allow users of this proxy to manually declare class mapping for defaults or where
* the proxy will not be able to map it automatically.
*
* @param resultType The type (class name) to be wrapped/proxied.
* @param iface The proxy interface for the type
*/
public void wrapResults (String resultType, Class iface) { this.interfaces.put(resultType, iface); }
}