protected void annotateClass(Class cls) throws IOException {
			ClassLoader loader = cls.getClassLoader();
			writeObject((loader instanceof INamedClassLoader) ? ((INamedClassLoader) loader)
					.getName() : null);
		}
	}

	final int skipObjectReference(byte[] obj, int offs) throws Exception {
		int oid = Bytes.unpack4(obj, offs);
		int len;
		offs += 4;
		if (oid < 0) {
			int tid = -1 - oid;

			switch (tid) {
			case ClassDescriptor.tpString:
			case ClassDescriptor.tpClass:
				offs = Bytes.skipString(obj, offs);
				break;
			case ClassDescriptor.tpArrayOfByte:
				len = Bytes.unpack4(obj, offs);
				offs += len + 4;
				break;
			case ClassDescriptor.tpArrayOfObject:
				len = Bytes.unpack4(obj, offs);
				offs += 4;

				for (int i = 0; i < len; i++) {
					offs = skipObjectReference(obj, offs);
				}

				break;
			case ClassDescriptor.tpArrayOfRaw:
				len = Bytes.unpack4(obj, offs);
				offs += 8;

				for (int i = 0; i < len; i++) {
					offs = skipObjectReference(obj, offs);
				}

				break;
			default:
				if (tid >= ClassDescriptor.tpValueTypeBias) {
					int typeOid = -ClassDescriptor.tpValueTypeBias - oid;
					ClassDescriptor desc = findClassDescriptor(typeOid);

					if (desc.isCollection) {
						len = Bytes.unpack4(obj, offs);
						offs += 4;

						for (int i = 0; i < len; i++) {
							offs = skipObjectReference(obj, offs);
						}
					}
					else if (desc.isMap) {
						len = Bytes.unpack4(obj, offs);
						offs += 4;

						for (int i = 0; i < len; i++) {
							offs = skipObjectReference(obj, offs);
							offs = skipObjectReference(obj, offs);
						}
					}
					else {
						offs = unpackObject(null, findClassDescriptor(typeOid),
								false, obj, offs, null);
					}
				}
				else {
					offs += ClassDescriptor.sizeof[tid];
				}
			}
		}

		return offs;
	}

	final Object unswizzle(ArrayPos obj, Class cls, Object parent,
			boolean recursiveLoading) throws Exception {
		byte[] body = obj.body;
		int offs = obj.offs;
		int oid = Bytes.unpack4(body, offs);
		offs += 4;
		Object val;

		if (oid < 0) {
			switch (-1 - oid) {
			case ClassDescriptor.tpBoolean:
				val = body[offs++] != 0;
				break;
			case ClassDescriptor.tpByte:
				val = body[offs++];
				break;
			case ClassDescriptor.tpChar:
				val = (char) Bytes.unpack2(body, offs);
				offs += 2;
				break;
			case ClassDescriptor.tpShort:
				val = Bytes.unpack2(body, offs);
				offs += 2;
				break;
			case ClassDescriptor.tpInt:
				val = Bytes.unpack4(body, offs);
				offs += 4;
				break;
			case ClassDescriptor.tpLong:
				val = Bytes.unpack8(body, offs);
				offs += 8;
				break;
			case ClassDescriptor.tpFloat:
				val = Bytes.unpackF4(body, offs);
				offs += 4;
				break;
			case ClassDescriptor.tpDouble:
				val = Bytes.unpackF8(body, offs);
				offs += 8;
				break;
			case ClassDescriptor.tpDate:
				val = new Date(Bytes.unpack8(body, offs));
				offs += 8;
				break;
			case ClassDescriptor.tpString:
				obj.offs = offs;
				return Bytes.unpackString(obj, encoding);
			case ClassDescriptor.tpClass:
				obj.offs = offs;
				return ClassDescriptor.loadClass(this,
						Bytes.unpackString(obj, encoding));
			case ClassDescriptor.tpLink: {
				int len = Bytes.unpack4(body, offs);
				offs += 4;
				Object[] arr = new Object[len];

				for (int j = 0; j < len; j++) {
					int elemOid = Bytes.unpack4(body, offs);
					offs += 4;

					if (elemOid != 0) {
						arr[j] = new PersistentStub(this, elemOid);
					}
				}

				val = new LinkImpl(this, arr, parent);
				break;
			}
			case ClassDescriptor.tpArrayOfByte: {
				int len = Bytes.unpack4(body, offs);
				offs += 4;
				byte[] arr = new byte[len];
				System.arraycopy(body, offs, arr, 0, len);
				offs += len;
				val = arr;
				break;
			}
			case ClassDescriptor.tpArrayOfBoolean: {
				int len = Bytes.unpack4(body, offs);
				offs += 4;
				boolean[] arr = new boolean[len];

				for (int j = 0; j < len; j++) {
					arr[j] = body[offs++] != 0;
				}

				val = arr;
				break;
			}
			case ClassDescriptor.tpArrayOfShort: {
				int len = Bytes.unpack4(body, offs);
				offs += 4;
				short[] arr = new short[len];

				for (int j = 0; j < len; j++) {
					arr[j] = Bytes.unpack2(body, offs);
					offs += 2;
				}

				val = arr;
				break;
			}
			case ClassDescriptor.tpArrayOfChar: {
				int len = Bytes.unpack4(body, offs);
				offs += 4;
				char[] arr = new char[len];

				for (int j = 0; j < len; j++) {
					arr[j] = (char) Bytes.unpack2(body, offs);
					offs += 2;
				}

				val = arr;
				break;
			}
			case ClassDescriptor.tpArrayOfInt: {
				int len = Bytes.unpack4(body, offs);
				offs += 4;
				int[] arr = new int[len];

				for (int j = 0; j < len; j++) {
					arr[j] = Bytes.unpack4(body, offs);
					offs += 4;
				}

				val = arr;
				break;
			}
			case ClassDescriptor.tpArrayOfLong: {
				int len = Bytes.unpack4(body, offs);
				offs += 4;
				long[] arr = new long[len];

				for (int j = 0; j < len; j++) {
					arr[j] = Bytes.unpack8(body, offs);
					offs += 8;
				}

				val = arr;
				break;
			}
			case ClassDescriptor.tpArrayOfFloat: {
				int len = Bytes.unpack4(body, offs);
				offs += 4;
				float[] arr = new float[len];

				for (int j = 0; j < len; j++) {
					arr[j] = Bytes.unpackF4(body, offs);
					offs += 4;
				}

				val = arr;
				break;
			}
			case ClassDescriptor.tpArrayOfDouble: {
				int len = Bytes.unpack4(body, offs);
				offs += 4;
				double[] arr = new double[len];

				for (int j = 0; j < len; j++) {
					arr[j] = Bytes.unpackF8(body, offs);
					offs += 8;
				}

				val = arr;
				break;
			}
			case ClassDescriptor.tpArrayOfObject: {
				int len = Bytes.unpack4(body, offs);
				obj.offs = offs + 4;
				Object[] arr = new Object[len];

				for (int j = 0; j < len; j++) {
					arr[j] = unswizzle(obj, Object.class, parent,
							recursiveLoading);
				}

				return arr;
			}
			case ClassDescriptor.tpArrayOfRaw: {
				int len = Bytes.unpack4(body, offs);
				int typeOid = Bytes.unpack4(body, offs + 4);
				obj.offs = offs + 8;
				Class elemType;

				if (typeOid == -1) {
					elemType = Comparable.class;
				}
				else {
					ClassDescriptor desc = findClassDescriptor(typeOid);
					elemType = desc.cls;
				}
				
				Object arr = Array.newInstance(elemType, len);

				for (int j = 0; j < len; j++) {
					Array.set(arr, j,
							unswizzle(obj, elemType, parent, recursiveLoading));
				}

				return arr;
			}
			case ClassDescriptor.tpCustom: {
				ByteArrayObjectInputStream in = new ByteArrayObjectInputStream(
						body, offs, parent, recursiveLoading, false);
				val = serializer.unpack(in);
				offs = in.getPosition();
				break;
			}
			default:
				if (oid < -ClassDescriptor.tpValueTypeBias) {
					int typeOid = -ClassDescriptor.tpValueTypeBias - oid;
					ClassDescriptor desc = findClassDescriptor(typeOid);
					val = desc.newInstance();

					if (desc.isCollection) {
						int len = Bytes.unpack4(body, offs);
						obj.offs = offs + 4;
						Collection collection = (Collection) val;

						for (int i = 0; i < len; i++) {
							collection.add(unswizzle(obj, Object.class, parent,
									recursiveLoading));
						}

						return collection;
					}
					else if (desc.isMap) {
						int len = Bytes.unpack4(body, offs);
						obj.offs = offs + 4;
						Map map = (Map) val;

						for (int i = 0; i < len; i++) {
							Object key = unswizzle(obj, Object.class, parent,
									recursiveLoading);
							Object value = unswizzle(obj, Object.class, parent,
									recursiveLoading);
							map.put(key, value);
						}

						return map;
					}
					else {
						offs = unpackObject(val, desc, recursiveLoading, body,
								offs, parent);
					}
				}
				else {
					throw new StorageError(StorageError.UNSUPPORTED_TYPE);
				}
			}
		}
		else {
			val = unswizzle(oid, cls, recursiveLoading);
		}

		obj.offs = offs;
		return val;
	}

	final int unpackObject(Object obj, ClassDescriptor desc,
			boolean recursiveLoading, byte[] body, int offs, Object parent)
			throws Exception {
		ClassDescriptor.FieldDescriptor[] all = desc.allFields;
		ReflectionProvider provider = ClassDescriptor.getReflectionProvider();
		int len;

		for (int i = 0, n = all.length; i < n; i++) {
			ClassDescriptor.FieldDescriptor fd = all[i];
			Field f = fd.field;

			if (f == null || obj == null) {
				switch (fd.type) {
				case ClassDescriptor.tpBoolean:
				case ClassDescriptor.tpByte:
					offs += 1;
					continue;
				case ClassDescriptor.tpChar:
				case ClassDescriptor.tpShort:
					offs += 2;
					continue;
				case ClassDescriptor.tpInt:
				case ClassDescriptor.tpFloat:
				case ClassDescriptor.tpEnum:
					offs += 4;
					continue;
				case ClassDescriptor.tpObject:
					offs = skipObjectReference(body, offs);
					continue;
				case ClassDescriptor.tpLong:
				case ClassDescriptor.tpDouble:
				case ClassDescriptor.tpDate:
					offs += 8;
					continue;
				case ClassDescriptor.tpString:
				case ClassDescriptor.tpClass:
					offs = Bytes.skipString(body, offs);
					continue;
				case ClassDescriptor.tpValue:
					offs = unpackObject(null, fd.valueDesc, recursiveLoading,
							body, offs, parent);
					continue;
				case ClassDescriptor.tpRaw:
				case ClassDescriptor.tpArrayOfByte:
				case ClassDescriptor.tpArrayOfBoolean:
					len = Bytes.unpack4(body, offs);
					offs += 4;

					if (len > 0) {
						offs += len;
					}
					else if (len < -1) {
						offs += ClassDescriptor.sizeof[-2 - len];
					}

					continue;
				case ClassDescriptor.tpCustom: {
					ByteArrayObjectInputStream in = new ByteArrayObjectInputStream(
							body, offs, parent, recursiveLoading, false);
					serializer.unpack(in);
					offs = in.getPosition();
					continue;
				}
				case ClassDescriptor.tpArrayOfShort:
				case ClassDescriptor.tpArrayOfChar:
					len = Bytes.unpack4(body, offs);
					offs += 4;

					if (len > 0) {
						offs += len * 2;
					}

					continue;
				case ClassDescriptor.tpArrayOfObject:
					len = Bytes.unpack4(body, offs);
					offs += 4;

					for (int j = 0; j < len; j++) {
						offs = skipObjectReference(body, offs);
					}

					continue;
				case ClassDescriptor.tpArrayOfInt:
				case ClassDescriptor.tpArrayOfEnum:
				case ClassDescriptor.tpArrayOfFloat:
				case ClassDescriptor.tpLink:
					len = Bytes.unpack4(body, offs);
					offs += 4;

					if (len > 0) {
						offs += len * 4;
					}

					continue;
				case ClassDescriptor.tpArrayOfLong:
				case ClassDescriptor.tpArrayOfDouble:
				case ClassDescriptor.tpArrayOfDate:
					len = Bytes.unpack4(body, offs);
					offs += 4;

					if (len > 0) {
						offs += len * 8;
					}

					continue;
				case ClassDescriptor.tpArrayOfString:
					len = Bytes.unpack4(body, offs);
					offs += 4;

					if (len > 0) {
						for (int j = 0; j < len; j++) {
							offs = Bytes.skipString(body, offs);
						}
					}

					continue;
				case ClassDescriptor.tpArrayOfValue:
					len = Bytes.unpack4(body, offs);
					offs += 4;

					if (len > 0) {
						ClassDescriptor valueDesc = fd.valueDesc;

						for (int j = 0; j < len; j++) {
							offs = unpackObject(null, valueDesc,
									recursiveLoading, body, offs, parent);
						}
					}

					continue;
				}
			}
			else if (offs < body.length) {
				switch (fd.type) {
				case ClassDescriptor.tpBoolean:
					provider.setBoolean(f, obj, body[offs++] != 0);
					continue;
				case ClassDescriptor.tpByte:
					provider.setByte(f, obj, body[offs++]);
					continue;
				case ClassDescriptor.tpChar:
					provider.setChar(f, obj, (char) Bytes.unpack2(body, offs));
					offs += 2;
					continue;
				case ClassDescriptor.tpShort:
					provider.setShort(f, obj, Bytes.unpack2(body, offs));
					offs += 2;
					continue;
				case ClassDescriptor.tpInt:
					provider.setInt(f, obj, Bytes.unpack4(body, offs));
					offs += 4;
					continue;
				case ClassDescriptor.tpLong:
					provider.setLong(f, obj, Bytes.unpack8(body, offs));
					offs += 8;
					continue;
				case ClassDescriptor.tpFloat:
					provider.setFloat(f, obj, Bytes.unpackF4(body, offs));
					offs += 4;
					continue;
				case ClassDescriptor.tpDouble:
					provider.setDouble(f, obj, Bytes.unpackF8(body, offs));
					offs += 8;
					continue;
				case ClassDescriptor.tpEnum: {
					int index = Bytes.unpack4(body, offs);

					if (index >= 0) {
						provider.set(f, obj, fd.field.getType()
								.getEnumConstants()[index]);
					}
					else {
						provider.set(f, obj, null);
					}

					offs += 4;
					continue;
				}
				case ClassDescriptor.tpString: {
					ArrayPos pos = new ArrayPos(body, offs);
					provider.set(f, obj, Bytes.unpackString(pos, encoding));
					offs = pos.offs;
					continue;
				}
				case ClassDescriptor.tpClass: {
					ArrayPos pos = new ArrayPos(body, offs);
					Class cls = ClassDescriptor.loadClass(this,
							Bytes.unpackString(pos, encoding));
					provider.set(f, obj, cls);
					offs = pos.offs;
					continue;
				}
				case ClassDescriptor.tpDate: {
					long msec = Bytes.unpack8(body, offs);
					offs += 8;
					Date date = null;

					if (msec >= 0) {
						date = new Date(msec);
					}

					provider.set(f, obj, date);
					continue;
				}
				case ClassDescriptor.tpObject: {
					ArrayPos pos = new ArrayPos(body, offs);
					provider.set(
							f,
							obj,
							unswizzle(pos, f.getType(), parent,
									recursiveLoading));
					offs = pos.offs;
					continue;
				}
				case ClassDescriptor.tpValue: {
					Object value = fd.valueDesc.newInstance();
					offs = unpackObject(value, fd.valueDesc, recursiveLoading,
							body, offs, parent);
					provider.set(f, obj, value);
					continue;
				}
				case ClassDescriptor.tpRaw:
					len = Bytes.unpack4(body, offs);
					offs += 4;

					if (len >= 0) {
						ByteArrayInputStream bin = new ByteArrayInputStream(
								body, offs, len);
						ObjectInputStream in = new PersistentObjectInputStream(
								bin);
						provider.set(f, obj, in.readObject());
						in.close();
						offs += len;
					}
					else if (len < 0) {
						Object val = null;

						switch (-2 - len) {
						case ClassDescriptor.tpBoolean:
							val = Boolean.valueOf(body[offs++] != 0);
							break;
						case ClassDescriptor.tpByte:
							val = new Byte(body[offs++]);
							break;
						case ClassDescriptor.tpChar:
							val = new Character(
									(char) Bytes.unpack2(body, offs));
							offs += 2;
							break;
						case ClassDescriptor.tpShort:
							val = new Short(Bytes.unpack2(body, offs));
							offs += 2;
							break;
						case ClassDescriptor.tpInt:
							val = new Integer(Bytes.unpack4(body, offs));
							offs += 4;
							break;
						case ClassDescriptor.tpLong:
							val = new Long(Bytes.unpack8(body, offs));
							offs += 8;
							break;
						case ClassDescriptor.tpFloat:
							val = new Float(Float.intBitsToFloat(Bytes.unpack4(
									body, offs)));
							offs += 4;
							break;
						case ClassDescriptor.tpDouble:
							val = new Double(Double.longBitsToDouble(Bytes
									.unpack8(body, offs)));
							offs += 8;
							break;
						case ClassDescriptor.tpDate:
							val = new Date(Bytes.unpack8(body, offs));
							offs += 8;
							break;
						case ClassDescriptor.tpObject:
							val = unswizzle(Bytes.unpack4(body, offs),
									Persistent.class, recursiveLoading);
							offs += 4;
						}

						provider.set(f, obj, val);
					}

					continue;
				case ClassDescriptor.tpCustom: {
					ByteArrayObjectInputStream in = new ByteArrayObjectInputStream(
							body, offs, parent, recursiveLoading, false);
					serializer.unpack(in);
					provider.set(f, obj, serializer.unpack(in));
					offs = in.getPosition();
					continue;
				}
				case ClassDescriptor.tpArrayOfByte:
					len = Bytes.unpack4(body, offs);
					offs += 4;

					if (len < 0) {
						provider.set(f, obj, null);
					}
					else {
						byte[] arr = new byte[len];
						System.arraycopy(body, offs, arr, 0, len);
						offs += len;
						provider.set(f, obj, arr);
					}

					continue;
				case ClassDescriptor.tpArrayOfBoolean:
					len = Bytes.unpack4(body, offs);
					offs += 4;

					if (len < 0) {
						provider.set(f, obj, null);
					}
					else {
						boolean[] arr = new boolean[len];

						for (int j = 0; j < len; j++) {
							arr[j] = body[offs++] != 0;
						}

						provider.set(f, obj, arr);
					}

					continue;
				case ClassDescriptor.tpArrayOfShort:
					len = Bytes.unpack4(body, offs);
					offs += 4;

					if (len < 0) {
						provider.set(f, obj, null);
					}
					else {
						short[] arr = new short[len];

						for (int j = 0; j < len; j++) {
							arr[j] = Bytes.unpack2(body, offs);
							offs += 2;
						}

						provider.set(f, obj, arr);
					}

					continue;
				case ClassDescriptor.tpArrayOfChar:
					len = Bytes.unpack4(body, offs);
					offs += 4;

					if (len < 0) {
						provider.set(f, obj, null);
					}
					else {
						char[] arr = new char[len];

						for (int j = 0; j < len; j++) {
							arr[j] = (char) Bytes.unpack2(body, offs);
							offs += 2;
						}

						provider.set(f, obj, arr);
					}

					continue;
				case ClassDescriptor.tpArrayOfInt:
					len = Bytes.unpack4(body, offs);
					offs += 4;

					if (len < 0) {
						provider.set(f, obj, null);
					}
					else {
						int[] arr = new int[len];

						for (int j = 0; j < len; j++) {
							arr[j] = Bytes.unpack4(body, offs);
							offs += 4;
						}

						provider.set(f, obj, arr);
					}

					continue;
				case ClassDescriptor.tpArrayOfEnum:
					len = Bytes.unpack4(body, offs);
					offs += 4;

					if (len < 0) {
						f.set(obj, null);
					}
					else {
						Class elemType = f.getType().getComponentType();
						Enum[] enumConstants = (Enum[]) elemType
								.getEnumConstants();
						Enum[] arr = (Enum[]) Array.newInstance(elemType, len);

						for (int j = 0; j < len; j++) {
							int index = Bytes.unpack4(body, offs);

							if (index >= 0) {
								arr[j] = enumConstants[index];
							}

							offs += 4;
						}

						f.set(obj, arr);
					}

					continue;
				case ClassDescriptor.tpArrayOfLong:
					len = Bytes.unpack4(body, offs);
					offs += 4;

					if (len < 0) {
						provider.set(f, obj, null);
					}
					else {
						long[] arr = new long[len];

						for (int j = 0; j < len; j++) {
							arr[j] = Bytes.unpack8(body, offs);
							offs += 8;
						}

						provider.set(f, obj, arr);
					}

					continue;
				case ClassDescriptor.tpArrayOfFloat:
					len = Bytes.unpack4(body, offs);
					offs += 4;

					if (len < 0) {
						provider.set(f, obj, null);
					}
					else {
						float[] arr = new float[len];

						for (int j = 0; j < len; j++) {
							arr[j] = Bytes.unpackF4(body, offs);
							offs += 4;
						}

						provider.set(f, obj, arr);
					}

					continue;
				case ClassDescriptor.tpArrayOfDouble:
					len = Bytes.unpack4(body, offs);
					offs += 4;

					if (len < 0) {
						provider.set(f, obj, null);
					}
					else {
						double[] arr = new double[len];

						for (int j = 0; j < len; j++) {
							arr[j] = Bytes.unpackF8(body, offs);
							offs += 8;
						}

						provider.set(f, obj, arr);
					}

					continue;
				case ClassDescriptor.tpArrayOfDate:
					len = Bytes.unpack4(body, offs);
					offs += 4;

					if (len < 0) {
						provider.set(f, obj, null);
					}
					else {
						Date[] arr = new Date[len];

						for (int j = 0; j < len; j++) {
							long msec = Bytes.unpack8(body, offs);
							offs += 8;

							if (msec >= 0) {
								arr[j] = new Date(msec);
							}
						}

						provider.set(f, obj, arr);
					}

					continue;
				case ClassDescriptor.tpArrayOfString:
					len = Bytes.unpack4(body, offs);
					offs += 4;

					if (len < 0) {
						provider.set(f, obj, null);
					}
					else {
						String[] arr = new String[len];
						ArrayPos pos = new ArrayPos(body, offs);

						for (int j = 0; j < len; j++) {
							arr[j] = Bytes.unpackString(pos, encoding);
						}

						offs = pos.offs;
						provider.set(f, obj, arr);
					}

					continue;
				case ClassDescriptor.tpArrayOfObject:
					len = Bytes.unpack4(body, offs);
					offs += 4;

					if (len < 0) {
						provider.set(f, obj, null);
					}
					else {
						Class elemType = f.getType().getComponentType();
						Object[] arr = (Object[]) Array.newInstance(elemType,
								len);
						ArrayPos pos = new ArrayPos(body, offs);

						for (int j = 0; j < len; j++) {
							arr[j] = unswizzle(pos, elemType, parent,
									recursiveLoading);
						}

						offs = pos.offs;
						provider.set(f, obj, arr);
					}

					continue;
				case ClassDescriptor.tpArrayOfValue:
					len = Bytes.unpack4(body, offs);
					offs += 4;

					if (len < 0) {
						provider.set(f, obj, null);
					}
					else {
						Class elemType = f.getType().getComponentType();
						Object[] arr = (Object[]) Array.newInstance(elemType,
								len);
						ClassDescriptor valueDesc = fd.valueDesc;

						for (int j = 0; j < len; j++) {
							Object value = valueDesc.newInstance();
							offs = unpackObject(value, valueDesc,
									recursiveLoading, body, offs, parent);
							arr[j] = value;
						}

						provider.set(f, obj, arr);
					}

					continue;
				case ClassDescriptor.tpLink:
					len = Bytes.unpack4(body, offs);
					offs += 4;

					if (len < 0) {
						provider.set(f, obj, null);
					}
					else {
						Object[] arr = new Object[len];

						for (int j = 0; j < len; j++) {
							int elemOid = Bytes.unpack4(body, offs);
							offs += 4;

							if (elemOid != 0) {
								arr[j] = new PersistentStub(this, elemOid);
							}
						}

						provider.set(f, obj, new LinkImpl(this, arr, parent));
					}
				}
			}
		}

		return offs;
	}

	final int packValue(Object value, int offs, ByteBuffer buf)
			throws Exception {
		if (value == null) {
			buf.extend(offs + 4);
			Bytes.pack4(buf.arr, offs, -1);
			offs += 4;
		}
		else if (value instanceof IPersistent) {
			buf.extend(offs + 8);
			Bytes.pack4(buf.arr, offs, -2 - ClassDescriptor.tpObject);
			Bytes.pack4(buf.arr, offs + 4,
					swizzle((IPersistent) value, buf.finalized));
			offs += 8;
		}
		else {
			Class c = value.getClass();

			if (c == Boolean.class) {
				buf.extend(offs + 5);
				Bytes.pack4(buf.arr, offs, -2 - ClassDescriptor.tpBoolean);
				buf.arr[offs + 4] = (byte) (((Boolean) value).booleanValue() ? 1
						: 0);
				offs += 5;
			}
			else if (c == Character.class) {
				buf.extend(offs + 6);
				Bytes.pack4(buf.arr, offs, -2 - ClassDescriptor.tpChar);
				Bytes.pack2(buf.arr, offs + 4,
						(short) ((Character) value).charValue());
				offs += 6;
			}
			else if (c == Byte.class) {
				buf.extend(offs + 5);
				Bytes.pack4(buf.arr, offs, -2 - ClassDescriptor.tpByte);
				buf.arr[offs + 4] = ((Byte) value).byteValue();
				offs += 5;
			}
			else if (c == Short.class) {
				buf.extend(offs + 6);
				Bytes.pack4(buf.arr, offs, -2 - ClassDescriptor.tpShort);
				Bytes.pack2(buf.arr, offs + 4, ((Short) value).shortValue());
				offs += 6;
			}
			else if (c == Integer.class) {
				buf.extend(offs + 8);
				Bytes.pack4(buf.arr, offs, -2 - ClassDescriptor.tpInt);
				Bytes.pack4(buf.arr, offs + 4, ((Integer) value).intValue());
				offs += 8;
			}
			else if (c == Long.class) {
				buf.extend(offs + 12);
				Bytes.pack4(buf.arr, offs, -2 - ClassDescriptor.tpLong);
				Bytes.pack8(buf.arr, offs + 4, ((Long) value).longValue());
				offs += 12;
			}
			else if (c == Float.class) {
				buf.extend(offs + 8);
				Bytes.pack4(buf.arr, offs, -2 - ClassDescriptor.tpFloat);
				Bytes.pack4(buf.arr, offs + 4,
						Float.floatToIntBits(((Float) value).floatValue()));
				offs += 8;
			}
			else if (c == Double.class) {
				buf.extend(offs + 12);
				Bytes.pack4(buf.arr, offs, -2 - ClassDescriptor.tpDouble);
				Bytes.pack8(buf.arr, offs + 4,
						Double.doubleToLongBits(((Double) value).doubleValue()));
				offs += 12;
			}
			else if (c == Date.class) {
				buf.extend(offs + 12);
				Bytes.pack4(buf.arr, offs, -2 - ClassDescriptor.tpDate);
				Bytes.pack8(buf.arr, offs + 4, ((Date) value).getTime());
				offs += 12;
			}
			else {
				ByteArrayOutputStream bout = new ByteArrayOutputStream();
				ObjectOutputStream out = loaderMap != null
						&& (compatibilityMode & CLASS_LOADER_SERIALIZATION_COMPATIBILITY_MODE) == 0 ? (ObjectOutputStream) new AnnotatedPersistentObjectOutputStream(
						bout)
						: (ObjectOutputStream) new PersistentObjectOutputStream(
								bout);
				out.writeObject(value);
				out.close();
				byte[] arr = bout.toByteArray();
				int len = arr.length;
				buf.extend(offs + 4 + len);
				Bytes.pack4(buf.arr, offs, len);
				offs += 4;
				System.arraycopy(arr, 0, buf.arr, offs, len);
				offs += len;
			}
		}

		return offs;
	}

	final byte[] packObject(Object obj, boolean finalized) {
		ByteBuffer buf = new ByteBuffer(this, obj, finalized);
		int offs = ObjectHeader.sizeof;
		buf.extend(offs);
		ClassDescriptor desc = getClassDescriptor(obj.getClass());

		try {
			if (obj instanceof SelfSerializable) {
				((SelfSerializable) obj).pack(buf.getOutputStream());
				offs = buf.used;
			}
			else if (desc.customSerializable) {
				serializer.pack(obj, buf.getOutputStream());
				offs = buf.used;
			}
			else {
				offs = packObject(obj, desc, offs, buf);
			}
		}
		catch (Exception x) {
			throw new StorageError(StorageError.ACCESS_VIOLATION, x);
		}

		ObjectHeader.setSize(buf.arr, 0, offs);
		ObjectHeader.setType(buf.arr, 0, desc.getOid());
		return buf.arr;
	}

	final int swizzle(ByteBuffer buf, int offs, Object obj) throws Exception {
		if (obj instanceof IPersistent || obj == null) {
			offs = buf.packI4(offs, swizzle(obj, buf.finalized));
		}
		else {
			Class t = obj.getClass();

			if (t == Boolean.class) {
				buf.extend(offs + 5);
				Bytes.pack4(buf.arr, offs, -1 - ClassDescriptor.tpBoolean);
				buf.arr[offs + 4] = (byte) (((Boolean) obj).booleanValue() ? 1
						: 0);
				offs += 5;
			}
			else if (t == Character.class) {
				buf.extend(offs + 6);
				Bytes.pack4(buf.arr, offs, -1 - ClassDescriptor.tpChar);
				Bytes.pack2(buf.arr, offs + 4,
						(short) ((Character) obj).charValue());
				offs += 6;
			}
			else if (t == Byte.class) {
				buf.extend(offs + 5);
				Bytes.pack4(buf.arr, offs, -1 - ClassDescriptor.tpByte);
				buf.arr[offs + 4] = ((Byte) obj).byteValue();
				offs += 5;
			}
			else if (t == Short.class) {
				buf.extend(offs + 6);
				Bytes.pack4(buf.arr, offs, -1 - ClassDescriptor.tpShort);
				Bytes.pack2(buf.arr, offs + 4, ((Short) obj).shortValue());
				offs += 6;
			}
			else if (t == Integer.class) {
				buf.extend(offs + 8);
				Bytes.pack4(buf.arr, offs, -1 - ClassDescriptor.tpInt);
				Bytes.pack4(buf.arr, offs + 4, ((Integer) obj).intValue());
				offs += 8;
			}
			else if (t == Long.class) {
				buf.extend(offs + 12);
				Bytes.pack4(buf.arr, offs, -1 - ClassDescriptor.tpLong);
				Bytes.pack8(buf.arr, offs + 4, ((Long) obj).longValue());
				offs += 12;
			}
			else if (t == Float.class) {
				buf.extend(offs + 8);
				Bytes.pack4(buf.arr, offs, -1 - ClassDescriptor.tpFloat);
				Bytes.packF4(buf.arr, offs + 4, ((Float) obj).floatValue());
				offs += 8;
			}
			else if (t == Double.class) {
				buf.extend(offs + 12);
				Bytes.pack4(buf.arr, offs, -1 - ClassDescriptor.tpDouble);
				Bytes.packF8(buf.arr, offs + 4, ((Double) obj).doubleValue());
				offs += 12;
			}
			else if (t == Date.class) {
				buf.extend(offs + 12);
				Bytes.pack4(buf.arr, offs, -1 - ClassDescriptor.tpDate);
				Bytes.pack8(buf.arr, offs + 4, ((Date) obj).getTime());
				offs += 12;
			}
			else if (t == String.class) {
				offs = buf.packI4(offs, -1 - ClassDescriptor.tpString);
				offs = buf.packString(offs, (String) obj);
			}
			else if (t == Class.class) {
				offs = buf.packI4(offs, -1 - ClassDescriptor.tpClass);
				offs = buf.packString(offs,
						ClassDescriptor.getClassName((Class) obj));
			}
			else if (obj instanceof LinkImpl) {
				LinkImpl link = (LinkImpl) obj;
				link.owner = buf.parent;
				int len = link.size();
				buf.extend(offs + 8 + len * 4);
				Bytes.pack4(buf.arr, offs, -1 - ClassDescriptor.tpLink);
				offs += 4;
				Bytes.pack4(buf.arr, offs, len);
				offs += 4;

				for (int j = 0; j < len; j++) {
					Bytes.pack4(buf.arr, offs,
							swizzle(link.getRaw(j), buf.finalized));
					offs += 4;
				}
			}
			else if (obj instanceof Collection
					&& (!serializeSystemCollections || t.getName().startsWith(
							"java.util."))) {
				ClassDescriptor valueDesc = getClassDescriptor(obj.getClass());
				offs = buf.packI4(offs, -ClassDescriptor.tpValueTypeBias
						- valueDesc.getOid());
				Collection c = (Collection) obj;
				offs = buf.packI4(offs, c.size());

				for (Object elem : c) {
					offs = swizzle(buf, offs, elem);
				}
			}
			else if (obj instanceof Map
					&& (!serializeSystemCollections || t.getName().startsWith(
							"java.util."))) {
				ClassDescriptor valueDesc = getClassDescriptor(obj.getClass());
				offs = buf.packI4(offs, -ClassDescriptor.tpValueTypeBias
						- valueDesc.getOid());
				Map map = (Map) obj;
				offs = buf.packI4(offs, map.size());

				for (Object entry : map.entrySet()) {
					Map.Entry e = (Map.Entry) entry;
					offs = swizzle(buf, offs, e.getKey());
					offs = swizzle(buf, offs, e.getValue());
				}
			}
			else if (obj instanceof IValue) {
				ClassDescriptor valueDesc = getClassDescriptor(obj.getClass());
				offs = buf.packI4(offs, -ClassDescriptor.tpValueTypeBias
						- valueDesc.getOid());
				offs = packObject(obj, valueDesc, offs, buf);
			}
			else if (t.isArray()) {
				Class elemType = t.getComponentType();

				if (elemType == byte.class) {
					byte[] arr = (byte[]) obj;
					int len = arr.length;
					buf.extend(offs + len + 8);
					Bytes.pack4(buf.arr, offs, -1
							- ClassDescriptor.tpArrayOfByte);
					Bytes.pack4(buf.arr, offs + 4, len);
					System.arraycopy(arr, 0, buf.arr, offs + 8, len);
					offs += 8 + len;
				}
				else if (elemType == boolean.class) {
					boolean[] arr = (boolean[]) obj;
					int len = arr.length;
					buf.extend(offs + len + 8);
					Bytes.pack4(buf.arr, offs, -1
							- ClassDescriptor.tpArrayOfBoolean);
					offs += 4;
					Bytes.pack4(buf.arr, offs, len);
					offs += 4;

					for (int i = 0; i < len; i++) {
						buf.arr[offs++] = (byte) (arr[i] ? 1 : 0);
					}
				}
				else if (elemType == char.class) {
					char[] arr = (char[]) obj;
					int len = arr.length;
					buf.extend(offs + len * 2 + 8);
					Bytes.pack4(buf.arr, offs, -1
							- ClassDescriptor.tpArrayOfChar);
					offs += 4;
					Bytes.pack4(buf.arr, offs, len);
					offs += 4;

					for (int i = 0; i < len; i++) {
						Bytes.pack2(buf.arr, offs, (short) arr[i]);
						offs += 2;
					}
				}
				else if (elemType == short.class) {
					short[] arr = (short[]) obj;
					int len = arr.length;
					buf.extend(offs + len * 2 + 8);
					Bytes.pack4(buf.arr, offs, -1
							- ClassDescriptor.tpArrayOfShort);
					offs += 4;
					Bytes.pack4(buf.arr, offs, len);
					offs += 4;

					for (int i = 0; i < len; i++) {
						Bytes.pack2(buf.arr, offs, arr[i]);
						offs += 2;
					}
				}
				else if (elemType == int.class) {
					int[] arr = (int[]) obj;
					int len = arr.length;
					buf.extend(offs + len * 4 + 8);
					Bytes.pack4(buf.arr, offs, -1
							- ClassDescriptor.tpArrayOfInt);
					offs += 4;
					Bytes.pack4(buf.arr, offs, len);
					offs += 4;

					for (int i = 0; i < len; i++) {
						Bytes.pack4(buf.arr, offs, arr[i]);
						offs += 4;
					}
				}
				else if (elemType == long.class) {
					long[] arr = (long[]) obj;
					int len = arr.length;
					buf.extend(offs + len * 8 + 8);
					Bytes.pack4(buf.arr, offs, -1
							- ClassDescriptor.tpArrayOfLong);
					offs += 4;
					Bytes.pack4(buf.arr, offs, len);
					offs += 4;

					for (int i = 0; i < len; i++) {
						Bytes.pack8(buf.arr, offs, arr[i]);
						offs += 8;
					}
				}
				else if (elemType == float.class) {
					float[] arr = (float[]) obj;
					int len = arr.length;
					buf.extend(offs + len * 4 + 8);
					Bytes.pack4(buf.arr, offs, -1
							- ClassDescriptor.tpArrayOfFloat);
					offs += 4;
					Bytes.pack4(buf.arr, offs, len);
					offs += 4;

					for (int i = 0; i < len; i++) {
						Bytes.packF4(buf.arr, offs, arr[i]);
						offs += 4;
					}
				}
				else if (elemType == double.class) {
					double[] arr = (double[]) obj;
					int len = arr.length;
					buf.extend(offs + len * 8 + 8);
					Bytes.pack4(buf.arr, offs, -1
							- ClassDescriptor.tpArrayOfLong);
					offs += 4;
					Bytes.pack4(buf.arr, offs, len);
					offs += 4;

					for (int i = 0; i < len; i++) {
						Bytes.packF8(buf.arr, offs, arr[i]);
						offs += 8;
					}
				}
				else if (elemType == Object.class) {
					offs = buf.packI4(offs, -1
							- ClassDescriptor.tpArrayOfObject);
					Object[] arr = (Object[]) obj;
					int len = arr.length;
					offs = buf.packI4(offs, len);

					for (int i = 0; i < len; i++) {
						offs = swizzle(buf, offs, arr[i]);
					}
				}
				else {
					offs = buf.packI4(offs, -1 - ClassDescriptor.tpArrayOfRaw);
					int len = Array.getLength(obj);
					offs = buf.packI4(offs, len);
					
					if (elemType.equals(Comparable.class)) {
						offs = buf.packI4(offs, -1);
					}
					else {
						ClassDescriptor desc = getClassDescriptor(elemType);
						offs = buf.packI4(offs, desc.getOid());
					}

					for (int i = 0; i < len; i++) {
						offs = swizzle(buf, offs, Array.get(obj, i));
					}
				}
			}
			else if (serializer != null && serializer.isEmbedded(obj)) {
				buf.packI4(offs, -1 - ClassDescriptor.tpCustom);
				serializer.pack(obj, buf.getOutputStream());
				offs = buf.used;
			}
			else {
				offs = buf.packI4(offs, swizzle(obj, buf.finalized));
			}
		}

		return offs;
	}

	final int packObject(Object obj, ClassDescriptor desc, int offs,
			ByteBuffer buf) throws Exception {
		ClassDescriptor.FieldDescriptor[] flds = desc.allFields;

		for (int i = 0, n = flds.length; i < n; i++) {
			ClassDescriptor.FieldDescriptor fd = flds[i];
			Field f = fd.field;

			switch (fd.type) {
			case ClassDescriptor.tpByte:
				buf.extend(offs + 1);
				buf.arr[offs++] = f.getByte(obj);
				continue;
			case ClassDescriptor.tpBoolean:
				buf.extend(offs + 1);
				buf.arr[offs++] = (byte) (f.getBoolean(obj) ? 1 : 0);
				continue;
			case ClassDescriptor.tpShort:
				buf.extend(offs + 2);
				Bytes.pack2(buf.arr, offs, f.getShort(obj));
				offs += 2;
				continue;
			case ClassDescriptor.tpChar:
				buf.extend(offs + 2);
				Bytes.pack2(buf.arr, offs, (short) f.getChar(obj));
				offs += 2;
				continue;
			case ClassDescriptor.tpInt:
				buf.extend(offs + 4);
				Bytes.pack4(buf.arr, offs, f.getInt(obj));
				offs += 4;
				continue;
			case ClassDescriptor.tpLong:
				buf.extend(offs + 8);
				Bytes.pack8(buf.arr, offs, f.getLong(obj));
				offs += 8;
				continue;
			case ClassDescriptor.tpFloat:
				buf.extend(offs + 4);
				Bytes.packF4(buf.arr, offs, f.getFloat(obj));
				offs += 4;
				continue;
			case ClassDescriptor.tpDouble:
				buf.extend(offs + 8);
				Bytes.packF8(buf.arr, offs, f.getDouble(obj));
				offs += 8;
				continue;
			case ClassDescriptor.tpEnum: {
				Enum e = (Enum) f.get(obj);
				buf.extend(offs + 4);

				if (e == null) {
					Bytes.pack4(buf.arr, offs, -1);
				}
				else {
					Bytes.pack4(buf.arr, offs, e.ordinal());
				}

				offs += 4;
				continue;
			}
			case ClassDescriptor.tpDate: {
				buf.extend(offs + 8);
				Date d = (Date) f.get(obj);
				long msec = (d == null) ? -1 : d.getTime();
				Bytes.pack8(buf.arr, offs, msec);
				offs += 8;
				continue;
			}
			case ClassDescriptor.tpString:
				offs = buf.packString(offs, (String) f.get(obj));
				continue;
			case ClassDescriptor.tpClass:
				offs = buf.packString(offs,
						ClassDescriptor.getClassName((Class) f.get(obj)));
				continue;
			case ClassDescriptor.tpObject:
				offs = swizzle(buf, offs, f.get(obj));
				continue;
			case ClassDescriptor.tpValue: {
				Object value = f.get(obj);

				if (value == null) {
					throw new StorageError(StorageError.NULL_VALUE,
							fd.fieldName);
				}
				else if (value instanceof IPersistent) {
					throw new StorageError(StorageError.SERIALIZE_PERSISTENT);
				}

				offs = packObject(value, fd.valueDesc, offs, buf);
				continue;
			}
			case ClassDescriptor.tpRaw:
				offs = packValue(f.get(obj), offs, buf);
				continue;
			case ClassDescriptor.tpCustom: {
				serializer.pack(f.get(obj), buf.getOutputStream());
				offs = buf.size();
				continue;
			}
			case ClassDescriptor.tpArrayOfByte: {
				byte[] arr = (byte[]) f.get(obj);

				if (arr == null) {
					buf.extend(offs + 4);
					Bytes.pack4(buf.arr, offs, -1);
					offs += 4;
				}
				else {
					int len = arr.length;
					buf.extend(offs + 4 + len);
					Bytes.pack4(buf.arr, offs, len);
					offs += 4;
					System.arraycopy(arr, 0, buf.arr, offs, len);
					offs += len;
				}

				continue;
			}
			case ClassDescriptor.tpArrayOfBoolean: {
				boolean[] arr = (boolean[]) f.get(obj);

				if (arr == null) {
					buf.extend(offs + 4);
					Bytes.pack4(buf.arr, offs, -1);
					offs += 4;
				}
				else {
					int len = arr.length;
					buf.extend(offs + 4 + len);
					Bytes.pack4(buf.arr, offs, len);
					offs += 4;

					for (int j = 0; j < len; j++, offs++) {
						buf.arr[offs] = (byte) (arr[j] ? 1 : 0);
					}
				}

				continue;
			}
			case ClassDescriptor.tpArrayOfShort: {
				short[] arr = (short[]) f.get(obj);

				if (arr == null) {
					buf.extend(offs + 4);
					Bytes.pack4(buf.arr, offs, -1);
					offs += 4;
				}
				else {
					int len = arr.length;
					buf.extend(offs + 4 + len * 2);
					Bytes.pack4(buf.arr, offs, len);
					offs += 4;

					for (int j = 0; j < len; j++) {
						Bytes.pack2(buf.arr, offs, arr[j]);
						offs += 2;
					}
				}

				continue;
			}
			case ClassDescriptor.tpArrayOfChar: {
				char[] arr = (char[]) f.get(obj);

				if (arr == null) {
					buf.extend(offs + 4);
					Bytes.pack4(buf.arr, offs, -1);
					offs += 4;
				}
				else {
					int len = arr.length;
					buf.extend(offs + 4 + len * 2);
					Bytes.pack4(buf.arr, offs, len);
					offs += 4;

					for (int j = 0; j < len; j++) {
						Bytes.pack2(buf.arr, offs, (short) arr[j]);
						offs += 2;
					}
				}

				continue;
			}
			case ClassDescriptor.tpArrayOfInt: {
				int[] arr = (int[]) f.get(obj);

				if (arr == null) {
					buf.extend(offs + 4);
					Bytes.pack4(buf.arr, offs, -1);
					offs += 4;
				}
				else {
					int len = arr.length;
					buf.extend(offs + 4 + len * 4);
					Bytes.pack4(buf.arr, offs, len);
					offs += 4;

					for (int j = 0; j < len; j++) {
						Bytes.pack4(buf.arr, offs, arr[j]);
						offs += 4;
					}
				}

				continue;
			}
			case ClassDescriptor.tpArrayOfEnum: {
				Enum[] arr = (Enum[]) f.get(obj);

				if (arr == null) {
					buf.extend(offs + 4);
					Bytes.pack4(buf.arr, offs, -1);
					offs += 4;
				}
				else {
					int len = arr.length;
					buf.extend(offs + 4 + len * 4);
					Bytes.pack4(buf.arr, offs, len);
					offs += 4;

					for (int j = 0; j < len; j++) {
						if (arr[j] == null) {
							Bytes.pack4(buf.arr, offs, -1);
						}
						else {
							Bytes.pack4(buf.arr, offs, arr[j].ordinal());
						}

						offs += 4;
					}
				}

				continue;
			}
			case ClassDescriptor.tpArrayOfLong: {
				long[] arr = (long[]) f.get(obj);

				if (arr == null) {
					buf.extend(offs + 4);
					Bytes.pack4(buf.arr, offs, -1);
					offs += 4;
				}
				else {
					int len = arr.length;
					buf.extend(offs + 4 + len * 8);
					Bytes.pack4(buf.arr, offs, len);
					offs += 4;

					for (int j = 0; j < len; j++) {
						Bytes.pack8(buf.arr, offs, arr[j]);
						offs += 8;
					}
				}

				continue;
			}
			case ClassDescriptor.tpArrayOfFloat: {
				float[] arr = (float[]) f.get(obj);

				if (arr == null) {
					buf.extend(offs + 4);
					Bytes.pack4(buf.arr, offs, -1);
					offs += 4;
				}
				else {
					int len = arr.length;
					buf.extend(offs + 4 + len * 4);
					Bytes.pack4(buf.arr, offs, len);
					offs += 4;

					for (int j = 0; j < len; j++) {
						Bytes.packF4(buf.arr, offs, arr[j]);
						offs += 4;
					}
				}

				continue;
			}
			case ClassDescriptor.tpArrayOfDouble: {
				double[] arr = (double[]) f.get(obj);

				if (arr == null) {
					buf.extend(offs + 4);
					Bytes.pack4(buf.arr, offs, -1);
					offs += 4;
				}
				else {
					int len = arr.length;
					buf.extend(offs + 4 + len * 8);
					Bytes.pack4(buf.arr, offs, len);
					offs += 4;

					for (int j = 0; j < len; j++) {
						Bytes.packF8(buf.arr, offs, arr[j]);
						offs += 8;
					}
				}

				continue;
			}
			case ClassDescriptor.tpArrayOfDate: {
				Date[] arr = (Date[]) f.get(obj);

				if (arr == null) {
					buf.extend(offs + 4);
					Bytes.pack4(buf.arr, offs, -1);
					offs += 4;
				}
				else {
					int len = arr.length;
					buf.extend(offs + 4 + len * 8);
					Bytes.pack4(buf.arr, offs, len);
					offs += 4;

					for (int j = 0; j < len; j++) {
						Date d = arr[j];
						long msec = (d == null) ? -1 : d.getTime();
						Bytes.pack8(buf.arr, offs, msec);
						offs += 8;
					}
				}

				continue;
			}
			case ClassDescriptor.tpArrayOfString: {
				String[] arr = (String[]) f.get(obj);

				if (arr == null) {
					buf.extend(offs + 4);
					Bytes.pack4(buf.arr, offs, -1);
					offs += 4;
				}
				else {
					int len = arr.length;
					buf.extend(offs + 4);
					Bytes.pack4(buf.arr, offs, len);
					offs += 4;

					for (int j = 0; j < len; j++) {
						offs = buf.packString(offs, (String) arr[j]);
					}
				}

				continue;
			}
			case ClassDescriptor.tpArrayOfObject: {
				Object[] arr = (Object[]) f.get(obj);

				if (arr == null) {
					buf.extend(offs + 4);
					Bytes.pack4(buf.arr, offs, -1);
					offs += 4;
				}
				else {
					int len = arr.length;
					buf.extend(offs + 4 + len * 4);
					Bytes.pack4(buf.arr, offs, len);
					offs += 4;

					for (int j = 0; j < len; j++) {
						offs = swizzle(buf, offs, arr[j]);
					}
				}

				continue;
			}
			case ClassDescriptor.tpArrayOfValue: {
				Object[] arr = (Object[]) f.get(obj);

				if (arr == null) {
					buf.extend(offs + 4);
					Bytes.pack4(buf.arr, offs, -1);
					offs += 4;
				}
				else {
					int len = arr.length;
					buf.extend(offs + 4);
					Bytes.pack4(buf.arr, offs, len);
					offs += 4;
					ClassDescriptor elemDesc = fd.valueDesc;

					for (int j = 0; j < len; j++) {
						Object value = arr[j];

						if (value == null) {
							throw new StorageError(StorageError.NULL_VALUE,
									fd.fieldName);
						}

						offs = packObject(value, elemDesc, offs, buf);
					}
				}

				continue;
			}
			case ClassDescriptor.tpLink: {
				LinkImpl link = (LinkImpl) f.get(obj);

				if (link == null) {
					buf.extend(offs + 4);
					Bytes.pack4(buf.arr, offs, -1);
					offs += 4;
				}
				else {
					link.owner = buf.parent;
					int len = link.size();
					buf.extend(offs + 4 + len * 4);
					Bytes.pack4(buf.arr, offs, len);
					offs += 4;

					for (int j = 0; j < len; j++) {
						Bytes.pack4(buf.arr, offs,
								swizzle(link.getRaw(j), buf.finalized));
						offs += 4;
					}

					if (!buf.finalized) {
						link.unpin();
					}
				}

				continue;
			}
			}
		}
		return offs;
	}

	public ClassLoader setClassLoader(ClassLoader loader) {
		ClassLoader prev = loader;
		this.loader = loader;
		return prev;
	}

	public ClassLoader getClassLoader() {
		return loader;
	}

	public void registerClassLoader(INamedClassLoader loader) {
		if (loaderMap == null) {
			loaderMap = new HashMap();
		}

		loaderMap.put(loader.getName(), loader);
	}

	public ClassLoader findClassLoader(String name) {
		if (loaderMap == null) {
			return null;
		}

		return (ClassLoader) loaderMap.get(name);
	}

	public void setCustomSerializer(CustomSerializer serializer) {
		this.serializer = serializer;
	}

	class HashIterator implements PersistentIterator, Iterator {
		Iterator oids;

		HashIterator(HashSet result) {
			oids = result.iterator();
		}

		public Object next() {
			int oid = ((Integer) oids.next()).intValue();
			return lookupObject(oid, null);
		}

		public int nextOid() {
			return oids.hasNext() ? ((Integer) oids.next()).intValue() : 0;
		}

		public boolean hasNext() {
			return oids.hasNext();
		}

		public void remove() {
			throw new UnsupportedOperationException();
		}
	}

	public Iterator merge(Iterator[] selections) {
		HashSet result = null;

		for (int i = 0; i < selections.length; i++) {
			PersistentIterator iterator = (PersistentIterator) selections[i];
			HashSet newResult = new HashSet();
			int oid;

			while ((oid = iterator.nextOid()) != 0) {
				Integer oidWrapper = new Integer(oid);
				if (result == null || result.contains(oidWrapper)) {
					newResult.add(oidWrapper);
				}
			}

			result = newResult;

			if (result.size() == 0) {
				break;
			}
		}

		if (result == null) {
			result = new HashSet();
		}

		return new HashIterator(result);
	}

	public Iterator join(Iterator[] selections) {
		HashSet result = new HashSet();

		for (int i = 0; i < selections.length; i++) {
			PersistentIterator iterator = (PersistentIterator) selections[i];
			int oid;

			while ((oid = iterator.nextOid()) != 0) {
				result.add(new Integer(oid));
			}
		}

		return new HashIterator(result);
	}

	public synchronized void registerCustomAllocator(Class cls,
			CustomAllocator allocator) {
		synchronized (objectCache) {
			ClassDescriptor desc = getClassDescriptor(cls);
			desc.allocator = allocator;
			storeObject0(desc, false);

			if (customAllocatorMap == null) {
				customAllocatorMap = new HashMap();
				customAllocatorList = new ArrayList();
			}

			customAllocatorMap.put(cls, allocator);
			customAllocatorList.add(allocator);
			reserveLocation(allocator.getSegmentBase(),
					allocator.getSegmentSize());
		}
	}

	public CustomAllocator createBitmapAllocator(int quantum, long base,
			long extension, long limit) {
		return new BitmapCustomAllocator(this, quantum, base, extension, limit);
	}

	public int getDatabaseFormatVersion() {
		return header.databaseFormatVersion;
	}

	public void deallocate(Object obj) {
		deallocateObject(obj);
	}

	public void store(Object obj) {
		if (obj instanceof IPersistent) {
			((IPersistent) obj).store();
		}
		else {
			synchronized (this) {
				synchronized (objectCache) {
					synchronized (objMap) {
						ObjectMap.Entry e = objMap.put(obj);

						if ((e.state & Persistent.RAW) != 0) {
							throw new StorageError(StorageError.ACCESS_TO_STUB);
						}

						storeObject(obj);
						e.state &= ~Persistent.DIRTY;
					}
				}
			}
		}
	}

	void unassignOid(Object obj) {
		if (obj instanceof IPersistent) {
			((IPersistent) obj).unassignOid();
		}
		else {
			objMap.remove(obj);
		}
	}

	void assignOid(Object obj, int oid, boolean raw) {
		if (obj instanceof IPersistent) {
			((IPersistent) obj).assignOid(this, oid, raw);
		}
		else {
			synchronized (objMap) {
				ObjectMap.Entry e = objMap.put(obj);
				e.oid = oid;

				if (raw) {
					e.state = Persistent.RAW;
				}
			}
		}

		if (listener != null) {
			listener.onObjectAssignOid(obj);
		}
	}

	public void modify(Object obj) {
		if (obj instanceof IPersistent) {
			((IPersistent) obj).modify();
		}
		else {
			if (useSerializableTransactions) {
				ThreadTransactionContext ctx = getTransactionContext();

				if (ctx.nested != 0) { // serializable transaction
					ctx.modified.add(obj);
					return;
				}
			}

			synchronized (this) {
				synchronized (objectCache) {
					synchronized (objMap) {
						ObjectMap.Entry e = objMap.put(obj);

						if ((e.state & Persistent.DIRTY) == 0 && e.oid != 0) {
							if ((e.state & Persistent.RAW) != 0) {
								throw new StorageError(
										StorageError.ACCESS_TO_STUB);
							}

							Assert.that((e.state & Persistent.DELETED) == 0);
							storeObject(obj);
							e.state &= ~Persistent.DIRTY;
						}
					}
				}
			}
		}
	}

	public void invalidate(Object obj) {
		if (obj instanceof IPersistent) {
			((IPersistent) obj).invalidate();
		}
		else {
			synchronized (objMap) {
				ObjectMap.Entry e = objMap.put(obj);
				e.state &= ~Persistent.DIRTY;
				e.state |= Persistent.RAW;
			}
		}
	}

	public void load(Object obj) {
		if (obj instanceof IPersistent) {
			((IPersistent) obj).load();
		}
		else {
			synchronized (objMap) {
				ObjectMap.Entry e = objMap.get(obj);

				if (e == null || (e.state & Persistent.RAW) == 0 || e.oid == 0) {
					return;
				}
			}

			loadObject(obj);
		}
	}

	boolean isLoaded(Object obj) {
		if (obj instanceof IPersistent) {
			IPersistent po = (IPersistent) obj;
			return !po.isRaw() && po.isPersistent();
		}
		else {
			synchronized (objMap) {
				ObjectMap.Entry e = objMap.get(obj);
				return e != null && (e.state & Persistent.RAW) == 0
						&& e.oid != 0;
			}
		}
	}

	public int getOid(Object obj) {
		return (obj instanceof IPersistent) ? ((IPersistent) obj).getOid()
				: obj == null ? 0 : objMap.getOid(obj);
	}

	boolean isPersistent(Object obj) {
		return getOid(obj) != 0;
	}

	boolean isDeleted(Object obj) {
		return (obj instanceof IPersistent) ? ((IPersistent) obj).isDeleted()
				: obj == null ? false
						: (objMap.getState(obj) & Persistent.DELETED) != 0;
	}

	boolean recursiveLoading(Object obj) {
		if (recursiveLoadingPolicyDefined) {
			synchronized (recursiveLoadingPolicy) {
				Class type = obj.getClass();

				do {
					Object enabled = recursiveLoadingPolicy.get(type);

					if (enabled != null) {
						return ((Boolean) enabled).booleanValue();
					}
				}
				while ((type = type.getSuperclass()) != null);
			}
		}

		return (obj instanceof IPersistent) ? ((IPersistent) obj)
				.recursiveLoading() : true;
	}

	public boolean setRecursiveLoading(Class type, boolean enabled) {
		synchronized (recursiveLoadingPolicy) {
			Object prevValue = recursiveLoadingPolicy.put(type, enabled);
			recursiveLoadingPolicyDefined = true;
			return prevValue == null ? true : ((Boolean) prevValue)
					.booleanValue();
		}
	}

	boolean isModified(Object obj) {
		return (obj instanceof IPersistent) ? ((IPersistent) obj).isModified()
				: obj == null ? false
						: (objMap.getState(obj) & Persistent.DIRTY) != 0;
	}

	boolean isRaw(Object obj) {
		return (obj instanceof IPersistent) ? ((IPersistent) obj).isRaw()
				: obj == null ? false
						: (objMap.getState(obj) & Persistent.RAW) != 0;
	}

	private ObjectMap objMap;

	protected int initIndexSize = dbDefaultInitIndexSize;
	protected int objectCacheInitSize = dbDefaultObjectCacheInitSize;
	protected long extensionQuantum = dbDefaultExtensionQuantum;
	protected String cacheKind = "default";
	protected boolean readOnly = false;
	protected boolean noFlush = false;
	protected boolean lockFile = false;
	protected boolean multiclientSupport = false;
	protected boolean alternativeBtree = false;
	protected boolean backgroundGc = false;
	protected boolean forceStore = false;
	protected boolean reloadObjectsOnRollback = false;
	protected boolean reuseOid = true;
	protected long pagePoolLruLimit = dbDefaultPagePoolLruLimit;
	protected int compatibilityMode = 0;
	protected boolean serializeSystemCollections = true;

	private HashMap customAllocatorMap;
	private ArrayList customAllocatorList;
	private CustomAllocator defaultAllocator;

	boolean replicationAck = false;
	boolean concurrentIterator = false;
	int slaveConnectionTimeout = 60; // seconds

	Properties properties = new Properties();

	String encoding = null;

	PagePool pool;
	Header header; // base address of database file mapping
	int dirtyPagesMap[]; // bitmap of changed pages in current index
	boolean modified;

	int currRBitmapPage;// current bitmap page for allocating records
	int currRBitmapOffs;// offset in current bitmap page for allocating
						// unaligned records
	int currPBitmapPage;// current bitmap page for allocating page objects
	int currPBitmapOffs;// offset in current bitmap page for allocating
						// page objects
	Location reservedChain;
	CloneNode cloneList;
	boolean insideCloneBitmap;

	int committedIndexSize;
	int currIndexSize;

	int currIndex; // copy of header.root, used to allow read access to the
					// database
					// during transaction commit
	long usedSize; // total size of allocated objects since the beginning of the
					// session
	int[] bitmapPageAvailableSpace;
	boolean opened;

	int[] greyBitmap; // bitmap of visited during GC but not yet marked object
	int[] blackBitmap; // bitmap of objects marked during GC
	long gcThreshold;
	long allocatedDelta;
	boolean gcDone;
	boolean gcActive;
	Object backgroundGcMonitor;
	Object backgroundGcStartMonitor;
	GcThread gcThread;

	int bitmapExtentBase;

	ClassLoader loader;
	HashMap loaderMap;
	HashMap recursiveLoadingPolicy;
	boolean recursiveLoadingPolicyDefined;

	CustomSerializer serializer;

	StorageListener listener;

	long transactionId;
	IFile file;

	int nNestedTransactions;
	int nBlockedTransactions;
	int nCommittedTransactions;
	long scheduledCommitTime;
	Object transactionMonitor;
	PersistentResource transactionLock;

	final ThreadLocal transactionContext = new ThreadLocal() {
		protected synchronized Object initialValue() {
			return new ThreadTransactionContext();
		}
	};

	boolean useSerializableTransactions;

	OidHashTable objectCache;
	HashMap classDescMap;
	ClassDescriptor descList;
}

