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