package jdepend.framework;
import java.io.*;
import java.util.*;
/**
* The <code>ClassFileParser</code> class is responsible for
* parsing a Java class file to create a <code>JavaClass</code>
* instance.
*
* @author <b>Mike Clark</b>
* @author Clarkware Consulting, Inc.
*/
public class ClassFileParser extends AbstractParser {
public static final int JAVA_MAGIC = 0xCAFEBABE;
public static final int CONSTANT_UTF8 = 1;
public static final int CONSTANT_UNICODE = 2;
public static final int CONSTANT_INTEGER = 3;
public static final int CONSTANT_FLOAT = 4;
public static final int CONSTANT_LONG = 5;
public static final int CONSTANT_DOUBLE = 6;
public static final int CONSTANT_CLASS = 7;
public static final int CONSTANT_STRING = 8;
public static final int CONSTANT_FIELD = 9;
public static final int CONSTANT_METHOD = 10;
public static final int CONSTANT_INTERFACEMETHOD = 11;
public static final int CONSTANT_NAMEANDTYPE = 12;
public static final char CLASS_DESCRIPTOR = 'L';
public static final int ACC_INTERFACE = 0x200;
public static final int ACC_ABSTRACT = 0x400;
private String fileName;
private String className;
private String superClassName;
private String interfaceNames[];
private boolean isAbstract;
private JavaClass jClass;
private Constant[] constantPool;
private FieldOrMethodInfo[] fields;
private FieldOrMethodInfo[] methods;
private AttributeInfo[] attributes;
private DataInputStream in;
public ClassFileParser() {
this(new PackageFilter());
}
public ClassFileParser(PackageFilter filter) {
super(filter);
reset();
}
private void reset() {
className = null;
superClassName = null;
interfaceNames = new String[0];
isAbstract = false;
jClass = null;
constantPool = new Constant[1];
fields = new FieldOrMethodInfo[0];
methods = new FieldOrMethodInfo[0];
attributes = new AttributeInfo[0];
}
/**
* Registered parser listeners are informed that the resulting
* <code>JavaClass</code> was parsed.
*/
public JavaClass parse(File classFile) throws IOException {
this.fileName = classFile.getCanonicalPath();
debug("\nParsing " + fileName + "...");
FileInputStream in = null;
try {
in = new FileInputStream(classFile);
return parse(in);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
}
public JavaClass parse(InputStream is) throws IOException {
reset();
jClass = new JavaClass("Unknown");
in = new DataInputStream(is);
int magic = parseMagic();
int minorVersion = parseMinorVersion();
int majorVersion = parseMajorVersion();
constantPool = parseConstantPool();
parseAccessFlags();
className = parseClassName();
superClassName = parseSuperClassName();
interfaceNames = parseInterfaces();
fields = parseFields();
methods = parseMethods();
parseAttributes();
addClassConstantReferences();
onParsedJavaClass(jClass);
return jClass;
}
private int parseMagic() throws IOException {
int magic = in.readInt();
if (magic != JAVA_MAGIC) {
throw new IOException("Invalid class file: " + fileName);
}
return magic;
}
private int parseMinorVersion() throws IOException {
return in.readUnsignedShort();
}
private int parseMajorVersion() throws IOException {
return in.readUnsignedShort();
}
private Constant[] parseConstantPool() throws IOException {
int constantPoolSize = in.readUnsignedShort();
Constant[] pool = new Constant[constantPoolSize];
for (int i = 1; i < constantPoolSize; i++) {
Constant constant = parseNextConstant();
pool[i] = constant;
//
// 8-byte constants use two constant pool entries
//
if (constant.getTag() == CONSTANT_DOUBLE
|| constant.getTag() == CONSTANT_LONG) {
i++;
}
}
return pool;
}
private void parseAccessFlags() throws IOException {
int accessFlags = in.readUnsignedShort();
boolean isAbstract = ((accessFlags & ACC_ABSTRACT) != 0);
boolean isInterface = ((accessFlags & ACC_INTERFACE) != 0);
this.isAbstract = isAbstract || isInterface;
jClass.isAbstract(this.isAbstract);
debug("Parser: abstract = " + this.isAbstract);
}
private String parseClassName() throws IOException {
int entryIndex = in.readUnsignedShort();
String className = getClassConstantName(entryIndex);
jClass.setName(className);
jClass.setPackageName(getPackageName(className));
debug("Parser: class name = " + className);
debug("Parser: package name = " + getPackageName(className));
return className;
}
private String parseSuperClassName() throws IOException {
int entryIndex = in.readUnsignedShort();
String superClassName = getClassConstantName(entryIndex);
addImport(getPackageName(superClassName));
debug("Parser: super class name = " + superClassName);
return superClassName;
}
private String[] parseInterfaces() throws IOException {
int interfacesCount = in.readUnsignedShort();
String[] interfaceNames = new String[interfacesCount];
for (int i = 0; i < interfacesCount; i++) {
int entryIndex = in.readUnsignedShort();
interfaceNames[i] = getClassConstantName(entryIndex);
addImport(getPackageName(interfaceNames[i]));
debug("Parser: interface = " + interfaceNames[i]);
}
return interfaceNames;
}
private FieldOrMethodInfo[] parseFields() throws IOException {
int fieldsCount = in.readUnsignedShort();
FieldOrMethodInfo[] fields = new FieldOrMethodInfo[fieldsCount];
for (int i = 0; i < fieldsCount; i++) {
fields[i] = parseFieldOrMethodInfo();
String descriptor = toUTF8(fields[i].getDescriptorIndex());
debug("Parser: field descriptor = " + descriptor);
String[] types = descriptorToTypes(descriptor);
for (int t = 0; t < types.length; t++) {
addImport(getPackageName(types[t]));
debug("Parser: field type = " + types[t]);
}
}
return fields;
}
private FieldOrMethodInfo[] parseMethods() throws IOException {
int methodsCount = in.readUnsignedShort();
FieldOrMethodInfo[] methods = new FieldOrMethodInfo[methodsCount];
for (int i = 0; i < methodsCount; i++) {
methods[i] = parseFieldOrMethodInfo();
String descriptor = toUTF8(methods[i].getDescriptorIndex());
debug("Parser: method descriptor = " + descriptor);
String[] types = descriptorToTypes(descriptor);
for (int t = 0; t < types.length; t++) {
if (types[t].length() > 0) {
addImport(getPackageName(types[t]));
debug("Parser: method type = " + types[t]);
}
}
}
return methods;
}
private Constant parseNextConstant() throws IOException {
Constant result;
byte tag = in.readByte();
switch (tag) {
case (ClassFileParser.CONSTANT_CLASS):
case (ClassFileParser.CONSTANT_STRING):
result = new Constant(tag, in.readUnsignedShort());
break;
case (ClassFileParser.CONSTANT_FIELD):
case (ClassFileParser.CONSTANT_METHOD):
case (ClassFileParser.CONSTANT_INTERFACEMETHOD):
case (ClassFileParser.CONSTANT_NAMEANDTYPE):
result = new Constant(tag, in.readUnsignedShort(), in
.readUnsignedShort());
break;
case (ClassFileParser.CONSTANT_INTEGER):
result = new Constant(tag, new Integer(in.readInt()));
break;
case (ClassFileParser.CONSTANT_FLOAT):
result = new Constant(tag, new Float(in.readFloat()));
break;
case (ClassFileParser.CONSTANT_LONG):
result = new Constant(tag, new Long(in.readLong()));
break;
case (ClassFileParser.CONSTANT_DOUBLE):
result = new Constant(tag, new Double(in.readDouble()));
break;
case (ClassFileParser.CONSTANT_UTF8):
result = new Constant(tag, in.readUTF());
break;
default:
throw new IOException("Unknown constant: " + tag);
}
return result;
}
private FieldOrMethodInfo parseFieldOrMethodInfo() throws IOException {
FieldOrMethodInfo result = new FieldOrMethodInfo(
in.readUnsignedShort(), in.readUnsignedShort(), in
.readUnsignedShort());
int attributesCount = in.readUnsignedShort();
for (int a = 0; a < attributesCount; a++) {
parseAttribute();
}
return result;
}
private void parseAttributes() throws IOException {
int attributesCount = in.readUnsignedShort();
attributes = new AttributeInfo[attributesCount];
for (int i = 0; i < attributesCount; i++) {
attributes[i] = parseAttribute();
// Section 4.7.7 of VM Spec - Class File Format
if (attributes[i].getName() != null) {
if (attributes[i].getName().equals("SourceFile")) {
byte[] b = attributes[i].getValue();
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;
String descriptor = toUTF8(pe);
jClass.setSourceFile(descriptor);
}
}
}
}
private AttributeInfo parseAttribute() throws IOException {
AttributeInfo result = new AttributeInfo();
int nameIndex = in.readUnsignedShort();
if (nameIndex != -1) {
result.setName(toUTF8(nameIndex));
}
int attributeLength = in.readInt();
byte[] value = new byte[attributeLength];
for (int b = 0; b < attributeLength; b++) {
value[b] = in.readByte();
}
result.setValue(value);
return result;
}
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() throws IOException {
for (int j = 1; j < constantPool.length; j++) {
if (constantPool[j].getTag() == CONSTANT_CLASS) {
String name = toUTF8(constantPool[j].getNameIndex());
addImport(getPackageName(name));
debug("Parser: class type = " + slashesToDots(name));
}
if (constantPool[j].getTag() == CONSTANT_DOUBLE
|| constantPool[j].getTag() == CONSTANT_LONG) {
j++;
}
}
}
private String getClassConstantName(int entryIndex) throws IOException {
Constant entry = getConstantPoolEntry(entryIndex);
if (entry == null) {
return "";
}
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 void addImport(String importPackage) {
if ((importPackage != null) && (getFilter().accept(importPackage))) {
jClass.addImportedPackage(new JavaPackage(importPackage));
}
}
private String slashesToDots(String s) {
return s.replace('/', '.');
}
private String getPackageName(String s) {
if ((s.length() > 0) && (s.charAt(0) == '[')) {
String types[] = descriptorToTypes(s);
if (types.length == 0) {
return null; // primitives
}
s = types[0];
}
s = slashesToDots(s);
int index = s.lastIndexOf(".");
if (index > 0) {
return s.substring(0, index);
}
return "Default";
}
private String[] descriptorToTypes(String descriptor) {
int typesCount = 0;
for (int index = 0; index < descriptor.length(); index++) {
if (descriptor.charAt(index) == ';') {
typesCount++;
}
}
String types[] = new String[typesCount];
int typeIndex = 0;
for (int index = 0; index < descriptor.length(); index++) {
int startIndex = descriptor.indexOf(CLASS_DESCRIPTOR, index);
if (startIndex < 0) {
break;
}
index = descriptor.indexOf(';', startIndex + 1);
types[typeIndex++] = descriptor.substring(startIndex + 1, index);
}
return types;
}
class Constant {
private byte _tag;
private int _nameIndex;
private int _typeIndex;
private Object _value;
Constant(byte tag, int nameIndex) {
this(tag, nameIndex, -1);
}
Constant(byte tag, Object value) {
this(tag, -1, -1);
_value = value;
}
Constant(byte tag, int nameIndex, int typeIndex) {
_tag = tag;
_nameIndex = nameIndex;
_typeIndex = typeIndex;
_value = null;
}
byte getTag() {
return _tag;
}
int getNameIndex() {
return _nameIndex;
}
int getTypeIndex() {
return _typeIndex;
}
Object getValue() {
return _value;
}
public String toString() {
StringBuffer s = new StringBuffer("");
s.append("tag: " + getTag());
if (getNameIndex() > -1) {
s.append(" nameIndex: " + getNameIndex());
}
if (getTypeIndex() > -1) {
s.append(" typeIndex: " + getTypeIndex());
}
if (getValue() != null) {
s.append(" value: " + getValue());
}
return s.toString();
}
}
class FieldOrMethodInfo {
private int _accessFlags;
private int _nameIndex;
private int _descriptorIndex;
FieldOrMethodInfo(int accessFlags, int nameIndex, int descriptorIndex) {
_accessFlags = accessFlags;
_nameIndex = nameIndex;
_descriptorIndex = descriptorIndex;
}
int accessFlags() {
return _accessFlags;
}
int getNameIndex() {
return _nameIndex;
}
int getDescriptorIndex() {
return _descriptorIndex;
}
public String toString() {
StringBuffer s = new StringBuffer("");
try {
s.append("\n name (#" + getNameIndex() + ") = "
+ toUTF8(getNameIndex()));
s.append("\n signature (#" + getDescriptorIndex() + ") = "
+ toUTF8(getDescriptorIndex()));
String[] types = descriptorToTypes(toUTF8(getDescriptorIndex()));
for (int t = 0; t < types.length; t++) {
s.append("\n type = " + types[t]);
}
} catch (Exception e) {
e.printStackTrace();
}
return s.toString();
}
}
class AttributeInfo {
private String name;
private byte[] value;
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setValue(byte[] value) {
this.value = value;
}
public byte[] getValue() {
return this.value;
}
}
/**
* Returns a string representation of this object.
*
* @return String representation.
*/
public String toString() {
StringBuffer s = new StringBuffer();
try {
s.append("\n" + className + ":\n");
s.append("\nConstants:\n");
for (int i = 1; i < constantPool.length; i++) {
Constant entry = getConstantPoolEntry(i);
s.append(" " + i + ". " + entry.toString() + "\n");
if (entry.getTag() == CONSTANT_DOUBLE
|| entry.getTag() == CONSTANT_LONG) {
i++;
}
}
s.append("\nClass Name: " + className + "\n");
s.append("Super Name: " + superClassName + "\n\n");
s.append(interfaceNames.length + " interfaces\n");
for (int i = 0; i < interfaceNames.length; i++) {
s.append(" " + interfaceNames[i] + "\n");
}
s.append("\n" + fields.length + " fields\n");
for (int i = 0; i < fields.length; i++) {
s.append(fields[i].toString() + "\n");
}
s.append("\n" + methods.length + " methods\n");
for (int i = 0; i < methods.length; i++) {
s.append(methods[i].toString() + "\n");
}
s.append("\nDependencies:\n");
Iterator imports = jClass.getImportedPackages().iterator();
while (imports.hasNext()) {
JavaPackage jPackage = (JavaPackage) imports.next();
s.append(" " + jPackage.getName() + "\n");
}
} catch (Exception e) {
e.printStackTrace();
}
return s.toString();
}
/**
* Test main.
*/
public static void main(String args[]) {
try {
ClassFileParser.DEBUG = true;
if (args.length <= 0) {
System.err.println("usage: ClassFileParser <class-file>");
System.exit(0);
}
ClassFileParser parser = new ClassFileParser();
parser.parse(new File(args[0]));
System.err.println(parser.toString());
} catch (Exception e) {
System.err.println(e.getMessage());
}
}
}