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