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