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 * @return Comparison strategy object 064 */ 065 public static BCELComparator<ClassGen> getComparator() { 066 return bcelComparator; 067 } 068 069 /** 070 * @param comparator Comparison strategy object 071 */ 072 public static void setComparator(final BCELComparator<ClassGen> comparator) { 073 bcelComparator = comparator; 074 } 075 076 /* 077 * Corresponds to the fields found in a JavaClass object. 078 */ 079 private String className; 080 private String superClassName; 081 private final String fileName; 082 private int classNameIndex = -1; 083 private int superclassNameIndex = -1; 084 private int major = Const.MAJOR_1_1; 085 private int minor = Const.MINOR_1_1; 086 private ConstantPoolGen cp; // Template for building up constant pool 087 // ArrayLists instead of arrays to gather fields, methods, etc. 088 private final List<Field> fieldList = new ArrayList<>(); 089 private final List<Method> methodList = new ArrayList<>(); 090 091 private final List<Attribute> attributeList = new ArrayList<>(); 092 093 private final List<String> interfaceList = new ArrayList<>(); 094 095 private final List<AnnotationEntryGen> annotationList = new ArrayList<>(); 096 097 private List<ClassObserver> observers; 098 099 /** 100 * Constructs a new instance from an existing class. 101 * 102 * @param clazz JavaClass object (for example read from file) 103 */ 104 public ClassGen(final JavaClass clazz) { 105 super(clazz.getAccessFlags()); 106 classNameIndex = clazz.getClassNameIndex(); 107 superclassNameIndex = clazz.getSuperclassNameIndex(); 108 className = clazz.getClassName(); 109 superClassName = clazz.getSuperclassName(); 110 fileName = clazz.getSourceFileName(); 111 cp = new ConstantPoolGen(clazz.getConstantPool()); 112 major = clazz.getMajor(); 113 minor = clazz.getMinor(); 114 final Attribute[] attributes = clazz.getAttributes(); 115 // J5TODO: Could make unpacking lazy, done on first reference 116 final AnnotationEntryGen[] annotations = unpackAnnotations(attributes); 117 final String[] interfaceNames = clazz.getInterfaceNames(); 118 if (interfaceNames != null) { 119 Collections.addAll(interfaceList, interfaceNames); 120 } 121 if (attributes != null) { 122 for (final Attribute attribute : attributes) { 123 if (!(attribute instanceof Annotations)) { 124 addAttribute(attribute); 125 } 126 } 127 } 128 Collections.addAll(annotationList, annotations); 129 final Method[] methods = clazz.getMethods(); 130 if (methods != null) { 131 Collections.addAll(methodList, methods); 132 } 133 final Field[] fields = clazz.getFields(); 134 if (fields != null) { 135 Collections.addAll(fieldList, fields); 136 } 137 } 138 139 /** 140 * Convenience constructor to set up some important values initially. 141 * 142 * @param className fully qualified class name 143 * @param superClassName fully qualified superclass name 144 * @param fileName source file name 145 * @param accessFlags access qualifiers 146 * @param interfaces implemented interfaces 147 */ 148 public ClassGen(final String className, final String superClassName, final String fileName, final int accessFlags, final String[] interfaces) { 149 this(className, superClassName, fileName, accessFlags, interfaces, new ConstantPoolGen()); 150 } 151 152 /** 153 * Convenience constructor to set up some important values initially. 154 * 155 * @param className fully qualified class name 156 * @param superClassName fully qualified superclass name 157 * @param fileName source file name 158 * @param accessFlags access qualifiers 159 * @param interfaces implemented interfaces 160 * @param cp constant pool to use 161 */ 162 public ClassGen(final String className, final String superClassName, final String fileName, final int accessFlags, final String[] interfaces, 163 final ConstantPoolGen cp) { 164 super(accessFlags); 165 this.className = className; 166 this.superClassName = superClassName; 167 this.fileName = fileName; 168 this.cp = cp; 169 // Put everything needed by default into the constant pool and the vectors 170 if (fileName != null) { 171 addAttribute(new SourceFile(cp.addUtf8("SourceFile"), 2, cp.addUtf8(fileName), cp.getConstantPool())); 172 } 173 classNameIndex = cp.addClass(className); 174 superclassNameIndex = cp.addClass(superClassName); 175 if (interfaces != null) { 176 Collections.addAll(interfaceList, interfaces); 177 } 178 } 179 180 public void addAnnotationEntry(final AnnotationEntryGen a) { 181 annotationList.add(a); 182 } 183 184 /** 185 * Add an attribute to this class. 186 * 187 * @param a attribute to add 188 */ 189 public void addAttribute(final Attribute a) { 190 attributeList.add(a); 191 } 192 193 /** 194 * Convenience method. 195 * 196 * Add an empty constructor to this class that does nothing but calling super(). 197 * 198 * @param accessFlags rights for constructor 199 */ 200 public void addEmptyConstructor(final int accessFlags) { 201 final InstructionList il = new InstructionList(); 202 il.append(InstructionConst.THIS); // Push 'this' 203 il.append(new INVOKESPECIAL(cp.addMethodref(superClassName, Const.CONSTRUCTOR_NAME, "()V"))); 204 il.append(InstructionConst.RETURN); 205 final MethodGen mg = new MethodGen(accessFlags, Type.VOID, Type.NO_ARGS, null, Const.CONSTRUCTOR_NAME, className, il, cp); 206 mg.setMaxStack(1); 207 addMethod(mg.getMethod()); 208 } 209 210 /** 211 * Add a field to this class. 212 * 213 * @param f field to add 214 */ 215 public void addField(final Field f) { 216 fieldList.add(f); 217 } 218 219 /** 220 * Add an interface to this class, i.e., this class has to implement it. 221 * 222 * @param name interface to implement (fully qualified class name) 223 */ 224 public void addInterface(final String name) { 225 interfaceList.add(name); 226 } 227 228 /** 229 * Add a method to this class. 230 * 231 * @param m method to add 232 */ 233 public void addMethod(final Method m) { 234 methodList.add(m); 235 } 236 237 /** 238 * Add observer for this object. 239 */ 240 public void addObserver(final ClassObserver o) { 241 if (observers == null) { 242 observers = new ArrayList<>(); 243 } 244 observers.add(o); 245 } 246 247 @Override 248 public Object clone() { 249 try { 250 return super.clone(); 251 } catch (final CloneNotSupportedException e) { 252 throw new UnsupportedOperationException("Clone Not Supported", e); // never happens 253 } 254 } 255 256 public boolean containsField(final Field f) { 257 return fieldList.contains(f); 258 } 259 260 /** 261 * @return field object with given name, or null 262 */ 263 public Field containsField(final String name) { 264 for (final Field f : fieldList) { 265 if (f.getName().equals(name)) { 266 return f; 267 } 268 } 269 return null; 270 } 271 272 /** 273 * @return method object with given name and signature, or null 274 */ 275 public Method containsMethod(final String name, final String signature) { 276 for (final Method m : methodList) { 277 if (m.getName().equals(name) && m.getSignature().equals(signature)) { 278 return m; 279 } 280 } 281 return null; 282 } 283 284 /** 285 * Return value as defined by given BCELComparator strategy. By default two ClassGen objects are said to be equal when 286 * their class names are equal. 287 * 288 * @see Object#equals(Object) 289 */ 290 @Override 291 public boolean equals(final Object obj) { 292 return obj instanceof ClassGen && bcelComparator.equals(this, (ClassGen) obj); 293 } 294 295 // J5TODO: Should we make calling unpackAnnotations() lazy and put it in here? 296 public AnnotationEntryGen[] getAnnotationEntries() { 297 return annotationList.toArray(AnnotationEntryGen.EMPTY_ARRAY); 298 } 299 300 public Attribute[] getAttributes() { 301 return attributeList.toArray(Attribute.EMPTY_ARRAY); 302 } 303 304 public String getClassName() { 305 return className; 306 } 307 308 public int getClassNameIndex() { 309 return classNameIndex; 310 } 311 312 public ConstantPoolGen getConstantPool() { 313 return cp; 314 } 315 316 public Field[] getFields() { 317 return fieldList.toArray(Field.EMPTY_ARRAY); 318 } 319 320 public String getFileName() { 321 return fileName; 322 } 323 324 public String[] getInterfaceNames() { 325 return interfaceList.toArray(ArrayUtils.EMPTY_STRING_ARRAY); 326 } 327 328 public int[] getInterfaces() { 329 final int size = interfaceList.size(); 330 final int[] interfaces = new int[size]; 331 Arrays.setAll(interfaces, i -> cp.addClass(interfaceList.get(i))); 332 return interfaces; 333 } 334 335 /** 336 * @return the (finally) built up Java class object. 337 */ 338 public JavaClass getJavaClass() { 339 final int[] interfaces = getInterfaces(); 340 final Field[] fields = getFields(); 341 final Method[] methods = getMethods(); 342 Attribute[] attributes = null; 343 if (annotationList.isEmpty()) { 344 attributes = getAttributes(); 345 } else { 346 // TODO: Sometime later, trash any attributes called 'RuntimeVisibleAnnotations' or 'RuntimeInvisibleAnnotations' 347 final Attribute[] annAttributes = AnnotationEntryGen.getAnnotationAttributes(cp, getAnnotationEntries()); 348 attributes = new Attribute[attributeList.size() + annAttributes.length]; 349 attributeList.toArray(attributes); 350 System.arraycopy(annAttributes, 0, attributes, attributeList.size(), annAttributes.length); 351 } 352 // Must be last since the above calls may still add something to it 353 final ConstantPool cp = this.cp.getFinalConstantPool(); 354 return new JavaClass(classNameIndex, superclassNameIndex, fileName, major, minor, super.getAccessFlags(), cp, interfaces, fields, methods, 355 attributes); 356 } 357 358 /** 359 * @return major version number of class file 360 */ 361 public int getMajor() { 362 return major; 363 } 364 365 public Method getMethodAt(final int pos) { 366 return methodList.get(pos); 367 } 368 369 public Method[] getMethods() { 370 return methodList.toArray(Method.EMPTY_ARRAY); 371 } 372 373 /** 374 * @return minor version number of class file 375 */ 376 public int getMinor() { 377 return minor; 378 } 379 380 public String getSuperclassName() { 381 return superClassName; 382 } 383 384 public int getSuperclassNameIndex() { 385 return superclassNameIndex; 386 } 387 388 /** 389 * Return value as defined by given BCELComparator strategy. By default return the hash code of the class name. 390 * 391 * @see Object#hashCode() 392 */ 393 @Override 394 public int hashCode() { 395 return bcelComparator.hashCode(this); 396 } 397 398 /** 399 * Remove an attribute from this class. 400 * 401 * @param a attribute to remove 402 */ 403 public void removeAttribute(final Attribute a) { 404 attributeList.remove(a); 405 } 406 407 /** 408 * Remove a field to this class. 409 * 410 * @param f field to remove 411 */ 412 public void removeField(final Field f) { 413 fieldList.remove(f); 414 } 415 416 /** 417 * Remove an interface from this class. 418 * 419 * @param name interface to remove (fully qualified name) 420 */ 421 public void removeInterface(final String name) { 422 interfaceList.remove(name); 423 } 424 425 /** 426 * Remove a method from this class. 427 * 428 * @param m method to remove 429 */ 430 public void removeMethod(final Method m) { 431 methodList.remove(m); 432 } 433 434 /** 435 * Remove observer for this object. 436 */ 437 public void removeObserver(final ClassObserver o) { 438 if (observers != null) { 439 observers.remove(o); 440 } 441 } 442 443 /** 444 * Replace given field with new one. If the old one does not exist add the new_ field to the class anyway. 445 */ 446 public void replaceField(final Field old, final Field newField) { 447 if (newField == null) { 448 throw new ClassGenException("Replacement method must not be null"); 449 } 450 final int i = fieldList.indexOf(old); 451 if (i < 0) { 452 fieldList.add(newField); 453 } else { 454 fieldList.set(i, newField); 455 } 456 } 457 458 /** 459 * Replace given method with new one. If the old one does not exist add the newMethod method to the class anyway. 460 */ 461 public void replaceMethod(final Method old, final Method newMethod) { 462 if (newMethod == null) { 463 throw new ClassGenException("Replacement method must not be null"); 464 } 465 final int i = methodList.indexOf(old); 466 if (i < 0) { 467 methodList.add(newMethod); 468 } else { 469 methodList.set(i, newMethod); 470 } 471 } 472 473 public void setClassName(final String name) { 474 className = Utility.pathToPackage(name); 475 classNameIndex = cp.addClass(name); 476 } 477 478 public void setClassNameIndex(final int classNameIndex) { 479 this.classNameIndex = classNameIndex; 480 this.className = Utility.pathToPackage(cp.getConstantPool().getConstantString(classNameIndex, Const.CONSTANT_Class)); 481 } 482 483 public void setConstantPool(final ConstantPoolGen constantPool) { 484 cp = constantPool; 485 } 486 487 /** 488 * Sets major version number of class file, default value is 45 (JDK 1.1) 489 * 490 * @param major major version number 491 */ 492 public void setMajor(final int major) { // TODO could be package-protected - only called by test code 493 this.major = major; 494 } 495 496 public void setMethodAt(final Method method, final int pos) { 497 methodList.set(pos, method); 498 } 499 500 public void setMethods(final Method[] methods) { 501 methodList.clear(); 502 if (methods != null) { 503 Collections.addAll(methodList, methods); 504 } 505 } 506 507 /** 508 * Sets minor version number of class file, default value is 3 (JDK 1.1) 509 * 510 * @param minor minor version number 511 */ 512 public void setMinor(final int minor) { // TODO could be package-protected - only called by test code 513 this.minor = minor; 514 } 515 516 public void setSuperclassName(final String name) { 517 superClassName = Utility.pathToPackage(name); 518 superclassNameIndex = cp.addClass(name); 519 } 520 521 public void setSuperclassNameIndex(final int superclassNameIndex) { 522 this.superclassNameIndex = superclassNameIndex; 523 superClassName = Utility.pathToPackage(cp.getConstantPool().getConstantString(superclassNameIndex, Const.CONSTANT_Class)); 524 } 525 526 /** 527 * Unpacks attributes representing annotations. 528 */ 529 private AnnotationEntryGen[] unpackAnnotations(final Attribute[] attributes) { 530 final List<AnnotationEntryGen> annotationGenObjs = new ArrayList<>(); 531 if (attributes != null) { 532 for (final Attribute attr : attributes) { 533 if (attr instanceof RuntimeVisibleAnnotations) { 534 final RuntimeVisibleAnnotations rva = (RuntimeVisibleAnnotations) attr; 535 rva.forEach(a -> annotationGenObjs.add(new AnnotationEntryGen(a, getConstantPool(), false))); 536 } else if (attr instanceof RuntimeInvisibleAnnotations) { 537 final RuntimeInvisibleAnnotations ria = (RuntimeInvisibleAnnotations) attr; 538 ria.forEach(a -> annotationGenObjs.add(new AnnotationEntryGen(a, getConstantPool(), false))); 539 } 540 } 541 } 542 return annotationGenObjs.toArray(AnnotationEntryGen.EMPTY_ARRAY); 543 } 544 545 /** 546 * Call notify() method on all observers. This method is not called automatically whenever the state has changed, but 547 * has to be called by the user after they have finished editing the object. 548 */ 549 public void update() { 550 if (observers != null) { 551 for (final ClassObserver observer : observers) { 552 observer.notify(this); 553 } 554 } 555 } 556}