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