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