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