/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.proxy.compiler;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.lang.reflect.Member;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Hashtable;
/**
* Routines for converting between strongly-typed interfaces and
* generic InvocationHandler objects.
*
* @version <tt>$Revision: 81030 $</tt>
* @author Unknown
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
*/
public final class Proxies
{
/**
* Disallow creation of Proxyies instances.
*/
private Proxies()
{
super();
}
/**
* Create a new target object <em>x</em> which is a proxy for
* the given InvocationHandler <tt>disp</tt>. The new object will be
* equivalent to <tt>disp</tt>, except that it will support normal Java
* method invocation, in place of the <tt>InvocationHandler.invoke</tt>
* protocol, for each method supported by the InvocationHandler.
*
* <p>
* The new object will implement each of the given target types.
* (The target types default to those declared by the InvocationHandler
* itself.) The new object will also implement the "administrative"
* interface <tt>Proxies.ProxyTarget</tt>.
*
* <p>
* For each "overrideable" (public, non-static, non-final)
* method <tt>T.m</tt> of <tt>T</tt>, calling <tt>x.m(...)</tt>
* will immediately cause a corresponding reflective call of the
* form <tt>disp.invoke(RM, new Object[]{ ... })</tt>, where <tt>RM</tt>
* is the reflective object for <tt>m</tt>.
*
* <p>
* The concrete class of this target object will be
* something mysterious and indefinite. Many callers will
* immediately cast the resulting proxy object to the target type
* of the InvocationHandler. For example:
* <code>
* MyInterface x1 = ...;
* InvocationHandler i = Proxies.newInvocationHandler(x1, MyInterface.class);
* MyInterface x2 = (MyInterface) ((Proxies.ProxyInvocationHandler)i).getTarget();
* // x1 == x2
* MyInterface x3 = (MyInterface) Proxies.newTarget(i);
* // x1 != x3, but calls to x3 are forwarded via i to x1
* </code>
*/
public static ProxyTarget newTarget(ClassLoader parent,
InvocationHandler invocationHandler,
Class targetTypes[])
throws Exception
{
return Impl.getImpl(targetTypes).newTarget(invocationHandler, parent);
}
/**
* A common interface shared by all objects created
* by <tt>Proxies.newTarget</tt>.
*/
public interface ProxyTarget
extends Serializable
{
/**
* Recover the original InvocationHandler object around which this
* proxy is wrapped.
*/
InvocationHandler getInvocationHandler();
/**
* Recover the original target types for which this proxy was wrapped.
*/
Class[] getTargetTypes();
}
/**
* Create a new reflective InvocationHandler object
* <tt>InvocationHandler</tt> wrapped around the given target object, for
* the given target type(s).
*
* <p>
* The new object will be operationally equivalent to <tt>target</tt>,
* except that it will support a reflective method invocation sequence
* (<tt>InvocationHandler.invoke</tt>) instead of the normal Java method
* invocation mechanism.
*
* <p>
* The target type must be specified, since the complete implementation
* type of the target object is usually irrelevant to the application.
* The target object must match the specified target type.
* For example:
* <code>
* MyInterface x1 = ...;
* InvocationHandler i = Proxies.newInvocationHandler(x1, MyInterface.class);
* </code>
*/
public static ProxyInvocationHandler newInvocationHandler(Object target,
Class targetType)
{
return Impl.getImpl(targetType).newInvocationHandler(target);
}
public static ProxyInvocationHandler newInvocationHandler(Object target,
Class targetTypes[])
{
return Impl.getImpl(targetTypes).newInvocationHandler(target);
}
/**
* A common interface shared by all objects created
* by <tt>Proxies.newInvocationHandler</tt>.
*/
public interface ProxyInvocationHandler
extends InvocationHandler, Serializable
{
/**
* Recover the original target object around which this
* InvocationHandler proxy is wrapped.
*/
Object getTarget();
}
/**
* Utility built on top of <tt>newTarget</tt> to find
* or create a proxy for the given InvocationHandler.
* It is the inverse of <tt>getInvocationHandler</tt>.
*
* <p>
* If the InvocationHandler implements <tt>ProxyInvocationHandler</tt>,
* it is a proxy for some original target object; extract and return
* that object. Otherwise, just call <tt>newTarget</tt>.
*/
public static Object getTarget(InvocationHandler invocationHandler)
{
if (invocationHandler instanceof ProxyInvocationHandler)
{
Object target = ((ProxyInvocationHandler)invocationHandler).getTarget();
if (target != null)
{
return target;
}
// and fall through...
}
return null;
}
/**
* Utility built on top of <tt>newInvocationHandler</tt> to find
* or create a proxy for the given target object.
* It is the inverse of <tt>getTarget</tt>.
*
* <p>
* If the target implements <tt>ProxyTarget</tt>, it is a proxy
* for some original InvocationHandler; extract and return that
* InvocationHandler. Otherwise, just call <tt>newInvocationHandler</tt>.
*
* @see #newInvocationHandler
*/
public static InvocationHandler getInvocationHandler(Object target,
Class targetTypes[])
{
if (target instanceof ProxyTarget)
{
ProxyTarget tproxy = (ProxyTarget)target;
InvocationHandler invocationHandler = tproxy.getInvocationHandler();
if (targetTypes == null ||
Impl.sameTypes(tproxy.getTargetTypes(), targetTypes))
{
return invocationHandler;
}
// and fall through...
}
return newInvocationHandler(target, targetTypes);
}
public static InvocationHandler getInvocationHandler(Object target,
Class targetType)
{
// (should this be optimized?)
if (targetType == null)
{
return getInvocationHandler(target, (Class[])null);
}
return getInvocationHandler(target, new Class[] { targetType });
}
/**
* Utility which reports the set of valid methods for a target type.
* It is exactly the set of <tt>public</tt>, <tt>abstract</tt> methods
* returned by <tt>targetType.getMethods()</tt>, which are neither
* <tt>static</tt> nor <tt>final</tt>.
* <p>
* Also, if the targetType is not a suitable type, an empty array
* will be returned. The target type must not contain <tt>protected</tt>
* <tt>abstract</tt> methods, must have a nullary constructor,
* and must not be something silly like
* an array or primitive type, or a <tt>final</tt> class.
*/
public static Method[] getMethods(Class targetType)
{
return Impl.getImpl(targetType).copyMethods();
}
public static Method[] getMethods(Class targetTypes[])
{
return Impl.getImpl(targetTypes).copyMethods();
}
public static void forgetProxyForClass(Class clazz)
{
Impl.forgetProxyForClass(clazz);
}
/**
* ???
*/
static class Impl
implements Serializable
{
static Hashtable impls = new Hashtable();
/** the types that this impl processes */
Class targetTypes[];
Method methods[];
/** hashtable link to Impls sharing a target type */
Impl more;
Class superclass = Object.class;
/** used in print names of proxies */
String proxyString;
Constructor proxyConstructor;
Impl(Class targetTypes[])
{
this.targetTypes = targetTypes;
Method methodLists[][] = new Method[targetTypes.length][];
for (int i = 0; i < targetTypes.length; i++)
{
methodLists[i] = checkTargetType(targetTypes[i]);
}
checkSuperclass();
this.methods = combineMethodLists(methodLists);
}
static synchronized Impl getImpl(Class targetType)
{
Impl impl = (Impl) impls.get(targetType);
if (impl == null)
{
impl = new Impl(new Class[] { targetType });
impls.put(targetType, impl);
}
return impl;
}
static synchronized Impl getImpl(Class targetTypes[])
{
int n = targetTypes.length;
if (n == 1)
{
return getImpl(targetTypes[0]);
}
// note that the desired Impl could be in any one of n places
// this requires extra searching, which is not a big deal
for (int i = 0; i < n; ++i)
{
for (Impl impl = (Impl) impls.get(targetTypes[i]); impl != null; impl = impl.more)
{
if (sameTypes(targetTypes, impl.targetTypes))
return impl;
}
}
// now link it into the table
targetTypes = copyAndUniquify(targetTypes);
Impl impl1 = getImpl(new Class[] { targetTypes[0] });
Impl impl = new Impl(targetTypes);
impl.more = impl1.more;
impl1.more = impl;
return impl;
}
/**
* The <code>forgetProxyForClass</code> method removes the impl from the
* class-impl map. This releases the UnifiedClassloader used to load the
* class we are constructing the proxy for.
*
* This may not work if the original class[] contained many classes, but
* seems OK with one class + Serializable, which is what is used by the cmp2
* engine. At present the cmp2 engine is the only caller of this method
* (through Proxy).
*
* @param clazz a <code>Class</code> value
*/
static synchronized void forgetProxyForClass(Class clazz)
{
impls.remove(clazz);
}
// do the arrays have the same elements?
// (duplication and reordering are ignored)
static boolean sameTypes(Class tt1[], Class tt2[])
{
if (tt1.length == 1 && tt2.length == 1)
{
return tt1[0] == tt2[0];
}
int totalSeen2 = 0;
each_type:
for (int i = tt1.length; --i >= 0; )
{
Class c = tt1[i];
for (int j = i; --j >= 0; )
{
if (c == tt1[j])
{
continue each_type;
}
}
// now c is a uniquified element of tt1
// count its occurrences in tt2
int seen2 = 0;
for (int j = tt2.length; --j >= 0; )
{
if (c == tt2[j])
{
++seen2;
}
}
if (seen2 == 0)
{
// c does not occur in tt2
return false;
}
totalSeen2 += seen2;
}
// now, each element of tt2 must have been visited
return totalSeen2 == tt2.length;
}
static Class[] copyAndUniquify(Class targetTypes[])
{
int n = targetTypes.length;
Class tt[] = new Class[n];
int k = 0;
each_type:
for (int i = 0; i < n; i++)
{
Class c = targetTypes[i];
for (int j = i; --j >= 0; )
{
if (c == targetTypes[j])
{
continue each_type;
}
}
tt[k++] = c;
}
if (k < n)
{
// oops; caller passed in duplicate
Class tt0[] = new Class[k];
for (int i = 0; i < k; i++)
{
tt0[i] = tt[i];
}
tt = tt0;
}
return tt;
}
// make sure a give target type is acceptable
// return a list of eligible methods (may also include nulls)
Method[] checkTargetType(Class targetType)
{
if (targetType.isArray())
{
throw new IllegalArgumentException
("cannot subclass an array type: " + targetType.getName());
}
if (targetType.isPrimitive())
{
throw new IllegalArgumentException
("cannot subclass a primitive type: " + targetType);
}
int tmod = targetType.getModifiers();
if (Modifier.isFinal(tmod))
{
throw new IllegalArgumentException
("cannot subclass a final type: " + targetType);
}
if (!Modifier.isPublic(tmod))
{
throw new IllegalArgumentException
("cannot subclass a non-public type: " + targetType);
}
// Make sure the subclass will not need a "super" statement.
if (!targetType.isInterface())
{
if (!targetType.isAssignableFrom(superclass))
{
if (superclass.isAssignableFrom(targetType))
{
superclass = targetType;
}
else {
throw new IllegalArgumentException
("inconsistent superclass: " + targetType);
}
}
}
// Decide what overrideable methods this type supports.
Method methodList[] = targetType.getMethods();
int nm = 0;
for (int i = 0; i < methodList.length; i++)
{
Method m = methodList[i];
if (eligibleForInvocationHandler(m))
{
methodList[nm++] = m; // (reuse the method array)
}
}
while (nm < methodList.length)
{
methodList[nm++] = null; // (pad the reused method array)
}
return methodList;
}
void checkSuperclass()
{
Constructor constructors[] = superclass.getConstructors();
for (int i = 0; i < constructors.length; i++)
{
Constructor c = constructors[i];
int mod = c.getModifiers();
if (Modifier.isPublic(mod)
&& c.getParameterTypes().length == 0)
{
return; // OK
}
}
throw new IllegalArgumentException
("cannot subclass without nullary constructor: "
+superclass.getName());
}
/**
* Tell if a given method will be passed by a proxy to its
* InvocationHandler
*/
static boolean eligibleForInvocationHandler(Method m)
{
int mod = m.getModifiers();
if (Modifier.isStatic(mod) || Modifier.isFinal(mod))
{
// can't override these
return false;
}
if (!Modifier.isAbstract(mod))
{
// do not support methods with "super"
return false;
}
return true;
}
/**
* Are the 2 methods equal in terms of conflicting with each other.
* i.e. String toString() and Map toString() are equal since only one
* toString() can be defined in a class.
* Also fixes problems with complex inheritance graphs and j2se1.4
*/
static boolean areEqual(Method m1, Method m2)
{
// Check method names.
if( ! m1.getName().equals(m2.getName()) )
return false;
// Check parameters
Class a1[] = m1.getParameterTypes();
Class a2[] = m2.getParameterTypes();
if( a1.length != a2.length )
return false;
for( int i=0; i < a1.length; i++)
if( !a1[i].equals(a2[i]) )
return false;
return true;
}
/**
* Combine the given list of method[]'s into one method[],
* removing any methods duplicates.
*/
static Method[] combineMethodLists(Method methodLists[][])
{
int nm = 0;
for (int i = 0; i < methodLists.length; i++)
{
nm += methodLists[i].length;
}
Method methods[] = new Method[nm];
// Merge the methods into a single array.
nm=0;
for (int i = 0; i < methodLists.length; i++)
for (int j = 0; j < methodLists[i].length; j++)
methods[nm++]=methodLists[i][j];
// Remove duplicate methods. (set them to null)
for( int i=0; i < methods.length; i++ )
{
if( methods[i] == null )
continue;
for( int j=i+1; j < methods.length; j++ )
{
if( methods[j] == null )
continue;
if( areEqual(methods[i], methods[j]) )
{
methods[j]=null;
nm--;
}
}
}
// shorten and copy the array
ArrayList tmp = new ArrayList();
for (int i = 0; i < methods.length; i++)
{
if( methods[i] != null )
tmp.add(methods[i]);
}
Method methodsCopy[] = new Method[tmp.size()];
tmp.toArray(methodsCopy);
return methodsCopy;
}
Method[] copyMethods()
{
return (Method[])methods.clone();
}
Class[] copyTargetTypes()
{
return (Class[])targetTypes.clone();
}
ProxyTarget newTarget(InvocationHandler invocationHandler,
ClassLoader parent)
throws Exception
{
if (proxyConstructor == null)
{
// make the proxy constructor
ProxyCompiler pc = new ProxyCompiler(parent,
superclass,
targetTypes,
methods);
Class type[] = { InvocationHandler.class };
proxyConstructor = pc.getProxyType().getConstructor(type);
}
Object args[] = { invocationHandler };
return (ProxyTarget)proxyConstructor.newInstance(args);
}
ProxyInvocationHandler newInvocationHandler(final Object target)
{
if (proxyString == null)
{
String s = "InvocationHandler@" + targetTypes[0].getName();
for (int i = 1; i < targetTypes.length; i++)
{
s += "," + targetTypes[i].getName();
}
proxyString = s;
}
return new ProxyInvocationHandler()
{
// (ISSUE: Should this be made subclassable?)
public Object getTarget()
{
return target;
}
public Class[] getTargetTypes()
{
return copyTargetTypes();
}
public String toString()
{
return proxyString + "[" + target + "]";
}
public Object invoke(Object dummy,
Method method,
Object values[])
throws Throwable
{
return Impl.this.invoke(target, method, values);
}
};
}
/**
* The heart of a ProxyInvocationHandler.
*/
Object invoke(Object target, Member method, Object values[])
throws Throwable
{
// Note:
//
// We will not invoke the method unless we are expecting it.
// Thus, we cannot blindly call Method.invoke, but must first
// check our list of allowed methods.
try
{
Method methods[] = this.methods; // cache
// use fast pointer equality (it usually succeeds)
for (int i = methods.length; --i >= 0; )
{
if (methods[i] == method)
{
return methods[i].invoke(target, values);
}
}
// special case: allow a null method to select the unique one
if (method == null)
{
if (methods.length == 1)
{
return methods[0].invoke(target, values);
}
throw new IllegalArgumentException("non-unique method");
}
// try the slower form of equality
for (int i = methods.length; --i >= 0; )
{
if (methods[i].equals(method))
{
return methods[i].invoke(target, values);
}
}
}
catch (InvocationTargetException e)
{
throw e.getTargetException();
}
throw new IllegalArgumentException("method unexpected "+method);
}
}
}