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.BCELComparator;
39  import org.apache.bcel.util.ClassQueue;
40  import org.apache.bcel.util.SyntheticRepository;
41  import org.apache.commons.lang3.ArrayUtils;
42  
43  /**
44   * Represents a Java class, that is, the data structures, constant pool, fields, methods and commands contained in a Java
45   * .class file. See <a href="https://docs.oracle.com/javase/specs/">JVM specification</a> for details. The intent of
46   * this class is to represent a parsed or otherwise existing class file. Those interested in programmatically generating
47   * classes should see the <a href="../generic/ClassGen.html">ClassGen</a> class.
48   *
49   * @see org.apache.bcel.generic.ClassGen
50   */
51  public class JavaClass extends AccessFlags implements Cloneable, Node, Comparable<JavaClass> {
52  
53      private static final String CLASS_NAME_OBJECT = "java.lang.Object";
54  
55      /**
56       * The standard class file extension.
57       *
58       * @since 6.7.0
59       */
60      public static final String EXTENSION = ".class";
61  
62      /**
63       * Empty array.
64       *
65       * @since 6.6.0
66       */
67      public static final JavaClass[] EMPTY_ARRAY = {};
68  
69      /** Source was read from heap. */
70      public static final byte HEAP = 1;
71  
72      /** Source was read from file. */
73      public static final byte FILE = 2;
74  
75      /** Source was read from ZIP. */
76      public static final byte ZIP = 3;
77  
78      private static final boolean debug = Boolean.getBoolean("JavaClass.debug"); // Debugging on/off
79  
80      private static BCELComparator<JavaClass> bcelComparator = new BCELComparator<JavaClass>() {
81  
82          @Override
83          public boolean equals(final JavaClass a, final JavaClass b) {
84              return a == b || a != null && b != null && Objects.equals(a.getClassName(), b.getClassName());
85          }
86  
87          @Override
88          public int hashCode(final JavaClass o) {
89              return o != null ? Objects.hashCode(o.getClassName()) : 0;
90          }
91      };
92  
93      /*
94       * Print debug information depending on 'JavaClass.debug'
95       */
96      static void Debug(final String str) {
97          if (debug) {
98              System.out.println(str);
99          }
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