KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > quilt > cl > ClassFactory


1 /* ClassFactory.java */
2
3 package org.quilt.cl;
4
5 import java.io.*;
6 import java.lang.reflect.Method JavaDoc;
7 import java.util.*;
8
9 import org.apache.bcel.Constants;
10 import org.apache.bcel.classfile.*;
11 import org.apache.bcel.generic.*;
12
13 /**
14  * Class synthesizer. Currently intended for debugging Quilt
15  * development and limited to instantiating classes with a
16  * no-argument constructor and a single method whose bytecode
17  * depends upon the base name of the class.
18  *
19  * By default classes whose name begins with <code>test.data.Test</code>
20  * will be synthesized. This can be set to a different string by a
21  * QuiltClassLoader method.
22  *
23  * @see QuiltClassLoader.
24  *
25  * @todo Add code for generating a method with nested tries.
26  * @todo Need a test method with multiple catches on a single try.
27  * @todo Add code for a method with a finally clause.
28  * @todo Make the prefix used to flag classes to be synthesized
29  * either a static constant of the class or a parameter
30  * to the class constructor.
31  * @todo Longer term: come up with a more generalized way for
32  * specifying classes to be synthesized; these should allow
33  * for more than just the constructor and runTest() methods.
34  *
35  * @author <a HREF="ddp@apache.org">David Dixon-Peugh</a>
36  * @author <a HREF="jddixon@users.sourceforge.net">Jim Dixon</a> -- major
37  * changes to the original code.
38  */

