/**************************************************************************
* Copyright (c) 2003 by Acunia N.V. All rights reserved. *
* *
* This software is copyrighted by and is the sole property of Acunia N.V. *
* and its licensors, if any. All rights, title, ownership, or other *
* interests in the software remain the property of Acunia N.V. and its *
* licensors, if any. *
* *
* This software may only be used in accordance with the corresponding *
* license agreement. Any unauthorized use, duplication, transmission, *
* distribution or disclosure of this software is expressly forbidden. *
* *
* This Copyright notice may not be removed or modified without prior *
* written consent of Acunia N.V. *
* *
* Acunia N.V. reserves the right to modify this software without notice. *
* *
* Acunia N.V. *
* Philips site 5, box 3 info@acunia.com *
* 3001 Leuven http://www.acunia.com *
* Belgium - EUROPE *
**************************************************************************/
/*
** $Id: Proxy.java,v 1.3 2006/03/29 09:27:14 cvs Exp $
*/
package java.lang.reflect;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Vector;
/**
This is a factory for dynamic proxy classes. Dynamic proxy classes are
created on demand, and are immortal(*); a dynamic proxy class is uniquely
identified by a classloader and the (ordered) list of interfaces which
it implements.
<p>
or found using the static method <code>getProxyClass(ClassLoader cl, Class[] interfaces)</code>.
<p>
(*) There seems to be no way to delete a dynamic proxy, so it will stay in
the list of known proxies for the lifetime of the VM.
*/
public class Proxy implements java.io.Serializable {
private static final long serialVersionUID = -2222568056686623797L;
private static final int CLASS_ACCESS_FLAGS = Modifier.SUPER + Modifier.FINAL + Modifier.PUBLIC;
private static final int INIT_ACCESS_FLAGS = Modifier.PUBLIC;
/**
Hashtable used to map classloader x interfaces -> dynamic proxy class.
The key is an ArrayList in which the first element is the classloader and
the remaining elements are the interfaces which are implemented; the
corresponding value is the Class object for the dynamic proxy class.
We call such an ArrayList a "proxyspec".
<p>
Method getProxyClass synchronises on this object while creating a new
proxy class, so we use this lock to protect other static structures.
*/
private static Hashtable proxies = new Hashtable();
private static Method method_defineProxyClass;
private static Method method_getProxyFlag;
private static Method method_hashCode;
private static Method method_equals;
private static Method method_toString;
private static class ClassFile {
private ByteArrayOutputStream s;
private Vector constants;
ClassFile() {
s = new ByteArrayOutputStream();
constants = new Vector();
}
/**
Write a 16-bit value to the stream in bigendian mode.
*/
void write16(int w) {
try {
s.write((w & 0x0000ff00) >> 8);
s.write(w & 0x000000ff);
}
catch (Exception e) {}
}
/**
Write an array of bytes to the stream.
*/
void write(byte[] b) {
try {
s.write(b);
}
catch (Exception e) {}
}
/**
Add an arbitrary constant to the pool.
*/
int addConstant(byte[] b) {
constants.add(b);
return constants.size();
}
/**
Add a UTF8 constant to the pool if necessary, and return the index of the
new or existing constant. (Remember constants are numbered from 1!).
<p>
(TODO: For now, we always add, even if it's a duplicate).
*/
int getUtf8Constant(String string) {
int n = constants.size();
constants.add(utf8Constant(string));
return n + 1;
}
/**
Add a class constant to the constant pool. The value returned is the index
(counting from 1) of the class constant.
*/
int addClassConstant(Class c) {
int n = constants.size();
constants.add(new byte[] {7, 0, (byte)(n + 2)});
constants.add(utf8Constant(c.getName().replace('.','/')));
return n + 1;
}
/**
Write the contents of the constant pool to the classfile.
*/
void writeConstants() {
int n = constants.size();
write16(n + 1);
try {
for (int i = 0; i < n; ++i) {
write((byte[])constants.elementAt(i));
}
}
catch (Exception e) {
}
constants = null;
}
byte[] toByteArray() {
return s.toByteArray();
}
}
static {
try {
Class class_bytearray = Class.forName("[B");
method_defineProxyClass = ClassLoader.class.getDeclaredMethod("defineProxyClass", new Class[] {String.class, class_bytearray, int.class, int.class});
method_hashCode = Object.class.getDeclaredMethod("hashCode", null);
method_equals = Object.class.getDeclaredMethod("equals", new Class[] {Object.class});
method_toString = Object.class.getDeclaredMethod("toString", null);
}
catch(Exception e) {
e.printStackTrace();
}
}
/**
The InvocationHandler for this instance.
*/
protected InvocationHandler h;
/**
When an instance of a dynamic proxy class is created, an InvocationHandler
is specified. (So the InvocationHandler belongs to the instance, not the
class).
*/
protected Proxy(InvocationHandler h){
// TODO: checks?
this.h = h;
}
/**
You can ask an instance of a dynamic proxy class for its InvocationHandler.
*/
public static InvocationHandler getInvocationHandler(Object object) throws IllegalArgumentException {
if(object instanceof Proxy){
return ((Proxy)object).h;
}
throw new IllegalArgumentException("'"+object+"' is not a Proxy");
}
/**
The namespace beginning "$Proxy" is reserved for dynamic proxy classes.
We make up the name as follows:
<pre>
$Proxy$$<hash>$$<cl>$$<i1>$$<i2>...
</pre>
where <hash> is the hashcode of the ArrayList used as the key to the
hashtable of known proxies, as a hex string; <cl> is the name of the
class of the classloader, and <i1> etc. are the names of the interfaces.
*/
private static String proxyname(Package pkg, ArrayList proxyspec) {
StringBuffer s = new StringBuffer();
if (pkg != null) {
s.append(pkg.getName());
s.append('.');
}
s.append("$Proxy$$");
s.append(Integer.toHexString(proxyspec.hashCode()));
s.append("$$");
s.append(proxyspec.get(0).getClass().getName().replace('.','_'));
int l = proxyspec.size();
for (int i = 1; i < l; ++i) {
s.append("$$");
s.append(((Class)proxyspec.get(i)).getName().replace('.','_'));
}
return s.toString();
}
/**
Write a UTF8 constant: the tag is 1, followed by the length in bytes and the
UTF8-encoded characters.
*/
static byte[] utf8Constant(String string) {
ByteArrayOutputStream temp = new ByteArrayOutputStream();
try {
byte[] b = string.getBytes("UTF8");
temp.write(1);
temp.write((b.length & 0x0000ff00) >> 8);
temp.write(b.length & 0x000000ff);
temp.write(b);
}
catch (Throwable t) {}
return temp.toByteArray();
}
/**
*/
private static String descriptor(Class c) {
if (c == void.class) {
return "V";
}
else if (c == boolean.class) {
return "Z";
}
else if (c == byte.class) {
return "B";
}
else if (c == short.class) {
return "S";
}
else if (c == char.class) {
return "C";
}
else if (c == int.class) {
return "I";
}
else if (c == float.class) {
return "F";
}
else if (c == long.class) {
return "J";
}
else if (c == double.class) {
return "D";
}
else if (c.isArray()) {
return "["+descriptor(c.getComponentType());
}
else {
return "L"+c.getName().replace('.','/')+";";
}
}
/**
Create a dynamic proxy class. We write a simple classfile in memory, and
pass it to classloader.defineProxyClass().
*/
private static Class makeproxy(Package pkg, ArrayList proxyspec) {
int numInterfaces = proxyspec.size() - 1;
int i;
String name = proxyname(pkg, proxyspec);
ClassFile classfile = new ClassFile();
// Build the constant pool. Remember where we put the class constant for the
// superclass java.lang.reflect.Proxy (super_index) and the superinterfaces
// (itf_index[]), the strings "<init>", "(Ljava/lang/reflect/InvocationHandler;)V",
// and "Code".
classfile.addConstant(new byte[] {7, 0, 2});
classfile.addConstant(utf8Constant(name.replace('.','/')));
int super_index = classfile.addClassConstant(java.lang.reflect.Proxy.class);
int init_index = classfile.getUtf8Constant("<init>");
int init_desc_index = classfile.getUtf8Constant("(Ljava/lang/reflect/InvocationHandler;)V");
int init_nat_index = classfile.addConstant(new byte[] {12, 0, (byte)init_index, 0, (byte)init_desc_index});
int init_method_index = classfile.addConstant(new byte[] {10, 0, (byte)super_index, 0, (byte)init_nat_index});
int code_tag_index = classfile.addConstant(utf8Constant("Code"));
int[] itf_index = new int[numInterfaces];
for (i = 0; i < numInterfaces; ++i) {
itf_index[i] = classfile.addClassConstant((Class)proxyspec.get(i + 1));
}
// OK, let's write out the class file.
// magic number, minor version, major version
classfile.write16(0xcafe);
classfile.write16(0xbabe);
classfile.write16(0);
classfile.write16(45);
// constant pool
classfile.writeConstants();
// access flags
classfile.write16(CLASS_ACCESS_FLAGS);
// this class
classfile.write16(1);
// super class
classfile.write16(super_index);
// interfaces
classfile.write16(numInterfaces);
for (i = 0; i < numInterfaces; ++i) {
classfile.write16(itf_index[i]);
}
// fields (there ain't no fields)
classfile.write16(0);
// methods : <init>(Ljava/lang/reflect/InvocationHandler;)V
// has one attribute, namely its Code.
classfile.write16(1);
classfile.write16(INIT_ACCESS_FLAGS);
classfile.write16(init_index);
classfile.write16(init_desc_index);
classfile.write16(1); // no. attributes
classfile.write16(code_tag_index);
classfile.write16(0); // MS half of attribute length
classfile.write16(18); // LS half of attribute length
classfile.write16(2); // max stack
classfile.write16(2); // max locals
classfile.write16(0); // MS half of code length
classfile.write16(6); // LS half of code length
// Code: aload0
// aload1
// invokespecial java.lang.reflect.Proxy(Ljava/lang/reflect/InvocationHandler;)V
// vreturn
classfile.write(new byte[] {0x2a, 0x2b, (byte)0xb7, 0, (byte)init_method_index, (byte)0xb1});
classfile.write16(0); // exception table length
classfile.write16(0); // code attributes (no attributes)
// class attributes
classfile.write16(0);
// That's it, folks ...
ClassLoader cl = (ClassLoader)proxyspec.get(0);
if (cl == null) {
cl = ClassLoader.getSystemClassLoader();
}
byte[] b = classfile.toByteArray();
Class c = null;
try {
method_defineProxyClass.setAccessible(true);
c = (Class)method_defineProxyClass.invoke(cl, new Object[] {name, b, new Integer(0), new Integer(b.length)});
method_defineProxyClass.setAccessible(false);
}
catch (IllegalAccessException e) {
e.printStackTrace();
}
catch (InvocationTargetException e) {
e.printStackTrace();
}
return c;
}
public static Class getProxyClass(ClassLoader loader, Class[] interfaces) throws IllegalArgumentException {
ArrayList al = new ArrayList();
Package pkg = null;
// Check the 'interfaces' array:
for (int i = 0; i < interfaces.length; ++i) {
Class itf = interfaces[i];
// must contain only interfaces, not classes or primitive types.
if (!itf.isInterface()) {
throw new IllegalArgumentException(itf + " is not an interface");
}
// no duplicates
if (al.contains(itf)) {
throw new IllegalArgumentException(itf + " is duplicate interface");
}
// must be visible through specified class loader
try {
if (Class.forName(itf.getName(), false, loader) != itf) {
throw new IllegalArgumentException(itf + " is not visible using " + loader);
}
}
catch (Exception e) {
throw new IllegalArgumentException(itf + " is not visible using " + loader + ": " + e);
}
if (!Modifier.isPublic(itf.getModifiers())) {
if (pkg == null) {
pkg = itf.getPackage();
}
else if (itf.getPackage() != pkg) {
throw new IllegalArgumentException(itf + " is not in same package as " + interfaces[0]);
}
}
// TODO: No two interfaces may each have a method with the same name and parameter signature but different return type.
al.add(itf);
}
ArrayList proxyspec = new ArrayList(interfaces.length + 1);
proxyspec.add(loader);
int l = interfaces.length;
for (int i = 0; i < l; ++i) {
proxyspec.add(interfaces[i]);
}
synchronized(proxies) {
Class proxy = (Class)proxies.get(proxyspec);
if (proxy != null) {
return proxy;
}
proxy = makeproxy(pkg, proxyspec);
proxies.put(proxyspec, proxy);
return proxy;
}
}
public static native boolean isProxyClass(Class clazz);
public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) throws IllegalArgumentException {
Class proxy = getProxyClass(loader, interfaces);
try {
Constructor constructor = proxy.getConstructor(new Class[] { InvocationHandler.class });
return constructor.newInstance(new Object[] { h });
}
catch (java.lang.NoSuchMethodException nsme) {
throw new java.lang.UnknownError(proxy + " has no constructor taking an InvocationHandler as argument : " + nsme);
}
catch (java.lang.InstantiationException ie) {
throw new java.lang.UnknownError(proxy + " is abstract (???) : " + ie);
}
catch (java.lang.IllegalAccessException iae) {
throw new java.lang.UnknownError(proxy + " constructor is not public : " + iae);
}
catch (InvocationTargetException ite) {
throw new java.lang.UnknownError(proxy + "constructor failed : " + ite);
}
}
public int hashCode() {
try {
return ((Integer)h.invoke(this, method_hashCode, null)).intValue();
}
catch (RuntimeException re) {
throw re;
}
catch (Error e) {
throw e;
}
catch (Throwable t) {
throw new UndeclaredThrowableException(t);
}
}
public boolean equals(Object o) {
try {
return ((Boolean)h.invoke(this, method_equals, new Object[] {o})).booleanValue();
}
catch (RuntimeException re) {
throw re;
}
catch (Error e) {
throw e;
}
catch (Throwable t) {
throw new UndeclaredThrowableException(t);
}
}
public String toString() {
try {
return (String)h.invoke(this, method_toString, null);
}
catch (RuntimeException re) {
throw re;
}
catch (Error e) {
throw e;
}
catch (Throwable t) {
throw new UndeclaredThrowableException(t);
}
}
}