// This product is provided under the terms of EPL (Eclipse Public License) // version 1.0. // // The full license text can be read from: http://www.eclipse.org/org/documents/epl-v10.php package org.dtangler.javaengine.classfileparser; import java.io.ByteArrayInputStream; import java.io.DataInput; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.dtangler.javaengine.types.JavaClass; public class ClassFileParser { private static final String SOURCE_FILE = "SourceFile"; private static final int CONSTANT_UTF8 = 1; private static final int JAVA_MAGIC = 0xCAFEBABE; private static final int CONSTANT_INTEGER = 3; private static final int CONSTANT_FLOAT = 4; private static final int CONSTANT_LONG = 5; private static final int CONSTANT_DOUBLE = 6; private static final int CONSTANT_CLASS = 7; private static final int CONSTANT_STRING = 8; private static final int CONSTANT_FIELD = 9; private static final int CONSTANT_METHOD = 10; private static final int CONSTANT_INTERFACEMETHOD = 11; private static final int CONSTANT_NAMEANDTYPE = 12; private static final char SEPARATOR = ';'; private static final char CLASS_DESCRIPTOR = 'L'; private static final char BRACKET_OPEN = '['; private static final char SLASH = '/'; private static final char DOT = '.'; private static final int ACC_INTERFACE = 0x200; private static final int ACC_ABSTRACT = 0x400; private final Constant DOUBLESLOT = new Constant((byte) 0, null); private Constant[] constantPool; public JavaClass parse(File classFile) { try { return parse(loadFile(classFile)); } catch (IOException e) { throw new RuntimeException("Could not parse file:" + classFile.getName(), e); } } private DataInput loadFile(File file) throws IOException { FileInputStream fileInputStream = null; try { fileInputStream = new FileInputStream(file); byte[] data = new byte[fileInputStream.available()]; fileInputStream.read(data, 0, data.length); return new DataInputStream(new ByteArrayInputStream(data)); } catch (RuntimeException e) { throw e; } finally { if (fileInputStream != null) fileInputStream.close(); } } public JavaClass parse(DataInput in) throws IOException { checkMagic(in); skipVersionInfo(in); constantPool = parseConstantPool(in); boolean isAbstract = parseAccessFlags(in); JavaClass jClass = parseClassName(in); jClass.setAbstract(isAbstract); parseSuperClassName(in, jClass); parseInterfaces(in, jClass); parseFields(in, jClass); parseMethods(in, jClass); parseSourceFile(in, jClass); addClassConstantReferences(jClass); return jClass; } private void checkMagic(DataInput in) throws IOException { if (in.readInt() != JAVA_MAGIC) throw new RuntimeException("Bad Magic"); } private void skipVersionInfo(DataInput in) throws IOException { // skip major and minor in.skipBytes(4); } private Constant[] parseConstantPool(DataInput in) throws IOException { Constant[] pool = new Constant[in.readUnsignedShort()]; for (int i = 1; i < pool.length; i++) { Constant constant = parseNextConstant(in); pool[i] = constant; // 8-byte constants use two constant pool entries if (constant != null && DOUBLESLOT.equals(constant)) i++; } return pool; } private boolean parseAccessFlags(DataInput in) throws IOException { int accessFlags = in.readUnsignedShort(); boolean isAbstract = ((accessFlags & ACC_ABSTRACT) != 0); boolean isInterface = ((accessFlags & ACC_INTERFACE) != 0); return isAbstract || isInterface; } private JavaClass parseClassName(DataInput in) throws IOException { int entryIndex = in.readUnsignedShort(); String className = getClassConstantName(entryIndex); return new JavaClass(className); } private void parseSuperClassName(DataInput in, JavaClass jClass) throws IOException { int entryIndex = in.readUnsignedShort(); String superClassName = getClassConstantName(entryIndex); addDependency(superClassName, jClass); } private void parseInterfaces(DataInput in, JavaClass jClass) throws IOException { int interfacesCount = in.readUnsignedShort(); for (int i = 0; i < interfacesCount; i++) { int entryIndex = in.readUnsignedShort(); String interfaceName = getClassConstantName(entryIndex); addDependency(interfaceName, jClass); } } private void parseFields(DataInput in, JavaClass jClass) throws IOException { int fieldsCount = in.readUnsignedShort(); for (int i = 0; i < fieldsCount; i++) { int descriptorIndex = parseFieldOrMethodInfo(in); String descriptor = toUTF8(descriptorIndex); List<String> types = descriptorToTypes(descriptor); for (int t = 0; t < types.size(); t++) { addDependency(types.get(t), jClass); } } } private void parseMethods(DataInput in, JavaClass jClass) throws IOException { int methodCount = in.readUnsignedShort(); for (int i = 0; i < methodCount; i++) { int descriptorIndex = parseFieldOrMethodInfo(in); String descriptor = toUTF8(descriptorIndex); List<String> types = descriptorToTypes(descriptor); for (int t = 0; t < types.size(); t++) { addDependency(types.get(t), jClass); } } } private Constant parseNextConstant(DataInput in) throws IOException { byte tag = in.readByte(); switch (tag) { case (CONSTANT_UTF8): return new Constant(tag, in.readUTF()); case (CONSTANT_FIELD): case (CONSTANT_METHOD): case (CONSTANT_INTERFACEMETHOD): case (CONSTANT_NAMEANDTYPE): case (CONSTANT_INTEGER): case (CONSTANT_FLOAT): in.skipBytes(4); return null; case (CONSTANT_CLASS): return new Constant(tag, in.readUnsignedShort()); case (CONSTANT_STRING): in.skipBytes(2); return null; case (CONSTANT_LONG): case (CONSTANT_DOUBLE): in.skipBytes(8); return DOUBLESLOT; } throw new IOException("Unknown constant: " + tag); } private int parseFieldOrMethodInfo(DataInput in) throws IOException { // skip accessFlags (unsigned short) and nameIndex (unsigned short) in.skipBytes(4); int descriptorIndex = in.readUnsignedShort(); // skip attributes int attributesCount = in.readUnsignedShort(); for (int a = 0; a < attributesCount; a++) { in.skipBytes(2); in.skipBytes(in.readInt()); } return descriptorIndex; } private void parseSourceFile(DataInput in, JavaClass jClass) throws IOException { int attributesCount = in.readUnsignedShort(); for (int i = 0; i < attributesCount; i++) { int nameIndex = in.readUnsignedShort(); int length = in.readInt(); if (nameIndex != -1 && SOURCE_FILE.equals(toUTF8(nameIndex))) { byte[] b = new byte[length]; in.readFully(b); // Section 4.7.7 of VM Spec - Class File Format int b0 = b[0] < 0 ? b[0] + 256 : b[0]; int b1 = b[1] < 0 ? b[1] + 256 : b[1]; int pe = b0 * 256 + b1; jClass.setSourceFile(toUTF8(pe)); break; } else in.skipBytes(length); } } private Constant getConstantPoolEntry(int entryIndex) throws IOException { if (entryIndex < 0 || entryIndex >= constantPool.length) { throw new IOException("Illegal constant pool index : " + entryIndex); } return constantPool[entryIndex]; } private void addClassConstantReferences(JavaClass jClass) throws IOException { for (int j = 1; j < constantPool.length; j++) { Constant constant = constantPool[j]; if (constant != null && constant.getTag() == CONSTANT_CLASS) { String name = toUTF8(constant.getNameIndex()); addDependency(name, jClass); } } } private String getClassConstantName(int entryIndex) throws IOException { Constant entry = getConstantPoolEntry(entryIndex); return slashesToDots(toUTF8(entry.getNameIndex())); } private String toUTF8(int entryIndex) throws IOException { Constant entry = getConstantPoolEntry(entryIndex); if (entry.getTag() == CONSTANT_UTF8) return (String) entry.getValue(); throw new IOException("Constant pool entry is not a UTF8 type: " + entryIndex); } private String slashesToDots(String s) { return s.replace(SLASH, DOT); } private void addDependency(String s, JavaClass jClass) { if (s.charAt(0) == BRACKET_OPEN) { List<String> types = descriptorToTypes(s); if (types.size() == 0) return; // primitives s = types.get(0); } s = slashesToDots(s); jClass.addDependency(s); } private List<String> descriptorToTypes(String descriptor) { List<String> types = null; int startIndex = 0; int splitIndex = descriptor.indexOf(SEPARATOR, startIndex); while (splitIndex >= 0) { String string = descriptor.substring(startIndex, splitIndex); startIndex = splitIndex + 1; splitIndex = descriptor.indexOf(SEPARATOR, startIndex); int index = string.indexOf(CLASS_DESCRIPTOR); if (index >= 0) { if (types == null) types = new ArrayList(); types.add(string.substring(index + 1)); } } return types != null ? types : Collections.EMPTY_LIST; } class Constant { private final byte tag; private final int nameIndex; private final Object value; Constant(byte tag, Object value) { this.tag = tag; this.nameIndex = -1; this.value = value; } Constant(byte tag, int nameIndex) { this.tag = tag; this.nameIndex = nameIndex; this.value = null; } byte getTag() { return tag; } int getNameIndex() { return nameIndex; } Object getValue() { return value; } } }