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