/*
* @(#)ProxyGenerator.java 1.12 06/10/10
*
* Copyright 1990-2008 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 only, as published by the Free Software Foundation.
*
* This program 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 version 2 for more details (a copy is
* included at /legal/license.txt).
*
* You should have received a copy of the GNU General Public License
* version 2 along with this work; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 or visit www.sun.com if you need additional
* information or have any questions.
*
*/
package sun.misc;
import java.lang.reflect.*;
import java.io.*;
import java.util.*;
import sun.tools.java.RuntimeConstants;
import sun.security.action.GetBooleanAction;
/**
* ProxyGenerator contains the code to generate a dynamic proxy class
* for the java.lang.reflect.Proxy API.
*
* The external interfaces to ProxyGenerator is the static
* "generateProxyClass" method.
*
* @author Peter Jones
* @version 1.3, 00/02/02
* @since JDK1.3
*/
public class ProxyGenerator {
/*
* In the comments below, "JVMS" refers to The Java Virtual Machine
* Specification Second Edition and "JLS" refers to the original
* version of The Java Language Specification, unless otherwise
* specified.
*/
/*
* Note that this class imports sun.tools.java.RuntimeConstants and
* references many final static primitive fields of that interface.
* By JLS section 13.4.8, the compiler should inline all of these
* references, so their presence should not require the loading of
* RuntimeConstants at runtime when ProxyGenerator is linked. This
* non-requirement is important because ProxyGenerator is intended
* to be bundled with the JRE, but classes in the sun.tools
* hierarchy, such as RuntimeConstants, are not.
*
* The Java compiler does add a CONSTANT_Class entry in the constant
* pool of this class for "sun/tools/java/RuntimeConstants". The
* evaluation of bugid 4162387 seems to imply that this is for the
* compiler's implementation of the "-Xdepend" option. This
* CONSTANT_Class entry may, however, confuse tools which use such
* entries to compute runtime class dependencies or virtual machine
* implementations which use them to effect eager class resolution.
*/
/** name of the superclass of proxy classes */
private final static String superclassName = "java/lang/reflect/Proxy";
/** name of field for storing a proxy instance's invocation handler */
private final static String handlerFieldName = "h";
/** debugging flag for saving generated class files */
private final static boolean saveGeneratedFiles =
((Boolean) java.security.AccessController.doPrivileged(
new GetBooleanAction(
"sun.misc.ProxyGenerator.saveGeneratedFiles"))).booleanValue();
/**
* Generate a proxy class given a name and a list of proxy interfaces.
*/
public static byte[] generateProxyClass(final String name,
Class[] interfaces)
{
ProxyGenerator gen = new ProxyGenerator(name, interfaces);
final byte[] classFile = gen.generateClassFile();
if (saveGeneratedFiles) {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {
public Object run() {
try {
FileOutputStream file =
new FileOutputStream(dotToSlash(name) + ".class");
file.write(classFile);
file.close();
return null;
} catch (IOException e) {
throw new InternalError(
"I/O exception saving generated file: " + e);
}
}
});
}
return classFile;
}
/* preloaded Method objects for methods in java.lang.Object */
private static Method hashCodeMethod;
private static Method equalsMethod;
private static Method toStringMethod;
static {
try {
hashCodeMethod = Object.class.getMethod("hashCode", null);
equalsMethod =
Object.class.getMethod("equals", new Class[] { Object.class });
toStringMethod = Object.class.getMethod("toString", null);
} catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
}
/** name of proxy class */
private String className;
/** proxy interfaces */
private Class[] interfaces;
/** constant pool of class being generated */
private ConstantPool cp = new ConstantPool();
/** FieldInfo struct for each field of generated class */
private List fields = new ArrayList();
/** MethodInfo struct for each method of generated class */
private List methods = new ArrayList();
/**
* for each method to be proxied, maps method name and parameter
* descriptor to ProxyMethod object
*/
private Map proxyMethods = new HashMap(11);
/**
* Construct a ProxyGenerator to generate a proxy class with the
* specified name and for the given interfaces.
*
* A ProxyGenerator object contains the state for the ongoing
* generation of a particular proxy class.
*/
private ProxyGenerator(String className, Class[] interfaces) {
this.className = className;
this.interfaces = interfaces;
}
/**
* Generate a class file for the proxy class. This method drives the
* class file generation process.
*/
private byte[] generateClassFile() {
/* ============================================================
* Step 1: Assemble ProxyMethod objects for all methods to
* generate proxy dispatching code for.
*/
/*
* Record that proxy methods are needed for the hashCode, equals,
* and toString methods of java.lang.Object. This is done before
* the methods from the proxy interfaces so that the methods from
* java.lang.Object take precedence over duplicate methods in the
* proxy interfaces.
*/
addProxyMethod(hashCodeMethod, Object.class);
addProxyMethod(equalsMethod, Object.class);
addProxyMethod(toStringMethod, Object.class);
/*
* Now record all of the methods from the proxy interfaces, giving
* earlier interfaces precedence over later ones with duplicate
* methods.
*/
for (int i = 0; i < interfaces.length; i++) {
Method[] methods = interfaces[i].getMethods();
for (int j = 0; j < methods.length; j++) {
addProxyMethod(methods[j], interfaces[i]);
}
}
/* ============================================================
* Step 2: Assemble FieldInfo and MethodInfo structs for all of
* fields and methods in the class we are generating.
*/
try {
// fields.add(new FieldInfo(
// handlerFieldName, "Ljava/lang/reflect/InvocationHandler;",
// RuntimeConstants.ACC_PRIVATE | RuntimeConstants.ACC_FINAL));
methods.add(generateConstructor());
for (Iterator iter = proxyMethods.values().iterator();
iter.hasNext();)
{
ProxyMethod pm = (ProxyMethod) iter.next();
// add static field for method's Method object
fields.add(new FieldInfo(pm.methodFieldName,
"Ljava/lang/reflect/Method;",
RuntimeConstants.ACC_PRIVATE |
RuntimeConstants.ACC_STATIC));
// generate code for proxy method and add it
methods.add(pm.generateMethod());
}
methods.add(generateStaticInitializer());
} catch (IOException e) {
throw new InternalError("unexpected I/O Exception");
}
/* ============================================================
* Step 3: Write the final class file.
*/
/*
* Make sure that constant pool indexes are reserved for the
* following items before starting to write the final class file.
*/
cp.getClass(dotToSlash(className));
cp.getClass(superclassName);
for (int i = 0; i < interfaces.length; i++) {
cp.getClass(dotToSlash(interfaces[i].getName()));
}
/*
* Disallow new constant pool additions beyond this point, since
* we are about to write the final constant pool table.
*/
cp.setReadOnly();
ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream(bout);
try {
/*
* Write all the items of the "ClassFile" structure.
* See JVMS section 4.1.
*/
// u4 magic;
dout.writeInt(RuntimeConstants.JAVA_MAGIC);
// u2 major_version;
dout.writeShort(RuntimeConstants.JAVA_DEFAULT_MINOR_VERSION);
// u2 minor_version;
dout.writeShort(RuntimeConstants.JAVA_DEFAULT_VERSION);
cp.write(dout); // (write constant pool)
// u2 access_flags;
dout.writeShort(RuntimeConstants.ACC_PUBLIC |
RuntimeConstants.ACC_FINAL |
RuntimeConstants.ACC_SUPER);
// u2 this_class;
dout.writeShort(cp.getClass(dotToSlash(className)));
// u2 super_class;
dout.writeShort(cp.getClass(superclassName));
// u2 interfaces_count;
dout.writeShort(interfaces.length);
// u2 interfaces[interfaces_count];
for (int i = 0; i < interfaces.length; i++) {
dout.writeShort(cp.getClass(
dotToSlash(interfaces[i].getName())));
}
// u2 fields_count;
dout.writeShort(fields.size());
// field_info fields[fields_count];
for (Iterator iter = fields.iterator(); iter.hasNext();) {
FieldInfo f = (FieldInfo) iter.next();
f.write(dout);
}
// u2 methods_count;
dout.writeShort(methods.size());
// method_info methods[methods_count];
for (Iterator iter = methods.iterator(); iter.hasNext();) {
MethodInfo m = (MethodInfo) iter.next();
m.write(dout);
}
// u2 attributes_count;
dout.writeShort(0); // (no ClassFile attributes for proxy classes)
} catch (IOException e) {
throw new InternalError("unexpected I/O Exception");
}
return bout.toByteArray();
}
/**
* Add another method to be proxied, either by creating a new ProxyMethod
* object or augmenting an old one for a duplicate method.
*
* "fromClass" indicates the proxy interface that the method was found
* through, which may be different from (a subinterface of) the method's
* "declaring class". Note that the first Method object passed for a
* given name and parameter types identifies the Method object (and thus
* the declaring class) that will be passed to the invocation handler's
* "invoke" method for a given set of duplicate methods.
*/
private void addProxyMethod(Method m, Class fromClass) {
String name = m.getName();
Class[] parameterTypes = m.getParameterTypes();
Class returnType = m.getReturnType();
Class[] exceptionTypes = m.getExceptionTypes();
String key = name + getParameterDescriptors(parameterTypes);
ProxyMethod pm = (ProxyMethod) proxyMethods.get(key);
if (pm != null) {
/*
* If a proxy method with the same name and parameter types has
* already been added, verify that it has the same return type...
*/
if (returnType != pm.returnType) {
throw new IllegalArgumentException(
"methods with same name and parameter " +
"signature but different return type in " +
pm.fromClass + " and " + fromClass +
": " + key);
}
/*
* ...and compute the greatest common set of exceptions that can
* thrown by the proxy method compatibly with both inherited
* methods.
*/
List legalExceptions = new ArrayList();
collectCompatibleTypes(
exceptionTypes, pm.exceptionTypes, legalExceptions);
collectCompatibleTypes(
pm.exceptionTypes, exceptionTypes, legalExceptions);
pm.exceptionTypes = new Class[legalExceptions.size()];
pm.exceptionTypes =
(Class[]) legalExceptions.toArray(pm.exceptionTypes);
} else {
pm = new ProxyMethod(name, parameterTypes, returnType,
exceptionTypes, fromClass, "m" + proxyMethods.size());
proxyMethods.put(key, pm);
}
}
/**
* A FieldInfo object contains information about a particular field
* in the class being generated. The class mirrors the data items of
* the "field_info" structure of the class file format (see JVMS 4.5).
*/
private class FieldInfo {
public int accessFlags;
public String name;
public String descriptor;
public FieldInfo(String name, String descriptor, int accessFlags) {
this.name = name;
this.descriptor = descriptor;
this.accessFlags = accessFlags;
/*
* Make sure that constant pool indexes are reserved for the
* following items before starting to write the final class file.
*/
cp.getUtf8(name);
cp.getUtf8(descriptor);
}
public void write(DataOutputStream out) throws IOException {
/*
* Write all the items of the "field_info" structure.
* See JVMS section 4.5.
*/
// u2 access_flags;
out.writeShort(accessFlags);
// u2 name_index;
out.writeShort(cp.getUtf8(name));
// u2 descriptor_index;
out.writeShort(cp.getUtf8(descriptor));
// u2 attributes_count;
out.writeShort(0); // (no field_info attributes for proxy classes)
}
}
/**
* An ExceptionTableEntry object holds values for the data items of
* an entry in the "exception_table" item of the "Code" attribute of
* "method_info" structures (see JVMS 4.7.3).
*/
private static class ExceptionTableEntry {
public short startPc;
public short endPc;
public short handlerPc;
public short catchType;
public ExceptionTableEntry(short startPc, short endPc,
short handlerPc, short catchType)
{
this.startPc = startPc;
this.endPc = endPc;
this.handlerPc = handlerPc;
this.catchType = catchType;
}
};
/**
* A MethodInfo object contains information about a particular method
* in the class being generated. This class mirrors the data items of
* the "method_info" structure of the class file format (see JVMS 4.6).
*/
private class MethodInfo {
public int accessFlags;
public String name;
public String descriptor;
public short maxStack;
public short maxLocals;
public ByteArrayOutputStream code = new ByteArrayOutputStream();
public List exceptionTable = new ArrayList();
public short[] declaredExceptions;
public MethodInfo(String name, String descriptor, int accessFlags) {
this.name = name;
this.descriptor = descriptor;
this.accessFlags = accessFlags;
/*
* Make sure that constant pool indexes are reserved for the
* following items before starting to write the final class file.
*/
cp.getUtf8(name);
cp.getUtf8(descriptor);
cp.getUtf8("Code");
cp.getUtf8("Exceptions");
}
public void write(DataOutputStream out) throws IOException {
/*
* Write all the items of the "method_info" structure.
* See JVMS section 4.6.
*/
// u2 access_flags;
out.writeShort(accessFlags);
// u2 name_index;
out.writeShort(cp.getUtf8(name));
// u2 descriptor_index;
out.writeShort(cp.getUtf8(descriptor));
// u2 attributes_count;
out.writeShort(2); // (two method_info attributes:)
// Write "Code" attribute. See JVMS section 4.7.3.
// u2 attribute_name_index;
out.writeShort(cp.getUtf8("Code"));
// u4 attribute_length;
out.writeInt(12 + code.size() + 8 * exceptionTable.size());
// u2 max_stack;
out.writeShort(maxStack);
// u2 max_locals;
out.writeShort(maxLocals);
// u2 code_length;
out.writeInt(code.size());
// u1 code[code_length];
code.writeTo(out);
// u2 exception_table_length;
out.writeShort(exceptionTable.size());
for (Iterator iter = exceptionTable.iterator(); iter.hasNext();) {
ExceptionTableEntry e = (ExceptionTableEntry) iter.next();
// u2 start_pc;
out.writeShort(e.startPc);
// u2 end_pc;
out.writeShort(e.endPc);
// u2 handler_pc;
out.writeShort(e.handlerPc);
// u2 catch_type;
out.writeShort(e.catchType);
}
// u2 attributes_count;
out.writeShort(0);
// write "Exceptions" attribute. See JVMS section 4.7.4.
// u2 attribute_name_index;
out.writeShort(cp.getUtf8("Exceptions"));
// u4 attributes_length;
out.writeInt(2 + 2 * declaredExceptions.length);
// u2 number_of_exceptions;
out.writeShort(declaredExceptions.length);
// u2 exception_index_table[number_of_exceptions];
for (int i = 0; i < declaredExceptions.length; i++) {
out.writeShort(declaredExceptions[i]);
}
}
}
/**
* A ProxyMethod object represents a proxy method in the proxy class
* being generated: a method whose implementation will encode and
* dispatch invocations to the proxy instance's invocation handler.
*/
private class ProxyMethod {
public String methodName;
public Class[] parameterTypes;
public Class returnType;
public Class[] exceptionTypes;
public Class fromClass;
public String methodFieldName;
private ProxyMethod(String methodName, Class[] parameterTypes,
Class returnType, Class[] exceptionTypes,
Class fromClass, String methodFieldName)
{
this.methodName = methodName;
this.parameterTypes = parameterTypes;
this.returnType = returnType;
this.exceptionTypes = exceptionTypes;
this.fromClass = fromClass;
this.methodFieldName = methodFieldName;
}
/**
* Return a MethodInfo object for this method, including generating
* the code and exception table entry.
*/
private MethodInfo generateMethod() throws IOException {
String desc = getMethodDescriptor(parameterTypes, returnType);
MethodInfo minfo = new MethodInfo(methodName, desc,
RuntimeConstants.ACC_PUBLIC | RuntimeConstants.ACC_FINAL);
int[] parameterSlot = new int[parameterTypes.length];
int nextSlot = 1;
for (int i = 0; i < parameterSlot.length; i++) {
parameterSlot[i] = nextSlot;
nextSlot += getWordsPerType(parameterTypes[i]);
}
int localSlot0 = nextSlot;
short pc, tryBegin = 0, tryEnd;
DataOutputStream out = new DataOutputStream(minfo.code);
code_aload(0, out);
out.writeByte(RuntimeConstants.opc_getfield);
out.writeShort(cp.getFieldRef(
// dotToSlash(className),
superclassName,
handlerFieldName, "Ljava/lang/reflect/InvocationHandler;"));
code_aload(0, out);
out.writeByte(RuntimeConstants.opc_getstatic);
out.writeShort(cp.getFieldRef(
dotToSlash(className),
methodFieldName, "Ljava/lang/reflect/Method;"));
if (parameterTypes.length > 0) {
code_ipush(parameterTypes.length, out);
out.writeByte(RuntimeConstants.opc_anewarray);
out.writeShort(cp.getClass("java/lang/Object"));
for (int i = 0; i < parameterTypes.length; i++) {
out.writeByte(RuntimeConstants.opc_dup);
code_ipush(i, out);
codeWrapArgument(parameterTypes[i], parameterSlot[i], out);
out.writeByte(RuntimeConstants.opc_aastore);
}
} else {
out.writeByte(RuntimeConstants.opc_aconst_null);
}
out.writeByte(RuntimeConstants.opc_invokeinterface);
out.writeShort(cp.getInterfaceMethodRef(
"java/lang/reflect/InvocationHandler",
"invoke",
"(Ljava/lang/Object;Ljava/lang/reflect/Method;" +
"[Ljava/lang/Object;)Ljava/lang/Object;"));
out.writeByte(4);
out.writeByte(0);
if (returnType == void.class) {
out.writeByte(RuntimeConstants.opc_pop);
out.writeByte(RuntimeConstants.opc_return);
} else {
codeUnwrapReturnValue(returnType, out);
}
tryEnd = pc = (short) minfo.code.size();
List catchList = computeUniqueCatchList(exceptionTypes);
if (catchList.size() > 0) {
for (Iterator iter = catchList.iterator(); iter.hasNext();) {
Class ex = (Class) iter.next();
minfo.exceptionTable.add(new ExceptionTableEntry(
tryBegin, tryEnd, pc,
cp.getClass(dotToSlash(ex.getName()))));
}
out.writeByte(RuntimeConstants.opc_athrow);
pc = (short) minfo.code.size();
minfo.exceptionTable.add(new ExceptionTableEntry(
tryBegin, tryEnd, pc, cp.getClass("java/lang/Throwable")));
code_astore(localSlot0, out);
out.writeByte(RuntimeConstants.opc_new);
out.writeShort(cp.getClass(
"java/lang/reflect/UndeclaredThrowableException"));
out.writeByte(RuntimeConstants.opc_dup);
code_aload(localSlot0, out);
out.writeByte(RuntimeConstants.opc_invokespecial);
out.writeShort(cp.getMethodRef(
"java/lang/reflect/UndeclaredThrowableException",
"<init>", "(Ljava/lang/Throwable;)V"));
out.writeByte(RuntimeConstants.opc_athrow);
}
minfo.maxStack = 10;
minfo.maxLocals = (short) (localSlot0 + 1);
minfo.declaredExceptions = new short[exceptionTypes.length];
for (int i = 0; i < exceptionTypes.length; i++) {
minfo.declaredExceptions[i] = cp.getClass(
dotToSlash(exceptionTypes[i].getName()));
}
return minfo;
}
/**
* Generate code for wrapping a parameter of the given type and whose
* value can be found at the specified local variable index to be
* passed to the invocation handler's "invoke" method (as an Object).
* The code is written to the supplied stream.
*/
private void codeWrapArgument(Class type, int slot,
DataOutputStream out)
throws IOException
{
if (type.isPrimitive()) {
PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(type);
out.writeByte(RuntimeConstants.opc_new);
out.writeShort(cp.getClass(prim.wrapperClassName));
out.writeByte(RuntimeConstants.opc_dup);
if (type == int.class ||
type == boolean.class ||
type == byte.class ||
type == char.class ||
type == short.class)
{
code_iload(slot, out);
} else if (type == long.class) {
code_lload(slot, out);
} else if (type == float.class) {
code_fload(slot, out);
} else if (type == double.class) {
code_dload(slot, out);
} else {
_assert(false);
}
out.writeByte(RuntimeConstants.opc_invokespecial);
out.writeShort(cp.getMethodRef(
prim.wrapperClassName,
"<init>", prim.wrapperConstructorDesc));
} else {
code_aload(slot, out);
}
}
/**
* Generate code for unwrapping the return value of the given type
* from the invocation handler's "invoke" method (of type Object) to
* its correct type. The code is written to the supplied stream.
*/
private void codeUnwrapReturnValue(Class type, DataOutputStream out)
throws IOException
{
if (type.isPrimitive()) {
PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(type);
out.writeByte(RuntimeConstants.opc_checkcast);
out.writeShort(cp.getClass(prim.wrapperClassName));
out.writeByte(RuntimeConstants.opc_invokevirtual);
out.writeShort(cp.getMethodRef(
prim.wrapperClassName,
prim.unwrapMethodName, prim.unwrapMethodDesc));
if (type == int.class ||
type == boolean.class ||
type == byte.class ||
type == char.class ||
type == short.class)
{
out.writeByte(RuntimeConstants.opc_ireturn);
} else if (type == long.class) {
out.writeByte(RuntimeConstants.opc_lreturn);
} else if (type == float.class) {
out.writeByte(RuntimeConstants.opc_freturn);
} else if (type == double.class) {
out.writeByte(RuntimeConstants.opc_dreturn);
} else {
_assert(false);
}
} else {
out.writeByte(RuntimeConstants.opc_checkcast);
out.writeShort(cp.getClass(dotToSlash(type.getName())));
out.writeByte(RuntimeConstants.opc_areturn);
}
}
/**
* Generate code for initializing the static field that stores
* the Method object for this proxy method. The code is written
* to the supplied stream.
*/
private void codeFieldInitialization(DataOutputStream out)
throws IOException
{
codeClassForName(fromClass, out);
code_ldc(cp.getString(methodName), out);
code_ipush(parameterTypes.length, out);
out.writeByte(RuntimeConstants.opc_anewarray);
out.writeShort(cp.getClass("java/lang/Class"));
for (int i = 0; i < parameterTypes.length; i++) {
out.writeByte(RuntimeConstants.opc_dup);
code_ipush(i, out);
if (parameterTypes[i].isPrimitive()) {
PrimitiveTypeInfo prim =
PrimitiveTypeInfo.get(parameterTypes[i]);
out.writeByte(RuntimeConstants.opc_getstatic);
out.writeShort(cp.getFieldRef(
prim.wrapperClassName, "TYPE", "Ljava/lang/Class;"));
} else {
codeClassForName(parameterTypes[i], out);
}
out.writeByte(RuntimeConstants.opc_aastore);
}
out.writeByte(RuntimeConstants.opc_invokevirtual);
out.writeShort(cp.getMethodRef(
"java/lang/Class",
"getMethod",
"(Ljava/lang/String;[Ljava/lang/Class;)" +
"Ljava/lang/reflect/Method;"));
out.writeByte(RuntimeConstants.opc_putstatic);
out.writeShort(cp.getFieldRef(
dotToSlash(className),
methodFieldName, "Ljava/lang/reflect/Method;"));
}
}
/**
* Generate the constructor method for the proxy class.
*/
private MethodInfo generateConstructor() throws IOException {
MethodInfo minfo = new MethodInfo(
"<init>", "(Ljava/lang/reflect/InvocationHandler;)V",
RuntimeConstants.ACC_PUBLIC);
DataOutputStream out = new DataOutputStream(minfo.code);
code_aload(0, out);
code_aload(1, out);
out.writeByte(RuntimeConstants.opc_invokespecial);
out.writeShort(cp.getMethodRef(
superclassName,
"<init>", "(Ljava/lang/reflect/InvocationHandler;)V"));
// code_aload(0, out);
//
// code_aload(1, out);
//
// out.writeByte(RuntimeConstants.opc_putfield);
// out.writeShort(cp.getFieldRef(
// dotToSlash(className),
// handlerFieldName, "Ljava/lang/reflect/InvocationHandler;"));
out.writeByte(RuntimeConstants.opc_return);
minfo.maxStack = 10;
minfo.maxLocals = 2;
minfo.declaredExceptions = new short[0];
return minfo;
}
/**
* Generate the static initializer method for the proxy class.
*/
private MethodInfo generateStaticInitializer() throws IOException {
MethodInfo minfo = new MethodInfo(
"<clinit>", "()V", RuntimeConstants.ACC_STATIC);
int localSlot0 = 1;
short pc, tryBegin = 0, tryEnd;
DataOutputStream out = new DataOutputStream(minfo.code);
for (Iterator iter = proxyMethods.values().iterator();
iter.hasNext();)
{
ProxyMethod pm = (ProxyMethod) iter.next();
pm.codeFieldInitialization(out);
}
out.writeByte(RuntimeConstants.opc_return);
tryEnd = pc = (short) minfo.code.size();
minfo.exceptionTable.add(new ExceptionTableEntry(
tryBegin, tryEnd, pc,
cp.getClass("java/lang/NoSuchMethodException")));
code_astore(localSlot0, out);
out.writeByte(RuntimeConstants.opc_new);
out.writeShort(cp.getClass("java/lang/NoSuchMethodError"));
out.writeByte(RuntimeConstants.opc_dup);
code_aload(localSlot0, out);
out.writeByte(RuntimeConstants.opc_invokevirtual);
out.writeShort(cp.getMethodRef(
"java/lang/Throwable", "getMessage", "()Ljava/lang/String;"));
out.writeByte(RuntimeConstants.opc_invokespecial);
out.writeShort(cp.getMethodRef(
"java/lang/NoSuchMethodError", "<init>", "(Ljava/lang/String;)V"));
out.writeByte(RuntimeConstants.opc_athrow);
pc = (short) minfo.code.size();
minfo.exceptionTable.add(new ExceptionTableEntry(
tryBegin, tryEnd, pc,
cp.getClass("java/lang/ClassNotFoundException")));
code_astore(localSlot0, out);
out.writeByte(RuntimeConstants.opc_new);
out.writeShort(cp.getClass("java/lang/NoClassDefFoundError"));
out.writeByte(RuntimeConstants.opc_dup);
code_aload(localSlot0, out);
out.writeByte(RuntimeConstants.opc_invokevirtual);
out.writeShort(cp.getMethodRef(
"java/lang/Throwable", "getMessage", "()Ljava/lang/String;"));
out.writeByte(RuntimeConstants.opc_invokespecial);
out.writeShort(cp.getMethodRef(
"java/lang/NoClassDefFoundError",
"<init>", "(Ljava/lang/String;)V"));
out.writeByte(RuntimeConstants.opc_athrow);
minfo.maxStack = 10;
minfo.maxLocals = (short) (localSlot0 + 1);
minfo.declaredExceptions = new short[0];
return minfo;
}
/*
* =============== Code Generation Utility Methods ===============
*/
/*
* The following methods generate code for the load or store operation
* indicated by their name for the given local variable. The code is
* written to the supplied stream.
*/
private void code_iload(int lvar, DataOutputStream out)
throws IOException
{
codeLocalLoadStore(lvar,
RuntimeConstants.opc_iload, RuntimeConstants.opc_iload_0, out);
}
private void code_lload(int lvar, DataOutputStream out)
throws IOException
{
codeLocalLoadStore(lvar,
RuntimeConstants.opc_lload, RuntimeConstants.opc_lload_0, out);
}
private void code_fload(int lvar, DataOutputStream out)
throws IOException
{
codeLocalLoadStore(lvar,
RuntimeConstants.opc_fload, RuntimeConstants.opc_fload_0, out);
}
private void code_dload(int lvar, DataOutputStream out)
throws IOException
{
codeLocalLoadStore(lvar,
RuntimeConstants.opc_dload, RuntimeConstants.opc_dload_0, out);
}
private void code_aload(int lvar, DataOutputStream out)
throws IOException
{
codeLocalLoadStore(lvar,
RuntimeConstants.opc_aload, RuntimeConstants.opc_aload_0, out);
}
private void code_istore(int lvar, DataOutputStream out)
throws IOException
{
codeLocalLoadStore(lvar,
RuntimeConstants.opc_istore, RuntimeConstants.opc_istore_0, out);
}
/* 6220850
private void code_lstore(int lvar, DataOutputStream out)
throws IOException
{
codeLocalLoadStore(lvar,
RuntimeConstants.opc_lstore, RuntimeConstants.opc_lstore_0, out);
}
private void code_fstore(int lvar, DataOutputStream out)
throws IOException
{
codeLocalLoadStore(lvar,
RuntimeConstants.opc_fstore, RuntimeConstants.opc_fstore_0, out);
}
private void code_dstore(int lvar, DataOutputStream out)
throws IOException
{
codeLocalLoadStore(lvar,
RuntimeConstants.opc_dstore, RuntimeConstants.opc_dstore_0, out);
}
6220850 */
private void code_astore(int lvar, DataOutputStream out)
throws IOException
{
codeLocalLoadStore(lvar,
RuntimeConstants.opc_astore, RuntimeConstants.opc_astore_0, out);
}
/**
* Generate code for a load or store instruction for the given local
* variable. The code is written to the supplied stream.
*
* "opcode" indicates the opcode form of the desired load or store
* instruction that takes an explicit local variable index, and
* "opcode_0" indicates the corresponding form of the instruction
* with the implicit index 0.
*/
private void codeLocalLoadStore(int lvar, int opcode, int opcode_0,
DataOutputStream out)
throws IOException
{
_assert(lvar >= 0 && lvar <= 0xFFFF);
if (lvar <= 3) {
out.writeByte(opcode_0 + lvar);
} else if (lvar <= 0xFF) {
out.writeByte(opcode);
out.writeByte(lvar & 0xFF);
} else {
/*
* Use the "wide" instruction modifier for local variable
* indexes that do not fit into an unsigned byte.
*/
out.writeByte(RuntimeConstants.opc_wide);
out.writeByte(opcode);
out.writeShort(lvar & 0xFFFF);
}
}
/**
* Generate code for an "ldc" instruction for the given constant pool
* index (the "ldc_w" instruction is used if the index does not fit
* into an unsigned byte). The code is written to the supplied stream.
*/
private void code_ldc(int index, DataOutputStream out)
throws IOException
{
_assert(index >= 0 && index <= 0xFFFF);
if (index <= 0xFF) {
out.writeByte(RuntimeConstants.opc_ldc);
out.writeByte(index & 0xFF);
} else {
out.writeByte(RuntimeConstants.opc_ldc_w);
out.writeShort(index & 0xFFFF);
}
}
/**
* Generate code to push a constant integer value on to the operand
* stack, using the "iconst_<i>", "bipush", or "sipush" instructions
* depending on the size of the value. The code is written to the
* supplied stream.
*/
private void code_ipush(int value, DataOutputStream out)
throws IOException
{
if (value >= -1 && value <= 5) {
out.writeByte(RuntimeConstants.opc_iconst_0 + value);
} else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
out.writeByte(RuntimeConstants.opc_bipush);
out.writeByte(value & 0xFF);
} else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {
out.writeByte(RuntimeConstants.opc_sipush);
out.writeShort(value & 0xFFFF);
} else {
_assert(false);
}
}
/**
* Generate code to invoke the Class.forName with the name of the given
* class to get its Class object at runtime. The code is written to
* the supplied stream. Note that the code generated by this method
* may caused the checked ClassNotFoundException to be thrown.
*/
private void codeClassForName(Class cl, DataOutputStream out)
throws IOException
{
code_ldc(cp.getString(cl.getName()), out);
out.writeByte(RuntimeConstants.opc_invokestatic);
out.writeShort(cp.getMethodRef(
"java/lang/Class",
"forName", "(Ljava/lang/String;)Ljava/lang/Class;"));
}
/*
* ==================== General Utility Methods ====================
*/
/**
* Assert that an assertion is true: throw InternalError if it is not.
*/
private static void _assert(boolean assertion) {
if (assertion != true) {
throw new InternalError("assertion failure");
}
}
/**
* Convert a fully qualified class name that uses '.' as the package
* separator, the external representation used by the Java language
* and APIs, to a fully qualified class name that uses '/' as the
* package separator, the representation used in the class file
* format (see JVMS section 4.2).
*/
private static String dotToSlash(String name) {
return name.replace('.', '/');
}
/**
* Return the "method descriptor" string for a method with the given
* parameter types and return type. See JVMS section 4.3.3.
*/
private static String getMethodDescriptor(Class[] parameterTypes,
Class returnType)
{
return getParameterDescriptors(parameterTypes) +
((returnType == void.class) ? "V" : getFieldType(returnType));
}
/**
* Return the list of "parameter descriptor" strings enclosed in
* parentheses corresponding to the given parameter types (in other
* words, a method descriptor without a return descriptor). This
* string is useful for constructing string keys for methods without
* regard to their return type.
*/
private static String getParameterDescriptors(Class[] parameterTypes) {
StringBuffer desc = new StringBuffer("(");
for (int i = 0; i < parameterTypes.length; i++) {
desc.append(getFieldType(parameterTypes[i]));
}
desc.append(')');
return desc.toString();
}
/**
* Return the "field type" string for the given type, appropriate for
* a field descriptor, a parameter descriptor, or a return descriptor
* other than "void". See JVMS section 4.3.2.
*/
private static String getFieldType(Class type) {
if (type.isPrimitive()) {
return PrimitiveTypeInfo.get(type).baseTypeString;
} else if (type.isArray()) {
/*
* According to JLS 20.3.2, the getName() method on Class does
* return the VM type descriptor format for array classes (only);
* using that should be quicker than the otherwise obvious code:
*
* return "[" + getTypeDescriptor(type.getComponentType());
*/
return type.getName().replace('.', '/');
} else {
return "L" + dotToSlash(type.getName()) + ";";
}
}
/**
* Return the number of abstract "words", or consecutive local variable
* indexes, required to contain a value of the given type. See JVMS
* section 3.6.1.
*
* Note that the original version of the JVMS contained a definition of
* this abstract notion of a "word" in section 3.4, but that definition
* was removed for the second edition.
*/
private static int getWordsPerType(Class type) {
if (type == long.class || type == double.class) {
return 2;
} else {
return 1;
}
}
/**
* Add to the given list all of the types in the "from" array that
* are not already contained in the liast and are assignable to at
* least one of the types in the "with" array.
*
* This method is useful for computing the greatest common set of
* declared exceptions from duplicate methods inherited from
* different interfaces.
*/
private static void collectCompatibleTypes(Class[] from, Class[] with,
List list)
{
for (int i = 0; i < from.length; i++) {
if (!list.contains(from[i])) {
for (int j = 0; j < with.length; j++) {
if (with[j].isAssignableFrom(from[i])) {
list.add(from[i]);
break;
}
}
}
}
}
/**
* Given the exceptions declared in the throws clause of a proxy method,
* compute the exceptions that need to be caught from the invocation
* handler's invoke method and rethrown intact in the method's
* implementation before catching other Throwables and wrapping them
* in UndeclaredThrowableExceptions.
*
* The exceptions to be caught are returned in a List object. Each
* exception in the returned list is guaranteed to not be a subclass of
* any of the other exceptions in the list, so the catch blocks for
* these exceptions may be generated in any order relative to each other.
*
* Error and RuntimeException are each always contained by the returned
* list (if none of their superclasses are contained), since those
* unchecked exceptions should always be rethrown intact, and thus their
* subclasses will never appear in the returned list.
*
* The returned List will be empty if java.lang.Throwable is in the
* given list of declared exceptions, indicating that no exceptions
* need to be caught.
*/
private static List computeUniqueCatchList(Class[] exceptions) {
List uniqueList = new ArrayList(); // unique exceptions to catch
uniqueList.add(Error.class); // always catch/rethrow these
uniqueList.add(RuntimeException.class);
nextException:
for (int i = 0; i < exceptions.length; i++) {
Class ex = exceptions[i];
if (ex.isAssignableFrom(Throwable.class)) {
/*
* If Throwable is declared to be thrown by the proxy method,
* then no catch blocks are necessary, because the invoke
* can, at most, throw Throwable anyway.
*/
uniqueList.clear();
break;
} else if (!Throwable.class.isAssignableFrom(ex)) {
/*
* Ignore types that cannot be thrown by the invoke method.
*/
continue;
}
/*
* Compare this exception against the current list of
* exceptions that need to be caught:
*/
for (int j = 0; j < uniqueList.size();) {
Class ex2 = (Class) uniqueList.get(j);
if (ex2.isAssignableFrom(ex)) {
/*
* if a superclass of this exception is already on
* the list to catch, then ignore this one and continue;
*/
continue nextException;
} else if (ex.isAssignableFrom(ex2)) {
/*
* if a subclass of this exception is on the list
* to catch, then remove it;
*/
uniqueList.remove(j);
} else {
j++; // else continue comparing.
}
}
// This exception is unique (so far): add it to the list to catch.
uniqueList.add(ex);
}
return uniqueList;
}
/**
* A PrimitiveTypeInfo object contains assorted information about
* a primitive type in its public fields. The struct for a particular
* primitive type can be obtained using the static "get" method.
*/
private static class PrimitiveTypeInfo {
/** "base type" used in various descriptors (see JVMS section 4.3.2) */
public String baseTypeString;
/** name of corresponding wrapper class */
public String wrapperClassName;
/** method descriptor for wrapper class constructor */
public String wrapperConstructorDesc;
/** name of wrapper class method for retrieving primitive value */
public String unwrapMethodName;
/** descriptor of same method */
public String unwrapMethodDesc;
private static Map table = new HashMap(11);
static {
table.put(int.class, new PrimitiveTypeInfo(
"I", "java/lang/Integer", "(I)V", "intValue", "()I"));
table.put(boolean.class, new PrimitiveTypeInfo(
"Z", "java/lang/Boolean", "(Z)V", "booleanValue", "()Z"));
table.put(byte.class, new PrimitiveTypeInfo(
"B", "java/lang/Byte", "(B)V", "byteValue", "()B"));
table.put(char.class, new PrimitiveTypeInfo(
"C", "java/lang/Character", "(C)V", "charValue", "()C"));
table.put(short.class, new PrimitiveTypeInfo(
"S", "java/lang/Short", "(S)V", "shortValue", "()S"));
table.put(long.class, new PrimitiveTypeInfo(
"J", "java/lang/Long", "(J)V", "longValue", "()J"));
table.put(float.class, new PrimitiveTypeInfo(
"F", "java/lang/Float", "(F)V", "floatValue", "()F"));
table.put(double.class, new PrimitiveTypeInfo(
"D", "java/lang/Double", "(D)V", "doubleValue", "()D"));
}
private PrimitiveTypeInfo(String baseTypeString,
String wrapperClassName,
String wrapperConstructorDesc,
String unwrapMethodName,
String unwrapMethodDesc)
{
this.baseTypeString = baseTypeString;
this.wrapperClassName = wrapperClassName;
this.wrapperConstructorDesc = wrapperConstructorDesc;
this.unwrapMethodName = unwrapMethodName;
this.unwrapMethodDesc = unwrapMethodDesc;
}
public static PrimitiveTypeInfo get(Class cl) {
return (PrimitiveTypeInfo) table.get(cl);
}
}
/**
* A ConstantPool object represents the constant pool of a class file
* being generated. This representation of a constant pool is designed
* specifically for use by ProxyGenerator; in particular, it assumes
* that constant pool entries will not need to be resorted (for example,
* by their type, as the Java compiler does), so that the final index
* value can be assigned and used when an entry is first created.
*
* Note that new entries cannot be created after the constant pool has
* been written to a class file. To prevent such logic errors, a
* ConstantPool instance can be marked "read only", so that further
* attempts to add new entries will fail with a runtime exception.
*
* See JVMS section 4.4 for more information about the constant pool
* of a class file.
*/
private static class ConstantPool {
/**
* list of constant pool entries, in constant pool index order.
*
* This list is used when writing the constant pool to a stream
* and for assigning the next index value. Note that element 0
* of this list corresponds to constant pool index 1.
*/
private List pool = new ArrayList(32);
/**
* maps constant pool data of all types to constant pool indexes.
*
* This map is used to look up the index of an existing entry for
* values of all types.
*/
private Map map = new HashMap(16);
/** true if no new constant pool entries may be added */
private boolean readOnly = false;
/**
* Get or assign the index for a CONSTANT_Utf8 entry.
*/
public short getUtf8(String s) {
if (s == null) {
throw new NullPointerException();
}
return getValue(s);
}
/**
* Get or assign the index for a CONSTANT_Integer entry.
*/
public short getInteger(int i) {
return getValue(new Integer(i));
}
/**
* Get or assign the index for a CONSTANT_Float entry.
*/
public short getFloat(float f) {
return getValue(new Float(f));
}
/**
* Get or assign the index for a CONSTANT_Long entry.
*/
public short getLong(long l) {
return getValue(new Long(l));
}
/**
* Get or assign the index for a CONSTANT_Double entry.
*/
public short getDouble(double d) {
return getValue(new Double(d));
}
/**
* Get or assign the index for a CONSTANT_Class entry.
*/
public short getClass(String name) {
short utf8Index = getUtf8(name);
return getIndirect(new IndirectEntry(
RuntimeConstants.CONSTANT_CLASS, utf8Index));
}
/**
* Get or assign the index for a CONSTANT_String entry.
*/
public short getString(String s) {
short utf8Index = getUtf8(s);
return getIndirect(new IndirectEntry(
RuntimeConstants.CONSTANT_STRING, utf8Index));
}
/**
* Get or assign the index for a CONSTANT_FieldRef entry.
*/
public short getFieldRef(String className,
String name, String descriptor)
{
short classIndex = getClass(className);
short nameAndTypeIndex = getNameAndType(name, descriptor);
return getIndirect(new IndirectEntry(
RuntimeConstants.CONSTANT_FIELD,
classIndex, nameAndTypeIndex));
}
/**
* Get or assign the index for a CONSTANT_MethodRef entry.
*/
public short getMethodRef(String className,
String name, String descriptor)
{
short classIndex = getClass(className);
short nameAndTypeIndex = getNameAndType(name, descriptor);
return getIndirect(new IndirectEntry(
RuntimeConstants.CONSTANT_METHOD,
classIndex, nameAndTypeIndex));
}
/**
* Get or assign the index for a CONSTANT_InterfaceMethodRef entry.
*/
public short getInterfaceMethodRef(String className, String name,
String descriptor)
{
short classIndex = getClass(className);
short nameAndTypeIndex = getNameAndType(name, descriptor);
return getIndirect(new IndirectEntry(
RuntimeConstants.CONSTANT_INTERFACEMETHOD,
classIndex, nameAndTypeIndex));
}
/**
* Get or assign the index for a CONSTANT_NameAndType entry.
*/
public short getNameAndType(String name, String descriptor) {
short nameIndex = getUtf8(name);
short descriptorIndex = getUtf8(descriptor);
return getIndirect(new IndirectEntry(
RuntimeConstants.CONSTANT_NAMEANDTYPE,
nameIndex, descriptorIndex));
}
/**
* Set this ConstantPool instance to be "read only".
*
* After this method has been called, further requests to get
* an index for a non-existent entry will cause an InternalError
* to be thrown instead of creating of the entry.
*/
public void setReadOnly() {
readOnly = true;
}
/**
* Write this constant pool to a stream as part of
* the class file format.
*
* This consists of writing the "constant_pool_count" and
* "constant_pool[]" items of the "ClassFile" structure, as
* described in JVMS section 4.1.
*/
public void write(OutputStream out) throws IOException {
DataOutputStream dataOut = new DataOutputStream(out);
// constant_pool_count: number of entries plus one
dataOut.writeShort(pool.size() + 1);
for (Iterator iter = pool.iterator(); iter.hasNext();) {
Entry e = (Entry) iter.next();
e.write(dataOut);
}
}
/**
* Add a new constant pool entry and return its index.
*/
private short addEntry(Entry entry) {
pool.add(entry);
return (short) pool.size();
}
/**
* Get or assign the index for an entry of a type that contains
* a direct value. The type of the given object determines the
* type of the desired entry as follows:
*
* java.lang.String CONSTANT_Utf8
* java.lang.Integer CONSTANT_Integer
* java.lang.Float CONSTANT_Float
* java.lang.Long CONSTANT_Long
* java.lang.Double CONSTANT_DOUBLE
*/
private short getValue(Object key) {
Short index = (Short) map.get(key);
if (index != null) {
return index.shortValue();
} else {
if (readOnly) {
throw new InternalError(
"late constant pool addition: " + key);
}
short i = addEntry(new ValueEntry(key));
map.put(key, new Short(i));
return i;
}
}
/**
* Get or assign the index for an entry of a type that contains
* references to other constant pool entries.
*/
private short getIndirect(IndirectEntry e) {
Short index = (Short) map.get(e);
if (index != null) {
return index.shortValue();
} else {
if (readOnly) {
throw new InternalError("late constant pool addition");
}
short i = addEntry(e);
map.put(e, new Short(i));
return i;
}
}
/**
* Entry is the abstact superclass of all constant pool entry types
* that can be stored in the "pool" list; its purpose is to define a
* common method for writing constant pool entries to a class file.
*/
private static abstract class Entry {
public abstract void write(DataOutputStream out)
throws IOException;
}
/**
* ValueEntry represents a constant pool entry of a type that
* contains a direct value (see the comments for the "getValue"
* method for a list of such types).
*
* ValueEntry objects are not used as keys for their entries in the
* Map "map", so no useful hashCode or equals methods are defined.
*/
private static class ValueEntry extends Entry {
private Object value;
public ValueEntry(Object value) {
this.value = value;
}
public void write(DataOutputStream out) throws IOException {
if (value instanceof String) {
out.writeByte(RuntimeConstants.CONSTANT_UTF8);
out.writeUTF((String) value);
} else if (value instanceof Integer) {
out.writeByte(RuntimeConstants.CONSTANT_INTEGER);
out.writeInt(((Integer) value).intValue());
} else if (value instanceof Float) {
out.writeByte(RuntimeConstants.CONSTANT_FLOAT);
out.writeFloat(((Float) value).floatValue());
} else if (value instanceof Long) {
out.writeByte(RuntimeConstants.CONSTANT_LONG);
out.writeLong(((Long) value).longValue());
} else if (value instanceof Double) {
out.writeDouble(RuntimeConstants.CONSTANT_DOUBLE);
out.writeDouble(((Double) value).doubleValue());
} else {
throw new InternalError("bogus value entry: " + value);
}
}
}
/**
* IndirectEntry represents a constant pool entry of a type that
* references other constant pool entries, i.e., the following types:
*
* CONSTANT_Class, CONSTANT_String, CONSTANT_Fieldref,
* CONSTANT_Methodref, CONSTANT_InterfaceMethodref, and
* CONSTANT_NameAndType.
*
* Each of these entry types contains either one or two indexes of
* other constant pool entries.
*
* IndirectEntry objects are used as the keys for their entries in
* the Map "map", so the hashCode and equals methods are overridden
* to allow matching.
*/
private static class IndirectEntry extends Entry {
private int tag;
private short index0;
private short index1;
/**
* Construct an IndirectEntry for a constant pool entry type
* that contains one index of another entry.
*/
public IndirectEntry(int tag, short index) {
this.tag = tag;
this.index0 = index;
this.index1 = 0;
}
/**
* Construct an IndirectEntry for a constant pool entry type
* that contains two indexes for other entries.
*/
public IndirectEntry(int tag, short index0, short index1) {
this.tag = tag;
this.index0 = index0;
this.index1 = index1;
}
public void write(DataOutputStream out) throws IOException {
out.writeByte(tag);
out.writeShort(index0);
/*
* If this entry type contains two indexes, write
* out the second, too.
*/
if (tag == RuntimeConstants.CONSTANT_FIELD ||
tag == RuntimeConstants.CONSTANT_METHOD ||
tag == RuntimeConstants.CONSTANT_INTERFACEMETHOD ||
tag == RuntimeConstants.CONSTANT_NAMEANDTYPE)
{
out.writeShort(index1);
}
}
public int hashCode() {
return tag + index0 + index1;
}
public boolean equals(Object obj) {
if (obj instanceof IndirectEntry) {
IndirectEntry other = (IndirectEntry) obj;
if (tag == other.tag &&
index0 == other.index0 && index1 == other.index1)
{
return true;
}
}
return false;
}
}
}
}