001/* 002 * BioJava development code 003 * 004 * This code may be freely distributed and modified under the 005 * terms of the GNU Lesser General Public Licence. This should 006 * be distributed with the code. If you do not have a copy, 007 * see: 008 * 009 * http://www.gnu.org/copyleft/lesser.html 010 * 011 * Copyright for this code is held jointly by the individual 012 * authors. These should be listed in @author doc comments. 013 * 014 * For more information on the BioJava project and its aims, 015 * or to join the biojava-l mailing list, visit the home page 016 * at: 017 * 018 * http://www.biojava.org/ 019 * 020 */ 021package org.biojava.utils.bytecode; 022 023import java.util.*; 024import java.io.*; 025 026/** 027 * A CodeClass implementation that is used to generate new classes. 028 * 029 * <p> 030 * When creating classes, instantiate one of these, add fields and methods. 031 * Associate CodeGenerator instances with methods. Then, use 032 * GeneratedClassLoader to make a new class. 033 * </p> 034 * 035 * @author Matthew Pocock 036 */ 037public class GeneratedCodeClass implements CodeClass { 038 private String name; 039 private CodeClass superClass; 040 private List interfaces; 041 private int modifiers; 042 private Map methods; 043 private Map fields; 044 private String sourceFile; 045 private boolean deprecated; 046 047 { 048 methods = new HashMap(); 049 fields = new HashMap(); 050 sourceFile = null; 051 } 052 053 public GeneratedCodeClass( 054 String name, 055 Class superClass, 056 Class[] interfaces, 057 int modifiers 058 ) throws CodeException 059 { 060 this.name = name; 061 this.modifiers = modifiers; 062 this.superClass = IntrospectedCodeClass.forClass(superClass); 063 this.interfaces = new ArrayList(Arrays.asList(interfaces)); 064 for (Iterator i = this.interfaces.iterator(); i.hasNext();) { 065 Class clazz = (Class) i.next(); 066 if (!clazz.isInterface()) { 067 throw new CodeException( 068 "Attempted to create class implemneting non-interface " + clazz 069 ); 070 } 071 } 072 } 073 074 public GeneratedCodeClass(String name, 075 CodeClass superClass, 076 CodeClass[] interfaces, 077 int modifiers) 078 throws CodeException 079 { 080 this.name = name; 081 this.modifiers = modifiers; 082 this.superClass = superClass; 083 this.interfaces = new ArrayList(Arrays.asList(interfaces)); 084 for (Iterator i = this.interfaces.iterator(); i.hasNext();) { 085 Object obj = i.next(); 086 if (!(obj instanceof CodeClass)) { 087 throw new CodeException( 088 "Interface list must contain CodeClass instances" 089 ); 090 } 091 } 092 } 093 094 /** 095 * Set the source file associated with this code class. 096 * 097 * <p> 098 * The source file appears in debugging output and stack traces. Use this 099 * method to set the source file that this generated class will clame to be 100 * from. You can use non-file names e.g. uri:myGenerator:proxy/foo 101 * </p> 102 * 103 * <p> 104 * To un-set the source file, use null. 105 * </p> 106 * 107 * @param sourceFile the source file for this class 108 */ 109 public void setSourceFile(String sourceFile) { 110 this.sourceFile = sourceFile; 111 } 112 113 /** 114 * Get the source file associated with this code class. 115 * 116 * <p> 117 * Null indicates that no source file is set. 118 * </p> 119 * 120 * @return the source file for this code class 121 */ 122 public String getSourceFile() { 123 return sourceFile; 124 } 125 126 /** 127 * Set the deprecation flag. 128 * 129 * <p> 130 * If deprecated is true, the class will be flagged as deprecated. 131 * </p> 132 * 133 * @param deprecated the new value of the deprecation 134 */ 135 public void setDeprecated(boolean deprecated) { 136 this.deprecated = deprecated; 137 } 138 139 /** 140 * Get the deprecation flag. 141 * 142 * @return wether or not this class is deprecated 143 */ 144 public boolean isDeprecated() { 145 return deprecated; 146 } 147 148 public List getInterfaces() { 149 return Collections.unmodifiableList(interfaces); 150 } 151 152 public Set getMethods() { 153 return methods.keySet(); 154 } 155 156 public Set getMethodsByName(String name) { 157 Set all = getMethods(); 158 Set some = new HashSet(); 159 for (Iterator i = all.iterator(); i.hasNext();) { 160 CodeMethod m = (CodeMethod) i.next(); 161 if (m.getName().equals(name)) { 162 some.add(m); 163 } 164 } 165 return some; 166 } 167 168 public CodeMethod getConstructor(CodeClass[] args) 169 throws NoSuchMethodException 170 { 171 return getMethod("<init>", args); 172 } 173 174 public CodeMethod getMethod(String name, CodeClass[] args) 175 throws NoSuchMethodException 176 { 177 Set poss = getMethodsByName(name); 178 METHOD_LOOP: 179 for (Iterator i = poss.iterator(); i.hasNext();) { 180 CodeMethod meth = (CodeMethod) i.next(); 181 if (meth.numParameters() != args.length) { 182 continue METHOD_LOOP; 183 } 184 for (int j = 0; j < args.length; j++) { 185 if (!meth.getParameterType(j).equals(args[j])) { 186 continue METHOD_LOOP; 187 } 188 } 189 return meth; 190 } 191 192 StringBuffer methodSig = new StringBuffer( 193 "Could not find method " + getName() + "." + name + "(" 194 ); 195 if (args.length > 0) { 196 methodSig.append(args[0].getName()); 197 } 198 for (int i = 1; i < args.length; i++) { 199 methodSig.append(","); 200 methodSig.append(args[i].getName()); 201 } 202 methodSig.append(")"); 203 throw new NoSuchMethodException(methodSig.toString()); 204 } 205 206 public Set getFields() { 207 return fields.keySet(); 208 } 209 210 public CodeClass getSuperClass() { 211 return superClass; 212 } 213 214 public CodeField getFieldByName(String name) 215 throws NoSuchFieldException 216 { 217 CodeField f = (CodeField) fields.get(name); 218 if (f == null) { 219 throw new NoSuchFieldException("No field for " + name + " in class " + getName()); 220 } 221 return f; 222 } 223 224 public String getName() { 225 return name; 226 } 227 228 public String getJName() { 229 String name = getName(); 230 StringBuffer sb = new StringBuffer(); 231 for (int i = 0; i < name.length(); ++i) { 232 char c = name.charAt(i); 233 if (c == '.') 234 sb.append('/'); 235 else 236 sb.append(c); 237 } 238 return sb.toString(); 239 } 240 241 public int getModifiers() { 242 return modifiers; 243 } 244 245 public String getDescriptor() { 246 String name = getName(); 247 StringBuffer sb = new StringBuffer(); 248 sb.append('L'); 249 for (int i = 0; i < name.length(); ++i) { 250 char c = name.charAt(i); 251 if (c == '.') 252 sb.append('/'); 253 else 254 sb.append(c); 255 } 256 sb.append(';'); 257 return sb.toString(); 258 } 259 260 /** 261 * Create a new method. 262 * 263 * <p> 264 * This defines the shape of a method that will be generated. Use 265 * {@link #setCodeGenerator} to associate code with the method. 266 * </p> 267 * 268 * <p> 269 * The argNames will become the names of local variables for each argument. 270 * </p> 271 * 272 * @param name the method name 273 * @param type the return type 274 * @param args arguments taken 275 * @param argNames names of the arguments 276 * @param mods access modifiers 277 * @return a new GeneratedCodeMethod 278 * @throws CodeException if the method could not be created 279 */ 280 public GeneratedCodeMethod createMethod( 281 String name, 282 CodeClass type, 283 CodeClass[] args, 284 String[] argNames, 285 int mods 286 ) 287 throws CodeException 288 { 289 GeneratedCodeMethod cm = new GeneratedCodeMethod(this, name, type, args, argNames, mods); 290 if (methods.containsKey(cm)) { 291 throw new CodeException("Attempt to create multiple methods with same signatures."); 292 } 293 294 methods.put(cm, null); 295 return cm; 296 } 297 298 /** 299 * Create a new method. 300 * 301 * <p> 302 * This defines the shape of a method that will be generated. Use 303 * {@link #setCodeGenerator} to associate code with the method. 304 * </p> 305 * 306 * @param name the method name 307 * @param type the return type 308 * @param args arguments taken 309 * @param mods access modifiers 310 * @return a new GeneratedCodeMethod 311 * @throws CodeException if the method could not be created 312 */ 313 public GeneratedCodeMethod createMethod( 314 String name, 315 CodeClass type, 316 CodeClass[] args, 317 int mods 318 ) 319 throws CodeException 320 { 321 return createMethod(name, type, args, new String[0], mods); 322 } 323 324 public CodeField createField(String name, CodeClass clazz, int mods) 325 throws CodeException 326 { 327 if (fields.containsKey(name)) { 328 throw new CodeException("Attempt to create multiple fields named " + name); 329 } 330 331 CodeField cf = new CodeField(this, name, clazz, mods); 332 fields.put(name, cf); 333 return cf; 334 } 335 336 public void setCodeGenerator(CodeMethod method, CodeGenerator cg) 337 throws CodeException 338 { 339 if (!methods.containsKey(method)) { 340 throw new CodeException("Class doesn't provide method " + method.getName()); 341 } 342 343 methods.put(method, cg); 344 } 345 346 public void createCode(OutputStream os) 347 throws IOException, CodeException 348 { 349 DataOutputStream dos = new DataOutputStream(os); 350 351 // Write classfile header 352 353 dos.writeInt((int) (0xcafebabe)); // Magic 354 dos.writeShort(3); // Minor version 355 dos.writeShort(45); // Major version (check!) 356 357 ConstantPool cp = new ConstantPool(); 358 359 // The rest of the classfile gets written to a buffer, accumulating a constant pool along the way 360 361 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 362 DataOutputStream bdos = new DataOutputStream(baos); 363 364 bdos.writeShort(modifiers); 365 bdos.writeShort(cp.resolveClass(this)); // this-class ID 366 bdos.writeShort(cp.resolveClass(superClass)); // super-class ID 367 bdos.writeShort(interfaces.size()); // number_of_interfaces 368 for (Iterator i = interfaces.iterator(); i.hasNext();) { 369 bdos.writeShort(cp.resolveClass((CodeClass) i.next())); // interface ID 370 } 371 372 // Write the fields 373 374 bdos.writeShort(fields.size()); 375 for (Iterator i = fields.values().iterator(); i.hasNext();) { 376 CodeField cf = (CodeField) i.next(); 377 bdos.writeShort(cf.getModifiers()); 378 bdos.writeShort(cp.resolveUtf8(cf.getName())); 379 bdos.writeShort(cp.resolveUtf8(cf.getType().getDescriptor())); 380 bdos.writeShort(0); // No attributes right now 381 } 382 383 // Write the methods (wahey!) 384 385 Set methSet = methods.entrySet(); 386 bdos.writeShort(methSet.size()); 387 for (Iterator i = methSet.iterator(); i.hasNext();) { 388 Map.Entry me = (Map.Entry) i.next(); 389 GeneratedCodeMethod cm = (GeneratedCodeMethod) me.getKey(); 390 CodeGenerator cg = (CodeGenerator) me.getValue(); 391 392 bdos.writeShort(cm.getModifiers()); // access_flags 393 bdos.writeShort(cp.resolveUtf8(cm.getName())); // name_index 394 bdos.writeShort(cp.resolveUtf8(cm.getDescriptor())); // descriptor_index 395 396 // Actually generate the code 397 MethodRootContext ctx = new MethodRootContext(this, cm, cp); 398 ctx.open(); 399 400 LocalVariable thisP = cm.getThis(); 401 if (thisP != null) { 402 // Non-static method 403 ctx.resolveLocal(thisP); 404 } 405 for (int parm = 0; parm < cm.numParameters(); ++parm) { 406 ctx.resolveLocal(cm.getVariable(parm)); 407 } 408 409 cg.writeCode(ctx); 410 ctx.close(); 411 412 Set thrownExceptions = cm.getThrownExceptions(); 413 414 // number of method attirbutes 415 int numMethAttrs = 1; // we always have code 416 417 // do we have exceptions? 418 if(!thrownExceptions.isEmpty()) { 419 numMethAttrs++; 420 } 421 422 bdos.writeShort(numMethAttrs); // attributes_count 423 424 // start attribute_info for method 425 426 // Code attribute 427 List exceptionTable = ctx.getExceptionTable(); 428 429 bdos.writeShort(cp.resolveUtf8("Code")); 430 bdos.writeInt(12 + ctx.getOffset() + exceptionTable.size() * 8); 431 bdos.writeShort(cg.stackDepth()); 432 bdos.writeShort(ctx.getMaxLocals()); 433 bdos.writeInt(ctx.getOffset()); 434 ctx.writeTo(bdos); 435 bdos.writeShort(exceptionTable.size()); 436 for (Iterator ei = exceptionTable.iterator(); ei.hasNext();) { 437 ExceptionMemento em = (ExceptionMemento) ei.next(); 438 if (!(em.isFullyResolved())) 439 throw new CodeException("Exception table entry refers to unresolved label"); 440 bdos.writeShort(em.startHandled.getOffset()); 441 bdos.writeShort(em.endHandled.getOffset()); 442 bdos.writeShort(em.handler.getOffset()); 443 if (em.eClass != null) 444 bdos.writeShort(cp.resolveClass(em.eClass)); 445 else 446 bdos.writeShort(0); // For `finally' 447 } 448 bdos.writeShort(0); // Code has no sub-attributes 449 450 // Exceptions attribute 451 if (thrownExceptions.size() > 0) { 452 bdos.writeShort(cp.resolveUtf8("Exceptions")); // attribute_name_index 453 bdos.writeInt(2 + thrownExceptions.size() * 2); // attribute_length 454 bdos.writeShort(thrownExceptions.size()); // number_of_exceptions 455 for (Iterator tei = thrownExceptions.iterator(); tei.hasNext();) { 456 CodeClass exClass = (CodeClass) tei.next(); 457 bdos.writeShort(cp.resolveClass(exClass)); // exception class 458 } 459 } 460 } 461 462 // class-wide attributes 463 // 464 // currently, these are SourceFile and Deprecated only 465 int classAttributes = 0; 466 467 if(sourceFile != null) { 468 classAttributes++; 469 } 470 if(deprecated) { 471 classAttributes++; 472 } 473 474 bdos.writeShort(classAttributes); // attributes_count 475 476 // write the source file attribute 477 if(sourceFile != null) { 478 bdos.writeShort(cp.resolveUtf8("SourceFile")); // attribute_name_index 479 bdos.writeInt(2); // attribute_length 480 bdos.writeShort(cp.resolveUtf8(sourceFile)); // sourcefile_index 481 } 482 483 // write the deprecate attribute 484 if(isDeprecated()) { 485 bdos.writeShort(cp.resolveUtf8("Deprecated")); // attribute_name_index 486 bdos.writeInt(0); // attribute_length 487 } 488 489 // All constants will now have been resolved, so we can finally write the cpool 490 491 dos.writeShort(cp.constantPoolSize()); 492 cp.writeConstantPool(dos); 493 494 // Append the rest of the classfile to the stream 495 496 baos.writeTo(dos); 497 } 498 499 public boolean isPrimitive() { 500 return false; 501 } 502 503 public boolean isArray() { 504 return false; 505 } 506}