View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  package org.apache.bcel.generic;
18  
19  import java.util.ArrayList;
20  import java.util.Arrays;
21  import java.util.Collections;
22  import java.util.List;
23  import java.util.Objects;
24  
25  import org.apache.bcel.Const;
26  import org.apache.bcel.classfile.AccessFlags;
27  import org.apache.bcel.classfile.Annotations;
28  import org.apache.bcel.classfile.Attribute;
29  import org.apache.bcel.classfile.ConstantPool;
30  import org.apache.bcel.classfile.Field;
31  import org.apache.bcel.classfile.JavaClass;
32  import org.apache.bcel.classfile.Method;
33  import org.apache.bcel.classfile.RuntimeInvisibleAnnotations;
34  import org.apache.bcel.classfile.RuntimeVisibleAnnotations;
35  import org.apache.bcel.classfile.SourceFile;
36  import org.apache.bcel.classfile.Utility;
37  import org.apache.bcel.util.BCELComparator;
38  import org.apache.commons.lang3.ArrayUtils;
39  
40  /**
41   * Template class for building up a java class. May be initialized with an existing Java class (file).
42   *
43   * @see JavaClass
44   */
45  public class ClassGen extends AccessFlags implements Cloneable {
46  
47      private static BCELComparator<ClassGen> bcelComparator = new BCELComparator<ClassGen>() {
48  
49          @Override
50          public boolean equals(final ClassGen a, final ClassGen b) {
51              return a == b || a != null && b != null && Objects.equals(a.getClassName(), b.getClassName());
52          }
53  
54          @Override
55          public int hashCode(final ClassGen o) {
56              return o != null ? Objects.hashCode(o.getClassName()) : 0;
57          }
58      };
59  
60      /**
61       * @return Comparison strategy object
62       */
63      public static BCELComparator<ClassGen> getComparator() {
64          return bcelComparator;
65      }
66  
67      /**
68       * @param comparator Comparison strategy object
69       */
70      public static void setComparator(final BCELComparator<ClassGen> comparator) {
71          bcelComparator = comparator;
72      }
73  
74      /*
75       * Corresponds to the fields found in a JavaClass object.
76       */
77      private String className;
78      private String superClassName;
79      private final String fileName;
80      private int classNameIndex = -1;
81      private int superclassNameIndex = -1;
82      private int major = Const.MAJOR_1_1;
83      private int minor = Const.MINOR_1_1;
84      private ConstantPoolGen cp; // Template for building up constant pool
85      // ArrayLists instead of arrays to gather fields, methods, etc.
86      private final List<Field> fieldList = new ArrayList<>();
87      private final List<Method> methodList = new ArrayList<>();
88  
89      private final List<Attribute> attributeList = new ArrayList<>();
90  
91      private final List<String> interfaceList = new ArrayList<>();
92  
93      private final List<AnnotationEntryGen> annotationList = new ArrayList<>();
94  
95      private List<ClassObserver> observers;
96  
97      /**
98       * Initialize with existing class.
99       *
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 }