    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 *  Unless required by applicable law or agreed to in writing, software
    012 *  distributed under the License is distributed on an "AS IS" BASIS,
    013 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 *  See the License for the specific language governing permissions and
    015 *  limitations under the License.
    016 *
    017 */
    018package org.apache.bcel.generic;
    019
    020import java.util.ArrayList;
    021import java.util.Arrays;
    022import java.util.Collections;
    023import java.util.List;
    024import java.util.Objects;
    025
    026import org.apache.bcel.Const;
    027import org.apache.bcel.classfile.AccessFlags;
    028import org.apache.bcel.classfile.Annotations;
    029import org.apache.bcel.classfile.Attribute;
    030import org.apache.bcel.classfile.ConstantPool;
    031import org.apache.bcel.classfile.Field;
    032import org.apache.bcel.classfile.JavaClass;
    033import org.apache.bcel.classfile.Method;
    034import org.apache.bcel.classfile.RuntimeInvisibleAnnotations;
    035import org.apache.bcel.classfile.RuntimeVisibleAnnotations;
    036import org.apache.bcel.classfile.SourceFile;
    037import org.apache.bcel.classfile.Utility;
    038import org.apache.bcel.util.BCELComparator;
    039import org.apache.commons.lang3.ArrayUtils;
    040
    041/**
    042 * Template class for building up a java class. May be initialized with an existing java class (file).
    043 *
    044 * @see JavaClass
    045 */
    046public class ClassGen extends AccessFlags implements Cloneable {
    047
    048    private static BCELComparator bcelComparator = new BCELComparator() {
    049
    050        @Override
    051        public boolean equals(final Object o1, final Object o2) {
    052            final ClassGen THIS = (ClassGen) o1;
    053            final ClassGen THAT = (ClassGen) o2;
    054            return Objects.equals(THIS.getClassName(), THAT.getClassName());
    055        }
    056
    057        @Override
    058        public int hashCode(final Object o) {
    059            final ClassGen THIS = (ClassGen) o;
    060            return THIS.getClassName().hashCode();
    061        }
    062    };
    063
    064    /**
    065     * @return Comparison strategy object
    066     */
    067    public static BCELComparator getComparator() {
    068        return bcelComparator;
    069    }
    070
    071    /**
    072     * @param comparator Comparison strategy object
    073     */
    074    public static void setComparator(final BCELComparator comparator) {
    075        bcelComparator = comparator;
    076    }
    077
    078    /*
    079     * Corresponds to the fields found in a JavaClass object.
    080     */
    081    private String className;
    082    private String superClassName;
    083    private final String fileName;
    084    private int classNameIndex = -1;
    085    private int superclassNameIndex = -1;
    086    private int major = Const.MAJOR_1_1;
    087    private int minor = Const.MINOR_1_1;
    088    private ConstantPoolGen cp; // Template for building up constant pool
    089    // ArrayLists instead of arrays to gather fields, methods, etc.
    090    private final List<Field> fieldList = new ArrayList<>();
    091    private final List<Method> methodList = new ArrayList<>();
    092
    093    private final List<Attribute> attributeList = new ArrayList<>();
    094
    095    private final List<String> interfaceList = new ArrayList<>();
    096
    097    private final List<AnnotationEntryGen> annotationList = new ArrayList<>();
    098
    099    private List<ClassObserver> observers;
    100
    101    /**
    102     * Initialize with existing class.
    103     *
    104     * @param clazz JavaClass object (e.g. read from file)
    105     */
    106    public ClassGen(final JavaClass clazz) {
    107        super(clazz.getAccessFlags());
    108        classNameIndex = clazz.getClassNameIndex();
    109        superclassNameIndex = clazz.getSuperclassNameIndex();
    110        className = clazz.getClassName();
    111        superClassName = clazz.getSuperclassName();
    112        fileName = clazz.getSourceFileName();
    113        cp = new ConstantPoolGen(clazz.getConstantPool());
    114        major = clazz.getMajor();
    115        minor = clazz.getMinor();
    116        final Attribute[] attributes = clazz.getAttributes();
    117        // J5TODO: Could make unpacking lazy, done on first reference
    118        final AnnotationEntryGen[] annotations = unpackAnnotations(attributes);
    119        Collections.addAll(interfaceList, clazz.getInterfaceNames());
    120        for (final Attribute attribute : attributes) {
    121            if (!(attribute instanceof Annotations)) {
    122                addAttribute(attribute);
    123            }
    124        }
    125        Collections.addAll(annotationList, annotations);
    126        Collections.addAll(methodList, clazz.getMethods());
    127        Collections.addAll(fieldList, clazz.getFields());
    128    }
    129
    130    /**
    131     * Convenience constructor to set up some important values initially.
    132     *
    133     * @param className fully qualified class name
    134     * @param superClassName fully qualified superclass name
    135     * @param fileName source file name
    136     * @param accessFlags access qualifiers
    137     * @param interfaces implemented interfaces
    138     */
    139    public ClassGen(final String className, final String superClassName, final String fileName, final int accessFlags, final String[] interfaces) {
    140        this(className, superClassName, fileName, accessFlags, interfaces, new ConstantPoolGen());
    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     * @param cp constant pool to use
    152     */
    153    public ClassGen(final String className, final String superClassName, final String fileName, final int accessFlags, final String[] interfaces,
    154        final ConstantPoolGen cp) {
    155        super(accessFlags);
    156        this.className = className;
    157        this.superClassName = superClassName;
    158        this.fileName = fileName;
    159        this.cp = cp;
    160        // Put everything needed by default into the constant pool and the vectors
    161        if (fileName != null) {
    162            addAttribute(new SourceFile(cp.addUtf8("SourceFile"), 2, cp.addUtf8(fileName), cp.getConstantPool()));
    163        }
    164        classNameIndex = cp.addClass(className);
    165        superclassNameIndex = cp.addClass(superClassName);
    166        if (interfaces != null) {
    167            Collections.addAll(interfaceList, interfaces);
    168        }
    169    }
    170
    171    public void addAnnotationEntry(final AnnotationEntryGen a) {
    172        annotationList.add(a);
    173    }
    174
    175    /**
    176     * Add an attribute to this class.
    177     *
    178     * @param a attribute to add
    179     */
    180    public void addAttribute(final Attribute a) {
    181        attributeList.add(a);
    182    }
    183
    184    /**
    185     * Convenience method.
    186     *
    187     * Add an empty constructor to this class that does nothing but calling super().
    188     *
    189     * @param access_flags rights for constructor
    190     */
    191    public void addEmptyConstructor(final int access_flags) {
    192        final InstructionList il = new InstructionList();
    193        il.append(InstructionConst.THIS); // Push `this'
    194        il.append(new INVOKESPECIAL(cp.addMethodref(superClassName, "<init>", "()V")));
    195        il.append(InstructionConst.RETURN);
    196        final MethodGen mg = new MethodGen(access_flags, Type.VOID, Type.NO_ARGS, null, "<init>", className, il, cp);
    197        mg.setMaxStack(1);
    198        addMethod(mg.getMethod());
    199    }
    200
    201    /**
    202     * Add a field to this class.
    203     *
    204     * @param f field to add
    205     */
    206    public void addField(final Field f) {
    207        fieldList.add(f);
    208    }
    209
    210    /**
    211     * Add an interface to this class, i.e., this class has to implement it.
    212     *
    213     * @param name interface to implement (fully qualified class name)
    214     */
    215    public void addInterface(final String name) {
    216        interfaceList.add(name);
    217    }
    218
    219    /**
    220     * Add a method to this class.
    221     *
    222     * @param m method to add
    223     */
    224    public void addMethod(final Method m) {
    225        methodList.add(m);
    226    }
    227
    228    /**
    229     * Add observer for this object.
    230     */
    231    public void addObserver(final ClassObserver o) {
    232        if (observers == null) {
    233            observers = new ArrayList<>();
    234        }
    235        observers.add(o);
    236    }
    237
    238    @Override
    239    public Object clone() {
    240        try {
    241            return super.clone();
    242        } catch (final CloneNotSupportedException e) {
    243            throw new Error("Clone Not Supported"); // never happens
    244        }
    245    }
    246
    247    public boolean containsField(final Field f) {
    248        return fieldList.contains(f);
    249    }
    250
    251    /**
    252     * @return field object with given name, or null
    253     */
    254    public Field containsField(final String name) {
    255        for (final Field f : fieldList) {
    256            if (f.getName().equals(name)) {
    257                return f;
    258            }
    259        }
    260        return null;
    261    }
    262
    263    /**
    264     * @return method object with given name and signature, or null
    265     */
    266    public Method containsMethod(final String name, final String signature) {
    267        for (final Method m : methodList) {
    268            if (m.getName().equals(name) && m.getSignature().equals(signature)) {
    269                return m;
    270            }
    271        }
    272        return null;
    273    }
    274
    275    /**
    276     * Return value as defined by given BCELComparator strategy. By default two ClassGen objects are said to be equal when
    277     * their class names are equal.
    278     *
    279     * @see Object#equals(Object)
    280     */
    281    @Override
    282    public boolean equals(final Object obj) {
    283        return bcelComparator.equals(this, obj);
    284    }
    285
    286    // J5TODO: Should we make calling unpackAnnotations() lazy and put it in here?
    287    public AnnotationEntryGen[] getAnnotationEntries() {
    288        return annotationList.toArray(AnnotationEntryGen.EMPTY_ARRAY);
    289    }
    290
    291    public Attribute[] getAttributes() {
    292        return attributeList.toArray(Attribute.EMPTY_ARRAY);
    293    }
    294
    295    public String getClassName() {
    296        return className;
    297    }
    298
    299    public int getClassNameIndex() {
    300        return classNameIndex;
    301    }
    302
    303    public ConstantPoolGen getConstantPool() {
    304        return cp;
    305    }
    306
    307    public Field[] getFields() {
    308        return fieldList.toArray(Field.EMPTY_ARRAY);
    309    }
    310
    311    public String getFileName() {
    312        return fileName;
    313    }
    314
    315    public String[] getInterfaceNames() {
    316        return interfaceList.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
    317    }
    318
    319    public int[] getInterfaces() {
    320        final int size = interfaceList.size();
    321        final int[] interfaces = new int[size];
    322        Arrays.setAll(interfaces, i -> cp.addClass(interfaceList.get(i)));
    323        return interfaces;
    324    }
    325
    326    /**
    327     * @return the (finally) built up Java class object.
    328     */
    329    public JavaClass getJavaClass() {
    330        final int[] interfaces = getInterfaces();
    331        final Field[] fields = getFields();
    332        final Method[] methods = getMethods();
    333        Attribute[] attributes = null;
    334        if (annotationList.isEmpty()) {
    335            attributes = getAttributes();
    336        } else {
    337            // TODO: Sometime later, trash any attributes called 'RuntimeVisibleAnnotations' or 'RuntimeInvisibleAnnotations'
    338            final Attribute[] annAttributes = AnnotationEntryGen.getAnnotationAttributes(cp, getAnnotationEntries());
    339            attributes = new Attribute[attributeList.size() + annAttributes.length];
    340            attributeList.toArray(attributes);
    341            System.arraycopy(annAttributes, 0, attributes, attributeList.size(), annAttributes.length);
    342        }
    343        // Must be last since the above calls may still add something to it
    344        final ConstantPool cp = this.cp.getFinalConstantPool();
    345        return new JavaClass(classNameIndex, superclassNameIndex, fileName, major, minor, super.getAccessFlags(), cp, interfaces, fields, methods,
    346            attributes);
    347    }
    348
    349    /**
    350     * @return major version number of class file
    351     */
    352    public int getMajor() {
    353        return major;
    354    }
    355
    356    public Method getMethodAt(final int pos) {
    357        return methodList.get(pos);
    358    }
    359
    360    public Method[] getMethods() {
    361        return methodList.toArray(Method.EMPTY_ARRAY);
    362    }
    363
    364    /**
    365     * @return minor version number of class file
    366     */
    367    public int getMinor() {
    368        return minor;
    369    }
    370
    371    public String getSuperclassName() {
    372        return superClassName;
    373    }
    374
    375    public int getSuperclassNameIndex() {
    376        return superclassNameIndex;
    377    }
    378
    379    /**
    380     * Return value as defined by given BCELComparator strategy. By default return the hashcode of the class name.
    381     *
    382     * @see Object#hashCode()
    383     */
    384    @Override
    385    public int hashCode() {
    386        return bcelComparator.hashCode(this);
    387    }
    388
    389    /**
    390     * Remove an attribute from this class.
    391     *
    392     * @param a attribute to remove
    393     */
    394    public void removeAttribute(final Attribute a) {
    395        attributeList.remove(a);
    396    }
    397
    398    /**
    399     * Remove a field to this class.
    400     *
    401     * @param f field to remove
    402     */
    403    public void removeField(final Field f) {
    404        fieldList.remove(f);
    405    }
    406
    407    /**
    408     * Remove an interface from this class.
    409     *
    410     * @param name interface to remove (fully qualified name)
    411     */
    412    public void removeInterface(final String name) {
    413        interfaceList.remove(name);
    414    }
    415
    416    /**
    417     * Remove a method from this class.
    418     *
    419     * @param m method to remove
    420     */
    421    public void removeMethod(final Method m) {
    422        methodList.remove(m);
    423    }
    424
    425    /**
    426     * Remove observer for this object.
    427     */
    428    public void removeObserver(final ClassObserver o) {
    429        if (observers != null) {
    430            observers.remove(o);
    431        }
    432    }
    433
    434    /**
    435     * Replace given field with new one. If the old one does not exist add the new_ field to the class anyway.
    436     */
    437    public void replaceField(final Field old, final Field new_) {
    438        if (new_ == null) {
    439            throw new ClassGenException("Replacement method must not be null");
    440        }
    441        final int i = fieldList.indexOf(old);
    442        if (i < 0) {
    443            fieldList.add(new_);
    444        } else {
    445            fieldList.set(i, new_);
    446        }
    447    }
    448
    449    /**
    450     * Replace given method with new one. If the old one does not exist add the new_ method to the class anyway.
    451     */
    452    public void replaceMethod(final Method old, final Method new_) {
    453        if (new_ == null) {
    454            throw new ClassGenException("Replacement method must not be null");
    455        }
    456        final int i = methodList.indexOf(old);
    457        if (i < 0) {
    458            methodList.add(new_);
    459        } else {
    460            methodList.set(i, new_);
    461        }
    462    }
    463
    464    public void setClassName(final String name) {
    465        className = Utility.pathToPackage(name);
    466        classNameIndex = cp.addClass(name);
    467    }
    468
    469    public void setClassNameIndex(final int classNameIndex) {
    470        this.classNameIndex = classNameIndex;
    471        this.className = Utility.pathToPackage(cp.getConstantPool().getConstantString(classNameIndex, Const.CONSTANT_Class));
    472    }
    473
    474    public void setConstantPool(final ConstantPoolGen constant_pool) {
    475        cp = constant_pool;
    476    }
    477
    478    /**
    479     * Set major version number of class file, default value is 45 (JDK 1.1)
    480     *
    481     * @param major major version number
    482     */
    483    public void setMajor(final int major) { // TODO could be package-protected - only called by test code
    484        this.major = major;
    485    }
    486
    487    public void setMethodAt(final Method method, final int pos) {
    488        methodList.set(pos, method);
    489    }
    490
    491    public void setMethods(final Method[] methods) {
    492        methodList.clear();
    493        Collections.addAll(methodList, methods);
    494    }
    495
    496    /**
    497     * Set minor version number of class file, default value is 3 (JDK 1.1)
    498     *
    499     * @param minor minor version number
    500     */
    501    public void setMinor(final int minor) { // TODO could be package-protected - only called by test code
    502        this.minor = minor;
    503    }
    504
    505    public void setSuperclassName(final String name) {
    506        superClassName = Utility.pathToPackage(name);
    507        superclassNameIndex = cp.addClass(name);
    508    }
    509
    510    public void setSuperclassNameIndex(final int superclassNameIndex) {
    511        this.superclassNameIndex = superclassNameIndex;
    512        superClassName = Utility.pathToPackage(cp.getConstantPool().getConstantString(superclassNameIndex, Const.CONSTANT_Class));
    513    }
    514
    515    /**
    516     * Look for attributes representing annotations and unpack them.
    517     */
    518    private AnnotationEntryGen[] unpackAnnotations(final Attribute[] attrs) {
    519        final List<AnnotationEntryGen> annotationGenObjs = new ArrayList<>();
    520        for (final Attribute attr : attrs) {
    521            if (attr instanceof RuntimeVisibleAnnotations) {
    522                final RuntimeVisibleAnnotations rva = (RuntimeVisibleAnnotations) attr;
    523                rva.forEach(a -> annotationGenObjs.add(new AnnotationEntryGen(a, getConstantPool(), false)));
    524            } else if (attr instanceof RuntimeInvisibleAnnotations) {
    525                final RuntimeInvisibleAnnotations ria = (RuntimeInvisibleAnnotations) attr;
    526                ria.forEach(a -> annotationGenObjs.add(new AnnotationEntryGen(a, getConstantPool(), false)));
    527            }
    528        }
    529        return annotationGenObjs.toArray(AnnotationEntryGen.EMPTY_ARRAY);
    530    }
    531
    532    /**
    533     * Call notify() method on all observers. This method is not called automatically whenever the state has changed, but
    534     * has to be called by the user after they have finished editing the object.
    535     */
    536    public void update() {
    537        if (observers != null) {
    538            for (final ClassObserver observer : observers) {
    539                observer.notify(this);
    540            }
    541        }
    542    }
    543}