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