001: /* ClassFactory.java */
002:
003: package org.quilt.cl;
004:
005: import java.io.*;
006: import java.lang.reflect.Method;
007: import java.util.*;
008:
009: import org.apache.bcel.Constants;
010: import org.apache.bcel.classfile.*;
011: import org.apache.bcel.generic.*;
012:
013: /**
014: * Class synthesizer. Currently intended for debugging Quilt
015: * development and limited to instantiating classes with a
016: * no-argument constructor and a single method whose bytecode
017: * depends upon the base name of the class.
018: *
019: * By default classes whose name begins with <code>test.data.Test</code>
020: * will be synthesized. This can be set to a different string by a
021: * QuiltClassLoader method.
022: *
023: * @see QuiltClassLoader.
024: *
025: * @todo Add code for generating a method with nested tries.
026: * @todo Need a test method with multiple catches on a single try.
027: * @todo Add code for a method with a finally clause.
028: * @todo Make the prefix used to flag classes to be synthesized
029: * either a static constant of the class or a parameter
030: * to the class constructor.
031: * @todo Longer term: come up with a more generalized way for
032: * specifying classes to be synthesized; these should allow
033: * for more than just the constructor and runTest() methods.
034: *
035: * @author <a href="ddp@apache.org">David Dixon-Peugh</a>
036: * @author <a href="jddixon@users.sourceforge.net">Jim Dixon</a> -- major
037: * changes to the original code.
038: */
039: public class ClassFactory {
040:
041: private static ClassFactory instance = new ClassFactory();
042: private String interfaces[] = new String[] { "org.quilt.cl.RunTest" };
043:
044: /** No-arg constructor, private because this is a singleton. */
045: private ClassFactory() {
046: }
047:
048: /**
049: * Use this method to get to the ClassFactory singleton.
050: *
051: * <p>XXX Is there any benefit to this being a singleton?</p>
052: */
053: public static ClassFactory getInstance() {
054: return instance;
055: }
056:
057: /**
058: * Get the bytecode for a synthesized class. The name passed
059: * looks like "test/data/TestMyStuff.class". This is
060: * converted to "test.data.TestMyStuff". The "test.data.Test"
061: * prefix will later be stripped off and the base name, "MyStuff"
062: * in this case, used to determine which version of the runTest
063: * method to generate.
064: *
065: * @param resName Name of the class to be synthesized.
066: */
067: public InputStream getResourceAsStream(final String resName) {
068: String className = resName.substring(0, resName
069: .indexOf(".class"));
070: className = className.replace(File.separatorChar, '.');
071:
072: try {
073: PipedInputStream returnStream = new PipedInputStream();
074: PipedOutputStream outputStream = new PipedOutputStream(
075: returnStream);
076: ClassGen clazz = ClassFactory.getInstance().makeClass(
077: className, resName);
078: clazz.getJavaClass().dump(outputStream);
079: return returnStream;
080: } catch (IOException exception) {
081: // DEBUG ////////////////////////////////////////
082: System.out
083: .println("Unable to return Resource as InputStream.");
084: exception.printStackTrace();
085: return null;
086: }
087: }
088:
089: /**
090: * Generate a class with a single no-arg constructor and a runTest
091: * method. By convention, if there is an underscore (_) in the
092: * class name, the underscore and everything after it are
093: * ignored in choosing method code. For example, if the class
094: * name is testWhile_1, then the method code comes from mgWhile
095: *
096: * <p>Methods available at this time are:</p>
097: * <ul>
098: * <li> mgDefault
099: * <li> mgIfThen
100: * <li> mgNPENoCatch
101: * <li> mgNPEWithCatch
102: * <li> mgSelect
103: * <li> mgWhile
104: * </ul>
105: *
106: * @param className Name of the class to be constructed.
107: * @param fileName Associated file name (??? XXX)
108: */
109: public ClassGen makeClass(String className, String fileName) {
110: ClassGen newClass = new ClassGen(className, "java.lang.Object",
111: fileName, Constants.ACC_PUBLIC, interfaces);
112:
113: MethodGen constructor = makeConstructor(newClass);
114: newClass.addMethod(constructor.getMethod());
115:
116: MethodGen testMethod = makeMethod(newClass);
117: org.apache.bcel.classfile.Method m = testMethod.getMethod();
118:
119: newClass.addMethod(m);
120:
121: return newClass;
122: }
123:
124: /**
125: * Creates the constructor for the synthesized class. It is a no-arg
126: * constructor that calls super.
127: *
128: * @param clazz Template for the class being synthesized.
129: * @return Method template for the constructor.
130: */
131: public MethodGen makeConstructor(ClassGen clazz) {
132: InstructionFactory factory = new InstructionFactory(clazz);
133:
134: InstructionList instructions = new InstructionList();
135:
136: instructions.append(new ALOAD(0));
137: instructions.append(factory.createInvoke("java.lang.Object",
138: "<init>", Type.VOID, new Type[0],
139: Constants.INVOKESPECIAL));
140: instructions.append(new RETURN());
141:
142: MethodGen returnMethod = new MethodGen(Constants.ACC_PUBLIC,
143: Type.VOID, new Type[0], new String[0], "<init>", clazz
144: .getClassName(), instructions, clazz
145: .getConstantPool());
146:
147: returnMethod.setMaxStack();
148: return returnMethod;
149: }
150:
151: /**
152: * Creates a method with bytecode determined by the name of
153: * the class.
154: *
155: * If we have class test.data.TestBogus, then we strip off the
156: * "test.data.Test" prefix and call mgBogus() to get an
157: * instruction list.
158: *
159: * In the current version, if there is an underscore in the
160: * class name, then the underscore and everything following it
161: * will be ignored. So test.data.TestBogus_27 would result in a
162: * call to mgBogus(), not mgBogus_27().
163: *
164: * If the method (mgBogus in this case) doesn't exist, then we call
165: * mgDefault() to generate the bytecode.
166: *
167: * @param clazz Template for the class being produced.
168: * @return Template method with bytecode.
169: */
170: public MethodGen makeMethod(ClassGen clazz) {
171: String className = clazz.getClassName();
172: String reflectMeth = "mg"
173: + className.substring("test.data.Test".length());
174: int underscore = reflectMeth.indexOf('_');
175: if (underscore > 0) {
176: reflectMeth = reflectMeth.substring(0, underscore);
177: }
178: InstructionList instructions = null;
179: List catchBlocks = new ArrayList();
180:
181: MethodGen synthMethod = null;
182: try {
183: Method method = this .getClass().getMethod(reflectMeth,
184: new Class[] { ClassGen.class });
185: synthMethod = (MethodGen) method.invoke(this ,
186: new Object[] { clazz });
187: } catch (Exception e) {
188: if (!(e instanceof NoSuchMethodException)) {
189: e.printStackTrace();
190: }
191: System.out
192: .println("WARNING: ClassFactory using Default bytecode for "
193: + className);
194: synthMethod = mgDefault(clazz);
195: }
196: synthMethod.setMaxStack(); // as suggested by BCEL docs
197: // jdd //////////////////////////////////////////////////////
198: if (reflectMeth.compareTo("test.data.TestNPEWithCatch") == 0) {
199: CodeExceptionGen cegs[] = synthMethod
200: .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: /**
212: * Generates bytecode for a method which simply returns 2. This
213: * is the method used if the class name is test.data.TestDefault.
214: *
215: * This method is also used if ClassFactory doesn't recognize the name;
216: * for example, if the class name is test.data.TestBogus, because there
217: * is no mgBogus method, this default method is used to generate the
218: * bytecode.
219: *
220: * <pre>
221: * public int runTest( int x ) {
222: * return 2;
223: * }
224: * </pre>
225: */
226: public MethodGen mgDefault(ClassGen clazz) {
227: InstructionList instructions = new InstructionList();
228: instructions.append(new ICONST(2));
229: instructions.append(new IRETURN());
230:
231: MethodGen returnMethod = new MethodGen(Constants.ACC_PUBLIC,
232: Type.INT, new Type[] { Type.INT },
233: new String[] { "x" }, "runTest", clazz.getClassName(),
234: instructions, clazz.getConstantPool());
235:
236: return returnMethod;
237: }
238:
239: /**
240: * Generates instructions for a method consisting of a single
241: * if-then clause.
242: *
243: * <pre>
244: * public int runTest( int x ) {
245: * if (x > 0) {
246: * return 3;
247: * } else {
248: * return 5;
249: * }
250: * }
251: * </pre>
252: */
253: public MethodGen mgIfThen(ClassGen clazz) {
254: InstructionList instructions = new InstructionList();
255: instructions.append(new ILOAD(1));
256:
257: InstructionList thenClause = new InstructionList();
258: thenClause.append(new ICONST(3));
259: thenClause.append(new IRETURN());
260:
261: InstructionList elseClause = new InstructionList();
262: elseClause.append(new ICONST(5));
263: elseClause.append(new IRETURN());
264:
265: InstructionHandle elseHandle = instructions.append(elseClause);
266: InstructionHandle thenHandle = instructions.append(thenClause);
267: instructions.insert(elseHandle, new IFGT(thenHandle));
268:
269: MethodGen returnMethod = new MethodGen(Constants.ACC_PUBLIC,
270: Type.INT, new Type[] { Type.INT },
271: new String[] { "x" }, "runTest", clazz.getClassName(),
272: instructions, clazz.getConstantPool());
273:
274: return returnMethod;
275: }
276:
277: /**
278: * Creates bytecode which will throw a NullPointerException
279: * without a catch block.
280: *
281: * <pre>
282: * public int runTest(int x) {
283: * null.runTest( 0 );
284: * return 0;
285: * }
286: * </pre>
287: */
288: public MethodGen mgNPENoCatch(ClassGen clazz) {
289: InstructionFactory factory = new InstructionFactory(clazz);
290: InstructionList instructions = new InstructionList();
291: Type argTypes[] = new Type[1];
292:
293: argTypes[0] = Type.INT;
294:
295: instructions.append(new ACONST_NULL());
296: instructions.append(new ICONST(0));
297: instructions
298: .append(factory.createInvoke(clazz.getClassName(),
299: "runTest", Type.INT, argTypes,
300: Constants.INVOKEVIRTUAL));
301:
302: instructions.append(new ICONST(0));
303: instructions.append(new IRETURN());
304:
305: MethodGen returnMethod = new MethodGen(Constants.ACC_PUBLIC,
306: Type.INT, new Type[] { Type.INT },
307: new String[] { "x" }, "runTest", clazz.getClassName(),
308: instructions, clazz.getConstantPool());
309:
310: return returnMethod;
311: }
312:
313: /**
314: * Returns bytecode which will throw a NullPointerException,
315: * but it will catch the NPE.
316: *
317: * <pre>
318: * try {
319: * null.runTest( 0 );
320: * return -1;
321: * } catch (NullPointerException npe) {
322: * return 3;
323: * }
324: * </pre>
325: */
326: public MethodGen mgNPEWithCatch(ClassGen clazz) {
327: InstructionFactory factory = new InstructionFactory(clazz);
328: InstructionList instructions = new InstructionList();
329:
330: Type argTypes[] = new Type[1];
331:
332: argTypes[0] = Type.INT;
333:
334: ObjectType npeType = new ObjectType(
335: "java.lang.NullPointerException");
336: instructions.append(new ACONST_NULL());
337: instructions.append(new ICONST(0));
338: instructions
339: .append(factory.createInvoke(clazz.getClassName(),
340: "runTest", Type.INT, argTypes,
341: Constants.INVOKEVIRTUAL));
342:
343: instructions.append(new ICONST(-1));
344: instructions.append(new IRETURN());
345:
346: InstructionHandle handler = instructions.append(new ICONST(3));
347: instructions.append(new IRETURN());
348:
349: // we expect an exception to occur, and then 3 to be
350: // returned by the handler
351: MethodGen returnMethod = new MethodGen(Constants.ACC_PUBLIC,
352: Type.INT, new Type[] { Type.INT },
353: new String[] { "x" }, "runTest", clazz.getClassName(),
354: instructions, clazz.getConstantPool());
355: // jdd
356: CodeExceptionGen ceg =
357: // end jdd
358: returnMethod.addExceptionHandler(instructions.getStart(),
359: handler.getPrev(), handler, npeType);
360:
361: // jdd: at this point the MethodGen should have a table of
362: // exception handlers with one entry
363: returnMethod.addException("java.lang.NullPointerException"); //jdd
364: CodeExceptionGen cegs[] = returnMethod.getExceptionHandlers();
365:
366: // IN PRACTICE, THIS WORKS: the two are the same
367: if (ceg != cegs[0]) {
368: System.out
369: .println(" INTERNAL ERROR: exception handler added not found");
370: }
371: // end jdd
372: return returnMethod;
373: }
374:
375: /**
376: * Generates bytecode for a switch statement:
377: *
378: * <pre>
379: * int runTest (int x) {
380: * switch (x) {
381: * case 1: return 1;
382: * case 2: return 3;
383: * case 3: return 5;
384: * default: return 2;
385: * }
386: * }
387: * </pre>
388: */
389: public MethodGen mgSelect(ClassGen clazz) {
390:
391: InstructionList instructions = new InstructionList();
392: instructions.append(new ILOAD(1));
393: InstructionHandle caseHandles[] = new InstructionHandle[3];
394: int caseMatches[] = new int[3];
395:
396: for (short i = 0; i < 3; i++) {
397: InstructionList caseList = new InstructionList();
398: caseList.append(new SIPUSH((short) (2 * i + 1)));
399: caseList.append(new IRETURN());
400:
401: caseHandles[i] = instructions.append(caseList);
402: caseMatches[i] = i + 1;
403: }
404: InstructionList dCase = new InstructionList();
405: dCase.append(new SIPUSH((short) 2));
406: dCase.append(new IRETURN());
407: InstructionHandle dHand = instructions.append(dCase);
408:
409: instructions.insert(caseHandles[0], new LOOKUPSWITCH(
410: caseMatches, caseHandles, dHand));
411: MethodGen returnMethod = new MethodGen(Constants.ACC_PUBLIC,
412: Type.INT, new Type[] { Type.INT },
413: new String[] { "x" }, "runTest", clazz.getClassName(),
414: instructions, clazz.getConstantPool());
415:
416: return returnMethod;
417: }
418:
419: /**
420: * Generates code for a while loop. The while loop returns 0 if
421: * the parameter x is greater than or equal to zero, but x
422: * otherwise.
423: *
424: * <pre>
425: * int runTest(int x) {
426: * while (x > 0) {
427: * x --;
428: * }
429: * return x;
430: * }
431: * </pre>
432: *
433: * <p>The actual bytecode produced is:</p>
434: *
435: * <table cellspacing="10">
436: * <tr><th>Label</th><th>Instruction</th> <th>Stack</th>
437: * <tr><td /> <td>ILOAD</td> <td>_ -> I</td></tr>
438: * <tr><td>if:</td> <td>DUP</td> <td>I -> II</td></tr>
439: * <tr><td /> <td>IFLE (ret)</td> <td>II -> I</td></tr>
440: * <tr><td>loop:</td><td>ICONST_1</td> <td>I -> II</td></tr>
441: * <tr><td /> <td>ISUB</td> <td>II -> I</td></tr>
442: * <tr><td /> <td>GOTO (if)</td> <td>I -> I</td></tr>
443: * <tr><td>ret:</td> <td>IRETURN</td> <td>I -> _</td></tr>
444: * </table>
445: */
446: public MethodGen mgWhile(ClassGen clazz) {
447: InstructionList instructions = new InstructionList();
448: instructions.append(new ILOAD(1));
449: InstructionHandle ifHandle = instructions.append(new DUP());
450:
451: InstructionHandle loopHandle = instructions
452: .append(new ICONST(1));
453: instructions.append(new ISUB());
454: instructions.append(new GOTO(ifHandle));
455:
456: InstructionHandle retHandle = instructions
457: .append(new IRETURN());
458:
459: instructions.insert(loopHandle, new IFLE(retHandle));
460:
461: MethodGen returnMethod = new MethodGen(Constants.ACC_PUBLIC,
462: Type.INT, new Type[] { Type.INT },
463: new String[] { "x" }, "runTest", clazz.getClassName(),
464: instructions, clazz.getConstantPool());
465:
466: return returnMethod;
467: }
468: }
|