/**
   * Used when a row is deleted as a result of some DML or DDL command. Adds the file space for the
   * row to the list of free positions. If there exists more than MAX_FREE_COUNT free positions,
   * then they are probably all too small, so we start a new list.
   *
   * <p>todo: This is wrong when deleting lots of records
   *
   * <p>Then remove the row from the cache data structures.
   */
  public void remove(int i, PersistentStore store) throws HsqlException {

    CachedObject r = release(i);
    int size = r == null ? getStorageSize(i) : r.getStorageSize();

    freeBlocks.add(i, size);
  }
  /**
   * Parameter write indicates either an orderly close, or a fast close without backup.
   *
   * <p>When false, just closes the file.
   *
   * <p>When true, writes out all cached rows that have been modified and the free position pointer
   * for the *.data file and then closes the file.
   */
  public void close(boolean write) throws HsqlException {

    try {
      if (cacheReadonly) {
        dataFile.close();

        return;
      }

      StopWatch sw = new StopWatch();

      if (write) {
        cache.saveAll();
        Trace.printSystemOut("saveAll: " + sw.elapsedTime());

        // set empty
        dataFile.seek(LONG_EMPTY_SIZE);
        dataFile.writeLong(freeBlocks.getLostBlocksSize());

        // set end
        dataFile.seek(LONG_FREE_POS_POS);
        dataFile.writeLong(fileFreePosition);

        // set saved flag;
        dataFile.seek(FLAGS_POS);

        int flag = BitMap.set(0, FLAG_ISSAVED);

        if (hasRowInfo) {
          flag = BitMap.set(flag, FLAG_ROWINFO);
        }

        dataFile.writeInt(flag);

        //
        dataFile.seek(fileFreePosition);
        Trace.printSystemOut("pos and flags: " + sw.elapsedTime());
      }

      if (dataFile != null) {
        dataFile.close();

        dataFile = null;

        Trace.printSystemOut("close: " + sw.elapsedTime());
      }

      boolean empty = fileFreePosition == INITIAL_FREE_POS;

      if (empty) {
        fa.removeElement(fileName);
        fa.removeElement(backupFileName);
      }
    } catch (Exception e) {
      database.logger.appLog.logContext(e);

      throw Trace.error(Trace.FILE_IO_ERROR, Trace.DataFileCache_close, new Object[] {e, fileName});
    }
  }
  /**
   * Allocates file space for the row.
   *
   * <p>A Row is added by walking the list of CacheFree objects to see if there is available space
   * to store it, reusing space if it exists. Otherwise the file is grown to accommodate it.
   */
  private int setFilePos(CachedObject r) throws IOException {

    int rowSize = r.getStorageSize();
    int i = freeBlocks == null ? -1 : freeBlocks.get(rowSize);

    if (i == -1) {
      i = (int) (fileFreePosition / cacheFileScale);

      long newFreePosition = fileFreePosition + rowSize;

      if (newFreePosition > maxDataFileSize) {
        throw new IOException(Trace.getMessage(Trace.DATA_FILE_IS_FULL));
      }

      fileFreePosition = newFreePosition;
    }

    r.setPos(i);

    return i;
  }
 public int getFreeBlockCount() {
   return freeBlocks.size();
 }
  /**
   * Parameter write indicates either an orderly close, or a fast close without backup.
   *
   * <p>When false, just closes the file.
   *
   * <p>When true, writes out all cached rows that have been modified and the free position pointer
   * for the *.data file and then closes the file.
   */
  public void close(boolean write) throws HsqlException {

    SimpleLog appLog = database.logger.appLog;

    try {
      if (cacheReadonly) {
        if (dataFile != null) {
          dataFile.close();
        }

        return;
      }

      StopWatch sw = new StopWatch();

      appLog.sendLine(SimpleLog.LOG_NORMAL, "DataFileCache.close(" + write + ") : start");

      if (write) {
        cache.saveAll();
        Trace.printSystemOut("saveAll: " + sw.elapsedTime());
        appLog.sendLine(SimpleLog.LOG_NORMAL, "DataFileCache.close() : save data");

        if (fileModified || freeBlocks.isModified()) {

          // set empty
          dataFile.seek(LONG_EMPTY_SIZE);
          dataFile.writeLong(freeBlocks.getLostBlocksSize());

          // set end
          dataFile.seek(LONG_FREE_POS_POS);
          dataFile.writeLong(fileFreePosition);

          // set saved flag;
          dataFile.seek(FLAGS_POS);

          int flag = BitMap.set(0, FLAG_ISSAVED);

          if (hasRowInfo) {
            flag = BitMap.set(flag, FLAG_ROWINFO);
          }

          dataFile.writeInt(flag);
          appLog.sendLine(SimpleLog.LOG_NORMAL, "DataFileCache.close() : flags");

          //
          if (dataFile.length() != fileFreePosition) {
            dataFile.seek(fileFreePosition);
          }

          appLog.sendLine(SimpleLog.LOG_NORMAL, "DataFileCache.close() : seek end");
          Trace.printSystemOut("pos and flags: " + sw.elapsedTime());
        }
      }

      if (dataFile != null) {
        dataFile.close();
        appLog.sendLine(SimpleLog.LOG_NORMAL, "DataFileCache.close() : close");

        dataFile = null;

        Trace.printSystemOut("close: " + sw.elapsedTime());
      }

      boolean empty = fileFreePosition == INITIAL_FREE_POS;

      if (empty) {
        fa.removeElement(fileName);
        fa.removeElement(backupFileName);
      }
    } catch (Throwable e) {
      appLog.logContext(e);

      throw Trace.error(Trace.FILE_IO_ERROR, Trace.DataFileCache_close, new Object[] {e, fileName});
    }
  }