package me.coley.recaf.parse.bytecode; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class MinimalReader { // Backing content private final byte[] code; private final List poolTypes = new ArrayList<>(); private final List interfaceIndices = new ArrayList<>(); private final Map strings = new HashMap<>(); private final Map redirects = new HashMap<>(); private final int classIndex; private final int superIndex; // Public public final int minor; public final int major; public final int access; public MinimalReader(byte[] code) throws IOException { this.code = code; DataInputStream is = new DataInputStream(new ByteArrayInputStream(code)); if (is.readInt() != 0xCAFEBABE) throw new IOException("Does not start with 0xCAFEBABE"); // Read version information minor = is.readUnsignedShort(); major = is.readUnsignedShort(); // Read the constant pool int poolIndex = 1; int poolLength = is.readUnsignedShort(); while (poolIndex < poolLength) { int tag = is.readUnsignedByte(); ConstantType type = ConstantType.fromTag(tag); if (type == null) throw new IllegalStateException("Unknown tag: " + tag); switch(type) { case UTF8: strings.put(poolIndex, is.readUTF()); break; case STRING: case METHOD_TYPE: case MODULE: case PACKAGE: case CLASS: redirects.put(poolIndex, is.readUnsignedShort()); break; case METHOD_HANDLE: is.skipBytes(3); break; case FIELD: case INT: case FLOAT: case NAME_TYPE: case INVOKEDYNAMIC: case DYNAMIC: case METHOD: case INTERFACE_METHOD: is.skipBytes(4); break; case LONG: case DOUBLE: is.skipBytes(8); break; } poolTypes.add(type); poolIndex++; if (type.isWide()) { poolTypes.add(null); poolIndex++; } } // Read access and index information access = is.readUnsignedShort(); classIndex = is.readUnsignedShort(); superIndex = is.readUnsignedShort(); // Read interfaces int interfaceLength = is.readUnsignedShort(); for(int i = 0; i < interfaceLength; i++) interfaceIndices.add(is.readUnsignedShort()); } /** * @return Class name. */ public String getName() { return strings.get(redirects.get(classIndex)); } /** * @return Class name. */ public String getSuperName() { return strings.get(redirects.get(superIndex)); } /** * Enumeration for handling constant pool entry types. * * @author Matt */ private enum ConstantType { UTF8(1), INT(3), FLOAT(4), LONG(5), DOUBLE(6), CLASS(7), STRING(8), FIELD(9), METHOD(10), INTERFACE_METHOD(11), NAME_TYPE(12), METHOD_HANDLE(15), METHOD_TYPE(16), DYNAMIC(17), INVOKEDYNAMIC(18), MODULE(19), PACKAGE(20); private static Map typeMap; private final int tag; ConstantType(int tag) { this.tag = tag; register(this); } private static ConstantType fromTag(int tag) { return typeMap.get(tag); } private void register(ConstantType type) { if (typeMap == null) { typeMap = new HashMap<>(); } typeMap.put(tag, type); } private boolean isWide() { return this == LONG || this == DOUBLE; } } }