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