/* * ClassWriter.java * Oct 16, 2008 */ package org.botnode.asm; /** * @author bbrown */ public class ClassWriter { /** * Minor and major version numbers of the class to be generated. */ int version; public static final int COMPUTE_FRAMES = 2; public static final int COMPUTE_MAXS = 1; static final byte[] TYPE; static final int CLASS = 7; static final int FIELD = 9; static final int METH = 10; static final int IMETH = 11; static final int STR = 8; static final int INT = 3; static final int FLOAT = 4; static final int LONG = 5; static final int DOUBLE = 6; static final int NAME_TYPE = 12; static final int UTF8 = 1; static final int TYPE_NORMAL = 13; static final int TYPE_UNINIT = 14; static final int TYPE_MERGED = 15; private int access; /** * Index of the next item to be added in the constant pool. */ int index; /** * A reusable key used to look for items in the {@link #items} hash table. */ final Item key; final Item key2; final Item key3; /** * The constant pool's hash table data. */ Item[] items; ByteVector pool; // Constant Pool Item for the name of the class. private int name; private int superName; /** * The internal name of this class. */ String thisName; /** * This array contains the indexes of the constant pool items * that contain the internal names of these interfaces. */ private int[] interfaces; private int interfaceCount = 0; int threshold; private final boolean computeMaxs; private final boolean computeFrames; boolean invalidFrames; FieldWriter firstField; FieldWriter lastField; MethodWriter firstMethod; MethodWriter lastMethod; /** * A type table used to temporarily store internal names that will not * necessarily be stored in the constant pool. */ Item[] typeTable; /** * Number of elements in the {@link #typeTable} array. */ private short typeCount; /** * The non standard attributes of this class. */ private Attribute attrs; // ------------------------------------------------------------------------ // Static initializer // ------------------------------------------------------------------------ /** * Computes the instruction types of JVM opcodes. */ static { int i; byte[] b = new byte[220]; String s = "AAAAAAAAAAAAAAAABCKLLDDDDDEEEEEEEEEEEEEEEEEEEEAAAAAAAADD" + "DDDEEEEEEEEEEEEEEEEEEEEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAIIIIIIIIIIIIIIIIDNOAA" + "AAAAGGGGGGGHAFBFAAFFAAQPIIJJIIIIIIIIIIIIIIIIII"; for (i = 0; i < b.length; ++i) { b[i] = (byte) (s.charAt(i) - 'A'); } TYPE = b; } // ------------------------------------------------------------------------ // Constructor // ------------------------------------------------------------------------ public ClassWriter(final int flags) { index = 1; pool = new ByteVector(); items = new Item[256]; threshold = (int) (0.75d * items.length); key = new Item(); key2 = new Item(); key3 = new Item(); this.computeMaxs = (flags & COMPUTE_MAXS) != 0; this.computeFrames = (flags & COMPUTE_FRAMES) != 0; } // ------------------------------------------------------------------------ // New Variable Definitions. // ------------------------------------------------------------------------ /** * Adds the given internal name to {@link #typeTable} and returns its index. * Does nothing if the type table already contains this internal name. */ int addType(final String type) { key.set(TYPE_NORMAL, type, null, null); Item result = get(key); if (result == null) { result = addType(key); } return result.index; } /** * Adds the given Item to {@link #typeTable}. */ private Item addType(final Item item) { ++typeCount; Item result = new Item(typeCount, key); put(result); if (typeTable == null) { typeTable = new Item[16]; } if (typeCount == typeTable.length) { Item[] newTable = new Item[2 * typeTable.length]; System.arraycopy(typeTable, 0, newTable, 0, typeTable.length); typeTable = newTable; } typeTable[typeCount] = result; return result; } /** * Adds the given "uninitialized" type to {@link #typeTable} and returns its * index. This method is used for UNINITIALIZED types, made of an internal * name and a bytecode offset. */ int addUninitializedType(final String type, final int offset) { key.type = TYPE_UNINIT; key.intVal = offset; key.strVal1 = type; key.hashCode = 0x7FFFFFFF & (TYPE_UNINIT + type.hashCode() + offset); Item result = get(key); if (result == null) { result = addType(key); } return result.index; } /** * Returns the index of the common super type of the two given types. This * method calls {@link #getCommonSuperClass} and caches the result in the * {@link #items} hash table to speedup future calls with the same * parameters. */ int getMergedType(final int type1, final int type2) { key2.type = TYPE_MERGED; key2.longVal = type1 | (((long) type2) << 32); key2.hashCode = 0x7FFFFFFF & (TYPE_MERGED + type1 + type2); Item result = get(key2); if (result == null) { String t = typeTable[type1].strVal1; String u = typeTable[type2].strVal1; key2.intVal = addType(getCommonSuperClass(t, u)); result = new Item((short) 0, key2); put(result); } return result.intVal; } /** * Returns the common super type of the two given types. The default * implementation of this method <i>loads<i> the two given classes and uses * the java.lang.Class methods to find the common super class. */ protected String getCommonSuperClass(final String type1, final String type2) { Class c, d; try { c = Class.forName(type1.replace('/', '.')); d = Class.forName(type2.replace('/', '.')); } catch (Exception e) { throw new RuntimeException(e.toString()); } if (c.isAssignableFrom(d)) { return type1; } if (d.isAssignableFrom(c)) { return type2; } if (c.isInterface() || d.isInterface()) { return "java/lang/Object"; } else { do { c = c.getSuperclass(); } while (!c.isAssignableFrom(d)); return c.getName().replace('.', '/'); } } /** * Adds an UTF8 string to the constant pool of the class being build. Does * nothing if the constant pool already contains a similar item. <i>This * method is intended for {@link Attribute} sub classes, and is normally not * needed by class generators or adapters.</i> */ public int newUTF8(final String value) { key.set(UTF8, value, null, null); Item result = get(key); if (result == null) { pool.putByte(UTF8).putUTF8(value); result = new Item(index++, key); put(result); } return result.index; } /** * Adds a number or string constant to the constant pool of the class being * build. Does nothing if the constant pool already contains a similar item. */ Item newConstItem(final Object cst) { if (cst instanceof Integer) { int val = ((Integer) cst).intValue(); return newInteger(val); } else if (cst instanceof Byte) { int val = ((Byte) cst).intValue(); return newInteger(val); } else if (cst instanceof Character) { int val = ((Character) cst).charValue(); return newInteger(val); } else if (cst instanceof Short) { int val = ((Short) cst).intValue(); return newInteger(val); } else if (cst instanceof Boolean) { int val = ((Boolean) cst).booleanValue() ? 1 : 0; return newInteger(val); } else if (cst instanceof Float) { float val = ((Float) cst).floatValue(); return newFloat(val); } else if (cst instanceof Long) { long val = ((Long) cst).longValue(); return newLong(val); } else if (cst instanceof Double) { double val = ((Double) cst).doubleValue(); return newDouble(val); } else if (cst instanceof String) { return newString((String) cst); } else if (cst instanceof Type) { Type t = (Type) cst; return newClassItem(t.getSort() == Type.OBJECT ? t.getInternalName() : t.getDescriptor()); } else { throw new IllegalArgumentException("value " + cst); } } /** * Adds a class reference to the constant pool of the class being build. * Does nothing if the constant pool already contains a similar item. * */ Item newClassItem(final String value) { key2.set(CLASS, value, null, null); Item result = get(key2); if (result == null) { pool.put12(CLASS, newUTF8(value)); result = new Item(index++, key2); put(result); } return result; } /** * Adds a number or string constant to the constant pool of the class being * build. Does nothing if the constant pool already contains a similar item. * <i>This method is intended for {@link Attribute} sub classes, and is * normally not needed by class generators or adapters.</i> */ public int newConst(final Object cst) { return newConstItem(cst).index; } /** * Adds an integer to the constant pool of the class being build. Does * nothing if the constant pool already contains a similar item. */ Item newInteger(final int value) { key.set(value); Item result = get(key); if (result == null) { pool.putByte(INT).putInt(value); result = new Item(index++, key); put(result); } return result; } /** * Adds a float to the constant pool of the class being build. Does nothing * if the constant pool already contains a similar item. */ Item newFloat(final float value) { key.set(value); Item result = get(key); if (result == null) { pool.putByte(FLOAT).putInt(key.intVal); result = new Item(index++, key); put(result); } return result; } /** * Adds a long to the constant pool of the class being build. Does nothing * if the constant pool already contains a similar item. */ Item newLong(final long value) { key.set(value); Item result = get(key); if (result == null) { pool.putByte(LONG).putLong(value); result = new Item(index, key); put(result); index += 2; } return result; } /** * Adds a double to the constant pool of the class being build. Does nothing * if the constant pool already contains a similar item. */ Item newDouble(final double value) { key.set(value); Item result = get(key); if (result == null) { pool.putByte(DOUBLE).putLong(key.longVal); result = new Item(index, key); put(result); index += 2; } return result; } /** * Adds a string to the constant pool of the class being build. Does nothing * if the constant pool already contains a similar item. */ private Item newString(final String value) { key2.set(STR, value, null, null); Item result = get(key2); if (result == null) { pool.put12(STR, newUTF8(value)); result = new Item(index++, key2); put(result); } return result; } //***************************************************** // GET and PUT: //***************************************************** /** * Returns the constant pool's hash table item which is equal to the given * item. */ private Item get(final Item key) { Item i = items[key.hashCode % items.length]; while (i != null && !key.isEqualTo(i)) { i = i.next; } return i; } /** * Puts the given item in the constant pool's hash table. The hash table * <i>must</i> not already contains this item. */ private void put(final Item i) { if (index > threshold) { int ll = items.length; int nl = ll * 2 + 1; Item[] newItems = new Item[nl]; for (int l = ll - 1; l >= 0; --l) { Item j = items[l]; while (j != null) { int index = j.hashCode % newItems.length; Item k = j.next; j.next = newItems[index]; newItems[index] = j; j = k; } } items = newItems; threshold = (int) (nl * 0.75); } int index = i.hashCode % items.length; i.next = items[index]; items[index] = i; } /** * Puts one byte and two shorts into the constant pool. */ private void put122(final int b, final int s1, final int s2) { pool.put12(b, s1).putShort(s2); } //***************************************************** // To Byte Array: //***************************************************** public byte[] toByteArray() { int size = 24 + (2 * interfaceCount); //*************************************** // Calculate the field data size. //*************************************** int nbFields = 0; FieldWriter fb = firstField; while (fb != null) { ++nbFields; size += fb.getSize(); fb = fb.next; } //*************************************** // Calculate method data size. //*************************************** int nbMethods = 0; MethodWriter mb = firstMethod; while (mb != null) { ++nbMethods; size += mb.getSize(); mb = mb.next; } int attributeCount = 0; size += pool.length; //*************************************** // Write the class data. //*************************************** ByteVector out = new ByteVector(size); out.putInt(0xCAFEBABE).putInt(version); out.putShort(index).putByteArray(pool.data, 0, pool.length); out.putShort(access).putShort(name).putShort(superName); out.putShort(interfaceCount); for (int i = 0; i < interfaceCount; ++i) { out.putShort(interfaces[i]); } //*************************************** // Write the field data //*************************************** out.putShort(nbFields); fb = firstField; while (fb != null) { fb.put(out); fb = fb.next; } //*************************************** // Write the method data //*************************************** out.putShort(nbMethods); mb = firstMethod; while (mb != null) { mb.put(out); mb = mb.next; } out.putShort(attributeCount); if (attrs != null) { attrs.put(this, null, 0, -1, -1, out); } return out.data; } // End of the Method toByteArray }