/**
   * Method declaration
   *
   * @throws SQLException
   */
  void checkpoint(boolean defrag) throws SQLException {

    if (defrag) {
      ArrayList rootsArray = cCache.defrag();

      for (int i = 0; i < rootsArray.size(); i++) {
        int[] roots = (int[]) rootsArray.get(i);

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

      DataFileDefrag2.updateTableIndexRoots(dDatabase.getTables(), rootsArray);
    }

    close(false);
    pProperties.setProperty("modified", "yes");
    pProperties.save();

    if (cCache != null) {
      cCache.open(false);
    }

    reopenAllTextCaches();
    openScript();
  }
  private void create() throws SQLException {

    if (Trace.TRACE) {
      Trace.trace(sName);
    }

    pProperties.setProperty("version", jdbcDriver.VERSION);
    pProperties.setProperty("sql.strict_fk", true);
    pProperties.save();
  }
  /**
   * Opens this database. The database should be opened after construction. or reopened by the
   * close(int closemode) method during a "shutdown compact". Closes the log if there is an error.
   */
  void reopen() throws HsqlException {

    setState(DATABASE_OPENING);

    try {
      User sysUser;

      isNew =
          (sType == DatabaseManager.S_MEM
              || !HsqlProperties.checkFileExists(sPath, isFilesInJar(), getClass()));
      databaseProperties = new HsqlDatabaseProperties(this);

      databaseProperties.load();
      databaseProperties.setURLProperties(urlProperties);
      compiledStatementManager.reset();

      tTable = new HsqlArrayList();
      userManager = new UserManager();
      hAlias = Library.getAliasMap();
      nameManager = new HsqlNameManager();
      triggerNameList = new DatabaseObjectNames();
      indexNameList = new DatabaseObjectNames();
      constraintNameList = new DatabaseObjectNames();
      sequenceManager = new SequenceManager();
      bReferentialIntegrity = true;
      sysUser = userManager.createSysUser(this);
      sessionManager = new SessionManager(this, sysUser);
      dInfo = DatabaseInformation.newDatabaseInformation(this);

      if (sType != DatabaseManager.S_MEM) {
        logger.openLog(this);
      }

      if (isNew) {
        sessionManager
            .getSysSession()
            .sqlExecuteDirectNoPreChecks("CREATE USER SA PASSWORD \"\" ADMIN");
      }

      dInfo.setWithContent(true);
    } catch (Throwable e) {
      logger.closeLog(Database.CLOSEMODE_IMMEDIATELY);
      logger.releaseLock();
      setState(DATABASE_SHUTDOWN);
      clearStructures();

      if (!(e instanceof HsqlException)) {
        e = Trace.error(Trace.GENERAL_ERROR, e.toString());
      }

      throw (HsqlException) e;
    }

    setState(DATABASE_ONLINE);
  }
  /**
   * Method declaration
   *
   * @return
   * @throws SQLException
   */
  private boolean isAlreadyOpen() throws SQLException {

    // reading the last modified, wait 3 seconds, read again.
    // if the same information was read the file was not changed
    // and is probably, except the other process is blocked
    if (Trace.TRACE) {
      Trace.trace();
    }

    File f = new File(sName + ".lock");
    long l1 = f.lastModified();

    try {
      Thread.sleep(3000);
    } catch (Exception e) {
    }

    long l2 = f.lastModified();

    if (l1 != l2) {
      return true;
    }

    return pProperties.isFileOpen();
  }
  Cache openTextCache(String table, String source, boolean readOnlyData, boolean reversed)
      throws SQLException {

    closeTextCache(table);

    if (pProperties.getProperty("textdb.allow_full_path", "false").equals("false")) {
      if (source.indexOf("..") != -1) {
        throw (Trace.error(Trace.ACCESS_IS_DENIED, source));
      }

      String path = new File(new File(sName).getAbsolutePath()).getParent();

      if (path != null) {
        source = path + File.separator + source;
      }
    }

    String prefix = "textdb." + table.toLowerCase() + ".";
    TextCache c;

    if (reversed) {
      c = new ReverseTextCache(source, prefix, dDatabase);
    } else {
      c = new TextCache(source, prefix, dDatabase);
    }

    c.open(readOnlyData || bReadOnly);
    textCacheList.put(table, c);

    return (c);
  }
  /**
   * Method declaration
   *
   * @param compact
   * @throws SQLException
   */
  void close(boolean compact) throws SQLException {

    if (Trace.TRACE) {
      Trace.trace();
    }

    if (bReadOnly) {
      return;
    }

    // no more scripting
    closeScript();

    // create '.script.new' (for this the cache may be still required)
    writeScript(compact);

    // flush the cache (important: after writing the script)
    if (cCache != null) {
      cCache.flush();
    }

    closeAllTextCaches(compact);

    // create '.backup.new' using the '.data'
    backup();

    // we have the new files
    pProperties.setProperty("modified", "yes-new-files");
    pProperties.save();

    // old files can be removed and new files renamed
    renameNewToCurrent(sFileScript);
    renameNewToCurrent(sFileBackup);

    // now its done completely
    pProperties.setProperty("modified", "no");
    pProperties.setProperty("version", jdbcDriver.VERSION);
    pProperties.setProperty("hsqldb.compatible_version", "1.7.0");
    pProperties.save();
    pProperties.close();

    if (compact) {

      // stop the runner thread of this process (just for security)
      stop();

      // delete the .data so then a new file is created
      (new File(sFileCache)).delete();
      (new File(sFileBackup)).delete();

      // tony_lai@users 20020820
      // The database re-open and close has been moved to
      // Database#close(int closemode) for saving memory usage.
    }
  }
  /**
   * Method declaration
   *
   * @throws SQLException
   */
  void shutdown() throws SQLException {

    tRunner = null;

    if (cCache != null) {
      cCache.closeFile();

      cCache = null;
    }

    shutdownAllTextCaches();
    closeScript();
    pProperties.close();
  }
  /**
   * Opens this database. The database can be opened by the constructor, or reopened by the
   * close(int closemode) method during a "shutdown compact".
   *
   * @see #close(int closemode)
   * @throws SQLException if a database access error occurs
   */
  private void open() throws SQLException {

    boolean newdatabase;
    User sysUser;

    if (sName.length() == 0) {
      throw Trace.error(Trace.GENERAL_ERROR, "bad database name");
    }

    setState(DATABASE_OPENING);
    compiledStatementManager.reset();

    tTable = new HsqlArrayList();
    aAccess = new UserManager();
    hAlias = Library.getAliasMap();
    triggerNameList = new DatabaseObjectNames();
    indexNameList = new DatabaseObjectNames();
    bReferentialIntegrity = true;
    newdatabase = false;
    sysUser = aAccess.createSysUser(this);
    sessionManager = new SessionManager(this, sysUser);
    databaseProperties = new HsqlDatabaseProperties(this);
    dInfo = DatabaseInformation.newDatabaseInformation(this);

    if (sName.equals(".")) {
      newdatabase = true;
    } else {

      // create properties file if not exits and report if new file
      newdatabase = !databaseProperties.load();

      logger.openLog(this, sName);
    }

    if (newdatabase) {
      execute("CREATE USER SA PASSWORD \"\" ADMIN", sessionManager.getSysSession());
    }

    setState(DATABASE_ONLINE);
    dInfo.setWithContent(true);
  }
  /**
   * Method declaration
   *
   * @param mb
   */
  void setLogSize(int mb) {

    iLogSize = mb;

    pProperties.setProperty("hsqldb.log_size", iLogSize);
  }
  /**
   * When opening a database, the hsqldb.compatible_version property is used to determine if this
   * version of the engine is equal to or greater than the earliest version of the engine capable of
   * opening that database.
   *
   * <p>
   *
   * @return
   * @throws SQLException
   */
  boolean open() throws SQLException {

    if (Trace.TRACE) {
      Trace.trace();
    }

    if (!pProperties.checkFileExists()) {
      create();
      open();

      // this is a new database
      return true;
    }

    // todo: some parts are not necessary for ready-only access
    pProperties.load();

    sFileScript = sName + ".script";
    sFileCache = sName + ".data";
    sFileBackup = sName + ".backup";
    scriptChecker = new File(sFileScript);

    // tony_lai@users 20020820
    // Allows the user to modify log size from the properties file.
    iLogSize = pProperties.getIntegerProperty("hsqldb.log_size", iLogSize);

    String version = pProperties.getProperty("hsqldb.compatible_version");

    // fredt@users 20020428 - patch 1.7.0 by fredt
    int check = version.substring(0, 5).compareTo(jdbcDriver.VERSION);

    Trace.check(check <= 0, Trace.WRONG_DATABASE_FILE_VERSION);

    // save the current version
    pProperties.setProperty("hsqldb.version", jdbcDriver.VERSION);

    if (pProperties.isPropertyTrue("readonly")) {
      bReadOnly = true;

      dDatabase.setReadOnly();

      if (cCache != null) {
        cCache.open(true);
      }

      reopenAllTextCaches();
      runScript();

      return false;
    }

    boolean needbackup = false;
    String state = pProperties.getProperty("modified");

    if (state.equals("yes-new-files")) {
      renameNewToCurrent(sFileScript);
      renameNewToCurrent(sFileBackup);
    } else if (state.equals("yes")) {
      if (isAlreadyOpen()) {
        throw Trace.error(Trace.DATABASE_ALREADY_IN_USE);
      }

      // recovering after a crash (or forgot to close correctly)
      restoreBackup();

      needbackup = true;
    }

    pProperties.setProperty("modified", "yes");
    pProperties.save();

    if (cCache != null) {
      cCache.open(false);
    }

    reopenAllTextCaches();
    runScript();

    if (needbackup) {
      close(false);
      pProperties.setProperty("modified", "yes");
      pProperties.save();

      if (cCache != null) {
        cCache.open(false);
      }

      reopenAllTextCaches();
    }

    openScript();

    // this is an existing database
    return false;
  }