1 /* ClassFactory.java */
2
3 package org.quilt.cl;
4
5 import java.io.*;
6 import java.lang.reflect.Method;
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 interfaces[] = new String[] {
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 resName ) {
67 String 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 className, String 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[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 className = clazz.getClassName();
171 String 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 method =
183 this.getClass().getMethod( reflectMeth,
184 new Class[] { ClassGen.class } );
185 synthMethod =
186 (MethodGen) method.invoke( this,
187 new Object[] { clazz });
188 } catch (Exception e) {
189 if (! (e instanceof NoSuchMethodException) ) {
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[] { "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[] { "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[] { "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[] { "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[] { "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>_ -> I</td></tr>
465 * <tr><td>if:</td> <td>DUP</td> <td>I -> II</td></tr>
466 * <tr><td /> <td>IFLE (ret)</td> <td>II -> I</td></tr>
467 * <tr><td>loop:</td><td>ICONST_1</td> <td>I -> II</td></tr>
468 * <tr><td /> <td>ISUB</td> <td>II -> I</td></tr>
469 * <tr><td /> <td>GOTO (if)</td> <td>I -> I</td></tr>
470 * <tr><td>ret:</td> <td>IRETURN</td> <td>I -> _</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[] { "x" },
494 "runTest",
495 clazz.getClassName(),
496 instructions,
497 clazz.getConstantPool());
498
499 return returnMethod;
500 }
501 }
This page was automatically generated by Maven