/** This is called internally when old rows need to be removed from the cache. */
  protected synchronized void saveRows(CachedObject[] rows, int offset, int count)
      throws IOException {

    for (int i = offset; i < offset + count; i++) {
      CachedObject r = rows[i];

      saveRow(r);

      rows[i] = null;
    }

    initBuffers();
  }
  /**
   * 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;

    database.logger.appLog.logContext(SimpleLog.LOG_NORMAL, "start");

    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(HsqlDatabaseProperties.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(database, 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;
      }

      initBuffers();

      fileModified = false;
      freeBlocks = new DataFileBlockManager(FREE_BLOCKS_COUNT, cacheFileScale, freesize);

      database.logger.appLog.logContext(SimpleLog.LOG_NORMAL, "end");
    } catch (Throwable e) {
      database.logger.appLog.logContext(SimpleLog.LOG_ERROR, "failed");
      database.logger.appLog.logContext(e);
      close(false);

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