class RootPage {
	long size; // database file size
	long index; // offset to object index
	long shadowIndex; // offset to shadow index
	long usedSize; // size used by objects
	int indexSize; // size of object index
	int shadowIndexSize; // size of object index
	int indexUsed; // userd part of the index
	int freeList; // L1 list of free descriptors
	int bitmapEnd; // index of last allocated bitmap page
	int rootObject; // OID of root object
	int classDescList; // List of class descriptors
	int bitmapExtent; // Offset of extended bitmap pages in object index

	final static int sizeof = 64;
}

class Header {
	int curr; // current root
	boolean dirty; // database was not closed normally
	byte databaseFormatVersion;

	RootPage root[];
	long transactionId;

	final static int sizeof = 3 + RootPage.sizeof * 2 + 8;

	final void pack(byte[] rec) {
		int offs = 0;

		rec[offs++] = (byte) curr;
		rec[offs++] = (byte) (dirty ? 1 : 0);
		rec[offs++] = databaseFormatVersion;

		for (int i = 0; i < 2; i++) {
			Bytes.pack8(rec, offs, root[i].size);
			offs += 8;
			Bytes.pack8(rec, offs, root[i].index);
			offs += 8;
			Bytes.pack8(rec, offs, root[i].shadowIndex);
			offs += 8;
			Bytes.pack8(rec, offs, root[i].usedSize);
			offs += 8;
			Bytes.pack4(rec, offs, root[i].indexSize);
			offs += 4;
			Bytes.pack4(rec, offs, root[i].shadowIndexSize);
			offs += 4;
			Bytes.pack4(rec, offs, root[i].indexUsed);
			offs += 4;
			Bytes.pack4(rec, offs, root[i].freeList);
			offs += 4;
			Bytes.pack4(rec, offs, root[i].bitmapEnd);
			offs += 4;
			Bytes.pack4(rec, offs, root[i].rootObject);
			offs += 4;
			Bytes.pack4(rec, offs, root[i].classDescList);
			offs += 4;
			Bytes.pack4(rec, offs, root[i].bitmapExtent);
			offs += 4;
		}

		Bytes.pack8(rec, offs, transactionId);
		offs += 8;
		Assert.that(offs == sizeof);
	}

