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