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       * Constructs a new instance from an 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         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 }