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