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       * Gets the comparison strategy object.
64       *
65       * @return Comparison strategy object.
66       */
67      public static BCELComparator<ClassGen> getComparator() {
68          return bcelComparator;
69      }
70  
71      /**
72       * Sets the comparison strategy object.
73       *
74       * @param comparator Comparison strategy object.
75       */
76      public static void setComparator(final BCELComparator<ClassGen> comparator) {
77          bcelComparator = comparator;
78      }
79  
80      /*
81       * Corresponds to the fields found in a JavaClass object.
82       */
83      private String className;
84      private String superClassName;
85      private final String fileName;
86      private int classNameIndex = -1;
87      private int superclassNameIndex = -1;
88      private int major = Const.MAJOR_1_1;
89      private int minor = Const.MINOR_1_1;
90      private ConstantPoolGen cp; // Template for building up constant pool
91      // ArrayLists instead of arrays to gather fields, methods, etc.
92      private final List<Field> fieldList = new ArrayList<>();
93      private final List<Method> methodList = new ArrayList<>();
94  
95      private final List<Attribute> attributeList = new ArrayList<>();
96  
97      private final List<String> interfaceList = new ArrayList<>();
98  
99      private final List<AnnotationEntryGen> annotationList = new ArrayList<>();
100 
101     private List<ClassObserver> observers;
102 
103     /**
104      * Constructs a new instance from an existing class.
105      *
106      * @param clazz JavaClass object (for example read from file).
107      */
108     public ClassGen(final JavaClass clazz) {
109         super(clazz.getAccessFlags());
110         classNameIndex = clazz.getClassNameIndex();
111         superclassNameIndex = clazz.getSuperclassNameIndex();
112         className = clazz.getClassName();
113         superClassName = clazz.getSuperclassName();
114         fileName = clazz.getSourceFileName();
115         cp = new ConstantPoolGen(clazz.getConstantPool());
116         major = clazz.getMajor();
117         minor = clazz.getMinor();
118         final Attribute[] attributes = clazz.getAttributes();
119         // J5TODO: Could make unpacking lazy, done on first reference
120         final AnnotationEntryGen[] annotations = unpackAnnotations(attributes);
121         final String[] interfaceNames = clazz.getInterfaceNames();
122         if (interfaceNames != null) {
123             Collections.addAll(interfaceList, interfaceNames);
124         }
125         if (attributes != null) {
126             for (final Attribute attribute : attributes) {
127                 if (!(attribute instanceof Annotations)) {
128                     addAttribute(attribute);
129                 }
130             }
131         }
132         Collections.addAll(annotationList, annotations);
133         final Method[] methods = clazz.getMethods();
134         if (methods != null) {
135             Collections.addAll(methodList, methods);
136         }
137         final Field[] fields = clazz.getFields();
138         if (fields != null) {
139             Collections.addAll(fieldList, fields);
140         }
141     }
142 
143     /**
144      * Convenience constructor to set up some important values initially.
145      *
146      * @param className fully qualified class name.
147      * @param superClassName fully qualified superclass name.
148      * @param fileName source file name.
149      * @param accessFlags access qualifiers.
150      * @param interfaces implemented interfaces.
151      */
152     public ClassGen(final String className, final String superClassName, final String fileName, final int accessFlags, final String[] interfaces) {
153         this(className, superClassName, fileName, accessFlags, interfaces, new ConstantPoolGen());
154     }
155 
156     /**
157      * Convenience constructor to set up some important values initially.
158      *
159      * @param className fully qualified class name.
160      * @param superClassName fully qualified superclass name.
161      * @param fileName source file name.
162      * @param accessFlags access qualifiers.
163      * @param interfaces implemented interfaces.
164      * @param cp constant pool to use.
165      */
166     public ClassGen(final String className, final String superClassName, final String fileName, final int accessFlags, final String[] interfaces,
167         final ConstantPoolGen cp) {
168         super(accessFlags);
169         this.className = className;
170         this.superClassName = superClassName;
171         this.fileName = fileName;
172         this.cp = cp;
173         // Put everything needed by default into the constant pool and the vectors
174         if (fileName != null) {
175             addAttribute(new SourceFile(cp.addUtf8("SourceFile"), 2, cp.addUtf8(fileName), cp.getConstantPool()));
176         }
177         classNameIndex = cp.addClass(className);
178         superclassNameIndex = cp.addClass(superClassName);
179         if (interfaces != null) {
180             Collections.addAll(interfaceList, interfaces);
181         }
182     }
183 
184     /**
185      * Adds an annotation entry to this class.
186      *
187      * @param a the annotation entry to add.
188      */
189     public void addAnnotationEntry(final AnnotationEntryGen a) {
190         annotationList.add(a);
191     }
192 
193     /**
194      * Add an attribute to this class.
195      *
196      * @param a attribute to add.
197      */
198     public void addAttribute(final Attribute a) {
199         attributeList.add(a);
200     }
201 
202     /**
203      * Convenience method.
204      *
205      * Add an empty constructor to this class that does nothing but calling super().
206      *
207      * @param accessFlags rights for constructor.
208      */
209     public void addEmptyConstructor(final int accessFlags) {
210         final InstructionList il = new InstructionList();
211         il.append(InstructionConst.THIS); // Push 'this'
212         il.append(new INVOKESPECIAL(cp.addMethodref(superClassName, Const.CONSTRUCTOR_NAME, "()V")));
213         il.append(InstructionConst.RETURN);
214         final MethodGen mg = new MethodGen(accessFlags, Type.VOID, Type.NO_ARGS, null, Const.CONSTRUCTOR_NAME, className, il, cp);
215         mg.setMaxStack(1);
216         addMethod(mg.getMethod());
217     }
218 
219     /**
220      * Add a field to this class.
221      *
222      * @param f field to add.
223      */
224     public void addField(final Field f) {
225         fieldList.add(f);
226     }
227 
228     /**
229      * Add an interface to this class, that is, this class has to implement it.
230      *
231      * @param name interface to implement (fully qualified class name).
232      */
233     public void addInterface(final String name) {
234         interfaceList.add(name);
235     }
236 
237     /**
238      * Add a method to this class.
239      *
240      * @param m method to add.
241      */
242     public void addMethod(final Method m) {
243         methodList.add(m);
244     }
245 
246     /**
247      * Add observer for this object.
248      *
249      * @param o the observer.
250      */
251     public void addObserver(final ClassObserver o) {
252         if (observers == null) {
253             observers = new ArrayList<>();
254         }
255         observers.add(o);
256     }
257 
258     @Override
259     public Object clone() {
260         try {
261             return super.clone();
262         } catch (final CloneNotSupportedException e) {
263             throw new UnsupportedOperationException("Clone Not Supported", e); // never happens
264         }
265     }
266 
267     /**
268      * Checks if this class contains the given field.
269      *
270      * @param f the field to check.
271      * @return true if this class contains the field.
272      */
273     public boolean containsField(final Field f) {
274         return fieldList.contains(f);
275     }
276 
277     /**
278      * Gets the field object with given name, or null.
279      *
280      * @param name the field name.
281      * @return field object with given name, or null.
282      */
283     public Field containsField(final String name) {
284         for (final Field f : fieldList) {
285             if (f.getName().equals(name)) {
286                 return f;
287             }
288         }
289         return null;
290     }
291 
292     /**
293      * Gets the method object with given name and signature, or null.
294      *
295      * @param name the method name.
296      * @param signature the method signature.
297      * @return method object with given name and signature, or null.
298      */
299     public Method containsMethod(final String name, final String signature) {
300         for (final Method m : methodList) {
301             if (m.getName().equals(name) && m.getSignature().equals(signature)) {
302                 return m;
303             }
304         }
305         return null;
306     }
307 
308     /**
309      * Return value as defined by given BCELComparator strategy. By default two ClassGen objects are said to be equal when
310      * their class names are equal.
311      *
312      * @see Object#equals(Object)
313      */
314     @Override
315     public boolean equals(final Object obj) {
316         return obj instanceof ClassGen && bcelComparator.equals(this, (ClassGen) obj);
317     }
318 
319     /**
320      * Gets the annotation entries.
321      *
322      * @return the annotation entries.
323      */
324     // J5TODO: Should we make calling unpackAnnotations() lazy and put it in here?
325     public AnnotationEntryGen[] getAnnotationEntries() {
326         return annotationList.toArray(AnnotationEntryGen.EMPTY_ARRAY);
327     }
328 
329     /**
330      * Gets the attributes.
331      *
332      * @return the attributes.
333      */
334     public Attribute[] getAttributes() {
335         return attributeList.toArray(Attribute.EMPTY_ARRAY);
336     }
337 
338     /**
339      * Gets the class name.
340      *
341      * @return the class name.
342      */
343     public String getClassName() {
344         return className;
345     }
346 
347     /**
348      * Gets the class name index.
349      *
350      * @return the class name index.
351      */
352     public int getClassNameIndex() {
353         return classNameIndex;
354     }
355 
356     /**
357      * Gets the constant pool.
358      *
359      * @return the constant pool.
360      */
361     public ConstantPoolGen getConstantPool() {
362         return cp;
363     }
364 
365     /**
366      * Gets the fields.
367      *
368      * @return the fields.
369      */
370     public Field[] getFields() {
371         return fieldList.toArray(Field.EMPTY_ARRAY);
372     }
373 
374     /**
375      * Gets the file name.
376      *
377      * @return the file name.
378      */
379     public String getFileName() {
380         return fileName;
381     }
382 
383     /**
384      * Gets the interface names.
385      *
386      * @return the interface names.
387      */
388     public String[] getInterfaceNames() {
389         return interfaceList.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
390     }
391 
392     /**
393      * Gets the interfaces.
394      *
395      * @return the interfaces.
396      */
397     public int[] getInterfaces() {
398         final int size = interfaceList.size();
399         final int[] interfaces = new int[size];
400         Arrays.setAll(interfaces, i -> cp.addClass(interfaceList.get(i)));
401         return interfaces;
402     }
403 
404     /**
405      * Gets the (finally) built up Java class object.
406      *
407      * @return the (finally) built up Java class object.
408      */
409     public JavaClass getJavaClass() {
410         final int[] interfaces = getInterfaces();
411         final Field[] fields = getFields();
412         final Method[] methods = getMethods();
413         Attribute[] attributes = null;
414         if (annotationList.isEmpty()) {
415             attributes = getAttributes();
416         } else {
417             // TODO: Sometime later, trash any attributes called 'RuntimeVisibleAnnotations' or 'RuntimeInvisibleAnnotations'
418             final Attribute[] annAttributes = AnnotationEntryGen.getAnnotationAttributes(cp, getAnnotationEntries());
419             attributes = new Attribute[attributeList.size() + annAttributes.length];
420             attributeList.toArray(attributes);
421             System.arraycopy(annAttributes, 0, attributes, attributeList.size(), annAttributes.length);
422         }
423         // Must be last since the above calls may still add something to it
424         final ConstantPool cp = this.cp.getFinalConstantPool();
425         return new JavaClass(classNameIndex, superclassNameIndex, fileName, major, minor, super.getAccessFlags(), cp, interfaces, fields, methods,
426             attributes);
427     }
428 
429     /**
430      * Gets major version number of class file.
431      *
432      * @return major version number of class file.
433      */
434     public int getMajor() {
435         return major;
436     }
437 
438     /**
439      * Gets the method at the given position.
440      *
441      * @param pos the position.
442      * @return the method at the given position.
443      */
444     public Method getMethodAt(final int pos) {
445         return methodList.get(pos);
446     }
447 
448     /**
449      * Gets the methods.
450      *
451      * @return the methods.
452      */
453     public Method[] getMethods() {
454         return methodList.toArray(Method.EMPTY_ARRAY);
455     }
456 
457     /**
458      * Gets minor version number of class file.
459      *
460      * @return minor version number of class file.
461      */
462     public int getMinor() {
463         return minor;
464     }
465 
466     /**
467      * Gets the superclass name.
468      *
469      * @return the superclass name.
470      */
471     public String getSuperclassName() {
472         return superClassName;
473     }
474 
475     /**
476      * Gets the superclass name index.
477      *
478      * @return the superclass name index.
479      */
480     public int getSuperclassNameIndex() {
481         return superclassNameIndex;
482     }
483 
484     /**
485      * Return value as defined by given BCELComparator strategy. By default return the hash code of the class name.
486      *
487      * @see Object#hashCode()
488      */
489     @Override
490     public int hashCode() {
491         return bcelComparator.hashCode(this);
492     }
493 
494     /**
495      * Remove an attribute from this class.
496      *
497      * @param a attribute to remove.
498      */
499     public void removeAttribute(final Attribute a) {
500         attributeList.remove(a);
501     }
502 
503     /**
504      * Remove a field to this class.
505      *
506      * @param f field to remove.
507      */
508     public void removeField(final Field f) {
509         fieldList.remove(f);
510     }
511 
512     /**
513      * Remove an interface from this class.
514      *
515      * @param name interface to remove (fully qualified name).
516      */
517     public void removeInterface(final String name) {
518         interfaceList.remove(name);
519     }
520 
521     /**
522      * Remove a method from this class.
523      *
524      * @param m method to remove.
525      */
526     public void removeMethod(final Method m) {
527         methodList.remove(m);
528     }
529 
530     /**
531      * Remove observer for this object.
532      *
533      * @param o the observer to remove.
534      */
535     public void removeObserver(final ClassObserver o) {
536         if (observers != null) {
537             observers.remove(o);
538         }
539     }
540 
541     /**
542      * Replace given field with new one. If the old one does not exist add the new_ field to the class anyway.
543      *
544      * @param old the old field.
545      * @param newField the new field.
546      */
547     public void replaceField(final Field old, final Field newField) {
548         if (newField == null) {
549             throw new ClassGenException("Replacement method must not be null");
550         }
551         final int i = fieldList.indexOf(old);
552         if (i < 0) {
553             fieldList.add(newField);
554         } else {
555             fieldList.set(i, newField);
556         }
557     }
558 
559     /**
560      * Replace given method with new one. If the old one does not exist add the newMethod method to the class anyway.
561      *
562      * @param old the old method.
563      * @param newMethod the new method.
564      */
565     public void replaceMethod(final Method old, final Method newMethod) {
566         if (newMethod == null) {
567             throw new ClassGenException("Replacement method must not be null");
568         }
569         final int i = methodList.indexOf(old);
570         if (i < 0) {
571             methodList.add(newMethod);
572         } else {
573             methodList.set(i, newMethod);
574         }
575     }
576 
577     /**
578      * Sets the class name.
579      *
580      * @param name the class name.
581      */
582     public void setClassName(final String name) {
583         className = Utility.pathToPackage(name);
584         classNameIndex = cp.addClass(name);
585     }
586 
587     /**
588      * Sets the class name index.
589      *
590      * @param classNameIndex the class name index.
591      */
592     public void setClassNameIndex(final int classNameIndex) {
593         this.classNameIndex = classNameIndex;
594         this.className = Utility.pathToPackage(cp.getConstantPool().getConstantString(classNameIndex, Const.CONSTANT_Class));
595     }
596 
597     /**
598      * Sets the constant pool.
599      *
600      * @param constantPool the constant pool.
601      */
602     public void setConstantPool(final ConstantPoolGen constantPool) {
603         cp = constantPool;
604     }
605 
606     /**
607      * Sets major version number of class file, default value is 45 (JDK 1.1).
608      *
609      * @param major major version number.
610      */
611     public void setMajor(final int major) { // TODO could be package-protected - only called by test code
612         this.major = major;
613     }
614 
615     /**
616      * Sets the method at the given position.
617      *
618      * @param method the method.
619      * @param pos the position.
620      */
621     public void setMethodAt(final Method method, final int pos) {
622         methodList.set(pos, method);
623     }
624 
625     /**
626      * Sets the methods.
627      *
628      * @param methods the methods.
629      */
630     public void setMethods(final Method[] methods) {
631         methodList.clear();
632         if (methods != null) {
633             Collections.addAll(methodList, methods);
634         }
635     }
636 
637     /**
638      * Sets minor version number of class file, default value is 3 (JDK 1.1).
639      *
640      * @param minor minor version number.
641      */
642     public void setMinor(final int minor) { // TODO could be package-protected - only called by test code
643         this.minor = minor;
644     }
645 
646     /**
647      * Sets the superclass name.
648      *
649      * @param name the superclass name.
650      */
651     public void setSuperclassName(final String name) {
652         superClassName = Utility.pathToPackage(name);
653         superclassNameIndex = cp.addClass(name);
654     }
655 
656     /**
657      * Sets the superclass name index.
658      *
659      * @param superclassNameIndex the superclass name index.
660      */
661     public void setSuperclassNameIndex(final int superclassNameIndex) {
662         this.superclassNameIndex = superclassNameIndex;
663         superClassName = Utility.pathToPackage(cp.getConstantPool().getConstantString(superclassNameIndex, Const.CONSTANT_Class));
664     }
665 
666     /**
667      * Unpacks attributes representing annotations.
668      */
669     private AnnotationEntryGen[] unpackAnnotations(final Attribute[] attributes) {
670         final List<AnnotationEntryGen> annotationGenObjs = new ArrayList<>();
671         if (attributes != null) {
672             for (final Attribute attr : attributes) {
673                 if (attr instanceof RuntimeVisibleAnnotations) {
674                     final RuntimeVisibleAnnotations rva = (RuntimeVisibleAnnotations) attr;
675                     rva.forEach(a -> annotationGenObjs.add(new AnnotationEntryGen(a, getConstantPool(), false)));
676                 } else if (attr instanceof RuntimeInvisibleAnnotations) {
677                     final RuntimeInvisibleAnnotations ria = (RuntimeInvisibleAnnotations) attr;
678                     ria.forEach(a -> annotationGenObjs.add(new AnnotationEntryGen(a, getConstantPool(), false)));
679                 }
680             }
681         }
682         return annotationGenObjs.toArray(AnnotationEntryGen.EMPTY_ARRAY);
683     }
684 
685     /**
686      * Call notify() method on all observers. This method is not called automatically whenever the state has changed, but
687      * has to be called by the user after they have finished editing the object.
688      */
689     public void update() {
690         if (observers != null) {
691             for (final ClassObserver observer : observers) {
692                 observer.notify(this);
693             }
694         }
695     }
696 }