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    private static BCELComparator bcelComparator = new BCELComparator() {
069
070        @Override
071        public boolean equals(final Object o1, final Object o2) {
072            final JavaClass THIS = (JavaClass) o1;
073            final JavaClass THAT = (JavaClass) o2;
074            return Objects.equals(THIS.getClassName(), THAT.getClassName());
075        }
076
077        @Override
078        public int hashCode(final Object o) {
079            final JavaClass THIS = (JavaClass) o;
080            return THIS.getClassName().hashCode();
081        }
082    };
083
084    /*
085     * Print debug information depending on 'JavaClass.debug'
086     */
087    static void Debug(final String str) {
088        if (debug) {
089            System.out.println(str);
090        }
091    }
092
093    /**
094     * @return Comparison strategy object
095     */
096    public static BCELComparator getComparator() {
097        return bcelComparator;
098    }
099
100    private static String indent(final Object obj) {
101        final StringTokenizer tokenizer = new StringTokenizer(obj.toString(), "\n");
102        final StringBuilder buf = new StringBuilder();
103        while (tokenizer.hasMoreTokens()) {
104            buf.append("\t").append(tokenizer.nextToken()).append("\n");
105        }
106        return buf.toString();
107    }
108
109    /**
110     * @param comparator Comparison strategy object
111     */
112    public static void setComparator(final BCELComparator comparator) {
113        bcelComparator = comparator;
114    }
115
116    private String fileName;
117    private final String packageName;
118    private String sourceFileName = "<Unknown>";
119    private int classNameIndex;
120    private int superclassNameIndex;
121    private String className;
122    private String superclassName;
123    private int major;
124    private int minor; // Compiler version
125    private ConstantPool constantPool; // Constant pool
126    private int[] interfaces; // implemented interfaces
127    private String[] interfaceNames;
128    private Field[] fields; // Fields, i.e., variables of class
129    private Method[] methods; // methods defined in the class
130    private Attribute[] attributes; // attributes defined in the class
131
132    private AnnotationEntry[] annotations; // annotations defined on the class
133    private byte source = HEAP; // Generated in memory
134
135    private boolean isAnonymous;
136
137    private boolean isNested;
138
139    private boolean computedNestedTypeStatus;
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        if (interfaces == null) {
188            interfaces = ArrayUtils.EMPTY_INT_ARRAY;
189        }
190        if (attributes == null) {
191            attributes = Attribute.EMPTY_ARRAY;
192        }
193        if (fields == null) {
194            fields = Field.EMPTY_FIELD_ARRAY;
195        }
196        if (methods == null) {
197            methods = Method.EMPTY_METHOD_ARRAY;
198        }
199        this.classNameIndex = classNameIndex;
200        this.superclassNameIndex = superclassNameIndex;
201        this.fileName = fileName;
202        this.major = major;
203        this.minor = minor;
204        this.constantPool = constantPool;
205        this.interfaces = interfaces;
206        this.fields = fields;
207        this.methods = methods;
208        this.attributes = attributes;
209        this.source = source;
210        // Get source file name if available
211        for (final Attribute attribute : attributes) {
212            if (attribute instanceof SourceFile) {
213                sourceFileName = ((SourceFile) attribute).getSourceFileName();
214                break;
215            }
216        }
217        /*
218         * According to the specification the following entries must be of type 'ConstantClass' but we check that anyway via the
219         * 'ConstPool.getConstant' method.
220         */
221        className = constantPool.getConstantString(classNameIndex, Const.CONSTANT_Class);
222        className = Utility.compactClassName(className, false);
223        final int index = className.lastIndexOf('.');
224        if (index < 0) {
225            packageName = "";
226        } else {
227            packageName = className.substring(0, index);
228        }
229        if (superclassNameIndex > 0) {
230            // May be zero -> class is java.lang.Object
231            superclassName = constantPool.getConstantString(superclassNameIndex, Const.CONSTANT_Class);
232            superclassName = Utility.compactClassName(superclassName, false);
233        } else {
234            superclassName = "java.lang.Object";
235        }
236        interfaceNames = new String[interfaces.length];
237        for (int i = 0; i < interfaces.length; i++) {
238            final String str = constantPool.getConstantString(interfaces[i], Const.CONSTANT_Class);
239            interfaceNames[i] = Utility.compactClassName(str, false);
240        }
241    }
242
243    /**
244     * Called by objects that are traversing the nodes of the tree implicitly defined by the contents of a Java class.
245     * I.e., the hierarchy of methods, fields, attributes, etc. spawns a tree of objects.
246     *
247     * @param v Visitor object
248     */
249    @Override
250    public void accept(final Visitor v) {
251        v.visitJavaClass(this);
252    }
253
254    /**
255     * Return the natural ordering of two JavaClasses. This ordering is based on the class name
256     *
257     * @since 6.0
258     */
259    @Override
260    public int compareTo(final JavaClass obj) {
261        return getClassName().compareTo(obj.getClassName());
262    }
263
264    private void computeNestedTypeStatus() {
265        if (computedNestedTypeStatus) {
266            return;
267        }
268        for (final Attribute attribute : this.attributes) {
269            if (attribute instanceof InnerClasses) {
270                ((InnerClasses) attribute).forEach(innerClass ->  {
271                    boolean innerClassAttributeRefersToMe = false;
272                    String innerClassName = constantPool.getConstantString(innerClass.getInnerClassIndex(), Const.CONSTANT_Class);
273                    innerClassName = Utility.compactClassName(innerClassName, false);
274                    if (innerClassName.equals(getClassName())) {
275                        innerClassAttributeRefersToMe = true;
276                    }
277                    if (innerClassAttributeRefersToMe) {
278                        this.isNested = true;
279                        if (innerClass.getInnerNameIndex() == 0) {
280                            this.isAnonymous = true;
281                        }
282                    }
283                });
284            }
285        }
286        this.computedNestedTypeStatus = true;
287    }
288
289    /**
290     * @return deep copy of this class
291     */
292    public JavaClass copy() {
293        try {
294            final JavaClass c = (JavaClass) clone();
295            c.constantPool = constantPool.copy();
296            c.interfaces = interfaces.clone();
297            c.interfaceNames = interfaceNames.clone();
298            c.fields = new Field[fields.length];
299            Arrays.setAll(c.fields, i -> fields[i].copy(c.constantPool));
300            c.methods = new Method[methods.length];
301            Arrays.setAll(c.methods, i -> methods[i].copy(c.constantPool));
302            c.attributes = new Attribute[attributes.length];
303            Arrays.setAll(c.attributes, i -> attributes[i].copy(c.constantPool));
304            return c;
305        } catch (final CloneNotSupportedException e) {
306            return null;
307        }
308    }
309
310    /**
311     * Dump Java class to output stream in binary format.
312     *
313     * @param file Output stream
314     * @throws IOException if an I/O error occurs.
315     */
316    public void dump(final DataOutputStream file) throws IOException {
317        file.writeInt(Const.JVM_CLASSFILE_MAGIC);
318        file.writeShort(minor);
319        file.writeShort(major);
320        constantPool.dump(file);
321        file.writeShort(super.getAccessFlags());
322        file.writeShort(classNameIndex);
323        file.writeShort(superclassNameIndex);
324        file.writeShort(interfaces.length);
325        for (final int interface1 : interfaces) {
326            file.writeShort(interface1);
327        }
328        file.writeShort(fields.length);
329        for (final Field field : fields) {
330            field.dump(file);
331        }
332        file.writeShort(methods.length);
333        for (final Method method : methods) {
334            method.dump(file);
335        }
336        if (attributes != null) {
337            file.writeShort(attributes.length);
338            for (final Attribute attribute : attributes) {
339                attribute.dump(file);
340            }
341        } else {
342            file.writeShort(0);
343        }
344        file.flush();
345    }
346
347    /**
348     * Dump class to a file.
349     *
350     * @param file Output file
351     * @throws IOException if an I/O error occurs.
352     */
353    public void dump(final File file) throws IOException {
354        final String parent = file.getParent();
355        if (parent != null) {
356            final File dir = new File(parent);
357            if (!dir.mkdirs() && !dir.isDirectory()) {
358                throw new IOException("Could not create the directory " + dir);
359            }
360        }
361        try (DataOutputStream dos = new DataOutputStream(new FileOutputStream(file))) {
362            dump(dos);
363        }
364    }
365
366    /**
367     * Dump Java class to output stream in binary format.
368     *
369     * @param file Output stream
370     * @throws IOException if an I/O error occurs.
371     */
372    public void dump(final OutputStream file) throws IOException {
373        dump(new DataOutputStream(file));
374    }
375
376    /**
377     * Dump class to a file named fileName.
378     *
379     * @param fileName Output file name
380     * @throws IOException if an I/O error occurs.
381     */
382    public void dump(final String fileName) throws IOException {
383        dump(new File(fileName));
384    }
385
386    /**
387     * Return value as defined by given BCELComparator strategy. By default two JavaClass objects are said to be equal when
388     * their class names are equal.
389     *
390     * @see Object#equals(Object)
391     */
392    @Override
393    public boolean equals(final Object obj) {
394        return bcelComparator.equals(this, obj);
395    }
396
397    /**
398     * Get all interfaces implemented by this JavaClass (transitively).
399     *
400     * @throws ClassNotFoundException if any of the class's superclasses or interfaces can't be found.
401     */
402    public JavaClass[] getAllInterfaces() throws ClassNotFoundException {
403        final ClassQueue queue = new ClassQueue();
404        final Set<JavaClass> allInterfaces = new TreeSet<>();
405        queue.enqueue(this);
406        while (!queue.empty()) {
407            final JavaClass clazz = queue.dequeue();
408            final JavaClass souper = clazz.getSuperClass();
409            final JavaClass[] interfaces = clazz.getInterfaces();
410            if (clazz.isInterface()) {
411                allInterfaces.add(clazz);
412            } else if (souper != null) {
413                queue.enqueue(souper);
414            }
415            for (final JavaClass iface : interfaces) {
416                queue.enqueue(iface);
417            }
418        }
419        return allInterfaces.toArray(JavaClass.EMPTY_ARRAY);
420    }
421
422    /**
423     * @return Annotations on the class
424     * @since 6.0
425     */
426    public AnnotationEntry[] getAnnotationEntries() {
427        if (annotations == null) {
428            annotations = AnnotationEntry.createAnnotationEntries(getAttributes());
429        }
430
431        return annotations;
432    }
433
434    /**
435     * @return Attributes of the class.
436     */
437    public Attribute[] getAttributes() {
438        return attributes;
439    }
440
441    /**
442     * @return class in binary format
443     */
444    public byte[] getBytes() {
445        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
446        try (DataOutputStream dos = new DataOutputStream(baos)) {
447            dump(dos);
448        } catch (final IOException e) {
449            e.printStackTrace();
450        }
451        return baos.toByteArray();
452    }
453
454    /**
455     * @return Class name.
456     */
457    public String getClassName() {
458        return className;
459    }
460
461    /**
462     * @return Class name index.
463     */
464    public int getClassNameIndex() {
465        return classNameIndex;
466    }
467
468    /**
469     * @return Constant pool.
470     */
471    public ConstantPool getConstantPool() {
472        return constantPool;
473    }
474
475    /**
476     * @return Fields, i.e., variables of the class. Like the JVM spec mandates for the classfile format, these fields are
477     *         those specific to this class, and not those of the superclass or superinterfaces.
478     */
479    public Field[] getFields() {
480        return fields;
481    }
482
483    /**
484     * @return File name of class, aka SourceFile attribute value
485     */
486    public String getFileName() {
487        return fileName;
488    }
489
490    /**
491     * @return Indices in constant pool of implemented interfaces.
492     */
493    public int[] getInterfaceIndices() {
494        return interfaces;
495    }
496
497    /**
498     * @return Names of implemented interfaces.
499     */
500    public String[] getInterfaceNames() {
501        return interfaceNames;
502    }
503
504    /**
505     * Get interfaces directly implemented by this JavaClass.
506     *
507     * @throws ClassNotFoundException if any of the class's interfaces can't be found.
508     */
509    public JavaClass[] getInterfaces() throws ClassNotFoundException {
510        final String[] interfaces = getInterfaceNames();
511        final JavaClass[] classes = new JavaClass[interfaces.length];
512        for (int i = 0; i < interfaces.length; i++) {
513            classes[i] = repository.loadClass(interfaces[i]);
514        }
515        return classes;
516    }
517
518    /**
519     * @return Major number of class file version.
520     */
521    public int getMajor() {
522        return major;
523    }
524
525    /**
526     * @return A {@link Method} corresponding to java.lang.reflect.Method if any
527     */
528    public Method getMethod(final java.lang.reflect.Method m) {
529        for (final Method method : methods) {
530            if (m.getName().equals(method.getName()) && m.getModifiers() == method.getModifiers() && Type.getSignature(m).equals(method.getSignature())) {
531                return method;
532            }
533        }
534        return null;
535    }
536
537    /**
538     * @return Methods of the class.
539     */
540    public Method[] getMethods() {
541        return methods;
542    }
543
544    /**
545     * @return Minor number of class file version.
546     */
547    public int getMinor() {
548        return minor;
549    }
550
551    /**
552     * @return Package name.
553     */
554    public String getPackageName() {
555        return packageName;
556    }
557
558    /**
559     * Gets the ClassRepository which holds its definition. By default this is the same as
560     * SyntheticRepository.getInstance();
561     */
562    public org.apache.bcel.util.Repository getRepository() {
563        return repository;
564    }
565
566    /**
567     * @return returns either HEAP (generated), FILE, or ZIP
568     */
569    public final byte getSource() {
570        return source;
571    }
572
573    /**
574     * @return file name where this class was read from
575     */
576    public String getSourceFileName() {
577        return sourceFileName;
578    }
579
580    /**
581     * Gets the source file path including the package path.
582     *
583     * @return path to original source file of parsed class, relative to original source directory.
584     * @since 6.7.0
585     */
586    public String getSourceFilePath() {
587        final StringBuilder outFileName = new StringBuilder();
588        if (!packageName.isEmpty()) {
589            outFileName.append(Utility.packageToPath(packageName));
590            outFileName.append('/');
591        }
592        outFileName.append(sourceFileName);
593        return outFileName.toString();
594    }
595
596    /**
597     * @return the superclass for this JavaClass object, or null if this is java.lang.Object
598     * @throws ClassNotFoundException if the superclass can't be found
599     */
600    public JavaClass getSuperClass() throws ClassNotFoundException {
601        if ("java.lang.Object".equals(getClassName())) {
602            return null;
603        }
604        return repository.loadClass(getSuperclassName());
605    }
606
607    /**
608     * @return list of super classes of this class in ascending order, i.e., java.lang.Object is always the last element
609     * @throws ClassNotFoundException if any of the superclasses can't be found
610     */
611    public JavaClass[] getSuperClasses() throws ClassNotFoundException {
612        JavaClass clazz = this;
613        final List<JavaClass> allSuperClasses = new ArrayList<>();
614        for (clazz = clazz.getSuperClass(); clazz != null; clazz = clazz.getSuperClass()) {
615            allSuperClasses.add(clazz);
616        }
617        return allSuperClasses.toArray(JavaClass.EMPTY_ARRAY);
618    }
619
620    /**
621     * returns the super class name of this class. In the case that this class is java.lang.Object, it will return itself
622     * (java.lang.Object). This is probably incorrect but isn't fixed at this time to not break existing clients.
623     *
624     * @return Superclass name.
625     */
626    public String getSuperclassName() {
627        return superclassName;
628    }
629
630    /**
631     * @return Class name index.
632     */
633    public int getSuperclassNameIndex() {
634        return superclassNameIndex;
635    }
636
637    /**
638     * Return value as defined by given BCELComparator strategy. By default return the hashcode of the class name.
639     *
640     * @see Object#hashCode()
641     */
642    @Override
643    public int hashCode() {
644        return bcelComparator.hashCode(this);
645    }
646
647    /**
648     * @return true, if this class is an implementation of interface inter
649     * @throws ClassNotFoundException if superclasses or superinterfaces of this class can't be found
650     */
651    public boolean implementationOf(final JavaClass inter) throws ClassNotFoundException {
652        if (!inter.isInterface()) {
653            throw new IllegalArgumentException(inter.getClassName() + " is no interface");
654        }
655        if (this.equals(inter)) {
656            return true;
657        }
658        final JavaClass[] superInterfaces = getAllInterfaces();
659        for (final JavaClass superInterface : superInterfaces) {
660            if (superInterface.equals(inter)) {
661                return true;
662            }
663        }
664        return false;
665    }
666
667    /**
668     * Equivalent to runtime "instanceof" operator.
669     *
670     * @return true if this JavaClass is derived from the super class
671     * @throws ClassNotFoundException if superclasses or superinterfaces of this object can't be found
672     */
673    public final boolean instanceOf(final JavaClass superclass) throws ClassNotFoundException {
674        if (this.equals(superclass)) {
675            return true;
676        }
677        for (final JavaClass clazz : getSuperClasses()) {
678            if (clazz.equals(superclass)) {
679                return true;
680            }
681        }
682        if (superclass.isInterface()) {
683            return implementationOf(superclass);
684        }
685        return false;
686    }
687
688    /**
689     * @since 6.0
690     */
691    public final boolean isAnonymous() {
692        computeNestedTypeStatus();
693        return this.isAnonymous;
694    }
695
696    public final boolean isClass() {
697        return (super.getAccessFlags() & Const.ACC_INTERFACE) == 0;
698    }
699
700    /**
701     * @since 6.0
702     */
703    public final boolean isNested() {
704        computeNestedTypeStatus();
705        return this.isNested;
706    }
707
708    public final boolean isSuper() {
709        return (super.getAccessFlags() & Const.ACC_SUPER) != 0;
710    }
711
712    /**
713     * @param attributes .
714     */
715    public void setAttributes(final Attribute[] attributes) {
716        this.attributes = attributes;
717    }
718
719    /**
720     * @param className .
721     */
722    public void setClassName(final String className) {
723        this.className = className;
724    }
725
726    /**
727     * @param classNameIndex .
728     */
729    public void setClassNameIndex(final int classNameIndex) {
730        this.classNameIndex = classNameIndex;
731    }
732
733    /**
734     * @param constantPool .
735     */
736    public void setConstantPool(final ConstantPool constantPool) {
737        this.constantPool = constantPool;
738    }
739
740    /**
741     * @param fields .
742     */
743    public void setFields(final Field[] fields) {
744        this.fields = fields;
745    }
746
747    /**
748     * Set File name of class, aka SourceFile attribute value
749     */
750    public void setFileName(final String fileName) {
751        this.fileName = fileName;
752    }
753
754    /**
755     * @param interfaceNames .
756     */
757    public void setInterfaceNames(final String[] interfaceNames) {
758        this.interfaceNames = interfaceNames;
759    }
760
761    /**
762     * @param interfaces .
763     */
764    public void setInterfaces(final int[] interfaces) {
765        this.interfaces = interfaces;
766    }
767
768    /**
769     * @param major .
770     */
771    public void setMajor(final int major) {
772        this.major = major;
773    }
774
775    /**
776     * @param methods .
777     */
778    public void setMethods(final Method[] methods) {
779        this.methods = methods;
780    }
781
782    /**
783     * @param minor .
784     */
785    public void setMinor(final int minor) {
786        this.minor = minor;
787    }
788
789    /**
790     * Sets the ClassRepository which loaded the JavaClass. Should be called immediately after parsing is done.
791     */
792    public void setRepository(final org.apache.bcel.util.Repository repository) { // TODO make protected?
793        this.repository = repository;
794    }
795
796    /**
797     * Set absolute path to file this class was read from.
798     */
799    public void setSourceFileName(final String sourceFileName) {
800        this.sourceFileName = sourceFileName;
801    }
802
803    /**
804     * @param superclassName .
805     */
806    public void setSuperclassName(final String superclassName) {
807        this.superclassName = superclassName;
808    }
809
810    /**
811     * @param superclassNameIndex .
812     */
813    public void setSuperclassNameIndex(final int superclassNameIndex) {
814        this.superclassNameIndex = superclassNameIndex;
815    }
816
817    /**
818     * @return String representing class contents.
819     */
820    @Override
821    public String toString() {
822        String access = Utility.accessToString(super.getAccessFlags(), true);
823        access = access.isEmpty() ? "" : access + " ";
824        final StringBuilder buf = new StringBuilder(128);
825        buf.append(access).append(Utility.classOrInterface(super.getAccessFlags())).append(" ").append(className).append(" extends ")
826            .append(Utility.compactClassName(superclassName, false)).append('\n');
827        final int size = interfaces.length;
828        if (size > 0) {
829            buf.append("implements\t\t");
830            for (int i = 0; i < size; i++) {
831                buf.append(interfaceNames[i]);
832                if (i < size - 1) {
833                    buf.append(", ");
834                }
835            }
836            buf.append('\n');
837        }
838        buf.append("file name\t\t").append(fileName).append('\n');
839        buf.append("compiled from\t\t").append(sourceFileName).append('\n');
840        buf.append("compiler version\t").append(major).append(".").append(minor).append('\n');
841        buf.append("access flags\t\t").append(super.getAccessFlags()).append('\n');
842        buf.append("constant pool\t\t").append(constantPool.getLength()).append(" entries\n");
843        buf.append("ACC_SUPER flag\t\t").append(isSuper()).append("\n");
844        if (attributes.length > 0) {
845            buf.append("\nAttribute(s):\n");
846            for (final Attribute attribute : attributes) {
847                buf.append(indent(attribute));
848            }
849        }
850        final AnnotationEntry[] annotations = getAnnotationEntries();
851        if (annotations != null && annotations.length > 0) {
852            buf.append("\nAnnotation(s):\n");
853            for (final AnnotationEntry annotation : annotations) {
854                buf.append(indent(annotation));
855            }
856        }
857        if (fields.length > 0) {
858            buf.append("\n").append(fields.length).append(" fields:\n");
859            for (final Field field : fields) {
860                buf.append("\t").append(field).append('\n');
861            }
862        }
863        if (methods.length > 0) {
864            buf.append("\n").append(methods.length).append(" methods:\n");
865            for (final Method method : methods) {
866                buf.append("\t").append(method).append('\n');
867            }
868        }
869        return buf.toString();
870    }
871}