public StorageRootFile(String dbPath, String options, int pageSize, FreeSpaceManager fsm) {
   this.fsm = fsm;
   PAGE_SIZE = pageSize;
   File file = new File(dbPath);
   if (!file.exists()) {
     throw DBLogger.newUser("DB file does not exist: " + dbPath);
   }
   try {
     raf = new RandomAccessFile(file, options);
     fc = raf.getChannel();
     try {
       // tryLock is supposed to return null, but it throws an Exception
       fileLock = fc.tryLock();
       if (fileLock == null) {
         fc.close();
         raf.close();
         throw DBLogger.newUser("This file is in use by another process: " + dbPath);
       }
     } catch (OverlappingFileLockException e) {
       fc.close();
       raf.close();
       throw DBLogger.newUser("This file is in use by another PersistenceManager: " + dbPath);
     }
     if (ZooDebug.isTesting()) {
       ZooDebug.registerFile(fc);
     }
   } catch (IOException e) {
     throw DBLogger.newFatal("Error opening database: " + dbPath, e);
   }
 }
  /**
   * In case this is an existing index, read() should be called afterwards. Key and value length are
   * used to calculate the man number of entries on a page.
   *
   * @param file The read/write byte stream.
   * @param isNew Whether this is a new index or existing (i.e. read from disk).
   * @param keyLen The number of bytes required for the key.
   * @param valLen The number of bytes required for the value.
   */
  public AbstractPagedIndex(
      StorageChannel file,
      boolean isNew,
      int keyLen,
      int valLen,
      boolean isUnique,
      PAGE_TYPE dataType) {
    super(file, isNew, isUnique);

    in = file.getReader(false);
    out = file.getWriter(false);
    int pageSize = file.getPageSize();
    this.dataType = dataType;

    keySize = keyLen;
    valSize = valLen;

    // how many entries fit on one page?
    //
    // all pages:
    // - 0 byte for flags (isRoot=(root==null)), isLeaf(leaves==0), isDirty(is transient))
    // - // NO! root page: 4 byte  -> Don't store the root! We would have to update it!!!
    // - # leaves: 2 byte
    // - # entries: 2 byte
    // - TODO index-ID (oid)?
    // - TODO index-type (oid for the schema of an index)????
    //
    // middle pages:
    // - keyLen = keyLen ; refLen = 4byte (pageID)
    // - n values; n+1 references
    // ---> n = (PAGE_SIZE - 4 - refLen) / (keyLen + refLen)
    //
    // leave pages: keyLen = keyLen ; valLen = valLen
    // - n values
    // ---> n = (PAGE_SIZE - 4) / (keyLen + valLen)

    final int pageHeader = 4 + DiskIO.PAGE_HEADER_SIZE; // 2 + 2 + general_header
    final int refLen = 4; // one int for pageID
    // we use only int, so it should round down automatically...
    maxLeafN = (pageSize - pageHeader) / (keyLen + valLen);
    if (maxLeafN * (keyLen + valLen) + pageHeader > pageSize) {
      throw DBLogger.newFatalInternal("Illegal Index size: " + maxLeafN);
    }
    minLeafN = maxLeafN >> 1;

    int innerEntrySize = keyLen + refLen;
    if (!isUnique) {
      innerEntrySize += valLen;
    }
    // -2 for short nKeys
    maxInnerN = (pageSize - pageHeader - refLen - 2) / innerEntrySize;
    if (maxInnerN * innerEntrySize + pageHeader + refLen > pageSize) {
      throw DBLogger.newFatalInternal("Illegal Index size: " + maxInnerN);
    }
    minInnerN = maxInnerN >> 1;

    DBLogger.debugPrintln(1, "OidIndex entries per page: " + maxLeafN + " / inner: " + maxInnerN);
  }
 public void checkValidity(int modCount, long txId) {
   if (this.file.getTxId() != txId) {
     throw DBLogger.newUser("This iterator has been invalidated by commit() or rollback().");
   }
   if (this.modCount != modCount) {
     throw new ConcurrentModificationException();
   }
 }
 @Override
 public int statsGetPageCount() {
   try {
     return (int) (raf.length() / PAGE_SIZE);
   } catch (IOException e) {
     throw DBLogger.newFatal("", e);
   }
 }
 @Override
 public final void readPage(ByteBuffer buf, long pageId) {
   try {
     fc.read(buf, pageId * PAGE_SIZE);
     if (DBStatistics.isEnabled()) {
       statNRead++;
       statNReadUnique.put(pageId, null);
     }
   } catch (IOException e) {
     throw DBLogger.newFatal("Error loading Page: " + pageId, e);
   }
 }
 @Override
 public final void close() {
   flush();
   try {
     fc.force(true);
     fileLock.release();
     fc.close();
     raf.close();
   } catch (IOException e) {
     throw DBLogger.newFatal("Error closing database file.", e);
   }
 }
 @Override
 public final void write(ByteBuffer buf, long pageId) {
   try {
     if (pageId < 0) {
       return;
     }
     if (DBStatistics.isEnabled()) {
       statNWrite++;
     }
     fc.write(buf, pageId * PAGE_SIZE);
   } catch (IOException e) {
     throw DBLogger.newFatal("Error writing page: " + pageId, e);
   }
 }
 /** Not a true flush, just writes the stuff... */
 @Override
 public final void flush() {
   // flush associated splits.
   for (StorageChannelOutput paf : viewsOut) {
     // flush() only writers
     paf.flush();
   }
   for (StorageChannelInput paf : viewsIn) {
     paf.reset();
   }
   try {
     fc.force(false);
   } catch (IOException e) {
     throw DBLogger.newFatal("Error writing database file.", e);
   }
 }