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.classfile;
018
019import java.io.ByteArrayOutputStream;
020import java.io.DataOutputStream;
021import java.io.File;
022import java.io.FileOutputStream;
023import java.io.IOException;
024import java.io.OutputStream;
025import java.util.ArrayList;
026import java.util.Arrays;
027import java.util.List;
028import java.util.Objects;
029import java.util.Set;
030import java.util.StringTokenizer;
031import java.util.TreeSet;
032
033import org.apache.bcel.Const;
034import org.apache.bcel.generic.Type;
035import org.apache.bcel.util.BCELComparator;
036import org.apache.bcel.util.ClassQueue;
037import org.apache.bcel.util.SyntheticRepository;
038import org.apache.commons.lang3.ArrayUtils;
039
040/**
041 * Represents a Java class, i.e., the data structures, constant pool, fields, methods and commands contained in a Java
042 * .class file. See <a href="https://docs.oracle.com/javase/specs/">JVM specification</a> for details. The intent of
043 * this class is to represent a parsed or otherwise existing class file. Those interested in programmatically generating
044 * classes should see the <a href="../generic/ClassGen.html">ClassGen</a> class.
045 *
046 * @see org.apache.bcel.generic.ClassGen
047 */
048public class JavaClass extends AccessFlags implements Cloneable, Node, Comparable<JavaClass> {
049
050    /**
051     * The standard class file extension.
052     *
053     * @since 6.7.0
054     */
055    public static final String EXTENSION = ".class";
056
057    /**
058     * Empty array.
059     *
060     * @since 6.6.0
061     */
062    public static final JavaClass[] EMPTY_ARRAY = {};
063
064    public static final byte HEAP = 1;
065    public static final byte FILE = 2;
066    public static final byte ZIP = 3;
067    private static final boolean debug = Boolean.getBoolean("JavaClass.debug"); // Debugging on/off
068
069    private static BCELComparator<JavaClass> bcelComparator = new BCELComparator<JavaClass>() {
070
071        @Override
072        public boolean equals(final JavaClass a, final JavaClass b) {
073            return a == b || a != null && b != null && Objects.equals(a.getClassName(), b.getClassName());
074        }
075
076        @Override
077        public int hashCode(final JavaClass o) {
078            return o != null ? Objects.hashCode(o.getClassName()) : 0;
079        }
080    };
081
082    /*
083     * Print debug information depending on 'JavaClass.debug'
084     */
085    static void Debug(final String str) {
086        if (debug) {
087            System.out.println(str);
088        }
089    }
090
091    /**
092     * @return Comparison strategy object.
093     */
094    public static BCELComparator<JavaClass> getComparator() {
095        return bcelComparator;
096    }
097
098    private static String indent(final Object obj) {
099        final StringTokenizer tokenizer = new StringTokenizer(obj.toString(), "\n");
100        final StringBuilder buf = new StringBuilder();
101        while (tokenizer.hasMoreTokens()) {
102            buf.append("\t").append(tokenizer.nextToken()).append("\n");
103        }
104        return buf.toString();
105    }
106
107    /**
108     * @param comparator Comparison strategy object.
109     */
110    public static void setComparator(final BCELComparator<JavaClass> comparator) {
111        bcelComparator = comparator;
112    }
113
114    private String fileName;
115    private final String packageName;
116    private String sourceFileName = "<Unknown>";
117    private int classNameIndex;
118    private int superclassNameIndex;
119    private String className;
120    private String superclassName;
121    private int major;
122    private int minor; // Compiler version
123    private ConstantPool constantPool; // Constant pool
124    private int[] interfaces; // implemented interfaces
125    private String[] interfaceNames;
126    private Field[] fields; // Fields, i.e., variables of class
127    private Method[] methods; // methods defined in the class
128    private Attribute[] attributes; // attributes defined in the class
129
130    private AnnotationEntry[] annotations; // annotations defined on the class
131    private byte source = HEAP; // Generated in memory
132
133    private boolean isAnonymous;
134
135    private boolean isNested;
136    private boolean isRecord;
137
138    private boolean computedNestedTypeStatus;
139    private boolean computedRecord;
140
141    /**
142     * In cases where we go ahead and create something, use the default SyntheticRepository, because we don't know any
143     * better.
144     */
145    private transient org.apache.bcel.util.Repository repository = SyntheticRepository.getInstance();
146
147    /**
148     * Constructor gets all contents as arguments.
149     *
150     * @param classNameIndex Class name
151     * @param superclassNameIndex Superclass name
152     * @param fileName File name
153     * @param major Major compiler version
154     * @param minor Minor compiler version
155     * @param accessFlags Access rights defined by bit flags
156     * @param constantPool Array of constants
157     * @param interfaces Implemented interfaces
158     * @param fields Class fields
159     * @param methods Class methods
160     * @param attributes Class attributes
161     */
162    public JavaClass(final int classNameIndex, final int superclassNameIndex, final String fileName, final int major, final int minor, final int accessFlags,
163        final ConstantPool constantPool, final int[] interfaces, final Field[] fields, final Method[] methods, final Attribute[] attributes) {
164        this(classNameIndex, superclassNameIndex, fileName, major, minor, accessFlags, constantPool, interfaces, fields, methods, attributes, HEAP);
165    }
166
167    /**
168     * Constructor gets all contents as arguments.
169     *
170     * @param classNameIndex Index into constant pool referencing a ConstantClass that represents this class.
171     * @param superclassNameIndex Index into constant pool referencing a ConstantClass that represents this class's
172     *        superclass.
173     * @param fileName File name
174     * @param major Major compiler version
175     * @param minor Minor compiler version
176     * @param accessFlags Access rights defined by bit flags
177     * @param constantPool Array of constants
178     * @param interfaces Implemented interfaces
179     * @param fields Class fields
180     * @param methods Class methods
181     * @param attributes Class attributes
182     * @param source Read from file or generated in memory?
183     */
184    public JavaClass(final int classNameIndex, final int superclassNameIndex, final String fileName, final int major, final int minor, final int accessFlags,
185        final ConstantPool constantPool, int[] interfaces, Field[] fields, Method[] methods, Attribute[] attributes, final byte source) {
186        super(accessFlags);
187        interfaces = ArrayUtils.nullToEmpty(interfaces);
188        if (attributes == null) {
189            attributes = Attribute.EMPTY_ARRAY;
190        }
191        if (fields == null) {
192            fields = Field.EMPTY_ARRAY;
193        }
194        if (methods == null) {
195            methods = Method.EMPTY_ARRAY;
196        }
197        this.classNameIndex = classNameIndex;
198        this.superclassNameIndex = superclassNameIndex;
199        this.fileName = fileName;
200        this.major = major;
201        this.minor = minor;
202        this.constantPool = constantPool;
203        this.interfaces = interfaces;
204        this.fields = fields;
205        this.methods = methods;
206        this.attributes = attributes;
207        this.source = source;
208        // Get source file name if available
209        for (final Attribute attribute : attributes) {
210            if (attribute instanceof SourceFile) {
211                sourceFileName = ((SourceFile) attribute).getSourceFileName();
212                break;
213            }
214        }
215        /*
216         * According to the specification the following entries must be of type 'ConstantClass' but we check that anyway via the
217         * 'ConstPool.getConstant' method.
218         */
219        className = constantPool.getConstantString(classNameIndex, Const.CONSTANT_Class);
220        className = Utility.compactClassName(className, false);
221        final int index = className.lastIndexOf('.');
222        if (index < 0) {
223            packageName = "";
224        } else {
225            packageName = className.substring(0, index);
226        }
227        if (superclassNameIndex > 0) {
228            // May be zero -> class is java.lang.Object
229            superclassName = constantPool.getConstantString(superclassNameIndex, Const.CONSTANT_Class);
230            superclassName = Utility.compactClassName(superclassName, false);
231        } else {
232            superclassName = "java.lang.Object";
233        }
234        interfaceNames = new String[interfaces.length];
235        for (int i = 0; i < interfaces.length; i++) {
236            final String str = constantPool.getConstantString(interfaces[i], Const.CONSTANT_Class);
237            interfaceNames[i] = Utility.compactClassName(str, false);
238        }
239    }
240
241    /**
242     * Called by objects that are traversing the nodes of the tree implicitly defined by the contents of a Java class.
243     * I.e., the hierarchy of methods, fields, attributes, etc. spawns a tree of objects.
244     *
245     * @param v Visitor object
246     */
247    @Override
248    public void accept(final Visitor v) {
249        v.visitJavaClass(this);
250    }
251
252    /**
253     * Return the natural ordering of two JavaClasses. This ordering is based on the class name
254     *
255     * @since 6.0
256     */
257    @Override
258    public int compareTo(final JavaClass obj) {
259        return getClassName().compareTo(obj.getClassName());
260    }
261
262    private void computeIsRecord() {
263        if (computedRecord) {
264            return;
265        }
266        for (final Attribute attribute : this.attributes) {
267            if (attribute instanceof Record) {
268                isRecord = true;
269                break;
270            }
271        }
272        this.computedRecord = true;
273    }
274
275    private void computeNestedTypeStatus() {
276        if (computedNestedTypeStatus) {
277            return;
278        }
279        for (final Attribute attribute : this.attributes) {
280            if (attribute instanceof InnerClasses) {
281                ((InnerClasses) attribute).forEach(innerClass ->  {
282                    boolean innerClassAttributeRefersToMe = false;
283                    String innerClassName = constantPool.getConstantString(innerClass.getInnerClassIndex(), Const.CONSTANT_Class);
284                    innerClassName = Utility.compactClassName(innerClassName, false);
285                    if (innerClassName.equals(getClassName())) {
286                        innerClassAttributeRefersToMe = true;
287                    }
288                    if (innerClassAttributeRefersToMe) {
289                        this.isNested = true;
290                        if (innerClass.getInnerNameIndex() == 0) {
291                            this.isAnonymous = true;
292                        }
293                    }
294                });
295            }
296        }
297        this.computedNestedTypeStatus = true;
298    }
299
300    /**
301     * @return deep copy of this class
302     */
303    public JavaClass copy() {
304        try {
305            final JavaClass c = (JavaClass) clone();
306            c.constantPool = constantPool.copy();
307            c.interfaces = interfaces.clone();
308            c.interfaceNames = interfaceNames.clone();
309            c.fields = new Field[fields.length];
310            Arrays.setAll(c.fields, i -> fields[i].copy(c.constantPool));
311            c.methods = new Method[methods.length];
312            Arrays.setAll(c.methods, i -> methods[i].copy(c.constantPool));
313            c.attributes = new Attribute[attributes.length];
314            Arrays.setAll(c.attributes, i -> attributes[i].copy(c.constantPool));
315            return c;
316        } catch (final CloneNotSupportedException e) {
317            return null;
318        }
319    }
320
321    /**
322     * Dump Java class to output stream in binary format.
323     *
324     * @param file Output stream
325     * @throws IOException if an I/O error occurs.
326     */
327    public void dump(final DataOutputStream file) throws IOException {
328        file.writeInt(Const.JVM_CLASSFILE_MAGIC);
329        file.writeShort(minor);
330        file.writeShort(major);
331        constantPool.dump(file);
332        file.writeShort(super.getAccessFlags());
333        file.writeShort(classNameIndex);
334        file.writeShort(superclassNameIndex);
335        file.writeShort(interfaces.length);
336        for (final int interface1 : interfaces) {
337            file.writeShort(interface1);
338        }
339        file.writeShort(fields.length);
340        for (final Field field : fields) {
341            field.dump(file);
342        }
343        file.writeShort(methods.length);
344        for (final Method method : methods) {
345            method.dump(file);
346        }
347        if (attributes != null) {
348            file.writeShort(attributes.length);
349            for (final Attribute attribute : attributes) {
350                attribute.dump(file);
351            }
352        } else {
353            file.writeShort(0);
354        }
355        file.flush();
356    }
357
358    /**
359     * Dump class to a file.
360     *
361     * @param file Output file
362     * @throws IOException if an I/O error occurs.
363     */
364    public void dump(final File file) throws IOException {
365        final String parent = file.getParent();
366        if (parent != null) {
367            final File dir = new File(parent);
368            if (!dir.mkdirs() && !dir.isDirectory()) {
369                throw new IOException("Could not create the directory " + dir);
370            }
371        }
372        try (DataOutputStream dos = new DataOutputStream(new FileOutputStream(file))) {
373            dump(dos);
374        }
375    }
376
377    /**
378     * Dump Java class to output stream in binary format.
379     *
380     * @param file Output stream
381     * @throws IOException if an I/O error occurs.
382     */
383    public void dump(final OutputStream file) throws IOException {
384        dump(new DataOutputStream(file));
385    }
386
387    /**
388     * Dump class to a file named fileName.
389     *
390     * @param fileName Output file name
391     * @throws IOException if an I/O error occurs.
392     */
393    public void dump(final String fileName) throws IOException {
394        dump(new File(fileName));
395    }
396
397    /**
398     * Return value as defined by given BCELComparator strategy. By default two JavaClass objects are said to be equal when
399     * their class names are equal.
400     *
401     * @see Object#equals(Object)
402     */
403    @Override
404    public boolean equals(final Object obj) {
405        return obj instanceof JavaClass && bcelComparator.equals(this, (JavaClass) obj);
406    }
407
408    /**
409     * Finds a visible field by name and type in this class and its super classes.
410     * @param fieldName the field name to find
411     * @param fieldType the field type to find
412     * @return field matching given name and type, null if field is not found or not accessible from this class.
413     * @throws ClassNotFoundException
414     * @since 6.8.0
415     */
416    public Field findField(final String fieldName, final Type fieldType) throws ClassNotFoundException {
417        for (final Field field : fields) {
418            if (field.getName().equals(fieldName)) {
419                final Type fType = Type.getType(field.getSignature());
420                /*
421                 * TODO: Check if assignment compatibility is sufficient. What does Sun do?
422                 */
423                if (fType.equals(fieldType)) {
424                    return field;
425                }
426            }
427        }
428
429        final JavaClass superclass = getSuperClass();
430        if (superclass != null && !"java.lang.Object".equals(superclass.getClassName())) {
431            final Field f = superclass.findField(fieldName, fieldType);
432            if (f != null && (f.isPublic() || f.isProtected() || !f.isPrivate() && packageName.equals(superclass.getPackageName()))) {
433                return f;
434            }
435        }
436        final JavaClass[] implementedInterfaces = getInterfaces();
437        if (implementedInterfaces != null) {
438            for (final JavaClass implementedInterface : implementedInterfaces) {
439                final Field f = implementedInterface.findField(fieldName, fieldType);
440                if (f != null) {
441                    return f;
442                }
443            }
444        }
445        return null;
446    }
447
448    /**
449     * Gets all interfaces implemented by this JavaClass (transitively).
450     *
451     * @throws ClassNotFoundException if any of the class's superclasses or interfaces can't be found.
452     */
453    public JavaClass[] getAllInterfaces() throws ClassNotFoundException {
454        final ClassQueue queue = new ClassQueue();
455        final Set<JavaClass> allInterfaces = new TreeSet<>();
456        queue.enqueue(this);
457        while (!queue.empty()) {
458            final JavaClass clazz = queue.dequeue();
459            final JavaClass souper = clazz.getSuperClass();
460            final JavaClass[] interfaces = clazz.getInterfaces();
461            if (clazz.isInterface()) {
462                allInterfaces.add(clazz);
463            } else if (souper != null) {
464                queue.enqueue(souper);
465            }
466            for (final JavaClass iface : interfaces) {
467                queue.enqueue(iface);
468            }
469        }
470        return allInterfaces.toArray(EMPTY_ARRAY);
471    }
472
473    /**
474     * @return Annotations on the class
475     * @since 6.0
476     */
477    public AnnotationEntry[] getAnnotationEntries() {
478        if (annotations == null) {
479            annotations = AnnotationEntry.createAnnotationEntries(getAttributes());
480        }
481
482        return annotations;
483    }
484
485    /**
486     * Gets attribute for given tag.
487     * @return Attribute for given tag, null if not found.
488     * Refer to {@link org.apache.bcel.Const#ATTR_UNKNOWN} constants named ATTR_* for possible values.
489     * @since 6.10.0
490     */
491    @SuppressWarnings("unchecked")
492    public final <T extends Attribute> T getAttribute(final byte tag) {
493        for (final Attribute attribute : getAttributes()) {
494            if (attribute.getTag() == tag) {
495                return (T) attribute;
496            }
497        }
498        return null;
499    }
500
501    /**
502     * @return Attributes of the class.
503     */
504    public Attribute[] getAttributes() {
505        return attributes;
506    }
507
508    /**
509     * @return class in binary format
510     */
511    public byte[] getBytes() {
512        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
513        try (DataOutputStream dos = new DataOutputStream(baos)) {
514            dump(dos);
515        } catch (final IOException e) {
516            e.printStackTrace();
517        }
518        return baos.toByteArray();
519    }
520
521    /**
522     * @return Class name.
523     */
524    public String getClassName() {
525        return className;
526    }
527
528    /**
529     * @return Class name index.
530     */
531    public int getClassNameIndex() {
532        return classNameIndex;
533    }
534
535    /**
536     * @return Constant pool.
537     */
538    public ConstantPool getConstantPool() {
539        return constantPool;
540    }
541
542    /**
543     * @return Fields, i.e., variables of the class. Like the JVM spec mandates for the classfile format, these fields are
544     *         those specific to this class, and not those of the superclass or superinterfaces.
545     */
546    public Field[] getFields() {
547        return fields;
548    }
549
550    /**
551     * @return File name of class, aka SourceFile attribute value
552     */
553    public String getFileName() {
554        return fileName;
555    }
556
557    /**
558     * @return Indices in constant pool of implemented interfaces.
559     */
560    public int[] getInterfaceIndices() {
561        return interfaces;
562    }
563
564    /**
565     * @return Names of implemented interfaces.
566     */
567    public String[] getInterfaceNames() {
568        return interfaceNames;
569    }
570
571    /**
572     * Gets interfaces directly implemented by this JavaClass.
573     *
574     * @throws ClassNotFoundException if any of the class's interfaces can't be found.
575     */
576    public JavaClass[] getInterfaces() throws ClassNotFoundException {
577        final String[] interfaces = getInterfaceNames();
578        final JavaClass[] classes = new JavaClass[interfaces.length];
579        for (int i = 0; i < interfaces.length; i++) {
580            classes[i] = repository.loadClass(interfaces[i]);
581        }
582        return classes;
583    }
584
585    /**
586     * @return Major number of class file version.
587     */
588    public int getMajor() {
589        return major;
590    }
591
592    /**
593     * @return A {@link Method} corresponding to java.lang.reflect.Method if any
594     */
595    public Method getMethod(final java.lang.reflect.Method m) {
596        for (final Method method : methods) {
597            if (m.getName().equals(method.getName()) && m.getModifiers() == method.getModifiers() && Type.getSignature(m).equals(method.getSignature())) {
598                return method;
599            }
600        }
601        return null;
602    }
603
604    /**
605     * @return Methods of the class.
606     */
607    public Method[] getMethods() {
608        return methods;
609    }
610
611    /**
612     * @return Minor number of class file version.
613     */
614    public int getMinor() {
615        return minor;
616    }
617
618    /**
619     * @return Package name.
620     */
621    public String getPackageName() {
622        return packageName;
623    }
624
625    /**
626     * Gets the ClassRepository which holds its definition. By default this is the same as
627     * SyntheticRepository.getInstance();
628     */
629    public org.apache.bcel.util.Repository getRepository() {
630        return repository;
631    }
632
633    /**
634     * @return returns either HEAP (generated), FILE, or ZIP
635     */
636    public final byte getSource() {
637        return source;
638    }
639
640    /**
641     * @return file name where this class was read from
642     */
643    public String getSourceFileName() {
644        return sourceFileName;
645    }
646
647    /**
648     * Gets the source file path including the package path.
649     *
650     * @return path to original source file of parsed class, relative to original source directory.
651     * @since 6.7.0
652     */
653    public String getSourceFilePath() {
654        final StringBuilder outFileName = new StringBuilder();
655        if (!packageName.isEmpty()) {
656            outFileName.append(Utility.packageToPath(packageName));
657            outFileName.append('/');
658        }
659        outFileName.append(sourceFileName);
660        return outFileName.toString();
661    }
662
663    /**
664     * @return the superclass for this JavaClass object, or null if this is {@link Object}
665     * @throws ClassNotFoundException if the superclass can't be found
666     */
667    public JavaClass getSuperClass() throws ClassNotFoundException {
668        if ("java.lang.Object".equals(getClassName())) {
669            return null;
670        }
671        return repository.loadClass(getSuperclassName());
672    }
673
674    /**
675     * @return list of super classes of this class in ascending order, i.e., java.lang.Object is always the last element
676     * @throws ClassNotFoundException if any of the superclasses can't be found
677     */
678    public JavaClass[] getSuperClasses() throws ClassNotFoundException {
679        JavaClass clazz = this;
680        final List<JavaClass> allSuperClasses = new ArrayList<>();
681        for (clazz = clazz.getSuperClass(); clazz != null; clazz = clazz.getSuperClass()) {
682            allSuperClasses.add(clazz);
683        }
684        return allSuperClasses.toArray(EMPTY_ARRAY);
685    }
686
687    /**
688     * returns the super class name of this class. In the case that this class is {@link Object}, it will return itself
689     * ({@link Object}). This is probably incorrect but isn't fixed at this time to not break existing clients.
690     *
691     * @return Superclass name.
692     */
693    public String getSuperclassName() {
694        return superclassName;
695    }
696
697    /**
698     * @return Class name index.
699     */
700    public int getSuperclassNameIndex() {
701        return superclassNameIndex;
702    }
703
704    /**
705     * Return value as defined by given BCELComparator strategy. By default return the hash code of the class name.
706     *
707     * @see Object#hashCode()
708     */
709    @Override
710    public int hashCode() {
711        return bcelComparator.hashCode(this);
712    }
713
714    /**
715     * @return true, if this class is an implementation of interface inter
716     * @throws ClassNotFoundException if superclasses or superinterfaces of this class can't be found
717     */
718    public boolean implementationOf(final JavaClass inter) throws ClassNotFoundException {
719        if (!inter.isInterface()) {
720            throw new IllegalArgumentException(inter.getClassName() + " is no interface");
721        }
722        if (equals(inter)) {
723            return true;
724        }
725        final JavaClass[] superInterfaces = getAllInterfaces();
726        for (final JavaClass superInterface : superInterfaces) {
727            if (superInterface.equals(inter)) {
728                return true;
729            }
730        }
731        return false;
732    }
733
734    /**
735     * Equivalent to runtime "instanceof" operator.
736     *
737     * @return true if this JavaClass is derived from the super class
738     * @throws ClassNotFoundException if superclasses or superinterfaces of this object can't be found
739     */
740    public final boolean instanceOf(final JavaClass superclass) throws ClassNotFoundException {
741        if (equals(superclass)) {
742            return true;
743        }
744        for (final JavaClass clazz : getSuperClasses()) {
745            if (clazz.equals(superclass)) {
746                return true;
747            }
748        }
749        if (superclass.isInterface()) {
750            return implementationOf(superclass);
751        }
752        return false;
753    }
754
755    /**
756     * @since 6.0
757     */
758    public final boolean isAnonymous() {
759        computeNestedTypeStatus();
760        return this.isAnonymous;
761    }
762
763    public final boolean isClass() {
764        return (super.getAccessFlags() & Const.ACC_INTERFACE) == 0;
765    }
766
767    /**
768     * @since 6.0
769     */
770    public final boolean isNested() {
771        computeNestedTypeStatus();
772        return this.isNested;
773    }
774
775    /**
776     * Tests whether this class was declared as a record
777     *
778     * @return true if a record attribute is present, false otherwise.
779     * @since 6.9.0
780     */
781    public boolean isRecord() {
782        computeIsRecord();
783        return this.isRecord;
784    }
785
786    public final boolean isSuper() {
787        return (super.getAccessFlags() & Const.ACC_SUPER) != 0;
788    }
789
790    /**
791     * @param attributes .
792     */
793    public void setAttributes(final Attribute[] attributes) {
794        this.attributes = attributes != null ? attributes : Attribute.EMPTY_ARRAY;
795    }
796
797    /**
798     * @param className .
799     */
800    public void setClassName(final String className) {
801        this.className = className;
802    }
803
804    /**
805     * @param classNameIndex .
806     */
807    public void setClassNameIndex(final int classNameIndex) {
808        this.classNameIndex = classNameIndex;
809    }
810
811    /**
812     * @param constantPool .
813     */
814    public void setConstantPool(final ConstantPool constantPool) {
815        this.constantPool = constantPool;
816    }
817
818    /**
819     * @param fields .
820     */
821    public void setFields(final Field[] fields) {
822        this.fields = fields != null ? fields : Field.EMPTY_ARRAY;
823    }
824
825    /**
826     * Sets File name of class, aka SourceFile attribute value
827     */
828    public void setFileName(final String fileName) {
829        this.fileName = fileName;
830    }
831
832    /**
833     * @param interfaceNames .
834     */
835    public void setInterfaceNames(final String[] interfaceNames) {
836        this.interfaceNames = ArrayUtils.nullToEmpty(interfaceNames);
837    }
838
839    /**
840     * @param interfaces .
841     */
842    public void setInterfaces(final int[] interfaces) {
843        this.interfaces = ArrayUtils.nullToEmpty(interfaces);
844    }
845
846    /**
847     * @param major .
848     */
849    public void setMajor(final int major) {
850        this.major = major;
851    }
852
853    /**
854     * @param methods .
855     */
856    public void setMethods(final Method[] methods) {
857        this.methods = methods != null ? methods : Method.EMPTY_ARRAY;
858    }
859
860    /**
861     * @param minor .
862     */
863    public void setMinor(final int minor) {
864        this.minor = minor;
865    }
866
867    /**
868     * Sets the ClassRepository which loaded the JavaClass. Should be called immediately after parsing is done.
869     */
870    public void setRepository(final org.apache.bcel.util.Repository repository) { // TODO make protected?
871        this.repository = repository;
872    }
873
874    /**
875     * Sets absolute path to file this class was read from.
876     */
877    public void setSourceFileName(final String sourceFileName) {
878        this.sourceFileName = sourceFileName;
879    }
880
881    /**
882     * @param superclassName .
883     */
884    public void setSuperclassName(final String superclassName) {
885        this.superclassName = superclassName;
886    }
887
888    /**
889     * @param superclassNameIndex .
890     */
891    public void setSuperclassNameIndex(final int superclassNameIndex) {
892        this.superclassNameIndex = superclassNameIndex;
893    }
894
895    /**
896     * @return String representing class contents.
897     */
898    @Override
899    public String toString() {
900        String access = Utility.accessToString(super.getAccessFlags(), true);
901        access = access.isEmpty() ? "" : access + " ";
902        final StringBuilder buf = new StringBuilder(128);
903        buf.append(access).append(Utility.classOrInterface(super.getAccessFlags())).append(" ").append(className).append(" extends ")
904            .append(Utility.compactClassName(superclassName, false)).append('\n');
905        final int size = interfaces.length;
906        if (size > 0) {
907            buf.append("implements\t\t");
908            for (int i = 0; i < size; i++) {
909                buf.append(interfaceNames[i]);
910                if (i < size - 1) {
911                    buf.append(", ");
912                }
913            }
914            buf.append('\n');
915        }
916        buf.append("file name\t\t").append(fileName).append('\n');
917        buf.append("compiled from\t\t").append(sourceFileName).append('\n');
918        buf.append("compiler version\t").append(major).append(".").append(minor).append('\n');
919        buf.append("access flags\t\t").append(super.getAccessFlags()).append('\n');
920        buf.append("constant pool\t\t").append(constantPool.getLength()).append(" entries\n");
921        buf.append("ACC_SUPER flag\t\t").append(isSuper()).append("\n");
922        if (attributes.length > 0) {
923            buf.append("\nAttribute(s):\n");
924            for (final Attribute attribute : attributes) {
925                buf.append(indent(attribute));
926            }
927        }
928        final AnnotationEntry[] annotations = getAnnotationEntries();
929        if (annotations != null && annotations.length > 0) {
930            buf.append("\nAnnotation(s):\n");
931            for (final AnnotationEntry annotation : annotations) {
932                buf.append(indent(annotation));
933            }
934        }
935        if (fields.length > 0) {
936            buf.append("\n").append(fields.length).append(" fields:\n");
937            for (final Field field : fields) {
938                buf.append("\t").append(field).append('\n');
939            }
940        }
941        if (methods.length > 0) {
942            buf.append("\n").append(methods.length).append(" methods:\n");
943            for (final Method method : methods) {
944                buf.append("\t").append(method).append('\n');
945            }
946        }
947        return buf.toString();
948    }
949}