/**
   * Writes out the specified Row. Will write only the Nodes or both Nodes and table row data
   * depending on what is not already persisted to disk.
   */
  private void saveRow(CachedObject row) throws IOException {

    setFileModified();
    rowOut.reset();
    row.write(rowOut);
    dataFile.seek((long) row.getPos() * cacheFileScale);
    dataFile.write(rowOut.getOutputStream().getBuffer(), 0, rowOut.getOutputStream().size());
  }
  protected boolean readObject(int pos) throws IOException {

    dataFile.seek((long) pos * cacheFileScale);

    int size = dataFile.readInt();

    rowIn.resetRow(pos, size);
    dataFile.read(rowIn.getBuffer(), 4, size - 4);

    return true;
  }
  /**
   * 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});
    }
  }
  protected synchronized RowInputInterface readObject(int pos) throws IOException {

    dataFile.seek((long) pos * cacheFileScale);

    int size = dataFile.readInt();

    rowIn.resetRow(pos, size);
    dataFile.read(rowIn.getBuffer(), 4, size - 4);

    return rowIn;
  }
  protected void setFileModified() throws IOException {

    if (!fileModified) {

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

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

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

      dataFile.writeInt(flag);

      fileModified = true;
    }
  }
  protected int readSize(int pos) throws IOException {

    dataFile.seek((long) pos * cacheFileScale);

    return dataFile.readInt();
  }
  /** Writes out all the rows to a new file without fragmentation. */
  public void defrag() throws HsqlException {

    if (cacheReadonly) {
      return;
    }

    if (fileFreePosition == INITIAL_FREE_POS) {
      return;
    }

    try {
      boolean wasNio = dataFile.wasNio();
      DataFileDefrag dfd = new DataFileDefrag(database, this, fileName);

      dfd.process();
      close(false);
      Trace.printSystemOut("closed old cache");

      // first attemp to delete
      fa.removeElement(fileName);

      if (wasNio) {
        System.gc();

        if (fa.isStreamElement(fileName)) {
          fa.removeElement(fileName);

          if (fa.isStreamElement(fileName)) {
            fa.renameElement(fileName, fileName + ".old");

            File oldfile = new File(fileName + ".old");

            FileUtil.deleteOnExit(oldfile);
          }
        }
      }

      // [email protected] - change to file access api
      fa.renameElement(fileName + ".new", fileName);
      backup();
      database
          .getProperties()
          .setProperty("hsqldb.cache_version", HsqlDatabaseProperties.VERSION_STRING_1_7_0);
      database.getProperties().save();
      initParams();

      cache = new Cache(this);

      open(cacheReadonly);
      dfd.updateTableIndexRoots();
      Trace.printSystemOut("opened cache");
    } catch (Exception e) {
      database.logger.appLog.logContext(e);

      throw new HsqlException(e, Trace.getMessage(Trace.GENERAL_IO_ERROR), Trace.GENERAL_IO_ERROR);
      /*
      Trace.error(Trace.FILE_IO_ERROR, Trace.DataFileCache_defrag, new Object[] {
          e, fileName
      });
      */
    }
  }
  /**
   * Opens the *.data file for this cache, setting the variables that allow access to the particular
   * database version of the *.data file.
   */
  public void open(boolean readonly) throws HsqlException {

    fileFreePosition = 0;

    try {
      boolean preexists = database.isFilesInJar();
      long freesize = 0;

      if (!preexists && fa.isStreamElement(fileName)) {
        if (database.isStoredFileAccess()) {
          preexists = true;
        } else {

          // discard "empty" databases
          File f = new File(fileName);

          preexists = f.length() > INITIAL_FREE_POS;
        }
      }

      if (preexists) {
        String version =
            database.getProperties().getProperty(HsqlDatabaseProperties.hsqldb_cache_version);
        boolean v17 = HsqlDatabaseProperties.VERSION_STRING_1_7_0.equals(version);

        // for later versions
        boolean v18 = HsqlDatabaseProperties.VERSION_STRING_1_8_0.equals(version);

        if (!v17) {
          throw Trace.error(Trace.WRONG_DATABASE_FILE_VERSION);
        }
      }

      boolean isNio = database.getProperties().isPropertyTrue("hsqldb.nio_data_file");
      int fileType = isNio ? ScaledRAFile.DATA_FILE_NIO : ScaledRAFile.DATA_FILE_RAF;

      if (database.isFilesInJar()) {
        fileType = ScaledRAFile.DATA_FILE_JAR;
      }

      // [email protected] - change to file access api
      String cname = database.getURLProperties().getProperty("storage_class_name");
      String skey = database.getURLProperties().getProperty("storage_key");

      dataFile = ScaledRAFile.newScaledRAFile(fileName, readonly, fileType, cname, skey);

      if (preexists) {
        dataFile.seek(FLAGS_POS);

        int flags = dataFile.readInt();

        hasRowInfo = BitMap.isSet(flags, FLAG_ROWINFO);

        dataFile.seek(LONG_EMPTY_SIZE);

        freesize = dataFile.readLong();

        dataFile.seek(LONG_FREE_POS_POS);

        fileFreePosition = dataFile.readLong();

        if (fileFreePosition < INITIAL_FREE_POS) {
          fileFreePosition = INITIAL_FREE_POS;
        }
      } else {
        fileFreePosition = INITIAL_FREE_POS;
      }

      resetBuffers();

      fileModified = false;
      freeBlocks = new DataFileBlockManager(FREE_BLOCKS_COUNT, cacheFileScale, freesize);
    } catch (Exception e) {
      database.logger.appLog.logContext(e);
      close(false);

      throw Trace.error(Trace.FILE_IO_ERROR, Trace.DataFileCache_open, new Object[] {e, fileName});
    }
  }
  /**
   * 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});
    }
  }
  void process() throws IOException {

    boolean complete = false;

    Error.printSystemOut("Defrag Transfer begins");

    transactionRowLookup = database.txManager.getTransactionIDList();

    HsqlArrayList allTables = database.schemaManager.getAllTables();

    rootsList = new int[allTables.size()][];

    Storage dest = null;

    try {
      OutputStream fos =
          database.logger.getFileAccess().openOutputStreamElement(dataFileName + ".new");

      fileStreamOut = new BufferedOutputStream(fos, 1 << 12);

      for (int i = 0; i < DataFileCache.INITIAL_FREE_POS; i++) {
        fileStreamOut.write(0);
      }

      fileOffset = DataFileCache.INITIAL_FREE_POS;

      for (int i = 0, tSize = allTables.size(); i < tSize; i++) {
        Table t = (Table) allTables.get(i);

        if (t.getTableType() == TableBase.CACHED_TABLE) {
          int[] rootsArray = writeTableToDataFile(t);

          rootsList[i] = rootsArray;
        } else {
          rootsList[i] = null;
        }

        Error.printSystemOut(t.getName().name + " complete");
      }

      writeTransactionRows();
      fileStreamOut.flush();
      fileStreamOut.close();

      fileStreamOut = null;

      // write out the end of file position
      dest =
          ScaledRAFile.newScaledRAFile(
              database,
              dataFileName + ".new",
              false,
              ScaledRAFile.DATA_FILE_RAF,
              database
                  .getURLProperties()
                  .getProperty(HsqlDatabaseProperties.url_storage_class_name),
              database.getURLProperties().getProperty(HsqlDatabaseProperties.url_storage_key));

      dest.seek(DataFileCache.LONG_FREE_POS_POS);
      dest.writeLong(fileOffset);

      // set shadowed flag;
      int flags = 0;

      if (database.logger.propIncrementBackup) {
        flags = BitMap.set(flags, DataFileCache.FLAG_ISSHADOWED);
      }

      flags = BitMap.set(flags, DataFileCache.FLAG_190);
      flags = BitMap.set(flags, DataFileCache.FLAG_ISSAVED);

      dest.seek(DataFileCache.FLAGS_POS);
      dest.writeInt(flags);
      dest.close();

      dest = null;

      for (int i = 0, size = rootsList.length; i < size; i++) {
        int[] roots = rootsList[i];

        if (roots != null) {
          Error.printSystemOut(org.hsqldb.lib.StringUtil.getList(roots, ",", ""));
        }
      }

      complete = true;
    } catch (IOException e) {
      throw Error.error(ErrorCode.FILE_IO_ERROR, dataFileName + ".new");
    } catch (OutOfMemoryError e) {
      throw Error.error(ErrorCode.OUT_OF_MEMORY);
    } finally {
      if (fileStreamOut != null) {
        fileStreamOut.close();
      }

      if (dest != null) {
        dest.close();
      }

      if (!complete) {
        database.logger.getFileAccess().removeElement(dataFileName + ".new");
      }
    }

    // Error.printSystemOut("Transfer complete: ", stopw.elapsedTime());
  }