	final void unpack(byte[] rec) {
		int offs = 0;
		curr = rec[offs++];
		dirty = rec[offs++] != 0;
		databaseFormatVersion = rec[offs++];
		root = new RootPage[2];

		for (int i = 0; i < 2; i++) {
			root[i] = new RootPage();
			root[i].size = Bytes.unpack8(rec, offs);
			offs += 8;
			root[i].index = Bytes.unpack8(rec, offs);
			offs += 8;
			root[i].shadowIndex = Bytes.unpack8(rec, offs);
			offs += 8;
			root[i].usedSize = Bytes.unpack8(rec, offs);
			offs += 8;
			root[i].indexSize = Bytes.unpack4(rec, offs);
			offs += 4;
			root[i].shadowIndexSize = Bytes.unpack4(rec, offs);
			offs += 4;
			root[i].indexUsed = Bytes.unpack4(rec, offs);
			offs += 4;
			root[i].freeList = Bytes.unpack4(rec, offs);
			offs += 4;
			root[i].bitmapEnd = Bytes.unpack4(rec, offs);
			offs += 4;
			root[i].rootObject = Bytes.unpack4(rec, offs);
			offs += 4;
			root[i].classDescList = Bytes.unpack4(rec, offs);
			offs += 4;
			root[i].bitmapExtent = Bytes.unpack4(rec, offs);
			offs += 4;
		}