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