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 UnsupportedOperationException("Clone Not Supported", e); // 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 hash code 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     * Sets 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     * Sets 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}