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