39 public class ClassFactory {
40
41     private static ClassFactory instance = new ClassFactory();
42     private String JavaDoc interfaces[] = new String JavaDoc[] {
43                                 "org.quilt.cl.RunTest" };
44
45     /** No-arg constructor, private because this is a singleton. */
46     private ClassFactory() { }
47
48     /**
49      * Use this method to get to the ClassFactory singleton.
50      *
51      * <p>XXX Is there any benefit to this being a singleton?</p>
52      */

53     public static ClassFactory getInstance() {
54         return instance;
55     }
56     /**
57      * Get the bytecode for a synthesized class. The name passed
58      * looks like "test/data/TestMyStuff.class". This is
59      * converted to "test.data.TestMyStuff". The "test.data.Test"
60      * prefix will later be stripped off and the base name, "MyStuff"
61      * in this case, used to determine which version of the runTest
62      * method to generate.
63      *
64      * @param resName Name of the class to be synthesized.
65      */

66     public InputStream getResourceAsStream( final String JavaDoc resName ) {
67         String JavaDoc className =
68             resName.substring(0, resName.indexOf(".class"));
69         className = className.replace( File.separatorChar, '.');
70
71         try {
72             PipedInputStream returnStream =
73                 new PipedInputStream();
74             PipedOutputStream outputStream =
75                 new PipedOutputStream( returnStream );
76             ClassGen clazz = ClassFactory.getInstance().
77                                     makeClass( className, resName);
78             clazz.getJavaClass().dump( outputStream );
79             return returnStream;
80         } catch (IOException exception) {
81             // DEBUG ////////////////////////////////////////
82
System.out.println("Unable to return Resource as InputStream.");
83             exception.printStackTrace();
84             return null;
85         }
86     }
87     /**
88      * Generate a class with a single no-arg constructor and a runTest
89      * method. By convention, if there is an underscore (_) in the
90      * class name, the underscore and everything after it are
91      * ignored in choosing method code. For example, if the class
92      * name is testWhile_1, then the method code comes from mgWhile
93      *
94      * <p>Methods available at this time are:</p>
95      * <ul>
96      * <li> mgDefault
97      * <li> mgIfThen
98      * <li> mgNPENoCatch
99      * <li> mgNPEWithCatch
100      * <li> mgSelect
101      * <li> mgWhile
102      * </ul>
103      *
104      * @param className Name of the class to be constructed.
105      * @param fileName Associated file name (??? XXX)
106      */

107     public ClassGen makeClass( String JavaDoc className, String JavaDoc fileName ) {
108         ClassGen newClass =
109             new ClassGen( className, "java.lang.Object", fileName,
110                   Constants.ACC_PUBLIC, interfaces);
111
112         MethodGen constructor = makeConstructor( newClass );
113         newClass.addMethod( constructor.getMethod() );
114
115         MethodGen testMethod = makeMethod( newClass );
116         org.apache.bcel.classfile.Method m = testMethod.getMethod();
117     
118         newClass.addMethod( m );
119
120         return newClass;
121     }
122     /**
123      * Creates the constructor for the synthesized class. It is a no-arg
124      * constructor that calls super.
125      *
126      * @param clazz Template for the class being synthesized.
127      * @return Method template for the constructor.
128      */

129     public MethodGen makeConstructor( ClassGen clazz ) {
130         InstructionFactory factory =
131             new InstructionFactory( clazz );
132
133         InstructionList instructions = new InstructionList();
134
135         instructions.append( new ALOAD(0) );
136         instructions.append( factory.createInvoke(
137                                 "java.lang.Object", "<init>", Type.VOID,
138                                 new Type[0], Constants.INVOKESPECIAL ) );
139         instructions.append( new RETURN() );
140
141         MethodGen returnMethod =
142             new MethodGen( Constants.ACC_PUBLIC, Type.VOID,
143                    new Type[0], new String JavaDoc[0],
144                    "<init>", clazz.getClassName(),
145                    instructions, clazz.getConstantPool() );
146
147         returnMethod.setMaxStack();
148         return returnMethod;
149     }
150     /**
151      * Creates a method with bytecode determined by the name of
152      * the class.
153      *
154      * If we have class test.data.TestBogus, then we strip off the
155      * "test.data.Test" prefix and call mgBogus() to get an
156      * instruction list.
157      *
158      * In the current version, if there is an underscore in the
159      * class name, then the underscore and everything following it
160      * will be ignored. So test.data.TestBogus_27 would result in a
161      * call to mgBogus(), not mgBogus_27().
162      *
163      * If the method (mgBogus in this case) doesn't exist, then we call
164      * mgDefault() to generate the bytecode.
165      *
166      * @param clazz Template for the class being produced.
167      * @return Template method with bytecode.
168      */

169     public MethodGen makeMethod( ClassGen clazz ) {
170         String JavaDoc className = clazz.getClassName();
171         String JavaDoc reflectMeth = "mg" + className.substring(
172                                             "test.data.Test".length() );
173         int underscore = reflectMeth.indexOf('_');
174         if (underscore > 0 ) {
175             reflectMeth = reflectMeth.substring(0, underscore);
176         }
177         InstructionList instructions = null;
178         List catchBlocks = new ArrayList();
179
180         MethodGen synthMethod = null;
181         try {
182             Method JavaDoc method =
183             this.getClass().getMethod( reflectMeth,
184                            new Class JavaDoc[] { ClassGen.class } );
185             synthMethod =
186                 (MethodGen) method.invoke( this,
187                                            new Object JavaDoc[] { clazz });
188         } catch (Exception JavaDoc e) {
189             if (! (e instanceof NoSuchMethodException JavaDoc) ) {
190                 e.printStackTrace();
191             }
192             System.out.println(
193                 "WARNING: ClassFactory using Default bytecode for "
194                     + className );
195             synthMethod = mgDefault( clazz );
196         }
197         synthMethod.setMaxStack(); // as suggested by BCEL docs
198
// jdd //////////////////////////////////////////////////////
199
if (reflectMeth.compareTo("test.data.TestNPEWithCatch") == 0 ) {
200             CodeExceptionGen cegs[] = synthMethod.getExceptionHandlers();
201             if (cegs.length != 1) {
202                 System.out.println ("INTERNAL ERROR: " + reflectMeth
203                     + "\n should have one exception handler, has "
204                     + cegs.length);
205             }
206         }
207         // end jdd
208
return synthMethod;
209     }
210     /**
211      * Generates bytecode for a method which simply returns 2. This
212      * is the method used if the class name is test.data.TestDefault.
213      *
214      * This method is also used if ClassFactory doesn't recognize the name;
215      * for example, if the class name is test.data.TestBogus, because there
216      * is no mgBogus method, this default method is used to generate the
217      * bytecode.
218      *
219      * <pre>
220      * public int runTest( int x ) {
221      * return 2;
222      * }
223      * </pre>
224      */

225     public MethodGen mgDefault( ClassGen clazz) {
226         InstructionList instructions = new InstructionList();
227         instructions.append( new ICONST( 2 ));
228         instructions.append( new IRETURN() );
229         
230         MethodGen returnMethod =
231             new MethodGen( Constants.ACC_PUBLIC,
232                            Type.INT,
233                            new Type[] { Type.INT },
234                            new String JavaDoc[] { "x" },
235                            "runTest",
236                            clazz.getClassName(),
237                            instructions,
238                            clazz.getConstantPool());
239         
240         return returnMethod;
241     }
242     /**
243      * Generates instructions for a method consisting of a single
244      * if-then clause.
245      *
246      * <pre>
247      * public int runTest( int x ) {
248      * if (x > 0) {
249      * return 3;
250      * } else {
251      * return 5;
252      * }
253      * }
254      * </pre>
255      */

256     public MethodGen mgIfThen( ClassGen clazz ) {
257         InstructionList instructions = new InstructionList();
258         instructions.append( new ILOAD( 1 ));
259
260         InstructionList thenClause = new InstructionList();
261         thenClause.append( new ICONST( 3 ));
262         thenClause.append( new IRETURN() );
263
264         InstructionList elseClause = new InstructionList();
265         elseClause.append( new ICONST( 5 ));
266         elseClause.append( new IRETURN() );
267     
268         InstructionHandle elseHandle =
269             instructions.append( elseClause );
270         InstructionHandle thenHandle =
271             instructions.append( thenClause );
272         instructions.insert( elseHandle, new IFGT( thenHandle ));
273
274         MethodGen returnMethod =
275             new MethodGen( Constants.ACC_PUBLIC,
276                            Type.INT,
277                            new Type[] { Type.INT },
278                            new String JavaDoc[] { "x" },
279                            "runTest",
280                            clazz.getClassName(),
281                            instructions,
282                            clazz.getConstantPool());
283         
284         return returnMethod;
285     }
286     /**
287      * Creates bytecode which will throw a NullPointerException
288      * without a catch block.
289      *
290      * <pre>
291      * public int runTest(int x) {
292      * null.runTest( 0 );
293      * return 0;
294      * }
295      * </pre>
296      */

297     public MethodGen mgNPENoCatch( ClassGen clazz ) {
298         InstructionFactory factory = new InstructionFactory( clazz );
299         InstructionList instructions = new InstructionList();
300         Type argTypes[] = new Type[1];
301
302         argTypes[0] = Type.INT;
303
304         instructions.append( new ACONST_NULL() );
305         instructions.append( new ICONST( 0 ));
306         instructions.append( factory.createInvoke( clazz.getClassName(),
307                                "runTest",
308                                Type.INT,
309                                argTypes,
310                                Constants.INVOKEVIRTUAL));
311
312         instructions.append( new ICONST( 0 ));
313         instructions.append( new IRETURN() );
314                     
315         MethodGen returnMethod =
316             new MethodGen( Constants.ACC_PUBLIC,
317                            Type.INT,
318                            new Type[] { Type.INT },
319                            new String JavaDoc[] { "x" },
320                            "runTest",
321                            clazz.getClassName(),
322                            instructions,
323                            clazz.getConstantPool());
324         
325         return returnMethod;
326     }
327     /**
328      * Returns bytecode which will throw a NullPointerException,
329      * but it will catch the NPE.
330      *
331      * <pre>
332      * try {
333      * null.runTest( 0 );
334      * return -1;
335      * } catch (NullPointerException npe) {
336      * return 3;
337      * }
338      * </pre>
339      */

340     public MethodGen mgNPEWithCatch( ClassGen clazz ) {
341         InstructionFactory factory = new InstructionFactory( clazz );
342         InstructionList instructions = new InstructionList();
343     
344         Type argTypes[] = new Type[1];
345
346         argTypes[0] = Type.INT;
347
348         ObjectType npeType = new ObjectType(
349                                         "java.lang.NullPointerException" );
350         instructions.append( new ACONST_NULL() );
351         instructions.append( new ICONST( 0 ));
352         instructions.append( factory.createInvoke( clazz.getClassName(),
353                                "runTest",
354                                Type.INT,
355                                argTypes,
356                                Constants.INVOKEVIRTUAL));
357                 
358         instructions.append( new ICONST( -1 ));
359         instructions.append( new IRETURN() );
360
361         InstructionHandle handler =
362             instructions.append( new ICONST( 3 ));
363         instructions.append( new IRETURN() );
364
365         // we expect an exception to occur, and then 3 to be
366
// returned by the handler
367
MethodGen returnMethod =
368             new MethodGen( Constants.ACC_PUBLIC,
369                            Type.INT,
370                            new Type[] { Type.INT },
371                            new String JavaDoc[] { "x" },
372                            "runTest",
373                            clazz.getClassName(),
374                            instructions,
375                            clazz.getConstantPool());
376         // jdd
377
CodeExceptionGen ceg =
378         // end jdd
379
returnMethod.addExceptionHandler(instructions.getStart(),
380                                          handler.getPrev(), handler,
381                                          npeType );
382
383         // jdd: at this point the MethodGen should have a table of
384
// exception handlers with one entry
385
returnMethod.addException("java.lang.NullPointerException"); //jdd
386
CodeExceptionGen cegs[] = returnMethod.getExceptionHandlers();
387         
388         // IN PRACTICE, THIS WORKS: the two are the same
389
if (ceg != cegs[0]) {
390             System.out.println(
391                     " INTERNAL ERROR: exception handler added not found");
392         }
393         // end jdd
394
return returnMethod;
395     }
396     /**
397      * Generates bytecode for a switch statement:
398      *
399      * <pre>
400      * int runTest (int x) {
401      * switch (x) {
402      * case 1: return 1;
403      * case 2: return 3;
404      * case 3: return 5;
405      * default: return 2;
406      * }
407      * }
408      * </pre>
409      */

410     public MethodGen mgSelect( ClassGen clazz ) {
411
412         InstructionList instructions = new InstructionList();
413         instructions.append( new ILOAD( 1 ));
414         InstructionHandle caseHandles[] = new InstructionHandle[3];
415         int caseMatches[] = new int[3];
416
417         for (short i = 0; i < 3; i++) {
418             InstructionList caseList = new InstructionList();
419             caseList.append( new SIPUSH( (short) (2*i + 1) ));
420             caseList.append( new IRETURN() );
421
422             caseHandles[i] = instructions.append( caseList );
423             caseMatches[i] = i + 1;
424         }
425         InstructionList dCase = new InstructionList();
426         dCase.append( new SIPUSH( (short) 2 ));
427         dCase.append( new IRETURN() );
428         InstructionHandle dHand = instructions.append( dCase );
429
430         instructions.insert( caseHandles[0],
431                      new LOOKUPSWITCH( caseMatches,
432                                        caseHandles,
433                                        dHand ));
434         MethodGen returnMethod =
435             new MethodGen( Constants.ACC_PUBLIC,
436                            Type.INT,
437                            new Type[] { Type.INT },
438                            new String JavaDoc[] { "x" },
439                            "runTest",
440                            clazz.getClassName(),
441                            instructions,
442                            clazz.getConstantPool());
443         
444         return returnMethod;
445     }
446     /**
447      * Generates code for a while loop. The while loop returns 0 if
448      * the parameter x is greater than or equal to zero, but x
449      * otherwise.
450      *
451      * <pre>
452      * int runTest(int x) {
453      * while (x > 0) {
454      * x --;
455      * }
456      * return x;
457      * }
458      * </pre>
459      *
460      * <p>The actual bytecode produced is:</p>
461      *
462      * <table cellspacing="10">
463      * <tr><th>Label</th><th>Instruction</th> <th>Stack</th>
464      * <tr><td /> <td>ILOAD</td> <td>_ -&gt; I</td></tr>
465      * <tr><td>if:</td> <td>DUP</td> <td>I -&gt; II</td></tr>
466      * <tr><td /> <td>IFLE (ret)</td> <td>II -&gt; I</td></tr>
467      * <tr><td>loop:</td><td>ICONST_1</td> <td>I -&gt; II</td></tr>
468      * <tr><td /> <td>ISUB</td> <td>II -&gt; I</td></tr>
469      * <tr><td /> <td>GOTO (if)</td> <td>I -&gt; I</td></tr>
470      * <tr><td>ret:</td> <td>IRETURN</td> <td>I -&gt; _</td></tr>
471      * </table>
472      */

473     public MethodGen mgWhile( ClassGen clazz ) {
474         InstructionList instructions = new InstructionList();
475         instructions.append( new ILOAD( 1 ));
476         InstructionHandle ifHandle =
477             instructions.append( new DUP() );
478
479         InstructionHandle loopHandle =
480             instructions.append( new ICONST( 1 ) );
481         instructions.append( new ISUB() );
482         instructions.append( new GOTO( ifHandle ));
483
484         InstructionHandle retHandle =
485             instructions.append( new IRETURN() );
486     
487         instructions.insert( loopHandle, new IFLE( retHandle ));
488
489         MethodGen returnMethod =
490             new MethodGen( Constants.ACC_PUBLIC,
491                            Type.INT,
492                            new Type[] { Type.INT },
493                            new String JavaDoc[] { "x" },
494                            "runTest",
495                            clazz.getClassName(),
496                            instructions,
497                            clazz.getConstantPool());
498         
499         return returnMethod;
500     }
501 }
502
Popular Tags