| /* Proxy.java -- build a proxy class that implements reflected interfaces |
| Copyright (C) 2001, 2002, 2003, 2004, 2005 Free Software Foundation, Inc. |
| |
| This file is part of GNU Classpath. |
| |
| GNU Classpath is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2, or (at your option) |
| any later version. |
| |
| GNU Classpath 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 |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GNU Classpath; see the file COPYING. If not, write to the |
| Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA |
| 02111-1307 USA. |
| |
| Linking this library statically or dynamically with other modules is |
| making a combined work based on this library. Thus, the terms and |
| conditions of the GNU General Public License cover the whole |
| combination. |
| |
| As a special exception, the copyright holders of this library give you |
| permission to link this library with independent modules to produce an |
| executable, regardless of the license terms of these independent |
| modules, and to copy and distribute the resulting executable under |
| terms of your choice, provided that you also meet, for each linked |
| independent module, the terms and conditions of the license of that |
| module. An independent module is a module which is not derived from |
| or based on this library. If you modify this library, you may extend |
| this exception to your version of the library, but you are not |
| obligated to do so. If you do not wish to do so, delete this |
| exception statement from your version. */ |
| |
| |
| package java.lang.reflect; |
| |
| import gnu.classpath.Configuration; |
| import gnu.java.lang.reflect.TypeSignature; |
| |
| import java.io.Serializable; |
| import java.security.ProtectionDomain; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * This class allows you to dynamically create an instance of any (or |
| * even multiple) interfaces by reflection, and decide at runtime |
| * how that instance will behave by giving it an appropriate |
| * {@link InvocationHandler}. Proxy classes serialize specially, so |
| * that the proxy object can be reused between VMs, without requiring |
| * a persistent copy of the generated class code. |
| * |
| * <h3>Creation</h3> |
| * To create a proxy for some interface Foo: |
| * |
| * <pre> |
| * InvocationHandler handler = new MyInvocationHandler(...); |
| * Class proxyClass = Proxy.getProxyClass( |
| * Foo.class.getClassLoader(), new Class[] { Foo.class }); |
| * Foo f = (Foo) proxyClass |
| * .getConstructor(new Class[] { InvocationHandler.class }) |
| * .newInstance(new Object[] { handler }); |
| * </pre> |
| * or more simply: |
| * <pre> |
| * Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(), |
| * new Class[] { Foo.class }, |
| * handler); |
| * </pre> |
| * |
| * <h3>Dynamic Proxy Classes</h3> |
| * A dynamic proxy class is created at runtime, and has the following |
| * properties: |
| * <ul> |
| * <li>The class is <code>public</code> and <code>final</code>, |
| * and is neither <code>abstract</code> nor an inner class.</li> |
| * <li>The class has no canonical name (there is no formula you can use |
| * to determine or generate its name), but begins with the |
| * sequence "$Proxy". Abuse this knowledge at your own peril. |
| * (For now, '$' in user identifiers is legal, but it may not |
| * be that way forever. You weren't using '$' in your |
| * identifiers, were you?)</li> |
| * <li>The class extends Proxy, and explicitly implements all the |
| * interfaces specified at creation, in order (this is important |
| * for determining how method invocation is resolved). Note that |
| * a proxy class implements {@link Serializable}, at least |
| * implicitly, since Proxy does, but true serial behavior |
| * depends on using a serializable invocation handler as well.</li> |
| * <li>If at least one interface is non-public, the proxy class |
| * will be in the same package. Otherwise, the package is |
| * unspecified. This will work even if the package is sealed |
| * from user-generated classes, because Proxy classes are |
| * generated by a trusted source. Meanwhile, the proxy class |
| * belongs to the classloader you designated.</li> |
| * <li>Reflection works as expected: {@link Class#getInterfaces()} and |
| * {@link Class#getMethods()} work as they do on normal classes.</li> |
| * <li>The method {@link #isProxyClass()} will distinguish between |
| * true proxy classes and user extensions of this class. It only |
| * returns true for classes created by {@link #getProxyClass}.</li> |
| * <li>The {@link ProtectionDomain} of a proxy class is the same as for |
| * bootstrap classes, such as Object or Proxy, since it is created by |
| * a trusted source. This protection domain will typically be granted |
| * {@link java.security.AllPermission}. But this is not a security |
| * risk, since there are adequate permissions on reflection, which is |
| * the only way to create an instance of the proxy class.</li> |
| * <li>The proxy class contains a single constructor, which takes as |
| * its only argument an {@link InvocationHandler}. The method |
| * {@link #newInstance} is shorthand to do the necessary |
| * reflection.</li> |
| * </ul> |
| * |
| * <h3>Proxy Instances</h3> |
| * A proxy instance is an instance of a proxy class. It has the |
| * following properties, many of which follow from the properties of a |
| * proxy class listed above: |
| * <ul> |
| * <li>For a proxy class with Foo listed as one of its interfaces, the |
| * expression <code>proxy instanceof Foo</code> will return true, |
| * and the expression <code>(Foo) proxy</code> will succeed without |
| * a {@link ClassCastException}.</li> |
| * <li>Each proxy instance has an invocation handler, which can be |
| * accessed by {@link #getInvocationHandler(Object)}. Any call |
| * to an interface method, including {@link Object#hashcode()}, |
| * {@link Object#equals(Object)}, or {@link Object#toString()}, |
| * but excluding the public final methods of Object, will be |
| * encoded and passed to the {@link InvocationHandler#invoke} |
| * method of this handler.</li> |
| * </ul> |
| * |
| * <h3>Inheritance Issues</h3> |
| * A proxy class may inherit a method from more than one interface. |
| * The order in which interfaces are listed matters, because it determines |
| * which reflected {@link Method} object will be passed to the invocation |
| * handler. This means that the dynamically generated class cannot |
| * determine through which interface a method is being invoked.<p> |
| * |
| * In short, if a method is declared in Object (namely, hashCode, |
| * equals, or toString), then Object will be used; otherwise, the |
| * leftmost interface that inherits or declares a method will be used, |
| * even if it has a more permissive throws clause than what the proxy |
| * class is allowed. Thus, in the invocation handler, it is not always |
| * safe to assume that every class listed in the throws clause of the |
| * passed Method object can safely be thrown; fortunately, the Proxy |
| * instance is robust enough to wrap all illegal checked exceptions in |
| * {@link UndeclaredThrowableException}. |
| * |
| * @see InvocationHandler |
| * @see UndeclaredThrowableException |
| * @see Class |
| * @author Eric Blake (ebb9@email.byu.edu) |
| * @since 1.3 |
| * @status updated to 1.4, except for the use of ProtectionDomain |
| */ |
| public class Proxy implements Serializable |
| { |
| /** |
| * Compatible with JDK 1.3+. |
| */ |
| private static final long serialVersionUID = -2222568056686623797L; |
| |
| /** |
| * Map of ProxyType to proxy class. |
| * |
| * @XXX This prevents proxy classes from being garbage collected. |
| * java.util.WeakHashSet is not appropriate, because that collects the |
| * keys, but we are interested in collecting the elements. |
| */ |
| private static final Map proxyClasses = new HashMap(); |
| |
| /** |
| * The invocation handler for this proxy instance. For Proxy, this |
| * field is unused, but it appears here in order to be serialized in all |
| * proxy classes. |
| * |
| * <em>NOTE</em>: This implementation is more secure for proxy classes |
| * than what Sun specifies. Sun does not require h to be immutable, but |
| * this means you could change h after the fact by reflection. However, |
| * by making h immutable, we may break non-proxy classes which extend |
| * Proxy. |
| * @serial invocation handler associated with this proxy instance |
| */ |
| protected InvocationHandler h; |
| |
| /** |
| * Constructs a new Proxy from a subclass (usually a proxy class), |
| * with the specified invocation handler. |
| * |
| * <em>NOTE</em>: This throws a NullPointerException if you attempt |
| * to create a proxy instance with a null handler using reflection. |
| * This behavior is not yet specified by Sun; see Sun Bug 4487672. |
| * |
| * @param handler the invocation handler, may be null if the subclass |
| * is not a proxy class |
| * @throws NullPointerException if handler is null and this is a proxy |
| * instance |
| */ |
| protected Proxy(InvocationHandler handler) |
| { |
| if (handler == null && isProxyClass(getClass())) |
| throw new NullPointerException("invalid handler"); |
| h = handler; |
| } |
| |
| /** |
| * Returns the proxy {@link Class} for the given ClassLoader and array |
| * of interfaces, dynamically generating it if necessary. |
| * |
| * <p>There are several restrictions on this method, the violation of |
| * which will result in an IllegalArgumentException or |
| * NullPointerException:</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 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>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 synchronized Class getProxyClass(ClassLoader loader, |
| Class[] interfaces) |
| { |
| interfaces = (Class[]) interfaces.clone(); |
| ProxyType pt = new ProxyType(loader, interfaces); |
| Class clazz = (Class) proxyClasses.get(pt); |
| if (clazz == null) |
| { |
| if (Configuration.HAVE_NATIVE_GET_PROXY_CLASS) |
| clazz = getProxyClass0(loader, interfaces); |
| else |
| { |
| ProxyData data = (Configuration.HAVE_NATIVE_GET_PROXY_DATA |
| ? getProxyData0(loader, interfaces) |
| : ProxyData.getProxyData(pt)); |
| |
| clazz = (Configuration.HAVE_NATIVE_GENERATE_PROXY_CLASS |
| ? generateProxyClass0(loader, data) |
| : new ClassFactory(data).generate(loader)); |
| } |
| |
| Object check = proxyClasses.put(pt, clazz); |
| // assert check == null && clazz != null; |
| if (check != null || clazz == null) |
| throw new InternalError(/*"Fatal flaw in getProxyClass"*/); |
| } |
| return clazz; |
| } |
| |
| /** |
| * Combines several methods into one. This is equivalent to: |
| * <pre> |
| * Proxy.getProxyClass(loader, interfaces) |
| * .getConstructor(new Class[] {InvocationHandler.class}) |
| * .newInstance(new Object[] {handler}); |
| * </pre> |
| * except that it will not fail with the normal problems caused |
| * by reflection. It can still fail for the same reasons documented |
| * in getProxyClass, or if handler is null. |
| * |
| * @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 |
| * @param handler the invocation handler, may not be null |
| * @return a proxy instance implementing the specified interfaces |
| * @throws IllegalArgumentException if the constraints for getProxyClass |
| * were violated, except for problems with null |
| * @throws NullPointerException if `interfaces' is null or contains |
| * a null entry, or if handler is null |
| * @see #getProxyClass(ClassLoader, Class[]) |
| * @see Class#getConstructor(Class[]) |
| * @see Constructor#newInstance(Object[]) |
| */ |
| public static Object newProxyInstance(ClassLoader loader, |
| Class[] interfaces, |
| InvocationHandler handler) |
| { |
| try |
| { |
| // getProxyClass() and Proxy() throw the necessary exceptions |
| return getProxyClass(loader, interfaces) |
| .getConstructor(new Class[] {InvocationHandler.class}) |
| .newInstance(new Object[] {handler}); |
| } |
| catch (RuntimeException e) |
| { |
| // Let IllegalArgumentException, NullPointerException escape. |
| // assert e instanceof IllegalArgumentException |
| // || e instanceof NullPointerException; |
| throw e; |
| } |
| catch (InvocationTargetException e) |
| { |
| // Let wrapped NullPointerException escape. |
| // assert e.getTargetException() instanceof NullPointerException |
| throw (NullPointerException) e.getCause(); |
| } |
| catch (Exception e) |
| { |
| // Covers InstantiationException, IllegalAccessException, |
| // NoSuchMethodException, none of which should be generated |
| // if the proxy class was generated correctly. |
| // assert false; |
| throw (Error) new InternalError("Unexpected: " + e).initCause(e); |
| } |
| } |
| |
| /** |
| * Returns true if and only if the Class object is a dynamically created |
| * proxy class (created by <code>getProxyClass</code> or by the |
| * syntactic sugar of <code>newProxyInstance</code>). |
| * |
| * <p>This check is secure (in other words, it is not simply |
| * <code>clazz.getSuperclass() == Proxy.class</code>), it will not |
| * be spoofed by non-proxy classes that extend Proxy. |
| * |
| * @param clazz the class to check, must not be null |
| * @return true if the class represents a proxy class |
| * @throws NullPointerException if clazz is null |
| */ |
| // This is synchronized on the off chance that another thread is |
| // trying to add a class to the map at the same time we read it. |
| public static synchronized boolean isProxyClass(Class clazz) |
| { |
| if (! Proxy.class.isAssignableFrom(clazz)) |
| return false; |
| // This is a linear search, even though we could do an O(1) search |
| // using new ProxyType(clazz.getClassLoader(), clazz.getInterfaces()). |
| return proxyClasses.containsValue(clazz); |
| } |
| |
| /** |
| * Returns the invocation handler for the given proxy instance.<p> |
| * |
| * <em>NOTE</em>: We guarantee a non-null result if successful, |
| * but Sun allows the creation of a proxy instance with a null |
| * handler. See the comments for {@link #Proxy(InvocationHandler)}. |
| * |
| * @param proxy the proxy instance, must not be null |
| * @return the invocation handler, guaranteed non-null. |
| * @throws IllegalArgumentException if |
| * <code>Proxy.isProxyClass(proxy.getClass())</code> returns false. |
| * @throws NullPointerException if proxy is null |
| */ |
| public static InvocationHandler getInvocationHandler(Object proxy) |
| { |
| if (! isProxyClass(proxy.getClass())) |
| throw new IllegalArgumentException("not a proxy instance"); |
| return ((Proxy) proxy).h; |
| } |
| |
| /** |
| * Optional native method to replace (and speed up) the pure Java |
| * implementation of getProxyClass. Only needed if |
| * Configuration.HAVE_NATIVE_GET_PROXY_CLASS is true, this does the |
| * work of both getProxyData0 and generateProxyClass0 with no |
| * intermediate form in Java. The native code may safely assume that |
| * this class must be created, and does not already exist. |
| * |
| * @param loader the class loader to define the proxy class in; null |
| * implies the bootstrap class loader |
| * @param interfaces the interfaces the class will extend |
| * @return the generated proxy class |
| * @throws IllegalArgumentException if the constraints for getProxyClass |
| * were violated, except for problems with null |
| * @throws NullPointerException if `interfaces' is null or contains |
| * a null entry, or if handler is null |
| * @see Configuration#HAVE_NATIVE_GET_PROXY_CLASS |
| * @see #getProxyClass(ClassLoader, Class[]) |
| * @see #getProxyData0(ClassLoader, Class[]) |
| * @see #generateProxyClass0(ProxyData) |
| */ |
| private static native Class getProxyClass0(ClassLoader loader, |
| Class[] interfaces); |
| |
| /** |
| * Optional native method to replace (and speed up) the pure Java |
| * implementation of getProxyData. Only needed if |
| * Configuration.HAVE_NATIVE_GET_PROXY_DATA is true. The native code |
| * may safely assume that a new ProxyData object must be created which |
| * does not duplicate any existing ones. |
| * |
| * @param loader the class loader to define the proxy class in; null |
| * implies the bootstrap class loader |
| * @param interfaces the interfaces the class will extend |
| * @return all data that is required to make this proxy class |
| * @throws IllegalArgumentException if the constraints for getProxyClass |
| * were violated, except for problems with null |
| * @throws NullPointerException if `interfaces' is null or contains |
| * a null entry, or if handler is null |
| * @see Configuration.HAVE_NATIVE_GET_PROXY_DATA |
| * @see #getProxyClass(ClassLoader, Class[]) |
| * @see #getProxyClass0(ClassLoader, Class[]) |
| * @see ProxyType#getProxyData() |
| */ |
| private static native ProxyData getProxyData0(ClassLoader loader, |
| Class[] interfaces); |
| |
| /** |
| * Optional native method to replace (and speed up) the pure Java |
| * implementation of generateProxyClass. Only needed if |
| * Configuration.HAVE_NATIVE_GENERATE_PROXY_CLASS is true. The native |
| * code may safely assume that a new Class must be created, and that |
| * the ProxyData object does not describe any existing class. |
| * |
| * @param loader the class loader to define the proxy class in; null |
| * implies the bootstrap class loader |
| * @param data the struct of information to convert to a Class. This |
| * has already been verified for all problems except exceeding |
| * VM limitations |
| * @return the newly generated class |
| * @throws IllegalArgumentException if VM limitations are exceeded |
| * @see #getProxyClass(ClassLoader, Class[]) |
| * @see #getProxyClass0(ClassLoader, Class[]) |
| * @see ProxyData#generateProxyClass(ClassLoader) |
| */ |
| private static native Class generateProxyClass0(ClassLoader loader, |
| ProxyData data); |
| |
| /** |
| * 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) |
| { |
| if (loader == null) |
| loader = ClassLoader.getSystemClassLoader(); |
| this.loader = loader; |
| this.interfaces = interfaces; |
| } |
| |
| /** |
| * Calculates the hash code. |
| * |
| * @return a combination of the classloader and interfaces hashcodes. |
| */ |
| public int hashCode() |
| { |
| //loader is always not null |
| int hash = loader.hashCode(); |
| for (int i = 0; i < interfaces.length; i++) |
| hash = hash * 31 + interfaces[i].hashCode(); |
| return hash; |
| } |
| |
| // A more comprehensive comparison of two arrays, |
| // ignore array element order, and |
| // ignore redundant elements |
| private static boolean sameTypes(Class arr1[], Class arr2[]) { |
| if (arr1.length == 1 && arr2.length == 1) { |
| return arr1[0] == arr2[0]; |
| } |
| |
| // total occurrance of elements of arr1 in arr2 |
| int total_occ_of_arr1_in_arr2 = 0; |
| each_type: |
| for (int i = arr1.length; --i >= 0; ) |
| { |
| Class t = arr1[i]; |
| for (int j = i; --j >= 0; ) |
| { |
| if (t == arr1[j]) |
| { //found duplicate type |
| continue each_type; |
| } |
| } |
| |
| // count c(a unique element of arr1)'s |
| // occurrences in arr2 |
| int occ_in_arr2 = 0; |
| for (int j = arr2.length; --j >= 0; ) |
| { |
| if (t == arr2[j]) |
| { |
| ++occ_in_arr2; |
| } |
| } |
| if (occ_in_arr2 == 0) |
| { // t does not occur in arr2 |
| return false; |
| } |
| |
| total_occ_of_arr1_in_arr2 += occ_in_arr2; |
| } |
| // now, each element of arr2 must have been visited |
| return total_occ_of_arr1_in_arr2 == arr2.length; |
| } |
| |
| /** |
| * Calculates equality. |
| * |
| * @param the 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; |
| return sameTypes(interfaces, pt.interfaces); |
| } |
| } // 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 coreMethods = new HashMap(); |
| 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 exceptions = new HashSet(); |
| |
| /** |
| * 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 the 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) |
| */ |
| private 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 method_set = (Map) 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) |
| { |
| 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; |
| } |
| } // 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 POOL = 0; |
| 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 INVOKESTATIC = 184; |
| 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; |
| |
| // 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(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) |
| 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); |
| if (ret_type == void.class) |
| 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 |
| */ |
| Class 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; |
| |
| try |
| { |
| Class vmClassLoader = Class.forName("java.lang.VMClassLoader"); |
| Class[] types = {ClassLoader.class, String.class, |
| byte[].class, int.class, int.class, |
| ProtectionDomain.class }; |
| Method m = vmClassLoader.getDeclaredMethod("defineClass", types); |
| // We can bypass the security check of setAccessible(true), since |
| // we're in the same package. |
| m.flag = true; |
| |
| Object[] args = {loader, qualName, bytecode, new Integer(0), |
| new Integer(bytecode.length), |
| Object.class.getProtectionDomain() }; |
| Class clazz = (Class) m.invoke(null, args); |
| |
| // Finally, initialize the m field of the proxy class, before |
| // returning it. |
| Field f = clazz.getDeclaredField("m"); |
| f.flag = true; |
| // we can share the array, because it is not publicized |
| f.set(null, methods); |
| |
| return clazz; |
| } |
| catch (Exception e) |
| { |
| // assert false; |
| throw (Error) new InternalError("Unexpected: " + e).initCause(e); |
| } |
| } |
| |
| /** |
| * 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 |
| } |