View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.bcel.generic;
20  
21  import java.util.ArrayList;
22  import java.util.Arrays;
23  import java.util.Collections;
24  import java.util.List;
25  import java.util.Objects;
26  
27  import org.apache.bcel.Const;
28  import org.apache.bcel.classfile.AccessFlags;
29  import org.apache.bcel.classfile.Annotations;
30  import org.apache.bcel.classfile.Attribute;
31  import org.apache.bcel.classfile.ConstantPool;
32  import org.apache.bcel.classfile.Field;
33  import org.apache.bcel.classfile.JavaClass;
34  import org.apache.bcel.classfile.Method;
35  import org.apache.bcel.classfile.RuntimeInvisibleAnnotations;
36  import org.apache.bcel.classfile.RuntimeVisibleAnnotations;
37  import org.apache.bcel.classfile.SourceFile;
38  import org.apache.bcel.classfile.Utility;
39  import org.apache.bcel.util.BCELComparator;
40  import org.apache.commons.lang3.ArrayUtils;
41  
42  /**
43   * Template class for building up a java class. May be initialized with an existing Java class (file).
44   *
45   * @see JavaClass
46   */
47  public class ClassGen extends AccessFlags implements Cloneable {
48  
49      private static BCELComparator<ClassGen> bcelComparator = new BCELComparator<ClassGen>() {
50  
51          @Override
52          public boolean equals(final ClassGen a, final ClassGen b) {
53              return a == b || a != null && b != null && Objects.equals(a.getClassName(), b.getClassName());
54          }
55  
56          @Override
57          public int hashCode(final ClassGen o) {
58              return o != null ? Objects.hashCode(o.getClassName()) : 0;
59          }
60      };
61  
62      /**
63       * @return Comparison strategy object
64       */
65      public static BCELComparator<ClassGen> getComparator() {
66          return bcelComparator;
67      }
68  
69      /**
70       * @param comparator Comparison strategy object
71       */
72      public static void setComparator(final BCELComparator<ClassGen> comparator) {
73          bcelComparator = comparator;
74      }
75  
76      /*
77       * Corresponds to the fields found in a JavaClass object.
78       */
79      private String className;
80      private String superClassName;
81      private final String fileName;
82      private int classNameIndex = -1;
83      private int superclassNameIndex = -1;
84      private int major = Const.MAJOR_1_1;
85      private int minor = Const.MINOR_1_1;
86      private ConstantPoolGen cp; // Template for building up constant pool
87      // ArrayLists instead of arrays to gather fields, methods, etc.
88      private final List<Field> fieldList = new ArrayList<>();
89      private final List<Method> methodList = new ArrayList<>();
90  
91      private final List<Attribute> attributeList = new ArrayList<>();
92  
93      private final List<String> interfaceList = new ArrayList<>();
94  
95      private final List<AnnotationEntryGen> annotationList = new ArrayList<>();
96  
97      private List<ClassObserver> observers;
98  
99      /**
100      * Constructs a new instance from an existing class.
101      *
102      * @param clazz JavaClass object (for example read from file)
103      */
104     public ClassGen(final JavaClass clazz) {
105         super(clazz.getAccessFlags());
106         classNameIndex = clazz.getClassNameIndex();
107         superclassNameIndex = clazz.getSuperclassNameIndex();
108         className = clazz.getClassName();
109         superClassName = clazz.getSuperclassName();
110         fileName = clazz.getSourceFileName();
111         cp = new ConstantPoolGen(clazz.getConstantPool());
112         major = clazz.getMajor();
113         minor = clazz.getMinor();
114         final Attribute[] attributes = clazz.getAttributes();
115         // J5TODO: Could make unpacking lazy, done on first reference
116         final AnnotationEntryGen[] annotations = unpackAnnotations(attributes);
117         final String[] interfaceNames = clazz.getInterfaceNames();
118         if (interfaceNames != null) {
119             Collections.addAll(interfaceList, interfaceNames);
120         }
121         if (attributes != null) {
122             for (final Attribute attribute : attributes) {
123                 if (!(attribute instanceof Annotations)) {
124                     addAttribute(attribute);
125                 }
126             }
127         }
128         Collections.addAll(annotationList, annotations);
129         final Method[] methods = clazz.getMethods();
130         if (methods != null) {
131             Collections.addAll(methodList, methods);
132         }
133         final Field[] fields = clazz.getFields();
134         if (fields != null) {
135             Collections.addAll(fieldList, fields);
136         }
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      */
148     public ClassGen(final String className, final String superClassName, final String fileName, final int accessFlags, final String[] interfaces) {
149         this(className, superClassName, fileName, accessFlags, interfaces, new ConstantPoolGen());
150     }
151 
152     /**
153      * Convenience constructor to set up some important values initially.
154      *
155      * @param className fully qualified class name
156      * @param superClassName fully qualified superclass name
157      * @param fileName source file name
158      * @param accessFlags access qualifiers
159      * @param interfaces implemented interfaces
160      * @param cp constant pool to use
161      */
162     public ClassGen(final String className, final String superClassName, final String fileName, final int accessFlags, final String[] interfaces,
163         final ConstantPoolGen cp) {
164         super(accessFlags);
165         this.className = className;
166         this.superClassName = superClassName;
167         this.fileName = fileName;
168         this.cp = cp;
169         // Put everything needed by default into the constant pool and the vectors
170         if (fileName != null) {
171             addAttribute(new SourceFile(cp.addUtf8("SourceFile"), 2, cp.addUtf8(fileName), cp.getConstantPool()));
172         }
173         classNameIndex = cp.addClass(className);
174         superclassNameIndex = cp.addClass(superClassName);
175         if (interfaces != null) {
176             Collections.addAll(interfaceList, interfaces);
177         }
178     }
179 
180     public void addAnnotationEntry(final AnnotationEntryGen a) {
181         annotationList.add(a);
182     }
183 
184     /**
185      * Add an attribute to this class.
186      *
187      * @param a attribute to add
188      */
189     public void addAttribute(final Attribute a) {
190         attributeList.add(a);
191     }
192 
193     /**
194      * Convenience method.
195      *
196      * Add an empty constructor to this class that does nothing but calling super().
197      *
198      * @param accessFlags rights for constructor
199      */
200     public void addEmptyConstructor(final int accessFlags) {
201         final InstructionList il = new InstructionList();
202         il.append(InstructionConst.THIS); // Push 'this'
203         il.append(new INVOKESPECIAL(cp.addMethodref(superClassName, Const.CONSTRUCTOR_NAME, "()V")));
204         il.append(InstructionConst.RETURN);
205         final MethodGen mg = new MethodGen(accessFlags, Type.VOID, Type.NO_ARGS, null, Const.CONSTRUCTOR_NAME, className, il, cp);
206         mg.setMaxStack(1);
207         addMethod(mg.getMethod());
208     }
209 
210     /**
211      * Add a field to this class.
212      *
213      * @param f field to add
214      */
215     public void addField(final Field f) {
216         fieldList.add(f);
217     }
218 
219     /**
220      * Add an interface to this class, i.e., this class has to implement it.
221      *
222      * @param name interface to implement (fully qualified class name)
223      */
224     public void addInterface(final String name) {
225         interfaceList.add(name);
226     }
227 
228     /**
229      * Add a method to this class.
230      *
231      * @param m method to add
232      */
233     public void addMethod(final Method m) {
234         methodList.add(m);
235     }
236 
237     /**
238      * Add observer for this object.
239      */
240     public void addObserver(final ClassObserver o) {
241         if (observers == null) {
242             observers = new ArrayList<>();
243         }
244         observers.add(o);
245     }
246 
247     @Override
248     public Object clone() {
249         try {
250             return super.clone();
251         } catch (final CloneNotSupportedException e) {
252             throw new UnsupportedOperationException("Clone Not Supported", e); // never happens
253         }
254     }
255 
256     public boolean containsField(final Field f) {
257         return fieldList.contains(f);
258     }
259 
260     /**
261      * @return field object with given name, or null
262      */
263     public Field containsField(final String name) {
264         for (final Field f : fieldList) {
265             if (f.getName().equals(name)) {
266                 return f;
267             }
268         }
269         return null;
270     }
271 
272     /**
273      * @return method object with given name and signature, or null
274      */
275     public Method containsMethod(final String name, final String signature) {
276         for (final Method m : methodList) {
277             if (m.getName().equals(name) && m.getSignature().equals(signature)) {
278                 return m;
279             }
280         }
281         return null;
282     }
283 
284     /**
285      * Return value as defined by given BCELComparator strategy. By default two ClassGen objects are said to be equal when
286      * their class names are equal.
287      *
288      * @see Object#equals(Object)
289      */
290     @Override
291     public boolean equals(final Object obj) {
292         return obj instanceof ClassGen && bcelComparator.equals(this, (ClassGen) obj);
293     }
294 
295     // J5TODO: Should we make calling unpackAnnotations() lazy and put it in here?
296     public AnnotationEntryGen[] getAnnotationEntries() {
297         return annotationList.toArray(AnnotationEntryGen.EMPTY_ARRAY);
298     }
299 
300     public Attribute[] getAttributes() {
301         return attributeList.toArray(Attribute.EMPTY_ARRAY);
302     }
303 
304     public String getClassName() {
305         return className;
306     }
307 
308     public int getClassNameIndex() {
309         return classNameIndex;
310     }
311 
312     public ConstantPoolGen getConstantPool() {
313         return cp;
314     }
315 
316     public Field[] getFields() {
317         return fieldList.toArray(Field.EMPTY_ARRAY);
318     }
319 
320     public String getFileName() {
321         return fileName;
322     }
323 
324     public String[] getInterfaceNames() {
325         return interfaceList.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
326     }
327 
328     public int[] getInterfaces() {
329         final int size = interfaceList.size();
330         final int[] interfaces = new int[size];
331         Arrays.setAll(interfaces, i -> cp.addClass(interfaceList.get(i)));
332         return interfaces;
333     }
334 
335     /**
336      * @return the (finally) built up Java class object.
337      */
338     public JavaClass getJavaClass() {
339         final int[] interfaces = getInterfaces();
340         final Field[] fields = getFields();
341         final Method[] methods = getMethods();
342         Attribute[] attributes = null;
343         if (annotationList.isEmpty()) {
344             attributes = getAttributes();
345         } else {
346             // TODO: Sometime later, trash any attributes called 'RuntimeVisibleAnnotations' or 'RuntimeInvisibleAnnotations'
347             final Attribute[] annAttributes = AnnotationEntryGen.getAnnotationAttributes(cp, getAnnotationEntries());
348             attributes = new Attribute[attributeList.size() + annAttributes.length];
349             attributeList.toArray(attributes);
350             System.arraycopy(annAttributes, 0, attributes, attributeList.size(), annAttributes.length);
351         }
352         // Must be last since the above calls may still add something to it
353         final ConstantPool cp = this.cp.getFinalConstantPool();
354         return new JavaClass(classNameIndex, superclassNameIndex, fileName, major, minor, super.getAccessFlags(), cp, interfaces, fields, methods,
355             attributes);
356     }
357 
358     /**
359      * @return major version number of class file
360      */
361     public int getMajor() {
362         return major;
363     }
364 
365     public Method getMethodAt(final int pos) {
366         return methodList.get(pos);
367     }
368 
369     public Method[] getMethods() {
370         return methodList.toArray(Method.EMPTY_ARRAY);
371     }
372 
373     /**
374      * @return minor version number of class file
375      */
376     public int getMinor() {
377         return minor;
378     }
379 
380     public String getSuperclassName() {
381         return superClassName;
382     }
383 
384     public int getSuperclassNameIndex() {
385         return superclassNameIndex;
386     }
387 
388     /**
389      * Return value as defined by given BCELComparator strategy. By default return the hash code of the class name.
390      *
391      * @see Object#hashCode()
392      */
393     @Override
394     public int hashCode() {
395         return bcelComparator.hashCode(this);
396     }
397 
398     /**
399      * Remove an attribute from this class.
400      *
401      * @param a attribute to remove
402      */
403     public void removeAttribute(final Attribute a) {
404         attributeList.remove(a);
405     }
406 
407     /**
408      * Remove a field to this class.
409      *
410      * @param f field to remove
411      */
412     public void removeField(final Field f) {
413         fieldList.remove(f);
414     }
415 
416     /**
417      * Remove an interface from this class.
418      *
419      * @param name interface to remove (fully qualified name)
420      */
421     public void removeInterface(final String name) {
422         interfaceList.remove(name);
423     }
424 
425     /**
426      * Remove a method from this class.
427      *
428      * @param m method to remove
429      */
430     public void removeMethod(final Method m) {
431         methodList.remove(m);
432     }
433 
434     /**
435      * Remove observer for this object.
436      */
437     public void removeObserver(final ClassObserver o) {
438         if (observers != null) {
439             observers.remove(o);
440         }
441     }
442 
443     /**
444      * Replace given field with new one. If the old one does not exist add the new_ field to the class anyway.
445      */
446     public void replaceField(final Field old, final Field newField) {
447         if (newField == null) {
448             throw new ClassGenException("Replacement method must not be null");
449         }
450         final int i = fieldList.indexOf(old);
451         if (i < 0) {
452             fieldList.add(newField);
453         } else {
454             fieldList.set(i, newField);
455         }
456     }
457 
458     /**
459      * Replace given method with new one. If the old one does not exist add the newMethod method to the class anyway.
460      */
461     public void replaceMethod(final Method old, final Method newMethod) {
462         if (newMethod == null) {
463             throw new ClassGenException("Replacement method must not be null");
464         }
465         final int i = methodList.indexOf(old);
466         if (i < 0) {
467             methodList.add(newMethod);
468         } else {
469             methodList.set(i, newMethod);
470         }
471     }
472 
473     public void setClassName(final String name) {
474         className = Utility.pathToPackage(name);
475         classNameIndex = cp.addClass(name);
476     }
477 
478     public void setClassNameIndex(final int classNameIndex) {
479         this.classNameIndex = classNameIndex;
480         this.className = Utility.pathToPackage(cp.getConstantPool().getConstantString(classNameIndex, Const.CONSTANT_Class));
481     }
482 
483     public void setConstantPool(final ConstantPoolGen constantPool) {
484         cp = constantPool;
485     }
486 
487     /**
488      * Sets major version number of class file, default value is 45 (JDK 1.1)
489      *
490      * @param major major version number
491      */
492     public void setMajor(final int major) { // TODO could be package-protected - only called by test code
493         this.major = major;
494     }
495 
496     public void setMethodAt(final Method method, final int pos) {
497         methodList.set(pos, method);
498     }
499 
500     public void setMethods(final Method[] methods) {
501         methodList.clear();
502         if (methods != null) {
503             Collections.addAll(methodList, methods);
504         }
505     }
506 
507     /**
508      * Sets minor version number of class file, default value is 3 (JDK 1.1)
509      *
510      * @param minor minor version number
511      */
512     public void setMinor(final int minor) { // TODO could be package-protected - only called by test code
513         this.minor = minor;
514     }
515 
516     public void setSuperclassName(final String name) {
517         superClassName = Utility.pathToPackage(name);
518         superclassNameIndex = cp.addClass(name);
519     }
520 
521     public void setSuperclassNameIndex(final int superclassNameIndex) {
522         this.superclassNameIndex = superclassNameIndex;
523         superClassName = Utility.pathToPackage(cp.getConstantPool().getConstantString(superclassNameIndex, Const.CONSTANT_Class));
524     }
525 
526     /**
527      * Unpacks attributes representing annotations.
528      */
529     private AnnotationEntryGen[] unpackAnnotations(final Attribute[] attributes) {
530         final List<AnnotationEntryGen> annotationGenObjs = new ArrayList<>();
531         if (attributes != null) {
532             for (final Attribute attr : attributes) {
533                 if (attr instanceof RuntimeVisibleAnnotations) {
534                     final RuntimeVisibleAnnotations rva = (RuntimeVisibleAnnotations) attr;
535                     rva.forEach(a -> annotationGenObjs.add(new AnnotationEntryGen(a, getConstantPool(), false)));
536                 } else if (attr instanceof RuntimeInvisibleAnnotations) {
537                     final RuntimeInvisibleAnnotations ria = (RuntimeInvisibleAnnotations) attr;
538                     ria.forEach(a -> annotationGenObjs.add(new AnnotationEntryGen(a, getConstantPool(), false)));
539                 }
540             }
541         }
542         return annotationGenObjs.toArray(AnnotationEntryGen.EMPTY_ARRAY);
543     }
544 
545     /**
546      * Call notify() method on all observers. This method is not called automatically whenever the state has changed, but
547      * has to be called by the user after they have finished editing the object.
548      */
549     public void update() {
550         if (observers != null) {
551             for (final ClassObserver observer : observers) {
552                 observer.notify(this);
553             }
554         }
555     }
556 }