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   */
18  package org.apache.bcel.generic;
19  
20  import java.util.ArrayList;
21  import java.util.List;
22  
23  import org.apache.bcel.Const;
24  import org.apache.bcel.classfile.AccessFlags;
25  import org.apache.bcel.classfile.AnnotationEntry;
26  import org.apache.bcel.classfile.Annotations;
27  import org.apache.bcel.classfile.Attribute;
28  import org.apache.bcel.classfile.ConstantPool;
29  import org.apache.bcel.classfile.Field;
30  import org.apache.bcel.classfile.JavaClass;
31  import org.apache.bcel.classfile.Method;
32  import org.apache.bcel.classfile.RuntimeInvisibleAnnotations;
33  import org.apache.bcel.classfile.RuntimeVisibleAnnotations;
34  import org.apache.bcel.classfile.SourceFile;
35  import org.apache.bcel.util.BCELComparator;
36  
37  /**
38   * Template class for building up a java class. May be initialized with an
39   * existing java class (file).
40   *
41   * @see JavaClass
42   * @version $Id: ClassGen.html 1018313 2017-09-18 09:03:04Z britter $
43   */
44  public class ClassGen extends AccessFlags implements Cloneable {
45  
46      /* Corresponds to the fields found in a JavaClass object.
47       */
48      private String class_name;
49      private String super_class_name;
50      private final String file_name;
51      private int class_name_index = -1;
52      private int superclass_name_index = -1;
53      private int major = Const.MAJOR_1_1;
54      private int minor = Const.MINOR_1_1;
55      private ConstantPoolGen cp; // Template for building up constant pool
56      // ArrayLists instead of arrays to gather fields, methods, etc.
57      private final List<Field> field_vec = new ArrayList<>();
58      private final List<Method> method_vec = new ArrayList<>();
59      private final List<Attribute> attribute_vec = new ArrayList<>();
60      private final List<String> interface_vec = new ArrayList<>();
61      private final List<AnnotationEntryGen> annotation_vec = new ArrayList<>();
62  
63      private static BCELComparator _cmp = new BCELComparator() {
64  
65          @Override
66          public boolean equals( final Object o1, final Object o2 ) {
67              final ClassGen THIS = (ClassGen) o1;
68              final ClassGen THAT = (ClassGen) o2;
69              return THIS.getClassName().equals(THAT.getClassName());
70          }
71  
72  
73          @Override
74          public int hashCode( final Object o ) {
75              final ClassGen THIS = (ClassGen) o;
76              return THIS.getClassName().hashCode();
77          }
78      };
79  
80  
81      /** Convenience constructor to set up some important values initially.
82       *
83       * @param class_name fully qualified class name
84       * @param super_class_name fully qualified superclass name
85       * @param file_name source file name
86       * @param access_flags access qualifiers
87       * @param interfaces implemented interfaces
88       * @param cp constant pool to use
89       */
90      public ClassGen(final String class_name, final String super_class_name, final String file_name, final int access_flags,
91              final String[] interfaces, final ConstantPoolGen cp) {
92          super(access_flags);
93          this.class_name = class_name;
94          this.super_class_name = super_class_name;
95          this.file_name = file_name;
96          this.cp = cp;
97          // Put everything needed by default into the constant pool and the vectors
98          if (file_name != null) {
99              addAttribute(new SourceFile(cp.addUtf8("SourceFile"), 2, cp.addUtf8(file_name), cp
100                     .getConstantPool()));
101         }
102         class_name_index = cp.addClass(class_name);
103         superclass_name_index = cp.addClass(super_class_name);
104         if (interfaces != null) {
105             for (final String interface1 : interfaces) {
106                 addInterface(interface1);
107             }
108         }
109     }
110 
111 
112     /** Convenience constructor to set up some important values initially.
113      *
114      * @param class_name fully qualified class name
115      * @param super_class_name fully qualified superclass name
116      * @param file_name source file name
117      * @param access_flags access qualifiers
118      * @param interfaces implemented interfaces
119      */
120     public ClassGen(final String class_name, final String super_class_name, final String file_name, final int access_flags,
121             final String[] interfaces) {
122         this(class_name, super_class_name, file_name, access_flags, interfaces,
123                 new ConstantPoolGen());
124     }
125 
126 
127     /**
128      * Initialize with existing class.
129      * @param clazz JavaClass object (e.g. read from file)
130      */
131     public ClassGen(final JavaClass clazz) {
132         super(clazz.getAccessFlags());
133         class_name_index = clazz.getClassNameIndex();
134         superclass_name_index = clazz.getSuperclassNameIndex();
135         class_name = clazz.getClassName();
136         super_class_name = clazz.getSuperclassName();
137         file_name = clazz.getSourceFileName();
138         cp = new ConstantPoolGen(clazz.getConstantPool());
139         major = clazz.getMajor();
140         minor = clazz.getMinor();
141         final Attribute[] attributes = clazz.getAttributes();
142         // J5TODO: Could make unpacking lazy, done on first reference
143         final AnnotationEntryGen[] annotations = unpackAnnotations(attributes);
144         final Method[] methods = clazz.getMethods();
145         final Field[] fields = clazz.getFields();
146         final String[] interfaces = clazz.getInterfaceNames();
147         for (final String interface1 : interfaces) {
148             addInterface(interface1);
149         }
150         for (final Attribute attribute : attributes) {
151             if (!(attribute instanceof Annotations)) {
152                 addAttribute(attribute);
153             }
154         }
155         for (final AnnotationEntryGen annotation : annotations) {
156             addAnnotationEntry(annotation);
157         }
158         for (final Method method : methods) {
159             addMethod(method);
160         }
161         for (final Field field : fields) {
162             addField(field);
163         }
164     }
165 
166     /**
167      * Look for attributes representing annotations and unpack them.
168      */
169     private AnnotationEntryGen[] unpackAnnotations(final Attribute[] attrs)
170     {
171         final List<AnnotationEntryGen> annotationGenObjs = new ArrayList<>();
172         for (final Attribute attr : attrs) {
173             if (attr instanceof RuntimeVisibleAnnotations)
174             {
175                 final RuntimeVisibleAnnotations rva = (RuntimeVisibleAnnotations) attr;
176                 final AnnotationEntry[] annos = rva.getAnnotationEntries();
177                 for (final AnnotationEntry a : annos) {
178                     annotationGenObjs.add(new AnnotationEntryGen(a,
179                             getConstantPool(), false));
180                 }
181             }
182             else
183                 if (attr instanceof RuntimeInvisibleAnnotations)
184                 {
185                     final RuntimeInvisibleAnnotations ria = (RuntimeInvisibleAnnotations) attr;
186                     final AnnotationEntry[] annos = ria.getAnnotationEntries();
187                     for (final AnnotationEntry a : annos) {
188                         annotationGenObjs.add(new AnnotationEntryGen(a,
189                                 getConstantPool(), false));
190                     }
191                 }
192         }
193         return annotationGenObjs.toArray(new AnnotationEntryGen[annotationGenObjs.size()]);
194     }
195 
196 
197     /**
198      * @return the (finally) built up Java class object.
199      */
200     public JavaClass getJavaClass() {
201         final int[] interfaces = getInterfaces();
202         final Field[] fields = getFields();
203         final Method[] methods = getMethods();
204         Attribute[] attributes = null;
205         if (annotation_vec.isEmpty()) {
206             attributes = getAttributes();
207         } else {
208             // TODO: Sometime later, trash any attributes called 'RuntimeVisibleAnnotations' or 'RuntimeInvisibleAnnotations'
209             final Attribute[] annAttributes  = AnnotationEntryGen.getAnnotationAttributes(cp, getAnnotationEntries());
210             attributes = new Attribute[attribute_vec.size()+annAttributes.length];
211             attribute_vec.toArray(attributes);
212             System.arraycopy(annAttributes,0,attributes,attribute_vec.size(),annAttributes.length);
213         }
214         // Must be last since the above calls may still add something to it
215         final ConstantPool _cp = this.cp.getFinalConstantPool();
216         return new JavaClass(class_name_index, superclass_name_index, file_name, major, minor,
217                 super.getAccessFlags(), _cp, interfaces, fields, methods, attributes);
218     }
219 
220 
221     /**
222      * Add an interface to this class, i.e., this class has to implement it.
223      * @param name interface to implement (fully qualified class name)
224      */
225     public void addInterface( final String name ) {
226         interface_vec.add(name);
227     }
228 
229 
230     /**
231      * Remove an interface from this class.
232      * @param name interface to remove (fully qualified name)
233      */
234     public void removeInterface( final String name ) {
235         interface_vec.remove(name);
236     }
237 
238 
239     /**
240      * @return major version number of class file
241      */
242     public int getMajor() {
243         return major;
244     }
245 
246 
247     /** Set major version number of class file, default value is 45 (JDK 1.1)
248      * @param major major version number
249      */
250     public void setMajor( final int major ) { // TODO could be package-protected - only called by test code
251         this.major = major;
252     }
253 
254 
255     /** Set minor version number of class file, default value is 3 (JDK 1.1)
256      * @param minor minor version number
257      */
258     public void setMinor( final int minor ) {  // TODO could be package-protected - only called by test code
259         this.minor = minor;
260     }
261 
262     /**
263      * @return minor version number of class file
264      */
265     public int getMinor() {
266         return minor;
267     }
268 
269 
270     /**
271      * Add an attribute to this class.
272      * @param a attribute to add
273      */
274     public void addAttribute( final Attribute a ) {
275         attribute_vec.add(a);
276     }
277 
278     public void addAnnotationEntry(final AnnotationEntryGen a) {
279         annotation_vec.add(a);
280     }
281 
282 
283     /**
284      * Add a method to this class.
285      * @param m method to add
286      */
287     public void addMethod( final Method m ) {
288         method_vec.add(m);
289     }
290 
291 
292     /**
293      * Convenience method.
294      *
295      * Add an empty constructor to this class that does nothing but calling super().
296      * @param access_flags rights for constructor
297      */
298     public void addEmptyConstructor( final int access_flags ) {
299         final InstructionList il = new InstructionList();
300         il.append(InstructionConst.THIS); // Push `this'
301         il.append(new INVOKESPECIAL(cp.addMethodref(super_class_name, "<init>", "()V")));
302         il.append(InstructionConst.RETURN);
303         final MethodGen mg = new MethodGen(access_flags, Type.VOID, Type.NO_ARGS, null, "<init>",
304                 class_name, il, cp);
305         mg.setMaxStack(1);
306         addMethod(mg.getMethod());
307     }
308 
309 
310     /**
311      * Add a field to this class.
312      * @param f field to add
313      */
314     public void addField( final Field f ) {
315         field_vec.add(f);
316     }
317 
318 
319     public boolean containsField( final Field f ) {
320         return field_vec.contains(f);
321     }
322 
323 
324     /** @return field object with given name, or null
325      */
326     public Field containsField( final String name ) {
327         for (final Field f : field_vec) {
328             if (f.getName().equals(name)) {
329                 return f;
330             }
331         }
332         return null;
333     }
334 
335 
336     /** @return method object with given name and signature, or null
337      */
338     public Method containsMethod( final String name, final String signature ) {
339         for (final Method m : method_vec) {
340             if (m.getName().equals(name) && m.getSignature().equals(signature)) {
341                 return m;
342             }
343         }
344         return null;
345     }
346 
347 
348     /**
349      * Remove an attribute from this class.
350      * @param a attribute to remove
351      */
352     public void removeAttribute( final Attribute a ) {
353         attribute_vec.remove(a);
354     }
355 
356 
357     /**
358      * Remove a method from this class.
359      * @param m method to remove
360      */
361     public void removeMethod( final Method m ) {
362         method_vec.remove(m);
363     }
364 
365 
366     /** Replace given method with new one. If the old one does not exist
367      * add the new_ method to the class anyway.
368      */
369     public void replaceMethod( final Method old, final Method new_ ) {
370         if (new_ == null) {
371             throw new ClassGenException("Replacement method must not be null");
372         }
373         final int i = method_vec.indexOf(old);
374         if (i < 0) {
375             method_vec.add(new_);
376         } else {
377             method_vec.set(i, new_);
378         }
379     }
380 
381 
382     /** Replace given field with new one. If the old one does not exist
383      * add the new_ field to the class anyway.
384      */
385     public void replaceField( final Field old, final Field new_ ) {
386         if (new_ == null) {
387             throw new ClassGenException("Replacement method must not be null");
388         }
389         final int i = field_vec.indexOf(old);
390         if (i < 0) {
391             field_vec.add(new_);
392         } else {
393             field_vec.set(i, new_);
394         }
395     }
396 
397 
398     /**
399      * Remove a field to this class.
400      * @param f field to remove
401      */
402     public void removeField( final Field f ) {
403         field_vec.remove(f);
404     }
405 
406 
407     public String getClassName() {
408         return class_name;
409     }
410 
411 
412     public String getSuperclassName() {
413         return super_class_name;
414     }
415 
416 
417     public String getFileName() {
418         return file_name;
419     }
420 
421 
422     public void setClassName( final String name ) {
423         class_name = name.replace('/', '.');
424         class_name_index = cp.addClass(name);
425     }
426 
427 
428     public void setSuperclassName( final String name ) {
429         super_class_name = name.replace('/', '.');
430         superclass_name_index = cp.addClass(name);
431     }
432 
433 
434     public Method[] getMethods() {
435         return method_vec.toArray(new Method[method_vec.size()]);
436     }
437 
438 
439     public void setMethods( final Method[] methods ) {
440         method_vec.clear();
441         for (final Method method : methods) {
442             addMethod(method);
443         }
444     }
445 
446 
447     public void setMethodAt( final Method method, final int pos ) {
448         method_vec.set(pos, method);
449     }
450 
451 
452     public Method getMethodAt( final int pos ) {
453         return method_vec.get(pos);
454     }
455 
456 
457     public String[] getInterfaceNames() {
458         final int size = interface_vec.size();
459         final String[] interfaces = new String[size];
460         interface_vec.toArray(interfaces);
461         return interfaces;
462     }
463 
464 
465     public int[] getInterfaces() {
466         final int size = interface_vec.size();
467         final int[] interfaces = new int[size];
468         for (int i = 0; i < size; i++) {
469             interfaces[i] = cp.addClass(interface_vec.get(i));
470         }
471         return interfaces;
472     }
473 
474 
475     public Field[] getFields() {
476         return field_vec.toArray(new Field[field_vec.size()]);
477     }
478 
479 
480     public Attribute[] getAttributes() {
481         return attribute_vec.toArray(new Attribute[attribute_vec.size()]);
482     }
483 
484     //  J5TODO: Should we make calling unpackAnnotations() lazy and put it in here?
485     public AnnotationEntryGen[] getAnnotationEntries() {
486         return annotation_vec.toArray(new AnnotationEntryGen[annotation_vec.size()]);
487     }
488 
489 
490     public ConstantPoolGen getConstantPool() {
491         return cp;
492     }
493 
494 
495     public void setConstantPool( final ConstantPoolGen constant_pool ) {
496         cp = constant_pool;
497     }
498 
499 
500     public void setClassNameIndex( final int class_name_index ) {
501         this.class_name_index = class_name_index;
502         class_name = cp.getConstantPool().getConstantString(class_name_index,
503                 Const.CONSTANT_Class).replace('/', '.');
504     }
505 
506 
507     public void setSuperclassNameIndex( final int superclass_name_index ) {
508         this.superclass_name_index = superclass_name_index;
509         super_class_name = cp.getConstantPool().getConstantString(superclass_name_index,
510                 Const.CONSTANT_Class).replace('/', '.');
511     }
512 
513 
514     public int getSuperclassNameIndex() {
515         return superclass_name_index;
516     }
517 
518 
519     public int getClassNameIndex() {
520         return class_name_index;
521     }
522 
523     private List<ClassObserver> observers;
524 
525 
526     /** Add observer for this object.
527      */
528     public void addObserver( final ClassObserver o ) {
529         if (observers == null) {
530             observers = new ArrayList<>();
531         }
532         observers.add(o);
533     }
534 
535 
536     /** Remove observer for this object.
537      */
538     public void removeObserver( final ClassObserver o ) {
539         if (observers != null) {
540             observers.remove(o);
541         }
542     }
543 
544 
545     /** Call notify() method on all observers. This method is not called
546      * automatically whenever the state has changed, but has to be
547      * called by the user after he has 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 
557 
558     @Override
559     public Object clone() {
560         try {
561             return super.clone();
562         } catch (final CloneNotSupportedException e) {
563             throw new Error("Clone Not Supported"); // never happens
564         }
565     }
566 
567 
568     /**
569      * @return Comparison strategy object
570      */
571     public static BCELComparator getComparator() {
572         return _cmp;
573     }
574 
575 
576     /**
577      * @param comparator Comparison strategy object
578      */
579     public static void setComparator( final BCELComparator comparator ) {
580         _cmp = comparator;
581     }
582 
583 
584     /**
585      * Return value as defined by given BCELComparator strategy.
586      * By default two ClassGen objects are said to be equal when
587      * their class names are equal.
588      *
589      * @see java.lang.Object#equals(java.lang.Object)
590      */
591     @Override
592     public boolean equals( final Object obj ) {
593         return _cmp.equals(this, obj);
594     }
595 
596 
597     /**
598      * Return value as defined by given BCELComparator strategy.
599      * By default return the hashcode of the class name.
600      *
601      * @see java.lang.Object#hashCode()
602      */
603     @Override
604     public int hashCode() {
605         return _cmp.hashCode(this);
606     }
607 }