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