View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.bcel.classfile;
20  
21  import java.io.ByteArrayOutputStream;
22  import java.io.DataOutputStream;
23  import java.io.File;
24  import java.io.FileOutputStream;
25  import java.io.IOException;
26  import java.io.OutputStream;
27  import java.util.ArrayList;
28  import java.util.Arrays;
29  import java.util.HashSet;
30  import java.util.List;
31  import java.util.Objects;
32  import java.util.Set;
33  import java.util.StringTokenizer;
34  import java.util.TreeSet;
35  
36  import org.apache.bcel.Const;
37  import org.apache.bcel.generic.Type;
38  import org.apache.bcel.util.Args;
39  import org.apache.bcel.util.BCELComparator;
40  import org.apache.bcel.util.ClassQueue;
41  import org.apache.bcel.util.SyntheticRepository;
42  import org.apache.commons.lang3.ArrayUtils;
43  
44  /**
45   * Represents a Java class, that is, the data structures, constant pool, fields, methods and commands contained in a Java
46   * .class file. See <a href="https://docs.oracle.com/javase/specs/">JVM specification</a> for details. The intent of
47   * this class is to represent a parsed or otherwise existing class file. Those interested in programmatically generating
48   * classes should see the <a href="../generic/ClassGen.html">ClassGen</a> class.
49   *
50   * @see org.apache.bcel.generic.ClassGen
51   */
52  public class JavaClass extends AccessFlags implements Cloneable, Node, Comparable<JavaClass> {
53  
54      private static final String CLASS_NAME_OBJECT = "java.lang.Object";
55  
56      /**
57       * The standard class file extension.
58       *
59       * @since 6.7.0
60       */
61      public static final String EXTENSION = ".class";
62  
63      /**
64       * Empty array.
65       *
66       * @since 6.6.0
67       */
68      public static final JavaClass[] EMPTY_ARRAY = {};
69  
70      /** Source was read from heap. */
71      public static final byte HEAP = 1;
72  
73      /** Source was read from file. */
74      public static final byte FILE = 2;
75  
76      /** Source was read from ZIP. */
77      public static final byte ZIP = 3;
78  
79      private static final boolean debug = Boolean.getBoolean("JavaClass.debug"); // Debugging on/off
80  
81      private static BCELComparator<JavaClass> bcelComparator = new BCELComparator<JavaClass>() {
82  
83          @Override
84          public boolean equals(final JavaClass a, final JavaClass b) {
85              return a == b || a != null && b != null && Objects.equals(a.getClassName(), b.getClassName());
86          }
87  
88          @Override
89          public int hashCode(final JavaClass o) {
90              return o != null ? Objects.hashCode(o.getClassName()) : 0;
91          }
92      };
93  
94      /*
95       * Print debug information depending on 'JavaClass.debug'
96       */
97      static void Debug(final String str) {
98          if (debug) {
99              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      * I.e., the hierarchy of methods, fields, attributes, etc. spawns 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      * @param file Output stream.
342      * @throws IOException Thrown if an I/O error occurs.
343      */
344     public void dump(final DataOutputStream file) throws IOException {
345         file.writeInt(Const.JVM_CLASSFILE_MAGIC);
346         file.writeShort(minor);
347         file.writeShort(major);
348         constantPool.dump(file);
349         file.writeShort(super.getAccessFlags());
350         file.writeShort(classNameIndex);
351         file.writeShort(superclassNameIndex);
352         file.writeShort(Args.requireU2(interfaces.length, "interfaces.length"));
353         for (final int interface1 : interfaces) {
354             file.writeShort(interface1);
355         }
356         file.writeShort(Args.requireU2(fields.length, "fields.length"));
357         for (final Field field : fields) {
358             field.dump(file);
359         }
360         file.writeShort(Args.requireU2(methods.length, "methods.length"));
361         for (final Method method : methods) {
362             method.dump(file);
363         }
364         if (attributes != null) {
365             file.writeShort(Args.requireU2(attributes.length, "attributes.length"));
366             for (final Attribute attribute : attributes) {
367                 attribute.dump(file);
368             }
369         } else {
370             file.writeShort(0);
371         }
372         file.flush();
373     }
374 
375     /**
376      * Dumps class to a file.
377      *
378      * @param file Output file.
379      * @throws IOException Thrown if an I/O error occurs.
380      */
381     public void dump(final File file) throws IOException {
382         final String parent = file.getParent();
383         if (parent != null) {
384             final File dir = new File(parent);
385             if (!dir.mkdirs() && !dir.isDirectory()) {
386                 throw new IOException("Could not create the directory " + dir);
387             }
388         }
389         try (DataOutputStream dos = new DataOutputStream(new FileOutputStream(file))) {
390             dump(dos);
391         }
392     }
393 
394     /**
395      * Dumps Java class to output stream in binary format.
396      *
397      * @param file Output stream.
398      * @throws IOException Thrown if an I/O error occurs.
399      */
400     public void dump(final OutputStream file) throws IOException {
401         dump(new DataOutputStream(file));
402     }
403 
404     /**
405      * Dumps class to a file named fileName.
406      *
407      * @param fileName Output file name.
408      * @throws IOException Thrown if an I/O error occurs.
409      */
410     public void dump(final String fileName) throws IOException {
411         dump(new File(fileName));
412     }
413 
414     /**
415      * Return value as defined by given BCELComparator strategy. By default two JavaClass objects are said to be equal when
416      * their class names are equal.
417      *
418      * @see Object#equals(Object)
419      */
420     @Override
421     public boolean equals(final Object obj) {
422         return obj instanceof JavaClass && bcelComparator.equals(this, (JavaClass) obj);
423     }
424 
425     /**
426      * Finds a visible field by name and type in this class and its super classes.
427      *
428      * @param fieldName The field name to find.
429      * @param fieldType The field type to find.
430      * @return field matching given name and type, null if field is not found or not accessible from this class.
431      * @throws ClassNotFoundException Thrown if the class cannot be found.
432      * @since 6.8.0
433      */
434     public Field findField(final String fieldName, final Type fieldType) throws ClassNotFoundException {
435         return findFieldVisit(fieldName, fieldType, new HashSet<>());
436     }
437 
438     private Field findFieldVisit(final String fieldName, final Type fieldType, final Set<JavaClass> visiting) throws ClassNotFoundException {
439         if (!visiting.add(this)) {
440             throw new ClassFormatException(getClassName());
441         }
442         try {
443             for (final Field field : fields) {
444                 if (field.getName().equals(fieldName)) {
445                     final Type fType = Type.getType(field.getSignature());
446                     // TODO: Check if assignment compatibility is sufficient. What does Sun do?
447                     if (fType.equals(fieldType)) {
448                         return field;
449                     }
450                 }
451             }
452             final JavaClass superclass = getSuperClass();
453             if (superclass != null && !CLASS_NAME_OBJECT.equals(superclass.getClassName())) {
454                 final Field f = superclass.findFieldVisit(fieldName, fieldType, visiting);
455                 if (f != null && (f.isPublic() || f.isProtected() || !f.isPrivate() && packageName.equals(superclass.getPackageName()))) {
456                     return f;
457                 }
458             }
459             final JavaClass[] implementedInterfaces = getInterfaces();
460             if (implementedInterfaces != null) {
461                 for (final JavaClass implementedInterface : implementedInterfaces) {
462                     final Field f = implementedInterface.findFieldVisit(fieldName, fieldType, visiting);
463                     if (f != null) {
464                         return f;
465                     }
466                 }
467             }
468             return null;
469         } finally {
470             visiting.remove(this);
471         }
472     }
473 
474     /**
475      * Gets all interfaces implemented by this JavaClass (transitively).
476      *
477      * @return all interfaces.
478      * @throws ClassNotFoundException Thrown if any of the class's superclasses or interfaces can't be found.
479      */
480     public JavaClass[] getAllInterfaces() throws ClassNotFoundException {
481         final ClassQueue queue = new ClassQueue();
482         final Set<JavaClass> allInterfaces = new TreeSet<>();
483         final Set<JavaClass> visited = new HashSet<>();
484         queue.enqueue(this);
485         while (!queue.empty()) {
486             final JavaClass clazz = queue.dequeue();
487             if (!visited.add(clazz)) {
488                 continue;
489             }
490             final JavaClass souper = clazz.getSuperClass();
491             final JavaClass[] interfaces = clazz.getInterfaces();
492             if (clazz.isInterface()) {
493                 allInterfaces.add(clazz);
494             } else if (souper != null) {
495                 queue.enqueue(souper);
496             }
497             for (final JavaClass iface : interfaces) {
498                 queue.enqueue(iface);
499             }
500         }
501         return allInterfaces.toArray(EMPTY_ARRAY);
502     }
503 
504     /**
505      * Gets annotations on the class.
506      *
507      * @return Annotations on the class.
508      * @since 6.0
509      */
510     public AnnotationEntry[] getAnnotationEntries() {
511         if (annotations == null) {
512             annotations = AnnotationEntry.createAnnotationEntries(getAttributes());
513         }
514 
515         return annotations;
516     }
517 
518     /**
519      * Gets attribute for given tag.
520      *
521      * @param <T> The attribute type.
522      * @param tag The attribute tag.
523      * @return Attribute for given tag, null if not found.
524      * Refer to {@link org.apache.bcel.Const#ATTR_UNKNOWN} constants named ATTR_* for possible values.
525      * @since 6.10.0
526      */
527     @SuppressWarnings("unchecked")
528     public final <T extends Attribute> T getAttribute(final byte tag) {
529         for (final Attribute attribute : getAttributes()) {
530             if (attribute.getTag() == tag) {
531                 return (T) attribute;
532             }
533         }
534         return null;
535     }
536 
537     /**
538      * Gets attributes of the class.
539      *
540      * @return Attributes of the class.
541      */
542     public Attribute[] getAttributes() {
543         return attributes;
544     }
545 
546     /**
547      * Gets class in binary format.
548      *
549      * @return class in binary format.
550      */
551     public byte[] getBytes() {
552         final ByteArrayOutputStream baos = new ByteArrayOutputStream();
553         try (DataOutputStream dos = new DataOutputStream(baos)) {
554             dump(dos);
555         } catch (final IOException e) {
556             e.printStackTrace();
557         }
558         return baos.toByteArray();
559     }
560 
561     /**
562      * Gets the class name.
563      *
564      * @return Class name.
565      */
566     public String getClassName() {
567         return className;
568     }
569 
570     /**
571      * Gets the class name index.
572      *
573      * @return Class name index.
574      */
575     public int getClassNameIndex() {
576         return classNameIndex;
577     }
578 
579     /**
580      * Gets the constant pool.
581      *
582      * @return Constant pool.
583      */
584     public ConstantPool getConstantPool() {
585         return constantPool;
586     }
587 
588     /**
589      * Gets the fields.
590      *
591      * @return Fields, that is, variables of the class. Like the JVM spec mandates for the classfile format, these fields are
592      *         those specific to this class, and not those of the superclass or superinterfaces.
593      */
594     public Field[] getFields() {
595         return fields;
596     }
597 
598     /**
599      * Gets the file name of class.
600      *
601      * @return File name of class, aka SourceFile attribute value.
602      */
603     public String getFileName() {
604         return fileName;
605     }
606 
607     /**
608      * Gets indices in constant pool of implemented interfaces.
609      *
610      * @return Indices in constant pool of implemented interfaces.
611      */
612     public int[] getInterfaceIndices() {
613         return interfaces;
614     }
615 
616     /**
617      * Gets names of implemented interfaces.
618      *
619      * @return Names of implemented interfaces.
620      */
621     public String[] getInterfaceNames() {
622         return interfaceNames;
623     }
624 
625     /**
626      * Gets interfaces directly implemented by this JavaClass.
627      *
628      * @return The interfaces.
629      * @throws ClassNotFoundException Thrown if any of the class's interfaces can't be found.
630      */
631     public JavaClass[] getInterfaces() throws ClassNotFoundException {
632         final String[] interfaces = getInterfaceNames();
633         final JavaClass[] classes = new JavaClass[interfaces.length];
634         for (int i = 0; i < interfaces.length; i++) {
635             classes[i] = repository.loadClass(interfaces[i]);
636         }
637         return classes;
638     }
639 
640     /**
641      * Gets the major number of class file version.
642      *
643      * @return Major number of class file version.
644      */
645     public int getMajor() {
646         return major;
647     }
648 
649     /**
650      * Gets a Method corresponding to java.lang.reflect.Method if any.
651      *
652      * @param m The method to find.
653      * @return A {@link Method} corresponding to java.lang.reflect.Method if any.
654      */
655     public Method getMethod(final java.lang.reflect.Method m) {
656         for (final Method method : methods) {
657             if (m.getName().equals(method.getName()) && m.getModifiers() == method.getModifiers() && Type.getSignature(m).equals(method.getSignature())) {
658                 return method;
659             }
660         }
661         return null;
662     }
663 
664     /**
665      * Gets the methods of the class.
666      *
667      * @return Methods of the class.
668      */
669     public Method[] getMethods() {
670         return methods;
671     }
672 
673     /**
674      * Gets the minor number of class file version.
675      *
676      * @return Minor number of class file version.
677      */
678     public int getMinor() {
679         return minor;
680     }
681 
682     /**
683      * Gets the package name.
684      *
685      * @return Package name.
686      */
687     public String getPackageName() {
688         return packageName;
689     }
690 
691     /**
692      * Gets the ClassRepository which holds its definition. By default this is the same as
693      * SyntheticRepository.getInstance().
694      *
695      * @return The repository.
696      */
697     public org.apache.bcel.util.Repository getRepository() {
698         return repository;
699     }
700 
701     /**
702      * Gets the source.
703      *
704      * @return either HEAP (generated), FILE, or ZIP.
705      */
706     public final byte getSource() {
707         return source;
708     }
709 
710     /**
711      * Gets the file name where this class was read from.
712      *
713      * @return file name where this class was read from.
714      */
715     public String getSourceFileName() {
716         return sourceFileName;
717     }
718 
719     /**
720      * Gets the source file path including the package path.
721      *
722      * @return path to original source file of parsed class, relative to original source directory.
723      * @since 6.7.0
724      */
725     public String getSourceFilePath() {
726         final StringBuilder outFileName = new StringBuilder();
727         if (!packageName.isEmpty()) {
728             outFileName.append(Utility.packageToPath(packageName));
729             outFileName.append('/');
730         }
731         outFileName.append(sourceFileName);
732         return outFileName.toString();
733     }
734 
735     /**
736      * Gets the superclass for this JavaClass object, or null if this is {@link Object}.
737      *
738      * @return The superclass for this JavaClass object, or null if this is {@link Object}.
739      * @throws ClassNotFoundException Thrown if the superclass can't be found.
740      */
741     public JavaClass getSuperClass() throws ClassNotFoundException {
742         if (CLASS_NAME_OBJECT.equals(getClassName())) {
743             return null;
744         }
745         return repository.loadClass(getSuperclassName());
746     }
747 
748     /**
749      * Gets list of super classes of this class in ascending order.
750      *
751      * @return list of super classes of this class in ascending order, that is, {@link Object} is always the last element.
752      * @throws ClassNotFoundException Thrown if any of the superclasses can't be found.
753      */
754     public JavaClass[] getSuperClasses() throws ClassNotFoundException {
755         JavaClass clazz = this;
756         final List<JavaClass> allSuperClasses = new ArrayList<>();
757         final Set<JavaClass> visited = new HashSet<>();
758         visited.add(this);
759         for (clazz = clazz.getSuperClass(); clazz != null; clazz = clazz.getSuperClass()) {
760             if (!visited.add(clazz)) {
761                 throw new ClassFormatException(clazz.getClassName());
762             }
763             allSuperClasses.add(clazz);
764         }
765         return allSuperClasses.toArray(EMPTY_ARRAY);
766     }
767 
768     /**
769      * returns the super class name of this class. In the case that this class is {@link Object}, it will return itself
770      * ({@link Object}). This is probably incorrect but isn't fixed at this time to not break existing clients.
771      *
772      * @return Superclass name.
773      */
774     public String getSuperclassName() {
775         return superclassName;
776     }
777 
778     /**
779      * Gets the class name index.
780      *
781      * @return Class name index.
782      */
783     public int getSuperclassNameIndex() {
784         return superclassNameIndex;
785     }
786 
787     /**
788      * Return value as defined by given BCELComparator strategy. By default return the hash code of the class name.
789      *
790      * @see Object#hashCode()
791      */
792     @Override
793     public int hashCode() {
794         return bcelComparator.hashCode(this);
795     }
796 
797     /**
798      * Checks if this class is an implementation of interface inter.
799      *
800      * @param inter The interface to check.
801      * @return true, if this class is an implementation of interface inter.
802      * @throws ClassNotFoundException Thrown if superclasses or superinterfaces of this class can't be found.
803      */
804     public boolean implementationOf(final JavaClass inter) throws ClassNotFoundException {
805         if (!inter.isInterface()) {
806             throw new IllegalArgumentException(inter.getClassName() + " is no interface");
807         }
808         if (equals(inter)) {
809             return true;
810         }
811         final JavaClass[] superInterfaces = getAllInterfaces();
812         for (final JavaClass superInterface : superInterfaces) {
813             if (superInterface.equals(inter)) {
814                 return true;
815             }
816         }
817         return false;
818     }
819 
820     /**
821      * Equivalent to runtime "instanceof" operator.
822      *
823      * @param superclass The superclass to check.
824      * @return true if this JavaClass is derived from the super class.
825      * @throws ClassNotFoundException Thrown if superclasses or superinterfaces of this object can't be found.
826      */
827     public final boolean instanceOf(final JavaClass superclass) throws ClassNotFoundException {
828         if (equals(superclass)) {
829             return true;
830         }
831         for (final JavaClass clazz : getSuperClasses()) {
832             if (clazz.equals(superclass)) {
833                 return true;
834             }
835         }
836         if (superclass.isInterface()) {
837             return implementationOf(superclass);
838         }
839         return false;
840     }
841 
842     /**
843      * Checks if this class is anonymous.
844      *
845      * @return true if anonymous.
846      * @since 6.0
847      */
848     public final boolean isAnonymous() {
849         computeNestedTypeStatus();
850         return this.isAnonymous;
851     }
852 
853     /**
854      * Checks if this is a class.
855      *
856      * @return true if this is a class.
857      */
858     public final boolean isClass() {
859         return (super.getAccessFlags() & Const.ACC_INTERFACE) == 0;
860     }
861 
862     /**
863      * Checks if this class is nested.
864      *
865      * @return true if nested.
866      * @since 6.0
867      */
868     public final boolean isNested() {
869         computeNestedTypeStatus();
870         return this.isNested;
871     }
872 
873     /**
874      * Tests whether this class was declared as a record
875      *
876      * @return true if a record attribute is present, false otherwise.
877      * @since 6.9.0
878      */
879     public boolean isRecord() {
880         computeIsRecord();
881         return this.isRecord;
882     }
883 
884     /**
885      * Checks if this is a super class.
886      *
887      * @return true if this is a super class.
888      */
889     public final boolean isSuper() {
890         return (super.getAccessFlags() & Const.ACC_SUPER) != 0;
891     }
892 
893     /**
894      * Sets the attributes.
895      *
896      * @param attributes The attributes.
897      */
898     public void setAttributes(final Attribute[] attributes) {
899         this.attributes = attributes != null ? attributes : Attribute.EMPTY_ARRAY;
900     }
901 
902     /**
903      * Sets the class name.
904      *
905      * @param className The class name.
906      */
907     public void setClassName(final String className) {
908         this.className = className;
909     }
910 
911     /**
912      * Sets the class name index.
913      *
914      * @param classNameIndex The class name index.
915      */
916     public void setClassNameIndex(final int classNameIndex) {
917         this.classNameIndex = classNameIndex;
918     }
919 
920     /**
921      * Sets the constant pool.
922      *
923      * @param constantPool The constant pool.
924      */
925     public void setConstantPool(final ConstantPool constantPool) {
926         this.constantPool = constantPool;
927     }
928 
929     /**
930      * Sets the fields.
931      *
932      * @param fields The fields.
933      */
934     public void setFields(final Field[] fields) {
935         this.fields = fields != null ? fields : Field.EMPTY_ARRAY;
936     }
937 
938     /**
939      * Sets File name of class, aka SourceFile attribute value.
940      *
941      * @param fileName The file name.
942      */
943     public void setFileName(final String fileName) {
944         this.fileName = fileName;
945     }
946 
947     /**
948      * Sets the interface names.
949      *
950      * @param interfaceNames The interface names.
951      */
952     public void setInterfaceNames(final String[] interfaceNames) {
953         this.interfaceNames = ArrayUtils.nullToEmpty(interfaceNames);
954     }
955 
956     /**
957      * Sets the interfaces.
958      *
959      * @param interfaces The interfaces.
960      */
961     public void setInterfaces(final int[] interfaces) {
962         this.interfaces = ArrayUtils.nullToEmpty(interfaces);
963     }
964 
965     /**
966      * Sets the major version.
967      *
968      * @param major The major version.
969      */
970     public void setMajor(final int major) {
971         this.major = major;
972     }
973 
974     /**
975      * Sets the methods.
976      *
977      * @param methods The methods.
978      */
979     public void setMethods(final Method[] methods) {
980         this.methods = methods != null ? methods : Method.EMPTY_ARRAY;
981     }
982 
983     /**
984      * Sets the minor version.
985      *
986      * @param minor The minor version.
987      */
988     public void setMinor(final int minor) {
989         this.minor = minor;
990     }
991 
992     /**
993      * Sets the ClassRepository which loaded the JavaClass. Should be called immediately after parsing is done.
994      *
995      * @param repository The repository.
996      */
997     public void setRepository(final org.apache.bcel.util.Repository repository) { // TODO make protected?
998         this.repository = repository;
999     }
1000 
1001     /**
1002      * Sets absolute path to file this class was read from.
1003      *
1004      * @param sourceFileName The source file name.
1005      */
1006     public void setSourceFileName(final String sourceFileName) {
1007         this.sourceFileName = sourceFileName;
1008     }
1009 
1010     /**
1011      * Sets the superclass name.
1012      *
1013      * @param superclassName The superclass name.
1014      */
1015     public void setSuperclassName(final String superclassName) {
1016         this.superclassName = superclassName;
1017     }
1018 
1019     /**
1020      * Sets the superclass name index.
1021      *
1022      * @param superclassNameIndex The superclass name index.
1023      */
1024     public void setSuperclassNameIndex(final int superclassNameIndex) {
1025         this.superclassNameIndex = superclassNameIndex;
1026     }
1027 
1028     /**
1029      * @return String representing class contents.
1030      */
1031     @Override
1032     public String toString() {
1033         String access = Utility.accessToString(super.getAccessFlags(), true);
1034         access = access.isEmpty() ? "" : access + " ";
1035         final StringBuilder buf = new StringBuilder(128);
1036         buf.append(access).append(Utility.classOrInterface(super.getAccessFlags())).append(" ").append(className).append(" extends ")
1037             .append(Utility.compactClassName(superclassName, false)).append('\n');
1038         final int size = interfaces.length;
1039         if (size > 0) {
1040             buf.append("implements\t\t");
1041             for (int i = 0; i < size; i++) {
1042                 buf.append(interfaceNames[i]);
1043                 if (i < size - 1) {
1044                     buf.append(", ");
1045                 }
1046             }
1047             buf.append('\n');
1048         }
1049         buf.append("file name\t\t").append(fileName).append('\n');
1050         buf.append("compiled from\t\t").append(sourceFileName).append('\n');
1051         buf.append("compiler version\t").append(major).append(".").append(minor).append('\n');
1052         buf.append("access flags\t\t").append(super.getAccessFlags()).append('\n');
1053         buf.append("constant pool\t\t").append(constantPool.getLength()).append(" entries\n");
1054         buf.append("ACC_SUPER flag\t\t").append(isSuper()).append("\n");
1055         if (attributes.length > 0) {
1056             buf.append("\nAttribute(s):\n");
1057             for (final Attribute attribute : attributes) {
1058                 buf.append(indent(attribute));
1059             }
1060         }
1061         final AnnotationEntry[] annotations = getAnnotationEntries();
1062         if (annotations != null && annotations.length > 0) {
1063             buf.append("\nAnnotation(s):\n");
1064             for (final AnnotationEntry annotation : annotations) {
1065                 buf.append(indent(annotation));
1066             }
1067         }
1068         if (fields.length > 0) {
1069             buf.append("\n").append(fields.length).append(" fields:\n");
1070             for (final Field field : fields) {
1071                 buf.append("\t").append(field).append('\n');
1072             }
1073         }
1074         if (methods.length > 0) {
1075             buf.append("\n").append(methods.length).append(" methods:\n");
1076             for (final Method method : methods) {
1077                 buf.append("\t").append(method).append('\n');
1078             }
1079         }
1080         return buf.toString();
1081     }
1082 }
1083