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