blob: f74aa6eabcb2e2f6eb814167e3330bf0459cf6b0 [file] [log] [blame]
/*
* 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 <a href="http://docs.oracle.com/javase/specs/">JVM specification</a> 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 <a href="../generic/ClassGen.html">ClassGen</a> class.
* @see com.sun.org.apache.bcel.internal.generic.ClassGen
* @LastModified: Jan 2020
*/
public class JavaClass extends AccessFlags implements Cloneable, Node, Comparable<JavaClass> {
private String file_name;
private String package_name;
private String source_file_name = "<Unknown>";
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<JavaClass> 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<JavaClass> 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);
}
}