Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.objectweb.asm.test.ClassFile Maven / Gradle / Ivy
Go to download
Utilities for testing ASM, a very small and fast Java bytecode manipulation framework
// ASM: a very small and fast Java bytecode manipulation framework
// Copyright (c) 2000-2011 INRIA, France Telecom
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// 3. Neither the name of the copyright holders nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
package org.objectweb.asm.test;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
/**
* A Java class file, whose content can be returned as a verbose, human "readable" string. As an
* example, the string representation of the HelloWorld class, obtained with the {@link #toString()}
* method, is:
*
*
* magic: -889275714
* minor_version: 0
* major_version: 49
* access_flags: 33
* this_class: ConstantClassInfo HelloWorld
* super_class: ConstantClassInfo java/lang/Object
* interfaces_count: 0
* fields_count: 0
* methods_count: 2
* access_flags: 1
* name_index: <init>
* descriptor_index: ()V
* attributes_count: 1
* attribute_name_index: Code
* max_stack: 1
* max_locals: 1
* 0: 25 0
* 1: 183 ConstantMethodRefInfo java/lang/Object.<init>()V
* 2: 177
* exception_table_length: 0
* attributes_count: 2
* attribute_name_index: LineNumberTable
* line_number_table_length: 1
* start_pc: <0>
* line_number: 31
* attribute_name_index: LocalVariableTable
* local_variable_table_length: 1
* start_pc: <0>
* length: <3>
* name_index: this
* descriptor_index: LHelloWorld;
* index: 0
* access_flags: 9
* name_index: main
* descriptor_index: ([Ljava/lang/String;)V
* attributes_count: 1
* attribute_name_index: Code
* max_stack: 2
* max_locals: 1
* 0: 178 ConstantFieldRefInfo java/lang/System.outLjava/io/PrintStream;
* 1: 18 ConstantStringInfo Hello, world!
* 2: 182 ConstantMethodRefInfo java/io/PrintStream.println(Ljava/lang/String;)V
* 3: 177
* exception_table_length: 0
* attributes_count: 2
* attribute_name_index: LineNumberTable
* line_number_table_length: 2
* start_pc: <0>
* line_number: 33
* start_pc: <3>
* line_number: 34
* attribute_name_index: LocalVariableTable
* local_variable_table_length: 1
* start_pc: <0>
* length: <4>
* name_index: args
* descriptor_index: [Ljava/lang/String;
* index: 0
* attributes_count: 1
* attribute_name_index: SourceFile
* sourcefile_index: HelloWorld.java
*
*
* This class is used to compare classes in unit tests. Its source code is as close as possible
* to the Java Virtual Machine specification for ease of reference. The constant pool and bytecode
* offsets are abstracted away so that two classes which differ only by their constant pool or low
* level byte code instruction representation (e.g. a ldc vs. a ldc_w) are still considered equal.
* Likewise, attributes (resp. type annotations) are re-ordered into alphabetical order, so that two
* classes which differ only via the ordering of their attributes (resp. type annotations) are still
* considered equal.
*
* @author Eric Bruneton
*/
public class ClassFile {
/** The name of JDK9 module classes. */
static final String MODULE_INFO = "module-info";
/** The binary content of a Java class file. */
private final byte[] classBytes;
/** The name of the class contained in this class file, lazily computed. */
private String className;
/** The dump of the constant pool of {@link #classBytes}, lazily computed. */
private String constantPoolDump;
/** The dump of {@link #classBytes}, lazily computed. */
private String dump;
/**
* Constructs a new ClassFile instance.
*
* @param classBytes the binary content of a Java class file.
*/
public ClassFile(final byte[] classBytes) {
this.classBytes = classBytes;
}
/**
* Returns a string representation of the constant pool of the class contained in this class file.
*
* @return a string representation of the constant pool of the class contained in this class file.
*/
public String getConstantPoolDump() {
if (constantPoolDump == null) {
computeNameAndDumps();
}
return constantPoolDump;
}
/**
* Returns a new instance of the class contained in this class file. The class is loaded in a new
* class loader.
*
* @return a new instance of the class, or {@literal null} if the class is abstract, is an enum,
* or a module info.
* @throws ReflectiveOperationException if the class is invalid or if an error occurs in its
* constructor.
*/
public Object newInstance() throws ReflectiveOperationException {
if (className == null) {
computeNameAndDumps();
}
return newInstance(className, classBytes);
}
/**
* Returns a new instance of the given class. The class is loaded in a new class loader.
*
* @param className the name of the class to load.
* @param classContent the content of the class to load.
* @return a new instance of the class, or {@literal null} if the class is abstract, is an enum,
* or a module info.
* @throws ReflectiveOperationException if the class is invalid or if an error occurs in its
* constructor.
*/
static Object newInstance(final String className, final byte[] classContent)
throws ReflectiveOperationException {
if (className.endsWith(MODULE_INFO)) {
if (Util.getMajorJavaVersion() < 9) {
throw new UnsupportedClassVersionError("Module info is not supported before JDK 9");
} else {
return null;
}
}
ByteClassLoader byteClassLoader = new ByteClassLoader(className, classContent);
Class> clazz = byteClassLoader.loadClass(className);
// Make sure the class is loaded from the given byte array, excluding any other source.
if (!byteClassLoader.classLoaded()) {
// This should never happen, given the implementation of ByteClassLoader.
throw new AssertionError("Class " + className + " loaded from wrong source");
}
if (!clazz.isEnum() && (clazz.getModifiers() & Modifier.ABSTRACT) == 0) {
Constructor> constructor = clazz.getDeclaredConstructors()[0];
ArrayList arguments = new ArrayList<>();
for (Class> parameterType : constructor.getParameterTypes()) {
arguments.add(Array.get(Array.newInstance(parameterType, 1), 0));
}
constructor.setAccessible(true);
return constructor.newInstance(arguments.toArray(new Object[0]));
}
return null;
}
/**
* Returns whether the given class file is the same as this one.
*
* @return true if 'other' is a {@link ClassFile} with the same string representation.
* @throws ClassFormatException if the class content can't be parsed.
*/
@Override
public boolean equals(final Object other) {
if (other instanceof ClassFile) {
return toString().equals(other.toString());
}
return false;
}
/**
* Returns the hashcode of this class file.
*
* @return the hashcode of the string representation of this class file.
* @throws ClassFormatException if the class content can't be parsed.
*/
@Override
public int hashCode() {
return toString().hashCode();
}
/**
* Returns a string representation of this class file.
*
* @return a string representation of this class file (see the class comments for more details).
* @throws ClassFormatException if the class content can't be parsed.
*/
@Override
public String toString() {
if (dump == null) {
computeNameAndDumps();
}
return dump;
}
/**
* Computes the name and the string representation of the class (and of its constant pool)
* contained in this class file.
*
* @throws ClassFormatException if the class content can't be parsed.
*/
private void computeNameAndDumps() {
try {
Builder builder = new Builder("ClassFile", /* parent = */ null);
Builder constantPoolBuilder = new Builder("ConstantPool", /* parent = */ null);
ConstantClassInfo classInfo =
dumpClassFile(new Parser(classBytes), builder, constantPoolBuilder);
className = classInfo.dump().replace('/', '.');
StringBuilder stringBuilder = new StringBuilder();
builder.build(stringBuilder);
dump = stringBuilder.toString();
StringBuilder constantPoolStringBuilder = new StringBuilder();
constantPoolBuilder.build(constantPoolStringBuilder);
constantPoolDump = constantPoolStringBuilder.toString();
} catch (IOException e) {
throw new ClassFormatException(e.getMessage(), e);
}
}
/**
* Parses and dumps the high level structure of the class.
*
* @param parser a class parser.
* @param builder a dump builder.
* @param constantPoolBuilder a dump builder for the constant pool.
* @return the ConstantClassInfo corresponding to the parsed class.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 4.1
*/
private static ConstantClassInfo dumpClassFile(
final Parser parser, final Builder builder, final Builder constantPoolBuilder)
throws IOException {
builder.add("magic: ", parser.u4());
builder.add("minor_version: ", parser.u2());
int majorVersion = parser.u2();
if (majorVersion > /* V12 = */ 56) {
throw new ClassFormatException("Unsupported class version");
}
builder.add("major_version: ", majorVersion);
int constantPoolCount = parser.u2();
int cpIndex = 1;
while (cpIndex < constantPoolCount) {
CpInfo cpInfo = parseCpInfo(parser, builder);
builder.putCpInfo(cpIndex, cpInfo);
constantPoolBuilder.putCpInfo(cpIndex, cpInfo);
constantPoolBuilder.addCpInfo("constant_pool: ", cpIndex);
cpIndex += cpInfo.size();
}
builder.add("access_flags: ", parser.u2());
int thisClass = parser.u2();
builder.addCpInfo("this_class: ", thisClass);
builder.addCpInfo("super_class: ", parser.u2());
int interfaceCount = builder.add("interfaces_count: ", parser.u2());
for (int i = 0; i < interfaceCount; ++i) {
builder.addCpInfo("interface: ", parser.u2());
}
int fieldCount = builder.add("fields_count: ", parser.u2());
for (int i = 0; i < fieldCount; ++i) {
dumpFieldInfo(parser, builder);
}
int methodCount = builder.add("methods_count: ", parser.u2());
for (int i = 0; i < methodCount; ++i) {
dumpMethodInfo(parser, builder);
}
dumpAttributeList(parser, builder);
return builder.getCpInfo(thisClass, ConstantClassInfo.class);
}
/**
* Parses and dumps a list of attributes.
*
* @param parser a class parser.
* @param builder a dump builder.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 4.1
*/
private static void dumpAttributeList(final Parser parser, final Builder builder)
throws IOException {
int attributeCount = builder.add("attributes_count: ", parser.u2());
SortedBuilder sortedBuilder = builder.addSortedBuilder();
for (int i = 0; i < attributeCount; ++i) {
dumpAttributeInfo(parser, sortedBuilder);
}
}
/**
* Parses a cp_info structure.
*
* @param parser a class parser.
* @param classContext a context to lookup constant pool items from their index.
* @return the parsed constant pool item.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 4.4
*/
private static CpInfo parseCpInfo(final Parser parser, final ClassContext classContext)
throws IOException {
int tag = parser.u1();
switch (tag) {
case 7:
return new ConstantClassInfo(parser, classContext);
case 9:
return new ConstantFieldRefInfo(parser, classContext);
case 10:
return new ConstantMethodRefInfo(parser, classContext);
case 11:
return new ConstantInterfaceMethodRefInfo(parser, classContext);
case 8:
return new ConstantStringInfo(parser, classContext);
case 3:
return new ConstantIntegerInfo(parser);
case 4:
return new ConstantFloatInfo(parser);
case 5:
return new ConstantLongInfo(parser);
case 6:
return new ConstantDoubleInfo(parser);
case 12:
return new ConstantNameAndTypeInfo(parser, classContext);
case 1:
return new ConstantUtf8Info(parser);
case 15:
return new ConstantMethodHandleInfo(parser, classContext);
case 16:
return new ConstantMethodTypeInfo(parser, classContext);
case 17:
return new ConstantDynamicInfo(parser, classContext);
case 18:
return new ConstantInvokeDynamicInfo(parser, classContext);
case 19:
return new ConstantModuleInfo(parser, classContext);
case 20:
return new ConstantPackageInfo(parser, classContext);
default:
throw new ClassFormatException("Invalid constant pool item tag " + tag);
}
}
/**
* Parses and dumps a field_info structure.
*
* @param parser a class parser.
* @param builder a dump builder.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 4.5
*/
private static void dumpFieldInfo(final Parser parser, final Builder builder) throws IOException {
builder.add("access_flags: ", parser.u2());
builder.addCpInfo("name_index: ", parser.u2());
builder.addCpInfo("descriptor_index: ", parser.u2());
dumpAttributeList(parser, builder);
}
/**
* Parses and dumps a method_info structure.
*
* @param parser a class parser.
* @param builder a dump builder.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 4.6
*/
private static void dumpMethodInfo(final Parser parser, final Builder builder)
throws IOException {
// method_info has the same top level structure as field_info.
dumpFieldInfo(parser, builder);
}
/**
* Parses and dumps an attribute_info structure.
*
* @param parser a class parser.
* @param sortedBuilder a dump builder.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 4.7
*/
private static void dumpAttributeInfo(final Parser parser, final SortedBuilder sortedBuilder)
throws IOException {
String attributeName = sortedBuilder.getCpInfo(parser.u2()).toString();
int attributeLength = parser.u4();
Builder builder = sortedBuilder.addBuilder(attributeName);
builder.add("attribute_name_index: ", attributeName);
if (attributeName.equals("ConstantValue")) {
dumpConstantValueAttribute(parser, builder);
} else if (attributeName.equals("Code")) {
dumpCodeAttribute(parser, builder);
} else if (attributeName.equals("StackMapTable")) {
dumpStackMapTableAttribute(parser, builder);
} else if (attributeName.equals("Exceptions")) {
dumpExceptionsAttribute(parser, builder);
} else if (attributeName.equals("InnerClasses")) {
dumpInnerClassesAttribute(parser, builder);
} else if (attributeName.equals("EnclosingMethod")) {
dumpEnclosingMethodAttribute(parser, builder);
} else if (attributeName.equals("Synthetic")) {
dumpSyntheticAttribute();
} else if (attributeName.equals("Signature")) {
dumpSignatureAttribute(parser, builder);
} else if (attributeName.equals("SourceFile")) {
dumpSourceFileAttribute(parser, builder);
} else if (attributeName.equals("SourceDebugExtension")) {
dumpSourceDebugAttribute(attributeLength, parser, builder);
} else if (attributeName.equals("LineNumberTable")) {
dumpLineNumberTableAttribute(parser, builder);
} else if (attributeName.equals("LocalVariableTable")) {
dumpLocalVariableTableAttribute(parser, builder);
} else if (attributeName.equals("LocalVariableTypeTable")) {
dumpLocalVariableTypeTableAttribute(parser, builder);
} else if (attributeName.equals("Deprecated")) {
dumpDeprecatedAttribute();
} else if (attributeName.equals("RuntimeVisibleAnnotations")) {
dumpRuntimeVisibleAnnotationsAttribute(parser, builder);
} else if (attributeName.equals("RuntimeInvisibleAnnotations")) {
dumpRuntimeInvisibleAnnotationsAttribute(parser, builder);
} else if (attributeName.equals("RuntimeVisibleParameterAnnotations")) {
dumpRuntimeVisibleParameterAnnotationsAttribute(parser, builder);
} else if (attributeName.equals("RuntimeInvisibleParameterAnnotations")) {
dumpRuntimeInvisibleParameterAnnotationsAttribute(parser, builder);
} else if (attributeName.equals("RuntimeVisibleTypeAnnotations")) {
dumpRuntimeVisibleTypeAnnotationsAttribute(parser, builder);
} else if (attributeName.equals("RuntimeInvisibleTypeAnnotations")) {
dumpRuntimeInvisibleTypeAnnotationsAttribute(parser, builder);
} else if (attributeName.equals("AnnotationDefault")) {
dumpAnnotationDefaultAttribute(parser, builder);
} else if (attributeName.equals("BootstrapMethods")) {
dumpBootstrapMethodsAttribute(parser, builder);
} else if (attributeName.equals("MethodParameters")) {
dumpMethodParametersAttribute(parser, builder);
} else if (attributeName.equals("Module")) {
dumpModuleAttribute(parser, builder);
} else if (attributeName.equals("ModulePackages")) {
dumpModulePackagesAttribute(parser, builder);
} else if (attributeName.equals("ModuleMainClass")) {
dumpModuleMainClassAttribute(parser, builder);
} else if (attributeName.equals("NestHost")) {
dumpNestHostAttribute(parser, builder);
} else if (attributeName.equals("NestMembers")) {
dumpNestMembersAttribute(parser, builder);
} else if (attributeName.equals("StackMap")) {
dumpStackMapAttribute(parser, builder);
} else if (!attributeName.equals("CodeComment") && !attributeName.equals("Comment")) {
// Not a standard attribute nor one the of empty non-standard attributes used for tests.
throw new ClassFormatException("Unknown attribute " + attributeName);
}
}
/**
* Parses and dumps a ConstantValue attribute.
*
* @param parser a class parser.
* @param builder a dump builder.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 4.7.2
*/
private static void dumpConstantValueAttribute(final Parser parser, final Builder builder)
throws IOException {
builder.addCpInfo("constantvalue_index: ", parser.u2());
}
/**
* Parses and dumps a Code attribute.
*
* @param parser a class parser.
* @param builder a dump builder.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 4.7.3
*/
private static void dumpCodeAttribute(final Parser parser, final Builder builder)
throws IOException {
builder.add("max_stack: ", parser.u2());
builder.add("max_locals: ", parser.u2());
int codeLength = parser.u4();
dumpInstructions(codeLength, parser, builder);
int exceptionCount = builder.add("exception_table_length: ", parser.u2());
for (int i = 0; i < exceptionCount; ++i) {
builder.addInsnIndex("start_pc: ", parser.u2());
builder.addInsnIndex("end_pc: ", parser.u2());
builder.addInsnIndex("handler_pc: ", parser.u2());
builder.addCpInfo("catch_type: ", parser.u2());
}
dumpAttributeList(parser, builder);
}
/**
* Parses and dumps the bytecode instructions of a method.
*
* @param codeLength the number of bytes to parse.
* @param parser a class parser.
* @param builder a dump builder.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 6.5
*/
private static void dumpInstructions(
final int codeLength, final Parser parser, final Builder builder) throws IOException {
int bytecodeOffset = 0; // Number of bytes parsed so far.
int insnIndex = 0; // Number of instructions parsed so far.
while (bytecodeOffset < codeLength) {
builder.putInsnIndex(bytecodeOffset, insnIndex);
int opcode = parser.u1();
int startOffset = bytecodeOffset++;
// Instructions are in alphabetical order of their opcode name, as
// in the specification. This leads to some duplicated code, but is
// done on purpose for ease of reference.
switch (opcode) {
case 0x32: // aaload
case 0x53: // aastore
case 0x01: // aconst_null
builder.addInsn(insnIndex, opcode);
break;
case 0x19: // aload
builder.addInsn(insnIndex, opcode, parser.u1());
bytecodeOffset += 1;
break;
case 0x2A: // aload_0
case 0x2B: // aload_1
case 0x2C: // aload_2
case 0x2D: // aload_3
builder.addInsn(insnIndex, 0x19, opcode - 0x2A);
break;
case 0xBD: // anewarray
builder.addInsn(insnIndex, opcode, builder.getCpInfo(parser.u2()));
bytecodeOffset += 2;
break;
case 0xB0: // areturn
case 0xBE: // arraylength
builder.addInsn(insnIndex, opcode);
break;
case 0x3A: // astore
builder.addInsn(insnIndex, opcode, parser.u1());
bytecodeOffset += 1;
break;
case 0x4B: // astore_0
case 0x4C: // astore_1
case 0x4D: // astore_2
case 0x4E: // astore_3
builder.addInsn(insnIndex, 0x3A, opcode - 0x4B);
break;
case 0xBF: // athrow
case 0x33: // baload
case 0x54: // bastore
builder.addInsn(insnIndex, opcode);
break;
case 0x10: // bipush
builder.addInsn(insnIndex, opcode, parser.u1());
bytecodeOffset += 1;
break;
case 0x34: // caload
case 0x55: // castore
builder.addInsn(insnIndex, opcode);
break;
case 0xC0: // checkcast
builder.addInsn(insnIndex, opcode, builder.getCpInfo(parser.u2()));
bytecodeOffset += 2;
break;
case 0x90: // d2f
case 0x8E: // d2i
case 0x8F: // d2l
case 0x63: // dadd
case 0x31: // daload
case 0x52: // dastore
case 0x98: // dcmpg
case 0x97: // dcmpl
case 0x0E: // dconst_0
case 0x0F: // dconst_1
case 0x6F: // ddiv
builder.addInsn(insnIndex, opcode);
break;
case 0x18: // dload
builder.addInsn(insnIndex, opcode, parser.u1());
bytecodeOffset += 1;
break;
case 0x26: // dload_0
case 0x27: // dload_1
case 0x28: // dload_2
case 0x29: // dload_3
builder.addInsn(insnIndex, 0x18, opcode - 0x26);
break;
case 0x6B: // dmul
case 0x77: // dneg
case 0x73: // drem
case 0xAF: // dreturn
builder.addInsn(insnIndex, opcode);
break;
case 0x39: // dstore
builder.addInsn(insnIndex, opcode, parser.u1());
bytecodeOffset += 1;
break;
case 0x47: // dstore_0
case 0x48: // dstore_1
case 0x49: // dstore_2
case 0x4A: // dstore_3
builder.addInsn(insnIndex, 0x39, opcode - 0x47);
break;
case 0x67: // dsub
case 0x59: // dup
case 0x5A: // dup_x1
case 0x5B: // dup_x2
case 0x5C: // dup2
case 0x5D: // dup2_x1
case 0x5E: // dup2_x2
case 0x8D: // f2d
case 0x8B: // f2i
case 0x8C: // f2l
case 0x62: // fadd
case 0x30: // faload
case 0x51: // fastore
case 0x96: // fcmpg
case 0x95: // fcmpl
case 0x0B: // fconst_0
case 0x0C: // fconst_1
case 0x0D: // fconst_2
case 0x6E: // fdiv
builder.addInsn(insnIndex, opcode);
break;
case 0x17: // fload
builder.addInsn(insnIndex, opcode, parser.u1());
bytecodeOffset += 1;
break;
case 0x22: // fload_0
case 0x23: // fload_1
case 0x24: // fload_2
case 0x25: // fload_3
builder.addInsn(insnIndex, 0x17, opcode - 0x22);
break;
case 0x6A: // fmul
case 0x76: // fneg
case 0x72: // frem
case 0xAE: // freturn
builder.addInsn(insnIndex, opcode);
break;
case 0x38: // fstore
builder.addInsn(insnIndex, opcode, parser.u1());
bytecodeOffset += 1;
break;
case 0x43: // fstore_0
case 0x44: // fstore_1
case 0x45: // fstore_2
case 0x46: // fstore_3
builder.addInsn(insnIndex, 0x38, opcode - 0x43);
break;
case 0x66: // fsub
builder.addInsn(insnIndex, opcode);
break;
case 0xB4: // getfield
case 0xB2: // getstatic
builder.addInsn(insnIndex, opcode, builder.getCpInfo(parser.u2()));
bytecodeOffset += 2;
break;
case 0xA7: // goto
builder.addInsn(
insnIndex, opcode, new InstructionIndex(startOffset + parser.s2(), builder));
bytecodeOffset += 2;
break;
case 0xC8: // goto_w
builder.addInsn(
insnIndex, 0xA7, new InstructionIndex(startOffset + parser.u4(), builder));
bytecodeOffset += 4;
break;
case 0x91: // i2b
case 0x92: // i2c
case 0x87: // i2d
case 0x86: // i2f
case 0x85: // i2l
case 0x93: // i2s
case 0x60: // iadd
case 0x2E: // iaload
case 0x7E: // iand
case 0x4F: // iastore
case 0x02: // iconst_m1
case 0x03: // iconst_0
case 0x04: // iconst_1
case 0x05: // iconst_2
case 0x06: // iconst_3
case 0x07: // iconst_4
case 0x08: // iconst_5
case 0x6C: // idiv
builder.addInsn(insnIndex, opcode);
break;
case 0xA5: // if_acmpeq
case 0xA6: // if_acmpne
case 0x9F: // if_icmpeq
case 0xA0: // if_icmpne
case 0xA1: // if_icmplt
case 0xA2: // if_icmpge
case 0xA3: // if_icmpgt
case 0xA4: // if_icmple
case 0x99: // ifeq
case 0x9A: // ifne
case 0x9B: // iflt
case 0x9C: // ifge
case 0x9D: // ifgt
case 0x9E: // ifle
case 0xC7: // ifnonnull
case 0xC6: // ifnull
builder.addInsn(
insnIndex, opcode, new InstructionIndex(startOffset + parser.s2(), builder));
bytecodeOffset += 2;
break;
case 0x84: // iinc
builder.addInsn(insnIndex, opcode, parser.u1(), parser.s1());
bytecodeOffset += 2;
break;
case 0x15: // iload
builder.addInsn(insnIndex, opcode, parser.u1());
bytecodeOffset += 1;
break;
case 0x1A: // iload_0
case 0x1B: // iload_1
case 0x1C: // iload_2
case 0x1D: // iload_3
builder.addInsn(insnIndex, 0x15, opcode - 0x1A);
break;
case 0x68: // imul
case 0x74: // ineg
builder.addInsn(insnIndex, opcode);
break;
case 0xC1: // instanceof
builder.addInsn(insnIndex, opcode, builder.getCpInfo(parser.u2()));
bytecodeOffset += 2;
break;
case 0xBA: // invokedynamic
builder.addInsn(insnIndex, opcode, builder.getCpInfo(parser.u2()));
parser.u2();
bytecodeOffset += 4;
break;
case 0xB9: // invokeinterface
builder.addInsn(insnIndex, opcode, builder.getCpInfo(parser.u2()), parser.u1());
parser.u1();
bytecodeOffset += 4;
break;
case 0xB7: // invokespecial
case 0xB8: // invokestatic
case 0xB6: // invokevirtual
builder.addInsn(insnIndex, opcode, builder.getCpInfo(parser.u2()));
bytecodeOffset += 2;
break;
case 0x80: // ior
case 0x70: // irem
case 0xAC: // ireturn
case 0x78: // ishl
case 0x7A: // ishr
builder.addInsn(insnIndex, opcode);
break;
case 0x36: // istore
builder.addInsn(insnIndex, opcode, parser.u1());
bytecodeOffset += 1;
break;
case 0x3B: // istore_0
case 0x3C: // istore_1
case 0x3D: // istore_2
case 0x3E: // istore_3
builder.addInsn(insnIndex, 0x36, opcode - 0x3B);
break;
case 0x64: // isub
case 0x7C: // iushr
case 0x82: // ixor
builder.addInsn(insnIndex, opcode);
break;
case 0xA8: // jsr
builder.addInsn(
insnIndex, opcode, new InstructionIndex(startOffset + parser.s2(), builder));
bytecodeOffset += 2;
break;
case 0xC9: // jsr_w
builder.addInsn(
insnIndex, 0xA8, new InstructionIndex(startOffset + parser.u4(), builder));
bytecodeOffset += 4;
break;
case 0x8A: // l2d
case 0x89: // l2f
case 0x88: // l2i
case 0x61: // ladd
case 0x2F: // laload
case 0x7F: // land
case 0x50: // lastore
case 0x94: // lcmp
case 0x09: // lconst_0
case 0x0A: // lconst_1
builder.addInsn(insnIndex, opcode);
break;
case 0x12: // ldc
builder.addInsn(insnIndex, 0x12, builder.getCpInfo(parser.u1()));
bytecodeOffset += 1;
break;
case 0x13: // ldc_w
case 0x14: // ldc2_w
builder.addInsn(insnIndex, opcode == 0x13 ? 0x12 : 0x14, builder.getCpInfo(parser.u2()));
bytecodeOffset += 2;
break;
case 0x6D: // ldiv
builder.addInsn(insnIndex, opcode);
break;
case 0x16: // lload
builder.addInsn(insnIndex, opcode, parser.u1());
bytecodeOffset += 1;
break;
case 0x1E: // lload_0
case 0x1F: // lload_1
case 0x20: // lload_2
case 0x21: // lload_3
builder.addInsn(insnIndex, 0x16, opcode - 0x1E);
break;
case 0x69: // lmul
case 0x75: // lneg
builder.addInsn(insnIndex, opcode);
break;
case 0xAB: // lookupswitch
builder.addInsn(insnIndex, opcode);
while (bytecodeOffset % 4 != 0) {
parser.u1();
bytecodeOffset++;
}
builder.addInsnIndex("default: ", startOffset + parser.u4());
int pairCount = builder.add("npairs: ", parser.u4());
bytecodeOffset += 8;
for (int i = 0; i < pairCount; ++i) {
builder.addInsnIndex(parser.u4() + ": ", startOffset + parser.u4());
bytecodeOffset += 8;
}
break;
case 0x81: // lor
case 0x71: // lrem
case 0xAD: // lreturn
case 0x79: // lshl
case 0x7B: // lshr
builder.addInsn(insnIndex, opcode);
break;
case 0x37: // lstore
builder.addInsn(insnIndex, opcode, parser.u1());
bytecodeOffset += 1;
break;
case 0x3F: // lstore_0
case 0x40: // lstore_1
case 0x41: // lstore_2
case 0x42: // lstore_3
builder.addInsn(insnIndex, 0x37, opcode - 0x3F);
break;
case 0x65: // lsub
case 0x7D: // lushr
case 0x83: // lxor
case 0xC2: // monitorenter
case 0xC3: // monitorexit
builder.addInsn(insnIndex, opcode);
break;
case 0xC5: // multianewarray
builder.addInsn(insnIndex, opcode, builder.getCpInfo(parser.u2()), parser.u1());
bytecodeOffset += 3;
break;
case 0xBB: // new
builder.addInsn(insnIndex, opcode, builder.getCpInfo(parser.u2()));
bytecodeOffset += 2;
break;
case 0xBC: // newarray
builder.addInsn(insnIndex, opcode, parser.u1());
bytecodeOffset += 1;
break;
case 0x00: // nop
case 0x57: // pop
case 0x58: // pop2
builder.addInsn(insnIndex, opcode);
break;
case 0xB5: // putfield
case 0xB3: // putstatic
builder.addInsn(insnIndex, opcode, builder.getCpInfo(parser.u2()));
bytecodeOffset += 2;
break;
case 0xA9: // ret
builder.addInsn(insnIndex, opcode, parser.u1());
bytecodeOffset += 1;
break;
case 0xB1: // return
case 0x35: // saload
case 0x56: // sastore
builder.addInsn(insnIndex, opcode);
break;
case 0x11: // sipush
builder.addInsn(insnIndex, opcode, parser.s2());
bytecodeOffset += 2;
break;
case 0x5F: // swap
builder.addInsn(insnIndex, opcode);
break;
case 0xAA: // tableswitch
builder.addInsn(insnIndex, opcode);
while (bytecodeOffset % 4 != 0) {
parser.u1();
bytecodeOffset++;
}
builder.addInsnIndex("default: ", startOffset + parser.u4());
int low = builder.add("low: ", parser.u4());
int high = builder.add("high: ", parser.u4());
bytecodeOffset += 12;
for (int i = low; i <= high; ++i) {
builder.addInsnIndex(i + ": ", startOffset + parser.u4());
bytecodeOffset += 4;
}
break;
case 0xC4: // wide
opcode = parser.u1();
bytecodeOffset += 1;
switch (opcode) {
case 0x15: // iload
case 0x17: // fload
case 0x19: // aload
case 0x16: // lload
case 0x18: // dload
case 0x36: // istore
case 0x38: // fstore
case 0x3A: // astore
case 0x37: // lstore
case 0x39: // dstore
case 0xA9: // ret
builder.addInsn(insnIndex, opcode, parser.u2());
bytecodeOffset += 2;
break;
case 0x84: // iinc
builder.addInsn(insnIndex, opcode, parser.u2(), parser.s2());
bytecodeOffset += 4;
break;
default:
throw new ClassFormatException("Unknown wide opcode: " + opcode);
}
break;
default:
throw new ClassFormatException("Unknown opcode: " + opcode);
}
insnIndex++;
}
builder.putInsnIndex(bytecodeOffset, insnIndex);
}
/**
* Parses and dumps a StackMapTable attribute.
*
* @param parser a class parser.
* @param builder a dump builder.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 4.7.4
*/
private static void dumpStackMapTableAttribute(final Parser parser, final Builder builder)
throws IOException {
int entryCount = builder.add("number_of_entries: ", parser.u2());
int bytecodeOffset = -1;
for (int i = 0; i < entryCount; ++i) {
int frameType = parser.u1();
if (frameType < 64) {
int offsetDelta = frameType;
bytecodeOffset += offsetDelta + 1;
builder.addInsnIndex("SAME ", bytecodeOffset);
} else if (frameType < 128) {
int offsetDelta = frameType - 64;
bytecodeOffset += offsetDelta + 1;
builder.addInsnIndex("SAME_LOCALS_1_STACK_ITEM ", bytecodeOffset);
dumpVerificationTypeInfo(parser, builder);
} else if (frameType < 247) {
throw new ClassFormatException("Unknown frame type " + frameType);
} else if (frameType == 247) {
int offsetDelta = parser.u2();
bytecodeOffset += offsetDelta + 1;
builder.addInsnIndex("SAME_LOCALS_1_STACK_ITEM ", bytecodeOffset);
dumpVerificationTypeInfo(parser, builder);
} else if (frameType < 251) {
int offsetDelta = parser.u2();
bytecodeOffset += offsetDelta + 1;
builder.addInsnIndex("CHOP_" + (251 - frameType) + " ", bytecodeOffset);
} else if (frameType == 251) {
int offsetDelta = parser.u2();
bytecodeOffset += offsetDelta + 1;
builder.addInsnIndex("SAME ", bytecodeOffset);
} else if (frameType < 255) {
int offsetDelta = parser.u2();
bytecodeOffset += offsetDelta + 1;
builder.addInsnIndex("APPEND_" + (frameType - 251) + " ", bytecodeOffset);
for (int j = 0; j < frameType - 251; ++j) {
dumpVerificationTypeInfo(parser, builder);
}
} else {
int offsetDelta = parser.u2();
bytecodeOffset += offsetDelta + 1;
builder.addInsnIndex("FULL ", bytecodeOffset);
int numberOfLocals = builder.add("number_of_locals: ", parser.u2());
for (int j = 0; j < numberOfLocals; ++j) {
dumpVerificationTypeInfo(parser, builder);
}
int numberOfStackItems = builder.add("number_of_stack_items: ", parser.u2());
for (int j = 0; j < numberOfStackItems; ++j) {
dumpVerificationTypeInfo(parser, builder);
}
}
}
}
/**
* Parses and dumps a verification_type_info structure.
*
* @param parser a class parser.
* @param builder a dump builder.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 4.7.2
*/
private static void dumpVerificationTypeInfo(final Parser parser, final Builder builder)
throws IOException {
int tag = builder.add("tag: ", parser.u1());
if (tag > 8) {
throw new ClassFormatException("Unknown verification_type_info tag: " + tag);
}
if (tag == 7) {
builder.addCpInfo("cpool_index: ", parser.u2());
} else if (tag == 8) {
builder.addInsnIndex("offset: ", parser.u2());
}
}
/**
* Parses and dumps an Exception attribute.
*
* @param parser a class parser.
* @param builder a dump builder.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 4.7.5
*/
private static void dumpExceptionsAttribute(final Parser parser, final Builder builder)
throws IOException {
int exceptionCount = builder.add("number_of_exceptions: ", parser.u2());
for (int i = 0; i < exceptionCount; ++i) {
builder.addCpInfo("exception_index: ", parser.u2());
}
}
/**
* Parses and dumps an InnerClasses attribute.
*
* @param parser a class parser.
* @param builder a dump builder.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 4.7.6
*/
private static void dumpInnerClassesAttribute(final Parser parser, final Builder builder)
throws IOException {
int classCount = builder.add("number_of_classes: ", parser.u2());
for (int i = 0; i < classCount; ++i) {
builder.addCpInfo("inner_class_info_index: ", parser.u2());
builder.addCpInfo("outer_class_info_index: ", parser.u2());
builder.addCpInfo("inner_name_index: ", parser.u2());
builder.add("inner_class_access_flags: ", parser.u2());
}
}
/**
* Parses and dumps an EnclosingMethod attribute.
*
* @param parser a class parser.
* @param builder a dump builder.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 4.7.7
*/
private static void dumpEnclosingMethodAttribute(final Parser parser, final Builder builder)
throws IOException {
builder.addCpInfo("class_index: ", parser.u2());
builder.addCpInfo("method_index: ", parser.u2());
}
/**
* Parses and dumps a Synthetic attribute.
*
* @see JVMS
* 4.7.8
*/
private static void dumpSyntheticAttribute() {
// Nothing to parse.
}
/**
* Parses and dumps a Signature attribute.
*
* @param parser a class parser.
* @param builder a dump builder.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 4.7.9
*/
private static void dumpSignatureAttribute(final Parser parser, final Builder builder)
throws IOException {
builder.addCpInfo("signature_index: ", parser.u2());
}
/**
* Parses and dumps a SourceFile attribute.
*
* @param parser a class parser.
* @param builder a dump builder.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 4.7.10
*/
private static void dumpSourceFileAttribute(final Parser parser, final Builder builder)
throws IOException {
builder.addCpInfo("sourcefile_index: ", parser.u2());
}
/**
* Parses and dumps a SourceDebug attribute.
*
* @param attributeLength the length of the SourceDebug attribute (excluding its 6 header bytes).
* @param parser a class parser.
* @param builder a dump builder.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 4.7.11
*/
private static void dumpSourceDebugAttribute(
final int attributeLength, final Parser parser, final Builder builder) throws IOException {
byte[] attributeData = parser.bytes(attributeLength);
StringBuilder stringBuilder = new StringBuilder();
for (byte data : attributeData) {
stringBuilder.append(data).append(',');
}
builder.add("debug_extension: ", stringBuilder.toString());
}
/**
* Parses and dumps a LineNumberTable attribute.
*
* @param parser a class parser.
* @param builder a dump builder.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 4.7.12
*/
private static void dumpLineNumberTableAttribute(final Parser parser, final Builder builder)
throws IOException {
int lineNumberCount = builder.add("line_number_table_length: ", parser.u2());
for (int i = 0; i < lineNumberCount; ++i) {
builder.addInsnIndex("start_pc: ", parser.u2());
builder.add("line_number: ", parser.u2());
}
}
/**
* Parses and dumps a LocalVariableTable attribute.
*
* @param parser a class parser.
* @param builder a dump builder.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 4.7.13
*/
private static void dumpLocalVariableTableAttribute(final Parser parser, final Builder builder)
throws IOException {
int localVariableCount = builder.add("local_variable_table_length: ", parser.u2());
for (int i = 0; i < localVariableCount; ++i) {
int startPc = builder.addInsnIndex("start_pc: ", parser.u2());
builder.addInsnIndex("length: ", startPc + parser.u2());
builder.addCpInfo("name_index: ", parser.u2());
builder.addCpInfo("descriptor_index: ", parser.u2());
builder.add("index: ", parser.u2());
}
}
/**
* Parses and dumps a LocalVariableTypeTable attribute.
*
* @param parser a class parser.
* @param builder a dump builder.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 4.7.14
*/
private static void dumpLocalVariableTypeTableAttribute(
final Parser parser, final Builder builder) throws IOException {
int localVariableCount = builder.add("local_variable_type_table_length: ", parser.u2());
for (int i = 0; i < localVariableCount; ++i) {
int startPc = builder.addInsnIndex("start_pc: ", parser.u2());
builder.addInsnIndex("length: ", startPc + parser.u2());
builder.addCpInfo("name_index: ", parser.u2());
builder.addCpInfo("signature_index: ", parser.u2());
builder.add("index: ", parser.u2());
}
}
/**
* Parses and dumps a Deprecated attribute.
*
* @see JVMS
* 4.7.15
*/
private static void dumpDeprecatedAttribute() {
// Nothing to parse.
}
/**
* Parses and dumps a RuntimeVisibleAnnotations attribute.
*
* @param parser a class parser.
* @param builder a dump builder.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 4.7.16
*/
private static void dumpRuntimeVisibleAnnotationsAttribute(
final Parser parser, final Builder builder) throws IOException {
int annotationCount = builder.add("num_annotations: ", parser.u2());
for (int i = 0; i < annotationCount; ++i) {
dumpAnnotation(parser, builder);
}
}
/**
* Parses and dumps an annotations structure.
*
* @param parser a class parser.
* @param builder a dump builder.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 4.7.16
*/
private static void dumpAnnotation(final Parser parser, final Builder builder)
throws IOException {
builder.addCpInfo("type_index: ", parser.u2());
int elementValuePairCount = builder.add("num_element_value_pairs: ", parser.u2());
for (int i = 0; i < elementValuePairCount; ++i) {
builder.addCpInfo("element_name_index: ", parser.u2());
dumpElementValue(parser, builder);
}
}
/**
* Parses and dumps an element_value structure.
*
* @param parser a class parser.
* @param builder a dump builder.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 4.7.16.1
*/
private static void dumpElementValue(final Parser parser, final Builder builder)
throws IOException {
int tag = parser.u1();
switch (tag) {
case 'B':
case 'C':
case 'D':
case 'F':
case 'I':
case 'J':
case 'S':
case 'Z':
case 's':
builder.addCpInfo(((char) tag) + ": ", parser.u2());
return;
case 'e':
builder.addCpInfo("e: ", parser.u2());
builder.addCpInfo("const_name_index: ", parser.u2());
return;
case 'c':
builder.addCpInfo(((char) tag) + ": ", parser.u2());
return;
case '@':
builder.add("@: ", "");
dumpAnnotation(parser, builder);
return;
case '[':
int valueCount = builder.add("[: ", parser.u2());
for (int i = 0; i < valueCount; ++i) {
dumpElementValue(parser, builder);
}
return;
default:
throw new ClassFormatException("Unknown element_type tag: " + tag);
}
}
/**
* Parses and dumps a RuntimeInvisibleAnnotations attribute.
*
* @param parser a class parser.
* @param builder a dump builder.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 4.7.17
*/
private static void dumpRuntimeInvisibleAnnotationsAttribute(
final Parser parser, final Builder builder) throws IOException {
dumpRuntimeVisibleAnnotationsAttribute(parser, builder);
}
/**
* Parses and dumps a RuntimeVisibleParameterAnnotations attribute.
*
* @param parser a class parser.
* @param builder a dump builder.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 4.7.18
*/
private static void dumpRuntimeVisibleParameterAnnotationsAttribute(
final Parser parser, final Builder builder) throws IOException {
int parameterCount = builder.add("num_parameters: ", parser.u1());
for (int i = 0; i < parameterCount; ++i) {
int annotationCount = builder.add("num_annotations: ", parser.u2());
for (int j = 0; j < annotationCount; ++j) {
dumpAnnotation(parser, builder);
}
}
}
/**
* Parses and dumps a RuntimeInvisibleParameterAnnotations attribute.
*
* @param parser a class parser.
* @param builder a dump builder.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 4.7.19
*/
private static void dumpRuntimeInvisibleParameterAnnotationsAttribute(
final Parser parser, final Builder builder) throws IOException {
dumpRuntimeVisibleParameterAnnotationsAttribute(parser, builder);
}
/**
* Parses and dumps a RuntimeVisibleTypeAnnotations attribute.
*
* @param parser a class parser.
* @param builder a dump builder.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 4.7.20
*/
private static void dumpRuntimeVisibleTypeAnnotationsAttribute(
final Parser parser, final Builder builder) throws IOException {
int annotationCount = builder.add("num_annotations: ", parser.u2());
SortedBuilder sortedBuilder = builder.addSortedBuilder();
for (int i = 0; i < annotationCount; ++i) {
dumpTypeAnnotation(parser, sortedBuilder);
}
}
/**
* Parses and dumps a type_annotation structure.
*
* @param parser a class parser.
* @param sortedBuilder a dump builder.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 4.7.20
*/
private static void dumpTypeAnnotation(final Parser parser, final SortedBuilder sortedBuilder)
throws IOException {
int targetType = parser.u1();
Builder builder = sortedBuilder.addBuilder(String.valueOf(targetType));
builder.add("target_type: ", targetType);
switch (targetType) {
case 0x00:
case 0x01:
// type_parameter_target
builder.add("type_parameter_index: ", parser.u1());
break;
case 0x10:
// supertype_target
builder.add("supertype_index: ", parser.u2());
break;
case 0x11:
case 0x12:
// type_parameter_bound_target
builder.add("type_parameter_index: ", parser.u1());
builder.add("bound_index: ", parser.u1());
break;
case 0x13:
case 0x14:
case 0x15:
// empty_target
// Nothing to parse.
break;
case 0x16:
// formal_parameter_target
builder.add("formal_parameter_index: ", parser.u1());
break;
case 0x17:
// throws_target
builder.add("throws_type_index: ", parser.u2());
break;
case 0x40:
case 0x41:
// localvar_target
int tableLength = builder.add("table_length: ", parser.u2());
for (int i = 0; i < tableLength; ++i) {
int startPc = builder.addInsnIndex("start_pc: ", parser.u2());
builder.addInsnIndex("length: ", startPc + parser.u2());
builder.add("index: ", parser.u2());
}
break;
case 0x42:
// catch_target
builder.add("exception_table_index: ", parser.u2());
break;
case 0x43:
case 0x44:
case 0x45:
case 0x46:
// offset_target
builder.addInsnIndex("offset: ", parser.u2());
break;
case 0x47:
case 0x48:
case 0x49:
case 0x4A:
case 0x4B:
// type_argument_target
builder.addInsnIndex("offset: ", parser.u2());
builder.add("type_argument_index: ", parser.u1());
break;
default:
throw new ClassFormatException("Unknown target_type: " + targetType);
}
dumpTypePath(parser, builder);
// Sort type annotations based on the full target_info and type_path (excluding the annotation
// content), instead of only on their target_type.
builder.sortByContent();
dumpAnnotation(parser, builder);
}
/**
* Parses and dumps a type_path structure.
*
* @param parser a class parser.
* @param builder a dump builder.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 4.7.20.2
*/
private static void dumpTypePath(final Parser parser, final Builder builder) throws IOException {
int pathLength = builder.add("path_length: ", parser.u1());
for (int i = 0; i < pathLength; ++i) {
builder.add("type_path_kind: ", parser.u1());
builder.add("type_argument_index: ", parser.u1());
}
}
/**
* Parses and dumps a RuntimeInvisibleTypeAnnotations attribute.
*
* @param parser a class parser.
* @param builder a dump builder.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 4.7.21
*/
private static void dumpRuntimeInvisibleTypeAnnotationsAttribute(
final Parser parser, final Builder builder) throws IOException {
dumpRuntimeVisibleTypeAnnotationsAttribute(parser, builder);
}
/**
* Parses and dumps an AnnotationDefault attribute.
*
* @param parser a class parser.
* @param builder a dump builder.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 4.7.22
*/
private static void dumpAnnotationDefaultAttribute(final Parser parser, final Builder builder)
throws IOException {
dumpElementValue(parser, builder);
}
/**
* Parses and dumps a BootstrapMethods attribute.
*
* @param parser a class parser.
* @param builder a dump builder.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 4.7.23
*/
private static void dumpBootstrapMethodsAttribute(final Parser parser, final Builder builder)
throws IOException {
int bootstrapMethodCount = builder.add("num_bootstrap_methods: ", parser.u2());
for (int i = 0; i < bootstrapMethodCount; ++i) {
builder.addCpInfo("bootstrap_method_ref: ", parser.u2());
int bootstrapArgumentCount = builder.add("num_bootstrap_arguments: ", parser.u2());
for (int j = 0; j < bootstrapArgumentCount; ++j) {
builder.addCpInfo("bootstrap_argument: ", parser.u2());
}
}
}
/**
* Parses and dumps a MethodParameters attribute.
*
* @param parser a class parser.
* @param builder a dump builder.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 4.7.24
*/
private static void dumpMethodParametersAttribute(final Parser parser, final Builder builder)
throws IOException {
int parameterCount = builder.add("parameters_count: ", parser.u1());
for (int i = 0; i < parameterCount; ++i) {
builder.addCpInfo("name_index: ", parser.u2());
builder.add("access_flags: ", parser.u2());
}
}
/**
* Parses and dumps a Module attribute.
*
* @param parser a class parser.
* @param builder a dump builder.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 4.7.25
*/
private static void dumpModuleAttribute(final Parser parser, final Builder builder)
throws IOException {
builder.addCpInfo("name: ", parser.u2());
builder.add("access: ", parser.u2());
builder.addCpInfo("version: ", parser.u2());
int requireCount = builder.add("require_count: ", parser.u2());
for (int i = 0; i < requireCount; ++i) {
builder.addCpInfo("name: ", parser.u2());
builder.add("access: ", parser.u2());
builder.addCpInfo("version: ", parser.u2());
}
int exportCount = builder.add("export_count: ", parser.u2());
for (int i = 0; i < exportCount; ++i) {
builder.addCpInfo("name: ", parser.u2());
builder.add("access: ", parser.u2());
int exportToCount = builder.add("export_to_count: ", parser.u2());
for (int j = 0; j < exportToCount; ++j) {
builder.addCpInfo("to: ", parser.u2());
}
}
int openCount = builder.add("open_count: ", parser.u2());
for (int i = 0; i < openCount; ++i) {
builder.addCpInfo("name: ", parser.u2());
builder.add("access: ", parser.u2());
int openToCount = builder.add("open_to_count: ", parser.u2());
for (int j = 0; j < openToCount; ++j) {
builder.addCpInfo("to: ", parser.u2());
}
}
int useCount = builder.add("use_count: ", parser.u2());
for (int i = 0; i < useCount; ++i) {
builder.addCpInfo("use: ", parser.u2());
}
int provideCount = builder.add("provide_count: ", parser.u2());
for (int i = 0; i < provideCount; ++i) {
builder.addCpInfo("provide: ", parser.u2());
int provideWithCount = builder.add("provide_with_count: ", parser.u2());
for (int j = 0; j < provideWithCount; ++j) {
builder.addCpInfo("with: ", parser.u2());
}
}
}
/**
* Parses and dumps a ModulePackages attribute.
*
* @param parser a class parser.
* @param builder a dump builder.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 4.7.26
*/
private static void dumpModulePackagesAttribute(final Parser parser, final Builder builder)
throws IOException {
int packageCount = builder.add("package_count: ", parser.u2());
for (int i = 0; i < packageCount; ++i) {
builder.addCpInfo("package: ", parser.u2());
}
}
/**
* Parses and dumps a ModuleMainClass attribute.
*
* @param parser a class parser.
* @param builder a dump builder.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 4.7.27
*/
private static void dumpModuleMainClassAttribute(final Parser parser, final Builder builder)
throws IOException {
builder.addCpInfo("main_class: ", parser.u2());
}
/**
* Parses and dumps a NestHost attribute.
*
* @param parser a class parser.
* @param builder a dump builder.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 4.7.28
*/
private static void dumpNestHostAttribute(final Parser parser, final Builder builder)
throws IOException {
builder.addCpInfo("host_class: ", parser.u2());
}
/**
* Parses and dumps a NestMembers attribute.
*
* @param parser a class parser.
* @param builder a dump builder.
* @throws IOException if the class can't be parsed.
* @see JVMS
* 4.7.29
*/
private static void dumpNestMembersAttribute(final Parser parser, final Builder builder)
throws IOException {
int numberOfClasses = builder.add("number_of_classes: ", parser.u2());
for (int i = 0; i < numberOfClasses; ++i) {
builder.addCpInfo("class: ", parser.u2());
}
}
/**
* Parses and dumps a StackMap attribute.
*
* @param parser a class parser.
* @param builder a dump builder.
* @throws IOException if the class can't be parsed.
* @see CLDC
*/
private static void dumpStackMapAttribute(final Parser parser, final Builder builder)
throws IOException {
int entryCount = builder.add("number_of_entries: ", parser.u2());
for (int i = 0; i < entryCount; ++i) {
builder.addInsnIndex("offset: ", parser.u2());
int numberOfLocals = builder.add("number_of_locals: ", parser.u2());
for (int j = 0; j < numberOfLocals; ++j) {
dumpVerificationTypeInfo(parser, builder);
}
int numberOfStackItems = builder.add("number_of_stack_items: ", parser.u2());
for (int j = 0; j < numberOfStackItems; ++j) {
dumpVerificationTypeInfo(parser, builder);
}
}
}
/** An abstract constant pool item. */
private abstract static class CpInfo {
/** The dump of this item. */
private String dump;
/** The context to use to get the referenced constant pool items. */
private final ClassContext classContext;
/**
* Constructs a CpInfo for an item without references to other items.
*
* @param dump the dump of this item.
*/
CpInfo(final String dump) {
this.dump = dump;
this.classContext = null;
}
/**
* Constructs a CpInfo for an item with references to other items.
*
* @param classContext a context to lookup constant pool items from their index.
*/
CpInfo(final ClassContext classContext) {
this.classContext = classContext;
}
/**
* Returns the number of entries used by this item in constant_pool.
*
* @return the number of entries used by this item in constant_pool.
*/
int size() {
return 1;
}
/**
* @param a CpInfo subclass.
* @param cpIndex a constant pool entry index.
* @param cpInfoType the expected type of the constant pool entry.
* @return the constant pool item with the given index.
*/
C getCpInfo(final int cpIndex, final Class cpInfoType) {
return classContext.getCpInfo(cpIndex, cpInfoType);
}
/**
* Returns the dump of this item.
*
* @return the dump of this item.
*/
String dump() {
return dump;
}
@Override
public String toString() {
if (dump == null) {
dump = getClass().getSimpleName() + " " + dump();
}
return dump;
}
}
/**
* A CONSTANT_Class_info item.
*
* @see JVMS
* 4.4.1
*/
private static class ConstantClassInfo extends CpInfo {
private final int nameIndex;
/**
* Parses a CONSTANT_Class_info item.
*
* @param parser a class parser.
* @param classContext a context to lookup constant pool items from their index.
* @throws IOException if the class can't be parsed.
*/
ConstantClassInfo(final Parser parser, final ClassContext classContext) throws IOException {
super(classContext);
this.nameIndex = parser.u2();
}
@Override
String dump() {
return getCpInfo(nameIndex, ConstantUtf8Info.class).dump();
}
}
/**
* A CONSTANT_Fieldref_info item.
*
* @see JVMS
* 4.4.2
*/
private static class ConstantFieldRefInfo extends CpInfo {
private final int classIndex;
private final int nameAndTypeIndex;
/**
* Parses a CONSTANT_Fieldref_info item.
*
* @param parser a class parser.
* @param classContext a context to lookup constant pool items from their index.
* @throws IOException if the class can't be parsed.
*/
ConstantFieldRefInfo(final Parser parser, final ClassContext classContext) throws IOException {
super(classContext);
this.classIndex = parser.u2();
this.nameAndTypeIndex = parser.u2();
}
@Override
String dump() {
return getCpInfo(classIndex, ConstantClassInfo.class).dump()
+ "."
+ getCpInfo(nameAndTypeIndex, ConstantNameAndTypeInfo.class).dump();
}
}
/**
* A CONSTANT_Methodref_info item.
*
* @see JVMS
* 4.4.2
*/
private static class ConstantMethodRefInfo extends CpInfo {
private final int classIndex;
private final int nameAndTypeIndex;
/**
* Parses a CONSTANT_Methodref_info item.
*
* @param parser a class parser.
* @param classContext a context to lookup constant pool items from their index.
* @throws IOException if the class can't be parsed.
*/
ConstantMethodRefInfo(final Parser parser, final ClassContext classContext) throws IOException {
super(classContext);
this.classIndex = parser.u2();
this.nameAndTypeIndex = parser.u2();
}
@Override
String dump() {
return getCpInfo(classIndex, ConstantClassInfo.class).dump()
+ "."
+ getCpInfo(nameAndTypeIndex, ConstantNameAndTypeInfo.class).dump();
}
}
/**
* A CONSTANT_InterfaceMethodref_info item.
*
* @see JVMS
* 4.4.2
*/
private static class ConstantInterfaceMethodRefInfo extends CpInfo {
private final int classIndex;
private final int nameAndTypeIndex;
/**
* Parses a CONSTANT_InterfaceMethodref_info item.
*
* @param parser a class parser.
* @param classContext a context to lookup constant pool items from their index.
* @throws IOException if the class can't be parsed.
*/
ConstantInterfaceMethodRefInfo(final Parser parser, final ClassContext classContext)
throws IOException {
super(classContext);
this.classIndex = parser.u2();
this.nameAndTypeIndex = parser.u2();
}
@Override
String dump() {
return getCpInfo(classIndex, ConstantClassInfo.class).dump()
+ "."
+ getCpInfo(nameAndTypeIndex, ConstantNameAndTypeInfo.class).dump();
}
}
/**
* A CONSTANT_String_info item.
*
* @see JVMS
* 4.4.3
*/
private static class ConstantStringInfo extends CpInfo {
final int stringIndex;
/**
* Parses a CONSTANT_String_info item.
*
* @param parser a class parser.
* @param classContext a context to lookup constant pool items from their index.
* @throws IOException if the class can't be parsed.
*/
ConstantStringInfo(final Parser parser, final ClassContext classContext) throws IOException {
super(classContext);
this.stringIndex = parser.u2();
}
@Override
String dump() {
return getCpInfo(stringIndex, ConstantUtf8Info.class).dump();
}
}
/**
* A CONSTANT_Integer_info item.
*
* @see JVMS
* 4.4.4
*/
private static class ConstantIntegerInfo extends CpInfo {
/**
* Parses a CONSTANT_Integer_info item.
*
* @param parser a class parser.
* @throws IOException if the class can't be parsed.
*/
ConstantIntegerInfo(final Parser parser) throws IOException {
super(Integer.toString(parser.u4()));
}
}
/**
* A CONSTANT_Float_info item.
*
* @see JVMS
* 4.4.4
*/
private static class ConstantFloatInfo extends CpInfo {
/**
* Parses a CONSTANT_Float_info item.
*
* @param parser a class parser.
* @throws IOException if the class can't be parsed.
*/
ConstantFloatInfo(final Parser parser) throws IOException {
super(Float.toString(Float.intBitsToFloat(parser.u4())));
}
}
/**
* A CONSTANT_Long_info item.
*
* @see JVMS
* 4.4.5
*/
private static class ConstantLongInfo extends CpInfo {
/**
* Parses a CONSTANT_Long_info item.
*
* @param parser a class parser.
* @throws IOException if the class can't be parsed.
*/
ConstantLongInfo(final Parser parser) throws IOException {
super(Long.toString(parser.s8()));
}
@Override
int size() {
return 2;
}
}
/**
* A CONSTANT_Double_info item.
*
* @see JVMS
* 4.4.5
*/
private static class ConstantDoubleInfo extends CpInfo {
/**
* Parses a CONSTANT_Double_info item.
*
* @param parser a class parser.
* @throws IOException if the class can't be parsed.
*/
ConstantDoubleInfo(final Parser parser) throws IOException {
super(Double.toString(Double.longBitsToDouble(parser.s8())));
}
@Override
int size() {
return 2;
}
}
/**
* A CONSTANT_NameAndType_info item.
*
* @see JVMS
* 4.4.6
*/
private static class ConstantNameAndTypeInfo extends CpInfo {
private final int nameIndex;
private final int descriptorIndex;
/**
* Parses a CONSTANT_NameAndType_info item.
*
* @param parser a class parser.
* @param classContext a context to lookup constant pool items from their index.
* @throws IOException if the class can't be parsed.
*/
ConstantNameAndTypeInfo(final Parser parser, final ClassContext classContext)
throws IOException {
super(classContext);
this.nameIndex = parser.u2();
this.descriptorIndex = parser.u2();
}
@Override
String dump() {
return getCpInfo(nameIndex, ConstantUtf8Info.class).dump()
+ getCpInfo(descriptorIndex, ConstantUtf8Info.class).dump();
}
}
/**
* A CONSTANT_Utf8_info item.
*
* @see JVMS
* 4.4.7
*/
private static class ConstantUtf8Info extends CpInfo {
/**
* Parses a CONSTANT_Utf8_info item.
*
* @param parser a class parser.
* @throws IOException if the class can't be parsed.
*/
ConstantUtf8Info(final Parser parser) throws IOException {
super(parser.utf8());
}
}
/**
* A CONSTANT_MethodHandle_info item.
*
* @see JVMS
* 4.4.8
*/
private static class ConstantMethodHandleInfo extends CpInfo {
private final int referenceKind;
private final int referenceIndex;
/**
* Parses a CONSTANT_MethodHandle_info item.
*
* @param parser a class parser.
* @param classContext a context to lookup constant pool items from their index.
* @throws IOException if the class can't be parsed.
*/
ConstantMethodHandleInfo(final Parser parser, final ClassContext classContext)
throws IOException {
super(classContext);
this.referenceKind = parser.u1();
this.referenceIndex = parser.u2();
}
@Override
String dump() {
return referenceKind + "." + getCpInfo(referenceIndex, CpInfo.class);
}
}
/**
* A CONSTANT_MethodType_info item.
*
* @see JVMS
* 4.4.9
*/
private static class ConstantMethodTypeInfo extends CpInfo {
private final int descriptorIndex;
/**
* Parses a CONSTANT_MethodType_info item.
*
* @param parser a class parser.
* @param classContext a context to lookup constant pool items from their index.
* @throws IOException if the class can't be parsed.
*/
ConstantMethodTypeInfo(final Parser parser, final ClassContext classContext)
throws IOException {
super(classContext);
this.descriptorIndex = parser.u2();
}
@Override
String dump() {
return getCpInfo(descriptorIndex, ConstantUtf8Info.class).dump();
}
}
/**
* A CONSTANT_InvokeDynamic_info item.
*
* @see JVMS
* 4.4.10
*/
private static class ConstantInvokeDynamicInfo extends CpInfo {
private final int bootstrapMethodAttrIndex;
private final int nameAndTypeIndex;
/**
* Parses a CONSTANT_InvokeDynamic_info item.
*
* @param parser a class parser.
* @param classContext a context to lookup constant pool items from their index.
* @throws IOException if the class can't be parsed.
*/
ConstantInvokeDynamicInfo(final Parser parser, final ClassContext classContext)
throws IOException {
super(classContext);
this.bootstrapMethodAttrIndex = parser.u2();
this.nameAndTypeIndex = parser.u2();
}
@Override
String dump() {
return bootstrapMethodAttrIndex
+ "."
+ getCpInfo(nameAndTypeIndex, ConstantNameAndTypeInfo.class).dump();
}
}
/**
* A CONSTANT_Module_info item.
*
* @see JVMS
* 4.4.11
*/
private static class ConstantModuleInfo extends CpInfo {
private final int descriptorIndex;
/**
* Parses a CONSTANT_Module_info item.
*
* @param parser a class parser.
* @param classContext a context to lookup constant pool items from their index.
* @throws IOException if the class can't be parsed.
*/
ConstantModuleInfo(final Parser parser, final ClassContext classContext) throws IOException {
super(classContext);
this.descriptorIndex = parser.u2();
}
@Override
String dump() {
return getCpInfo(descriptorIndex, ConstantUtf8Info.class).dump();
}
}
/**
* A CONSTANT_Package_info item.
*
* @see JVMS
* 4.4.12
*/
private static class ConstantPackageInfo extends CpInfo {
private final int descriptorIndex;
/**
* Parses a CONSTANT_Package_info item.
*
* @param parser a class parser.
* @param classContext a context to lookup constant pool items from their index.
* @throws IOException if the class can't be parsed.
*/
ConstantPackageInfo(final Parser parser, final ClassContext classContext) throws IOException {
super(classContext);
this.descriptorIndex = parser.u2();
}
@Override
String dump() {
return getCpInfo(descriptorIndex, ConstantUtf8Info.class).dump();
}
}
/**
* A CONSTANT_Dynamic_info item.
*
* @see JVMS
* 4.4.13
*/
private static class ConstantDynamicInfo extends CpInfo {
private final int bootstrapMethodAttrIndex;
private final int nameAndTypeIndex;
/**
* Parses a CONSTANT_Dynamic_info item.
*
* @param parser a class parser.
* @param classContext a context to lookup constant pool items from their index.
* @throws IOException if the class can't be parsed.
*/
ConstantDynamicInfo(final Parser parser, final ClassContext classContext) throws IOException {
super(classContext);
this.bootstrapMethodAttrIndex = parser.u2();
this.nameAndTypeIndex = parser.u2();
}
@Override
String dump() {
return bootstrapMethodAttrIndex
+ "."
+ getCpInfo(nameAndTypeIndex, ConstantNameAndTypeInfo.class).dump();
}
}
/**
* The index of a bytecode instruction. This index is computed in {@link #toString}, from the
* bytecode offset of the instruction, after the whole class has been parsed. Indeed, due to
* forward references, the index of an instruction might not be known when its offset is used.
*
* Dumps use instruction indices instead of bytecode offsets in order to abstract away the low
* level byte code instruction representation details (e.g. an ldc vs. an ldc_w).
*/
private static class InstructionIndex {
/** An offset in bytes from the start of the bytecode of a method. */
private final int bytecodeOffset;
/** The context to use to find the index from the bytecode offset. */
private final MethodContext methodContext;
InstructionIndex(final int bytecodeOffset, final MethodContext methodContext) {
this.bytecodeOffset = bytecodeOffset;
this.methodContext = methodContext;
}
int getBytecodeOffset() {
return bytecodeOffset;
}
@Override
public String toString() {
return "<" + methodContext.getInsnIndex(bytecodeOffset) + ">";
}
}
/**
* A simple byte array parser. The method names reflect the type names used in the Java Virtual
* Machine Specification for ease of reference.
*/
private static class Parser {
private final DataInputStream dataInputStream;
Parser(final byte[] data) {
this.dataInputStream = new DataInputStream(new ByteArrayInputStream(data));
}
int u1() throws IOException {
return dataInputStream.readUnsignedByte();
}
int s1() throws IOException {
return dataInputStream.readByte();
}
int u2() throws IOException {
return dataInputStream.readUnsignedShort();
}
int s2() throws IOException {
return dataInputStream.readShort();
}
int u4() throws IOException {
return dataInputStream.readInt();
}
long s8() throws IOException {
long highBytes = dataInputStream.readInt();
long lowBytes = dataInputStream.readInt() & 0xFFFFFFFFL;
return (highBytes << 32) | lowBytes;
}
String utf8() throws IOException {
return dataInputStream.readUTF();
}
byte[] bytes(final int length) throws IOException {
byte[] bytes = new byte[length];
dataInputStream.readFully(bytes);
return bytes;
}
}
/** A context to lookup constant pool items from their index. */
private interface ClassContext {
C getCpInfo(int cpIndex, Class cpInfoType);
}
/** A context to lookup instruction indices from their bytecode offset. */
private interface MethodContext {
int getInsnIndex(int bytecodeOffset);
}
/**
* A helper class to build the dump of a class file. The dump can't be output fully sequentially,
* as the input class is parsed, in particular due to the re-ordering of attributes and
* annotations. Instead, a tree is constructed first, then its nodes are sorted and finally the
* tree is parsed in Depth First Search order to build the dump. This class is the super class of
* the internal nodes of the tree.
*
* Each internal node is a context that can store a mapping between constant pool indices and
* constant pool items and between bytecode offsets and instructions indices. This can be used to
* resolve references to such objects. Contexts inherit from their parent, i.e. if a lookup fails
* in some builder, the lookup continues in the parent, and so on until the root is reached.
*/
private abstract static class AbstractBuilder implements ClassContext, MethodContext {
/** Flag used to distinguish CpInfo keys in {@link #context}. */
private static final int CP_INFO_KEY = 0xF0000000;
/** The parent node of this node. May be null. */
private final AbstractBuilder> parent;
/** The children of this builder. */
final ArrayList children;
/** The map used to implement the Context interfaces. */
private final HashMap context;
AbstractBuilder(final AbstractBuilder> parent) {
this.parent = parent;
this.children = new ArrayList<>();
this.context = new HashMap<>();
}
/**
* Lookup constant pool items from their index.
*
* @param cpIndex a constant pool item index.
* @return the constant pool item at the given index.
*/
CpInfo getCpInfo(final int cpIndex) {
return getCpInfo(cpIndex, CpInfo.class);
}
@Override
public C getCpInfo(final int cpIndex, final Class cpInfoType) {
Object cpInfo = get(CP_INFO_KEY | cpIndex);
if (cpInfo == null) {
throw new ClassFormatException("Invalid constant pool index: " + cpIndex);
} else if (!cpInfoType.isInstance(cpInfo)) {
throw new ClassFormatException(
"Invalid constant pool type: "
+ cpInfo.getClass().getName()
+ " should be "
+ cpInfoType.getName());
}
return cpInfoType.cast(cpInfo);
}
@Override
public int getInsnIndex(final int bytecodeOffset) {
Integer insnIndex = (Integer) get(bytecodeOffset);
if (insnIndex == null) {
throw new ClassFormatException("Invalid bytecode offset: " + bytecodeOffset);
}
return insnIndex;
}
/**
* Registers the CpInfo for the given constant pool index.
*
* @param cpIndex a constant pool item index.
* @param cpInfo a constant pool item.
*/
void putCpInfo(final int cpIndex, final CpInfo cpInfo) {
context.put(CP_INFO_KEY | cpIndex, cpInfo);
}
/**
* Registers the instruction index for the given bytecode offset.
*
* @param bytecodeOffset a bytecode offset.
* @param instructionIndex the index of a bytecode instruction.
*/
void putInsnIndex(final int bytecodeOffset, final int instructionIndex) {
context.put(bytecodeOffset, instructionIndex);
}
/**
* Recursively appends the builder's children to the given string.
*
* @param stringBuilder a string builder.
*/
void build(final StringBuilder stringBuilder) {
for (Object child : children) {
if (child instanceof AbstractBuilder>) {
((AbstractBuilder>) child).build(stringBuilder);
} else {
stringBuilder.append(child);
}
}
}
/**
* Returns the value associated with the given key.
*
* @param key a context key.
* @return the value associated with the given key in this context or, if not found, in the
* parent context (recursively).
*/
private Object get(final int key) {
Object value = context.get(key);
if (value != null) {
return value;
}
return parent == null ? null : parent.get(key);
}
}
/** An {@link AbstractBuilder} with concrete methods to add children. */
private static class Builder extends AbstractBuilder implements Comparable {
/** The name of this builder, for sorting in {@link SortedBuilder}. */
private String name;
Builder(final String name, final AbstractBuilder> parent) {
super(parent);
this.name = name;
}
/**
* Appends name and value to children and returns value.
*
* @param a value type.
* @param name a name.
* @param value a value.
* @return value
*/
T add(final String name, final T value) {
children.add(name);
children.add(value);
children.add("\n");
return value;
}
/**
* Appends name and the instruction index corresponding to bytecodeOffset to children, and
* returns bytecodeOffset.
*
* @param name a name.
* @param bytecodeOffset the offset of a bytecode instruction.
* @return bytecodeOffset.
*/
int addInsnIndex(final String name, final int bytecodeOffset) {
add(name, new InstructionIndex(bytecodeOffset, this));
return bytecodeOffset;
}
/**
* Appends the given arguments to children.
*
* @param insnIndex the index of a bytecode instruction.
* @param opcode a bytecode instruction opcode.
* @param arguments the bytecode instruction arguments.
*/
void addInsn(final int insnIndex, final int opcode, final Object... arguments) {
children.add(insnIndex);
children.add(": ");
children.add(opcode);
for (Object argument : arguments) {
children.add(" ");
children.add(argument);
}
children.add("\n");
}
/**
* Appends name and the CpInfo corresponding to cpIndex to children.
*
* @param name a name.
* @param cpIndex a constant pool item index.
*/
void addCpInfo(final String name, final int cpIndex) {
add(name, cpIndex == 0 ? 0 : getCpInfo(cpIndex));
}
/**
* Appends a new {@link SortedBuilder} to children and returns it.
*
* @return a new {@link SortedBuilder}.
*/
SortedBuilder addSortedBuilder() {
SortedBuilder sortedBuilder = new SortedBuilder(this);
children.add(sortedBuilder);
return sortedBuilder;
}
/** Use the content of this builder, instead of its name, to sort it in a SortedBuilder. */
void sortByContent() {
StringBuilder stringBuilder = new StringBuilder();
for (Object child : children) {
if (child instanceof InstructionIndex) {
// Instruction index might not be known at this point, use bytecodeOffset instead.
stringBuilder.append(((InstructionIndex) child).getBytecodeOffset());
} else {
stringBuilder.append(child.toString());
}
}
name = stringBuilder.toString();
}
@Override
public int compareTo(final Builder builder) {
return name.compareTo(builder.name);
}
@Override
public boolean equals(final Object other) {
return (other instanceof Builder) && name.equals(((Builder) other).name);
}
@Override
public int hashCode() {
return name.hashCode();
}
}
/** An {@link AbstractBuilder} which sorts its children by name before building. */
private static class SortedBuilder extends AbstractBuilder {
SortedBuilder(final Builder parent) {
super(parent);
}
/**
* Appends a new {@link Builder} to children and returns it.
*
* @param name the name of the new builder.
* @return the new builder.
*/
Builder addBuilder(final String name) {
Builder builder = new Builder(name, this);
children.add(builder);
return builder;
}
@Override
void build(final StringBuilder stringBuilder) {
Collections.sort(children);
super.build(stringBuilder);
}
}
/** A simple ClassLoader to test that a class can be loaded in the JVM. */
private static class ByteClassLoader extends ClassLoader {
private final String className;
private final byte[] classContent;
private boolean classLoaded;
ByteClassLoader(final String className, final byte[] classContent) {
this.className = className;
this.classContent = classContent;
}
boolean classLoaded() {
return classLoaded;
}
@Override
protected Class> loadClass(final String name, final boolean resolve)
throws ClassNotFoundException {
if (name.equals(className)) {
classLoaded = true;
return defineClass(className, classContent, 0, classContent.length);
} else {
return super.loadClass(name, resolve);
}
}
}
}