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