Java tutorial
/* * ClassFile.java 2:58 AM, August 5, 2007 * * Copyright 2007, FreeInternals.org. All rights reserved. * Use is subject to license terms. */ package org.freeinternals.format.classfile; import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; import org.freeinternals.format.FileFormatException; import org.freeinternals.format.classfile.attribute.AttributeInfo; import org.freeinternals.format.classfile.constpool.AbstractCPInfo; import org.freeinternals.format.classfile.constpool.ConstantClassInfo; import org.freeinternals.format.classfile.constpool.ConstantDoubleInfo; import org.freeinternals.format.classfile.constpool.ConstantFieldrefInfo; import org.freeinternals.format.classfile.constpool.ConstantFloatInfo; import org.freeinternals.format.classfile.constpool.ConstantIntegerInfo; import org.freeinternals.format.classfile.constpool.ConstantInterfaceMethodrefInfo; import org.freeinternals.format.classfile.constpool.ConstantInvokeDynamicInfo; import org.freeinternals.format.classfile.constpool.ConstantLongInfo; import org.freeinternals.format.classfile.constpool.ConstantMethodHandleInfo; import org.freeinternals.format.classfile.constpool.ConstantMethodTypeInfo; import org.freeinternals.format.classfile.constpool.ConstantMethodrefInfo; import org.freeinternals.format.classfile.constpool.ConstantNameAndTypeInfo; import org.freeinternals.format.classfile.constpool.ConstantStringInfo; import org.freeinternals.format.classfile.constpool.ConstantUtf8Info; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonPropertyOrder; /** * Represents a {@code class} file. A {@code class} file structure has the * following format: * * <pre> * ClassFile { * u4 magic; * u2 minor_version; * u2 major_version; * u2 constant_pool_count; * cp_info constant_pool[constant_pool_count-1]; * u2 access_flags; * u2 this_class; * u2 super_class; * u2 interfaces_count; * u2 interfaces[interfaces_count]; * u2 fields_count; * field_info fields[fields_count]; * u2 methods_count; * method_info methods[methods_count]; * u2 attributes_count; * attribute_info attributes[attributes_count]; * } * </pre> * * The {@code ClassFile} object is constructed from the class byte array. * * * * @author Amos Shi * @since JDK 6.0 * @see <a * href="http://www.freeinternals.org/mirror/java.sun.com/vmspec.2nded/ClassFile.doc.html#74353"> * VM Spec: The ClassFile Structure </a> */ public class ClassFile { private byte[] classByteArray; private PosDataInputStream posDataInputStream; /** * Magic number of {@code class} file. */ public static final int MAGIC = 0xCAFEBABE; private u4 magic; // Class file Version private MinorVersion minor_version; private MajorVersion major_version; // Constant pool private CPCount constant_pool_count; private AbstractCPInfo[] constant_pool; // Class Declaration private AccessFlags access_flags; private ThisClass this_class; private SuperClass super_class; private InterfaceCount interfaces_count; private Interface[] interfaces; // Field private FieldCount fields_count; private FieldInfo[] fields; // Method private MethodCount methods_count; private MethodInfo[] methods; // Attribute private AttributeCount attributes_count; private AttributeInfo[] attributes; /** * Creates a new instance of ClassFile from byte array. * * @param classByteArray * Byte array of a class file * @throws java.io.IOException * Error happened when reading the byte array * @throws org.freeinternals.classfile.core.ClassFormatException * The input parameter {@code classByteArray} is not a valid * class */ public ClassFile(final byte[] classByteArray) throws java.io.IOException, FileFormatException { this.classByteArray = classByteArray.clone(); final ClassFile.Parser parser = new Parser(); parser.parse(); this.analysisDeclarations(); } private void analysisDeclarations() throws FileFormatException { // Analysis field declarations if (this.fields_count.getValue() > 0) { String type; for (FieldInfo field : fields) { try { type = SignatureConvertor.signature2Type(this.getConstantUtf8Value(field.getDescriptorIndex())); } catch (SignatureException se) { type = "[Unexpected signature type]: " + this.getConstantUtf8Value(field.getDescriptorIndex()); // System.err.println(se.toString()); } field.setDeclaration(String.format("%s %s %s", field.getModifiers(), type, this.getConstantUtf8Value(field.getNameIndex()))); } } // Analysis method declarations if (this.methods_count.getValue() > 0) { String mtdReturnType; String mtdParameters; for (MethodInfo method : methods) { try { mtdReturnType = SignatureConvertor .parseMethodReturnType(this.getConstantUtf8Value(method.getDescriptorIndex())); } catch (SignatureException se) { mtdReturnType = String.format("[Unexpected method return type: %s]", this.getConstantUtf8Value(method.getDescriptorIndex())); // System.err.println(se.toString()); } try { mtdParameters = SignatureConvertor .parseMethodParameters(this.getConstantUtf8Value(method.getDescriptorIndex())); } catch (SignatureException se) { mtdParameters = String.format("[Unexpected method parameters: %s]", this.getConstantUtf8Value(method.getDescriptorIndex())); // System.err.println(se.toString()); } method.setDeclaration(String.format("%s %s %s %s", method.getModifiers(), mtdReturnType, this.getConstantUtf8Value(method.getNameIndex()), mtdParameters)); } } } /** * Get a UTF-8 text from the constant pool. * * @param cpIndex * Constant Pool object Index * @return The UTF-8 text */ public String getConstantUtf8Value(final int cpIndex) throws FileFormatException { String returnValue = null; if ((cpIndex == 0) || (cpIndex >= this.constant_pool_count.value.value)) { throw new FileFormatException(String.format( "Constant Pool index is out of range. CP index cannot be zero, and should be less than CP count (=%d). CP index = %d.", this.constant_pool_count.value.value, cpIndex)); } if (this.constant_pool[cpIndex].tag.value == AbstractCPInfo.CONSTANT_Utf8) { final ConstantUtf8Info utf8Info = (ConstantUtf8Info) this.constant_pool[cpIndex]; returnValue = utf8Info.getValue(); } else { throw new FileFormatException( String.format("Unexpected constant pool type: Utf8(%d) expected, but it is '%d'.", AbstractCPInfo.CONSTANT_Utf8, this.constant_pool[cpIndex].tag.value)); } return returnValue; } // ///////////////////////////////////////////////////////////////////////// // Get raw data /** * Get the byte array of current class. * * @return Byte array of the class */ @JsonIgnore public byte[] getClassByteArray() { return this.classByteArray; } /** * Get part of the class byte array. The array begins at the specified * {@code startIndex} and extends to the byte at {@code startIndex}+ * {@code length}. * * @param startIndex * The start index * @param length * The length of the array * @return Part of the class byte array */ public byte[] getClassByteArray(final int startIndex, final int length) { if ((startIndex < 0) || (length < 1)) { throw new IllegalArgumentException( "startIndex or length is not valid. startIndex = " + startIndex + ", length = " + length); } if (startIndex + length - 1 > this.classByteArray.length) { throw new ArrayIndexOutOfBoundsException("The last item index is bigger than class byte array size."); } byte[] data = new byte[length]; System.arraycopy(this.classByteArray, startIndex, data, 0, length); return data; } /** * Get the length of the class byte array. * * @return Length of class byte array */ public int getByteArraySize() { return this.classByteArray.length; } /** * Get the {@code minor_version} of the {@code ClassFile} structure. * * @return The {@code minor_version} */ public MinorVersion getMinorVersion() { return this.minor_version; } /** * Get the {@code major_version} of the {@code ClassFile} structure. * * @return The {@code major_version} */ public MajorVersion getMajorVersion() { return this.major_version; } /** * Get the {@code constant_pool_count} of the {@code ClassFile} structure. * * @return The {@code constant_pool_count} */ public CPCount getCPCount() { return this.constant_pool_count; } /** * Get the {@code constant_pool[]} of the {@code ClassFile} structure. * * @return The {@code constant_pool[]} */ public AbstractCPInfo[] getConstantPool() { return this.constant_pool; } /** * Returns a string of the constant pool item at the specified {@code index} * . * * @param index * Index in the constant pool * @return String of the constant pool item at {@code index} */ public String getCPDescription(final int index) { // Invalid index if (index >= this.constant_pool_count.getValue()) { return null; } // Special index: empty if (index == 0) { return null; } return new CPDescr().getCPDescr(index); } /** * Get the {@code access_flags} of the {@code ClassFile} structure. * * @return The {@code access_flags} */ public AccessFlags getAccessFlags() { return this.access_flags; } /** * Get the {@code this_class} of the {@code ClassFile} structure. * * @return The {@code this_class} */ public ThisClass getThisClass() { return this.this_class; } /** * Get the {@code super_class} of the {@code ClassFile} structure. * * @return The {@code super_class} */ public SuperClass getSuperClass() { return this.super_class; } /** * Get the {@code interfaces_count} of the {@code ClassFile} structure. * * @return The {@code interfaces_count} */ public InterfaceCount getInterfacesCount() { return this.interfaces_count; } /** * Get the {@code interfaces}[] of the {@code ClassFile} structure. * * @return The {@code interfaces}[] */ public Interface[] getInterfaces() { return this.interfaces; } /** * Get the {@code fields_count} of the {@code ClassFile} structure. * * @return The {@code fields_count} */ public FieldCount getFieldCount() { return this.fields_count; } /** * Get the {@code fields}[] of the {@code ClassFile} structure. * * @return The {@code fields}[] */ public FieldInfo[] getFields() { return this.fields; } /** * Get the {@code methods_count} of the {@code ClassFile} structure. * * @return The {@code methods_count} */ public MethodCount getMethodCount() { return this.methods_count; } /** * Get the {@code methods}[] of the {@code ClassFile} structure. * * @return The {@code methods}[] */ public MethodInfo[] getMethods() { return this.methods; } /** * Get the {@code attributes_count} of the {@code ClassFile} structure. * * @return The {@code attributes_count} */ public AttributeCount getAttributeCount() { return this.attributes_count; } /** * Get the {@code attributes}[] of the {@code ClassFile} structure. * * @return The {@code attributes}[] */ public AttributeInfo[] getAttributes() { return this.attributes; } // ///////////////////////////////////////////////////////////////////////// // Get extracted data // ///////////////////////////////////////////////////////////////////////// // Internal Classes private class Parser { Parser() { } public void parse() throws FileFormatException, IOException { final PosByteArrayInputStream posByteArrayInputStream = new PosByteArrayInputStream(classByteArray); ClassFile.this.posDataInputStream = new PosDataInputStream(posByteArrayInputStream); ClassFile.this.magic = new u4(ClassFile.this.posDataInputStream.readInt()); if (ClassFile.this.magic.value != ClassFile.MAGIC) { throw new FileFormatException("The magic number of the byte array is not 0xCAFEBABE"); } this.parseClassFileVersion(); this.parseConstantPool(); this.parseClassDeclaration(); this.parseFields(); this.parseMethods(); this.parseAttributes(); } private void parseClassFileVersion() throws java.io.IOException, FileFormatException { ClassFile.this.minor_version = new MinorVersion(ClassFile.this.posDataInputStream); ClassFile.this.major_version = new MajorVersion(ClassFile.this.posDataInputStream); } private void parseConstantPool() throws java.io.IOException, FileFormatException { ClassFile.this.constant_pool_count = new CPCount(ClassFile.this.posDataInputStream); final int cp_count = ClassFile.this.constant_pool_count.getValue(); ClassFile.this.constant_pool = new AbstractCPInfo[cp_count]; short tag; for (int i = 1; i < cp_count; i++) { tag = (short) ClassFile.this.posDataInputStream.readUnsignedByte(); switch (tag) { case AbstractCPInfo.CONSTANT_Utf8: ClassFile.this.constant_pool[i] = new ConstantUtf8Info(ClassFile.this.posDataInputStream); break; case AbstractCPInfo.CONSTANT_Integer: ClassFile.this.constant_pool[i] = new ConstantIntegerInfo(ClassFile.this.posDataInputStream); break; case AbstractCPInfo.CONSTANT_Float: ClassFile.this.constant_pool[i] = new ConstantFloatInfo(ClassFile.this.posDataInputStream); break; case AbstractCPInfo.CONSTANT_Long: ClassFile.this.constant_pool[i] = new ConstantLongInfo(ClassFile.this.posDataInputStream); i++; break; case AbstractCPInfo.CONSTANT_Double: ClassFile.this.constant_pool[i] = new ConstantDoubleInfo(ClassFile.this.posDataInputStream); i++; break; case AbstractCPInfo.CONSTANT_Class: ClassFile.this.constant_pool[i] = new ConstantClassInfo(ClassFile.this.posDataInputStream); break; case AbstractCPInfo.CONSTANT_String: ClassFile.this.constant_pool[i] = new ConstantStringInfo(ClassFile.this.posDataInputStream); break; case AbstractCPInfo.CONSTANT_Fieldref: ClassFile.this.constant_pool[i] = new ConstantFieldrefInfo(ClassFile.this.posDataInputStream); break; case AbstractCPInfo.CONSTANT_Methodref: ClassFile.this.constant_pool[i] = new ConstantMethodrefInfo(ClassFile.this.posDataInputStream); break; case AbstractCPInfo.CONSTANT_InterfaceMethodref: ClassFile.this.constant_pool[i] = new ConstantInterfaceMethodrefInfo( ClassFile.this.posDataInputStream); break; case AbstractCPInfo.CONSTANT_NameAndType: ClassFile.this.constant_pool[i] = new ConstantNameAndTypeInfo( ClassFile.this.posDataInputStream); break; case AbstractCPInfo.CONSTANT_MethodType: ClassFile.this.constant_pool[i] = new ConstantMethodTypeInfo(ClassFile.this.posDataInputStream); break; case AbstractCPInfo.CONSTANT_MethodHandle: ClassFile.this.constant_pool[i] = new ConstantMethodHandleInfo( ClassFile.this.posDataInputStream); break; case AbstractCPInfo.CONSTANT_InvokeDynamic: ClassFile.this.constant_pool[i] = new ConstantInvokeDynamicInfo( ClassFile.this.posDataInputStream); break; default: throw new FileFormatException(String.format( "Unreconizable constant pool type found. Constant pool tag: [%d]; class file offset: [%d].", tag, ClassFile.this.posDataInputStream.getPos() - 1)); } // -- Debug information. // if (ClassFile.this.constant_pool[i] != null) // { // System.out.print(ClassFile.this.constant_pool[i].getDescription()); // } // else // { // System.out.print(ClassFile.this.constant_pool[i-1].getDescription()); // } // System.out.println (); } } private void parseClassDeclaration() throws java.io.IOException, FileFormatException { ClassFile.this.access_flags = new AccessFlags(ClassFile.this.posDataInputStream); ClassFile.this.this_class = new ThisClass(ClassFile.this.posDataInputStream); ClassFile.this.super_class = new SuperClass(ClassFile.this.posDataInputStream); ClassFile.this.interfaces_count = new InterfaceCount(ClassFile.this.posDataInputStream); if (ClassFile.this.interfaces_count.getValue() > 0) { ClassFile.this.interfaces = new Interface[ClassFile.this.interfaces_count.getValue()]; for (int i = 0; i < ClassFile.this.interfaces_count.getValue(); i++) { ClassFile.this.interfaces[i] = new Interface(ClassFile.this.posDataInputStream); } } } private void parseFields() throws java.io.IOException, FileFormatException { ClassFile.this.fields_count = new FieldCount(ClassFile.this.posDataInputStream); final int fieldCount = ClassFile.this.fields_count.getValue(); if (fieldCount > 0) { ClassFile.this.fields = new FieldInfo[fieldCount]; for (int i = 0; i < fieldCount; i++) { ClassFile.this.fields[i] = new FieldInfo(ClassFile.this.posDataInputStream, ClassFile.this.constant_pool); } } } private void parseMethods() throws java.io.IOException, FileFormatException { ClassFile.this.methods_count = new MethodCount(ClassFile.this.posDataInputStream); final int methodCount = ClassFile.this.methods_count.getValue(); if (methodCount > 0) { ClassFile.this.methods = new MethodInfo[methodCount]; for (int i = 0; i < methodCount; i++) { ClassFile.this.methods[i] = new MethodInfo(ClassFile.this.posDataInputStream, ClassFile.this.constant_pool); } } } private void parseAttributes() throws java.io.IOException, FileFormatException { ClassFile.this.attributes_count = new AttributeCount(ClassFile.this.posDataInputStream); final int attributeCount = ClassFile.this.attributes_count.getValue(); if (attributeCount > 0) { ClassFile.this.attributes = new AttributeInfo[attributeCount]; for (int i = 0; i < attributeCount; i++) { ClassFile.this.attributes[i] = AttributeInfo.parse(ClassFile.this.posDataInputStream, ClassFile.this.constant_pool); } } } } private static enum Descr_NameAndType { RAW(1), FIELD(2), METHOD(3); private final int enum_value; Descr_NameAndType(final int value) { this.enum_value = value; } public int value() { return this.enum_value; } } private class CPDescr { CPDescr() { } public String getCPDescr(final int index) { final StringBuilder sb = new StringBuilder(40); switch (ClassFile.this.constant_pool[index].getTag()) { case AbstractCPInfo.CONSTANT_Utf8: sb.append("Utf8: "); sb.append(this.getDescr_Utf8((ConstantUtf8Info) ClassFile.this.constant_pool[index])); break; case AbstractCPInfo.CONSTANT_Integer: sb.append("Integer: "); sb.append(this.getDescr_Integer((ConstantIntegerInfo) ClassFile.this.constant_pool[index])); break; case AbstractCPInfo.CONSTANT_Float: sb.append("Float: "); sb.append(this.getDescr_Float((ConstantFloatInfo) ClassFile.this.constant_pool[index])); break; case AbstractCPInfo.CONSTANT_Long: sb.append("Long: "); sb.append(this.getDescr_Long((ConstantLongInfo) ClassFile.this.constant_pool[index])); break; case AbstractCPInfo.CONSTANT_Double: sb.append("Double: "); sb.append(this.getDescr_Double((ConstantDoubleInfo) ClassFile.this.constant_pool[index])); break; case AbstractCPInfo.CONSTANT_Class: sb.append("Class: "); sb.append(this.getDescr_Class((ConstantClassInfo) ClassFile.this.constant_pool[index])); break; case AbstractCPInfo.CONSTANT_String: sb.append("String: "); sb.append(this.getDescr_String((ConstantStringInfo) ClassFile.this.constant_pool[index])); break; case AbstractCPInfo.CONSTANT_Fieldref: sb.append("Fieldref: "); sb.append(this.getDescr_Fieldref((ConstantFieldrefInfo) ClassFile.this.constant_pool[index])); break; case AbstractCPInfo.CONSTANT_Methodref: sb.append("Methodref: "); sb.append(this.getDescr_Methodref((ConstantMethodrefInfo) ClassFile.this.constant_pool[index])); break; case AbstractCPInfo.CONSTANT_InterfaceMethodref: sb.append("InterfaceMethodref: "); sb.append(this.getDescr_InterfaceMethodref( (ConstantInterfaceMethodrefInfo) ClassFile.this.constant_pool[index])); break; case AbstractCPInfo.CONSTANT_NameAndType: sb.append("NameAndType: "); sb.append(this.getDescr_NameAndType((ConstantNameAndTypeInfo) ClassFile.this.constant_pool[index], ClassFile.Descr_NameAndType.RAW)); break; default: sb.append("!!! Un-supported CP type."); break; } return sb.toString(); } private String getDescr_Utf8(final ConstantUtf8Info info) { return info.getValue(); } private String getDescr_Integer(final ConstantIntegerInfo info) { return String.valueOf(info.getValue()); } private String getDescr_Float(final ConstantFloatInfo info) { return String.valueOf(info.getValue()); } private String getDescr_Long(final ConstantLongInfo info) { return String.valueOf(info.getValue()); } private String getDescr_Double(final ConstantDoubleInfo info) { return String.valueOf(info.getValue()); } private String getDescr_Class(final ConstantClassInfo info) { // The value of the name_index item must be a valid index into the // constant_pool table. // The constant_pool entry at that index must be a // CONSTANT_Utf8_info structure // representing a valid fully qualified class or interface name // encoded in internal form. return SignatureConvertor.parseClassSignature( this.getDescr_Utf8((ConstantUtf8Info) ClassFile.this.constant_pool[info.getNameIndex()])); } private String getDescr_String(final ConstantStringInfo info) { // The value of the string_index item must be a valid index into the // constant_pool table. // The constant_pool entry at that index must be a // CONSTANT_Utf8_info (.4.7) structure // representing the sequence of characters to which the String // object is to be initialized. return SignatureConvertor.parseClassSignature( this.getDescr_Utf8((ConstantUtf8Info) ClassFile.this.constant_pool[info.getStringIndex()])); } private String getDescr_Fieldref(final ConstantFieldrefInfo info) { return this.getDescr_ref(info.getClassIndex(), info.getNameAndTypeIndex(), ClassFile.Descr_NameAndType.FIELD); } private String getDescr_Methodref(final ConstantMethodrefInfo info) { return this.getDescr_ref(info.getClassIndex(), info.getNameAndTypeIndex(), ClassFile.Descr_NameAndType.METHOD); } private String getDescr_InterfaceMethodref(final ConstantInterfaceMethodrefInfo info) { return this.getDescr_ref(info.getClassIndex(), info.getNameAndTypeIndex(), ClassFile.Descr_NameAndType.METHOD); } private String getDescr_ref(final int classindex, final int natindex, final ClassFile.Descr_NameAndType type) { final StringBuilder sb = new StringBuilder(); sb.append(this.getDescr_Class((ConstantClassInfo) ClassFile.this.constant_pool[classindex])); sb.append("."); sb.append(this.getDescr_NameAndType((ConstantNameAndTypeInfo) ClassFile.this.constant_pool[natindex], type)); return sb.toString(); } private String getDescr_NameAndType(final ConstantNameAndTypeInfo info, final ClassFile.Descr_NameAndType format) { final StringBuilder sb = new StringBuilder(); String type; sb.append(this.getDescr_Utf8((ConstantUtf8Info) ClassFile.this.constant_pool[info.getNameIndex()])); sb.append(", "); type = this.getDescr_Utf8((ConstantUtf8Info) ClassFile.this.constant_pool[info.getDescriptorIndex()]); switch (format) { case RAW: sb.append(type); break; case FIELD: try { sb.append("type = "); sb.append(SignatureConvertor.signature2Type(type)); } catch (SignatureException ex) { Logger.getLogger(ClassFile.class.getName()).log(Level.SEVERE, null, ex); sb.append(type); sb.append(" !!! Un-recognized type"); } break; case METHOD: final StringBuilder sb_mtd = new StringBuilder(); try { sb_mtd.append("parameter = "); sb_mtd.append(SignatureConvertor.parseMethodParameters(type)); sb_mtd.append(", returns = "); sb_mtd.append(SignatureConvertor.parseMethodReturnType(type)); sb.append(sb_mtd); } catch (SignatureException ex) { Logger.getLogger(ClassFile.class.getName()).log(Level.SEVERE, null, ex); sb.append(type); sb.append(" !!! Un-recognized type"); } break; default: break; } return sb.toString(); } } }