package tc.oc.commons.core.reflect; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import com.google.common.collect.ImmutableMap; import com.google.common.reflect.Invokable; import com.google.common.reflect.TypeToken; import com.google.inject.TypeLiteral; import tc.oc.commons.core.util.ExceptionUtils; import tc.oc.commons.core.util.ProxyUtils; import static com.google.common.base.Preconditions.checkArgument; /** * Given an interface and a target class, these methods generate a proxy object used to * access methods and fields in the target class, bypassing visibility and access restrictions. * The names and signatures of the methods in the proxy interface are used to resolve * their respective members in the target class, and strict type checking is performed. * * When you need to access private members in a 3rd party class, this is somewhat * cleaner and safer than using reflection and dynamic invocation directly. If you ensure * that the proxy is created at application startup, then you will know right away if the * target member changes. */ public class Delegates { public static <T> T newStaticMethodDelegate(TypeToken<T> proxyType, Class<?> targetType) { return ProxyUtils.newProxy((Class<T>) proxyType.getRawType(), new StaticMethodDelegate<>(proxyType, targetType)); } public static <T> T newStaticMethodDelegate(TypeLiteral<T> proxyType, Class<?> targetType) { return newStaticMethodDelegate(Types.toToken(proxyType), targetType); } public static <T> T newStaticMethodDelegate(Class<T> proxyType, Class<?> targetType) { return newStaticMethodDelegate(TypeToken.of(proxyType), targetType); } public static <T> T newStaticFieldDelegate(TypeToken<T> proxyType, Class<?> targetType) { return ProxyUtils.newProxy((Class<T>) proxyType.getRawType(), new StaticFieldDelegate<>(proxyType, targetType)); } public static <T> T newStaticFieldDelegate(TypeLiteral<T> proxyType, Class<?> targetType) { return newStaticFieldDelegate(Types.toToken(proxyType), targetType); } public static <T> T newStaticFieldDelegate(Class<T> proxyType, Class<?> targetType) { return newStaticFieldDelegate(TypeToken.of(proxyType), targetType); } public static <T> T newConstructorDelegate(TypeToken<T> proxyType, Class<?> targetType) { return ProxyUtils.newProxy((Class<T>) proxyType.getRawType(), new ConstructorDelegate<>(proxyType, targetType)); } public static <T> T newConstructorDelegate(TypeLiteral<T> proxyType, Class<?> targetType) { return newConstructorDelegate(Types.toToken(proxyType), targetType); } public static <T> T newConstructorDelegate(Class<T> proxyType, Class<?> targetType) { return newConstructorDelegate(TypeToken.of(proxyType), targetType); } } abstract class BaseDelegate<T> implements InvocationHandler { final Class<?> targetType; final MethodHandles.Lookup lookup; final ImmutableMap<Method, MethodHandle> map; BaseDelegate(TypeToken<T> proxyType, Class<?> targetType) { this.targetType = targetType; this.lookup = MethodHandleUtils.privateLookup(targetType); final Class<T> rawProxyType = (Class<T>) proxyType.getRawType(); checkArgument(rawProxyType.isInterface()); final ImmutableMap.Builder<Method, MethodHandle> builder = ImmutableMap.builder(); for(Method rawProxyMethod : rawProxyType.getMethods()) { final Invokable<T, ?> proxyMethod = proxyType.method(rawProxyMethod); try { builder.put(rawProxyMethod, createHandle(rawProxyMethod, proxyMethod)); } catch(NoSuchMethodException e) { throw new NoSuchMethodError(missingError(proxyMethod)); } catch(NoSuchFieldException e) { throw new NoSuchFieldError(missingError(proxyMethod)); } catch(ReflectiveOperationException e) { throw ExceptionUtils.propagate(e); } } this.map = builder.build(); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return map.get(method).invokeWithArguments(args); } abstract String missingError(Invokable<T, ?> proxyMethod); abstract MethodHandle createHandle(Method rawProxyMethod, Invokable<T, ?> proxyMethod) throws ReflectiveOperationException; } class ConstructorDelegate<T> extends BaseDelegate<T> { ConstructorDelegate(TypeToken<T> proxyType, Class<?> targetType) { super(proxyType, targetType); } @Override String missingError(Invokable<T, ?> proxyMethod) { return "Target class " + targetType.getName() + " has no constructor matching " + proxyMethod; } @Override MethodHandle createHandle(Method rawProxyMethod, Invokable<T, ?> proxyMethod) throws ReflectiveOperationException { if(!proxyMethod.getReturnType().getRawType().isAssignableFrom(targetType)) { throw new MethodFormException(rawProxyMethod, "Constructor delegate must return target type " + targetType.getName()); } // findConstructor requires the return type to be void return lookup.findConstructor(targetType, Methods.methodType(proxyMethod) .changeReturnType(void.class)); } } class StaticMethodDelegate<T> extends BaseDelegate<T> { StaticMethodDelegate(TypeToken<T> proxyType, Class<?> targetType) { super(proxyType, targetType); } @Override String missingError(Invokable<T, ?> proxyMethod) { return "Target class " + targetType.getName() + " has no static method matching " + proxyMethod; } @Override MethodHandle createHandle(Method rawProxyMethod, Invokable<T, ?> proxyMethod) throws ReflectiveOperationException { return lookup.findStatic(targetType, proxyMethod.getName(), Methods.methodType(proxyMethod)); } } class StaticFieldDelegate<T> extends BaseDelegate<T> { StaticFieldDelegate(TypeToken<T> proxyType, Class<?> targetType) { super(proxyType, targetType); } @Override String missingError(Invokable<T, ?> proxyMethod) { return "Target class " + targetType.getName() + " has no static field matching " + proxyMethod; } @Override MethodHandle createHandle(Method rawProxyMethod, Invokable<T, ?> proxyMethod) throws ReflectiveOperationException { if(proxyMethod.getReturnType().getRawType().equals(void.class)) { if(proxyMethod.getParameters().size() == 1) { return lookup.findStaticSetter(targetType, proxyMethod.getName(), proxyMethod.getParameters().get(0).getType().getRawType()); } } else { if(proxyMethod.getParameters().isEmpty()) { return lookup.findStaticGetter(targetType, proxyMethod.getName(), proxyMethod.getReturnType().getRawType()); } } throw new MethodFormException(rawProxyMethod, "Field delegate method does not have a getter or setter signature"); } }