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