    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}