KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > exolab > jms > plugins > proxygen > ProxyGenerator


1 /**
2  * Redistribution and use of this software and associated documentation
3  * ("Software"), with or without modification, are permitted provided
4  * that the following conditions are met:
5  *
6  * 1. Redistributions of source code must retain copyright
7  * statements and notices. Redistributions must also contain a
8  * copy of this document.
9  *
10  * 2. Redistributions in binary form must reproduce the
11  * above copyright notice, this list of conditions and the
12  * following disclaimer in the documentation and/or other
13  * materials provided with the distribution.
14  *
15  * 3. The name "Exolab" must not be used to endorse or promote
16  * products derived from this Software without prior written
17  * permission of Exoffice Technologies. For written permission,
18  * please contact info@exolab.org.
19  *
20  * 4. Products derived from this Software may not be called "Exolab"
21  * nor may "Exolab" appear in their names without prior written
22  * permission of Exoffice Technologies. Exolab is a registered
23  * trademark of Exoffice Technologies.
24  *
25  * 5. Due credit should be given to the Exolab Project
26  * (http://www.exolab.org/).
27  *
28  * THIS SOFTWARE IS PROVIDED BY EXOFFICE TECHNOLOGIES AND CONTRIBUTORS
29  * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
30  * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
31  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
32  * EXOFFICE TECHNOLOGIES OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
33  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
34  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
35  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
37  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
39  * OF THE POSSIBILITY OF SUCH DAMAGE.
40  *
41  * Copyright 2003-2005 (C) Exoffice Technologies Inc. All Rights Reserved.
42  *
43  * $Id: ProxyGenerator.java,v 1.6 2005/05/24 13:38:20 tanderson Exp $
44  */

45 package org.exolab.jms.plugins.proxygen;
46
47 import java.io.IOException JavaDoc;
48 import java.io.OutputStream JavaDoc;
49 import java.io.OutputStreamWriter JavaDoc;
50 import java.lang.reflect.Method JavaDoc;
51 import java.rmi.RemoteException JavaDoc;
52 import java.util.Arrays JavaDoc;
53 import java.util.Comparator JavaDoc;
54 import java.util.HashMap JavaDoc;
55 import java.util.Iterator JavaDoc;
56
57
58 /**
59  * Generates source code for a <code>Proxy</code> implementation of a class.
60  *
61  * @author <a HREF="mailto:tma@netspace.net.au">Tim Anderson</a>
62  * @version $Revision: 1.6 $ $Date: 2005/05/24 13:38:20 $
63  */

