View Javadoc
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