/* FJBG -- Fast Java Bytecode Generator * Copyright 2002-2013 LAMP/EPFL * @author Michel Schinz */ package ch.epfl.lamp.fjbg; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.util.HashMap; /** * Constant pool, holding constants for a Java class file. * * @author Michel Schinz * @version 2.0 */ public class JConstantPool { protected boolean frozen = false; protected HashMap/**/ entryToIndex = new HashMap(); protected Entry[] indexToEntry; protected int currIndex; public static final short CONSTANT_Utf8 = 1; public static final short CONSTANT_Integer = 3; public static final short CONSTANT_Float = 4; public static final short CONSTANT_Long = 5; public static final short CONSTANT_Double = 6; public static final short CONSTANT_Class = 7; public static final short CONSTANT_String = 8; public static final short CONSTANT_Fieldref = 9; public static final short CONSTANT_Methodref = 10; public static final short CONSTANT_InterfaceMethodref = 11; public static final short CONSTANT_NameAndType = 12; protected JConstantPool(FJBGContext context) { indexToEntry = new Entry[8]; currIndex = 1; } protected JConstantPool(FJBGContext context, DataInputStream stream) throws IOException { int count = stream.readShort(); indexToEntry = new EntryIndex[count]; currIndex = 1; while (currIndex < count) { EntryIndex e; int tag = stream.readByte(); switch (tag) { case CONSTANT_Utf8: e = new Utf8Entry(stream); // no duplicates entryToIndex.put(e, new Integer(currIndex)); break; case CONSTANT_Integer: e = new IntegerEntry(stream); break; case CONSTANT_Float: e = new FloatEntry(stream); break; case CONSTANT_Long: e = new LongEntry(stream); break; case CONSTANT_Double: e = new DoubleEntry(stream); break; case CONSTANT_Class: e = new DescriptorEntryIndex(stream); break; case CONSTANT_String: e = new StringEntryIndex(stream); break; case CONSTANT_Fieldref: case CONSTANT_Methodref: case CONSTANT_InterfaceMethodref: e = new FieldOrMethodRefEntryIndex(tag, stream); break; case CONSTANT_NameAndType: e = new NameAndTypeEntryIndex(stream); break; default: throw new IllegalArgumentException("unknown entry in pool: " + tag); } indexToEntry[currIndex] = e; currIndex += e.getSize(); } } public void freeze() { frozen = true; } /** * Returns a string representing the type of an entry * knowing its tag * @param tag The tag representing the type of the * constant pool entry */ public String getEntryType(int tag) { switch (tag) { case CONSTANT_Utf8 : return "Utf8"; case CONSTANT_Integer : return "Integer"; case CONSTANT_Float : return "Float"; case CONSTANT_Long : return "Long"; case CONSTANT_Double : return "Double"; case CONSTANT_Class : return "Class"; case CONSTANT_String : return "String"; case CONSTANT_Fieldref : return "Field"; case CONSTANT_Methodref : return "Method"; case CONSTANT_InterfaceMethodref : return "InterfaceMethod"; case CONSTANT_NameAndType : return "NameAndType"; default : throw new Error("invalid constant pool tag : " + tag); } } public int addClass(String className) { return addDescriptor(className.replace('.', '/')); } public int addDescriptor(JReferenceType type) { return addDescriptor(type.getDescriptor()); } protected int addDescriptor(String name) { return addEntry(new DescriptorEntryValue(name)); } public int addClassMethodRef(String className, String methodName, String signature) { return addMethodRef(true, className, methodName, signature); } public int addInterfaceMethodRef(String className, String methodName, String signature) { return addMethodRef(false, className, methodName, signature); } public int addMethodRef(boolean isClass, String className, String methodName, String signature) { return addEntry(new FieldOrMethodRefEntryValue(isClass ? CONSTANT_Methodref : CONSTANT_InterfaceMethodref, className, methodName, signature)); } public int addFieldRef(String className, String fieldName, String signature) { return addEntry(new FieldOrMethodRefEntryValue(CONSTANT_Fieldref, className, fieldName, signature)); } public int addInteger(int value) { return addEntry(new IntegerEntry(value)); } public int addFloat(float value) { return addEntry(new FloatEntry(value)); } public int addLong(long value) { return addEntry(new LongEntry(value)); } public int addDouble(double value) { return addEntry(new DoubleEntry(value)); } public int addString(String value) { return addEntry(new StringEntryValue(value)); } public int addNameAndType(String name, String descriptor) { return addEntry(new NameAndTypeEntryValue(name, descriptor)); } public int addUtf8(String value) { return addEntry(new Utf8Entry(value)); } public int addUtf8(byte[] value) { return addEntry(new Utf8Entry(value)); } protected int addEntry(EntryValue e) { assert !frozen; Integer idx = (Integer)entryToIndex.get(e); if (idx != null) return idx.intValue(); e.addChildren(); int index = currIndex; currIndex += e.getSize(); entryToIndex.put(e, new Integer(index)); if (index >= indexToEntry.length) { Entry[] newI2E = new Entry[indexToEntry.length * 2]; System.arraycopy(indexToEntry, 0, newI2E, 0, indexToEntry.length); indexToEntry = newI2E; } indexToEntry[index] = e; return index; } /// Lookup methods ////////////////////////////////////////////////////////////////////// public Entry lookupEntry(int index) { assert index > 0 && index < currIndex : "invalid index: " + index; assert indexToEntry[index] != null : "invalid index (null contents): " + index; return indexToEntry[index]; } public String lookupClass(int index) { DescriptorEntry entry = (DescriptorEntry)lookupEntry(index); return entry.getValue(); } public String lookupNameAndType(int index) { NameAndTypeEntry entry = (NameAndTypeEntry)lookupEntry(index); return entry.getName()+":"+entry.getDescriptor(); } public String lookupUtf8(int index) { Utf8Entry entry = (Utf8Entry)lookupEntry(index); return entry.getValue(); } /// Output ////////////////////////////////////////////////////////////////////// public void writeTo(DataOutputStream stream) throws IOException { if (! frozen) freeze(); stream.writeShort(currIndex); for (int i = 0; i < currIndex; ++i) { Entry entry = indexToEntry[i]; if (entry != null) { stream.writeByte(entry.getTag()); entry.writeContentsTo(stream); } } } // Follows javap output format for constant pool. /*@Override*/ public String toString() { StringBuffer buf = new StringBuffer(" Constant pool:"); for (int i = 0; i < currIndex; ++i) { Entry entry = indexToEntry[i]; if (entry != null) { if (i > 0) buf.append("\n"); buf.append("const #"); buf.append(i); buf.append(" = "); buf.append(entry); } } buf.append("\n"); return buf.toString(); } /// Classes for the various kinds of entries ////////////////////////////////////////////////////////////////////// public interface Entry { public int getTag(); int getSize(); void writeContentsTo(DataOutputStream stream) throws IOException; String toComment(String ownerClassName); } protected interface EntryValue extends Entry { abstract void addChildren(); } protected interface EntryIndex extends Entry { abstract void fetchChildren(); } abstract protected class ChildlessEntry implements EntryValue, EntryIndex { public void addChildren() {} public void fetchChildren() {} } public class IntegerEntry extends ChildlessEntry implements Entry { private final int value; public IntegerEntry(int value) { this.value = value; } public IntegerEntry(DataInputStream stream) throws IOException { this(stream.readInt()); } public int hashCode() { return value; } public boolean equals(Object o) { return o instanceof IntegerEntry && ((IntegerEntry)o).value == value; } public int getTag() { return CONSTANT_Integer; } public int getValue() { return value; } public int getSize() { return 1; } public void writeContentsTo(DataOutputStream stream) throws IOException { stream.writeInt(value); } /*@Override*/ public String toString() { StringBuffer buf = new StringBuffer("int\t"); buf.append(getValue()); buf.append(";"); return buf.toString(); } public String toComment(String ownerClassname) { return "//int "+getValue(); } } public class FloatEntry extends ChildlessEntry implements Entry { private final float value; public FloatEntry(float value) { this.value = value; } public FloatEntry(DataInputStream stream) throws IOException { this(stream.readFloat()); } public int hashCode() { return (int)value; } public boolean equals(Object o) { return o instanceof FloatEntry && ((FloatEntry)o).value == value; } public int getTag() { return CONSTANT_Float; } public float getValue() { return value; } public int getSize() { return 1; } public void writeContentsTo(DataOutputStream stream) throws IOException { stream.writeFloat(value); } /*@Override*/ public String toString() { StringBuffer buf = new StringBuffer("float\t"); buf.append(getValue()); buf.append("f"); return buf.toString(); } public String toComment(String ownerClassname) { return "//float "+getValue()+"f"; } } public class LongEntry extends ChildlessEntry implements Entry { private final long value; public LongEntry(long value) { this.value = value; } public LongEntry(DataInputStream stream) throws IOException { this(stream.readLong()); } public int hashCode() { return (int)value; } public boolean equals(Object o) { return o instanceof LongEntry && ((LongEntry)o).value == value; } public int getTag() { return CONSTANT_Long; } public long getValue() { return value; } public int getSize() { return 2; } public void writeContentsTo(DataOutputStream stream) throws IOException { stream.writeLong(value); } /*@Override*/ public String toString() { StringBuffer buf = new StringBuffer("long\t"); buf.append(getValue()); buf.append("l;"); return buf.toString(); } public String toComment(String ownerClassname) { return "//long "+getValue()+"l"; } } public class DoubleEntry extends ChildlessEntry implements Entry { private final double value; public DoubleEntry(double value) { this.value = value; } public DoubleEntry(DataInputStream stream) throws IOException { this(stream.readDouble()); } public int hashCode() { return (int)value; } public boolean equals(Object o) { return o instanceof DoubleEntry && ((DoubleEntry)o).value == value; } public int getTag() { return CONSTANT_Double; } public double getValue() { return value; } public int getSize() { return 2; } public void writeContentsTo(DataOutputStream stream) throws IOException { stream.writeDouble(value); } /*@Override*/ public String toString() { StringBuffer buf = new StringBuffer("double\t"); buf.append(getValue()); return buf.toString(); } public String toComment(String ownerClassname) { return "//double "+getValue(); } } public class Utf8Entry extends ChildlessEntry implements Entry { private final String value; private final byte[] bytes; public Utf8Entry(String value) { this.value = value.intern(); this.bytes = null; } public Utf8Entry(DataInputStream stream) throws IOException { this(stream.readUTF()); } public Utf8Entry(byte[] bytes) { this.bytes = bytes; this.value = null; } public int hashCode() { if (bytes != null) return bytes.hashCode(); return value.hashCode(); } public boolean equals(Object o) { boolean isEqual = o instanceof Utf8Entry; if (bytes != null) { isEqual = isEqual && ((Utf8Entry)o).bytes == bytes; } else { isEqual = isEqual && ((Utf8Entry)o).value == value; } return isEqual; } public int getTag() { return CONSTANT_Utf8; } public String getValue() { return value; } public byte[] getBytes() { return bytes; } public int getSize() { return 1; } public void writeContentsTo(DataOutputStream stream) throws IOException { if (bytes != null) { if (bytes.length > 65535) { throw new IOException("String literal of length " + bytes.length + " does not fit in Classfile"); } stream.writeShort(bytes.length); stream.write(bytes); } else stream.writeUTF(value); } // Follows javap output format for Utf8 pool entries. public String toString() { return "Asciz\t"+escaped(getValue())+";"; } public String toComment(String ownerClassname) { return "//Asciz "+escaped(getValue()); } private String escaped(String s) { return s.replace("\n", "\\n"); } } abstract public class StringEntry implements Entry { protected String value; protected int valueIndex; public int hashCode() { assert value != null; return value.hashCode(); } public boolean equals(Object o) { return o instanceof StringEntry && ((StringEntry)o).value == value; } public int getTag() { return CONSTANT_String; } public String getValue() { return value; } public int getSize() { return 1; } public void writeContentsTo(DataOutputStream stream) throws IOException { stream.writeShort(valueIndex); } // Follows javap output format for String pool entries. public String toString() { return "String\t#"+valueIndex+";\t// "+escaped(getValue()); } public String toComment(String ownerClassname) { return "//String "+escaped(getValue()); } private String escaped(String s) { return s.replace("\n", "\\n"); } } public class StringEntryValue extends StringEntry implements EntryValue { public StringEntryValue(String value) { this.value = value.intern(); } public void addChildren() { valueIndex = addUtf8(value); } } public class StringEntryIndex extends StringEntry implements EntryIndex { public StringEntryIndex(int valueIndex) { this.valueIndex = valueIndex; } public StringEntryIndex(DataInputStream stream) throws IOException { this(stream.readShort()); } public String getValue() { if (value == null) fetchChildren(); return super.getValue(); } public void fetchChildren() { value = lookupUtf8(valueIndex); } } abstract public class DescriptorEntry implements Entry { protected String name; protected int nameIndex; public int hashCode() { assert name != null; return name.hashCode(); } public boolean equals(Object o) { return o instanceof DescriptorEntry && ((DescriptorEntry)o).name == name; } public int getTag() { return CONSTANT_Class; } public String getValue() { return name; } public int getSize() { return 1; } public void writeContentsTo(DataOutputStream stream) throws IOException { stream.writeShort(nameIndex); } // Follows javap output format for class pool entries. public String toString() { StringBuffer buf = new StringBuffer("class\t#"); buf.append(nameIndex); buf.append(";\t// "); buf.append(getClassName()); return buf.toString(); } public String toComment(String ownerClassname) { return "//class "+getClassName(); } private String getClassName() { StringBuffer buf = new StringBuffer(); String value = getValue(); if (value.startsWith("[")) buf.append("\""); buf.append(value); if (value.startsWith("[")) buf.append("\""); return buf.toString(); } } protected class DescriptorEntryValue extends DescriptorEntry implements EntryValue { public DescriptorEntryValue(String name) { this.name = name.intern(); } public void addChildren() { nameIndex = addUtf8(name); } } protected class DescriptorEntryIndex extends DescriptorEntry implements EntryIndex { public DescriptorEntryIndex(int nameIndex) { this.nameIndex = nameIndex; } public DescriptorEntryIndex(DataInputStream stream) throws IOException { this(stream.readShort()); } public String getValue() { if (name == null) fetchChildren(); return super.getValue(); } public void fetchChildren() { name = lookupUtf8(nameIndex); } } abstract public class FieldOrMethodRefEntry implements Entry { private final int tag; protected String className, thingName, signature; protected int classIndex, nameAndTypeIndex; public FieldOrMethodRefEntry(int tag) { assert tag == CONSTANT_Fieldref || tag == CONSTANT_Methodref || tag == CONSTANT_InterfaceMethodref; this.tag = tag; } public int hashCode() { return tag + className.hashCode() + thingName.hashCode() + signature.hashCode(); } public boolean equals(Object o) { return o instanceof FieldOrMethodRefEntry && ((FieldOrMethodRefEntry)o).tag == tag && ((FieldOrMethodRefEntry)o).className == className && ((FieldOrMethodRefEntry)o).thingName == thingName && ((FieldOrMethodRefEntry)o).signature == signature; } public int getTag() { return tag; } public String getClassName() { return className; } public String getFieldOrMethodName() { return thingName; } public String getSignature() { return signature; } public int getSize() { return 1; } public void writeContentsTo(DataOutputStream stream) throws IOException { stream.writeShort(classIndex); stream.writeShort(nameAndTypeIndex); } // Follows javap output format for field/method pool entries. public String toString() { return getEntryType(tag)+"\t#"+classIndex+".#"+nameAndTypeIndex+ ";\t// "+getName("")+":"+signature; } public String toComment(String ownerClassName) { return "//"+getEntryType(tag)+" "+getName(ownerClassName)+":"+signature; } private String getName(String ownerClassName) { String name = getFieldOrMethodName(); if (JMethod.INSTANCE_CONSTRUCTOR_NAME.equals(name)) name = "\""+name+"\""; if (!getClassName().equals(ownerClassName)) name = getClassName()+"."+name; return name; } } protected class FieldOrMethodRefEntryValue extends FieldOrMethodRefEntry implements EntryValue { public FieldOrMethodRefEntryValue(int tag, String className, String thingName, String signature) { super(tag); this.className = className.intern(); this.thingName = thingName.intern(); this.signature = signature.intern(); } public void addChildren() { classIndex = addClass(className); nameAndTypeIndex = addNameAndType(thingName, signature); } } protected class FieldOrMethodRefEntryIndex extends FieldOrMethodRefEntry implements EntryIndex { public FieldOrMethodRefEntryIndex(int tag, int classIndex, int nameAndTypeIndex) { super(tag); this.classIndex = classIndex; this.nameAndTypeIndex = nameAndTypeIndex; } public FieldOrMethodRefEntryIndex(int tag, DataInputStream stream) throws IOException { this(tag, stream.readShort(), stream.readShort()); } public String getClassName() { if (className == null) fetchChildren(); return super.getClassName(); } public String getFieldOrMethodName() { if (thingName == null) fetchChildren(); return super.getFieldOrMethodName(); } public String getSignature() { if (signature == null) fetchChildren(); return super.getSignature(); } public void fetchChildren() { className = lookupClass(classIndex); NameAndTypeEntry nat = (NameAndTypeEntry)lookupEntry(nameAndTypeIndex); thingName = nat.getName(); signature = nat.getDescriptor(); } } abstract public class NameAndTypeEntry implements Entry { protected String name, descriptor; protected int nameIndex, descriptorIndex; public int hashCode() { return name.hashCode() + descriptor.hashCode(); } public boolean equals(Object o) { return o instanceof NameAndTypeEntry && ((NameAndTypeEntry)o).name == name && ((NameAndTypeEntry)o).descriptor == descriptor; } public int getTag() { return CONSTANT_NameAndType; } public String getName() { return name; } public String getDescriptor() { return descriptor; } public int getSize() { return 1; } public void writeContentsTo(DataOutputStream stream) throws IOException { stream.writeShort(nameIndex); stream.writeShort(descriptorIndex); } // Follows javap output format for name/type pool entries. public String toString() { String natName = getName(); if (JMethod.INSTANCE_CONSTRUCTOR_NAME.equals(natName)) natName = "\""+natName+"\""; return "NameAndType\t#"+nameIndex+":#"+descriptorIndex+ ";// "+natName+":"+getDescriptor(); } public String toComment(String ownerClassname) { return ""; } } protected class NameAndTypeEntryValue extends NameAndTypeEntry implements EntryValue { public NameAndTypeEntryValue(String name, String descriptor) { this.name = name.intern(); this.descriptor = descriptor.intern(); } public void addChildren() { nameIndex = addUtf8(name); descriptorIndex = addUtf8(descriptor); } } protected class NameAndTypeEntryIndex extends NameAndTypeEntry implements EntryIndex { public NameAndTypeEntryIndex(int nameIndex, int descriptorIndex) { this.nameIndex = nameIndex; this.descriptorIndex = descriptorIndex; } public NameAndTypeEntryIndex(DataInputStream stream) throws IOException { this(stream.readShort(), stream.readShort()); } public String getName() { if (name == null) fetchChildren(); return super.getName(); } public String getDescriptor() { if (descriptor == null) fetchChildren(); return super.getDescriptor(); } public void fetchChildren() { name = lookupUtf8(nameIndex); descriptor = lookupUtf8(descriptorIndex); } } }