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