/* * Copyright (c) 2017, 2020, Oracle and/or its affiliates. All rights reserved. */ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.sun.org.apache.bcel.internal.classfile; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.Objects; import java.util.StringTokenizer; import java.util.List; import java.util.Set; import java.util.TreeSet; import com.sun.org.apache.bcel.internal.Const; import com.sun.org.apache.bcel.internal.generic.Type; import com.sun.org.apache.bcel.internal.util.BCELComparator; import com.sun.org.apache.bcel.internal.util.ClassQueue; import com.sun.org.apache.bcel.internal.util.SyntheticRepository; /** * Represents a Java class, i.e., the data structures, constant pool, * fields, methods and commands contained in a Java .class file. * See JVM specification for details. * The intent of this class is to represent a parsed or otherwise existing * class file. Those interested in programatically generating classes * should see the ClassGen class. * @see com.sun.org.apache.bcel.internal.generic.ClassGen * @LastModified: Jan 2020 */ public class JavaClass extends AccessFlags implements Cloneable, Node, Comparable { private String file_name; private String package_name; private String source_file_name = ""; private int class_name_index; private int superclass_name_index; private String class_name; private String superclass_name; private int major; private int minor; // Compiler version private ConstantPool constant_pool; // Constant pool private int[] interfaces; // implemented interfaces private String[] interface_names; private Field[] fields; // Fields, i.e., variables of class private Method[] methods; // methods defined in the class private Attribute[] attributes; // attributes defined in the class private AnnotationEntry[] annotations; // annotations defined on the class private byte source = HEAP; // Generated in memory private boolean isAnonymous = false; private boolean isNested = false; private boolean computedNestedTypeStatus = false; public static final byte HEAP = 1; public static final byte FILE = 2; public static final byte ZIP = 3; private static final boolean debug = false; private static BCELComparator bcelComparator = new BCELComparator() { @Override public boolean equals( final Object o1, final Object o2 ) { final JavaClass THIS = (JavaClass) o1; final JavaClass THAT = (JavaClass) o2; return Objects.equals(THIS.getClassName(), THAT.getClassName()); } @Override public int hashCode( final Object o ) { final JavaClass THIS = (JavaClass) o; return THIS.getClassName().hashCode(); } }; /** * In cases where we go ahead and create something, * use the default SyntheticRepository, because we * don't know any better. */ private transient com.sun.org.apache.bcel.internal.util.Repository repository = SyntheticRepository.getInstance(); /** * Constructor gets all contents as arguments. * * @param class_name_index Index into constant pool referencing a * ConstantClass that represents this class. * @param superclass_name_index Index into constant pool referencing a * ConstantClass that represents this class's superclass. * @param file_name File name * @param major Major compiler version * @param minor Minor compiler version * @param access_flags Access rights defined by bit flags * @param constant_pool Array of constants * @param interfaces Implemented interfaces * @param fields Class fields * @param methods Class methods * @param attributes Class attributes * @param source Read from file or generated in memory? */ public JavaClass(final int class_name_index, final int superclass_name_index, final String file_name, final int major, final int minor, final int access_flags, final ConstantPool constant_pool, int[] interfaces, Field[] fields, Method[] methods, Attribute[] attributes, final byte source) { super(access_flags); if (interfaces == null) { interfaces = new int[0]; } if (attributes == null) { attributes = new Attribute[0]; } if (fields == null) { fields = new Field[0]; } if (methods == null) { methods = new Method[0]; } this.class_name_index = class_name_index; this.superclass_name_index = superclass_name_index; this.file_name = file_name; this.major = major; this.minor = minor; this.constant_pool = constant_pool; this.interfaces = interfaces; this.fields = fields; this.methods = methods; this.attributes = attributes; this.source = source; // Get source file name if available for (final Attribute attribute : attributes) { if (attribute instanceof SourceFile) { source_file_name = ((SourceFile) attribute).getSourceFileName(); break; } } /* According to the specification the following entries must be of type * `ConstantClass' but we check that anyway via the * `ConstPool.getConstant' method. */ class_name = constant_pool.getConstantString(class_name_index, Const.CONSTANT_Class); class_name = Utility.compactClassName(class_name, false); final int index = class_name.lastIndexOf('.'); if (index < 0) { package_name = ""; } else { package_name = class_name.substring(0, index); } if (superclass_name_index > 0) { // May be zero -> class is java.lang.Object superclass_name = constant_pool.getConstantString(superclass_name_index, Const.CONSTANT_Class); superclass_name = Utility.compactClassName(superclass_name, false); } else { superclass_name = "java.lang.Object"; } interface_names = new String[interfaces.length]; for (int i = 0; i < interfaces.length; i++) { final String str = constant_pool.getConstantString(interfaces[i], Const.CONSTANT_Class); interface_names[i] = Utility.compactClassName(str, false); } } /** * Constructor gets all contents as arguments. * * @param class_name_index Class name * @param superclass_name_index Superclass name * @param file_name File name * @param major Major compiler version * @param minor Minor compiler version * @param access_flags Access rights defined by bit flags * @param constant_pool Array of constants * @param interfaces Implemented interfaces * @param fields Class fields * @param methods Class methods * @param attributes Class attributes */ public JavaClass(final int class_name_index, final int superclass_name_index, final String file_name, final int major, final int minor, final int access_flags, final ConstantPool constant_pool, final int[] interfaces, final Field[] fields, final Method[] methods, final Attribute[] attributes) { this(class_name_index, superclass_name_index, file_name, major, minor, access_flags, constant_pool, interfaces, fields, methods, attributes, HEAP); } /** * Called by objects that are traversing the nodes of the tree implicitely * defined by the contents of a Java class. I.e., the hierarchy of methods, * fields, attributes, etc. spawns a tree of objects. * * @param v Visitor object */ @Override public void accept( final Visitor v ) { v.visitJavaClass(this); } /* Print debug information depending on `JavaClass.debug' */ static void Debug( final String str ) { if (debug) { System.out.println(str); } } /** * Dump class to a file. * * @param file Output file * @throws IOException */ public void dump(final File file) throws IOException { final String parent = file.getParent(); if (parent != null) { final File dir = new File(parent); if (!dir.mkdirs()) { // either was not created or already existed if (!dir.isDirectory()) { throw new IOException("Could not create the directory " + dir); } } } try (DataOutputStream dos = new DataOutputStream(new FileOutputStream(file))) { dump(dos); } } /** * Dump class to a file named file_name. * * @param _file_name Output file name * @throws IOException */ public void dump( final String _file_name ) throws IOException { dump(new File(_file_name)); } /** * @return class in binary format */ public byte[] getBytes() { final ByteArrayOutputStream s = new ByteArrayOutputStream(); final DataOutputStream ds = new DataOutputStream(s); try { dump(ds); } catch (final IOException e) { System.err.println("Error dumping class: " + e.getMessage()); } finally { try { ds.close(); } catch (final IOException e2) { System.err.println("Error dumping class: " + e2.getMessage()); } } return s.toByteArray(); } /** * Dump Java class to output stream in binary format. * * @param file Output stream * @throws IOException */ public void dump( final OutputStream file ) throws IOException { dump(new DataOutputStream(file)); } /** * Dump Java class to output stream in binary format. * * @param file Output stream * @throws IOException */ public void dump( final DataOutputStream file ) throws IOException { file.writeInt(Const.JVM_CLASSFILE_MAGIC); file.writeShort(minor); file.writeShort(major); constant_pool.dump(file); file.writeShort(super.getAccessFlags()); file.writeShort(class_name_index); file.writeShort(superclass_name_index); file.writeShort(interfaces.length); for (final int interface1 : interfaces) { file.writeShort(interface1); } file.writeShort(fields.length); for (final Field field : fields) { field.dump(file); } file.writeShort(methods.length); for (final Method method : methods) { method.dump(file); } if (attributes != null) { file.writeShort(attributes.length); for (final Attribute attribute : attributes) { attribute.dump(file); } } else { file.writeShort(0); } file.flush(); } /** * @return Attributes of the class. */ public Attribute[] getAttributes() { return attributes; } /** * @return Annotations on the class * @since 6.0 */ public AnnotationEntry[] getAnnotationEntries() { if (annotations == null) { annotations = AnnotationEntry.createAnnotationEntries(getAttributes()); } return annotations; } /** * @return Class name. */ public String getClassName() { return class_name; } /** * @return Package name. */ public String getPackageName() { return package_name; } /** * @return Class name index. */ public int getClassNameIndex() { return class_name_index; } /** * @return Constant pool. */ public ConstantPool getConstantPool() { return constant_pool; } /** * @return Fields, i.e., variables of the class. Like the JVM spec * mandates for the classfile format, these fields are those specific to * this class, and not those of the superclass or superinterfaces. */ public Field[] getFields() { return fields; } /** * @return File name of class, aka SourceFile attribute value */ public String getFileName() { return file_name; } /** * @return Names of implemented interfaces. */ public String[] getInterfaceNames() { return interface_names; } /** * @return Indices in constant pool of implemented interfaces. */ public int[] getInterfaceIndices() { return interfaces; } /** * @return Major number of class file version. */ public int getMajor() { return major; } /** * @return Methods of the class. */ public Method[] getMethods() { return methods; } /** * @return A {@link Method} corresponding to * java.lang.reflect.Method if any */ public Method getMethod( final java.lang.reflect.Method m ) { for (final Method method : methods) { if (m.getName().equals(method.getName()) && (m.getModifiers() == method.getModifiers()) && Type.getSignature(m).equals(method.getSignature())) { return method; } } return null; } /** * @return Minor number of class file version. */ public int getMinor() { return minor; } /** * @return sbsolute path to file where this class was read from */ public String getSourceFileName() { return source_file_name; } /** * returns the super class name of this class. In the case that this class is * java.lang.Object, it will return itself (java.lang.Object). This is probably incorrect * but isn't fixed at this time to not break existing clients. * * @return Superclass name. */ public String getSuperclassName() { return superclass_name; } /** * @return Class name index. */ public int getSuperclassNameIndex() { return superclass_name_index; } /** * @param attributes . */ public void setAttributes( final Attribute[] attributes ) { this.attributes = attributes; } /** * @param class_name . */ public void setClassName( final String class_name ) { this.class_name = class_name; } /** * @param class_name_index . */ public void setClassNameIndex( final int class_name_index ) { this.class_name_index = class_name_index; } /** * @param constant_pool . */ public void setConstantPool( final ConstantPool constant_pool ) { this.constant_pool = constant_pool; } /** * @param fields . */ public void setFields( final Field[] fields ) { this.fields = fields; } /** * Set File name of class, aka SourceFile attribute value */ public void setFileName( final String file_name ) { this.file_name = file_name; } /** * @param interface_names . */ public void setInterfaceNames( final String[] interface_names ) { this.interface_names = interface_names; } /** * @param interfaces . */ public void setInterfaces( final int[] interfaces ) { this.interfaces = interfaces; } /** * @param major . */ public void setMajor( final int major ) { this.major = major; } /** * @param methods . */ public void setMethods( final Method[] methods ) { this.methods = methods; } /** * @param minor . */ public void setMinor( final int minor ) { this.minor = minor; } /** * Set absolute path to file this class was read from. */ public void setSourceFileName( final String source_file_name ) { this.source_file_name = source_file_name; } /** * @param superclass_name . */ public void setSuperclassName( final String superclass_name ) { this.superclass_name = superclass_name; } /** * @param superclass_name_index . */ public void setSuperclassNameIndex( final int superclass_name_index ) { this.superclass_name_index = superclass_name_index; } /** * @return String representing class contents. */ @Override public String toString() { String access = Utility.accessToString(super.getAccessFlags(), true); access = access.isEmpty() ? "" : (access + " "); final StringBuilder buf = new StringBuilder(128); buf.append(access).append(Utility.classOrInterface(super.getAccessFlags())).append(" ").append( class_name).append(" extends ").append( Utility.compactClassName(superclass_name, false)).append('\n'); final int size = interfaces.length; if (size > 0) { buf.append("implements\t\t"); for (int i = 0; i < size; i++) { buf.append(interface_names[i]); if (i < size - 1) { buf.append(", "); } } buf.append('\n'); } buf.append("file name\t\t").append(file_name).append('\n'); buf.append("compiled from\t\t").append(source_file_name).append('\n'); buf.append("compiler version\t").append(major).append(".").append(minor).append('\n'); buf.append("access flags\t\t").append(super.getAccessFlags()).append('\n'); buf.append("constant pool\t\t").append(constant_pool.getLength()).append(" entries\n"); buf.append("ACC_SUPER flag\t\t").append(isSuper()).append("\n"); if (attributes.length > 0) { buf.append("\nAttribute(s):\n"); for (final Attribute attribute : attributes) { buf.append(indent(attribute)); } } final AnnotationEntry[] annotations = getAnnotationEntries(); if (annotations!=null && annotations.length>0) { buf.append("\nAnnotation(s):\n"); for (final AnnotationEntry annotation : annotations) { buf.append(indent(annotation)); } } if (fields.length > 0) { buf.append("\n").append(fields.length).append(" fields:\n"); for (final Field field : fields) { buf.append("\t").append(field).append('\n'); } } if (methods.length > 0) { buf.append("\n").append(methods.length).append(" methods:\n"); for (final Method method : methods) { buf.append("\t").append(method).append('\n'); } } return buf.toString(); } private static String indent( final Object obj ) { final StringTokenizer tok = new StringTokenizer(obj.toString(), "\n"); final StringBuilder buf = new StringBuilder(); while (tok.hasMoreTokens()) { buf.append("\t").append(tok.nextToken()).append("\n"); } return buf.toString(); } /** * @return deep copy of this class */ public JavaClass copy() { JavaClass c = null; try { c = (JavaClass) clone(); c.constant_pool = constant_pool.copy(); c.interfaces = interfaces.clone(); c.interface_names = interface_names.clone(); c.fields = new Field[fields.length]; for (int i = 0; i < fields.length; i++) { c.fields[i] = fields[i].copy(c.constant_pool); } c.methods = new Method[methods.length]; for (int i = 0; i < methods.length; i++) { c.methods[i] = methods[i].copy(c.constant_pool); } c.attributes = new Attribute[attributes.length]; for (int i = 0; i < attributes.length; i++) { c.attributes[i] = attributes[i].copy(c.constant_pool); } } catch (final CloneNotSupportedException e) { // TODO should this throw? } return c; } public final boolean isSuper() { return (super.getAccessFlags() & Const.ACC_SUPER) != 0; } public final boolean isClass() { return (super.getAccessFlags() & Const.ACC_INTERFACE) == 0; } /** * @since 6.0 */ public final boolean isAnonymous() { computeNestedTypeStatus(); return this.isAnonymous; } /** * @since 6.0 */ public final boolean isNested() { computeNestedTypeStatus(); return this.isNested; } private void computeNestedTypeStatus() { if (computedNestedTypeStatus) { return; } for (final Attribute attribute : this.attributes) { if (attribute instanceof InnerClasses) { final InnerClass[] innerClasses = ((InnerClasses) attribute).getInnerClasses(); for (final InnerClass innerClasse : innerClasses) { boolean innerClassAttributeRefersToMe = false; String inner_class_name = constant_pool.getConstantString(innerClasse.getInnerClassIndex(), Const.CONSTANT_Class); inner_class_name = Utility.compactClassName(inner_class_name, false); if (inner_class_name.equals(getClassName())) { innerClassAttributeRefersToMe = true; } if (innerClassAttributeRefersToMe) { this.isNested = true; if (innerClasse.getInnerNameIndex() == 0) { this.isAnonymous = true; } } } } } this.computedNestedTypeStatus = true; } /** @return returns either HEAP (generated), FILE, or ZIP */ public final byte getSource() { return source; } /********************* New repository functionality *********************/ /** * Gets the ClassRepository which holds its definition. By default * this is the same as SyntheticRepository.getInstance(); */ public com.sun.org.apache.bcel.internal.util.Repository getRepository() { return repository; } /** * Sets the ClassRepository which loaded the JavaClass. * Should be called immediately after parsing is done. */ public void setRepository( final com.sun.org.apache.bcel.internal.util.Repository repository ) { // TODO make protected? this.repository = repository; } /** Equivalent to runtime "instanceof" operator. * * @return true if this JavaClass is derived from the super class * @throws ClassNotFoundException if superclasses or superinterfaces * of this object can't be found */ public final boolean instanceOf( final JavaClass super_class ) throws ClassNotFoundException { if (this.equals(super_class)) { return true; } final JavaClass[] super_classes = getSuperClasses(); for (final JavaClass super_classe : super_classes) { if (super_classe.equals(super_class)) { return true; } } if (super_class.isInterface()) { return implementationOf(super_class); } return false; } /** * @return true, if this class is an implementation of interface inter * @throws ClassNotFoundException if superclasses or superinterfaces * of this class can't be found */ public boolean implementationOf( final JavaClass inter ) throws ClassNotFoundException { if (!inter.isInterface()) { throw new IllegalArgumentException(inter.getClassName() + " is no interface"); } if (this.equals(inter)) { return true; } final JavaClass[] super_interfaces = getAllInterfaces(); for (final JavaClass super_interface : super_interfaces) { if (super_interface.equals(inter)) { return true; } } return false; } /** * @return the superclass for this JavaClass object, or null if this * is java.lang.Object * @throws ClassNotFoundException if the superclass can't be found */ public JavaClass getSuperClass() throws ClassNotFoundException { if ("java.lang.Object".equals(getClassName())) { return null; } return repository.loadClass(getSuperclassName()); } /** * @return list of super classes of this class in ascending order, i.e., * java.lang.Object is always the last element * @throws ClassNotFoundException if any of the superclasses can't be found */ public JavaClass[] getSuperClasses() throws ClassNotFoundException { JavaClass clazz = this; final List allSuperClasses = new ArrayList<>(); for (clazz = clazz.getSuperClass(); clazz != null; clazz = clazz.getSuperClass()) { allSuperClasses.add(clazz); } return allSuperClasses.toArray(new JavaClass[allSuperClasses.size()]); } /** * Get interfaces directly implemented by this JavaClass. */ public JavaClass[] getInterfaces() throws ClassNotFoundException { final String[] _interfaces = getInterfaceNames(); final JavaClass[] classes = new JavaClass[_interfaces.length]; for (int i = 0; i < _interfaces.length; i++) { classes[i] = repository.loadClass(_interfaces[i]); } return classes; } /** * Get all interfaces implemented by this JavaClass (transitively). */ public JavaClass[] getAllInterfaces() throws ClassNotFoundException { final ClassQueue queue = new ClassQueue(); final Set allInterfaces = new TreeSet<>(); queue.enqueue(this); while (!queue.empty()) { final JavaClass clazz = queue.dequeue(); final JavaClass souper = clazz.getSuperClass(); final JavaClass[] _interfaces = clazz.getInterfaces(); if (clazz.isInterface()) { allInterfaces.add(clazz); } else { if (souper != null) { queue.enqueue(souper); } } for (final JavaClass _interface : _interfaces) { queue.enqueue(_interface); } } return allInterfaces.toArray(new JavaClass[allInterfaces.size()]); } /** * @return Comparison strategy object */ public static BCELComparator getComparator() { return bcelComparator; } /** * @param comparator Comparison strategy object */ public static void setComparator( final BCELComparator comparator ) { bcelComparator = comparator; } /** * Return value as defined by given BCELComparator strategy. * By default two JavaClass objects are said to be equal when * their class names are equal. * * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals( final Object obj ) { return bcelComparator.equals(this, obj); } /** * Return the natural ordering of two JavaClasses. * This ordering is based on the class name * @since 6.0 */ @Override public int compareTo( final JavaClass obj ) { return getClassName().compareTo(obj.getClassName()); } /** * Return value as defined by given BCELComparator strategy. * By default return the hashcode of the class name. * * @see java.lang.Object#hashCode() */ @Override public int hashCode() { return bcelComparator.hashCode(this); } }