/*
* Copyright 2004-2010 Brian S O'Neill
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.cojen.classfile;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import org.cojen.classfile.constant.ConstantClassInfo;
import org.cojen.classfile.constant.ConstantDoubleInfo;
import org.cojen.classfile.constant.ConstantFieldInfo;
import org.cojen.classfile.constant.ConstantFloatInfo;
import org.cojen.classfile.constant.ConstantIntegerInfo;
import org.cojen.classfile.constant.ConstantInterfaceMethodInfo;
import org.cojen.classfile.constant.ConstantLongInfo;
import org.cojen.classfile.constant.ConstantMethodInfo;
import org.cojen.classfile.constant.ConstantNameAndTypeInfo;
import org.cojen.classfile.constant.ConstantStringInfo;
import org.cojen.classfile.constant.ConstantUTFInfo;
/**
* This class corresponds to the constant_pool structure as defined in
* section 4.4 of <i>The Java Virtual Machine Specification</i>.
*
* <p>ConstantPool entries are not written out in the order in which they were
* added to it. Instead, their ordering is changed such that String, Integer
* and Float constants are written out first. This provides a slight
* optimization for referencing these constants from a code attribute.
* It means that Opcode.LDC will more likely be used (one-byte index) than
* Opcode.LDC_W (two-byte index).
*
* @author Brian S O'Neill
* @see Opcode
*/
public class ConstantPool {
// A set of ConstantInfo objects.
private Map<ConstantInfo, ConstantInfo> mConstants = new HashMap<ConstantInfo, ConstantInfo>();
// Indexed list of constants.
private Vector<ConstantInfo> mIndexedConstants;
private int mEntries;
// Preserve the order only if the constant pool was read in.
private boolean mPreserveOrder;
ConstantPool() {
}
private ConstantPool(Vector<ConstantInfo> indexedConstants) {
mIndexedConstants = indexedConstants;
int size = indexedConstants.size();
for (int i=1; i<size; i++) {
ConstantInfo ci = indexedConstants.get(i);
if (ci != null) {
mConstants.put(ci, ci);
mEntries += ci.getEntryCount();
}
}
mPreserveOrder = true;
}
/**
* Returns a constant from the pool by index, or null if not found. If this
* constant pool has not yet been written or was not created by the read
* method, indexes are not assigned, and an IllegalStateException is
* thrown.
*
* @throws ArrayIndexOutOfBoundsException if index is out of range.
* @throws IllegalStateException if indexes are not assigned
*/
public ConstantInfo getConstant(int index) {
if (mIndexedConstants == null) {
throw new IllegalStateException
("Constant pool indexes have not been assigned");
}
return mIndexedConstants.get(index);
}
/**
* Returns all the constants in the pool, in no particular order.
*/
public Set<ConstantInfo> getAllConstants() {
return Collections.unmodifiableSet(mConstants.keySet());
}
/**
* Returns the number of constants in the pool.
*/
public int getSize() {
return mEntries;
}
/**
* Get or create a constant from the constant pool representing a class.
*/
public ConstantClassInfo addConstantClass(String className) {
return (ConstantClassInfo)addConstant(new ConstantClassInfo(this, className));
}
/**
* Get or create a constant from the constant pool representing an array
* class.
*
* @param dim Number of array dimensions.
*/
public ConstantClassInfo addConstantClass(String className, int dim) {
return (ConstantClassInfo)addConstant(new ConstantClassInfo(this, className, dim));
}
/**
* Get or create a constant from the constant pool representing a class.
*/
public ConstantClassInfo addConstantClass(TypeDesc type) {
return (ConstantClassInfo)addConstant(new ConstantClassInfo(this, type));
}
/**
* Get or create a constant from the constant pool representing a field in
* any class.
*/
public ConstantFieldInfo addConstantField(String className,
String fieldName,
TypeDesc type) {
ConstantInfo ci = new ConstantFieldInfo
(addConstantClass(className), addConstantNameAndType(fieldName, type));
return (ConstantFieldInfo)addConstant(ci);
}
/**
* Get or create a constant from the constant pool representing a method
* in any class. If the method returns void, set ret to null.
*/
public ConstantMethodInfo addConstantMethod(String className,
String methodName,
TypeDesc ret,
TypeDesc[] params) {
MethodDesc md = MethodDesc.forArguments(ret, params);
ConstantInfo ci = new ConstantMethodInfo
(addConstantClass(className), addConstantNameAndType(methodName, md));
return (ConstantMethodInfo)addConstant(ci);
}
/**
* Get or create a constant from the constant pool representing an
* interface method in any interface.
*/
public ConstantInterfaceMethodInfo addConstantInterfaceMethod(String className,
String methodName,
TypeDesc ret,
TypeDesc[] params) {
MethodDesc md = MethodDesc.forArguments(ret, params);
ConstantInfo ci = new ConstantInterfaceMethodInfo
(addConstantClass(className), addConstantNameAndType(methodName, md));
return (ConstantInterfaceMethodInfo)addConstant(ci);
}
/**
* Get or create a constant from the constant pool representing a
* constructor in any class.
*/
public ConstantMethodInfo addConstantConstructor(String className,
TypeDesc[] params) {
return addConstantMethod(className, "<init>", null, params);
}
/**
* Get or create a constant integer from the constant pool.
*/
public ConstantIntegerInfo addConstantInteger(int value) {
return (ConstantIntegerInfo)addConstant(new ConstantIntegerInfo(value));
}
/**
* Get or create a constant long from the constant pool.
*/
public ConstantLongInfo addConstantLong(long value) {
return (ConstantLongInfo)addConstant(new ConstantLongInfo(value));
}
/**
* Get or create a constant float from the constant pool.
*/
public ConstantFloatInfo addConstantFloat(float value) {
return (ConstantFloatInfo)addConstant(new ConstantFloatInfo(value));
}
/**
* Get or create a constant double from the constant pool.
*/
public ConstantDoubleInfo addConstantDouble(double value) {
return (ConstantDoubleInfo)addConstant(new ConstantDoubleInfo(value));
}
/**
* Get or create a constant string from the constant pool.
*/
public ConstantStringInfo addConstantString(String str) {
return (ConstantStringInfo)addConstant(new ConstantStringInfo(this, str));
}
/**
* Get or create a constant UTF string from the constant pool.
*/
public ConstantUTFInfo addConstantUTF(String str) {
return (ConstantUTFInfo)addConstant(new ConstantUTFInfo(str));
}
/**
* Get or create a constant name and type structure from the constant pool.
*/
public ConstantNameAndTypeInfo addConstantNameAndType(String name,
Descriptor type) {
return (ConstantNameAndTypeInfo)addConstant(new ConstantNameAndTypeInfo(this, name, type));
}
/**
* Get or create a constant name and type structure from the constant pool.
*/
public ConstantNameAndTypeInfo addConstantNameAndType(ConstantUTFInfo nameConstant,
ConstantUTFInfo descConstant) {
return (ConstantNameAndTypeInfo)addConstant
(new ConstantNameAndTypeInfo(nameConstant, descConstant));
}
/**
* Will only insert into the pool if the constant is not already in the
* pool.
*
* @return The actual constant in the pool.
*/
public ConstantInfo addConstant(ConstantInfo constant) {
ConstantInfo info = mConstants.get(constant);
if (info != null) {
return info;
}
int entryCount = constant.getEntryCount();
if (mIndexedConstants != null && mPreserveOrder) {
int size = mIndexedConstants.size();
mIndexedConstants.setSize(size + entryCount);
mIndexedConstants.set(size, constant);
constant.mIndex = size;
}
mConstants.put(constant, constant);
mEntries += entryCount;
return constant;
}
public void writeTo(DataOutput dout) throws IOException {
// Write out the size (number of entries) of the constant pool.
int size = getSize() + 1; // add one because constant 0 is reserved
if (size >= 65535) {
throw new IllegalStateException
("Constant pool entry count cannot exceed 65535: " + size);
}
dout.writeShort(size);
if (mIndexedConstants == null || !mPreserveOrder) {
mIndexedConstants = new Vector<ConstantInfo>(size);
mIndexedConstants.setSize(size);
int index = 1; // one-based constant pool index
// First write constants of higher priority -- String, Integer,
// Float.
// This is a slight optimization. It means that Opcode.LDC will
// more likely be used (one-byte index) than Opcode.LDC_W (two-byte
// index).
Iterator it = mConstants.keySet().iterator();
while (it.hasNext()) {
ConstantInfo constant = (ConstantInfo)it.next();
if (constant.hasPriority()) {
constant.mIndex = index;
mIndexedConstants.set(index, constant);
index += constant.getEntryCount();
}
}
// Now write all non-priority constants.
it = mConstants.keySet().iterator();
while (it.hasNext()) {
ConstantInfo constant = (ConstantInfo)it.next();
if (!constant.hasPriority()) {
constant.mIndex = index;
mIndexedConstants.set(index, constant);
index += constant.getEntryCount();
}
}
}
// Now actually write out the constants since the indexes have been
// resolved.
for (int i=1; i<size; i++) {
Object obj = mIndexedConstants.get(i);
if (obj != null) {
((ConstantInfo)obj).writeTo(dout);
}
}
}
public static ConstantPool readFrom(DataInput din) throws IOException {
int size = din.readUnsignedShort();
Vector<ConstantInfo> constants = new Vector<ConstantInfo>(size);
constants.setSize(size);
int index = 1;
while (index < size) {
int tag = din.readByte();
int entryCount = 1;
ConstantInfo constant;
switch (tag) {
case ConstantInfo.TAG_UTF8:
constant = new ConstantUTFInfo(din.readUTF());
break;
case ConstantInfo.TAG_INTEGER:
constant = new ConstantIntegerInfo(din.readInt());
break;
case ConstantInfo.TAG_FLOAT:
constant = new ConstantFloatInfo(din.readFloat());
break;
case ConstantInfo.TAG_LONG:
constant = new ConstantLongInfo(din.readLong());
entryCount++;
break;
case ConstantInfo.TAG_DOUBLE:
constant = new ConstantDoubleInfo(din.readDouble());
entryCount++;
break;
case ConstantInfo.TAG_CLASS:
case ConstantInfo.TAG_STRING:
constant = new TempEntry(tag, din.readUnsignedShort());
break;
case ConstantInfo.TAG_FIELD:
case ConstantInfo.TAG_METHOD:
case ConstantInfo.TAG_INTERFACE_METHOD:
case ConstantInfo.TAG_NAME_AND_TYPE:
constant = new TempEntry
(tag, (din.readShort() << 16) | (din.readUnsignedShort()));
break;
default:
throw new IOException("Invalid constant pool tag: " + tag);
}
if (constant instanceof ConstantInfo) {
((ConstantInfo)constant).mIndex = index;
}
constants.set(index, constant);
index += entryCount;
}
for (index = 1; index < size; index++) {
resolve(constants, index);
}
return new ConstantPool(constants);
}
private static ConstantInfo resolve(List<ConstantInfo> constants, int index) {
ConstantInfo constant = constants.get(index);
if (constant == null) {
return null;
}
if (!(constant instanceof TempEntry)) {
return constant;
}
TempEntry entry = (TempEntry)constant;
int data = entry.mData;
int index1 = data & 0xffff;
ConstantInfo ci1 = constants.get(index1);
if (ci1 instanceof TempEntry) {
ci1 = resolve(constants, index1);
}
ConstantInfo ci = null;
switch (entry.mTag) {
case ConstantInfo.TAG_CLASS:
ci = new ConstantClassInfo((ConstantUTFInfo)ci1);
break;
case ConstantInfo.TAG_STRING:
ci = new ConstantStringInfo((ConstantUTFInfo)ci1);
break;
case ConstantInfo.TAG_FIELD:
case ConstantInfo.TAG_METHOD:
case ConstantInfo.TAG_INTERFACE_METHOD:
case ConstantInfo.TAG_NAME_AND_TYPE:
int index2 = data >>> 16;
ConstantInfo ci2 = constants.get(index2);
if (ci2 instanceof TempEntry) {
ci2 = resolve(constants, index2);
}
switch (entry.mTag) {
case ConstantInfo.TAG_FIELD:
ci = new ConstantFieldInfo
((ConstantClassInfo)ci2, (ConstantNameAndTypeInfo)ci1);
break;
case ConstantInfo.TAG_METHOD:
ci = new ConstantMethodInfo
((ConstantClassInfo)ci2, (ConstantNameAndTypeInfo)ci1);
break;
case ConstantInfo.TAG_INTERFACE_METHOD:
ci = new ConstantInterfaceMethodInfo
((ConstantClassInfo)ci2, (ConstantNameAndTypeInfo)ci1);
break;
case ConstantInfo.TAG_NAME_AND_TYPE:
ci = new ConstantNameAndTypeInfo
((ConstantUTFInfo)ci2, (ConstantUTFInfo)ci1);
break;
}
break;
}
ci.mIndex = index;
constants.set(index, ci);
return ci;
}
private static class TempEntry extends ConstantInfo {
public int mTag;
public int mData;
public TempEntry(int tag, int data) {
super(-1);
mTag = tag;
mData = data;
}
}
}