001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 * 017 */ 018package org.apache.bcel.generic; 019 020import java.util.ArrayList; 021import java.util.List; 022import java.util.Objects; 023 024import org.apache.bcel.Const; 025import org.apache.bcel.classfile.AccessFlags; 026import org.apache.bcel.classfile.AnnotationEntry; 027import org.apache.bcel.classfile.Annotations; 028import org.apache.bcel.classfile.Attribute; 029import org.apache.bcel.classfile.ConstantPool; 030import org.apache.bcel.classfile.Field; 031import org.apache.bcel.classfile.JavaClass; 032import org.apache.bcel.classfile.Method; 033import org.apache.bcel.classfile.RuntimeInvisibleAnnotations; 034import org.apache.bcel.classfile.RuntimeVisibleAnnotations; 035import org.apache.bcel.classfile.SourceFile; 036import org.apache.bcel.util.BCELComparator; 037 038/** 039 * Template class for building up a java class. May be initialized with an 040 * existing java class (file). 041 * 042 * @see JavaClass 043 * @version $Id$ 044 */ 045public class ClassGen extends AccessFlags implements Cloneable { 046 047 /* Corresponds to the fields found in a JavaClass object. 048 */ 049 private String class_name; 050 private String super_class_name; 051 private final String file_name; 052 private int class_name_index = -1; 053 private int superclass_name_index = -1; 054 private int major = Const.MAJOR_1_1; 055 private int minor = Const.MINOR_1_1; 056 private ConstantPoolGen cp; // Template for building up constant pool 057 // ArrayLists instead of arrays to gather fields, methods, etc. 058 private final List<Field> field_vec = new ArrayList<>(); 059 private final List<Method> method_vec = new ArrayList<>(); 060 private final List<Attribute> attribute_vec = new ArrayList<>(); 061 private final List<String> interface_vec = new ArrayList<>(); 062 private final List<AnnotationEntryGen> annotation_vec = new ArrayList<>(); 063 064 private static BCELComparator _cmp = new BCELComparator() { 065 066 @Override 067 public boolean equals( final Object o1, final Object o2 ) { 068 final ClassGen THIS = (ClassGen) o1; 069 final ClassGen THAT = (ClassGen) o2; 070 return Objects.equals(THIS.getClassName(), THAT.getClassName()); 071 } 072 073 074 @Override 075 public int hashCode( final Object o ) { 076 final ClassGen THIS = (ClassGen) o; 077 return THIS.getClassName().hashCode(); 078 } 079 }; 080 081 082 /** Convenience constructor to set up some important values initially. 083 * 084 * @param class_name fully qualified class name 085 * @param super_class_name fully qualified superclass name 086 * @param file_name source file name 087 * @param access_flags access qualifiers 088 * @param interfaces implemented interfaces 089 * @param cp constant pool to use 090 */ 091 public ClassGen(final String class_name, final String super_class_name, final String file_name, final int access_flags, 092 final String[] interfaces, final ConstantPoolGen cp) { 093 super(access_flags); 094 this.class_name = class_name; 095 this.super_class_name = super_class_name; 096 this.file_name = file_name; 097 this.cp = cp; 098 // Put everything needed by default into the constant pool and the vectors 099 if (file_name != null) { 100 addAttribute(new SourceFile(cp.addUtf8("SourceFile"), 2, cp.addUtf8(file_name), cp 101 .getConstantPool())); 102 } 103 class_name_index = cp.addClass(class_name); 104 superclass_name_index = cp.addClass(super_class_name); 105 if (interfaces != null) { 106 for (final String interface1 : interfaces) { 107 addInterface(interface1); 108 } 109 } 110 } 111 112 113 /** Convenience constructor to set up some important values initially. 114 * 115 * @param class_name fully qualified class name 116 * @param super_class_name fully qualified superclass name 117 * @param file_name source file name 118 * @param access_flags access qualifiers 119 * @param interfaces implemented interfaces 120 */ 121 public ClassGen(final String class_name, final String super_class_name, final String file_name, final int access_flags, 122 final String[] interfaces) { 123 this(class_name, super_class_name, file_name, access_flags, interfaces, 124 new ConstantPoolGen()); 125 } 126 127 128 /** 129 * Initialize with existing class. 130 * @param clazz JavaClass object (e.g. read from file) 131 */ 132 public ClassGen(final JavaClass clazz) { 133 super(clazz.getAccessFlags()); 134 class_name_index = clazz.getClassNameIndex(); 135 superclass_name_index = clazz.getSuperclassNameIndex(); 136 class_name = clazz.getClassName(); 137 super_class_name = clazz.getSuperclassName(); 138 file_name = clazz.getSourceFileName(); 139 cp = new ConstantPoolGen(clazz.getConstantPool()); 140 major = clazz.getMajor(); 141 minor = clazz.getMinor(); 142 final Attribute[] attributes = clazz.getAttributes(); 143 // J5TODO: Could make unpacking lazy, done on first reference 144 final AnnotationEntryGen[] annotations = unpackAnnotations(attributes); 145 final Method[] methods = clazz.getMethods(); 146 final Field[] fields = clazz.getFields(); 147 final String[] interfaces = clazz.getInterfaceNames(); 148 for (final String interface1 : interfaces) { 149 addInterface(interface1); 150 } 151 for (final Attribute attribute : attributes) { 152 if (!(attribute instanceof Annotations)) { 153 addAttribute(attribute); 154 } 155 } 156 for (final AnnotationEntryGen annotation : annotations) { 157 addAnnotationEntry(annotation); 158 } 159 for (final Method method : methods) { 160 addMethod(method); 161 } 162 for (final Field field : fields) { 163 addField(field); 164 } 165 } 166 167 /** 168 * Look for attributes representing annotations and unpack them. 169 */ 170 private AnnotationEntryGen[] unpackAnnotations(final Attribute[] attrs) 171 { 172 final List<AnnotationEntryGen> annotationGenObjs = new ArrayList<>(); 173 for (final Attribute attr : attrs) { 174 if (attr instanceof RuntimeVisibleAnnotations) 175 { 176 final RuntimeVisibleAnnotations rva = (RuntimeVisibleAnnotations) attr; 177 final AnnotationEntry[] annos = rva.getAnnotationEntries(); 178 for (final AnnotationEntry a : annos) { 179 annotationGenObjs.add(new AnnotationEntryGen(a, 180 getConstantPool(), false)); 181 } 182 } 183 else 184 if (attr instanceof RuntimeInvisibleAnnotations) 185 { 186 final RuntimeInvisibleAnnotations ria = (RuntimeInvisibleAnnotations) attr; 187 final AnnotationEntry[] annos = ria.getAnnotationEntries(); 188 for (final AnnotationEntry a : annos) { 189 annotationGenObjs.add(new AnnotationEntryGen(a, 190 getConstantPool(), false)); 191 } 192 } 193 } 194 return annotationGenObjs.toArray(new AnnotationEntryGen[annotationGenObjs.size()]); 195 } 196 197 198 /** 199 * @return the (finally) built up Java class object. 200 */ 201 public JavaClass getJavaClass() { 202 final int[] interfaces = getInterfaces(); 203 final Field[] fields = getFields(); 204 final Method[] methods = getMethods(); 205 Attribute[] attributes = null; 206 if (annotation_vec.isEmpty()) { 207 attributes = getAttributes(); 208 } else { 209 // TODO: Sometime later, trash any attributes called 'RuntimeVisibleAnnotations' or 'RuntimeInvisibleAnnotations' 210 final Attribute[] annAttributes = AnnotationEntryGen.getAnnotationAttributes(cp, getAnnotationEntries()); 211 attributes = new Attribute[attribute_vec.size()+annAttributes.length]; 212 attribute_vec.toArray(attributes); 213 System.arraycopy(annAttributes,0,attributes,attribute_vec.size(),annAttributes.length); 214 } 215 // Must be last since the above calls may still add something to it 216 final ConstantPool _cp = this.cp.getFinalConstantPool(); 217 return new JavaClass(class_name_index, superclass_name_index, file_name, major, minor, 218 super.getAccessFlags(), _cp, interfaces, fields, methods, attributes); 219 } 220 221 222 /** 223 * Add an interface to this class, i.e., this class has to implement it. 224 * @param name interface to implement (fully qualified class name) 225 */ 226 public void addInterface( final String name ) { 227 interface_vec.add(name); 228 } 229 230 231 /** 232 * Remove an interface from this class. 233 * @param name interface to remove (fully qualified name) 234 */ 235 public void removeInterface( final String name ) { 236 interface_vec.remove(name); 237 } 238 239 240 /** 241 * @return major version number of class file 242 */ 243 public int getMajor() { 244 return major; 245 } 246 247 248 /** Set major version number of class file, default value is 45 (JDK 1.1) 249 * @param major major version number 250 */ 251 public void setMajor( final int major ) { // TODO could be package-protected - only called by test code 252 this.major = major; 253 } 254 255 256 /** Set minor version number of class file, default value is 3 (JDK 1.1) 257 * @param minor minor version number 258 */ 259 public void setMinor( final int minor ) { // TODO could be package-protected - only called by test code 260 this.minor = minor; 261 } 262 263 /** 264 * @return minor version number of class file 265 */ 266 public int getMinor() { 267 return minor; 268 } 269 270 271 /** 272 * Add an attribute to this class. 273 * @param a attribute to add 274 */ 275 public void addAttribute( final Attribute a ) { 276 attribute_vec.add(a); 277 } 278 279 public void addAnnotationEntry(final AnnotationEntryGen a) { 280 annotation_vec.add(a); 281 } 282 283 284 /** 285 * Add a method to this class. 286 * @param m method to add 287 */ 288 public void addMethod( final Method m ) { 289 method_vec.add(m); 290 } 291 292 293 /** 294 * Convenience method. 295 * 296 * Add an empty constructor to this class that does nothing but calling super(). 297 * @param access_flags rights for constructor 298 */ 299 public void addEmptyConstructor( final int access_flags ) { 300 final InstructionList il = new InstructionList(); 301 il.append(InstructionConst.THIS); // Push `this' 302 il.append(new INVOKESPECIAL(cp.addMethodref(super_class_name, "<init>", "()V"))); 303 il.append(InstructionConst.RETURN); 304 final MethodGen mg = new MethodGen(access_flags, Type.VOID, Type.NO_ARGS, null, "<init>", 305 class_name, il, cp); 306 mg.setMaxStack(1); 307 addMethod(mg.getMethod()); 308 } 309 310 311 /** 312 * Add a field to this class. 313 * @param f field to add 314 */ 315 public void addField( final Field f ) { 316 field_vec.add(f); 317 } 318 319 320 public boolean containsField( final Field f ) { 321 return field_vec.contains(f); 322 } 323 324 325 /** @return field object with given name, or null 326 */ 327 public Field containsField( final String name ) { 328 for (final Field f : field_vec) { 329 if (f.getName().equals(name)) { 330 return f; 331 } 332 } 333 return null; 334 } 335 336 337 /** @return method object with given name and signature, or null 338 */ 339 public Method containsMethod( final String name, final String signature ) { 340 for (final Method m : method_vec) { 341 if (m.getName().equals(name) && m.getSignature().equals(signature)) { 342 return m; 343 } 344 } 345 return null; 346 } 347 348 349 /** 350 * Remove an attribute from this class. 351 * @param a attribute to remove 352 */ 353 public void removeAttribute( final Attribute a ) { 354 attribute_vec.remove(a); 355 } 356 357 358 /** 359 * Remove a method from this class. 360 * @param m method to remove 361 */ 362 public void removeMethod( final Method m ) { 363 method_vec.remove(m); 364 } 365 366 367 /** Replace given method with new one. If the old one does not exist 368 * add the new_ method to the class anyway. 369 */ 370 public void replaceMethod( final Method old, final Method new_ ) { 371 if (new_ == null) { 372 throw new ClassGenException("Replacement method must not be null"); 373 } 374 final int i = method_vec.indexOf(old); 375 if (i < 0) { 376 method_vec.add(new_); 377 } else { 378 method_vec.set(i, new_); 379 } 380 } 381 382 383 /** Replace given field with new one. If the old one does not exist 384 * add the new_ field to the class anyway. 385 */ 386 public void replaceField( final Field old, final Field new_ ) { 387 if (new_ == null) { 388 throw new ClassGenException("Replacement method must not be null"); 389 } 390 final int i = field_vec.indexOf(old); 391 if (i < 0) { 392 field_vec.add(new_); 393 } else { 394 field_vec.set(i, new_); 395 } 396 } 397 398 399 /** 400 * Remove a field to this class. 401 * @param f field to remove 402 */ 403 public void removeField( final Field f ) { 404 field_vec.remove(f); 405 } 406 407 408 public String getClassName() { 409 return class_name; 410 } 411 412 413 public String getSuperclassName() { 414 return super_class_name; 415 } 416 417 418 public String getFileName() { 419 return file_name; 420 } 421 422 423 public void setClassName( final String name ) { 424 class_name = name.replace('/', '.'); 425 class_name_index = cp.addClass(name); 426 } 427 428 429 public void setSuperclassName( final String name ) { 430 super_class_name = name.replace('/', '.'); 431 superclass_name_index = cp.addClass(name); 432 } 433 434 435 public Method[] getMethods() { 436 return method_vec.toArray(new Method[method_vec.size()]); 437 } 438 439 440 public void setMethods( final Method[] methods ) { 441 method_vec.clear(); 442 for (final Method method : methods) { 443 addMethod(method); 444 } 445 } 446 447 448 public void setMethodAt( final Method method, final int pos ) { 449 method_vec.set(pos, method); 450 } 451 452 453 public Method getMethodAt( final int pos ) { 454 return method_vec.get(pos); 455 } 456 457 458 public String[] getInterfaceNames() { 459 final int size = interface_vec.size(); 460 final String[] interfaces = new String[size]; 461 interface_vec.toArray(interfaces); 462 return interfaces; 463 } 464 465 466 public int[] getInterfaces() { 467 final int size = interface_vec.size(); 468 final int[] interfaces = new int[size]; 469 for (int i = 0; i < size; i++) { 470 interfaces[i] = cp.addClass(interface_vec.get(i)); 471 } 472 return interfaces; 473 } 474 475 476 public Field[] getFields() { 477 return field_vec.toArray(new Field[field_vec.size()]); 478 } 479 480 481 public Attribute[] getAttributes() { 482 return attribute_vec.toArray(new Attribute[attribute_vec.size()]); 483 } 484 485 // J5TODO: Should we make calling unpackAnnotations() lazy and put it in here? 486 public AnnotationEntryGen[] getAnnotationEntries() { 487 return annotation_vec.toArray(new AnnotationEntryGen[annotation_vec.size()]); 488 } 489 490 491 public ConstantPoolGen getConstantPool() { 492 return cp; 493 } 494 495 496 public void setConstantPool( final ConstantPoolGen constant_pool ) { 497 cp = constant_pool; 498 } 499 500 501 public void setClassNameIndex( final int class_name_index ) { 502 this.class_name_index = class_name_index; 503 class_name = cp.getConstantPool().getConstantString(class_name_index, 504 Const.CONSTANT_Class).replace('/', '.'); 505 } 506 507 508 public void setSuperclassNameIndex( final int superclass_name_index ) { 509 this.superclass_name_index = superclass_name_index; 510 super_class_name = cp.getConstantPool().getConstantString(superclass_name_index, 511 Const.CONSTANT_Class).replace('/', '.'); 512 } 513 514 515 public int getSuperclassNameIndex() { 516 return superclass_name_index; 517 } 518 519 520 public int getClassNameIndex() { 521 return class_name_index; 522 } 523 524 private List<ClassObserver> observers; 525 526 527 /** Add observer for this object. 528 */ 529 public void addObserver( final ClassObserver o ) { 530 if (observers == null) { 531 observers = new ArrayList<>(); 532 } 533 observers.add(o); 534 } 535 536 537 /** Remove observer for this object. 538 */ 539 public void removeObserver( final ClassObserver o ) { 540 if (observers != null) { 541 observers.remove(o); 542 } 543 } 544 545 546 /** Call notify() method on all observers. This method is not called 547 * automatically whenever the state has changed, but has to be 548 * called by the user after he has finished editing the object. 549 */ 550 public void update() { 551 if (observers != null) { 552 for (final ClassObserver observer : observers) { 553 observer.notify(this); 554 } 555 } 556 } 557 558 559 @Override 560 public Object clone() { 561 try { 562 return super.clone(); 563 } catch (final CloneNotSupportedException e) { 564 throw new Error("Clone Not Supported"); // never happens 565 } 566 } 567 568 569 /** 570 * @return Comparison strategy object 571 */ 572 public static BCELComparator getComparator() { 573 return _cmp; 574 } 575 576 577 /** 578 * @param comparator Comparison strategy object 579 */ 580 public static void setComparator( final BCELComparator comparator ) { 581 _cmp = comparator; 582 } 583 584 585 /** 586 * Return value as defined by given BCELComparator strategy. 587 * By default two ClassGen objects are said to be equal when 588 * their class names are equal. 589 * 590 * @see java.lang.Object#equals(java.lang.Object) 591 */ 592 @Override 593 public boolean equals( final Object obj ) { 594 return _cmp.equals(this, obj); 595 } 596 597 598 /** 599 * Return value as defined by given BCELComparator strategy. 600 * By default return the hashcode of the class name. 601 * 602 * @see java.lang.Object#hashCode() 603 */ 604 @Override 605 public int hashCode() { 606 return _cmp.hashCode(this); 607 } 608}