/* * This file is part of the X10 project (http://x10-lang.org). * * This file is licensed to You under the Eclipse Public License (EPL); * You may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.opensource.org/licenses/eclipse-1.0.php * * This file was originally derived from the Polyglot extensible compiler framework. * * (C) Copyright 2000-2007 Polyglot project group, Cornell University * (C) Copyright IBM Corporation 2007-2012. */ package polyglot.types.reflect; import java.io.*; import java.lang.reflect.Modifier; import java.util.*; import polyglot.frontend.*; import polyglot.types.*; import polyglot.types.reflect.InnerClasses.Info; import polyglot.util.*; import x10.util.CollectionFactory; /** * ClassFile represents a Java classfile as it is found on * disk. The classfile is modelled according to the Java Virtual Machine * Specification. Methods are provided to access the classfile at a very * low level. * * @see polyglot.types.reflect Attribute * @see polyglot.types.reflect ConstantValue * @see polyglot.types.reflect Field * @see polyglot.types.reflect Method * * @author Nate Nystrom */ public class ClassFile { protected Constant[] constants; // The constant pool protected int modifiers; // This class's modifier bit field protected int thisClass; protected int superClass; protected int[] interfaces; protected Field[] fields; protected Method[] methods; protected Attribute[] attrs; protected InnerClasses innerClasses; protected InnerClasses.Info innerClassInfo; protected Signature signature; protected File classFileSource; protected ExtensionInfo extensionInfo; protected Map<String, JLCInfo> jlcInfoCache = CollectionFactory.newHashMap(); /** * Constructor. This constructor parses the class file from the byte array * * @param code * A byte array containing the class data */ public ClassFile(File classFileSource, byte[] code, ExtensionInfo ext) { this.classFileSource = classFileSource; this.extensionInfo = ext; try { ByteArrayInputStream bin = new ByteArrayInputStream(code); DataInputStream in = new DataInputStream(bin); read(in); in.close(); bin.close(); } catch (IOException e) { throw new InternalCompilerError("I/O exception on ByteArrayInputStream"); } } JLCInfo getJLCInfo(String typeSystemKey) { // Check if already set. JLCInfo jlc = (JLCInfo) jlcInfoCache.get(typeSystemKey); if (jlc != null) { return jlc; } jlc = new JLCInfo(); jlcInfoCache.put(typeSystemKey, jlc); try { int mask = 0; for (int i = 0; i < fields.length; i++) { if (fields[i].name().equals("jlc$SourceLastModified$" + typeSystemKey)) { jlc.sourceLastModified = fields[i].getLong(); mask |= 1; } else if (fields[i].name().equals("jlc$CompilerVersion$" + typeSystemKey)) { jlc.compilerVersion = fields[i].getString(); mask |= 2; } else if (fields[i].name().equals("jlc$ClassType$" + typeSystemKey)) { // there is encoded class type information. StringBuffer encodedClassTypeInfo = new StringBuffer(fields[i].getString()); // check to see if there are more fields. int seeking = 1; boolean found; do { found = false; String suffix = ("$" + seeking); String seekingFieldName = "jlc$ClassType$" + typeSystemKey + suffix; for (int j = 0; j < fields.length; j++) { if (fields[j].name().equals(seekingFieldName)) { encodedClassTypeInfo.append(fields[j].getString()); found = true; seeking++; break; } } } while (found); jlc.encodedClassType = encodedClassTypeInfo.toString(); mask |= 4; } } if (mask != 7) { // Not all the information is there. Reset to default. jlc.sourceLastModified = 0; jlc.compilerVersion = null; jlc.encodedClassType = null; } } catch (SemanticException e) { jlc.sourceLastModified = 0; jlc.compilerVersion = null; jlc.encodedClassType = null; } return jlc; } /** * Get the encoded source modified time. */ public long sourceLastModified(String ts) { JLCInfo jlc = getJLCInfo(ts); return jlc.sourceLastModified; } /** * Get the encoded compiler version used to compile the source. */ public String compilerVersion(String ts) { JLCInfo jlc = getJLCInfo(ts); return jlc.compilerVersion; } /** * Get the encoded class type for the given type system. */ public String encodedClassType(String typeSystemKey) { JLCInfo jlc = getJLCInfo(typeSystemKey); return jlc.encodedClassType; } /** * Read the class file. */ void read(DataInputStream in) throws IOException { // Read in file contents from stream readHeader(in); readConstantPool(in); readAccessFlags(in); readClassInfo(in); readFields(in); readMethods(in); readAttributes(in); } /** * Get the class name at the given constant pool index. */ public String classNameCP(int index) { Constant c = constants[index]; if (c != null && c.tag() == Constant.CLASS) { Integer nameIndex = (Integer) c.value(); if (nameIndex != null) { return className(nameIndex.intValue()); } } return null; } public String className(int idx) { Constant c; c = constants[idx]; if (c.tag() == Constant.UTF8) { String s = (String) c.value(); return s.replace('/', '.'); } return null; } /** * Get the name of the class, including the package name. * * @return * The name of the class. */ public String name() { Constant c = constants[thisClass]; if (c.tag() == Constant.CLASS) { Integer nameIndex = (Integer) c.value(); if (nameIndex != null) { c = constants[nameIndex.intValue()]; if (c.tag() == Constant.UTF8) { return (String) c.value(); } } } throw new ClassFormatError("Couldn't find class name in file"); } /** * Get the source of the class. * * @return * The source of the class. */ public File classFileSource() { return this.classFileSource; } /** * Read a constant from the constant pool. * * @param in * The stream from which to read. * @return * The constant. * @exception IOException * If an error occurs while reading. */ Constant readConstant(DataInputStream in) throws IOException { int tag = in.readUnsignedByte(); Object value; switch (tag) { case Constant.CLASS: case Constant.STRING: value = new Integer(in.readUnsignedShort()); break; case Constant.FIELD_REF: case Constant.METHOD_REF: case Constant.INTERFACE_METHOD_REF: case Constant.NAME_AND_TYPE: value = new int[2]; ((int[]) value)[0] = in.readUnsignedShort(); ((int[]) value)[1] = in.readUnsignedShort(); break; case Constant.INTEGER: value = new Integer(in.readInt()); break; case Constant.FLOAT: value = new Float(in.readFloat()); break; case Constant.LONG: // Longs take up 2 constant pool entries. value = new Long(in.readLong()); break; case Constant.DOUBLE: // Doubles take up 2 constant pool entries. value = new Double(in.readDouble()); break; case Constant.UTF8: value = in.readUTF(); break; default: throw new ClassFormatError("Invalid constant tag: " + tag); } return new Constant(tag, value); } /** * Read the class file header. * * @param in * The stream from which to read. * @exception IOException * If an error occurs while reading. */ void readHeader(DataInputStream in) throws IOException { int magic = in.readInt(); if (magic != 0xCAFEBABE) { throw new ClassFormatError("Bad magic number."); } int major = in.readUnsignedShort(); int minor = in.readUnsignedShort(); } /** * Read the class's constant pool. Constants in the constant pool * are modeled by an array of <tt>reflect.Constant</tt>/ * * @param in * The stream from which to read. * @exception IOException * If an error occurs while reading. * * @see Constant * @see #constants */ void readConstantPool(DataInputStream in) throws IOException { int count = in.readUnsignedShort(); constants = new Constant[count]; // The first constant is reserved for internal use by the JVM. constants[0] = null; // Read the constants. for (int i = 1; i < count; i++) { constants[i] = readConstant(in); switch (constants[i].tag()) { case Constant.LONG: case Constant.DOUBLE: // Longs and doubles take up 2 constant pool entries. constants[++i] = null; break; } } } /** * Read the class's access flags. * * @param in * The stream from which to read. * @exception IOException * If an error occurs while reading. */ void readAccessFlags(DataInputStream in) throws IOException { modifiers = in.readUnsignedShort(); } /** * Read the class's name, superclass, and interfaces. * * @param in * The stream from which to read. * @exception IOException * If an error occurs while reading. */ void readClassInfo(DataInputStream in) throws IOException { int index; thisClass = in.readUnsignedShort(); superClass = in.readUnsignedShort(); int numInterfaces = in.readUnsignedShort(); interfaces = new int[numInterfaces]; for (int i = 0; i < numInterfaces; i++) { interfaces[i] = in.readUnsignedShort(); } } /** * Read the class's fields. * * @param in * The stream from which to read. * @exception IOException * If an error occurs while reading. */ void readFields(DataInputStream in) throws IOException { int numFields = in.readUnsignedShort(); fields = new Field[numFields]; for (int i = 0; i < numFields; i++) { fields[i] = createField(in); } } /** * Read the class's methods. * * @param in * The stream from which to read. * @exception IOException * If an error occurs while reading. */ void readMethods(DataInputStream in) throws IOException { int numMethods = in.readUnsignedShort(); methods = new Method[numMethods]; for (int i = 0; i < numMethods; i++) { methods[i] = createMethod(in); } } /** * Read the class's attributes. Since none of the attributes * are required, just read the length of each attribute and * skip that many bytes. * * @param in * The stream from which to read. * @exception IOException * If an error occurs while reading. */ public void readAttributes(DataInputStream in) throws IOException { int numAttributes = in.readUnsignedShort(); attrs = new Attribute[numAttributes]; for (int i = 0; i < numAttributes; i++) { int nameIndex = in.readUnsignedShort(); int length = in.readInt(); String name = (String) constants[nameIndex].value(); Attribute a = createAttribute(in, name, nameIndex, length); if (a != null) { attrs[i] = a; } else { long n = in.skip(length); if (n != length) { throw new EOFException(); } } } } public Method createMethod(DataInputStream in) throws IOException { Method m = new Method(in, this); m.initialize(); return m; } public Field createField(DataInputStream in) throws IOException { Field f = new Field(in, this); f.initialize(); return f; } public Attribute createAttribute(DataInputStream in, String name, int nameIndex, int length) throws IOException { if (name.equals("InnerClasses")) { innerClasses = new InnerClasses(in, nameIndex, length); Info[] allInnerInfos = innerClasses.getClasses(); for (int i = 0; i < allInnerInfos.length; i++) { if (allInnerInfos[i].classIndex == getThisClass()) { innerClassInfo = allInnerInfos[i]; } } return innerClasses; } if (name.equals("Signature")) { signature = new Signature(this, in, nameIndex, length); return signature; } return null; } public Attribute[] getAttrs() { return attrs; } public Constant[] getConstants() { return constants; } public Field[] getFields() { return fields; } public InnerClasses getInnerClasses() { return innerClasses; } public InnerClasses.Info getInnerClassInfo() { return innerClassInfo; } public int[] getInterfaces() { return interfaces; } public Method[] getMethods() { return methods; } public int getModifiers() { if (innerClassInfo != null) return innerClassInfo.modifiers; return modifiers; } public int getSuperClass() { return superClass; } public int getThisClass() { return thisClass; } public Signature getSignature() { return signature; } public String signature() { if (this.signature != null) { return (String) constants[this.signature.getSignature()].value(); } return ""; } public String toString() { return Modifier.toString(modifiers)+"("+Integer.toHexString(modifiers)+") "+name()+signature(); } }