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