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