jaroslav@601: /* jaroslav@601: * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. jaroslav@601: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. jaroslav@601: * jaroslav@601: * This code is free software; you can redistribute it and/or modify it jaroslav@601: * under the terms of the GNU General Public License version 2 only, as jaroslav@601: * published by the Free Software Foundation. Oracle designates this jaroslav@601: * particular file as subject to the "Classpath" exception as provided jaroslav@601: * by Oracle in the LICENSE file that accompanied this code. jaroslav@601: * jaroslav@601: * This code is distributed in the hope that it will be useful, but WITHOUT jaroslav@601: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or jaroslav@601: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License jaroslav@601: * version 2 for more details (a copy is included in the LICENSE file that jaroslav@601: * accompanied this code). jaroslav@601: * jaroslav@601: * You should have received a copy of the GNU General Public License version jaroslav@601: * 2 along with this work; if not, write to the Free Software Foundation, jaroslav@601: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. jaroslav@601: * jaroslav@601: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA jaroslav@601: * or visit www.oracle.com if you need additional information or have any jaroslav@601: * questions. jaroslav@601: */ jaroslav@601: jaroslav@1378: package org.apidesign.bck2brwsr.emul.reflect; jaroslav@601: jaroslav@1378: import java.io.ByteArrayOutputStream; jaroslav@1378: import java.io.DataOutputStream; jaroslav@1378: import java.io.IOException; jaroslav@1378: import java.io.OutputStream; jaroslav@1378: import java.lang.ref.Reference; jaroslav@1378: import java.lang.ref.WeakReference; jaroslav@1378: import java.lang.reflect.Array; jaroslav@1378: import java.lang.reflect.Constructor; jaroslav@1378: import java.lang.reflect.InvocationHandler; jaroslav@1378: import java.lang.reflect.InvocationTargetException; jaroslav@1378: import java.lang.reflect.Method; jaroslav@1378: import java.lang.reflect.Modifier; jaroslav@1378: import java.util.ArrayList; jaroslav@1378: import java.util.Arrays; jaroslav@1378: import java.util.Collections; jaroslav@1378: import java.util.HashMap; jaroslav@1378: import java.util.HashSet; jaroslav@1378: import java.util.LinkedList; jaroslav@1378: import java.util.Map; jaroslav@1378: import java.util.Set; jaroslav@1378: import java.util.List; jaroslav@1378: import java.util.ListIterator; jaroslav@1378: import java.util.WeakHashMap; jaroslav@1378: import org.apidesign.bck2brwsr.core.JavaScriptBody; jaroslav@1378: import org.apidesign.bck2brwsr.emul.reflect.MethodImpl; jaroslav@601: jaroslav@601: /** jaroslav@601: * {@code Proxy} provides static methods for creating dynamic proxy jaroslav@601: * classes and instances, and it is also the superclass of all jaroslav@601: * dynamic proxy classes created by those methods. jaroslav@601: * jaroslav@601: *

To create a proxy for some interface {@code Foo}: jaroslav@601: *

jaroslav@601:  *     InvocationHandler handler = new MyInvocationHandler(...);
jaroslav@601:  *     Class proxyClass = Proxy.getProxyClass(
jaroslav@601:  *         Foo.class.getClassLoader(), new Class[] { Foo.class });
jaroslav@601:  *     Foo f = (Foo) proxyClass.
jaroslav@601:  *         getConstructor(new Class[] { InvocationHandler.class }).
jaroslav@601:  *         newInstance(new Object[] { handler });
jaroslav@601:  * 
jaroslav@601: * or more simply: jaroslav@601: *
jaroslav@601:  *     Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
jaroslav@601:  *                                          new Class[] { Foo.class },
jaroslav@601:  *                                          handler);
jaroslav@601:  * 
jaroslav@601: * jaroslav@601: *

A dynamic proxy class (simply referred to as a proxy jaroslav@601: * class below) is a class that implements a list of interfaces jaroslav@601: * specified at runtime when the class is created, with behavior as jaroslav@601: * described below. jaroslav@601: * jaroslav@601: * A proxy interface is such an interface that is implemented jaroslav@601: * by a proxy class. jaroslav@601: * jaroslav@601: * A proxy instance is an instance of a proxy class. jaroslav@601: * jaroslav@601: * Each proxy instance has an associated invocation handler jaroslav@601: * object, which implements the interface {@link InvocationHandler}. jaroslav@601: * A method invocation on a proxy instance through one of its proxy jaroslav@601: * interfaces will be dispatched to the {@link InvocationHandler#invoke jaroslav@601: * invoke} method of the instance's invocation handler, passing the proxy jaroslav@601: * instance, a {@code java.lang.reflect.Method} object identifying jaroslav@601: * the method that was invoked, and an array of type {@code Object} jaroslav@601: * containing the arguments. The invocation handler processes the jaroslav@601: * encoded method invocation as appropriate and the result that it jaroslav@601: * returns will be returned as the result of the method invocation on jaroslav@601: * the proxy instance. jaroslav@601: * jaroslav@601: *

A proxy class has the following properties: jaroslav@601: * jaroslav@601: *

jaroslav@601: * jaroslav@601: *

A proxy instance has the following properties: jaroslav@601: * jaroslav@601: *

jaroslav@601: * jaroslav@601: *

Methods Duplicated in Multiple Proxy Interfaces

jaroslav@601: * jaroslav@601: *

When two or more interfaces of a proxy class contain a method with jaroslav@601: * the same name and parameter signature, the order of the proxy class's jaroslav@601: * interfaces becomes significant. When such a duplicate method jaroslav@601: * is invoked on a proxy instance, the {@code Method} object passed jaroslav@601: * to the invocation handler will not necessarily be the one whose jaroslav@601: * declaring class is assignable from the reference type of the interface jaroslav@601: * that the proxy's method was invoked through. This limitation exists jaroslav@601: * because the corresponding method implementation in the generated proxy jaroslav@601: * class cannot determine which interface it was invoked through. jaroslav@601: * Therefore, when a duplicate method is invoked on a proxy instance, jaroslav@601: * the {@code Method} object for the method in the foremost interface jaroslav@601: * that contains the method (either directly or inherited through a jaroslav@601: * superinterface) in the proxy class's list of interfaces is passed to jaroslav@601: * the invocation handler's {@code invoke} method, regardless of the jaroslav@601: * reference type through which the method invocation occurred. jaroslav@601: * jaroslav@601: *

If a proxy interface contains a method with the same name and jaroslav@601: * parameter signature as the {@code hashCode}, {@code equals}, jaroslav@601: * or {@code toString} methods of {@code java.lang.Object}, jaroslav@601: * when such a method is invoked on a proxy instance, the jaroslav@601: * {@code Method} object passed to the invocation handler will have jaroslav@601: * {@code java.lang.Object} as its declaring class. In other words, jaroslav@601: * the public, non-final methods of {@code java.lang.Object} jaroslav@601: * logically precede all of the proxy interfaces for the determination of jaroslav@601: * which {@code Method} object to pass to the invocation handler. jaroslav@601: * jaroslav@601: *

Note also that when a duplicate method is dispatched to an jaroslav@601: * invocation handler, the {@code invoke} method may only throw jaroslav@601: * checked exception types that are assignable to one of the exception jaroslav@601: * types in the {@code throws} clause of the method in all of jaroslav@601: * the proxy interfaces that it can be invoked through. If the jaroslav@601: * {@code invoke} method throws a checked exception that is not jaroslav@601: * assignable to any of the exception types declared by the method in one jaroslav@601: * of the proxy interfaces that it can be invoked through, then an jaroslav@601: * unchecked {@code UndeclaredThrowableException} will be thrown by jaroslav@601: * the invocation on the proxy instance. This restriction means that not jaroslav@601: * all of the exception types returned by invoking jaroslav@601: * {@code getExceptionTypes} on the {@code Method} object jaroslav@601: * passed to the {@code invoke} method can necessarily be thrown jaroslav@601: * successfully by the {@code invoke} method. jaroslav@601: * jaroslav@601: * @author Peter Jones jaroslav@601: * @see InvocationHandler jaroslav@601: * @since 1.3 jaroslav@601: */ jaroslav@1378: public final class ProxyImpl implements java.io.Serializable { jaroslav@601: jaroslav@601: private static final long serialVersionUID = -2222568056686623797L; jaroslav@601: jaroslav@1378: /** prefix for all proxy class names */ jaroslav@1378: private final static String proxyClassNamePrefix = "$Proxy"; jaroslav@601: jaroslav@1378: /** parameter types of a proxy class constructor */ jaroslav@1378: private final static Class[] constructorParams = jaroslav@1378: { InvocationHandler.class }; jaroslav@1378: jaroslav@1378: /** maps a class loader to the proxy class cache for that loader */ jaroslav@1378: private static Map, Object>> loaderToCache jaroslav@1378: = new WeakHashMap<>(); jaroslav@1378: jaroslav@1378: /** marks that a particular proxy class is currently being generated */ jaroslav@1378: private static Object pendingGenerationMarker = new Object(); jaroslav@1378: jaroslav@1378: /** next number to use for generation of unique proxy class names */ jaroslav@1378: private static long nextUniqueNumber = 0; jaroslav@1378: private static Object nextUniqueNumberLock = new Object(); jaroslav@1378: jaroslav@1378: /** set of all generated proxy classes, for isProxyClass implementation */ jaroslav@1378: private static Map, Void> proxyClasses = jaroslav@1378: Collections.synchronizedMap(new WeakHashMap, Void>()); jaroslav@601: jaroslav@601: /** jaroslav@601: * the invocation handler for this proxy instance. jaroslav@601: * @serial jaroslav@601: */ jaroslav@601: protected InvocationHandler h; jaroslav@601: jaroslav@601: /** jaroslav@601: * Prohibits instantiation. jaroslav@601: */ jaroslav@1378: private ProxyImpl() { jaroslav@601: } jaroslav@601: jaroslav@601: /** jaroslav@601: * Constructs a new {@code Proxy} instance from a subclass jaroslav@601: * (typically, a dynamic proxy class) with the specified value jaroslav@601: * for its invocation handler. jaroslav@601: * jaroslav@601: * @param h the invocation handler for this proxy instance jaroslav@601: */ jaroslav@1378: protected ProxyImpl(InvocationHandler h) { jaroslav@601: this.h = h; jaroslav@601: } jaroslav@601: jaroslav@601: /** jaroslav@601: * Returns the {@code java.lang.Class} object for a proxy class jaroslav@601: * given a class loader and an array of interfaces. The proxy class jaroslav@601: * will be defined by the specified class loader and will implement jaroslav@601: * all of the supplied interfaces. If a proxy class for the same jaroslav@601: * permutation of interfaces has already been defined by the class jaroslav@601: * loader, then the existing proxy class will be returned; otherwise, jaroslav@601: * a proxy class for those interfaces will be generated dynamically jaroslav@601: * and defined by the class loader. jaroslav@601: * jaroslav@601: *

There are several restrictions on the parameters that may be jaroslav@601: * passed to {@code Proxy.getProxyClass}: jaroslav@601: * jaroslav@601: *

jaroslav@601: * jaroslav@601: *

If any of these restrictions are violated, jaroslav@601: * {@code Proxy.getProxyClass} will throw an jaroslav@601: * {@code IllegalArgumentException}. If the {@code interfaces} jaroslav@601: * array argument or any of its elements are {@code null}, a jaroslav@601: * {@code NullPointerException} will be thrown. jaroslav@601: * jaroslav@601: *

Note that the order of the specified proxy interfaces is jaroslav@601: * significant: two requests for a proxy class with the same combination jaroslav@601: * of interfaces but in a different order will result in two distinct jaroslav@601: * proxy classes. jaroslav@601: * jaroslav@601: * @param loader the class loader to define the proxy class jaroslav@601: * @param interfaces the list of interfaces for the proxy class jaroslav@601: * to implement jaroslav@601: * @return a proxy class that is defined in the specified class loader jaroslav@601: * and that implements the specified interfaces jaroslav@601: * @throws IllegalArgumentException if any of the restrictions on the jaroslav@601: * parameters that may be passed to {@code getProxyClass} jaroslav@601: * are violated jaroslav@601: * @throws NullPointerException if the {@code interfaces} array jaroslav@601: * argument or any of its elements are {@code null} jaroslav@601: */ jaroslav@601: public static Class getProxyClass(ClassLoader loader, jaroslav@601: Class... interfaces) jaroslav@601: throws IllegalArgumentException jaroslav@601: { jaroslav@1378: if (interfaces.length > 65535) { jaroslav@1378: throw new IllegalArgumentException("interface limit exceeded"); jaroslav@1378: } jaroslav@1378: jaroslav@1378: Class proxyClass = null; jaroslav@1378: jaroslav@1378: /* collect interface names to use as key for proxy class cache */ jaroslav@1378: String[] interfaceNames = new String[interfaces.length]; jaroslav@1378: jaroslav@1378: // for detecting duplicates jaroslav@1378: Set> interfaceSet = new HashSet<>(); jaroslav@1378: jaroslav@1378: for (int i = 0; i < interfaces.length; i++) { jaroslav@1378: /* jaroslav@1378: * Verify that the class loader resolves the name of this jaroslav@1378: * interface to the same Class object. jaroslav@1378: */ jaroslav@1378: String interfaceName = interfaces[i].getName(); jaroslav@1378: Class interfaceClass = null; jaroslav@1378: try { jaroslav@1378: interfaceClass = Class.forName(interfaceName, false, loader); jaroslav@1378: } catch (ClassNotFoundException e) { jaroslav@1378: } jaroslav@1378: if (interfaceClass != interfaces[i]) { jaroslav@1378: throw new IllegalArgumentException( jaroslav@1378: interfaces[i] + " is not visible from class loader"); jaroslav@1378: } jaroslav@1378: jaroslav@1378: /* jaroslav@1378: * Verify that the Class object actually represents an jaroslav@1378: * interface. jaroslav@1378: */ jaroslav@1378: if (!interfaceClass.isInterface()) { jaroslav@1378: throw new IllegalArgumentException( jaroslav@1378: interfaceClass.getName() + " is not an interface"); jaroslav@1378: } jaroslav@1378: jaroslav@1378: /* jaroslav@1378: * Verify that this interface is not a duplicate. jaroslav@1378: */ jaroslav@1378: if (interfaceSet.contains(interfaceClass)) { jaroslav@1378: throw new IllegalArgumentException( jaroslav@1378: "repeated interface: " + interfaceClass.getName()); jaroslav@1378: } jaroslav@1378: interfaceSet.add(interfaceClass); jaroslav@1378: jaroslav@1378: interfaceNames[i] = interfaceName; jaroslav@1378: } jaroslav@1378: jaroslav@1378: /* jaroslav@1378: * Using string representations of the proxy interfaces as jaroslav@1378: * keys in the proxy class cache (instead of their Class jaroslav@1378: * objects) is sufficient because we require the proxy jaroslav@1378: * interfaces to be resolvable by name through the supplied jaroslav@1378: * class loader, and it has the advantage that using a string jaroslav@1378: * representation of a class makes for an implicit weak jaroslav@1378: * reference to the class. jaroslav@1378: */ jaroslav@1378: List key = Arrays.asList(interfaceNames); jaroslav@1378: jaroslav@1378: /* jaroslav@1378: * Find or create the proxy class cache for the class loader. jaroslav@1378: */ jaroslav@1378: Map, Object> cache; jaroslav@1378: synchronized (loaderToCache) { jaroslav@1378: cache = loaderToCache.get(loader); jaroslav@1378: if (cache == null) { jaroslav@1378: cache = new HashMap<>(); jaroslav@1378: loaderToCache.put(loader, cache); jaroslav@1378: } jaroslav@1378: /* jaroslav@1378: * This mapping will remain valid for the duration of this jaroslav@1378: * method, without further synchronization, because the mapping jaroslav@1378: * will only be removed if the class loader becomes unreachable. jaroslav@1378: */ jaroslav@1378: } jaroslav@1378: jaroslav@1378: /* jaroslav@1378: * Look up the list of interfaces in the proxy class cache using jaroslav@1378: * the key. This lookup will result in one of three possible jaroslav@1378: * kinds of values: jaroslav@1378: * null, if there is currently no proxy class for the list of jaroslav@1378: * interfaces in the class loader, jaroslav@1378: * the pendingGenerationMarker object, if a proxy class for the jaroslav@1378: * list of interfaces is currently being generated, jaroslav@1378: * or a weak reference to a Class object, if a proxy class for jaroslav@1378: * the list of interfaces has already been generated. jaroslav@1378: */ jaroslav@1378: synchronized (cache) { jaroslav@1378: /* jaroslav@1378: * Note that we need not worry about reaping the cache for jaroslav@1378: * entries with cleared weak references because if a proxy class jaroslav@1378: * has been garbage collected, its class loader will have been jaroslav@1378: * garbage collected as well, so the entire cache will be reaped jaroslav@1378: * from the loaderToCache map. jaroslav@1378: */ jaroslav@1378: do { jaroslav@1378: Object value = cache.get(key); jaroslav@1378: if (value instanceof Reference) { jaroslav@1378: proxyClass = (Class) ((Reference) value).get(); jaroslav@1378: } jaroslav@1378: if (proxyClass != null) { jaroslav@1378: // proxy class already generated: return it jaroslav@1378: return proxyClass; jaroslav@1378: } else if (value == pendingGenerationMarker) { jaroslav@1378: // proxy class being generated: wait for it jaroslav@1378: try { jaroslav@1378: cache.wait(); jaroslav@1378: } catch (InterruptedException e) { jaroslav@1378: /* jaroslav@1378: * The class generation that we are waiting for should jaroslav@1378: * take a small, bounded time, so we can safely ignore jaroslav@1378: * thread interrupts here. jaroslav@1378: */ jaroslav@1378: } jaroslav@1378: continue; jaroslav@1378: } else { jaroslav@1378: /* jaroslav@1378: * No proxy class for this list of interfaces has been jaroslav@1378: * generated or is being generated, so we will go and jaroslav@1378: * generate it now. Mark it as pending generation. jaroslav@1378: */ jaroslav@1378: cache.put(key, pendingGenerationMarker); jaroslav@1378: break; jaroslav@1378: } jaroslav@1378: } while (true); jaroslav@1378: } jaroslav@1378: jaroslav@1378: try { jaroslav@1378: String proxyPkg = null; // package to define proxy class in jaroslav@1378: jaroslav@1378: /* jaroslav@1378: * Record the package of a non-public proxy interface so that the jaroslav@1378: * proxy class will be defined in the same package. Verify that jaroslav@1378: * all non-public proxy interfaces are in the same package. jaroslav@1378: */ jaroslav@1378: for (int i = 0; i < interfaces.length; i++) { jaroslav@1378: int flags = interfaces[i].getModifiers(); jaroslav@1378: if (!Modifier.isPublic(flags)) { jaroslav@1378: String name = interfaces[i].getName(); jaroslav@1378: int n = name.lastIndexOf('.'); jaroslav@1378: String pkg = ((n == -1) ? "" : name.substring(0, n + 1)); jaroslav@1378: if (proxyPkg == null) { jaroslav@1378: proxyPkg = pkg; jaroslav@1378: } else if (!pkg.equals(proxyPkg)) { jaroslav@1378: throw new IllegalArgumentException( jaroslav@1378: "non-public interfaces from different packages"); jaroslav@1378: } jaroslav@1378: } jaroslav@1378: } jaroslav@1378: jaroslav@1378: if (proxyPkg == null) { // if no non-public proxy interfaces, jaroslav@1378: proxyPkg = ""; // use the unnamed package jaroslav@1378: } jaroslav@1378: jaroslav@1378: { jaroslav@1378: /* jaroslav@1378: * Choose a name for the proxy class to generate. jaroslav@1378: */ jaroslav@1378: long num; jaroslav@1378: synchronized (nextUniqueNumberLock) { jaroslav@1378: num = nextUniqueNumber++; jaroslav@1378: } jaroslav@1378: String proxyName = proxyPkg + proxyClassNamePrefix + num; jaroslav@1378: /* jaroslav@1378: * Verify that the class loader hasn't already jaroslav@1378: * defined a class with the chosen name. jaroslav@1378: */ jaroslav@1378: jaroslav@1378: /* jaroslav@1378: * Generate the specified proxy class. jaroslav@1378: */ jaroslav@1378: Generator gen = new Generator(proxyName, interfaces); jaroslav@1378: final byte[] proxyClassFile = gen.generateClassFile(); jaroslav@1378: try { jaroslav@1378: proxyClass = defineClass0(loader, proxyName, jaroslav@1378: proxyClassFile); jaroslav@1378: } catch (ClassFormatError e) { jaroslav@1378: /* jaroslav@1378: * A ClassFormatError here means that (barring bugs in the jaroslav@1378: * proxy class generation code) there was some other jaroslav@1378: * invalid aspect of the arguments supplied to the proxy jaroslav@1378: * class creation (such as virtual machine limitations jaroslav@1378: * exceeded). jaroslav@1378: */ jaroslav@1378: throw new IllegalArgumentException(e.toString()); jaroslav@1378: } jaroslav@1378: gen.fillInMethods(proxyClass); jaroslav@1378: } jaroslav@1378: // add to set of all generated proxy classes, for isProxyClass jaroslav@1378: proxyClasses.put(proxyClass, null); jaroslav@1378: jaroslav@1378: } finally { jaroslav@1378: /* jaroslav@1378: * We must clean up the "pending generation" state of the proxy jaroslav@1378: * class cache entry somehow. If a proxy class was successfully jaroslav@1378: * generated, store it in the cache (with a weak reference); jaroslav@1378: * otherwise, remove the reserved entry. In all cases, notify jaroslav@1378: * all waiters on reserved entries in this cache. jaroslav@1378: */ jaroslav@1378: synchronized (cache) { jaroslav@1378: if (proxyClass != null) { jaroslav@1378: cache.put(key, new WeakReference>(proxyClass)); jaroslav@1378: } else { jaroslav@1378: cache.remove(key); jaroslav@1378: } jaroslav@1378: cache.notifyAll(); jaroslav@1378: } jaroslav@1378: } jaroslav@1378: return proxyClass; jaroslav@601: } jaroslav@601: jaroslav@601: /** jaroslav@601: * Returns an instance of a proxy class for the specified interfaces jaroslav@601: * that dispatches method invocations to the specified invocation jaroslav@601: * handler. This method is equivalent to: jaroslav@601: *

jaroslav@601:      *     Proxy.getProxyClass(loader, interfaces).
jaroslav@601:      *         getConstructor(new Class[] { InvocationHandler.class }).
jaroslav@601:      *         newInstance(new Object[] { handler });
jaroslav@601:      * 
jaroslav@601: * jaroslav@601: *

{@code Proxy.newProxyInstance} throws jaroslav@601: * {@code IllegalArgumentException} for the same reasons that jaroslav@601: * {@code Proxy.getProxyClass} does. jaroslav@601: * jaroslav@601: * @param loader the class loader to define the proxy class jaroslav@601: * @param interfaces the list of interfaces for the proxy class jaroslav@601: * to implement jaroslav@601: * @param h the invocation handler to dispatch method invocations to jaroslav@601: * @return a proxy instance with the specified invocation handler of a jaroslav@601: * proxy class that is defined by the specified class loader jaroslav@601: * and that implements the specified interfaces jaroslav@601: * @throws IllegalArgumentException if any of the restrictions on the jaroslav@601: * parameters that may be passed to {@code getProxyClass} jaroslav@601: * are violated jaroslav@601: * @throws NullPointerException if the {@code interfaces} array jaroslav@601: * argument or any of its elements are {@code null}, or jaroslav@601: * if the invocation handler, {@code h}, is jaroslav@601: * {@code null} jaroslav@601: */ jaroslav@601: public static Object newProxyInstance(ClassLoader loader, jaroslav@601: Class[] interfaces, jaroslav@601: InvocationHandler h) jaroslav@601: throws IllegalArgumentException jaroslav@601: { jaroslav@601: if (h == null) { jaroslav@601: throw new NullPointerException(); jaroslav@601: } jaroslav@1378: jaroslav@1378: /* jaroslav@1378: * Look up or generate the designated proxy class. jaroslav@1378: */ jaroslav@1378: Class cl = getProxyClass(loader, interfaces); jaroslav@1378: jaroslav@1378: /* jaroslav@1378: * Invoke its constructor with the designated invocation handler. jaroslav@1378: */ jaroslav@1378: try { jaroslav@1378: Constructor cons = cl.getConstructor(constructorParams); jaroslav@1378: return cons.newInstance(new Object[] { h }); jaroslav@1378: } catch (NoSuchMethodException e) { jaroslav@1378: throw new InternalError(e.toString()); jaroslav@1378: } catch (IllegalAccessException e) { jaroslav@1378: throw new InternalError(e.toString()); jaroslav@1378: } catch (InstantiationException e) { jaroslav@1378: throw new InternalError(e.toString()); jaroslav@1378: } catch (InvocationTargetException e) { jaroslav@1378: throw new InternalError(e.toString()); jaroslav@1378: } jaroslav@601: } jaroslav@601: jaroslav@601: /** jaroslav@601: * Returns true if and only if the specified class was dynamically jaroslav@601: * generated to be a proxy class using the {@code getProxyClass} jaroslav@601: * method or the {@code newProxyInstance} method. jaroslav@601: * jaroslav@601: *

The reliability of this method is important for the ability jaroslav@601: * to use it to make security decisions, so its implementation should jaroslav@601: * not just test if the class in question extends {@code Proxy}. jaroslav@601: * jaroslav@601: * @param cl the class to test jaroslav@601: * @return {@code true} if the class is a proxy class and jaroslav@601: * {@code false} otherwise jaroslav@601: * @throws NullPointerException if {@code cl} is {@code null} jaroslav@601: */ jaroslav@601: public static boolean isProxyClass(Class cl) { jaroslav@601: if (cl == null) { jaroslav@601: throw new NullPointerException(); jaroslav@601: } jaroslav@601: jaroslav@1378: return proxyClasses.containsKey(cl); jaroslav@601: } jaroslav@601: jaroslav@601: /** jaroslav@601: * Returns the invocation handler for the specified proxy instance. jaroslav@601: * jaroslav@601: * @param proxy the proxy instance to return the invocation handler for jaroslav@601: * @return the invocation handler for the proxy instance jaroslav@601: * @throws IllegalArgumentException if the argument is not a jaroslav@601: * proxy instance jaroslav@601: */ jaroslav@601: public static InvocationHandler getInvocationHandler(Object proxy) jaroslav@601: throws IllegalArgumentException jaroslav@601: { jaroslav@601: /* jaroslav@601: * Verify that the object is actually a proxy instance. jaroslav@601: */ jaroslav@601: if (!isProxyClass(proxy.getClass())) { jaroslav@601: throw new IllegalArgumentException("not a proxy instance"); jaroslav@601: } jaroslav@601: jaroslav@1378: ProxyImpl p = (ProxyImpl) proxy; jaroslav@601: return p.h; jaroslav@601: } jaroslav@601: jaroslav@1378: @JavaScriptBody(args = { "ignore", "name", "byteCode" }, jaroslav@1378: body = "return vm._reload(name, byteCode).constructor.$class;" jaroslav@1378: ) jaroslav@1378: private static native Class defineClass0( jaroslav@1378: ClassLoader loader, String name, byte[] b jaroslav@1378: ); jaroslav@1378: jaroslav@1378: private static class Generator { jaroslav@1378: /* jaroslav@1378: * In the comments below, "JVMS" refers to The Java Virtual Machine jaroslav@1378: * Specification Second Edition and "JLS" refers to the original jaroslav@1378: * version of The Java Language Specification, unless otherwise jaroslav@1378: * specified. jaroslav@1378: */ jaroslav@1378: jaroslav@1378: /* need 1.6 bytecode */ jaroslav@1378: private static final int CLASSFILE_MAJOR_VERSION = 50; jaroslav@1378: private static final int CLASSFILE_MINOR_VERSION = 0; jaroslav@1378: jaroslav@1378: /* jaroslav@1378: * beginning of constants copied from jaroslav@1378: * sun.tools.java.RuntimeConstants (which no longer exists): jaroslav@1378: */ jaroslav@1378: jaroslav@1378: /* constant pool tags */ jaroslav@1378: private static final int CONSTANT_UTF8 = 1; jaroslav@1378: private static final int CONSTANT_UNICODE = 2; jaroslav@1378: private static final int CONSTANT_INTEGER = 3; jaroslav@1378: private static final int CONSTANT_FLOAT = 4; jaroslav@1378: private static final int CONSTANT_LONG = 5; jaroslav@1378: private static final int CONSTANT_DOUBLE = 6; jaroslav@1378: private static final int CONSTANT_CLASS = 7; jaroslav@1378: private static final int CONSTANT_STRING = 8; jaroslav@1378: private static final int CONSTANT_FIELD = 9; jaroslav@1378: private static final int CONSTANT_METHOD = 10; jaroslav@1378: private static final int CONSTANT_INTERFACEMETHOD = 11; jaroslav@1378: private static final int CONSTANT_NAMEANDTYPE = 12; jaroslav@1378: jaroslav@1378: /* access and modifier flags */ jaroslav@1378: private static final int ACC_PUBLIC = 0x00000001; jaroslav@1378: private static final int ACC_FINAL = 0x00000010; jaroslav@1378: private static final int ACC_SUPER = 0x00000020; jaroslav@1378: jaroslav@1378: // end of constants copied from sun.tools.java.RuntimeConstants jaroslav@1378: /** jaroslav@1378: * name of the superclass of proxy classes jaroslav@1378: */ jaroslav@1378: private final static String superclassName = "java/lang/reflect/Proxy"; jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * name of field for storing a proxy instance's invocation handler jaroslav@1378: */ jaroslav@1378: private final static String handlerFieldName = "h"; jaroslav@1378: jaroslav@1378: /* preloaded Method objects for methods in java.lang.Object */ jaroslav@1378: private static Method hashCodeMethod; jaroslav@1378: private static Method equalsMethod; jaroslav@1378: private static Method toStringMethod; jaroslav@1378: jaroslav@1378: static { jaroslav@1378: try { jaroslav@1378: hashCodeMethod = Object.class.getMethod("hashCode"); jaroslav@1378: equalsMethod jaroslav@1378: = Object.class.getMethod("equals", new Class[]{Object.class}); jaroslav@1378: toStringMethod = Object.class.getMethod("toString"); jaroslav@1378: } catch (NoSuchMethodException e) { jaroslav@1378: throw new IllegalStateException(e.getMessage()); jaroslav@1378: } jaroslav@1378: } jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * name of proxy class jaroslav@1378: */ jaroslav@1378: private String className; jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * proxy interfaces jaroslav@1378: */ jaroslav@1378: private Class[] interfaces; jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * constant pool of class being generated jaroslav@1378: */ jaroslav@1378: private ConstantPool cp = new ConstantPool(); jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * maps method signature string to list of ProxyMethod objects for proxy jaroslav@1378: * methods with that signature jaroslav@1378: */ jaroslav@1378: private Map> proxyMethods jaroslav@1378: = new HashMap>(); jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * count of ProxyMethod objects added to proxyMethods jaroslav@1378: */ jaroslav@1378: private int proxyMethodCount = 0; jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * Construct a ProxyGenerator to generate a proxy class with the jaroslav@1378: * specified name and for the given interfaces. jaroslav@1378: * jaroslav@1378: * A ProxyGenerator object contains the state for the ongoing generation jaroslav@1378: * of a particular proxy class. jaroslav@1378: */ jaroslav@1378: private Generator(String className, Class[] interfaces) { jaroslav@1378: this.className = className; jaroslav@1378: this.interfaces = interfaces; jaroslav@1378: } jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * Generate a class file for the proxy class. This method drives the jaroslav@1378: * class file generation process. jaroslav@1378: */ jaroslav@1378: private byte[] generateClassFile() { jaroslav@1378: jaroslav@1378: /* ============================================================ jaroslav@1378: * Step 1: Assemble ProxyMethod objects for all methods to jaroslav@1378: * generate proxy dispatching code for. jaroslav@1378: */ jaroslav@1378: jaroslav@1378: /* jaroslav@1378: * Record that proxy methods are needed for the hashCode, equals, jaroslav@1378: * and toString methods of java.lang.Object. This is done before jaroslav@1378: * the methods from the proxy interfaces so that the methods from jaroslav@1378: * java.lang.Object take precedence over duplicate methods in the jaroslav@1378: * proxy interfaces. jaroslav@1378: */ jaroslav@1378: addProxyMethod(hashCodeMethod, Object.class); jaroslav@1378: addProxyMethod(equalsMethod, Object.class); jaroslav@1378: addProxyMethod(toStringMethod, Object.class); jaroslav@1378: jaroslav@1378: /* jaroslav@1378: * Now record all of the methods from the proxy interfaces, giving jaroslav@1378: * earlier interfaces precedence over later ones with duplicate jaroslav@1378: * methods. jaroslav@1378: */ jaroslav@1378: for (int i = 0; i < interfaces.length; i++) { jaroslav@1378: Method[] methods = interfaces[i].getMethods(); jaroslav@1378: for (int j = 0; j < methods.length; j++) { jaroslav@1378: addProxyMethod(methods[j], interfaces[i]); jaroslav@1378: } jaroslav@1378: } jaroslav@1378: jaroslav@1378: /* jaroslav@1378: * For each set of proxy methods with the same signature, jaroslav@1378: * verify that the methods' return types are compatible. jaroslav@1378: */ jaroslav@1378: for (List sigmethods : proxyMethods.values()) { jaroslav@1378: checkReturnTypes(sigmethods); jaroslav@1378: } jaroslav@1378: jaroslav@1378: /* ============================================================ jaroslav@1378: * Step 2: Assemble FieldInfo and MethodInfo structs for all of jaroslav@1378: * fields and methods in the class we are generating. jaroslav@1378: */ jaroslav@1378: jaroslav@1378: // will be done in fillInMethods jaroslav@1378: jaroslav@1378: /* ============================================================ jaroslav@1378: * Step 3: Write the final class file. jaroslav@1378: */ jaroslav@1378: jaroslav@1378: /* jaroslav@1378: * Make sure that constant pool indexes are reserved for the jaroslav@1378: * following items before starting to write the final class file. jaroslav@1378: */ jaroslav@1378: cp.getClass(dotToSlash(className)); jaroslav@1378: cp.getClass(superclassName); jaroslav@1378: for (int i = 0; i < interfaces.length; i++) { jaroslav@1378: cp.getClass(dotToSlash(interfaces[i].getName())); jaroslav@1378: } jaroslav@1378: jaroslav@1378: /* jaroslav@1378: * Disallow new constant pool additions beyond this point, since jaroslav@1378: * we are about to write the final constant pool table. jaroslav@1378: */ jaroslav@1378: cp.setReadOnly(); jaroslav@1378: jaroslav@1378: ByteArrayOutputStream bout = new ByteArrayOutputStream(); jaroslav@1378: DataOutputStream dout = new DataOutputStream(bout); jaroslav@1378: jaroslav@1378: try { jaroslav@1378: /* jaroslav@1378: * Write all the items of the "ClassFile" structure. jaroslav@1378: * See JVMS section 4.1. jaroslav@1378: */ jaroslav@1378: // u4 magic; jaroslav@1378: dout.writeInt(0xCAFEBABE); jaroslav@1378: // u2 minor_version; jaroslav@1378: dout.writeShort(CLASSFILE_MINOR_VERSION); jaroslav@1378: // u2 major_version; jaroslav@1378: dout.writeShort(CLASSFILE_MAJOR_VERSION); jaroslav@1378: jaroslav@1378: cp.write(dout); // (write constant pool) jaroslav@1378: jaroslav@1378: // u2 access_flags; jaroslav@1378: dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER); jaroslav@1378: // u2 this_class; jaroslav@1378: dout.writeShort(cp.getClass(dotToSlash(className))); jaroslav@1378: // u2 super_class; jaroslav@1378: dout.writeShort(cp.getClass(superclassName)); jaroslav@1378: jaroslav@1378: // u2 interfaces_count; jaroslav@1378: dout.writeShort(interfaces.length); jaroslav@1378: // u2 interfaces[interfaces_count]; jaroslav@1378: for (int i = 0; i < interfaces.length; i++) { jaroslav@1378: dout.writeShort(cp.getClass( jaroslav@1378: dotToSlash(interfaces[i].getName()))); jaroslav@1378: } jaroslav@1378: jaroslav@1378: // u2 fields_count; jaroslav@1378: dout.writeShort(0); jaroslav@1378: jaroslav@1378: // u2 methods_count; jaroslav@1378: dout.writeShort(0); jaroslav@1378: jaroslav@1378: // u2 attributes_count; jaroslav@1378: dout.writeShort(0); // (no ClassFile attributes for proxy classes) jaroslav@1378: jaroslav@1378: } catch (IOException e) { jaroslav@1378: throw new InternalError("unexpected I/O Exception"); jaroslav@1378: } jaroslav@1378: jaroslav@1378: return bout.toByteArray(); jaroslav@1378: } jaroslav@1378: jaroslav@1378: @JavaScriptBody(args = { "c", "sig", "method", "primitive" }, body = jaroslav@1378: "var p = c.cnstr.prototype;\n" + jaroslav@1378: "p[sig] = function() {\n" + jaroslav@1378: " var h = this._h();\n" + jaroslav@1378: " var res = h.invoke__Ljava_lang_Object_2Ljava_lang_Object_2Ljava_lang_reflect_Method_2_3Ljava_lang_Object_2(this, method, arguments);\n" + jaroslav@1378: " \n" + jaroslav@1378: " \n" + jaroslav@1378: " return res;\n" + jaroslav@1378: "};" jaroslav@1378: ) jaroslav@1378: private static native void defineMethod(Class proxyClass, String sig, Method method, boolean primitive); jaroslav@1378: jaroslav@1378: @JavaScriptBody(args = "c", body = jaroslav@1378: "var h = c.cnstr.cons__VLjava_lang_reflect_InvocationHandler_2 = function(h) {\n" jaroslav@1378: + " c.superclass.cnstr.cons__VLjava_lang_reflect_InvocationHandler_2.call(this, h);\n" jaroslav@1378: + "}\n" jaroslav@1378: + "h.cls = c.cnstr;\n" jaroslav@1378: ) jaroslav@1378: private static native void defineConstructor(Class proxyClass); jaroslav@1378: jaroslav@1378: final void fillInMethods(Class proxyClass) { jaroslav@1378: for (List sigmethods : proxyMethods.values()) { jaroslav@1378: for (ProxyMethod pm : sigmethods) { jaroslav@1378: String sig = MethodImpl.toSignature(pm.method); jaroslav@1378: defineMethod(proxyClass, sig, pm.method, pm.method.getReturnType().isPrimitive()); jaroslav@1378: } jaroslav@1378: } jaroslav@1378: defineConstructor(proxyClass); jaroslav@1378: } jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * Add another method to be proxied, either by creating a new jaroslav@1378: * ProxyMethod object or augmenting an old one for a duplicate method. jaroslav@1378: * jaroslav@1378: * "fromClass" indicates the proxy interface that the method was found jaroslav@1378: * through, which may be different from (a subinterface of) the method's jaroslav@1378: * "declaring class". Note that the first Method object passed for a jaroslav@1378: * given name and descriptor identifies the Method object (and thus the jaroslav@1378: * declaring class) that will be passed to the invocation handler's jaroslav@1378: * "invoke" method for a given set of duplicate methods. jaroslav@1378: */ jaroslav@1378: private void addProxyMethod(Method m, Class fromClass) { jaroslav@1378: String name = m.getName(); jaroslav@1378: Class[] parameterTypes = m.getParameterTypes(); jaroslav@1378: Class returnType = m.getReturnType(); jaroslav@1378: Class[] exceptionTypes = m.getExceptionTypes(); jaroslav@1378: jaroslav@1378: String sig = MethodImpl.toSignature(m); jaroslav@1378: List sigmethods = proxyMethods.get(sig); jaroslav@1378: if (sigmethods != null) { jaroslav@1378: for (ProxyMethod pm : sigmethods) { jaroslav@1378: if (returnType == pm.returnType) { jaroslav@1378: /* jaroslav@1378: * Found a match: reduce exception types to the jaroslav@1378: * greatest set of exceptions that can thrown jaroslav@1378: * compatibly with the throws clauses of both jaroslav@1378: * overridden methods. jaroslav@1378: */ jaroslav@1378: List> legalExceptions = new ArrayList>(); jaroslav@1378: collectCompatibleTypes( jaroslav@1378: exceptionTypes, pm.exceptionTypes, legalExceptions); jaroslav@1378: collectCompatibleTypes( jaroslav@1378: pm.exceptionTypes, exceptionTypes, legalExceptions); jaroslav@1378: pm.exceptionTypes = new Class[legalExceptions.size()]; jaroslav@1378: pm.exceptionTypes jaroslav@1378: = legalExceptions.toArray(pm.exceptionTypes); jaroslav@1378: return; jaroslav@1378: } jaroslav@1378: } jaroslav@1378: } else { jaroslav@1378: sigmethods = new ArrayList(3); jaroslav@1378: proxyMethods.put(sig, sigmethods); jaroslav@1378: } jaroslav@1378: sigmethods.add(new ProxyMethod(m, name, parameterTypes, returnType, jaroslav@1378: exceptionTypes, fromClass)); jaroslav@1378: } jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * For a given set of proxy methods with the same signature, check that jaroslav@1378: * their return types are compatible according to the Proxy jaroslav@1378: * specification. jaroslav@1378: * jaroslav@1378: * Specifically, if there is more than one such method, then all of the jaroslav@1378: * return types must be reference types, and there must be one return jaroslav@1378: * type that is assignable to each of the rest of them. jaroslav@1378: */ jaroslav@1378: private static void checkReturnTypes(List methods) { jaroslav@1378: /* jaroslav@1378: * If there is only one method with a given signature, there jaroslav@1378: * cannot be a conflict. This is the only case in which a jaroslav@1378: * primitive (or void) return type is allowed. jaroslav@1378: */ jaroslav@1378: if (methods.size() < 2) { jaroslav@1378: return; jaroslav@1378: } jaroslav@1378: jaroslav@1378: /* jaroslav@1378: * List of return types that are not yet known to be jaroslav@1378: * assignable from ("covered" by) any of the others. jaroslav@1378: */ jaroslav@1378: LinkedList> uncoveredReturnTypes = new LinkedList>(); jaroslav@1378: jaroslav@1378: nextNewReturnType: jaroslav@1378: for (ProxyMethod pm : methods) { jaroslav@1378: Class newReturnType = pm.returnType; jaroslav@1378: if (newReturnType.isPrimitive()) { jaroslav@1378: throw new IllegalArgumentException( jaroslav@1378: "methods with same signature " jaroslav@1378: + getFriendlyMethodSignature(pm.methodName, jaroslav@1378: pm.parameterTypes) jaroslav@1378: + " but incompatible return types: " jaroslav@1378: + newReturnType.getName() + " and others"); jaroslav@1378: } jaroslav@1378: boolean added = false; jaroslav@1378: jaroslav@1378: /* jaroslav@1378: * Compare the new return type to the existing uncovered jaroslav@1378: * return types. jaroslav@1378: */ jaroslav@1378: ListIterator> liter = uncoveredReturnTypes.listIterator(); jaroslav@1378: while (liter.hasNext()) { jaroslav@1378: Class uncoveredReturnType = liter.next(); jaroslav@1378: jaroslav@1378: /* jaroslav@1378: * If an existing uncovered return type is assignable jaroslav@1378: * to this new one, then we can forget the new one. jaroslav@1378: */ jaroslav@1378: if (newReturnType.isAssignableFrom(uncoveredReturnType)) { jaroslav@1378: assert !added; jaroslav@1378: continue nextNewReturnType; jaroslav@1378: } jaroslav@1378: jaroslav@1378: /* jaroslav@1378: * If the new return type is assignable to an existing jaroslav@1378: * uncovered one, then should replace the existing one jaroslav@1378: * with the new one (or just forget the existing one, jaroslav@1378: * if the new one has already be put in the list). jaroslav@1378: */ jaroslav@1378: if (uncoveredReturnType.isAssignableFrom(newReturnType)) { jaroslav@1378: // (we can assume that each return type is unique) jaroslav@1378: if (!added) { jaroslav@1378: liter.set(newReturnType); jaroslav@1378: added = true; jaroslav@1378: } else { jaroslav@1378: liter.remove(); jaroslav@1378: } jaroslav@1378: } jaroslav@1378: } jaroslav@1378: jaroslav@1378: /* jaroslav@1378: * If we got through the list of existing uncovered return jaroslav@1378: * types without an assignability relationship, then add jaroslav@1378: * the new return type to the list of uncovered ones. jaroslav@1378: */ jaroslav@1378: if (!added) { jaroslav@1378: uncoveredReturnTypes.add(newReturnType); jaroslav@1378: } jaroslav@1378: } jaroslav@1378: jaroslav@1378: /* jaroslav@1378: * We shouldn't end up with more than one return type that is jaroslav@1378: * not assignable from any of the others. jaroslav@1378: */ jaroslav@1378: if (uncoveredReturnTypes.size() > 1) { jaroslav@1378: ProxyMethod pm = methods.get(0); jaroslav@1378: throw new IllegalArgumentException( jaroslav@1378: "methods with same signature " jaroslav@1378: + getFriendlyMethodSignature(pm.methodName, pm.parameterTypes) jaroslav@1378: + " but incompatible return types: " + uncoveredReturnTypes); jaroslav@1378: } jaroslav@1378: } jaroslav@1378: jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * A ProxyMethod object represents a proxy method in the proxy class jaroslav@1378: * being generated: a method whose implementation will encode and jaroslav@1378: * dispatch invocations to the proxy instance's invocation handler. jaroslav@1378: */ jaroslav@1378: private class ProxyMethod { jaroslav@1378: jaroslav@1378: private final Method method; jaroslav@1378: public String methodName; jaroslav@1378: public Class[] parameterTypes; jaroslav@1378: public Class returnType; jaroslav@1378: public Class[] exceptionTypes; jaroslav@1378: public Class fromClass; jaroslav@1378: public String methodFieldName; jaroslav@1378: jaroslav@1378: private ProxyMethod(Method m, jaroslav@1378: String methodName, Class[] parameterTypes, jaroslav@1378: Class returnType, Class[] exceptionTypes, jaroslav@1378: Class fromClass jaroslav@1378: ) { jaroslav@1378: this.method = m; jaroslav@1378: this.methodName = methodName; jaroslav@1378: this.parameterTypes = parameterTypes; jaroslav@1378: this.returnType = returnType; jaroslav@1378: this.exceptionTypes = exceptionTypes; jaroslav@1378: this.fromClass = fromClass; jaroslav@1378: this.methodFieldName = "m" + proxyMethodCount++; jaroslav@1378: } jaroslav@1378: jaroslav@1378: } jaroslav@1378: jaroslav@1378: /* jaroslav@1378: * ==================== General Utility Methods ==================== jaroslav@1378: */ jaroslav@1378: /** jaroslav@1378: * Convert a fully qualified class name that uses '.' as the package jaroslav@1378: * separator, the external representation used by the Java language and jaroslav@1378: * APIs, to a fully qualified class name that uses '/' as the package jaroslav@1378: * separator, the representation used in the class file format (see JVMS jaroslav@1378: * section 4.2). jaroslav@1378: */ jaroslav@1378: private static String dotToSlash(String name) { jaroslav@1378: return name.replace('.', '/'); jaroslav@1378: } jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * Return the list of "parameter descriptor" strings enclosed in jaroslav@1378: * parentheses corresponding to the given parameter types (in other jaroslav@1378: * words, a method descriptor without a return descriptor). This string jaroslav@1378: * is useful for constructing string keys for methods without regard to jaroslav@1378: * their return type. jaroslav@1378: */ jaroslav@1378: private static String getParameterDescriptors(Class[] parameterTypes) { jaroslav@1378: StringBuilder desc = new StringBuilder("("); jaroslav@1378: for (int i = 0; i < parameterTypes.length; i++) { jaroslav@1378: desc.append(getFieldType(parameterTypes[i])); jaroslav@1378: } jaroslav@1378: desc.append(')'); jaroslav@1378: return desc.toString(); jaroslav@1378: } jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * Return the "field type" string for the given type, appropriate for a jaroslav@1378: * field descriptor, a parameter descriptor, or a return descriptor jaroslav@1378: * other than "void". See JVMS section 4.3.2. jaroslav@1378: */ jaroslav@1378: private static String getFieldType(Class type) { jaroslav@1378: if (type.isPrimitive()) { jaroslav@1378: return PrimitiveTypeInfo.get(type).baseTypeString; jaroslav@1378: } else if (type.isArray()) { jaroslav@1378: /* jaroslav@1378: * According to JLS 20.3.2, the getName() method on Class does jaroslav@1378: * return the VM type descriptor format for array classes (only); jaroslav@1378: * using that should be quicker than the otherwise obvious code: jaroslav@1378: * jaroslav@1378: * return "[" + getTypeDescriptor(type.getComponentType()); jaroslav@1378: */ jaroslav@1378: return type.getName().replace('.', '/'); jaroslav@1378: } else { jaroslav@1378: return "L" + dotToSlash(type.getName()) + ";"; jaroslav@1378: } jaroslav@1378: } jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * Returns a human-readable string representing the signature of a jaroslav@1378: * method with the given name and parameter types. jaroslav@1378: */ jaroslav@1378: private static String getFriendlyMethodSignature(String name, jaroslav@1378: Class[] parameterTypes) { jaroslav@1378: StringBuilder sig = new StringBuilder(name); jaroslav@1378: sig.append('('); jaroslav@1378: for (int i = 0; i < parameterTypes.length; i++) { jaroslav@1378: if (i > 0) { jaroslav@1378: sig.append(','); jaroslav@1378: } jaroslav@1378: Class parameterType = parameterTypes[i]; jaroslav@1378: int dimensions = 0; jaroslav@1378: while (parameterType.isArray()) { jaroslav@1378: parameterType = parameterType.getComponentType(); jaroslav@1378: dimensions++; jaroslav@1378: } jaroslav@1378: sig.append(parameterType.getName()); jaroslav@1378: while (dimensions-- > 0) { jaroslav@1378: sig.append("[]"); jaroslav@1378: } jaroslav@1378: } jaroslav@1378: sig.append(')'); jaroslav@1378: return sig.toString(); jaroslav@1378: } jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * Add to the given list all of the types in the "from" array that are jaroslav@1378: * not already contained in the list and are assignable to at least one jaroslav@1378: * of the types in the "with" array. jaroslav@1378: * jaroslav@1378: * This method is useful for computing the greatest common set of jaroslav@1378: * declared exceptions from duplicate methods inherited from different jaroslav@1378: * interfaces. jaroslav@1378: */ jaroslav@1378: private static void collectCompatibleTypes(Class[] from, jaroslav@1378: Class[] with, jaroslav@1378: List> list) { jaroslav@1378: for (int i = 0; i < from.length; i++) { jaroslav@1378: if (!list.contains(from[i])) { jaroslav@1378: for (int j = 0; j < with.length; j++) { jaroslav@1378: if (with[j].isAssignableFrom(from[i])) { jaroslav@1378: list.add(from[i]); jaroslav@1378: break; jaroslav@1378: } jaroslav@1378: } jaroslav@1378: } jaroslav@1378: } jaroslav@1378: } jaroslav@1378: jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * A PrimitiveTypeInfo object contains assorted information about a jaroslav@1378: * primitive type in its public fields. The struct for a particular jaroslav@1378: * primitive type can be obtained using the static "get" method. jaroslav@1378: */ jaroslav@1378: private static class PrimitiveTypeInfo { jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * "base type" used in various descriptors (see JVMS section 4.3.2) jaroslav@1378: */ jaroslav@1378: public String baseTypeString; jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * name of corresponding wrapper class jaroslav@1378: */ jaroslav@1378: public String wrapperClassName; jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * method descriptor for wrapper class "valueOf" factory method jaroslav@1378: */ jaroslav@1378: public String wrapperValueOfDesc; jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * name of wrapper class method for retrieving primitive value jaroslav@1378: */ jaroslav@1378: public String unwrapMethodName; jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * descriptor of same method jaroslav@1378: */ jaroslav@1378: public String unwrapMethodDesc; jaroslav@1378: jaroslav@1378: private static Map table jaroslav@1378: = new HashMap(); jaroslav@1378: jaroslav@1378: static { jaroslav@1378: add(byte.class, Byte.class); jaroslav@1378: add(char.class, Character.class); jaroslav@1378: add(double.class, Double.class); jaroslav@1378: add(float.class, Float.class); jaroslav@1378: add(int.class, Integer.class); jaroslav@1378: add(long.class, Long.class); jaroslav@1378: add(short.class, Short.class); jaroslav@1378: add(boolean.class, Boolean.class); jaroslav@1378: } jaroslav@1378: jaroslav@1378: private static void add(Class primitiveClass, Class wrapperClass) { jaroslav@1378: table.put(primitiveClass, jaroslav@1378: new PrimitiveTypeInfo(primitiveClass, wrapperClass)); jaroslav@1378: } jaroslav@1378: jaroslav@1378: private PrimitiveTypeInfo(Class primitiveClass, Class wrapperClass) { jaroslav@1378: assert primitiveClass.isPrimitive(); jaroslav@1378: jaroslav@1378: baseTypeString jaroslav@1378: = Array.newInstance(primitiveClass, 0) jaroslav@1378: .getClass().getName().substring(1); jaroslav@1378: wrapperClassName = dotToSlash(wrapperClass.getName()); jaroslav@1378: wrapperValueOfDesc jaroslav@1378: = "(" + baseTypeString + ")L" + wrapperClassName + ";"; jaroslav@1378: unwrapMethodName = primitiveClass.getName() + "Value"; jaroslav@1378: unwrapMethodDesc = "()" + baseTypeString; jaroslav@1378: } jaroslav@1378: jaroslav@1378: public static PrimitiveTypeInfo get(Class cl) { jaroslav@1378: return table.get(cl); jaroslav@1378: } jaroslav@1378: } jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * A ConstantPool object represents the constant pool of a class file jaroslav@1378: * being generated. This representation of a constant pool is designed jaroslav@1378: * specifically for use by ProxyGenerator; in particular, it assumes jaroslav@1378: * that constant pool entries will not need to be resorted (for example, jaroslav@1378: * by their type, as the Java compiler does), so that the final index jaroslav@1378: * value can be assigned and used when an entry is first created. jaroslav@1378: * jaroslav@1378: * Note that new entries cannot be created after the constant pool has jaroslav@1378: * been written to a class file. To prevent such logic errors, a jaroslav@1378: * ConstantPool instance can be marked "read only", so that further jaroslav@1378: * attempts to add new entries will fail with a runtime exception. jaroslav@1378: * jaroslav@1378: * See JVMS section 4.4 for more information about the constant pool of jaroslav@1378: * a class file. jaroslav@1378: */ jaroslav@1378: private static class ConstantPool { jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * list of constant pool entries, in constant pool index order. jaroslav@1378: * jaroslav@1378: * This list is used when writing the constant pool to a stream and jaroslav@1378: * for assigning the next index value. Note that element 0 of this jaroslav@1378: * list corresponds to constant pool index 1. jaroslav@1378: */ jaroslav@1378: private List pool = new ArrayList(32); jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * maps constant pool data of all types to constant pool indexes. jaroslav@1378: * jaroslav@1378: * This map is used to look up the index of an existing entry for jaroslav@1378: * values of all types. jaroslav@1378: */ jaroslav@1378: private Map map = new HashMap(16); jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * true if no new constant pool entries may be added jaroslav@1378: */ jaroslav@1378: private boolean readOnly = false; jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * Get or assign the index for a CONSTANT_Utf8 entry. jaroslav@1378: */ jaroslav@1378: public short getUtf8(String s) { jaroslav@1378: if (s == null) { jaroslav@1378: throw new NullPointerException(); jaroslav@1378: } jaroslav@1378: return getValue(s); jaroslav@1378: } jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * Get or assign the index for a CONSTANT_Integer entry. jaroslav@1378: */ jaroslav@1378: public short getInteger(int i) { jaroslav@1378: return getValue(new Integer(i)); jaroslav@1378: } jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * Get or assign the index for a CONSTANT_Float entry. jaroslav@1378: */ jaroslav@1378: public short getFloat(float f) { jaroslav@1378: return getValue(new Float(f)); jaroslav@1378: } jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * Get or assign the index for a CONSTANT_Class entry. jaroslav@1378: */ jaroslav@1378: public short getClass(String name) { jaroslav@1378: short utf8Index = getUtf8(name); jaroslav@1378: return getIndirect(new IndirectEntry( jaroslav@1378: CONSTANT_CLASS, utf8Index)); jaroslav@1378: } jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * Get or assign the index for a CONSTANT_String entry. jaroslav@1378: */ jaroslav@1378: public short getString(String s) { jaroslav@1378: short utf8Index = getUtf8(s); jaroslav@1378: return getIndirect(new IndirectEntry( jaroslav@1378: CONSTANT_STRING, utf8Index)); jaroslav@1378: } jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * Get or assign the index for a CONSTANT_FieldRef entry. jaroslav@1378: */ jaroslav@1378: public short getFieldRef(String className, jaroslav@1378: String name, String descriptor) { jaroslav@1378: short classIndex = getClass(className); jaroslav@1378: short nameAndTypeIndex = getNameAndType(name, descriptor); jaroslav@1378: return getIndirect(new IndirectEntry( jaroslav@1378: CONSTANT_FIELD, classIndex, nameAndTypeIndex)); jaroslav@1378: } jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * Get or assign the index for a CONSTANT_MethodRef entry. jaroslav@1378: */ jaroslav@1378: public short getMethodRef(String className, jaroslav@1378: String name, String descriptor) { jaroslav@1378: short classIndex = getClass(className); jaroslav@1378: short nameAndTypeIndex = getNameAndType(name, descriptor); jaroslav@1378: return getIndirect(new IndirectEntry( jaroslav@1378: CONSTANT_METHOD, classIndex, nameAndTypeIndex)); jaroslav@1378: } jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * Get or assign the index for a CONSTANT_InterfaceMethodRef entry. jaroslav@1378: */ jaroslav@1378: public short getInterfaceMethodRef(String className, String name, jaroslav@1378: String descriptor) { jaroslav@1378: short classIndex = getClass(className); jaroslav@1378: short nameAndTypeIndex = getNameAndType(name, descriptor); jaroslav@1378: return getIndirect(new IndirectEntry( jaroslav@1378: CONSTANT_INTERFACEMETHOD, classIndex, nameAndTypeIndex)); jaroslav@1378: } jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * Get or assign the index for a CONSTANT_NameAndType entry. jaroslav@1378: */ jaroslav@1378: public short getNameAndType(String name, String descriptor) { jaroslav@1378: short nameIndex = getUtf8(name); jaroslav@1378: short descriptorIndex = getUtf8(descriptor); jaroslav@1378: return getIndirect(new IndirectEntry( jaroslav@1378: CONSTANT_NAMEANDTYPE, nameIndex, descriptorIndex)); jaroslav@1378: } jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * Set this ConstantPool instance to be "read only". jaroslav@1378: * jaroslav@1378: * After this method has been called, further requests to get an jaroslav@1378: * index for a non-existent entry will cause an InternalError to be jaroslav@1378: * thrown instead of creating of the entry. jaroslav@1378: */ jaroslav@1378: public void setReadOnly() { jaroslav@1378: readOnly = true; jaroslav@1378: } jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * Write this constant pool to a stream as part of the class file jaroslav@1378: * format. jaroslav@1378: * jaroslav@1378: * This consists of writing the "constant_pool_count" and jaroslav@1378: * "constant_pool[]" items of the "ClassFile" structure, as jaroslav@1378: * described in JVMS section 4.1. jaroslav@1378: */ jaroslav@1378: public void write(OutputStream out) throws IOException { jaroslav@1378: DataOutputStream dataOut = new DataOutputStream(out); jaroslav@1378: jaroslav@1378: // constant_pool_count: number of entries plus one jaroslav@1378: dataOut.writeShort(pool.size() + 1); jaroslav@1378: jaroslav@1378: for (Entry e : pool) { jaroslav@1378: e.write(dataOut); jaroslav@1378: } jaroslav@1378: } jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * Add a new constant pool entry and return its index. jaroslav@1378: */ jaroslav@1378: private short addEntry(Entry entry) { jaroslav@1378: pool.add(entry); jaroslav@1378: /* jaroslav@1378: * Note that this way of determining the index of the jaroslav@1378: * added entry is wrong if this pool supports jaroslav@1378: * CONSTANT_Long or CONSTANT_Double entries. jaroslav@1378: */ jaroslav@1378: if (pool.size() >= 65535) { jaroslav@1378: throw new IllegalArgumentException( jaroslav@1378: "constant pool size limit exceeded"); jaroslav@1378: } jaroslav@1378: return (short) pool.size(); jaroslav@1378: } jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * Get or assign the index for an entry of a type that contains a jaroslav@1378: * direct value. The type of the given object determines the type of jaroslav@1378: * the desired entry as follows: jaroslav@1378: * jaroslav@1378: * java.lang.String CONSTANT_Utf8 java.lang.Integer CONSTANT_Integer jaroslav@1378: * java.lang.Float CONSTANT_Float java.lang.Long CONSTANT_Long jaroslav@1378: * java.lang.Double CONSTANT_DOUBLE jaroslav@1378: */ jaroslav@1378: private short getValue(Object key) { jaroslav@1378: Short index = map.get(key); jaroslav@1378: if (index != null) { jaroslav@1378: return index.shortValue(); jaroslav@1378: } else { jaroslav@1378: if (readOnly) { jaroslav@1378: throw new InternalError( jaroslav@1378: "late constant pool addition: " + key); jaroslav@1378: } jaroslav@1378: short i = addEntry(new ValueEntry(key)); jaroslav@1378: map.put(key, new Short(i)); jaroslav@1378: return i; jaroslav@1378: } jaroslav@1378: } jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * Get or assign the index for an entry of a type that contains jaroslav@1378: * references to other constant pool entries. jaroslav@1378: */ jaroslav@1378: private short getIndirect(IndirectEntry e) { jaroslav@1378: Short index = map.get(e); jaroslav@1378: if (index != null) { jaroslav@1378: return index.shortValue(); jaroslav@1378: } else { jaroslav@1378: if (readOnly) { jaroslav@1378: throw new InternalError("late constant pool addition"); jaroslav@1378: } jaroslav@1378: short i = addEntry(e); jaroslav@1378: map.put(e, new Short(i)); jaroslav@1378: return i; jaroslav@1378: } jaroslav@1378: } jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * Entry is the abstact superclass of all constant pool entry types jaroslav@1378: * that can be stored in the "pool" list; its purpose is to define a jaroslav@1378: * common method for writing constant pool entries to a class file. jaroslav@1378: */ jaroslav@1378: private static abstract class Entry { jaroslav@1378: jaroslav@1378: public abstract void write(DataOutputStream out) jaroslav@1378: throws IOException; jaroslav@1378: } jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * ValueEntry represents a constant pool entry of a type that jaroslav@1378: * contains a direct value (see the comments for the "getValue" jaroslav@1378: * method for a list of such types). jaroslav@1378: * jaroslav@1378: * ValueEntry objects are not used as keys for their entries in the jaroslav@1378: * Map "map", so no useful hashCode or equals methods are defined. jaroslav@1378: */ jaroslav@1378: private static class ValueEntry extends Entry { jaroslav@1378: jaroslav@1378: private Object value; jaroslav@1378: jaroslav@1378: public ValueEntry(Object value) { jaroslav@1378: this.value = value; jaroslav@1378: } jaroslav@1378: jaroslav@1378: public void write(DataOutputStream out) throws IOException { jaroslav@1378: if (value instanceof String) { jaroslav@1378: out.writeByte(CONSTANT_UTF8); jaroslav@1378: out.writeUTF((String) value); jaroslav@1378: } else if (value instanceof Integer) { jaroslav@1378: out.writeByte(CONSTANT_INTEGER); jaroslav@1378: out.writeInt(((Integer) value).intValue()); jaroslav@1378: } else if (value instanceof Float) { jaroslav@1378: out.writeByte(CONSTANT_FLOAT); jaroslav@1378: out.writeFloat(((Float) value).floatValue()); jaroslav@1378: } else if (value instanceof Long) { jaroslav@1378: out.writeByte(CONSTANT_LONG); jaroslav@1378: out.writeLong(((Long) value).longValue()); jaroslav@1378: } else if (value instanceof Double) { jaroslav@1378: out.writeDouble(CONSTANT_DOUBLE); jaroslav@1378: out.writeDouble(((Double) value).doubleValue()); jaroslav@1378: } else { jaroslav@1378: throw new InternalError("bogus value entry: " + value); jaroslav@1378: } jaroslav@1378: } jaroslav@1378: } jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * IndirectEntry represents a constant pool entry of a type that jaroslav@1378: * references other constant pool entries, i.e., the following jaroslav@1378: * types: jaroslav@1378: * jaroslav@1378: * CONSTANT_Class, CONSTANT_String, CONSTANT_Fieldref, jaroslav@1378: * CONSTANT_Methodref, CONSTANT_InterfaceMethodref, and jaroslav@1378: * CONSTANT_NameAndType. jaroslav@1378: * jaroslav@1378: * Each of these entry types contains either one or two indexes of jaroslav@1378: * other constant pool entries. jaroslav@1378: * jaroslav@1378: * IndirectEntry objects are used as the keys for their entries in jaroslav@1378: * the Map "map", so the hashCode and equals methods are overridden jaroslav@1378: * to allow matching. jaroslav@1378: */ jaroslav@1378: private static class IndirectEntry extends Entry { jaroslav@1378: jaroslav@1378: private int tag; jaroslav@1378: private short index0; jaroslav@1378: private short index1; jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * Construct an IndirectEntry for a constant pool entry type jaroslav@1378: * that contains one index of another entry. jaroslav@1378: */ jaroslav@1378: public IndirectEntry(int tag, short index) { jaroslav@1378: this.tag = tag; jaroslav@1378: this.index0 = index; jaroslav@1378: this.index1 = 0; jaroslav@1378: } jaroslav@1378: jaroslav@1378: /** jaroslav@1378: * Construct an IndirectEntry for a constant pool entry type jaroslav@1378: * that contains two indexes for other entries. jaroslav@1378: */ jaroslav@1378: public IndirectEntry(int tag, short index0, short index1) { jaroslav@1378: this.tag = tag; jaroslav@1378: this.index0 = index0; jaroslav@1378: this.index1 = index1; jaroslav@1378: } jaroslav@1378: jaroslav@1378: public void write(DataOutputStream out) throws IOException { jaroslav@1378: out.writeByte(tag); jaroslav@1378: out.writeShort(index0); jaroslav@1378: /* jaroslav@1378: * If this entry type contains two indexes, write jaroslav@1378: * out the second, too. jaroslav@1378: */ jaroslav@1378: if (tag == CONSTANT_FIELD jaroslav@1378: || tag == CONSTANT_METHOD jaroslav@1378: || tag == CONSTANT_INTERFACEMETHOD jaroslav@1378: || tag == CONSTANT_NAMEANDTYPE) { jaroslav@1378: out.writeShort(index1); jaroslav@1378: } jaroslav@1378: } jaroslav@1378: jaroslav@1378: public int hashCode() { jaroslav@1378: return tag + index0 + index1; jaroslav@1378: } jaroslav@1378: jaroslav@1378: public boolean equals(Object obj) { jaroslav@1378: if (obj instanceof IndirectEntry) { jaroslav@1378: IndirectEntry other = (IndirectEntry) obj; jaroslav@1378: if (tag == other.tag jaroslav@1378: && index0 == other.index0 && index1 == other.index1) { jaroslav@1378: return true; jaroslav@1378: } jaroslav@1378: } jaroslav@1378: return false; jaroslav@1378: } jaroslav@1378: } jaroslav@1378: } jaroslav@1378: } jaroslav@1378: jaroslav@601: }