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