001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.proxy.compiler;
023:
024: import java.lang.reflect.Method;
025: import java.lang.reflect.Constructor;
026: import java.lang.reflect.InvocationTargetException;
027: import java.lang.reflect.Modifier;
028: import java.lang.reflect.Member;
029:
030: import java.io.Serializable;
031:
032: import java.util.ArrayList;
033: import java.util.Hashtable;
034:
035: /**
036: * Routines for converting between strongly-typed interfaces and
037: * generic InvocationHandler objects.
038: *
039: * @version <tt>$Revision: 57209 $</tt>
040: * @author Unknown
041: * @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
042: */
043: public final class Proxies {
044: /**
045: * Disallow creation of Proxyies instances.
046: */
047: private Proxies() {
048: super ();
049: }
050:
051: /**
052: * Create a new target object <em>x</em> which is a proxy for
053: * the given InvocationHandler <tt>disp</tt>. The new object will be
054: * equivalent to <tt>disp</tt>, except that it will support normal Java
055: * method invocation, in place of the <tt>InvocationHandler.invoke</tt>
056: * protocol, for each method supported by the InvocationHandler.
057: *
058: * <p>
059: * The new object will implement each of the given target types.
060: * (The target types default to those declared by the InvocationHandler
061: * itself.) The new object will also implement the "administrative"
062: * interface <tt>Proxies.ProxyTarget</tt>.
063: *
064: * <p>
065: * For each "overrideable" (public, non-static, non-final)
066: * method <tt>T.m</tt> of <tt>T</tt>, calling <tt>x.m(...)</tt>
067: * will immediately cause a corresponding reflective call of the
068: * form <tt>disp.invoke(RM, new Object[]{ ... })</tt>, where <tt>RM</tt>
069: * is the reflective object for <tt>m</tt>.
070: *
071: * <p>
072: * The concrete class of this target object will be
073: * something mysterious and indefinite. Many callers will
074: * immediately cast the resulting proxy object to the target type
075: * of the InvocationHandler. For example:
076: * <code>
077: * MyInterface x1 = ...;
078: * InvocationHandler i = Proxies.newInvocationHandler(x1, MyInterface.class);
079: * MyInterface x2 = (MyInterface) ((Proxies.ProxyInvocationHandler)i).getTarget();
080: * // x1 == x2
081: * MyInterface x3 = (MyInterface) Proxies.newTarget(i);
082: * // x1 != x3, but calls to x3 are forwarded via i to x1
083: * </code>
084: */
085: public static ProxyTarget newTarget(ClassLoader parent,
086: InvocationHandler invocationHandler, Class targetTypes[])
087: throws Exception {
088: return Impl.getImpl(targetTypes).newTarget(invocationHandler,
089: parent);
090: }
091:
092: /**
093: * A common interface shared by all objects created
094: * by <tt>Proxies.newTarget</tt>.
095: */
096: public interface ProxyTarget extends Serializable {
097: /**
098: * Recover the original InvocationHandler object around which this
099: * proxy is wrapped.
100: */
101: InvocationHandler getInvocationHandler();
102:
103: /**
104: * Recover the original target types for which this proxy was wrapped.
105: */
106: Class[] getTargetTypes();
107: }
108:
109: /**
110: * Create a new reflective InvocationHandler object
111: * <tt>InvocationHandler</tt> wrapped around the given target object, for
112: * the given target type(s).
113: *
114: * <p>
115: * The new object will be operationally equivalent to <tt>target</tt>,
116: * except that it will support a reflective method invocation sequence
117: * (<tt>InvocationHandler.invoke</tt>) instead of the normal Java method
118: * invocation mechanism.
119: *
120: * <p>
121: * The target type must be specified, since the complete implementation
122: * type of the target object is usually irrelevant to the application.
123: * The target object must match the specified target type.
124: * For example:
125: * <code>
126: * MyInterface x1 = ...;
127: * InvocationHandler i = Proxies.newInvocationHandler(x1, MyInterface.class);
128: * </code>
129: */
130: public static ProxyInvocationHandler newInvocationHandler(
131: Object target, Class targetType) {
132: return Impl.getImpl(targetType).newInvocationHandler(target);
133: }
134:
135: public static ProxyInvocationHandler newInvocationHandler(
136: Object target, Class targetTypes[]) {
137: return Impl.getImpl(targetTypes).newInvocationHandler(target);
138: }
139:
140: /**
141: * A common interface shared by all objects created
142: * by <tt>Proxies.newInvocationHandler</tt>.
143: */
144: public interface ProxyInvocationHandler extends InvocationHandler,
145: Serializable {
146: /**
147: * Recover the original target object around which this
148: * InvocationHandler proxy is wrapped.
149: */
150: Object getTarget();
151: }
152:
153: /**
154: * Utility built on top of <tt>newTarget</tt> to find
155: * or create a proxy for the given InvocationHandler.
156: * It is the inverse of <tt>getInvocationHandler</tt>.
157: *
158: * <p>
159: * If the InvocationHandler implements <tt>ProxyInvocationHandler</tt>,
160: * it is a proxy for some original target object; extract and return
161: * that object. Otherwise, just call <tt>newTarget</tt>.
162: */
163: public static Object getTarget(InvocationHandler invocationHandler) {
164: if (invocationHandler instanceof ProxyInvocationHandler) {
165: Object target = ((ProxyInvocationHandler) invocationHandler)
166: .getTarget();
167: if (target != null) {
168: return target;
169: }
170: // and fall through...
171: }
172:
173: return null;
174: }
175:
176: /**
177: * Utility built on top of <tt>newInvocationHandler</tt> to find
178: * or create a proxy for the given target object.
179: * It is the inverse of <tt>getTarget</tt>.
180: *
181: * <p>
182: * If the target implements <tt>ProxyTarget</tt>, it is a proxy
183: * for some original InvocationHandler; extract and return that
184: * InvocationHandler. Otherwise, just call <tt>newInvocationHandler</tt>.
185: *
186: * @see #newInvocationHandler
187: */
188: public static InvocationHandler getInvocationHandler(Object target,
189: Class targetTypes[]) {
190: if (target instanceof ProxyTarget) {
191: ProxyTarget tproxy = (ProxyTarget) target;
192: InvocationHandler invocationHandler = tproxy
193: .getInvocationHandler();
194: if (targetTypes == null
195: || Impl.sameTypes(tproxy.getTargetTypes(),
196: targetTypes)) {
197: return invocationHandler;
198: }
199: // and fall through...
200: }
201: return newInvocationHandler(target, targetTypes);
202: }
203:
204: public static InvocationHandler getInvocationHandler(Object target,
205: Class targetType) {
206: // (should this be optimized?)
207: if (targetType == null) {
208: return getInvocationHandler(target, (Class[]) null);
209: }
210:
211: return getInvocationHandler(target, new Class[] { targetType });
212: }
213:
214: /**
215: * Utility which reports the set of valid methods for a target type.
216: * It is exactly the set of <tt>public</tt>, <tt>abstract</tt> methods
217: * returned by <tt>targetType.getMethods()</tt>, which are neither
218: * <tt>static</tt> nor <tt>final</tt>.
219: * <p>
220: * Also, if the targetType is not a suitable type, an empty array
221: * will be returned. The target type must not contain <tt>protected</tt>
222: * <tt>abstract</tt> methods, must have a nullary constructor,
223: * and must not be something silly like
224: * an array or primitive type, or a <tt>final</tt> class.
225: */
226: public static Method[] getMethods(Class targetType) {
227: return Impl.getImpl(targetType).copyMethods();
228: }
229:
230: public static Method[] getMethods(Class targetTypes[]) {
231: return Impl.getImpl(targetTypes).copyMethods();
232: }
233:
234: public static void forgetProxyForClass(Class clazz) {
235: Impl.forgetProxyForClass(clazz);
236: }
237:
238: /**
239: * ???
240: */
241: static class Impl implements Serializable {
242: static final long serialVersionUID = 2911933124627738645L;
243: static Hashtable impls = new Hashtable();
244:
245: /** the types that this impl processes */
246: Class targetTypes[];
247:
248: Method methods[];
249:
250: /** hashtable link to Impls sharing a target type */
251: Impl more;
252:
253: Class super class = Object.class;
254:
255: /** used in print names of proxies */
256: String proxyString;
257:
258: Constructor proxyConstructor;
259:
260: Impl(Class targetTypes[]) {
261: this .targetTypes = targetTypes;
262:
263: Method methodLists[][] = new Method[targetTypes.length][];
264: for (int i = 0; i < targetTypes.length; i++) {
265: methodLists[i] = checkTargetType(targetTypes[i]);
266: }
267:
268: checkSuperclass();
269: this .methods = combineMethodLists(methodLists);
270: }
271:
272: static synchronized Impl getImpl(Class targetType) {
273: Impl impl = (Impl) impls.get(targetType);
274: if (impl == null) {
275: impl = new Impl(new Class[] { targetType });
276: impls.put(targetType, impl);
277: }
278: return impl;
279: }
280:
281: static synchronized Impl getImpl(Class targetTypes[]) {
282: int n = targetTypes.length;
283: if (n == 1) {
284: return getImpl(targetTypes[0]);
285: }
286: // note that the desired Impl could be in any one of n places
287: // this requires extra searching, which is not a big deal
288: for (int i = 0; i < n; ++i) {
289: for (Impl impl = (Impl) impls.get(targetTypes[i]); impl != null; impl = impl.more) {
290: if (sameTypes(targetTypes, impl.targetTypes))
291: return impl;
292: }
293: }
294:
295: // now link it into the table
296: targetTypes = copyAndUniquify(targetTypes);
297: Impl impl1 = getImpl(new Class[] { targetTypes[0] });
298: Impl impl = new Impl(targetTypes);
299: impl.more = impl1.more;
300: impl1.more = impl;
301: return impl;
302: }
303:
304: /**
305: * The <code>forgetProxyForClass</code> method removes the impl from the
306: * class-impl map. This releases the UnifiedClassloader used to load the
307: * class we are constructing the proxy for.
308: *
309: * This may not work if the original class[] contained many classes, but
310: * seems OK with one class + Serializable, which is what is used by the cmp2
311: * engine. At present the cmp2 engine is the only caller of this method
312: * (through Proxy).
313: *
314: * @param clazz a <code>Class</code> value
315: */
316: static synchronized void forgetProxyForClass(Class clazz) {
317: impls.remove(clazz);
318: }
319:
320: // do the arrays have the same elements?
321: // (duplication and reordering are ignored)
322: static boolean sameTypes(Class tt1[], Class tt2[]) {
323: if (tt1.length == 1 && tt2.length == 1) {
324: return tt1[0] == tt2[0];
325: }
326:
327: int totalSeen2 = 0;
328: each_type: for (int i = tt1.length; --i >= 0;) {
329: Class c = tt1[i];
330: for (int j = i; --j >= 0;) {
331: if (c == tt1[j]) {
332: continue each_type;
333: }
334: }
335: // now c is a uniquified element of tt1
336: // count its occurrences in tt2
337: int seen2 = 0;
338: for (int j = tt2.length; --j >= 0;) {
339: if (c == tt2[j]) {
340: ++seen2;
341: }
342: }
343:
344: if (seen2 == 0) {
345: // c does not occur in tt2
346: return false;
347: }
348: totalSeen2 += seen2;
349: }
350: // now, each element of tt2 must have been visited
351: return totalSeen2 == tt2.length;
352: }
353:
354: static Class[] copyAndUniquify(Class targetTypes[]) {
355: int n = targetTypes.length;
356: Class tt[] = new Class[n];
357: int k = 0;
358: each_type: for (int i = 0; i < n; i++) {
359: Class c = targetTypes[i];
360: for (int j = i; --j >= 0;) {
361: if (c == targetTypes[j]) {
362: continue each_type;
363: }
364: }
365: tt[k++] = c;
366: }
367: if (k < n) {
368: // oops; caller passed in duplicate
369: Class tt0[] = new Class[k];
370: for (int i = 0; i < k; i++) {
371: tt0[i] = tt[i];
372: }
373: tt = tt0;
374: }
375: return tt;
376: }
377:
378: // make sure a give target type is acceptable
379: // return a list of eligible methods (may also include nulls)
380: Method[] checkTargetType(Class targetType) {
381: if (targetType.isArray()) {
382: throw new IllegalArgumentException(
383: "cannot subclass an array type: "
384: + targetType.getName());
385: }
386:
387: if (targetType.isPrimitive()) {
388: throw new IllegalArgumentException(
389: "cannot subclass a primitive type: "
390: + targetType);
391: }
392:
393: int tmod = targetType.getModifiers();
394: if (Modifier.isFinal(tmod)) {
395: throw new IllegalArgumentException(
396: "cannot subclass a final type: " + targetType);
397: }
398:
399: if (!Modifier.isPublic(tmod)) {
400: throw new IllegalArgumentException(
401: "cannot subclass a non-public type: "
402: + targetType);
403: }
404:
405: // Make sure the subclass will not need a "super" statement.
406: if (!targetType.isInterface()) {
407: if (!targetType.isAssignableFrom(super class)) {
408: if (super class.isAssignableFrom(targetType)) {
409: super class = targetType;
410: } else {
411: throw new IllegalArgumentException(
412: "inconsistent superclass: "
413: + targetType);
414: }
415: }
416: }
417:
418: // Decide what overrideable methods this type supports.
419: Method methodList[] = targetType.getMethods();
420: int nm = 0;
421: for (int i = 0; i < methodList.length; i++) {
422: Method m = methodList[i];
423: if (eligibleForInvocationHandler(m)) {
424: methodList[nm++] = m; // (reuse the method array)
425: }
426: }
427:
428: while (nm < methodList.length) {
429: methodList[nm++] = null; // (pad the reused method array)
430: }
431:
432: return methodList;
433: }
434:
435: void checkSuperclass() {
436: Constructor constructors[] = super class.getConstructors();
437: for (int i = 0; i < constructors.length; i++) {
438: Constructor c = constructors[i];
439: int mod = c.getModifiers();
440: if (Modifier.isPublic(mod)
441: && c.getParameterTypes().length == 0) {
442: return; // OK
443: }
444: }
445:
446: throw new IllegalArgumentException(
447: "cannot subclass without nullary constructor: "
448: + super class.getName());
449: }
450:
451: /**
452: * Tell if a given method will be passed by a proxy to its
453: * InvocationHandler
454: */
455: static boolean eligibleForInvocationHandler(Method m) {
456: int mod = m.getModifiers();
457:
458: if (Modifier.isStatic(mod) || Modifier.isFinal(mod)) {
459: // can't override these
460: return false;
461: }
462:
463: if (!Modifier.isAbstract(mod)) {
464: // do not support methods with "super"
465: return false;
466: }
467:
468: return true;
469: }
470:
471: /**
472: * Are the 2 methods equal in terms of conflicting with each other.
473: * i.e. String toString() and Map toString() are equal since only one
474: * toString() can be defined in a class.
475: * Also fixes problems with complex inheritance graphs and j2se1.4
476: */
477: static boolean areEqual(Method m1, Method m2) {
478: // Check method names.
479: if (!m1.getName().equals(m2.getName()))
480: return false;
481:
482: // Check parameters
483: Class a1[] = m1.getParameterTypes();
484: Class a2[] = m2.getParameterTypes();
485: if (a1.length != a2.length)
486: return false;
487: for (int i = 0; i < a1.length; i++)
488: if (!a1[i].equals(a2[i]))
489: return false;
490:
491: return true;
492: }
493:
494: /**
495: * Combine the given list of method[]'s into one method[],
496: * removing any methods duplicates.
497: */
498: static Method[] combineMethodLists(Method methodLists[][]) {
499: int nm = 0;
500: for (int i = 0; i < methodLists.length; i++) {
501: nm += methodLists[i].length;
502: }
503: Method methods[] = new Method[nm];
504:
505: // Merge the methods into a single array.
506: nm = 0;
507: for (int i = 0; i < methodLists.length; i++)
508: for (int j = 0; j < methodLists[i].length; j++)
509: methods[nm++] = methodLists[i][j];
510:
511: // Remove duplicate methods. (set them to null)
512: for (int i = 0; i < methods.length; i++) {
513: if (methods[i] == null)
514: continue;
515: for (int j = i + 1; j < methods.length; j++) {
516: if (methods[j] == null)
517: continue;
518: if (areEqual(methods[i], methods[j])) {
519: methods[j] = null;
520: nm--;
521: }
522: }
523: }
524:
525: // shorten and copy the array
526: ArrayList tmp = new ArrayList();
527: for (int i = 0; i < methods.length; i++) {
528: if (methods[i] != null)
529: tmp.add(methods[i]);
530: }
531: Method methodsCopy[] = new Method[tmp.size()];
532: tmp.toArray(methodsCopy);
533: return methodsCopy;
534: }
535:
536: Method[] copyMethods() {
537: return (Method[]) methods.clone();
538: }
539:
540: Class[] copyTargetTypes() {
541: return (Class[]) targetTypes.clone();
542: }
543:
544: ProxyTarget newTarget(InvocationHandler invocationHandler,
545: ClassLoader parent) throws Exception {
546: if (proxyConstructor == null) {
547: // make the proxy constructor
548: ProxyCompiler pc = new ProxyCompiler(parent,
549: super class, targetTypes, methods);
550:
551: Class type[] = { InvocationHandler.class };
552: proxyConstructor = pc.getProxyType().getConstructor(
553: type);
554: }
555:
556: Object args[] = { invocationHandler };
557: return (ProxyTarget) proxyConstructor.newInstance(args);
558: }
559:
560: ProxyInvocationHandler newInvocationHandler(final Object target) {
561: if (proxyString == null) {
562: String s = "InvocationHandler@"
563: + targetTypes[0].getName();
564: for (int i = 1; i < targetTypes.length; i++) {
565: s += "," + targetTypes[i].getName();
566: }
567: proxyString = s;
568: }
569:
570: return new ProxyInvocationHandler() {
571: // (ISSUE: Should this be made subclassable?)
572: public Object getTarget() {
573: return target;
574: }
575:
576: public Class[] getTargetTypes() {
577: return copyTargetTypes();
578: }
579:
580: public String toString() {
581: return proxyString + "[" + target + "]";
582: }
583:
584: public Object invoke(Object dummy, Method method,
585: Object values[]) throws Throwable {
586: return Impl.this .invoke(target, method, values);
587: }
588: };
589: }
590:
591: /**
592: * The heart of a ProxyInvocationHandler.
593: */
594: Object invoke(Object target, Member method, Object values[])
595: throws Throwable {
596: // Note:
597: //
598: // We will not invoke the method unless we are expecting it.
599: // Thus, we cannot blindly call Method.invoke, but must first
600: // check our list of allowed methods.
601:
602: try {
603: Method methods[] = this .methods; // cache
604:
605: // use fast pointer equality (it usually succeeds)
606: for (int i = methods.length; --i >= 0;) {
607: if (methods[i] == method) {
608: return methods[i].invoke(target, values);
609: }
610: }
611:
612: // special case: allow a null method to select the unique one
613: if (method == null) {
614: if (methods.length == 1) {
615: return methods[0].invoke(target, values);
616: }
617: throw new IllegalArgumentException(
618: "non-unique method");
619: }
620:
621: // try the slower form of equality
622: for (int i = methods.length; --i >= 0;) {
623: if (methods[i].equals(method)) {
624: return methods[i].invoke(target, values);
625: }
626: }
627:
628: } catch (InvocationTargetException e) {
629: throw e.getTargetException();
630: }
631:
632: throw new IllegalArgumentException("method unexpected "
633: + method);
634: }
635: }
636: }
|