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