package com.googlecode.totallylazy.proxy; import com.googlecode.totallylazy.LazyException; import com.googlecode.totallylazy.Randoms; import com.googlecode.totallylazy.Runnables; import com.googlecode.totallylazy.functions.Lazy; import com.googlecode.totallylazy.reflection.Asm; import com.googlecode.totallylazy.reflection.Declaration; import com.googlecode.totallylazy.reflection.Reflection; import com.googlecode.totallylazy.reflection.Types; import jdk.internal.org.objectweb.asm.ClassWriter; import jdk.internal.org.objectweb.asm.MethodVisitor; import jdk.internal.org.objectweb.asm.Type; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import static com.googlecode.totallylazy.Runnables.VOID; import static com.googlecode.totallylazy.Sequences.sequence; import static com.googlecode.totallylazy.Unchecked.cast; import static java.lang.String.format; import static java.lang.reflect.Modifier.isFinal; import static java.lang.reflect.Modifier.isPublic; import static java.lang.reflect.Modifier.isStatic; import static java.lang.reflect.Proxy.newProxyInstance; import static jdk.internal.org.objectweb.asm.Opcodes.*; public class Proxy { public static final String HANDLER = "handler"; private static ConcurrentMap<Class<?>, Class<?>> cache = new ConcurrentHashMap<>(); public static <T> T proxy(Class<T> aClass, InvocationHandler handler) { if(isFinal(aClass.getModifiers())) throw new UnsupportedOperationException("Can not create a Proxy instance of final class: " + aClass); try { if (aClass.isInterface()) { Object instance = newProxyInstance(aClass.getClassLoader(), new Class[]{aClass}, handler); return cast(instance); } Class<?> definedClass = cache.computeIfAbsent(aClass, k -> { String name = generatePackageName(k) + ".Proxy" + UUID.randomUUID(); String jvmName = name.replace('.', '/'); byte[] bytes = bytes(jvmName, k); return defineClass(k, name, bytes); }); T instance = Reflection.create(definedClass); Field field = definedClass.getDeclaredField(HANDLER); field.setAccessible(true); field.set(instance, handler); return instance; } catch (Exception e) { throw LazyException.lazyException(e); } } private static Class<?> defineClass(Class<?> aClass, String name, byte[] bytes) { return Reflection.defineClass(isPublic(aClass.getModifiers()) ? Proxy.class.getClassLoader() : aClass.getClassLoader(), name, bytes); } private static String generatePackageName(Class<?> aClass) { return isPublic(aClass.getModifiers()) ? packageName(Proxy.class) : packageName(aClass); } private static String packageName(Class<?> aClass){ return sequence(aClass.getName().split("\\.")).init().toString("."); } public static byte[] bytes(String name, Class<?> superClass) { ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); String superName = Type.getInternalName(superClass); cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, name, null, superName, null); handlerField(cw); for (Method method : sequence(superClass.getMethods()). reject(m -> isFinal(m.getModifiers())). reject(m -> isStatic(m.getModifiers())). reject(m -> m.getName().equals("toString"))) { method(cw, name, method, superName); } cw.visitEnd(); return cw.toByteArray(); } private static void handlerField(ClassWriter cw) { cw.visitField(ACC_PROTECTED, HANDLER, "Ljava/lang/reflect/InvocationHandler;", null, null).visitEnd(); } private static void method(ClassWriter cw, String name, Method method, String superClass) { String[] exceptions = sequence(method.getExceptionTypes()).map(Type::getInternalName).toArray(String.class); String methodName = method.getName(); String methodDescriptor = Type.getMethodDescriptor(method); MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + (method.isVarArgs() ? ACC_VARARGS : 0), methodName, methodDescriptor, null, exceptions); mv.visitCode(); loadHandler(mv, name); loadThis(mv); loadMethod(mv, superClass, methodName, methodDescriptor); loadArguments(mv, method); invokeHandler(mv); returnResult(mv, method); mv.visitMaxs(0, 0); mv.visitEnd(); } private static void invokeHandler(MethodVisitor mv) { mv.visitMethodInsn(INVOKEINTERFACE, "java/lang/reflect/InvocationHandler", "invoke", "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;", true); } private static void loadThis(MethodVisitor mv) { mv.visitVarInsn(ALOAD, 0); } private static void loadHandler(MethodVisitor mv, String name) { loadThis(mv); mv.visitFieldInsn(GETFIELD, name, HANDLER, "Ljava/lang/reflect/InvocationHandler;"); } private static void loadMethod(MethodVisitor mv, String superClass, String methodName, String methodDescriptor) { mv.visitLdcInsn(superClass); mv.visitLdcInsn(methodName); mv.visitLdcInsn(methodDescriptor); mv.visitMethodInsn(INVOKESTATIC, "com/googlecode/totallylazy/reflection/Methods", "method", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/reflect/Method;", false); } private static void loadArguments(MethodVisitor mv, Method method) { int arguments = method.getParameterCount(); mv.visitLdcInsn(arguments); mv.visitTypeInsn(ANEWARRAY, "java/lang/Object"); Class<?>[] parameterTypes = method.getParameterTypes(); for (int i = 0, local = 0; i < parameterTypes.length; i++) { Class<?> parameterType = parameterTypes[i]; local++; mv.visitInsn(DUP); mv.visitLdcInsn(i); mv.visitVarInsn(Asm.load(parameterType), local); if (parameterType.isPrimitive() && !parameterType.equals(void.class)) { String internalName = Type.getInternalName(Reflection.box(parameterType)); mv.visitMethodInsn(INVOKESTATIC, internalName, "valueOf", format("(%s)L%s;", Type.getDescriptor(parameterType), internalName), false); } if (parameterType.equals(double.class) || parameterType.equals(long.class)) local++; mv.visitInsn(AASTORE); } } private static void returnResult(MethodVisitor mv, Method method) { Class<?> returnType = method.getReturnType(); if (returnType.equals(void.class)) { mv.visitInsn(POP); } else if (returnType.isPrimitive()) { String internalName = Type.getInternalName(Reflection.box(returnType)); mv.visitTypeInsn(CHECKCAST, internalName); mv.visitMethodInsn(INVOKEVIRTUAL, internalName, format("%sValue", returnType.getSimpleName()), format("()%s", Type.getDescriptor(returnType)), false); } else { mv.visitTypeInsn(CHECKCAST, Type.getInternalName(returnType)); } mv.visitInsn(Asm.returns(returnType)); } public static <T> T lazy(Callable<? extends T> callable) { Declaration declaration = Declaration.declaration(); return lazy(declaration.<T>forClass(), callable); } public static <T> T lazy(Class<? extends T> aClass, Callable<? extends T> callable) { if(aClass.equals(void.class) || aClass.equals(Void.class)) return null; Lazy<T> lazy = Lazy.lazy(callable); return proxy(aClass, (proxy, method, arguments) -> method.invoke(lazy.value(), arguments)); } public static <T> T async(T instance, ExecutorService executor) { return async(cast(instance.getClass()), instance, executor); } public static <T, I extends T> T async(Class<T> aClass, I instance, ExecutorService executor) { return Proxy.proxy(aClass, (proxy, method, args) -> { Future<?> future = executor.submit(() -> method.invoke(instance, args)); return Proxy.lazy(method.getReturnType(), future::get); }); } public static boolean isProxy(Object instance) { return isProxy(instance.getClass()); } public static boolean isProxy(Class<?> aClass) { return java.lang.reflect.Proxy.class.isAssignableFrom(aClass) || Proxy.class.isAssignableFrom(aClass); } }