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;
	}