/* * The MIT License (MIT) * * Copyright (c) 2015 Igor Konev * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package sun.misc; import java.io.File; import java.io.IOException; import java.lang.reflect.Array; import java.lang.reflect.Method; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Map; import jdk.internal.org.objectweb.asm.ClassVisitor; import jdk.internal.org.objectweb.asm.ClassWriter; import jdk.internal.org.objectweb.asm.Label; import jdk.internal.org.objectweb.asm.MethodVisitor; import jdk.internal.org.objectweb.asm.Type; import sun.security.action.GetBooleanAction; import static jdk.internal.org.objectweb.asm.Opcodes.AASTORE; import static jdk.internal.org.objectweb.asm.Opcodes.ACC_FINAL; import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PRIVATE; import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PUBLIC; import static jdk.internal.org.objectweb.asm.Opcodes.ACC_STATIC; import static jdk.internal.org.objectweb.asm.Opcodes.ACC_SUPER; import static jdk.internal.org.objectweb.asm.Opcodes.ACONST_NULL; import static jdk.internal.org.objectweb.asm.Opcodes.ALOAD; import static jdk.internal.org.objectweb.asm.Opcodes.ANEWARRAY; import static jdk.internal.org.objectweb.asm.Opcodes.ARETURN; import static jdk.internal.org.objectweb.asm.Opcodes.ASTORE; import static jdk.internal.org.objectweb.asm.Opcodes.ATHROW; import static jdk.internal.org.objectweb.asm.Opcodes.BIPUSH; import static jdk.internal.org.objectweb.asm.Opcodes.CHECKCAST; import static jdk.internal.org.objectweb.asm.Opcodes.DLOAD; import static jdk.internal.org.objectweb.asm.Opcodes.DRETURN; import static jdk.internal.org.objectweb.asm.Opcodes.DUP; import static jdk.internal.org.objectweb.asm.Opcodes.FLOAD; import static jdk.internal.org.objectweb.asm.Opcodes.FRETURN; import static jdk.internal.org.objectweb.asm.Opcodes.GETFIELD; import static jdk.internal.org.objectweb.asm.Opcodes.GETSTATIC; import static jdk.internal.org.objectweb.asm.Opcodes.ICONST_0; import static jdk.internal.org.objectweb.asm.Opcodes.ILOAD; import static jdk.internal.org.objectweb.asm.Opcodes.INVOKEINTERFACE; import static jdk.internal.org.objectweb.asm.Opcodes.INVOKESPECIAL; import static jdk.internal.org.objectweb.asm.Opcodes.INVOKESTATIC; import static jdk.internal.org.objectweb.asm.Opcodes.INVOKEVIRTUAL; import static jdk.internal.org.objectweb.asm.Opcodes.IRETURN; import static jdk.internal.org.objectweb.asm.Opcodes.LLOAD; import static jdk.internal.org.objectweb.asm.Opcodes.LRETURN; import static jdk.internal.org.objectweb.asm.Opcodes.NEW; import static jdk.internal.org.objectweb.asm.Opcodes.POP; import static jdk.internal.org.objectweb.asm.Opcodes.PUTSTATIC; import static jdk.internal.org.objectweb.asm.Opcodes.RETURN; import static jdk.internal.org.objectweb.asm.Opcodes.SIPUSH; import static jdk.internal.org.objectweb.asm.Opcodes.V1_6; public class ProxyGenerator { /* * In the comments below, "JVMS" refers to The Java Virtual Machine * Specification Second Edition and "JLS" refers to the original * version of The Java Language Specification, unless otherwise * specified. */ /** * name of the superclass of proxy classes */ private static final String SUPERCLASS_NAME = "java/lang/reflect/Proxy"; /** * name of field for storing a proxy instance's invocation handler */ private static final String HANDLER_FIELD_NAME = "h"; /** * debugging flag for saving generated class files */ private static final boolean saveGeneratedFiles = AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles")); /** * Generate a public proxy class given a name and a list of proxy interfaces. */ public static byte[] generateProxyClass(String name, Class<?>[] interfaces) { return generateProxyClass(name, interfaces, ACC_PUBLIC | ACC_FINAL | ACC_SUPER); } /** * Generate a proxy class given a name and a list of proxy interfaces. * * @param name the class name of the proxy class * @param interfaces proxy interfaces * @param accessFlags access flags of the proxy class */ public static byte[] generateProxyClass(String name, Class<?>[] interfaces, int accessFlags) { ProxyGenerator gen = new ProxyGenerator(name, interfaces, accessFlags); byte[] classFile = gen.generateClassFile(); if (saveGeneratedFiles) { AccessController.doPrivileged(new PrivilegedAction<Void>() { @Override public Void run() { try { int i = name.lastIndexOf('.'); Path path; if (i > 0) { Path dir = Paths.get(name.substring(0, i).replace('.', File.separatorChar)); Files.createDirectories(dir); path = dir.resolve(name.substring(i + 1, name.length()) + ".class"); } else { path = Paths.get(name + ".class"); } Files.write(path, classFile); return null; } catch (IOException e) { throw new InternalError("I/O exception saving generated file: " + e); } } }); } return classFile; } /* preloaded Method objects for methods in java.lang.Object */ private static final Method hashCodeMethod; private static final Method equalsMethod; private static final Method toStringMethod; static { try { hashCodeMethod = Object.class.getMethod("hashCode"); equalsMethod = Object.class.getMethod("equals", Object.class); toStringMethod = Object.class.getMethod("toString"); } catch (NoSuchMethodException e) { throw new NoSuchMethodError(e.getMessage()); } } /** * name of proxy class */ private final String className; /** * proxy interfaces */ private final Class<?>[] interfaces; /** * proxy class access flags */ private final int accessFlags; /** * maps method signature string to list of ProxyMethod objects for * proxy methods with that signature */ private final Map<String, List<ProxyMethod>> proxyMethods = new HashMap<>(); /** * count of ProxyMethod objects added to proxyMethods */ private int proxyMethodCount; /** * Construct a ProxyGenerator to generate a proxy class with the * specified name and for the given interfaces. * <p> * A ProxyGenerator object contains the state for the ongoing * generation of a particular proxy class. */ private ProxyGenerator(String className, Class<?>[] interfaces, int accessFlags) { this.className = className; this.interfaces = interfaces; this.accessFlags = accessFlags; } /** * Generate a class file for the proxy class. This method drives the * class file generation process. */ private byte[] generateClassFile() { /* ============================================================ * Step 1: Assemble ProxyMethod objects for all methods to * generate proxy dispatching code for. */ /* * Record that proxy methods are needed for the hashCode, equals, * and toString methods of java.lang.Object. This is done before * the methods from the proxy interfaces so that the methods from * java.lang.Object take precedence over duplicate methods in the * proxy interfaces. */ addProxyMethod(hashCodeMethod); addProxyMethod(equalsMethod); addProxyMethod(toStringMethod); /* * Now record all of the methods from the proxy interfaces, giving * earlier interfaces precedence over later ones with duplicate * methods. */ for (Class<?> intf : interfaces) { for (Method method : intf.getMethods()) { addProxyMethod(method); } } /* * For each set of proxy methods with the same signature, * verify that the methods' return types are compatible. */ for (List<ProxyMethod> sigmethods : proxyMethods.values()) { checkReturnTypes(sigmethods); } /* ============================================================ * Step 2: Assemble FieldInfo and MethodInfo structs for all of * fields and methods in the class we are generating. */ ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); String[] interfaces = new String[this.interfaces.length]; for (int i = 0, n = this.interfaces.length; i < n; i++) { interfaces[i] = dotToSlash(this.interfaces[i].getName()); } cw.visit(V1_6, accessFlags, dotToSlash(className), null, SUPERCLASS_NAME, interfaces); generateConstructor(cw); for (List<ProxyMethod> sigmethods : proxyMethods.values()) { for (ProxyMethod pm : sigmethods) { // add static field for method's Method object cw.visitField(ACC_PRIVATE | ACC_STATIC, pm.methodFieldName, "Ljava/lang/reflect/Method;", null, null); // generate code for proxy method and add it generateMethod(pm, cw); } } generateStaticInitializer(cw); cw.visitEnd(); /* ============================================================ * Step 3: Write the final class file. */ return cw.toByteArray(); } /** * Add another method to be proxied, either by creating a new * ProxyMethod object or augmenting an old one for a duplicate * method. * <p> * "fromClass" indicates the proxy interface that the method was * found through, which may be different from (a subinterface of) * the method's "declaring class". Note that the first Method * object passed for a given name and descriptor identifies the * Method object (and thus the declaring class) that will be * passed to the invocation handler's "invoke" method for a given * set of duplicate methods. */ private void addProxyMethod(Method method) { Class<?> returnType = method.getReturnType(); Class<?>[] exceptionTypes = method.getExceptionTypes(); String sig = method.getName() + Type.getMethodDescriptor(method); List<ProxyMethod> sigmethods = proxyMethods.get(sig); if (sigmethods != null) { for (ProxyMethod pm : sigmethods) { if (returnType == pm.method.getReturnType()) { /* * Found a match: reduce exception types to the * greatest set of exceptions that can thrown * compatibly with the throws clauses of both * overridden methods. */ List<Class<?>> legalExceptions = new ArrayList<>(); collectCompatibleTypes(exceptionTypes, pm.exceptionTypes, legalExceptions); collectCompatibleTypes(pm.exceptionTypes, exceptionTypes, legalExceptions); pm.exceptionTypes = new Class<?>[legalExceptions.size()]; pm.exceptionTypes = legalExceptions.toArray(pm.exceptionTypes); return; } } } else { sigmethods = new ArrayList<>(3); proxyMethods.put(sig, sigmethods); } sigmethods.add(new ProxyMethod(method, "m" + proxyMethodCount)); proxyMethodCount++; } /** * For a given set of proxy methods with the same signature, check * that their return types are compatible according to the Proxy * specification. * <p> * Specifically, if there is more than one such method, then all * of the return types must be reference types, and there must be * one return type that is assignable to each of the rest of them. */ private static void checkReturnTypes(List<ProxyMethod> methods) { /* * If there is only one method with a given signature, there * cannot be a conflict. This is the only case in which a * primitive (or void) return type is allowed. */ if (methods.size() < 2) { return; } /* * List of return types that are not yet known to be * assignable from ("covered" by) any of the others. */ List<Class<?>> uncoveredReturnTypes = new LinkedList<>(); nextNewReturnType: for (ProxyMethod pm : methods) { Class<?> newReturnType = pm.method.getReturnType(); if (newReturnType.isPrimitive()) { throw new IllegalArgumentException("methods with same signature " + getFriendlyMethodSignature(pm.method.getName(), pm.method.getParameterTypes()) + " but incompatible return types: " + newReturnType.getName() + " and others"); } boolean added = false; /* * Compare the new return type to the existing uncovered * return types. */ ListIterator<Class<?>> liter = uncoveredReturnTypes.listIterator(); while (liter.hasNext()) { Class<?> uncoveredReturnType = liter.next(); /* * If an existing uncovered return type is assignable * to this new one, then we can forget the new one. */ if (newReturnType.isAssignableFrom(uncoveredReturnType)) { assert !added; continue nextNewReturnType; } /* * If the new return type is assignable to an existing * uncovered one, then should replace the existing one * with the new one (or just forget the existing one, * if the new one has already be put in the list). */ if (uncoveredReturnType.isAssignableFrom(newReturnType)) { // (we can assume that each return type is unique) if (added) { liter.remove(); } else { liter.set(newReturnType); added = true; } } } /* * If we got through the list of existing uncovered return * types without an assignability relationship, then add * the new return type to the list of uncovered ones. */ if (!added) { uncoveredReturnTypes.add(newReturnType); } } /* * We shouldn't end up with more than one return type that is * not assignable from any of the others. */ if (uncoveredReturnTypes.size() > 1) { ProxyMethod pm = methods.get(0); throw new IllegalArgumentException("methods with same signature " + getFriendlyMethodSignature(pm.method.getName(), pm.method.getParameterTypes()) + " but incompatible return types: " + uncoveredReturnTypes); } } private void generateMethod(ProxyMethod pm, ClassVisitor cv) { String desc = Type.getMethodDescriptor(pm.method); Class<?>[] exceptionTypes = pm.exceptionTypes; String[] exceptions = new String[exceptionTypes.length]; for (int i = 0, n = exceptionTypes.length; i < n; i++) { exceptions[i] = dotToSlash(exceptionTypes[i].getName()); } MethodVisitor mv = cv.visitMethod(ACC_PUBLIC | ACC_FINAL, pm.method.getName(), desc, null, exceptions); mv.visitCode(); Label start = new Label(); mv.visitLabel(start); Class<?>[] parameterTypes = pm.method.getParameterTypes(); int[] parameterSlot = new int[parameterTypes.length]; int nextSlot = 1; for (int i = 0, n = parameterSlot.length; i < n; i++) { parameterSlot[i] = nextSlot; nextSlot += parameterTypes[i] == long.class || parameterTypes[i] == double.class ? 2 : 1; } mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, SUPERCLASS_NAME, HANDLER_FIELD_NAME, "Ljava/lang/reflect/InvocationHandler;"); mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETSTATIC, dotToSlash(className), pm.methodFieldName, "Ljava/lang/reflect/Method;"); if (parameterTypes.length > 0) { visitPush(mv, parameterTypes.length); mv.visitTypeInsn(ANEWARRAY, "java/lang/Object"); for (int i = 0, n = parameterTypes.length; i < n; i++) { mv.visitInsn(DUP); visitPush(mv, i); codeWrapArgument(parameterTypes[i], parameterSlot[i], mv); mv.visitInsn(AASTORE); } } else { mv.visitInsn(ACONST_NULL); } mv.visitMethodInsn(INVOKEINTERFACE, "java/lang/reflect/InvocationHandler", "invoke", "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;", true); Class<?> returnType = pm.method.getReturnType(); if (returnType == void.class) { mv.visitInsn(POP); mv.visitInsn(RETURN); } else { codeUnwrapReturnValue(returnType, mv); } List<Class<?>> catchList = computeUniqueCatchList(exceptionTypes); if (!catchList.isEmpty()) { Label end = new Label(); mv.visitLabel(end); Label handler1 = new Label(); mv.visitLabel(handler1); for (Class<?> ex : catchList) { mv.visitTryCatchBlock(start, end, handler1, dotToSlash(ex.getName())); } mv.visitInsn(ATHROW); Label handler2 = new Label(); mv.visitLabel(handler2); mv.visitTryCatchBlock(start, end, handler2, "java/lang/Throwable"); mv.visitVarInsn(ASTORE, nextSlot); mv.visitTypeInsn(NEW, "java/lang/reflect/UndeclaredThrowableException"); mv.visitInsn(DUP); mv.visitVarInsn(ALOAD, nextSlot); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/reflect/UndeclaredThrowableException", "<init>", "(Ljava/lang/Throwable;)V", false); mv.visitInsn(ATHROW); } mv.visitMaxs(10, nextSlot + 1); mv.visitEnd(); } /** * Generate code for wrapping an argument of the given type * whose value can be found at the specified local variable * index, in order for it to be passed (as an Object) to the * invocation handler's "invoke" method. The code is written * to the supplied stream. */ private static void codeWrapArgument(Class<?> type, int slot, MethodVisitor mv) { if (type.isPrimitive()) { PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(type); if (type == int.class || type == boolean.class || type == byte.class || type == char.class || type == short.class) { mv.visitVarInsn(ILOAD, slot); } else if (type == long.class) { mv.visitVarInsn(LLOAD, slot); } else if (type == float.class) { mv.visitVarInsn(FLOAD, slot); } else if (type == double.class) { mv.visitVarInsn(DLOAD, slot); } else { throw new AssertionError(); } mv.visitMethodInsn(INVOKESTATIC, prim.wrapperClassName, "valueOf", prim.wrapperValueOfDesc, false); } else { mv.visitVarInsn(ALOAD, slot); } } /** * Generate code for unwrapping a return value of the given * type from the invocation handler's "invoke" method (as type * Object) to its correct type. The code is written to the * supplied stream. */ private static void codeUnwrapReturnValue(Class<?> type, MethodVisitor mv) { if (type.isPrimitive()) { PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(type); mv.visitTypeInsn(CHECKCAST, prim.wrapperClassName); mv.visitMethodInsn(INVOKEVIRTUAL, prim.wrapperClassName, prim.unwrapMethodName, prim.unwrapMethodDesc, false); if (type == int.class || type == boolean.class || type == byte.class || type == char.class || type == short.class) { mv.visitInsn(IRETURN); } else if (type == long.class) { mv.visitInsn(LRETURN); } else if (type == float.class) { mv.visitInsn(FRETURN); } else if (type == double.class) { mv.visitInsn(DRETURN); } else { throw new AssertionError(); } } else { mv.visitTypeInsn(CHECKCAST, dotToSlash(type.getName())); mv.visitInsn(ARETURN); } } /** * Generate code for initializing the static field that stores * the Method object for this proxy method. The code is written * to the supplied stream. */ private void codeFieldInitialization(ProxyMethod pm, MethodVisitor mv) { mv.visitLdcInsn(pm.method.getDeclaringClass().getName()); mv.visitMethodInsn(INVOKESTATIC, "java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;", false); mv.visitLdcInsn(pm.method.getName()); Class<?>[] parameterTypes = pm.method.getParameterTypes(); visitPush(mv, parameterTypes.length); mv.visitTypeInsn(ANEWARRAY, "java/lang/Class"); for (int i = 0, n = parameterTypes.length; i < n; i++) { mv.visitInsn(DUP); visitPush(mv, i); if (parameterTypes[i].isPrimitive()) { PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(parameterTypes[i]); mv.visitFieldInsn(GETSTATIC, prim.wrapperClassName, "TYPE", "Ljava/lang/Class;"); } else { mv.visitLdcInsn(parameterTypes[i].getName()); mv.visitMethodInsn(INVOKESTATIC, "java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;", false); } mv.visitInsn(AASTORE); } mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", false); mv.visitFieldInsn(PUTSTATIC, dotToSlash(className), pm.methodFieldName, "Ljava/lang/reflect/Method;"); } /** * Generate the constructor method for the proxy class. */ private static void generateConstructor(ClassVisitor cv) { MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "<init>", "(Ljava/lang/reflect/InvocationHandler;)V", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn(INVOKESPECIAL, SUPERCLASS_NAME, "<init>", "(Ljava/lang/reflect/InvocationHandler;)V", false); mv.visitInsn(RETURN); mv.visitMaxs(10, 2); mv.visitEnd(); } /** * Generate the static initializer method for the proxy class. */ private void generateStaticInitializer(ClassVisitor cv) { MethodVisitor mv = cv.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null); mv.visitCode(); Label start = new Label(); mv.visitLabel(start); for (List<ProxyMethod> sigmethods : proxyMethods.values()) { for (ProxyMethod pm : sigmethods) { codeFieldInitialization(pm, mv); } } mv.visitInsn(RETURN); Label end = new Label(); mv.visitLabel(end); Label handler1 = new Label(); mv.visitLabel(handler1); mv.visitTryCatchBlock(start, end, handler1, "java/lang/NoSuchMethodException"); mv.visitVarInsn(ASTORE, 1); mv.visitTypeInsn(NEW, "java/lang/NoSuchMethodError"); mv.visitInsn(DUP); mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Throwable", "getMessage", "()Ljava/lang/String;", false); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/NoSuchMethodError", "<init>", "(Ljava/lang/String;)V", false); mv.visitInsn(ATHROW); Label handler2 = new Label(); mv.visitLabel(handler2); mv.visitTryCatchBlock(start, end, handler2, "java/lang/ClassNotFoundException"); mv.visitVarInsn(ASTORE, 1); mv.visitTypeInsn(NEW, "java/lang/NoClassDefFoundError"); mv.visitInsn(DUP); mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Throwable", "getMessage", "()Ljava/lang/String;", false); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/NoClassDefFoundError", "<init>", "(Ljava/lang/String;)V", false); mv.visitInsn(ATHROW); mv.visitMaxs(10, 2); mv.visitEnd(); } /* * =============== Code Generation Utility Methods =============== */ private static void visitPush(MethodVisitor mv, int operand) { if (operand <= 5) { mv.visitInsn(ICONST_0 + operand); } else if (operand <= Byte.MAX_VALUE) { mv.visitIntInsn(BIPUSH, operand); } else if (operand <= Short.MAX_VALUE) { mv.visitIntInsn(SIPUSH, operand); } else { mv.visitLdcInsn(operand); } } /* * ==================== General Utility Methods ==================== */ /** * Convert a fully qualified class name that uses '.' as the package * separator, the external representation used by the Java language * and APIs, to a fully qualified class name that uses '/' as the * package separator, the representation used in the class file * format (see JVMS section 4.2). */ private static String dotToSlash(String name) { return name.replace('.', '/'); } /** * Returns a human-readable string representing the signature of a * method with the given name and parameter types. */ private static String getFriendlyMethodSignature(String name, Class<?>[] parameterTypes) { StringBuilder sig = new StringBuilder(name); sig.append('('); for (int i = 0, n = parameterTypes.length; i < n; i++) { if (i > 0) { sig.append(','); } Class<?> parameterType = parameterTypes[i]; int dimensions = 0; while (parameterType.isArray()) { parameterType = parameterType.getComponentType(); dimensions++; } sig.append(parameterType.getName()); while (dimensions-- > 0) { sig.append("[]"); } } sig.append(')'); return sig.toString(); } /** * Add to the given list all of the types in the "from" array that * are not already contained in the list and are assignable to at * least one of the types in the "with" array. * <p> * This method is useful for computing the greatest common set of * declared exceptions from duplicate methods inherited from * different interfaces. */ private static void collectCompatibleTypes(Class<?>[] from, Class<?>[] with, Collection<Class<?>> list) { for (Class<?> fc : from) { if (!list.contains(fc)) { for (Class<?> wc : with) { if (wc.isAssignableFrom(fc)) { list.add(fc); break; } } } } } /** * Given the exceptions declared in the throws clause of a proxy method, * compute the exceptions that need to be caught from the invocation * handler's invoke method and rethrown intact in the method's * implementation before catching other Throwables and wrapping them * in UndeclaredThrowableExceptions. * <p> * The exceptions to be caught are returned in a List object. Each * exception in the returned list is guaranteed to not be a subclass of * any of the other exceptions in the list, so the catch blocks for * these exceptions may be generated in any order relative to each other. * <p> * Error and RuntimeException are each always contained by the returned * list (if none of their superclasses are contained), since those * unchecked exceptions should always be rethrown intact, and thus their * subclasses will never appear in the returned list. * <p> * The returned List will be empty if java.lang.Throwable is in the * given list of declared exceptions, indicating that no exceptions * need to be caught. */ private static List<Class<?>> computeUniqueCatchList(Class<?>[] exceptions) { List<Class<?>> uniqueList = new ArrayList<>(); // unique exceptions to catch uniqueList.add(Error.class); // always catch/rethrow these uniqueList.add(RuntimeException.class); nextException: for (Class<?> ex : exceptions) { if (ex.isAssignableFrom(Throwable.class)) { /* * If Throwable is declared to be thrown by the proxy method, * then no catch blocks are necessary, because the invoke * can, at most, throw Throwable anyway. */ uniqueList.clear(); break; } if (!Throwable.class.isAssignableFrom(ex)) { /* * Ignore types that cannot be thrown by the invoke method. */ continue; } /* * Compare this exception against the current list of * exceptions that need to be caught: */ for (int j = 0; j < uniqueList.size(); ) { Class<?> ex2 = uniqueList.get(j); if (ex2.isAssignableFrom(ex)) { /* * if a superclass of this exception is already on * the list to catch, then ignore this one and continue; */ continue nextException; } else if (ex.isAssignableFrom(ex2)) { /* * if a subclass of this exception is on the list * to catch, then remove it; */ uniqueList.remove(j); } else { j++; // else continue comparing. } } // This exception is unique (so far): add it to the list to catch. uniqueList.add(ex); } return uniqueList; } /** * A ProxyMethod object represents a proxy method in the proxy class * being generated: a method whose implementation will encode and * dispatch invocations to the proxy instance's invocation handler. */ private static final class ProxyMethod { final Method method; final String methodFieldName; Class<?>[] exceptionTypes; ProxyMethod(Method method, String methodFieldName) { this.method = method; this.methodFieldName = methodFieldName; exceptionTypes = method.getExceptionTypes(); } } /** * A PrimitiveTypeInfo object contains assorted information about * a primitive type in its public fields. The struct for a particular * primitive type can be obtained using the static "get" method. */ private static final class PrimitiveTypeInfo { /** * "base type" used in various descriptors (see JVMS section 4.3.2) */ String baseTypeString; /** * name of corresponding wrapper class */ String wrapperClassName; /** * method descriptor for wrapper class "valueOf" factory method */ String wrapperValueOfDesc; /** * name of wrapper class method for retrieving primitive value */ String unwrapMethodName; /** * descriptor of same method */ String unwrapMethodDesc; private static final Map<Class<?>, PrimitiveTypeInfo> table = new HashMap<>(); static { add(byte.class, Byte.class); add(char.class, Character.class); add(double.class, Double.class); add(float.class, Float.class); add(int.class, Integer.class); add(long.class, Long.class); add(short.class, Short.class); add(boolean.class, Boolean.class); } private static void add(Class<?> primitiveClass, Class<?> wrapperClass) { table.put(primitiveClass, new PrimitiveTypeInfo(primitiveClass, wrapperClass)); } private PrimitiveTypeInfo(Class<?> primitiveClass, Class<?> wrapperClass) { assert primitiveClass.isPrimitive(); baseTypeString = Array.newInstance(primitiveClass, 0).getClass().getName().substring(1); wrapperClassName = dotToSlash(wrapperClass.getName()); wrapperValueOfDesc = "(" + baseTypeString + ")L" + wrapperClassName + ";"; unwrapMethodName = primitiveClass.getName() + "Value"; unwrapMethodDesc = "()" + baseTypeString; } static PrimitiveTypeInfo get(Class<?> cl) { return table.get(cl); } } }