public void run() {
			try {
				while (true) {
					synchronized (backgroundGcStartMonitor) {
						while (!go && opened) {
							backgroundGcStartMonitor.wait();
						}

						if (!opened) {
							return;
						}

						go = false;
					}

					synchronized (backgroundGcMonitor) {
						if (!opened) {
							return;
						}

						mark();

						synchronized (StorageImpl.this) {
							synchronized (objectCache) {
								sweep();
							}
						}
					}
				}
			}
			catch (InterruptedException x) {}
		}
	}

	public synchronized int gc() {
		return gc0();
	}

	private int gc0() {
		synchronized (objectCache) {
			if (!opened) {
				throw new StorageError(StorageError.STORAGE_NOT_OPENED);
			}

			if (gcDone || gcActive) {
				return 0;
			}

			gcActive = true;

			if (backgroundGc) {
				if (gcThread == null) {
					gcThread = new GcThread();
				}

				gcThread.activate();
				return 0;
			}
			// System.out.println("Start GC, allocatedDelta=" + allocatedDelta +
			// ", header[" + currIndex + "].size=" + header.root[currIndex].size
			// + ", gcTreshold=" + gcThreshold);

			mark();

			return sweep();
		}
	}

	public synchronized HashMap getMemoryDump() {
		synchronized (objectCache) {
			if (!opened) {
				throw new StorageError(StorageError.STORAGE_NOT_OPENED);
			}

			int bitmapSize = (int) (header.root[currIndex].size >>> (dbAllocationQuantumBits + 5)) + 1;
			boolean existsNotMarkedObjects;
			long pos;
			int i, j, n;

			// mark
			greyBitmap = new int[bitmapSize];
			blackBitmap = new int[bitmapSize];
			int rootOid = header.root[currIndex].rootObject;
			HashMap map = new HashMap();

			if (rootOid != 0) {
				MemoryUsage indexUsage = new MemoryUsage(Index.class);
				MemoryUsage fieldIndexUsage = new MemoryUsage(FieldIndex.class);
				MemoryUsage classUsage = new MemoryUsage(Class.class);

				markOid(rootOid);

				do {
					existsNotMarkedObjects = false;

					for (i = 0; i < bitmapSize; i++) {
						if (greyBitmap[i] != 0) {
							existsNotMarkedObjects = true;

							for (j = 0; j < 32; j++) {
								if ((greyBitmap[i] & (1 << j)) != 0) {
									pos = (((long) i << 5) + j) << dbAllocationQuantumBits;
									greyBitmap[i] &= ~(1 << j);
									blackBitmap[i] |= 1 << j;
									int offs = (int) pos & (Page.pageSize - 1);
									Page pg = pool.getPage(pos - offs);
									int typeOid = ObjectHeader.getType(pg.data,
											offs);
									int objSize = ObjectHeader.getSize(pg.data,
											offs);
									int alignedSize = (objSize
											+ dbAllocationQuantum - 1)
											& ~(dbAllocationQuantum - 1);

									if (typeOid != 0) {
										markOid(typeOid);
										ClassDescriptor desc = findClassDescriptor(typeOid);

										if (Btree.class
												.isAssignableFrom(desc.cls)) {
											Btree btree = new Btree(pg.data,
													ObjectHeader.sizeof + offs);
											btree.assignOid(this, 0, false);
											int nPages = btree.markTree();

											if (FieldIndex.class
													.isAssignableFrom(desc.cls)) {
												fieldIndexUsage.nInstances += 1;
												fieldIndexUsage.totalSize += (long) nPages
														* Page.pageSize
														+ objSize;
												fieldIndexUsage.allocatedSize += (long) nPages
														* Page.pageSize
														+ alignedSize;
											}
											else {
												indexUsage.nInstances += 1;
												indexUsage.totalSize += (long) nPages
														* Page.pageSize
														+ objSize;
												indexUsage.allocatedSize += (long) nPages
														* Page.pageSize
														+ alignedSize;
											}
										}
										else {
											MemoryUsage usage = (MemoryUsage) map
													.get(desc.cls);

											if (usage == null) {
												usage = new MemoryUsage(
														desc.cls);
												map.put(desc.cls, usage);
											}

											usage.nInstances += 1;
											usage.totalSize += objSize;
											usage.allocatedSize += alignedSize;

											if (desc.hasReferences) {
												markObject(pool.get(pos),
														ObjectHeader.sizeof,
														desc);
											}
										}
									}
									else {
										classUsage.nInstances += 1;
										classUsage.totalSize += objSize;
										classUsage.allocatedSize += alignedSize;
									}

									pool.unfix(pg);
								}
							}
						}
					}
				}
				while (existsNotMarkedObjects);

				if (indexUsage.nInstances != 0) {
					map.put(Index.class, indexUsage);
				}

				if (fieldIndexUsage.nInstances != 0) {
					map.put(FieldIndex.class, fieldIndexUsage);
				}

				if (classUsage.nInstances != 0) {
					map.put(Class.class, classUsage);
				}

				MemoryUsage system = new MemoryUsage(Storage.class);
				system.totalSize += header.root[0].indexSize * 8L;
				system.totalSize += header.root[1].indexSize * 8L;
				system.totalSize += (long) (header.root[currIndex].bitmapEnd - dbBitmapId)
						* Page.pageSize;
				system.totalSize += Page.pageSize; // root page

				if (header.root[currIndex].bitmapExtent != 0) {
					system.allocatedSize = getBitmapUsedSpace(dbBitmapId,
							dbBitmapId + dbBitmapPages)
							+ getBitmapUsedSpace(
									header.root[currIndex].bitmapExtent
											+ dbBitmapPages - bitmapExtentBase,
									header.root[currIndex].bitmapExtent
											+ header.root[currIndex].bitmapEnd
											- dbBitmapId - bitmapExtentBase);
				}
				else {
					system.allocatedSize = getBitmapUsedSpace(dbBitmapId,
							header.root[currIndex].bitmapEnd);
				}

				system.nInstances = header.root[currIndex].indexSize;
				map.put(Storage.class, system);
			}

			return map;
		}
	}

	final long getBitmapUsedSpace(int from, int till) {
		long allocated = 0;

		while (from < till) {
			Page pg = getGCPage(from);

			for (int j = 0; j < Page.pageSize; j++) {
				int mask = pg.data[j] & 0xFF;

				while (mask != 0) {
					if ((mask & 1) != 0) {
						allocated += dbAllocationQuantum;
					}

					mask >>= 1;
				}
			}

			pool.unfix(pg);
			from += 1;
		}

		return allocated;
	}

	final int markObjectReference(byte[] obj, int offs) {
		int oid = Bytes.unpack4(obj, offs);
		offs += 4;

		if (oid < 0) {
			int tid = -1 - oid;
			int len;
			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 = markObjectReference(obj, offs);
				}
				break;
			case ClassDescriptor.tpArrayOfRaw:
				len = Bytes.unpack4(obj, offs);
				offs += 8;
				for (int i = 0; i < len; i++) {
					offs = markObjectReference(obj, offs);
				}
				break;
			case ClassDescriptor.tpCustom:
				try {
					ByteArrayObjectInputStream in = new ByteArrayObjectInputStream(
							obj, offs, null, false, true);
					serializer.unpack(in);
					offs = in.getPosition();
					break;
				}
				catch (IOException x) {
					throw new StorageError(StorageError.ACCESS_VIOLATION, x);
				}
			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 = markObjectReference(obj, offs);
						}
					}
					else if (desc.isMap) {
						len = Bytes.unpack4(obj, offs);
						offs += 4;
						for (int i = 0; i < len; i++) {
							offs = markObjectReference(obj, offs);
							offs = markObjectReference(obj, offs);
						}
					}
					else {
						offs = markObject(obj, offs, desc);
					}
				}
				else {
					offs += ClassDescriptor.sizeof[tid];
				}
			}
		}
		else {
			markOid(oid);
		}

		return offs;
	}

	final int markObject(byte[] obj, int offs, ClassDescriptor desc) {
		ClassDescriptor.FieldDescriptor[] all = desc.allFields;

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

			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.tpEnum:
			case ClassDescriptor.tpFloat:
				offs += 4;
				continue;
			case ClassDescriptor.tpLong:
			case ClassDescriptor.tpDouble:
			case ClassDescriptor.tpDate:
				offs += 8;
				continue;
			case ClassDescriptor.tpString:
			case ClassDescriptor.tpClass:
				offs = Bytes.skipString(obj, offs);
				continue;
			case ClassDescriptor.tpObject:
				offs = markObjectReference(obj, offs);
				continue;
			case ClassDescriptor.tpValue:
				offs = markObject(obj, offs, fd.valueDesc);
				continue;
			case ClassDescriptor.tpRaw: {
				int len = Bytes.unpack4(obj, offs);
				offs += 4;
				if (len > 0) {
					offs += len;
				}
				else if (len == -2 - ClassDescriptor.tpObject) {
					markOid(Bytes.unpack4(obj, offs));
					offs += 4;
				}
				else if (len < -1) {
					offs += ClassDescriptor.sizeof[-2 - len];
				}
				continue;
			}
			case ClassDescriptor.tpCustom:
				try {
					ByteArrayObjectInputStream in = new ByteArrayObjectInputStream(
							obj, offs, null, false, true);
					serializer.unpack(in);
					offs = in.getPosition();
				}
				catch (IOException x) {
					throw new StorageError(StorageError.ACCESS_VIOLATION, x);
				}
				continue;
			case ClassDescriptor.tpArrayOfByte:
			case ClassDescriptor.tpArrayOfBoolean: {
				int len = Bytes.unpack4(obj, offs);
				offs += 4;
				if (len > 0) {
					offs += len;
				}
				else if (len < -1) {
					offs += ClassDescriptor.sizeof[-2 - len];
				}
				continue;
			}
			case ClassDescriptor.tpArrayOfShort:
			case ClassDescriptor.tpArrayOfChar: {
				int len = Bytes.unpack4(obj, offs);
				offs += 4;
				if (len > 0) {
					offs += len * 2;
				}
				continue;
			}
			case ClassDescriptor.tpArrayOfInt:
			case ClassDescriptor.tpArrayOfEnum:
			case ClassDescriptor.tpArrayOfFloat: {
				int len = Bytes.unpack4(obj, offs);
				offs += 4;
				if (len > 0) {
					offs += len * 4;
				}
				continue;
			}
			case ClassDescriptor.tpArrayOfLong:
			case ClassDescriptor.tpArrayOfDouble:
			case ClassDescriptor.tpArrayOfDate: {
				int len = Bytes.unpack4(obj, offs);
				offs += 4;
				if (len > 0) {
					offs += len * 8;
				}
				continue;
			}
			case ClassDescriptor.tpArrayOfString: {
				int len = Bytes.unpack4(obj, offs);
				offs += 4;
				while (--len >= 0) {
					offs = Bytes.skipString(obj, offs);
				}
				continue;
			}
			case ClassDescriptor.tpArrayOfObject: {
				int len = Bytes.unpack4(obj, offs);
				offs += 4;
				while (--len >= 0) {
					offs = markObjectReference(obj, offs);
				}
				continue;
			}
			case ClassDescriptor.tpLink: {
				int len = Bytes.unpack4(obj, offs);
				offs += 4;
				while (--len >= 0) {
					markOid(Bytes.unpack4(obj, offs));
					offs += 4;
				}
				continue;
			}
			case ClassDescriptor.tpArrayOfValue: {
				int len = Bytes.unpack4(obj, offs);
				offs += 4;
				ClassDescriptor valueDesc = fd.valueDesc;
				while (--len >= 0) {
					offs = markObject(obj, offs, valueDesc);
				}
				continue;
			}
			case ClassDescriptor.tpArrayOfRaw: {
				int len = Bytes.unpack4(obj, offs);
				offs += 8;
				while (--len >= 0) {
					offs = markObjectReference(obj, offs);
				}
				continue;
			}
			}
		}

		return offs;
	}

	/**
	 * This method is used internally by Sodbox to get transaction context
	 * associated with current thread. But it can be also used by application to
	 * get transaction context, store it in some variable and use in another
	 * thread. I will make it possible to share one transaction between multiple
	 * threads.
	 * 
	 * @return transaction context associated with current thread
	 */
	public ThreadTransactionContext getTransactionContext() {
		return (ThreadTransactionContext) transactionContext.get();
	}

	/**
	 * Associate transaction context with the thread This method can be used by
	 * application to share the same transaction between multiple threads
	 * 
	 * @param ctx new transaction context
	 * @return transaction context previously associated with this thread
	 */
	public ThreadTransactionContext setTransactionContext(
			ThreadTransactionContext ctx) {
		ThreadTransactionContext oldCtx = (ThreadTransactionContext) transactionContext
				.get();
		transactionContext.set(ctx);
		return oldCtx;
	}

	public void beginSerializableTransaction() {
		if (multiclientSupport) {
			beginThreadTransaction(READ_WRITE_TRANSACTION);
		}
		else {
			beginThreadTransaction(SERIALIZABLE_TRANSACTION);
		}
	}

	public void commitSerializableTransaction() {
		if (!isInsideThreadTransaction()) {
			throw new StorageError(StorageError.NOT_IN_TRANSACTION);
		}

		endThreadTransaction(Integer.MAX_VALUE);
	}

	public void rollbackSerializableTransaction() {
		if (!isInsideThreadTransaction()) {
			throw new StorageError(StorageError.NOT_IN_TRANSACTION);
		}

		rollbackThreadTransaction();
	}

	public void beginThreadTransaction(int mode) {
		switch (mode) {
		case SERIALIZABLE_TRANSACTION:
			if (multiclientSupport) {
				throw new IllegalArgumentException("Illegal transaction mode");
			}

			useSerializableTransactions = true;
			getTransactionContext().nested += 1;
			;
			break;
		case EXCLUSIVE_TRANSACTION:
		case COOPERATIVE_TRANSACTION:
			if (multiclientSupport) {
				if (mode == EXCLUSIVE_TRANSACTION) {
					transactionLock.exclusiveLock();
				}
				else {
					transactionLock.sharedLock();
				}

				synchronized (transactionMonitor) {
					if (nNestedTransactions++ == 0) {
						file.lock(mode == READ_ONLY_TRANSACTION);
						byte[] buf = new byte[Header.sizeof];
						int rc = file.read(0, buf);

						if (rc > 0 && rc < Header.sizeof) {
							throw new StorageError(
									StorageError.DATABASE_CORRUPTED);
						}

						header.unpack(buf);
						int curr = header.curr;
						currIndex = curr;
						currIndexSize = header.root[1 - curr].indexUsed;
						committedIndexSize = currIndexSize;
						usedSize = header.root[curr].size;

						if (header.transactionId != transactionId) {
							if (bitmapPageAvailableSpace != null) {
								for (int i = 0; i < bitmapPageAvailableSpace.length; i++) {
									bitmapPageAvailableSpace[i] = Integer.MAX_VALUE;
								}
							}

							objectCache.clear();
							pool.clear();
							transactionId = header.transactionId;
						}
					}
				}
			}
			else {
				synchronized (transactionMonitor) {
					if (scheduledCommitTime != Long.MAX_VALUE) {
						nBlockedTransactions += 1;

						while (System.currentTimeMillis() >= scheduledCommitTime) {
							try {
								transactionMonitor.wait();
							}
							catch (InterruptedException x) {}
						}

						nBlockedTransactions -= 1;
					}

					nNestedTransactions += 1;
				}

				if (mode == EXCLUSIVE_TRANSACTION) {
					transactionLock.exclusiveLock();
				}
				else {
					transactionLock.sharedLock();
				}
			}
			break;
		default:
			throw new IllegalArgumentException("Illegal transaction mode");
		}
	}

	public void endThreadTransaction() {
		endThreadTransaction(Integer.MAX_VALUE);
	}

	public void endThreadTransaction(int maxDelay) {
		if (multiclientSupport) {
			if (maxDelay != Integer.MAX_VALUE) {
				throw new IllegalArgumentException(
						"Delay is not supported for global transactions");
			}

			synchronized (transactionMonitor) {
				transactionLock.unlock();

				if (nNestedTransactions != 0) { // may be everything is already
												// aborted
					if (nNestedTransactions == 1) {
						commit();
						pool.flush();
						file.unlock();
					}

					nNestedTransactions -= 1;
				}
			}

			return;
		}

		ThreadTransactionContext ctx = getTransactionContext();

		if (ctx.nested != 0) { // serializable transaction
			if (--ctx.nested == 0) {
				ArrayList modified = ctx.modified;
				ArrayList deleted = ctx.deleted;
				Map locked = ctx.locked;

				synchronized (backgroundGcMonitor) {
					synchronized (this) {
						synchronized (objectCache) {
							for (int i = modified.size(); --i >= 0;) {
								store(modified.get(i));
							}

							for (int i = deleted.size(); --i >= 0;) {
								deallocateObject0(deleted.get(i));
							}

							if (modified.size() + deleted.size() > 0) {
								commit0();
							}
						}
					}
				}

				Iterator iterator = locked.values().iterator();

				while (iterator.hasNext()) {
					((IResource) iterator.next()).reset();
				}

				modified.clear();
				deleted.clear();
				locked.clear();
			}
		}
		else { // exclusive or cooperative transaction
			synchronized (transactionMonitor) {
				transactionLock.unlock();

				if (nNestedTransactions != 0) { // may be everything is already
												// aborted
					if (--nNestedTransactions == 0) {
						nCommittedTransactions += 1;
						commit();
						scheduledCommitTime = Long.MAX_VALUE;

						if (nBlockedTransactions != 0) {
							transactionMonitor.notifyAll();
						}
					}
					else {
						if (maxDelay != Integer.MAX_VALUE) {
							long nextCommit = System.currentTimeMillis()
									+ maxDelay;
							if (nextCommit < scheduledCommitTime) {
								scheduledCommitTime = nextCommit;
							}

							if (maxDelay == 0) {
								int n = nCommittedTransactions;
								nBlockedTransactions += 1;

								do {
									try {
										transactionMonitor.wait();
									}
									catch (InterruptedException x) {}
								}
								while (nCommittedTransactions == n);

								nBlockedTransactions -= 1;
							}
						}
					}
				}
			}
		}
	}

	public boolean isInsideThreadTransaction() {
		return getTransactionContext().nested != 0 || nNestedTransactions != 0;
	}

	public void rollbackThreadTransaction() {
		if (multiclientSupport) {
			synchronized (transactionMonitor) {
				transactionLock.reset();
				rollback();
				file.unlock();
				nNestedTransactions = 0;
			}

			return;
		}

		ThreadTransactionContext ctx = getTransactionContext();

		if (ctx.nested != 0) { // serializable transaction
			ArrayList modified = ctx.modified;
			Map locked = ctx.locked;

			synchronized (this) {
				synchronized (objectCache) {
					int i = modified.size();

					while (--i >= 0) {
						Object obj = modified.get(i);
						int oid = getOid(obj);
						Assert.that(oid != 0);
						invalidate(obj);

						if (getPos(oid) == 0) {
							freeId(oid);
							objectCache.remove(oid);
						}
						else {
							loadStub(oid, obj, obj.getClass());
							objectCache.clearDirty(obj);
						}
					}
				}
			}

			Iterator iterator = locked.values().iterator();

			while (iterator.hasNext()) {
				((IResource) iterator.next()).reset();
			}

			ctx.nested = 0;
			modified.clear();
			ctx.deleted.clear();
			locked.clear();

			if (listener != null) {
				listener.onTransactionRollback();
			}
		}
		else {
			synchronized (transactionMonitor) {
				transactionLock.reset();
				nNestedTransactions = 0;

				if (nBlockedTransactions != 0) {
					transactionMonitor.notifyAll();
				}

				rollback();
			}
		}
	}

	public/* protected */boolean lockObject(Object obj) {
		if (useSerializableTransactions) {
			ThreadTransactionContext ctx = getTransactionContext();

			if (ctx.nested != 0) { // serializable transaction
				return ctx.locked.put(obj, obj) == null;
			}
		}

		return true;
	}

	public void close() {
		synchronized (backgroundGcMonitor) {
			commit();
			opened = false;
		}

		if (gcThread != null) {
			gcThread.activate();

			try {
				gcThread.join();
			}
			catch (InterruptedException x) {}
		}

		if (isDirty()) {
			Page pg = pool.putPage(0);
			header.pack(pg.data);
			pool.flush();
			pool.modify(pg);
			header.dirty = false;
			header.pack(pg.data);
			pool.unfix(pg);
			pool.flush();
		}

		pool.close();
		// make GC easier
		pool = null;
		objectCache = null;
		classDescMap = null;
		bitmapPageAvailableSpace = null;
		dirtyPagesMap = null;
		descList = null;
	}

	public synchronized void exportXML(java.io.Writer writer)
			throws java.io.IOException {
		if (!opened) {
			throw new StorageError(StorageError.STORAGE_NOT_OPENED);
		}

		objectCache.flush();

		int rootOid = header.root[1 - currIndex].rootObject;

		if (rootOid != 0) {
			XMLExporter xmlExporter = new XMLExporter(this, writer);
			xmlExporter.exportDatabase(rootOid);
		}
	}

	public synchronized void importXML(java.io.Reader reader)
			throws XMLImportException {
		if (!opened) {
			throw new StorageError(StorageError.STORAGE_NOT_OPENED);
		}

		XMLImporter xmlImporter = new XMLImporter(this, reader);
		xmlImporter.importDatabase();
	}

	private boolean getBooleanValue(Object value) {
		if (value instanceof Boolean) {
			return ((Boolean) value).booleanValue();
		}
		else if (value instanceof String) {
			String s = (String) value;

			if ("true".equalsIgnoreCase(s) || "t".equalsIgnoreCase(s)
					|| "1".equals(s)) {
				return true;
			}
			else if ("false".equalsIgnoreCase(s) || "f".equalsIgnoreCase(s)
					|| "0".equals(s)) {
				return false;
			}
		}

		throw new StorageError(StorageError.BAD_PROPERTY_VALUE);
	}

	private long getIntegerValue(Object value) {
		if (value instanceof Number) {
			return ((Number) value).longValue();
		}
		else if (value instanceof String) {
			try {
				return Long.parseLong((String) value, 10);
			}
			catch (NumberFormatException x) {}
		}

		throw new StorageError(StorageError.BAD_PROPERTY_VALUE);
	}

	public void setProperties(Properties props) {
		String value;
		properties.putAll(props);

		if ((value = props.getProperty("sodbox.implicit.values")) != null) {
			ClassDescriptor.treateAnyNonPersistentObjectAsValue = getBooleanValue(value);
		}

		if ((value = props.getProperty("sodbox.serialize.transient.objects")) != null) {
			ClassDescriptor.serializeNonPersistentObjects = getBooleanValue(value);
		}

		if ((value = props.getProperty("sodbox.object.cache.init.size")) != null) {
			objectCacheInitSize = (int) getIntegerValue(value);

			if (objectCacheInitSize <= 0) {
				throw new IllegalArgumentException(
						"Initial object cache size should be positive");
			}
		}

		if ((value = props.getProperty("sodbox.object.cache.kind")) != null) {
			cacheKind = value;
		}

		if ((value = props.getProperty("sodbox.object.index.init.size")) != null) {
			initIndexSize = (int) getIntegerValue(value);
		}

		if ((value = props.getProperty("sodbox.extension.quantum")) != null) {
			extensionQuantum = getIntegerValue(value);
		}

		if ((value = props.getProperty("sodbox.gc.threshold")) != null) {
			gcThreshold = getIntegerValue(value);
		}

		if ((value = props.getProperty("sodbox.file.readonly")) != null) {
			readOnly = getBooleanValue(value);
		}

		if ((value = props.getProperty("sodbox.file.noflush")) != null) {
			noFlush = getBooleanValue(value);
		}

		if ((value = props.getProperty("sodbox.alternative.btree")) != null) {
			alternativeBtree = getBooleanValue(value);
		}

		if ((value = props.getProperty("sodbox.background.gc")) != null) {
			backgroundGc = getBooleanValue(value);
		}

		if ((value = props.getProperty("sodbox.string.encoding")) != null) {
			encoding = value;
		}

		if ((value = props.getProperty("sodbox.lock.file")) != null) {
			lockFile = getBooleanValue(value);
		}

		if ((value = props.getProperty("sodbox.replication.ack")) != null) {
			replicationAck = getBooleanValue(value);
		}

		if ((value = props.getProperty("sodbox.concurrent.iterator")) != null) {
			concurrentIterator = getBooleanValue(value);
		}

		if ((value = props.getProperty("sodbox.slave.connection.timeout")) != null) {
			slaveConnectionTimeout = (int) getIntegerValue(value);
		}

		if ((value = props.getProperty("sodbox.force.store")) != null) {
			forceStore = getBooleanValue(value);
		}

		if ((value = props.getProperty("sodbox.page.pool.lru.limit")) != null) {
			pagePoolLruLimit = getIntegerValue(value);
		}

		if ((value = props.getProperty("sodbox.multiclient.support")) != null) {
			multiclientSupport = getBooleanValue(value);
		}

		if ((value = props.getProperty("sodbox.reload.objects.on.rollback")) != null) {
			reloadObjectsOnRollback = getBooleanValue(value);
		}

		if ((value = props.getProperty("sodbox.reuse.oid")) != null) {
			reuseOid = getBooleanValue(value);
		}

		if ((value = props.getProperty("sodbox.serialize.system.collections")) != null) {
			serializeSystemCollections = getBooleanValue(value);
		}

		if ((value = props.getProperty("sodbox.compatibility.mode")) != null) {
			compatibilityMode = (int) getIntegerValue(value);
		}

		if (multiclientSupport && backgroundGc) {
			throw new IllegalArgumentException(
					"In mutliclient access mode bachround GC is not supported");
		}
	}

	public void setProperty(String name, Object value) {
		properties.put(name, value);

		if (name.equals("sodbox.implicit.values")) {
			ClassDescriptor.treateAnyNonPersistentObjectAsValue = getBooleanValue(value);
		}
		else if (name.equals("sodbox.serialize.transient.objects")) {
			ClassDescriptor.serializeNonPersistentObjects = getBooleanValue(value);
		}
		else if (name.equals("sodbox.object.cache.init.size")) {
			objectCacheInitSize = (int) getIntegerValue(value);

			if (objectCacheInitSize <= 0) {
				throw new IllegalArgumentException(
						"Initial object cache size should be positive");
			}
		}
		else if (name.equals("sodbox.object.cache.kind")) {
			cacheKind = (String) value;
		}
		else if (name.equals("sodbox.object.index.init.size")) {
			initIndexSize = (int) getIntegerValue(value);
		}
		else if (name.equals("sodbox.extension.quantum")) {
			extensionQuantum = getIntegerValue(value);
		}
		else if (name.equals("sodbox.gc.threshold")) {
			gcThreshold = getIntegerValue(value);
		}
		else if (name.equals("sodbox.file.readonly")) {
			readOnly = getBooleanValue(value);
		}
		else if (name.equals("sodbox.file.noflush")) {
			noFlush = getBooleanValue(value);
		}
		else if (name.equals("sodbox.alternative.btree")) {
			alternativeBtree = getBooleanValue(value);
		}
		else if (name.equals("sodbox.background.gc")) {
			backgroundGc = getBooleanValue(value);
		}
		else if (name.equals("sodbox.string.encoding")) {
			encoding = (value == null) ? null : value.toString();
		}
		else if (name.equals("sodbox.lock.file")) {
			lockFile = getBooleanValue(value);
		}
		else if (name.equals("sodbox.replication.ack")) {
			replicationAck = getBooleanValue(value);
		}
		else if (name.equals("sodbox.concurrent.iterator")) {
			concurrentIterator = getBooleanValue(value);
		}
		else if (name.equals("sodbox.slave.connection.timeout")) {
			slaveConnectionTimeout = (int) getIntegerValue(value);
		}
		else if (name.equals("sodbox.force.store")) {
			forceStore = getBooleanValue(value);
		}
		else if (name.equals("sodbox.page.pool.lru.limit")) {
			pagePoolLruLimit = getIntegerValue(value);
		}
		else if (name.equals("sodbox.multiclient.support")) {
			multiclientSupport = getBooleanValue(value);
		}
		else if (name.equals("sodbox.reload.objects.on.rollback")) {
			reloadObjectsOnRollback = getBooleanValue(value);
		}
		else if (name.equals("sodbox.reuse.oid")) {
			reuseOid = getBooleanValue(value);
		}
		else if (name.equals("sodbox.compatibility.mode")) {
			compatibilityMode = (int) getIntegerValue(value);
		}
		else if (name.equals("sodbox.serialize.system.collections")) {
			serializeSystemCollections = getBooleanValue(value);
		}

		if (multiclientSupport && backgroundGc) {
			throw new IllegalArgumentException(
					"In mutliclient access mode bachround GC is not supported");
		}
	}

	public Object getProperty(String name) {
		return properties.get(name);
	}

	public Properties getProperties() {
		return properties;
	}

	public StorageListener setListener(StorageListener listener) {
		StorageListener prevListener = this.listener;
		this.listener = listener;
		return prevListener;
	}

	public StorageListener getListener() {
		return listener;
	}

	public synchronized Object getObjectByOID(int oid) {
		return oid == 0 ? null : lookupObject(oid, null);
	}

	public/* protected */synchronized void modifyObject(Object obj) {
		synchronized (objectCache) {
			if (!isModified(obj)) {
				modified = true;

				if (useSerializableTransactions) {
					ThreadTransactionContext ctx = getTransactionContext();

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

				objectCache.setDirty(obj);
			}
		}
	}

	public/* protected */synchronized void storeObject(Object obj) {
		if (!opened) {
			throw new StorageError(StorageError.STORAGE_NOT_OPENED);
		}

		if (useSerializableTransactions && getTransactionContext().nested != 0) {
			// Store should not be used in serializable transaction mode
			throw new StorageError(StorageError.INVALID_OPERATION,
					"store object");
		}

		synchronized (objectCache) {
			storeObject0(obj, false);
		}
	}

	public/* protected */void storeFinalizedObject(Object obj) {
		if (opened) {
			synchronized (objectCache) {
				if (getOid(obj) != 0) {
					storeObject0(obj, true);
				}
			}
		}
	}

	public synchronized int makePersistent(Object obj) {
		if (!opened) {
			throw new StorageError(StorageError.STORAGE_NOT_OPENED);
		}

		if (obj == null) {
			return 0;
		}

		int oid = getOid(obj);

		if (oid != 0) {
			return oid;
		}

		if (forceStore
				&& (!useSerializableTransactions || getTransactionContext().nested == 0)) {
			synchronized (objectCache) {
				storeObject0(obj, false);
			}

			return getOid(obj);
		}
		else {
			synchronized (objectCache) {
				oid = allocateId();
				assignOid(obj, oid, false);
				setPos(oid, 0);
				objectCache.put(oid, obj);
				modify(obj);
				return oid;
			}
		}
	}

	private final CustomAllocator getCustomAllocator(Class cls) {
		Object a = customAllocatorMap.get(cls);

		if (a != null) {
			return a == defaultAllocator ? null : (CustomAllocator) a;
		}

		Class superclass = cls.getSuperclass();

		if (superclass != null) {
			CustomAllocator alloc = getCustomAllocator(superclass);

			if (alloc != null) {
				customAllocatorMap.put(cls, alloc);
				return alloc;
			}
		}

		Class[] interfaces = cls.getInterfaces();

		for (int i = 0; i < interfaces.length; i++) {
			CustomAllocator alloc = getCustomAllocator(interfaces[i]);

			if (alloc != null) {
				customAllocatorMap.put(cls, alloc);
				return alloc;
			}
		}

		customAllocatorMap.put(cls, defaultAllocator);
		return null;
	}

	private final void storeObject0(Object obj, boolean finalized) {
		if (obj instanceof IStoreable) {
			((IPersistent) obj).onStore();
		}

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

		int oid = getOid(obj);
		boolean newObject = false;

		if (oid == 0) {
			oid = allocateId();

			if (!finalized) {
				objectCache.put(oid, obj);
			}

			assignOid(obj, oid, false);
			newObject = true;
		}
		else if (isModified(obj)) {
			objectCache.clearDirty(obj);
		}

		byte[] data = packObject(obj, finalized);
		long pos;
		int newSize = ObjectHeader.getSize(data, 0);
		CustomAllocator allocator = (customAllocatorMap != null) ? getCustomAllocator(obj
				.getClass()) : null;

		if (newObject || (pos = getPos(oid)) == 0) {
			pos = allocator != null ? allocator.allocate(newSize) : allocate(
					newSize, 0);
			setPos(oid, pos | dbModifiedFlag);
		}
		else {
			int offs = (int) pos & (Page.pageSize - 1);

			if ((offs & (dbFreeHandleFlag | dbPageObjectFlag)) != 0) {
				throw new StorageError(StorageError.DELETED_OBJECT);
			}

			Page pg = pool.getPage(pos - offs);
			int size = ObjectHeader.getSize(pg.data, offs & ~dbFlagsMask);
			pool.unfix(pg);

			if ((pos & dbModifiedFlag) == 0) {
				if (allocator != null) {
					allocator.free(pos & ~dbFlagsMask, size);
					pos = allocator.allocate(newSize);
				}
				else {
					cloneBitmap(pos & ~dbFlagsMask, size);
					pos = allocate(newSize, 0);
				}

				setPos(oid, pos | dbModifiedFlag);
			}
			else {
				pos &= ~dbFlagsMask;

				if (newSize != size) {
					if (allocator != null) {
						long newPos = allocator.reallocate(pos, size, newSize);

						if (newPos != pos) {
							pos = newPos;
							setPos(oid, pos | dbModifiedFlag);
						}
						else if (newSize < size) {
							ObjectHeader.setSize(data, 0, size);
						}
					}
					else {
						if (((newSize + dbAllocationQuantum - 1) & ~(dbAllocationQuantum - 1)) > ((size
								+ dbAllocationQuantum - 1) & ~(dbAllocationQuantum - 1))) {
							long newPos = allocate(newSize, 0);
							cloneBitmap(pos, size);
							free(pos, size);
							pos = newPos;
							setPos(oid, pos | dbModifiedFlag);
						}
						else if (newSize < size) {
							ObjectHeader.setSize(data, 0, size);
						}
					}
				}
			}
		}

		modified = true;
		pool.put(pos, data, newSize);
	}

	public/* protected */synchronized void loadObject(Object obj) {
		if (isRaw(obj)) {
			loadStub(getOid(obj), obj, obj.getClass());
		}
	}

	final synchronized Object lookupObject(int oid, Class cls) {
		Object obj = objectCache.get(oid);

		if (obj == null || isRaw(obj)) {
			obj = loadStub(oid, obj, cls);
		}

		return obj;
	}

	protected int swizzle(Object obj, boolean finalized) {
		int oid = 0;

		if (obj != null) {
			if (!isPersistent(obj)) {
				storeObject0(obj, finalized);
			}

			oid = getOid(obj);
		}

		return oid;
	}

	final ClassDescriptor findClassDescriptor(int oid) {
		return (ClassDescriptor) lookupObject(oid, ClassDescriptor.class);
	}

	class ByteArrayObjectInputStream extends SodboxInputStream {
		private byte[] buf;
		private Object parent;
		private boolean recursiveLoading;
		private boolean markReferences;

		ByteArrayObjectInputStream(byte[] buf, int offs, Object parent,
				boolean resursiveLoading, boolean markReferenes) {
			super(new ByteArrayInputStream(buf, offs, buf.length - offs));
			this.buf = buf;
			this.parent = parent;
			this.recursiveLoading = recursiveLoading;
			this.markReferences = markReferences;
		}

		int getPosition() throws IOException {
			return buf.length - in.available();
		}

		public String readString() throws IOException {
			int offs = getPosition();
			ArrayPos pos = new ArrayPos(buf, offs);
			String str = Bytes.unpackString(pos, encoding);
			in.skip(pos.offs - offs);
			return str;
		}

		public Object readObject() throws IOException {
			int offs = getPosition();
			Object obj = null;

			if (markReferences) {
				offs = markObjectReference(buf, offs) - offs;
			}
			else {
				try {
					ArrayPos pos = new ArrayPos(buf, offs);
					obj = unswizzle(pos, Object.class, parent, recursiveLoading);
					offs = pos.offs - offs;
				}
				catch (Exception x) {
					throw new StorageError(StorageError.ACCESS_VIOLATION, x);
				}
			}

			in.skip(offs);
			return obj;
		}
	}

	protected Object unswizzle(int oid, Class cls, boolean recursiveLoading) {
		if (oid == 0) {
			return null;
		}

		if (recursiveLoading) {
			return lookupObject(oid, cls);
		}

		Object stub = objectCache.get(oid);

		if (stub != null) {
			return stub;
		}

		ClassDescriptor desc;

		if (cls == Object.class || (desc = findClassDescriptor(cls)) == null
				|| desc.hasSubclasses) {
			long pos = getPos(oid);
			int offs = (int) pos & (Page.pageSize - 1);

			if ((offs & (dbFreeHandleFlag | dbPageObjectFlag)) != 0) {
				throw new StorageError(StorageError.DELETED_OBJECT);
			}

			Page pg = pool.getPage(pos - offs);
			int typeOid = ObjectHeader.getType(pg.data, offs & ~dbFlagsMask);
			pool.unfix(pg);
			desc = findClassDescriptor(typeOid);
		}

		stub = desc.newInstance();
		assignOid(stub, oid, true);
		objectCache.put(oid, stub);
		return stub;
	}

	final Object loadStub(int oid, Object obj, Class cls) {
		long pos = getPos(oid);

		if ((pos & (dbFreeHandleFlag | dbPageObjectFlag)) != 0) {
			throw new StorageError(StorageError.DELETED_OBJECT);
		}

		byte[] body = pool.get(pos & ~dbFlagsMask);
		ClassDescriptor desc;
		int typeOid = ObjectHeader.getType(body, 0);

		if (typeOid == 0) {
			desc = findClassDescriptor(cls);
		}
		else {
			desc = findClassDescriptor(typeOid);
		}

		// synchronized (objectCache)
		{
			if (obj == null) {
				obj = desc.customSerializable ? serializer.create(desc.cls)
						: desc.newInstance();
				objectCache.put(oid, obj);
			}

			assignOid(obj, oid, false);

			try {
				if (obj instanceof SelfSerializable) {
					((SelfSerializable) obj)
							.unpack(new ByteArrayObjectInputStream(body,
									ObjectHeader.sizeof, obj,
									recursiveLoading(obj), false));
				}
				else if (desc.customSerializable) {
					serializer.unpack(obj, new ByteArrayObjectInputStream(body,
							ObjectHeader.sizeof, obj, recursiveLoading(obj),
							false));
				}
				else {
					unpackObject(obj, desc, recursiveLoading(obj), body,
							ObjectHeader.sizeof, obj);
				}
			}
			catch (Exception x) {
				throw new StorageError(StorageError.ACCESS_VIOLATION, x);
			}
		}

		if (obj instanceof ILoadable) {
			((IPersistent) obj).onLoad();
		}

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

		return obj;
	}

	class PersistentObjectInputStream extends ObjectInputStream {
		PersistentObjectInputStream(InputStream in) throws IOException {
			super(in);
			enableResolveObject(true);
		}

		protected Object resolveObject(Object obj) throws IOException {
			int oid = getOid(obj);

			if (oid != 0) {
				return lookupObject(oid, obj.getClass());
			}

			return obj;
		}

		protected Class resolveClass(ObjectStreamClass desc)
				throws IOException, ClassNotFoundException {
			String classLoaderName = null;

			if (loaderMap != null
					&& (compatibilityMode & CLASS_LOADER_SERIALIZATION_COMPATIBILITY_MODE) == 0) {
				classLoaderName = (String) readObject();
			}

			ClassLoader cl = (classLoaderName != null) ? findClassLoader(classLoaderName)
					: loader;

			if (cl != null) {
				try {
					return Class.forName(desc.getName(), false, cl);
				}
				catch (ClassNotFoundException x) {}
			}

			return super.resolveClass(desc);
		}
	}

	public class PersistentObjectOutputStream extends ObjectOutputStream {
		PersistentObjectOutputStream(OutputStream out) throws IOException {
			super(out);
		}

		public Storage getStorage() {
			return StorageImpl.this;
		}
	}

	public class AnnotatedPersistentObjectOutputStream extends
			PersistentObjectOutputStream {
		AnnotatedPersistentObjectOutputStream(OutputStream out)
				throws IOException {
			super(out);
		}

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