public synchronized void open(IFile file, long pagePoolSize) {
		Page pg;
		int i;

		if (opened) {
			throw new StorageError(StorageError.STORAGE_ALREADY_OPENED);
		}

		initialize(file, pagePoolSize);

		if (multiclientSupport) {
			beginThreadTransaction(readOnly ? READ_ONLY_TRANSACTION
					: READ_WRITE_TRANSACTION);
		}

		byte[] buf = new byte[Header.sizeof];
		int rc = file.read(0, buf);
		int corruptionError = (file instanceof Rc4File || file instanceof CompressedReadWriteFile) ? StorageError.WRONG_CIPHER_KEY
				: StorageError.DATABASE_CORRUPTED;

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

		header.unpack(buf);

		if (header.curr < 0 || header.curr > 1) {
			throw new StorageError(corruptionError);
		}

		transactionId = header.transactionId;

		if (header.databaseFormatVersion == 0) { // database not initialized
			if (readOnly) {
				throw new StorageError(StorageError.READ_ONLY_DATABASE);
			}

			int indexSize = initIndexSize;

			if (indexSize < dbFirstUserId) {
				indexSize = dbFirstUserId;
			}

			indexSize = (indexSize + dbHandlesPerPage - 1)
					& ~(dbHandlesPerPage - 1);

			bitmapExtentBase = dbBitmapPages;

			header.curr = currIndex = 0;
			long used = Page.pageSize;
			header.root[0].index = used;
			header.root[0].indexSize = indexSize;
			header.root[0].indexUsed = dbFirstUserId;
			header.root[0].freeList = 0;
			used += indexSize * 8L;
			header.root[1].index = used;
			header.root[1].indexSize = indexSize;
			header.root[1].indexUsed = dbFirstUserId;
			header.root[1].freeList = 0;
			used += indexSize * 8L;

			header.root[0].shadowIndex = header.root[1].index;
			header.root[1].shadowIndex = header.root[0].index;
			header.root[0].shadowIndexSize = indexSize;
			header.root[1].shadowIndexSize = indexSize;

			int bitmapPages = (int) ((used + Page.pageSize
					* (dbAllocationQuantum * 8 - 1) - 1) / (Page.pageSize * (dbAllocationQuantum * 8 - 1)));
			long bitmapSize = (long) bitmapPages * Page.pageSize;
			int usedBitmapSize = (int) ((used + bitmapSize) >>> (dbAllocationQuantumBits + 3));

			for (i = 0; i < bitmapPages; i++) {
				pg = pool.putPage(used + (long) i * Page.pageSize);

				byte[] bitmap = pg.data;
				int n = usedBitmapSize > Page.pageSize ? Page.pageSize
						: usedBitmapSize;

				for (int j = 0; j < n; j++) {
					bitmap[j] = (byte) 0xFF;
				}

				usedBitmapSize -= Page.pageSize;
				pool.unfix(pg);
			}

			int bitmapIndexSize = ((dbBitmapId + dbBitmapPages) * 8
					+ Page.pageSize - 1)
					& ~(Page.pageSize - 1);
			byte[] index = new byte[bitmapIndexSize];
			Bytes.pack8(index, dbInvalidId * 8, dbFreeHandleFlag);

			for (i = 0; i < bitmapPages; i++) {
				Bytes.pack8(index, (dbBitmapId + i) * 8, used
						| dbPageObjectFlag);
				used += Page.pageSize;
			}

			header.root[0].bitmapEnd = dbBitmapId + i;
			header.root[1].bitmapEnd = dbBitmapId + i;

			while (i < dbBitmapPages) {
				Bytes.pack8(index, (dbBitmapId + i) * 8, dbFreeHandleFlag);
				i += 1;
			}

			header.root[0].size = used;
			header.root[1].size = used;
			usedSize = used;
			committedIndexSize = currIndexSize = dbFirstUserId;

			pool.write(header.root[1].index, index);
			pool.write(header.root[0].index, index);

			modified = true;
			header.dirty = true;
			header.root[0].size = header.root[1].size;
			pg = pool.putPage(0);
			header.pack(pg.data);
			pool.flush();
			pool.modify(pg);
			header.databaseFormatVersion = dbDatabaseFormatVersion;
			header.pack(pg.data);
			pool.unfix(pg);
			pool.flush();
		}
		else {
			int curr = header.curr;

			currIndex = curr;

			if (header.root[curr].indexSize != header.root[curr].shadowIndexSize) {
				throw new StorageError(corruptionError);
			}

			bitmapExtentBase = (header.databaseFormatVersion < 2) ? 0
					: dbBitmapPages;

			if (isDirty()) {
				if (listener != null) {
					listener.databaseCorrupted();
				}

				if (LOGGER.isWarnEnabled()) {
					LOGGER.warn("Database was not normally closed: start recovery");
				}

				header.root[1 - curr].size = header.root[curr].size;
				header.root[1 - curr].indexUsed = header.root[curr].indexUsed;
				header.root[1 - curr].freeList = header.root[curr].freeList;
				header.root[1 - curr].index = header.root[curr].shadowIndex;
				header.root[1 - curr].indexSize = header.root[curr].shadowIndexSize;
				header.root[1 - curr].shadowIndex = header.root[curr].index;
				header.root[1 - curr].shadowIndexSize = header.root[curr].indexSize;
				header.root[1 - curr].bitmapEnd = header.root[curr].bitmapEnd;
				header.root[1 - curr].rootObject = header.root[curr].rootObject;
				header.root[1 - curr].classDescList = header.root[curr].classDescList;
				header.root[1 - curr].bitmapExtent = header.root[curr].bitmapExtent;

				modified = true;
				pg = pool.putPage(0);
				header.pack(pg.data);
				pool.unfix(pg);

				pool.copy(header.root[1 - curr].index, header.root[curr].index,
						(header.root[curr].indexUsed * 8L + Page.pageSize - 1)
								& ~(Page.pageSize - 1));

				if (listener != null) {
					listener.recoveryCompleted();
				}

				if (LOGGER.isWarnEnabled()) {
					LOGGER.warn("Database recovery completed");
				}
			}

			currIndexSize = header.root[1 - curr].indexUsed;
			committedIndexSize = currIndexSize;
			usedSize = header.root[curr].size;
		}

		int bitmapSize = header.root[1 - currIndex].bitmapExtent == 0 ? dbBitmapPages
				: dbLargeBitmapPages;

		bitmapPageAvailableSpace = new int[bitmapSize];

		for (i = 0; i < bitmapPageAvailableSpace.length; i++) {
			bitmapPageAvailableSpace[i] = Integer.MAX_VALUE;
		}

		currRBitmapPage = currPBitmapPage = 0;
		currRBitmapOffs = currPBitmapOffs = 0;

		opened = true;
		reloadScheme();

		if (multiclientSupport) {
			// modified = true; ??? Why it is needed here?
			endThreadTransaction();
		}
		else {
			commit(); // commit scheme changes
		}
	}