001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   https://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.bcel.classfile;
020
021import java.io.ByteArrayOutputStream;
022import java.io.DataOutputStream;
023import java.io.File;
024import java.io.FileOutputStream;
025import java.io.IOException;
026import java.io.OutputStream;
027import java.util.ArrayList;
028import java.util.Arrays;
029import java.util.List;
030import java.util.Objects;
031import java.util.Set;
032import java.util.StringTokenizer;
033import java.util.TreeSet;
034
035import org.apache.bcel.Const;
036import org.apache.bcel.generic.Type;
037import org.apache.bcel.util.BCELComparator;
038import org.apache.bcel.util.ClassQueue;
039import org.apache.bcel.util.SyntheticRepository;
040import org.apache.commons.lang3.ArrayUtils;
041
042/**
043 * Represents a Java class, i.e., the data structures, constant pool, fields, methods and commands contained in a Java
044 * .class file. See <a href="https://docs.oracle.com/javase/specs/">JVM specification</a> for details. The intent of
045 * this class is to represent a parsed or otherwise existing class file. Those interested in programmatically generating
046 * classes should see the <a href="../generic/ClassGen.html">ClassGen</a> class.
047 *
048 * @see org.apache.bcel.generic.ClassGen
049 */
050public class JavaClass extends AccessFlags implements Cloneable, Node, Comparable<JavaClass> {
051
052    /**
053     * The standard class file extension.
054     *
055     * @since 6.7.0
056     */
057    public static final String EXTENSION = ".class";
058
059    /**
060     * Empty array.
061     *
062     * @since 6.6.0
063     */
064    public static final JavaClass[] EMPTY_ARRAY = {};
065
066    public static final byte HEAP = 1;
067    public static final byte FILE = 2;
068    public static final byte ZIP = 3;
069    private static final boolean debug = Boolean.getBoolean("JavaClass.debug"); // Debugging on/off
070
071    private static BCELComparator<JavaClass> bcelComparator = new BCELComparator<JavaClass>() {
072
073        @Override
074        public boolean equals(final JavaClass a, final JavaClass b) {
075            return a == b || a != null && b != null && Objects.equals(a.getClassName(), b.getClassName());
076        }
077
078        @Override
079        public int hashCode(final JavaClass o) {
080            return o != null ? Objects.hashCode(o.getClassName()) : 0;
081        }
082    };
083
084    /*
085     * Print debug information depending on 'JavaClass.debug'
086     */
087    static void Debug(final String str) {
088        if (debug) {
089            System.out.println(str);
090        }
091    }
092
093    /**
094     * @return Comparison strategy object.
095     */
096    public static BCELComparator<JavaClass> getComparator() {
097        return bcelComparator;
098    }
099
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}