/* * $Id$ * * Copyright (C) 2003-2015 JNode.org * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; If not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.jnode.test.core; import gnu.java.lang.reflect.TypeSignature; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; public class ProxyBuilder { /** * Returns the proxy {@link Class} for the given ClassLoader and array of * interfaces, dynamically generating it if necessary. * <p/> * <p> * There are several restrictions on this method, the violation of which * will result in an IllegalArgumentException or NullPointerException: * </p> * <p/> * <ul> * <li>All objects in `interfaces' must represent distinct interfaces. * Classes, primitive types, null, and duplicates are forbidden.</li> * <li>The interfaces must be visible in the specified ClassLoader. In * other words, for each interface i: * <code>Class.forName(i.getName(), false, loader) == i</code> must be * true.</li> * <li>All non-public interfaces (if any) must reside in the same package, * or the proxy class would be non-instantiable. If there are no non-public * interfaces, the package of the proxy class is unspecified.</li> * <li>All interfaces must be compatible - if two declare a method with the * same name and parameters, the return type must be the same and the throws * clause of the proxy class will be the maximal subset of subclasses of the * throws clauses for each method that is overridden.</li> * <li>VM constraints limit the number of interfaces a proxy class may * directly implement (however, the indirect inheritance of * {@link java.io.Serializable} does not count against this limit). Even though most * VMs can theoretically have 65535 superinterfaces for a class, the actual * limit is smaller because a class's constant pool is limited to 65535 * entries, and not all entries can be interfaces.</li> * </ul> * <p/> * <p> * Note that different orders of interfaces produce distinct classes. * </p> * * @param loader the class loader to define the proxy class in; null implies * the bootstrap class loader * @param interfaces the array of interfaces the proxy class implements, may be * empty, but not null * @return the Class object of the proxy class * @throws IllegalArgumentException if the constraints above were violated, except for problems * with null * @throws NullPointerException if `interfaces' is null or contains a null entry */ // synchronized so that we aren't trying to build the same class // simultaneously in two threads public static byte[] getProxyClass(ClassLoader loader, Class[] interfaces) { ProxyType pt = new ProxyType(loader, interfaces); ProxyData data = ProxyData.getProxyData(pt); return new ClassFactory(data).generate(loader); } /** * Helper class for mapping unique ClassLoader and interface combinations to * proxy classes. * * @author Eric Blake (ebb9@email.byu.edu) */ private static final class ProxyType { /** * Store the class loader (may be null) */ final ClassLoader loader; /** * Store the interfaces (never null, all elements are interfaces) */ final Class[] interfaces; /** * Construct the helper object. * * @param loader the class loader to define the proxy class in; null * implies the bootstrap class loader * @param interfaces an array of interfaces */ ProxyType(ClassLoader loader, Class[] interfaces) { this.loader = loader; this.interfaces = interfaces; } /** * Calculates the hash code. * * @return a combination of the classloader and interfaces hashcodes. */ public int hashCode() { int hash = loader == null ? 0 : loader.hashCode(); for (int i = 0; i < interfaces.length; i++) hash = hash * 31 + interfaces[i].hashCode(); return hash; } /** * Calculates equality. * * @param other object to compare to * @return true if it is a ProxyType with same data */ public boolean equals(Object other) { ProxyType pt = (ProxyType) other; if (loader != pt.loader || interfaces.length != pt.interfaces.length) return false; for (int i = 0; i < interfaces.length; i++) if (interfaces[i] != pt.interfaces[i]) return false; return true; } } // class ProxyType /** * Helper class which allows hashing of a method name and signature without * worrying about return type, declaring class, or throws clause, and which * reduces the maximally common throws clause between two methods * * @author Eric Blake (ebb9@email.byu.edu) */ private static final class ProxySignature { /** * The core signatures which all Proxy instances handle. */ static final HashMap<ProxySignature, ProxySignature> coreMethods = new HashMap<ProxySignature, ProxySignature>(); static { try { ProxySignature sig = new ProxySignature(Object.class.getMethod( "equals", new Class[]{Object.class})); coreMethods.put(sig, sig); sig = new ProxySignature(Object.class.getMethod("hashCode", null)); coreMethods.put(sig, sig); sig = new ProxySignature(Object.class.getMethod("toString", null)); coreMethods.put(sig, sig); } catch (Exception e) { // assert false; throw (Error) new InternalError("Unexpected: " + e) .initCause(e); } } /** * The underlying Method object, never null */ final Method method; /** * The set of compatible thrown exceptions, may be empty */ final Set<Class> exceptions = new HashSet<Class>(); /** * Construct a signature * * @param method the Method this signature is based on, never null */ ProxySignature(Method method) { this.method = method; Class[] exc = method.getExceptionTypes(); int i = exc.length; while (--i >= 0) { // discard unchecked exceptions if (Error.class.isAssignableFrom(exc[i]) || RuntimeException.class.isAssignableFrom(exc[i])) continue; exceptions.add(exc[i]); } } /** * Given a method, make sure it's return type is identical to this, and * adjust this signature's throws clause appropriately * * @param other the signature to merge in * @throws IllegalArgumentException if the return types conflict */ void checkCompatibility(ProxySignature other) { if (method.getReturnType() != other.method.getReturnType()) throw new IllegalArgumentException( "incompatible return types: " + method + ", " + other.method); // if you can think of a more efficient way than this O(n^2) search, // implement it! int size1 = exceptions.size(); int size2 = other.exceptions.size(); boolean[] valid1 = new boolean[size1]; boolean[] valid2 = new boolean[size2]; Iterator itr = exceptions.iterator(); int pos = size1; while (--pos >= 0) { Class<?> c1 = (Class) itr.next(); Iterator itr2 = other.exceptions.iterator(); int pos2 = size2; while (--pos2 >= 0) { Class<?> c2 = (Class) itr2.next(); if (c2.isAssignableFrom(c1)) valid1[pos] = true; if (c1.isAssignableFrom(c2)) valid2[pos2] = true; } } pos = size1; itr = exceptions.iterator(); while (--pos >= 0) { itr.next(); if (!valid1[pos]) itr.remove(); } pos = size2; itr = other.exceptions.iterator(); while (--pos >= 0) { itr.next(); if (!valid2[pos]) itr.remove(); } exceptions.addAll(other.exceptions); } /** * Calculates the hash code. * * @return a combination of name and parameter types */ public int hashCode() { int hash = method.getName().hashCode(); Class[] types = method.getParameterTypes(); for (int i = 0; i < types.length; i++) hash = hash * 31 + types[i].hashCode(); return hash; } /** * Calculates equality. * * @param other object to compare to * @return true if it is a ProxySignature with same data */ public boolean equals(Object other) { ProxySignature ps = (ProxySignature) other; Class[] types1 = method.getParameterTypes(); Class[] types2 = ps.method.getParameterTypes(); if (!method.getName().equals(ps.method.getName()) || types1.length != types2.length) return false; int i = types1.length; while (--i >= 0) if (types1[i] != types2[i]) return false; return true; } } // class ProxySignature /** * A flat representation of all data needed to generate bytecode/instantiate * a proxy class. This is basically a struct. * * @author Eric Blake (ebb9@email.byu.edu) */ static final class ProxyData { /** * The package this class is in <b>including the trailing dot</b> or an * empty string for the unnamed (aka default) package. */ String pack = ""; /** * The interfaces this class implements. Non-null, but possibly empty. */ Class[] interfaces; /** * The Method objects this class must pass as the second argument to * invoke (also useful for determining what methods this class has). * Non-null, non-empty (includes at least Object.hashCode, * Object.equals, and Object.toString). */ Method[] methods; /** * The exceptions that do not need to be wrapped in * UndeclaredThrowableException. exceptions[i] is the same as, or a * subset of subclasses, of methods[i].getExceptionTypes(), depending on * compatible throws clauses with multiple inheritance. It is * unspecified if these lists include or exclude subclasses of Error and * RuntimeException, but excluding them is harmless and generates a * smaller class. */ Class[][] exceptions; /** * For unique id's */ private static int count; /** * The id of this proxy class */ final int id = count++; /** * Construct a ProxyData with uninitialized data members. */ ProxyData() { } /** * Return the name of a package (including the trailing dot) given the * name of a class. Returns an empty string if no package. We use this * in preference to using Class.getPackage() to avoid problems with * ClassLoaders that don't set the package. */ private static String getPackage(Class k) { String name = k.getName(); int idx = name.lastIndexOf('.'); return name.substring(0, idx + 1); } /** * Verifies that the arguments are legal, and sets up remaining data * This should only be called when a class must be generated, as it is * expensive. * * @param pt the ProxyType to convert to ProxyData * @return the flattened, verified ProxyData structure for use in class * generation * @throws IllegalArgumentException if `interfaces' contains non-interfaces or incompatible * combinations, and verify is true * @throws NullPointerException if interfaces is null or contains null */ static ProxyData getProxyData(ProxyType pt) { Map<ProxySignature, ProxySignature> method_set = (Map<ProxySignature, ProxySignature>) ProxySignature.coreMethods .clone(); boolean in_package = false; // true if we encounter non-public // interface ProxyData data = new ProxyData(); data.interfaces = pt.interfaces; // if interfaces is too large, we croak later on when the constant // pool overflows int i = data.interfaces.length; while (--i >= 0) { Class inter = data.interfaces[i]; if (!inter.isInterface()) throw new IllegalArgumentException("not an interface: " + inter); try { if (Class.forName(inter.getName(), false, pt.loader) != inter) throw new IllegalArgumentException("not accessible in " + "classloader: " + inter); } catch (ClassNotFoundException e) { throw new IllegalArgumentException("not accessible in " + "classloader: " + inter); } if (!Modifier.isPublic(inter.getModifiers())) if (in_package) { String p = getPackage(inter); if (!data.pack.equals(p)) throw new IllegalArgumentException( "non-public interfaces " + "from different " + "packages"); } else { in_package = true; data.pack = getPackage(inter); } for (int j = i - 1; j >= 0; j--) if (data.interfaces[j] == inter) throw new IllegalArgumentException( "duplicate interface: " + inter); Method[] methods = inter.getMethods(); int j = methods.length; while (--j >= 0) { if (isCoreObjectMethod(methods[j])) { // In the case of an attempt to redefine a public // non-final // method of Object, we must skip it continue; } ProxySignature sig = new ProxySignature(methods[j]); ProxySignature old = (ProxySignature) method_set.put(sig, sig); if (old != null) sig.checkCompatibility(old); } } i = method_set.size(); data.methods = new Method[i]; data.exceptions = new Class[i][]; Iterator itr = method_set.values().iterator(); while (--i >= 0) { ProxySignature sig = (ProxySignature) itr.next(); data.methods[i] = sig.method; data.exceptions[i] = (Class[]) sig.exceptions .toArray(new Class[sig.exceptions.size()]); } return data; } /** * Checks whether the method is similar to a public non-final method of * Object or not (i.e. with the same name and parameter types). Note * that we can't rely, directly or indirectly (via Collection.contains) * on Method.equals as it would also check the declaring class, what we * do not want. We only want to check that the given method have the * same signature as a core method (same name and parameter types) * * @param method the method to check * @return whether the method has the same name and parameter types as * Object.equals, Object.hashCode or Object.toString * @see java.lang.Object#equals(Object) * @see java.lang.Object#hashCode() * @see java.lang.Object#toString() */ private static boolean isCoreObjectMethod(Method method) { String methodName = method.getName(); if (methodName.equals("equals")) { return Arrays.equals(method.getParameterTypes(), new Class[]{Object.class}); } if (methodName.equals("hashCode")) { return method.getParameterTypes().length == 0; } if (methodName.equals("toString")) { return method.getParameterTypes().length == 0; } return false; } } // class ProxyData /** * Does all the work of building a class. By making this a nested class, * this code is not loaded in memory if the VM has a native implementation * instead. * * @author Eric Blake (ebb9@email.byu.edu) */ private static final class ClassFactory { /** * Constants for assisting the compilation */ private static final byte FIELD = 1; private static final byte METHOD = 2; private static final byte INTERFACE = 3; private static final String CTOR_SIG = "(Ljava/lang/reflect/InvocationHandler;)V"; private static final String INVOKE_SIG = "(Ljava/lang/Object;" + "Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;"; /** * Bytecodes for insertion in the class definition byte[] */ private static final char ACONST_NULL = 1; private static final char ICONST_0 = 3; private static final char BIPUSH = 16; private static final char SIPUSH = 17; private static final char ILOAD = 21; private static final char ILOAD_0 = 26; private static final char ALOAD_0 = 42; private static final char ALOAD_1 = 43; private static final char AALOAD = 50; private static final char AASTORE = 83; private static final char DUP = 89; private static final char DUP_X1 = 90; private static final char SWAP = 95; private static final char IRETURN = 172; private static final char LRETURN = 173; private static final char FRETURN = 174; private static final char DRETURN = 175; private static final char ARETURN = 176; private static final char RETURN = 177; private static final char GETSTATIC = 178; private static final char GETFIELD = 180; private static final char INVOKEVIRTUAL = 182; private static final char INVOKESPECIAL = 183; private static final char INVOKEINTERFACE = 185; private static final char NEW = 187; private static final char ANEWARRAY = 189; private static final char ATHROW = 191; private static final char CHECKCAST = 192; private static final char POP = 87; // Implementation note: we use StringBuffers to hold the byte data, // since // they automatically grow. However, we only use the low 8 bits of // every char in the array, so we are using twice the necessary memory // for the ease StringBuffer provides. /** * The constant pool. */ private final StringBuffer pool = new StringBuffer(); /** * The rest of the class data. */ private final StringBuffer stream = new StringBuffer(); /** * Map of strings to byte sequences, to minimize size of pool. */ private final Map poolEntries = new HashMap(); /** * The VM name of this proxy class. */ private final String qualName; /** * The Method objects the proxy class refers to when calling the * invocation handler. */ private final Method[] methods; /** * Initializes the buffers with the bytecode contents for a proxy class. * * @param data the remainder of the class data * @throws IllegalArgumentException if anything else goes wrong this late in the game; as far * as I can tell, this will only happen if the constant pool * overflows, which is possible even when the user doesn't * exceed the 65535 interface limit */ ClassFactory(ProxyData data) { methods = data.methods; // magic = 0xcafebabe // minor_version = 0 // major_version = 46 // constant_pool_count: place-holder for now pool.append("\u00ca\u00fe\u00ba\u00be\0\0\0\56\0\0"); // constant_pool[], filled in as we go // access_flags putU2(0x0020/* Modifier.SUPER */ | Modifier.FINAL | Modifier.PUBLIC); // this_class qualName = (data.pack + "$Proxy" + data.id); putU2(classInfo(TypeSignature.getEncodingOfClass(qualName, false))); // super_class putU2(classInfo("java/lang/reflect/Proxy")); // interfaces_count putU2(data.interfaces.length); // interfaces[] for (int i = 0; i < data.interfaces.length; i++) putU2(classInfo(data.interfaces[i])); // Recall that Proxy classes serialize specially, so we do not need // to worry about a <clinit> method for this field. Instead, we // just assign it by reflection after the class is successfully // loaded. // fields_count - private static Method[] m; putU2(1); // fields[] // m.access_flags putU2(Modifier.PRIVATE | Modifier.STATIC); // m.name_index putU2(utf8Info("m")); // m.descriptor_index putU2(utf8Info("[Ljava/lang/reflect/Method;")); // m.attributes_count putU2(0); // m.attributes[] // methods_count - # handler methods, plus <init> putU2(methods.length + 1); // methods[] // <init>.access_flags putU2(Modifier.PUBLIC); // <init>.name_index putU2(utf8Info("<init>")); // <init>.descriptor_index putU2(utf8Info(CTOR_SIG)); // <init>.attributes_count - only Code is needed putU2(1); // <init>.Code.attribute_name_index putU2(utf8Info("Code")); // <init>.Code.attribute_length = 18 // <init>.Code.info: // $Proxynn(InvocationHandler h) { super(h); } // <init>.Code.max_stack = 2 // <init>.Code.max_locals = 2 // <init>.Code.code_length = 6 // <init>.Code.code[] stream.append("\0\0\0\22\0\2\0\2\0\0\0\6" + ALOAD_0 + ALOAD_1 + INVOKESPECIAL); putU2(refInfo(METHOD, "java/lang/reflect/Proxy", "<init>", CTOR_SIG)); // <init>.Code.exception_table_length = 0 // <init>.Code.exception_table[] // <init>.Code.attributes_count = 0 // <init>.Code.attributes[] stream.append(RETURN + "\0\0\0\0"); for (int i = methods.length - 1; i >= 0; i--) emitMethod(i, data.exceptions[i]); // attributes_count putU2(0); // attributes[] - empty; omit SourceFile attribute // XXX should we mark this with a Synthetic attribute? } /** * Produce the bytecode for a single method. * * @param i the index of the method we are building * @param e the exceptions possible for the method */ private void emitMethod(int i, Class[] e) { // First, we precalculate the method length and other information. Method m = methods[i]; Class[] paramtypes = m.getParameterTypes(); int wrap_overhead = 0; // max words taken by wrapped primitive int param_count = 1; // 1 for this int code_length = 16; // aload_0, getfield, aload_0, getstatic, // const, // aaload, const/aconst_null, invokeinterface if (i > 5) { if (i > Byte.MAX_VALUE) code_length += 2; // sipush else code_length++; // bipush } if (paramtypes.length > 0) { code_length += 3; // anewarray if (paramtypes.length > Byte.MAX_VALUE) code_length += 2; // sipush else if (paramtypes.length > 5) code_length++; // bipush for (int j = 0; j < paramtypes.length; j++) { code_length += 4; // dup, const, load, store Class type = paramtypes[j]; if (j > 5) { if (j > Byte.MAX_VALUE) code_length += 2; // sipush else code_length++; // bipush } if (param_count >= 4) code_length++; // 2-byte load param_count++; if (type.isPrimitive()) { code_length += 7; // new, dup, invokespecial if (type == long.class || type == double.class) { wrap_overhead = 3; param_count++; } else if (wrap_overhead < 2) wrap_overhead = 2; } } } int end_pc = code_length; Class ret_type = m.getReturnType(); if (ret_type == void.class) { System.out.println("ret_type==void"); code_length++; // pop code_length++; // return } else if (ret_type.isPrimitive()) code_length += 7; // cast, invokevirtual, return else code_length += 4; // cast, return int exception_count = 0; boolean throws_throwable = false; for (int j = 0; j < e.length; j++) if (e[j] == Throwable.class) { throws_throwable = true; break; } if (!throws_throwable) { exception_count = e.length + 3; // Throwable, Error, // RuntimeException code_length += 9; // new, dup_x1, swap, invokespecial, athrow } int handler_pc = code_length - 1; StringBuffer signature = new StringBuffer("("); for (int j = 0; j < paramtypes.length; j++) signature.append(TypeSignature .getEncodingOfClass(paramtypes[j])); signature.append(")").append( TypeSignature.getEncodingOfClass(ret_type)); // Now we have enough information to emit the method. // handler.access_flags putU2(Modifier.PUBLIC | Modifier.FINAL); // handler.name_index putU2(utf8Info(m.getName())); // handler.descriptor_index putU2(utf8Info(signature.toString())); // handler.attributes_count - Code is necessary, Exceptions possible putU2(e.length > 0 ? 2 : 1); // handler.Code.info: // type name(args) { // try { // return (type) h.invoke(this, methods[i], new Object[] {args}); // } catch (<declared Exceptions> e) { // throw e; // } catch (Throwable t) { // throw new UndeclaredThrowableException(t); // } // } // Special cases: // if arg_n is primitive, wrap it // if method throws Throwable, try-catch is not needed // if method returns void, return statement not needed // if method returns primitive, unwrap it // save space by sharing code for all the declared handlers // handler.Code.attribute_name_index putU2(utf8Info("Code")); // handler.Code.attribute_length putU4(12 + code_length + 8 * exception_count); // handler.Code.max_stack putU2(param_count == 1 ? 4 : 7 + wrap_overhead); // handler.Code.max_locals putU2(param_count); // handler.Code.code_length putU4(code_length); // handler.Code.code[] putU1(ALOAD_0); putU1(GETFIELD); putU2(refInfo(FIELD, "java/lang/reflect/Proxy", "h", "Ljava/lang/reflect/InvocationHandler;")); putU1(ALOAD_0); putU1(GETSTATIC); putU2(refInfo(FIELD, TypeSignature.getEncodingOfClass(qualName, false), "m", "[Ljava/lang/reflect/Method;")); putConst(i); putU1(AALOAD); if (paramtypes.length > 0) { putConst(paramtypes.length); putU1(ANEWARRAY); putU2(classInfo("java/lang/Object")); param_count = 1; for (int j = 0; j < paramtypes.length; j++, param_count++) { putU1(DUP); putConst(j); if (paramtypes[j].isPrimitive()) { putU1(NEW); putU2(classInfo(wrapper(paramtypes[j]))); putU1(DUP); } putLoad(param_count, paramtypes[j]); if (paramtypes[j].isPrimitive()) { putU1(INVOKESPECIAL); putU2(refInfo( METHOD, wrapper(paramtypes[j]), "<init>", '(' + (TypeSignature .getEncodingOfClass(paramtypes[j]) + ")V"))); if (paramtypes[j] == long.class || paramtypes[j] == double.class) param_count++; } putU1(AASTORE); } } else putU1(ACONST_NULL); putU1(INVOKEINTERFACE); putU2(refInfo(INTERFACE, "java/lang/reflect/InvocationHandler", "invoke", INVOKE_SIG)); putU1(4); // InvocationHandler, this, Method, Object[] putU1(0); System.out.println(" return type: " + ret_type.getName()); if (ret_type == void.class) { System.out.println(" return type == void"); putU1(POP); putU1(RETURN); } else if (ret_type.isPrimitive()) { putU1(CHECKCAST); putU2(classInfo(wrapper(ret_type))); putU1(INVOKEVIRTUAL); putU2(refInfo(METHOD, wrapper(ret_type), ret_type.getName() + "Value", "()" + TypeSignature.getEncodingOfClass(ret_type))); if (ret_type == long.class) putU1(LRETURN); else if (ret_type == float.class) putU1(FRETURN); else if (ret_type == double.class) putU1(DRETURN); else putU1(IRETURN); } else { putU1(CHECKCAST); putU2(classInfo(ret_type)); putU1(ARETURN); } if (!throws_throwable) { putU1(NEW); putU2(classInfo("java/lang/reflect/UndeclaredThrowableException")); putU1(DUP_X1); putU1(SWAP); putU1(INVOKESPECIAL); putU2(refInfo(METHOD, "java/lang/reflect/UndeclaredThrowableException", "<init>", "(Ljava/lang/Throwable;)V")); putU1(ATHROW); } // handler.Code.exception_table_length putU2(exception_count); // handler.Code.exception_table[] if (!throws_throwable) { // handler.Code.exception_table.start_pc putU2(0); // handler.Code.exception_table.end_pc putU2(end_pc); // handler.Code.exception_table.handler_pc putU2(handler_pc); // handler.Code.exception_table.catch_type putU2(classInfo("java/lang/Error")); // handler.Code.exception_table.start_pc putU2(0); // handler.Code.exception_table.end_pc putU2(end_pc); // handler.Code.exception_table.handler_pc putU2(handler_pc); // handler.Code.exception_table.catch_type putU2(classInfo("java/lang/RuntimeException")); for (int j = 0; j < e.length; j++) { // handler.Code.exception_table.start_pc putU2(0); // handler.Code.exception_table.end_pc putU2(end_pc); // handler.Code.exception_table.handler_pc putU2(handler_pc); // handler.Code.exception_table.catch_type putU2(classInfo(e[j])); } // handler.Code.exception_table.start_pc putU2(0); // handler.Code.exception_table.end_pc putU2(end_pc); // handler.Code.exception_table.handler_pc - // -8 for undeclared handler, which falls thru to normal one putU2(handler_pc - 8); // handler.Code.exception_table.catch_type putU2(0); } // handler.Code.attributes_count putU2(0); // handler.Code.attributes[] if (e.length > 0) { // handler.Exceptions.attribute_name_index putU2(utf8Info("Exceptions")); // handler.Exceptions.attribute_length putU4(2 * e.length + 2); // handler.Exceptions.number_of_exceptions putU2(e.length); // handler.Exceptions.exception_index_table[] for (int j = 0; j < e.length; j++) putU2(classInfo(e[j])); } } /** * Creates the Class object that corresponds to the bytecode buffers * built when this object was constructed. * * @param loader the class loader to define the proxy class in; null * implies the bootstrap class loader * @return the proxy class Class object */ byte[] generate(ClassLoader loader) { byte[] bytecode = new byte[pool.length() + stream.length()]; // More efficient to bypass calling charAt() repetitively. char[] c = pool.toString().toCharArray(); int i = c.length; while (--i >= 0) bytecode[i] = (byte) c[i]; c = stream.toString().toCharArray(); i = c.length; int j = bytecode.length; while (i > 0) bytecode[--j] = (byte) c[--i]; // Patch the constant pool size, which we left at 0 earlier. int count = poolEntries.size() + 1; bytecode[8] = (byte) (count >> 8); bytecode[9] = (byte) count; return bytecode; } /** * Put a single byte on the stream. * * @param i the information to add (only lowest 8 bits are used) */ private void putU1(int i) { stream.append((char) i); } /** * Put two bytes on the stream. * * @param i the information to add (only lowest 16 bits are used) */ private void putU2(int i) { stream.append((char) (i >> 8)).append((char) i); } /** * Put four bytes on the stream. * * @param i the information to add (treated as unsigned) */ private void putU4(int i) { stream.append((char) (i >> 24)).append((char) (i >> 16)); stream.append((char) (i >> 8)).append((char) i); } /** * Put bytecode to load a constant integer on the stream. This only * needs to work for values less than Short.MAX_VALUE. * * @param i the int to add */ private void putConst(int i) { if (i >= -1 && i <= 5) putU1(ICONST_0 + i); else if (i >= Byte.MIN_VALUE && i <= Byte.MAX_VALUE) { putU1(BIPUSH); putU1(i); } else { putU1(SIPUSH); putU2(i); } } /** * Put bytecode to load a given local variable on the stream. * * @param i the slot to load * @param type the base type of the load */ private void putLoad(int i, Class type) { int offset = 0; if (type == long.class) offset = 1; else if (type == float.class) offset = 2; else if (type == double.class) offset = 3; else if (!type.isPrimitive()) offset = 4; if (i < 4) putU1(ILOAD_0 + 4 * offset + i); else { putU1(ILOAD + offset); putU1(i); } } /** * Given a primitive type, return its wrapper class name. * * @param clazz the primitive type (but not void.class) * @return the internal form of the wrapper class name */ private String wrapper(Class clazz) { if (clazz == boolean.class) return "java/lang/Boolean"; if (clazz == byte.class) return "java/lang/Byte"; if (clazz == short.class) return "java/lang/Short"; if (clazz == char.class) return "java/lang/Character"; if (clazz == int.class) return "java/lang/Integer"; if (clazz == long.class) return "java/lang/Long"; if (clazz == float.class) return "java/lang/Float"; if (clazz == double.class) return "java/lang/Double"; // assert false; return null; } /** * Returns the entry of this String in the Constant pool, adding it if * necessary. * * @param str the String to resolve * @return the index of the String in the constant pool */ private char utf8Info(String str) { String utf8 = toUtf8(str); int len = utf8.length(); return poolIndex("\1" + (char) (len >> 8) + (char) (len & 0xff) + utf8); } /** * Returns the entry of the appropriate class info structure in the * Constant pool, adding it if necessary. * * @param name the class name, in internal form * @return the index of the ClassInfo in the constant pool */ private char classInfo(String name) { char index = utf8Info(name); char[] c = {7, (char) (index >> 8), (char) (index & 0xff)}; return poolIndex(new String(c)); } /** * Returns the entry of the appropriate class info structure in the * Constant pool, adding it if necessary. * * @param clazz the class type * @return the index of the ClassInfo in the constant pool */ private char classInfo(Class clazz) { return classInfo(TypeSignature.getEncodingOfClass(clazz.getName(), false)); } /** * Returns the entry of the appropriate fieldref, methodref, or * interfacemethodref info structure in the Constant pool, adding it if * necessary. * * @param structure FIELD, METHOD, or INTERFACE * @param clazz the class name, in internal form * @param name the simple reference name * @param type the type of the reference * @return the index of the appropriate Info structure in the constant * pool */ private char refInfo(byte structure, String clazz, String name, String type) { char cindex = classInfo(clazz); char ntindex = nameAndTypeInfo(name, type); // relies on FIELD == 1, METHOD == 2, INTERFACE == 3 char[] c = {(char) (structure + 8), (char) (cindex >> 8), (char) (cindex & 0xff), (char) (ntindex >> 8), (char) (ntindex & 0xff)}; return poolIndex(new String(c)); } /** * Returns the entry of the appropriate nameAndTyperef info structure in * the Constant pool, adding it if necessary. * * @param name the simple name * @param type the reference type * @return the index of the NameAndTypeInfo structure in the constant * pool */ private char nameAndTypeInfo(String name, String type) { char nindex = utf8Info(name); char tindex = utf8Info(type); char[] c = {12, (char) (nindex >> 8), (char) (nindex & 0xff), (char) (tindex >> 8), (char) (tindex & 0xff)}; return poolIndex(new String(c)); } /** * Converts a regular string to a UTF8 string, where the upper byte of * every char is 0, and '\\u0000' is not in the string. This is * basically to use a String as a fancy byte[], and while it is less * efficient in memory use, it is easier for hashing. * * @param str the original, in straight unicode * @return a modified string, in UTF8 format in the low bytes */ private String toUtf8(String str) { final char[] ca = str.toCharArray(); final int len = ca.length; // Avoid object creation, if str is already fits UTF8. int i; for (i = 0; i < len; i++) if (ca[i] == 0 || ca[i] > '\u007f') break; if (i == len) return str; final StringBuffer sb = new StringBuffer(str); sb.setLength(i); for (; i < len; i++) { final char c = ca[i]; if (c > 0 && c <= '\u007f') sb.append(c); else if (c <= '\u07ff') { // includes '\0' sb.append((char) (0xc0 | (c >> 6))); sb.append((char) (0x80 | (c & 0x6f))); } else { sb.append((char) (0xe0 | (c >> 12))); sb.append((char) (0x80 | ((c >> 6) & 0x6f))); sb.append((char) (0x80 | (c & 0x6f))); } } return sb.toString(); } /** * Returns the location of a byte sequence (conveniently wrapped in a * String with all characters between \u0001 and \u00ff inclusive) in * the constant pool, adding it if necessary. * * @param sequence the byte sequence to look for * @return the index of the sequence * @throws IllegalArgumentException if this would make the constant pool overflow */ private char poolIndex(String sequence) { Integer i = (Integer) poolEntries.get(sequence); if (i == null) { // pool starts at index 1 int size = poolEntries.size() + 1; if (size >= 65535) throw new IllegalArgumentException("exceeds VM limitations"); i = new Integer(size); poolEntries.put(sequence, i); pool.append(sequence); } return (char) i.intValue(); } } // class ClassFactory }