64 public class ProxyGenerator {
65
66     /**
67      * The class to generate a proxy source for.
68      */

69     private final Class JavaDoc _clazz;
70
71     /**
72      * The package.
73      */

74     private final String JavaDoc _package;
75
76     /**
77      * The class name.
78      */

79     private final String JavaDoc _className;
80
81     /**
82      * A map of adapters of {@link Throwable}. The key is the target
83      * exception type, the value the adapter class. The adapter class must
84      * implement <code>ThrowableAdapter</code>. It is accessed via
85      * introspection to avoid circular build dependencies.
86      */

87     private HashMap JavaDoc _adapters = new HashMap JavaDoc();
88
89     /**
90      * Interfaces implemented by the class.
91      */

92     private Class JavaDoc[] _interfaces;
93
94     /**
95      * The methods implemented by the class.
96      */

97     private Method JavaDoc[] _methods;
98
99     /**
100      * Primitive mappings.
101      */

102     private static final Class JavaDoc[][] MAPPINGS = new Class JavaDoc[][]{
103         {boolean.class, Boolean JavaDoc.class},
104         {byte.class, Byte JavaDoc.class},
105         {short.class, Short JavaDoc.class},
106         {char.class, Character JavaDoc.class},
107         {int.class, Integer JavaDoc.class},
108         {long.class, Long JavaDoc.class},
109         {float.class, Float JavaDoc.class},
110         {double.class, Double JavaDoc.class}};
111
112     /**
113      * The fully qualified Delegate class name.
114      */

115     private static final String JavaDoc DELEGATE = "org.exolab.jms.net.proxy.Delegate";
116
117     /**
118      * The fully qualified Proxy class name.
119      */

120     private static final String JavaDoc PROXY = "org.exolab.jms.net.proxy.Proxy";
121
122     /**
123      * The suffix for generated proxies.
124      */

125     private static final String JavaDoc PROXY_SUFFIX = "__Proxy";
126
127     /**
128      * The fully qualified RemoteInvocationException class name.
129      */

130     private static final String JavaDoc REMOTE_INVOCATION_EXCEPTION =
131             "org.exolab.jms.net.proxy.RemoteInvocationException";
132
133     /**
134      * The fully qualified ThrowableAdapter class name.
135      */

136     private static final String JavaDoc THROWABLE_ADAPTER =
137             "org.exolab.jms.net.proxy.ThrowableAdapter";
138
139
140     /**
141      * Construct a new <code>ProxyGenerator</code>.
142      *
143      * @param clazz the class to generate proxy code for
144      * @param adapters adapter classes for {@link RemoteException}. May be
145      * <code>null</code>
146      * @throws Exception if <code>adapter</code> is specified but can't be
147      * instantiated
148      */

149     public ProxyGenerator(Class JavaDoc clazz, Class JavaDoc[] adapters)
150             throws Exception JavaDoc {
151         if (clazz == null) {
152             throw new IllegalArgumentException JavaDoc("Argument 'clazz' is null");
153         }
154         if (clazz.isArray()) {
155             throw new IllegalArgumentException JavaDoc(
156                     "Can't generate proxies for array types");
157         }
158         if (clazz.isPrimitive()) {
159             throw new IllegalArgumentException JavaDoc(
160                     "Can't generate proxies for primitive types");
161         }
162         _clazz = clazz;
163         _package = ClassHelper.getPackage(_clazz);
164
165         String JavaDoc name;
166         if (_package != null) {
167             name = _clazz.getName().substring(_package.length() + 1);
168         } else {
169             name = _clazz.getName();
170         }
171         _className = name + PROXY_SUFFIX;
172
173         _interfaces = _clazz.getInterfaces();
174         if (_interfaces.length == 0) {
175             if (MethodHelper.getAllInterfaces(clazz).length == 0) {
176                 throw new IllegalArgumentException JavaDoc(
177                         "Cannot generate proxy for class " + _clazz.getName()
178                         + ": class doesn't implement any interfaces");
179             }
180         }
181
182         if (adapters != null && adapters.length != 0) {
183             _adapters = getAdapters(adapters);
184         }
185
186         _methods = MethodHelper.getInterfaceMethods(_clazz);
187         Arrays.sort(_methods, new MethodComparator());
188     }
189
190     /**
191      * Generates the code for the proxy implementation.
192      *
193      * @param stream the stream to write to
194      * @throws IOException for any I/O error
195      */

196     public void generate(OutputStream JavaDoc stream) throws IOException JavaDoc {
197         SourceWriter writer = new SourceWriter(new OutputStreamWriter JavaDoc(stream));
198
199         if (_package != null) {
200             writer.writeln("package " + _package + ";");
201         }
202
203         writer.writelnInc("public class " + _className);
204         writer.writeln("extends " + getSuperclassProxy(_clazz));
205         if (_interfaces.length != 0) {
206             writer.write("implements ");
207             for (int i = 0; i < _interfaces.length; ++i) {
208                 if (i > 0) {
209                     writer.write(", ");
210                 }
211                 writer.write(_interfaces[i].getName());
212             }
213         }
214         writer.writeln(" {");
215         generateStaticDeclarations(writer);
216         generateConstructor(writer);
217         generateMethods(writer);
218         generateStaticInitialiser(writer);
219         writer.writelnDec();
220         writer.writeln("}");
221         writer.flush();
222     }
223
224     /**
225      * Generates static declarations.
226      *
227      * @param writer the writer to write to
228      * @throws IOException for any I/O error
229      */

230     protected void generateStaticDeclarations(SourceWriter writer)
231             throws IOException JavaDoc {
232
233         if (_methods.length > 0) {
234             writer.writeln();
235             for (int i = 0; i < _methods.length; ++i) {
236                 Method JavaDoc method = _methods[i];
237                 String JavaDoc name = getMethodVarName(method);
238                 writer.writeln("private static final java.lang.reflect.Method "
239                                + name
240                                + ";");
241             }
242             writer.writeln();
243
244             Iterator JavaDoc iterator = _adapters.values().iterator();
245             while (iterator.hasNext()) {
246                 Class JavaDoc adapter = (Class JavaDoc) iterator.next();
247                 String JavaDoc name = getAdapterInstanceName(adapter);
248                 writer.writeln("private static final "
249                                + THROWABLE_ADAPTER + " " + name
250                                + " = new " + adapter.getName()+ "();");
251             }
252             writer.writeln();
253         }
254     }
255
256     /**
257      * Generates the constructor.
258      *
259      * @param writer the writer to write to
260      * @throws IOException for any I/O error
261      */

262     protected void generateConstructor(SourceWriter writer)
263             throws IOException JavaDoc {
264
265         writer.writelnInc("public " + _className + "(" + DELEGATE
266                           + " delegate) {");
267         writer.writelnDec("super(delegate);");
268         writer.writeln("}");
269     }
270
271     /**
272      * Generates the public methods.
273      *
274      * @param writer the writer to write to
275      * @throws IOException for any I/O error
276      */

277     protected void generateMethods(SourceWriter writer) throws IOException JavaDoc {
278         for (int i = 0; i < _methods.length; ++i) {
279             writer.writeln();
280             Method JavaDoc method = _methods[i];
281             generateMethod(method, writer);
282         }
283     }
284
285     /**
286      * Generates a method.
287      *
288      * @param method the method to generate code for
289      * @param writer the writer to write to
290      * @throws IOException for any I/O error
291      */

292     protected void generateMethod(Method JavaDoc method, SourceWriter writer)
293             throws IOException JavaDoc {
294
295         Class JavaDoc returnType = method.getReturnType();
296         Class JavaDoc[] argTypes = method.getParameterTypes();
297         Class JavaDoc[] exceptionTypes = method.getExceptionTypes();
298         boolean declaresThrowable = false;
299         boolean declaresException = false;
300         boolean declaresRuntimeException = false;
301         boolean declaresRemoteException = false;
302         boolean declaresRemoteInvocationException = false;
303         boolean adaptThrowable = false;
304         Class JavaDoc adaptType = null;
305
306         for (int i = 0; i < exceptionTypes.length; ++i) {
307             Class JavaDoc exceptionType = exceptionTypes[i];
308             if (exceptionType.equals(Throwable JavaDoc.class)) {
309                 declaresThrowable = true;
310             } else if (exceptionType.equals(Exception JavaDoc.class)) {
311                 declaresException = true;
312             } else if (exceptionType.equals(RuntimeException JavaDoc.class)) {
313                 declaresRuntimeException = true;
314             } else if (exceptionType.equals(RemoteException JavaDoc.class)) {
315                 declaresRemoteException = true;
316             } else if (exceptionType.getName().equals(
317                     REMOTE_INVOCATION_EXCEPTION)) {
318                 declaresRemoteInvocationException = true;
319             } else if (_adapters.get(exceptionType) != null) {
320                 adaptType = exceptionType;
321             }
322         }
323
324         if (!declaresThrowable && adaptType != null) {
325             // rethrow all uncaught exceptions as an instance of adaptType
326
adaptThrowable = true;
327         }
328
329         // determine the set of exceptions to catch.
330
Class JavaDoc[] catchTypes = method.getExceptionTypes();
331         Arrays.sort(catchTypes, new ClassComparator());
332
333         // generate the method signature
334
String JavaDoc returnClass = ClassHelper.getQualifiedName(returnType);
335         writer.write("public " + returnClass + " " + method.getName() + "(");
336         for (int i = 0; i < argTypes.length; ++i) {
337             if (i > 0) {
338                 writer.write(", ");
339             }
340             String JavaDoc argClass = ClassHelper.getQualifiedName(argTypes[i]);
341             writer.write(argClass + " arg" + i);
342         }
343         writer.write(")");
344
345         // generate throws clause
346
if (exceptionTypes.length > 0) {
347             writer.writelnInc();
348             writer.write("throws ");
349             for (int i = 0; i < exceptionTypes.length; ++i) {
350                 if (i > 0) {
351                     writer.write(", ");
352                 }
353                 writer.write(exceptionTypes[i].getName());
354             }
355             writer.writeln(" { ");
356         } else {
357             writer.writelnInc(" {");
358         }
359
360         // generate the invocation arguments, if the method takes arguments
361
String JavaDoc argValue = null;
362         if (argTypes.length > 0) {
363             argValue = "args";
364             writer.write("Object[] " + argValue + " = new Object[] {");
365             for (int i = 0; i < argTypes.length; ++i) {
366                 if (i > 0) {
367                     writer.write(", ");
368                 }
369                 Class JavaDoc arg = argTypes[i];
370                 String JavaDoc name = "arg" + i;
371                 writer.write(wrapArgument(arg, name));
372             }
373             writer.writeln("};");
374         } else {
375             argValue = "null";
376         }
377
378         // generate the invoke() call
379
boolean hasReturn = (returnType != void.class);
380         if (hasReturn) {
381             writer.writeln("Object result;");
382         }
383
384         writer.writelnInc("try {");
385
386         if (hasReturn) {
387             writer.write("result = ");
388         }
389         long methodId = MethodHelper.getMethodID(method);
390         writer.writelnDec("invoke(" + getMethodVarName(method) + ", "
391                           + argValue + ", 0x" + Long.toHexString(methodId)
392                           + "L);");
393
394         boolean caughtRIE = false;
395         boolean rethrowRIE = false;
396         if (!declaresThrowable && !declaresException
397                 && !declaresRuntimeException
398                 && !declaresRemoteInvocationException) {
399             rethrowRIE = true;
400         }
401
402         for (int i = 0; i < catchTypes.length; ++i) {
403             Class JavaDoc catchType = catchTypes[i];
404             if (rethrowRIE && !caughtRIE) {
405                 if (catchType.equals(Throwable JavaDoc.class)
406                     || catchType.equals(Exception JavaDoc.class)
407                     || catchType.equals(RuntimeException JavaDoc.class)) {
408                     generateRethrow(writer, REMOTE_INVOCATION_EXCEPTION);
409                     caughtRIE = true;
410                 }
411             }
412             generateRethrow(writer, catchType.getName());
413         }
414         if (rethrowRIE && !caughtRIE) {
415             generateRethrow(writer, REMOTE_INVOCATION_EXCEPTION);
416         }
417         if (!declaresThrowable) {
418             writer.writelnInc("} catch (java.lang.Throwable exception) {");
419             if (adaptThrowable) {
420                 Class JavaDoc adapter = (Class JavaDoc) _adapters.get(adaptType);
421                 String JavaDoc instance = getAdapterInstanceName(adapter);
422                 writer.writeln(adaptType.getName() + " error = ("
423                         + adaptType.getName() + ") " + instance
424                                + ".adapt(exception);");
425                 writer.writelnDec("throw error;");
426             } else if (declaresRemoteException) {
427                 writer.writelnDec("throw new "
428                                   + RemoteException JavaDoc.class.getName()
429                                   + "(exception.getMessage(), exception);");
430             } else {
431                 writer.writelnDec("throw new " + REMOTE_INVOCATION_EXCEPTION
432                                   + "(exception);");
433             }
434         }
435         writer.writeln("}");
436
437         if (hasReturn) {
438             writer.writelnDec("return " + unwrapReturn(method.getReturnType(),
439                                                        "result"));
440         } else {
441             writer.writelnDec();
442         }
443         writer.writeln("}");
444     }
445
446     /**
447      * Generates the class static initialiser.
448      *
449      * @param writer the writer to write to
450      * @throws IOException for any I/O error
451      */

452     protected void generateStaticInitialiser(SourceWriter writer)
453             throws IOException JavaDoc {
454
455         if (_methods.length > 0) {
456             writer.writeln();
457             writer.writelnInc("static {");
458             writer.writelnInc("try {");
459             for (int i = 0; i < _methods.length; ++i) {
460                 Method JavaDoc method = _methods[i];
461                 String JavaDoc name = getMethodVarName(method);
462                 Class JavaDoc clazz = method.getDeclaringClass();
463                 writer.write(name + " = " + clazz.getName()
464                              + ".class.getMethod(\"" + method.getName()
465                              + "\", " + "new Class[] {");
466                 Class JavaDoc[] args = method.getParameterTypes();
467                 for (int j = 0; j < args.length; ++j) {
468                     if (j > 0) {
469                         writer.write(", ");
470                     }
471                     writer.write(ClassHelper.getQualifiedName(args[j]) + ".class");
472                 }
473                 writer.writeln("});");
474             }
475             writer.decIndent();
476             writer.writelnInc("} catch (NoSuchMethodException exception) {");
477             writer.writelnDec(
478                     "throw new NoSuchMethodError(exception.getMessage());");
479             writer.writelnDec("}");
480             writer.writeln("}");
481         }
482     }
483
484     /**
485      * Generates a catch/rethrow clause.
486      *
487      * @param writer the writer to write to
488      * @param name the type name catch
489      * @throws IOException for any I/O error
490      */

491     protected void generateRethrow(SourceWriter writer, String JavaDoc name)
492         throws IOException JavaDoc {
493         writer.writelnInc("} catch (" + name + " exception) {");
494         writer.writelnDec("throw exception;");
495     }
496
497     /**
498      * Wraps primitive arguments into their objectified equivalents.
499      *
500      * @param clazz the argument class type
501      * @param name the argument name
502      * @return the wrapped argument name, or <code>name</code> if
503      * <code>clazz</code> isn't a primitive type
504      */

505     protected String JavaDoc wrapArgument(Class JavaDoc clazz, String JavaDoc name) {
506         String JavaDoc result;
507         if (clazz.isPrimitive()) {
508             Class JavaDoc wrapper = null;
509             for (int i = 0; i < MAPPINGS.length; ++i) {
510                 if (MAPPINGS[i][0] == clazz) {
511                     wrapper = MAPPINGS[i][1];
512                 }
513             }
514             result = "new " + ClassHelper.getQualifiedName(wrapper)
515                     + "(" + name + ")";
516         } else {
517             result = name;
518         }
519         return result;
520     }
521
522     /**
523      * Generates code to unwrap a return type If the return class type is a
524      * primitve, generates code to unbox the objectified primitve. If the return
525      * class type is an object, generates code to cast the variable name to that
526      * type.
527      *
528      * @param clazz the return class type
529      * @param name the variable name
530      * @return code to unwrap the return type
531      */

532     protected String JavaDoc unwrapReturn(Class JavaDoc clazz, String JavaDoc name) {
533         String JavaDoc result = null;
534         if (clazz.isPrimitive()) {
535             Class JavaDoc wrapper = null;
536             for (int i = 0; i < MAPPINGS.length; ++i) {
537                 if (MAPPINGS[i][0] == clazz) {
538                     wrapper = MAPPINGS[i][1];
539                     break;
540                 }
541             }
542             result = "((" + wrapper.getName() + ") " + name + ")."
543                     + clazz.getName() + "Value();";
544         } else {
545             result = "(" + ClassHelper.getQualifiedName(clazz) + ") " + name +
546                     ";";
547         }
548         return result;
549     }
550
551     /**
552      * Generates a name for a static Method variable.
553      *
554      * @param method the method
555      * @return a name for the variable
556      */

557     protected String JavaDoc getMethodVarName(Method JavaDoc method) {
558         return method.getName().toUpperCase() + "_"
559                 + Long.toHexString(MethodHelper.getMethodID(method));
560     }
561
562     /**
563      * Generates a unique instance name for an adapter.
564
565      * @param adapter the adapter class
566      * @return a unique instance name
567      */

568     protected String JavaDoc getAdapterInstanceName(Class JavaDoc adapter) {
569         // determine the class name, minus its package
570
String JavaDoc name;
571         String JavaDoc qualifiedName = adapter.getName();
572         int lastDot = qualifiedName.lastIndexOf(".");
573         if (lastDot != -1) {
574             name = qualifiedName.substring(lastDot + 1);
575         } else {
576             name = qualifiedName;
577         }
578         StringBuffer JavaDoc result = new StringBuffer JavaDoc(name.toUpperCase());
579         result.append("_");
580         result.append(Long.toHexString(qualifiedName.hashCode()));
581         return result.toString();
582     }
583
584     /**
585      * Returns a set of <code>ThrowableAdapter</code>s.
586      */

587     private HashMap JavaDoc getAdapters(Class JavaDoc[] adapterClasses)
588             throws Exception JavaDoc {
589         HashMap JavaDoc result = new HashMap JavaDoc();
590
591         for (int i = 0; i < adapterClasses.length; ++i) {
592             Class JavaDoc adapterClass = adapterClasses[i];
593             Object JavaDoc adapter = adapterClass.newInstance();
594             Method JavaDoc method = adapterClass.getMethod("getTarget", new Class JavaDoc[0]);
595             Class JavaDoc exceptionType = (Class JavaDoc) method.invoke(adapter, new Object JavaDoc[0]);
596             if (!Throwable JavaDoc.class.isAssignableFrom(exceptionType)) {
597                 throw new Exception JavaDoc(
598                         "Invalid exception class " + exceptionType.getName()
599                         + ": class doesn't extend " + Throwable JavaDoc.class);
600             }
601             result.put(exceptionType, adapterClass);
602         }
603         return result;
604     }
605
606     /**
607      * Returns the proxy superclass name for a given class.
608      *
609      * @param clazz the class
610      * @return the proxy superclass name
611      */

612     private static String JavaDoc getSuperclassProxy(Class JavaDoc clazz) {
613         String JavaDoc name = PROXY;
614         Class JavaDoc superClass = clazz.getSuperclass();
615         if (superClass != null) {
616             if (superClass.getInterfaces().length != 0) {
617                 name = superClass.getName() + PROXY_SUFFIX;
618             } else {
619                 name = getSuperclassProxy(superClass);
620             }
621         }
622         return name;
623     }
624
625     /**
626      * Helper class to compare two classes.
627      */

628     private static class ClassComparator implements Comparator JavaDoc {
629
630         /**
631          * Compare two classes.
632          *
633          * @param o1 the first class
634          * @param o2 the second class
635          * @return a negative integer, zero, or a positive integer if the first
636          * class is less than, equal to, or greater than the second.
637          */

638         public int compare(Object JavaDoc o1, Object JavaDoc o2) {
639             int result;
640             Class JavaDoc c1 = (Class JavaDoc) o1;
641             Class JavaDoc c2 = (Class JavaDoc) o2;
642             if (c1 == c2) {
643                 result = 0;
644             } else if (c1.isAssignableFrom(c2)) {
645                 result = 1;
646             } else {
647                 result = -1;
648             }
649             return result;
650         }
651
652     }
653
654     /**
655      * Helper to compare two methods on their names.
656      */

657     private static class MethodComparator implements Comparator JavaDoc {
658
659         /**
660          * Compare two methods.
661          *
662          * @param o1 the first method
663          * @param o2 the second method
664          * @return a negative integer, zero, or a positive integer if the first
665          * method is less than, equal to, or greater than the second.
666          */

667         public int compare(Object JavaDoc o1, Object JavaDoc o2) {
668             Method JavaDoc m1 = (Method JavaDoc) o1;
669             Method JavaDoc m2 = (Method JavaDoc) o2;
670             return m1.getName().compareTo(m2.getName());
671         }
672
673     }
674
675 }
676
Popular Tags