/******************************************************************************* * Copyright 2010 Cees De Groot, Alex Boisvert, Jan Kotek * * 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.apache.jdbm; import java.io.*; import java.lang.reflect.Array; import java.math.BigDecimal; import java.math.BigInteger; import java.util.*; import static org.apache.jdbm.SerializationHeader.*; /** * Serialization util. It reduces serialized data size for most common java types. *

* Common pattern is one byte header which identifies data type, then size is written (if required) and * data. *

* On unknown types normal java serialization is used *

*

* Header byte values bellow 180 are reserved by author for future use. If you want to customize * this class, use values over 180, to be compatible with future updates. * * @author Jan Kotek */ @SuppressWarnings("unchecked") public class Serialization extends SerialClassInfo implements Serializer { /** * print statistics to STDOUT */ static final boolean DEBUG = false; static final String UTF8 = "UTF-8"; Serialization(DBAbstract db, long serialClassInfoRecid, ArrayList info) throws IOException { super(db, serialClassInfoRecid,info); } public Serialization() { super(null,0L,new ArrayList()); // Add java.lang.Object as registered class registered.add(new ClassInfo(Object.class.getName(), new FieldInfo[]{},false,false)); } /** * Serialize the object into a byte array. */ public byte[] serialize(Object obj) throws IOException { DataInputOutput ba = new DataInputOutput(); serialize(ba, obj); return ba.toByteArray(); } boolean isSerializable(Object obj) { //TODO suboptimal code try { serialize(new DataOutputStream(new ByteArrayOutputStream()), obj); return true; } catch (Exception e) { return false; } } public void serialize(final DataOutput out, final Object obj) throws IOException { serialize(out, obj, null); } public void serialize(final DataOutput out, final Object obj, FastArrayList objectStack) throws IOException { /**try to find object on stack if it exists*/ if (objectStack != null) { int indexInObjectStack = objectStack.identityIndexOf(obj); if (indexInObjectStack != -1) { //object was already serialized, just write reference to it and return out.write(OBJECT_STACK); LongPacker.packInt(out, indexInObjectStack); return; } //add this object to objectStack objectStack.add(obj); } final Class clazz = obj != null ? obj.getClass() : null; /** first try to serialize object without initializing object stack*/ if (obj == null) { out.write(NULL); return; } else if (clazz == Boolean.class) { if (((Boolean) obj).booleanValue()) out.write(BOOLEAN_TRUE); else out.write(BOOLEAN_FALSE); return; } else if (clazz == Integer.class) { final int val = (Integer) obj; writeInteger(out, val); return; } else if (clazz == Double.class) { double v = (Double) obj; if (v == -1d) out.write(DOUBLE_MINUS_1); else if (v == 0d) out.write(DOUBLE_0); else if (v == 1d) out.write(DOUBLE_1); else if (v >= 0 && v <= 255 && (int) v == v) { out.write(DOUBLE_255); out.write((int) v); } else if (v >= Short.MIN_VALUE && v <= Short.MAX_VALUE && (short) v == v) { out.write(DOUBLE_SHORT); out.writeShort((int) v); } else { out.write(DOUBLE_FULL); out.writeDouble(v); } return; } else if (clazz == Float.class) { float v = (Float) obj; if (v == -1f) out.write(FLOAT_MINUS_1); else if (v == 0f) out.write(FLOAT_0); else if (v == 1f) out.write(FLOAT_1); else if (v >= 0 && v <= 255 && (int) v == v) { out.write(FLOAT_255); out.write((int) v); } else if (v >= Short.MIN_VALUE && v <= Short.MAX_VALUE && (short) v == v) { out.write(FLOAT_SHORT); out.writeShort((int) v); } else { out.write(FLOAT_FULL); out.writeFloat(v); } return; } else if (clazz == BigInteger.class) { out.write(BIGINTEGER); byte[] buf = ((BigInteger) obj).toByteArray(); serializeByteArrayInt(out, buf); return; } else if (clazz == BigDecimal.class) { out.write(BIGDECIMAL); BigDecimal d = (BigDecimal) obj; serializeByteArrayInt(out, d.unscaledValue().toByteArray()); LongPacker.packInt(out, d.scale()); return; } else if (clazz == Long.class) { final long val = (Long) obj; writeLong(out, val); return; } else if (clazz == Short.class) { short val = (Short) obj; if (val == -1) out.write(SHORT_MINUS_1); else if (val == 0) out.write(SHORT_0); else if (val == 1) out.write(SHORT_1); else if (val > 0 && val < 255) { out.write(SHORT_255); out.write(val); } else { out.write(SHORT_FULL); out.writeShort(val); } return; } else if (clazz == Byte.class) { byte val = (Byte) obj; if (val == -1) out.write(BYTE_MINUS_1); else if (val == 0) out.write(BYTE_0); else if (val == 1) out.write(BYTE_1); else { out.write(SHORT_FULL); out.writeByte(val); } return; } else if (clazz == Character.class) { out.write(CHAR); out.writeChar((Character) obj); return; } else if (clazz == String.class) { String s = (String) obj; if (s.length() == 0) { out.write(STRING_EMPTY); } else { out.write(STRING); serializeString(out, s); } return; } else if (obj instanceof Class) { out.write(CLASS); serialize(out, ((Class) obj).getName()); return; } else if (obj instanceof int[]) { writeIntArray(out, (int[]) obj); return; } else if (obj instanceof long[]) { writeLongArray(out, (long[]) obj); return; } else if (obj instanceof short[]) { out.write(SHORT_ARRAY); short[] a = (short[]) obj; LongPacker.packInt(out,a.length); for(short s:a) out.writeShort(s); return; } else if (obj instanceof boolean[]) { out.write(BOOLEAN_ARRAY); boolean[] a = (boolean[]) obj; LongPacker.packInt(out,a.length); for(boolean s:a) out.writeBoolean(s); //TODO pack 8 booleans to single byte return; } else if (obj instanceof double[]) { out.write(DOUBLE_ARRAY); double[] a = (double[]) obj; LongPacker.packInt(out,a.length); for(double s:a) out.writeDouble(s); return; } else if (obj instanceof float[]) { out.write(FLOAT_ARRAY); float[] a = (float[]) obj; LongPacker.packInt(out,a.length); for(float s:a) out.writeFloat(s); return; } else if (obj instanceof char[]) { out.write(CHAR_ARRAY); char[] a = (char[]) obj; LongPacker.packInt(out,a.length); for(char s:a) out.writeChar(s); return; } else if (obj instanceof byte[]) { byte[] b = (byte[]) obj; out.write(ARRAY_BYTE_INT); serializeByteArrayInt(out, b); return; } else if (clazz == Date.class) { out.write(DATE); out.writeLong(((Date) obj).getTime()); return; } else if (clazz == UUID.class) { out.write(UUID); serializeUUID(out,(UUID) obj); return; } else if (clazz == BTree.class) { out.write(BTREE); ((BTree) obj).writeExternal(out); return; } else if (clazz == HTree.class) { out.write(HTREE); ((HTree) obj).serialize(out); return; } else if (clazz == LinkedList2.class) { out.write(JDBMLINKEDLIST); ((LinkedList2) obj).serialize(out); return; } /** classes bellow need object stack, so initialize it if not alredy initialized*/ if (objectStack == null) { objectStack = new FastArrayList(); objectStack.add(obj); } if (obj instanceof Object[]) { Object[] b = (Object[]) obj; boolean packableLongs = b.length <= 255; if (packableLongs) { //check if it contains packable longs for (Object o : b) { if (o != null && (o.getClass() != Long.class || (((Long) o).longValue() < 0 && ((Long) o).longValue() != Long.MAX_VALUE))) { packableLongs = false; break; } } } if (packableLongs) { //packable Longs is special case, it is often used in JDBM to reference fields out.write(ARRAY_OBJECT_PACKED_LONG); out.write(b.length); for (Object o : b) { if (o == null) LongPacker.packLong(out, 0); else LongPacker.packLong(out, ((Long) o).longValue() + 1); } } else { out.write(ARRAY_OBJECT); LongPacker.packInt(out, b.length); // Write class id for components Class componentType = obj.getClass().getComponentType(); registerClass(componentType); //write class header int classId = getClassId(componentType); LongPacker.packInt(out, classId); for (Object o : b) serialize(out, o, objectStack); } } else if (clazz == ArrayList.class) { ArrayList l = (ArrayList) obj; boolean packableLongs = l.size() < 255; if (packableLongs) { //packable Longs is special case, it is often used in JDBM to reference fields for (Object o : l) { if (o != null && (o.getClass() != Long.class || (((Long) o).longValue() < 0 && ((Long) o).longValue() != Long.MAX_VALUE))) { packableLongs = false; break; } } } if (packableLongs) { out.write(ARRAYLIST_PACKED_LONG); out.write(l.size()); for (Object o : l) { if (o == null) LongPacker.packLong(out, 0); else LongPacker.packLong(out, ((Long) o).longValue() + 1); } } else { serializeCollection(ARRAYLIST, out, obj, objectStack); } } else if (clazz == java.util.LinkedList.class) { serializeCollection(LINKEDLIST, out, obj, objectStack); } else if (clazz == Vector.class) { serializeCollection(VECTOR, out, obj, objectStack); } else if (clazz == TreeSet.class) { TreeSet l = (TreeSet) obj; out.write(TREESET); LongPacker.packInt(out, l.size()); serialize(out, l.comparator(), objectStack); for (Object o : l) serialize(out, o, objectStack); } else if (clazz == HashSet.class) { serializeCollection(HASHSET, out, obj, objectStack); } else if (clazz == LinkedHashSet.class) { serializeCollection(LINKEDHASHSET, out, obj, objectStack); } else if (clazz == TreeMap.class) { TreeMap l = (TreeMap) obj; out.write(TREEMAP); LongPacker.packInt(out, l.size()); serialize(out, l.comparator(), objectStack); for (Object o : l.keySet()) { serialize(out, o, objectStack); serialize(out, l.get(o), objectStack); } } else if (clazz == HashMap.class) { serializeMap(HASHMAP, out, obj, objectStack); } else if (clazz == IdentityHashMap.class) { serializeMap(IDENTITYHASHMAP, out, obj, objectStack); } else if (clazz == LinkedHashMap.class) { serializeMap(LINKEDHASHMAP, out, obj, objectStack); } else if (clazz == Hashtable.class) { serializeMap(HASHTABLE, out, obj, objectStack); } else if (clazz == Properties.class) { serializeMap(PROPERTIES, out, obj, objectStack); } else if (clazz == Locale.class){ out.write(LOCALE); Locale l = (Locale) obj; out.writeUTF(l.getLanguage()); out.writeUTF(l.getCountry()); out.writeUTF(l.getVariant()); } else { out.write(NORMAL); writeObject(out, obj, objectStack); } } static void serializeString(DataOutput out, String obj) throws IOException { final int len = obj.length(); LongPacker.packInt(out, len); for (int i = 0; i < len; i++) { int c = (int) obj.charAt(i); //TODO investigate if c could be negative here LongPacker.packInt(out, c); } } private void serializeUUID(DataOutput out, UUID uuid) throws IOException { out.writeLong(uuid.getMostSignificantBits()); out.writeLong(uuid.getLeastSignificantBits()); } private void serializeMap(int header, DataOutput out, Object obj, FastArrayList objectStack) throws IOException { Map l = (Map) obj; out.write(header); LongPacker.packInt(out, l.size()); for (Object o : l.keySet()) { serialize(out, o, objectStack); serialize(out, l.get(o), objectStack); } } private void serializeCollection(int header, DataOutput out, Object obj, FastArrayList objectStack) throws IOException { Collection l = (Collection) obj; out.write(header); LongPacker.packInt(out, l.size()); for (Object o : l) serialize(out, o, objectStack); } private void serializeByteArrayInt(DataOutput out, byte[] b) throws IOException { LongPacker.packInt(out, b.length); out.write(b); } private void writeLongArray(DataOutput da, long[] obj) throws IOException { long max = Long.MIN_VALUE; long min = Long.MAX_VALUE; for (long i : obj) { max = Math.max(max, i); min = Math.min(min, i); } if (0 <= min && max <= 255) { da.write(ARRAY_LONG_B); LongPacker.packInt(da, obj.length); for (long l : obj) da.write((int) l); } else if (0 <= min && max <= Long.MAX_VALUE) { da.write(ARRAY_LONG_PACKED); LongPacker.packInt(da, obj.length); for (long l : obj) LongPacker.packLong(da, l); } else if (Short.MIN_VALUE <= min && max <= Short.MAX_VALUE) { da.write(ARRAY_LONG_S); LongPacker.packInt(da, obj.length); for (long l : obj) da.writeShort((short) l); } else if (Integer.MIN_VALUE <= min && max <= Integer.MAX_VALUE) { da.write(ARRAY_LONG_I); LongPacker.packInt(da, obj.length); for (long l : obj) da.writeInt((int) l); } else { da.write(ARRAY_LONG_L); LongPacker.packInt(da, obj.length); for (long l : obj) da.writeLong(l); } } private void writeIntArray(DataOutput da, int[] obj) throws IOException { int max = Integer.MIN_VALUE; int min = Integer.MAX_VALUE; for (int i : obj) { max = Math.max(max, i); min = Math.min(min, i); } boolean fitsInByte = 0 <= min && max <= 255; boolean fitsInShort = Short.MIN_VALUE >= min && max <= Short.MAX_VALUE; if (obj.length <= 255 && fitsInByte) { da.write(ARRAY_INT_B_255); da.write(obj.length); for (int i : obj) da.write(i); } else if (fitsInByte) { da.write(ARRAY_INT_B_INT); LongPacker.packInt(da, obj.length); for (int i : obj) da.write(i); } else if (0 <= min && max <= Integer.MAX_VALUE) { da.write(ARRAY_INT_PACKED); LongPacker.packInt(da, obj.length); for (int l : obj) LongPacker.packInt(da, l); } else if (fitsInShort) { da.write(ARRAY_INT_S); LongPacker.packInt(da, obj.length); for (int i : obj) da.writeShort(i); } else { da.write(ARRAY_INT_I); LongPacker.packInt(da, obj.length); for (int i : obj) da.writeInt(i); } } private void writeInteger(DataOutput da, final int val) throws IOException { if (val == -1) da.write(INTEGER_MINUS_1); else if (val == 0) da.write(INTEGER_0); else if (val == 1) da.write(INTEGER_1); else if (val == 2) da.write(INTEGER_2); else if (val == 3) da.write(INTEGER_3); else if (val == 4) da.write(INTEGER_4); else if (val == 5) da.write(INTEGER_5); else if (val == 6) da.write(INTEGER_6); else if (val == 7) da.write(INTEGER_7); else if (val == 8) da.write(INTEGER_8); else if (val == Integer.MIN_VALUE) da.write(INTEGER_MINUS_MAX); else if (val > 0 && val < 255) { da.write(INTEGER_255); da.write(val); } else if (val < 0) { da.write(INTEGER_PACK_NEG); LongPacker.packInt(da, -val); } else { da.write(INTEGER_PACK); LongPacker.packInt(da, val); } } private void writeLong(DataOutput da, final long val) throws IOException { if (val == -1) da.write(LONG_MINUS_1); else if (val == 0) da.write(LONG_0); else if (val == 1) da.write(LONG_1); else if (val == 2) da.write(LONG_2); else if (val == 3) da.write(LONG_3); else if (val == 4) da.write(LONG_4); else if (val == 5) da.write(LONG_5); else if (val == 6) da.write(LONG_6); else if (val == 7) da.write(LONG_7); else if (val == 8) da.write(LONG_8); else if (val == Long.MIN_VALUE) da.write(LONG_MINUS_MAX); else if (val > 0 && val < 255) { da.write(LONG_255); da.write((int) val); } else if (val < 0) { da.write(LONG_PACK_NEG); LongPacker.packLong(da, -val); } else { da.write(LONG_PACK); LongPacker.packLong(da, val); } } /** * Deserialize an object from a byte array * * @throws IOException * @throws ClassNotFoundException */ public Object deserialize(byte[] buf) throws ClassNotFoundException, IOException { DataInputOutput bs = new DataInputOutput(buf); Object ret = deserialize(bs); if (bs.available() != 0) throw new InternalError("bytes left: " + bs.available()); return ret; } static String deserializeString(DataInput buf) throws IOException { int len = LongPacker.unpackInt(buf); char[] b = new char[len]; for (int i = 0; i < len; i++) b[i] = (char) LongPacker.unpackInt(buf); return new String(b); } public Object deserialize(DataInput is) throws IOException, ClassNotFoundException { return deserialize(is, null); } public Object deserialize(DataInput is, FastArrayList objectStack) throws IOException, ClassNotFoundException { Object ret = null; final int head = is.readUnsignedByte(); /** first try to deserialize object without allocating object stack*/ switch (head) { case NULL: break; case BOOLEAN_TRUE: ret = Boolean.TRUE; break; case BOOLEAN_FALSE: ret = Boolean.FALSE; break; case INTEGER_MINUS_1: ret = Integer.valueOf(-1); break; case INTEGER_0: ret = Integer.valueOf(0); break; case INTEGER_1: ret = Integer.valueOf(1); break; case INTEGER_2: ret = Integer.valueOf(2); break; case INTEGER_3: ret = Integer.valueOf(3); break; case INTEGER_4: ret = Integer.valueOf(4); break; case INTEGER_5: ret = Integer.valueOf(5); break; case INTEGER_6: ret = Integer.valueOf(6); break; case INTEGER_7: ret = Integer.valueOf(7); break; case INTEGER_8: ret = Integer.valueOf(8); break; case INTEGER_MINUS_MAX: ret = Integer.valueOf(Integer.MIN_VALUE); break; case INTEGER_255: ret = Integer.valueOf(is.readUnsignedByte()); break; case INTEGER_PACK_NEG: ret = Integer.valueOf(-LongPacker.unpackInt(is)); break; case INTEGER_PACK: ret = Integer.valueOf(LongPacker.unpackInt(is)); break; case LONG_MINUS_1: ret = Long.valueOf(-1); break; case LONG_0: ret = Long.valueOf(0); break; case LONG_1: ret = Long.valueOf(1); break; case LONG_2: ret = Long.valueOf(2); break; case LONG_3: ret = Long.valueOf(3); break; case LONG_4: ret = Long.valueOf(4); break; case LONG_5: ret = Long.valueOf(5); break; case LONG_6: ret = Long.valueOf(6); break; case LONG_7: ret = Long.valueOf(7); break; case LONG_8: ret = Long.valueOf(8); break; case LONG_255: ret = Long.valueOf(is.readUnsignedByte()); break; case LONG_PACK_NEG: ret = Long.valueOf(-LongPacker.unpackLong(is)); break; case LONG_PACK: ret = Long.valueOf(LongPacker.unpackLong(is)); break; case LONG_MINUS_MAX: ret = Long.valueOf(Long.MIN_VALUE); break; case SHORT_MINUS_1: ret = Short.valueOf((short) -1); break; case SHORT_0: ret = Short.valueOf((short) 0); break; case SHORT_1: ret = Short.valueOf((short) 1); break; case SHORT_255: ret = Short.valueOf((short) is.readUnsignedByte()); break; case SHORT_FULL: ret = Short.valueOf(is.readShort()); break; case BYTE_MINUS_1: ret = Byte.valueOf((byte) -1); break; case BYTE_0: ret = Byte.valueOf((byte) 0); break; case BYTE_1: ret = Byte.valueOf((byte) 1); break; case BYTE_FULL: ret = Byte.valueOf(is.readByte()); break; case SHORT_ARRAY: int size = LongPacker.unpackInt(is); ret = new short[size]; for(int i=0;i deserializeArrayList(DataInput is, FastArrayList objectStack) throws IOException, ClassNotFoundException { int size = LongPacker.unpackInt(is); ArrayList s = new ArrayList(size); objectStack.add(s); for (int i = 0; i < size; i++) { s.add(deserialize(is, objectStack)); } return s; } private ArrayList deserializeArrayListPackedLong(DataInput is) throws IOException, ClassNotFoundException { int size = is.readUnsignedByte(); if (size < 0) throw new EOFException(); ArrayList s = new ArrayList(size); for (int i = 0; i < size; i++) { long l = LongPacker.unpackLong(is); if (l == 0) s.add(null); else s.add(Long.valueOf(l - 1)); } return s; } private java.util.LinkedList deserializeLinkedList(DataInput is, FastArrayList objectStack) throws IOException, ClassNotFoundException { int size = LongPacker.unpackInt(is); java.util.LinkedList s = new java.util.LinkedList(); objectStack.add(s); for (int i = 0; i < size; i++) s.add(deserialize(is, objectStack)); return s; } private Vector deserializeVector(DataInput is, FastArrayList objectStack) throws IOException, ClassNotFoundException { int size = LongPacker.unpackInt(is); Vector s = new Vector(size); objectStack.add(s); for (int i = 0; i < size; i++) s.add(deserialize(is, objectStack)); return s; } private HashSet deserializeHashSet(DataInput is, FastArrayList objectStack) throws IOException, ClassNotFoundException { int size = LongPacker.unpackInt(is); HashSet s = new HashSet(size); objectStack.add(s); for (int i = 0; i < size; i++) s.add(deserialize(is, objectStack)); return s; } private LinkedHashSet deserializeLinkedHashSet(DataInput is, FastArrayList objectStack) throws IOException, ClassNotFoundException { int size = LongPacker.unpackInt(is); LinkedHashSet s = new LinkedHashSet(size); objectStack.add(s); for (int i = 0; i < size; i++) s.add(deserialize(is, objectStack)); return s; } private TreeSet deserializeTreeSet(DataInput is, FastArrayList objectStack) throws IOException, ClassNotFoundException { int size = LongPacker.unpackInt(is); TreeSet s = new TreeSet(); objectStack.add(s); Comparator comparator = (Comparator) deserialize(is, objectStack); if (comparator != null) s = new TreeSet(comparator); for (int i = 0; i < size; i++) s.add(deserialize(is, objectStack)); return s; } private TreeMap deserializeTreeMap(DataInput is, FastArrayList objectStack) throws IOException, ClassNotFoundException { int size = LongPacker.unpackInt(is); TreeMap s = new TreeMap(); objectStack.add(s); Comparator comparator = (Comparator) deserialize(is, objectStack); if (comparator != null) s = new TreeMap(comparator); for (int i = 0; i < size; i++) s.put(deserialize(is, objectStack), deserialize(is, objectStack)); return s; } private HashMap deserializeHashMap(DataInput is, FastArrayList objectStack) throws IOException, ClassNotFoundException { int size = LongPacker.unpackInt(is); HashMap s = new HashMap(size); objectStack.add(s); for (int i = 0; i < size; i++) s.put(deserialize(is, objectStack), deserialize(is, objectStack)); return s; } private IdentityHashMap deserializeIdentityHashMap(DataInput is, FastArrayList objectStack) throws IOException, ClassNotFoundException { int size = LongPacker.unpackInt(is); IdentityHashMap s = new IdentityHashMap(size); objectStack.add(s); for (int i = 0; i < size; i++) s.put(deserialize(is, objectStack), deserialize(is, objectStack)); return s; } private LinkedHashMap deserializeLinkedHashMap(DataInput is, FastArrayList objectStack) throws IOException, ClassNotFoundException { int size = LongPacker.unpackInt(is); LinkedHashMap s = new LinkedHashMap(size); objectStack.add(s); for (int i = 0; i < size; i++) s.put(deserialize(is, objectStack), deserialize(is, objectStack)); return s; } private Hashtable deserializeHashtable(DataInput is, FastArrayList objectStack) throws IOException, ClassNotFoundException { int size = LongPacker.unpackInt(is); Hashtable s = new Hashtable(size); objectStack.add(s); for (int i = 0; i < size; i++) s.put(deserialize(is, objectStack), deserialize(is, objectStack)); return s; } private Properties deserializeProperties(DataInput is, FastArrayList objectStack) throws IOException, ClassNotFoundException { int size = LongPacker.unpackInt(is); Properties s = new Properties(); objectStack.add(s); for (int i = 0; i < size; i++) s.put(deserialize(is, objectStack), deserialize(is, objectStack)); return s; } /** * Utility class similar to ArrayList, but with fast identity search. */ static class FastArrayList { private int size = 0; private K[] elementData = (K[]) new Object[8]; K get(int index) { if (index >= size) throw new IndexOutOfBoundsException(); return elementData[index]; } void add(K o) { if (elementData.length == size) { //grow array if necessary elementData = Arrays.copyOf(elementData, elementData.length * 2); } elementData[size] = o; size++; } int size() { return size; } /** * This method is reason why ArrayList is not used. * Search an item in list and returns its index. * It uses identity rather than 'equalsTo' * One could argue that TreeMap should be used instead, * but we do not expect large object trees. * This search is VERY FAST compared to Maps, it does not allocate * new instances or uses method calls. * * @param obj * @return index of object in list or -1 if not found */ int identityIndexOf(Object obj) { for (int i = 0; i < size; i++) { if (obj == elementData[i]) return i; } return -1; } } }