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