001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   https://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.bcel.generic;
020
021import java.util.ArrayList;
022import java.util.Arrays;
023import java.util.Collections;
024import java.util.List;
025import java.util.Objects;
026
027import org.apache.bcel.Const;
028import org.apache.bcel.classfile.AccessFlags;
029import org.apache.bcel.classfile.Annotations;
030import org.apache.bcel.classfile.Attribute;
031import org.apache.bcel.classfile.ConstantPool;
032import org.apache.bcel.classfile.Field;
033import org.apache.bcel.classfile.JavaClass;
034import org.apache.bcel.classfile.Method;
035import org.apache.bcel.classfile.RuntimeInvisibleAnnotations;
036import org.apache.bcel.classfile.RuntimeVisibleAnnotations;
037import org.apache.bcel.classfile.SourceFile;
038import org.apache.bcel.classfile.Utility;
039import org.apache.bcel.util.BCELComparator;
040import org.apache.commons.lang3.ArrayUtils;
041
042/**
043 * Template class for building up a java class. May be initialized with an existing Java class (file).
044 *
045 * @see JavaClass
046 */
047public class ClassGen extends AccessFlags implements Cloneable {
048
049    private static BCELComparator<ClassGen> bcelComparator = new BCELComparator<ClassGen>() {
050
051        @Override
052        public boolean equals(final ClassGen a, final ClassGen b) {
053            return a == b || a != null && b != null && Objects.equals(a.getClassName(), b.getClassName());
054        }
055
056        @Override
057        public int hashCode(final ClassGen o) {
058            return o != null ? Objects.hashCode(o.getClassName()) : 0;
059        }
060    };
061
062    /**
063     * Gets the comparison strategy object.
064     *
065     * @return Comparison strategy object.
066     */
067    public static BCELComparator<ClassGen> getComparator() {
068        return bcelComparator;
069    }
070
071    /**
072     * Sets the comparison strategy object.
073     *
074     * @param comparator Comparison strategy object.
075     */
076    public static void setComparator(final BCELComparator<ClassGen> comparator) {
077        bcelComparator = comparator;
078    }
079
080    /*
081     * Corresponds to the fields found in a JavaClass object.
082     */
083    private String className;
084    private String superClassName;
085    private final String fileName;
086    private int classNameIndex = -1;
087    private int superclassNameIndex = -1;
088    private int major = Const.MAJOR_1_1;
089    private int minor = Const.MINOR_1_1;
090    private ConstantPoolGen cp; // Template for building up constant pool
091    // ArrayLists instead of arrays to gather fields, methods, etc.
092    private final List<Field> fieldList = new ArrayList<>();
093    private final List<Method> methodList = new ArrayList<>();
094
095    private final List<Attribute> attributeList = new ArrayList<>();
096
097    private final List<String> interfaceList = new ArrayList<>();
098
099    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}