/**
   * Get a map from author names to their ids in the database. The authors that are not in the
   * database are added to it.
   *
   * @param conn the connection to the database
   * @param history the history to get the author names from
   * @param reposId the id of the repository
   * @return a map from author names to author ids
   */
  private Map<String, Integer> getAuthors(ConnectionResource conn, History history, int reposId)
      throws SQLException {
    HashMap<String, Integer> map = new HashMap<String, Integer>();
    PreparedStatement ps = conn.getStatement(GET_AUTHORS);
    ps.setInt(1, reposId);
    try (ResultSet rs = ps.executeQuery()) {
      while (rs.next()) {
        map.put(rs.getString(1), rs.getInt(2));
      }
    }

    PreparedStatement insert = conn.getStatement(ADD_AUTHOR);
    insert.setInt(1, reposId);
    for (HistoryEntry entry : history.getHistoryEntries()) {
      String author = entry.getAuthor();
      if (!map.containsKey(author)) {
        int id = nextAuthorId.getAndIncrement();
        insert.setString(2, author);
        insert.setInt(3, id);
        insert.executeUpdate();
        map.put(author, id);
        conn.commit();
      }
    }

    return map;
  }
  /** Test of parse method, of class ClearCaseHistoryParser. */
  @Test
  public void parseFileHistory() throws Exception {
    String author1 = "First Last (username)";
    String author2 = "First2 Last2 (username2)";
    String output =
        "create version\n"
            + "20020618.212343\n"
            + author1
            + "\n"
            + "/main/3\n"
            + "Merge from eeeee for ffffff\n"
            + ".\n"
            + "create version\n"
            + "20020222.164239\n"
            + author2
            + "\n"
            + "/main/2\n"
            + "Merge from projper branch.\n"
            + "Adding aaaaaaa to the yyyyy.\n"
            + ".\n"
            + "create version\n"
            + "20020208.150825\n"
            + author2
            + "\n"
            + "/main/1\n"
            + "Target for javac set to 1.3.\n"
            + "Fixed handling of "
            + " res directory.\n"
            + ".\n"
            + "create version\n"
            + "20010924.095104\n"
            + author2
            + "\n"
            + "/main/0\n"
            + "\n"
            + ".\n"
            + "create branch\n"
            + "20010924.095104\n"
            + author2
            + "\n"
            + "/main\n"
            + "\n"
            + ".\n"
            + "create file element\n"
            + "20010924.095104\n"
            + author1
            + "\n"
            + "\n"
            + "\n"
            + ".";

    History result = instance.parse(output);
    assertNotNull(result);
    assertEquals(4, result.getHistoryEntries().size());

    HistoryEntry e1 = result.getHistoryEntries().get(0);
    assertEquals("/main/3", e1.getRevision());
    assertEquals(author1, e1.getAuthor());
    assertEquals(0, e1.getFiles().size());
    assertTrue(e1.getMessage().contains("eeeee"));

    HistoryEntry e4 = result.getHistoryEntries().get(3);
    assertEquals("/main/0", e4.getRevision());
    assertEquals(author2, e4.getAuthor());
    assertEquals(0, e4.getFiles().size());
  }
  private void storeHistory(ConnectionResource conn, History history, Repository repository)
      throws SQLException {

    Integer reposId = null;
    Map<String, Integer> authors = null;
    Map<String, Integer> files = null;
    Map<String, Integer> directories = null;
    PreparedStatement addChangeset = null;
    PreparedStatement addDirchange = null;
    PreparedStatement addFilechange = null;
    PreparedStatement addFilemove = null;
    RuntimeEnvironment env = RuntimeEnvironment.getInstance();

    // return immediately when there is nothing to do
    List<HistoryEntry> entries = history.getHistoryEntries();
    if (entries.isEmpty()) {
      return;
    }

    for (int i = 0; ; i++) {
      try {
        if (reposId == null) {
          reposId = getRepositoryId(conn, repository);
          conn.commit();
        }

        if (authors == null) {
          authors = getAuthors(conn, history, reposId);
          conn.commit();
        }

        if (directories == null || files == null) {
          Map<String, Integer> dirs = new HashMap<String, Integer>();
          Map<String, Integer> fls = new HashMap<String, Integer>();
          getFilesAndDirectories(conn, history, reposId, dirs, fls);
          conn.commit();
          directories = dirs;
          files = fls;
        }

        if (addChangeset == null) {
          addChangeset = conn.getStatement(ADD_CHANGESET);
        }

        if (addDirchange == null) {
          addDirchange = conn.getStatement(ADD_DIRCHANGE);
        }

        if (addFilechange == null) {
          addFilechange = conn.getStatement(ADD_FILECHANGE);
        }

        if (addFilemove == null) {
          addFilemove = conn.getStatement(ADD_FILEMOVE);
        }

        // Success! Break out of the loop.
        break;

      } catch (SQLException sqle) {
        handleSQLException(sqle, i);
        conn.rollback();
      }
    }

    addChangeset.setInt(1, reposId);

    // getHistoryEntries() returns the entries in reverse chronological
    // order, but we want to insert them in chronological order so that
    // their auto-generated identity column can be used as a chronological
    // ordering column. Otherwise, incremental updates will make the
    // identity column unusable for chronological ordering. So therefore
    // we walk the list backwards.
    for (ListIterator<HistoryEntry> it = entries.listIterator(entries.size()); it.hasPrevious(); ) {
      HistoryEntry entry = it.previous();
      retry:
      for (int i = 0; ; i++) {
        try {
          addChangeset.setString(2, entry.getRevision());
          addChangeset.setInt(3, authors.get(entry.getAuthor()));
          addChangeset.setTimestamp(4, new Timestamp(entry.getDate().getTime()));
          String msg = entry.getMessage();
          // Truncate the message if it can't fit in a VARCHAR
          // (bug #11663).
          if (msg.length() > MAX_MESSAGE_LENGTH) {
            msg = truncate(msg, MAX_MESSAGE_LENGTH);
          }
          addChangeset.setString(5, msg);
          int changesetId = nextChangesetId.getAndIncrement();
          addChangeset.setInt(6, changesetId);
          addChangeset.executeUpdate();

          // Add one row for each file in FILECHANGES, and one row
          // for each path element of the directories in DIRCHANGES.
          Set<String> addedDirs = new HashSet<String>();
          addDirchange.setInt(1, changesetId);
          addFilechange.setInt(1, changesetId);
          for (String file : entry.getFiles()) {
            // ignore ignored files
            String repodir = "";
            try {
              repodir = env.getPathRelativeToSourceRoot(new File(repository.getDirectoryName()), 0);
            } catch (IOException ex) {
              Logger.getLogger(JDBCHistoryCache.class.getName()).log(Level.SEVERE, null, ex);
            }

            String fullPath = toUnixPath(file);
            if (!history.isIgnored(file.substring(repodir.length() + 1))) {
              int fileId = files.get(fullPath);
              addFilechange.setInt(2, fileId);
              addFilechange.executeUpdate();
            }
            String[] pathElts = splitPath(fullPath);
            for (int j = 0; j < pathElts.length; j++) {
              String dir = unsplitPath(pathElts, j);
              // Only add to DIRCHANGES if we haven't already
              // added this dir/changeset combination.
              if (!addedDirs.contains(dir)) {
                addDirchange.setInt(2, directories.get(dir));
                addDirchange.executeUpdate();
                addedDirs.add(dir);
              }
            }
          }

          conn.commit();

          // Successfully added the entry. Break out of retry loop.
          break retry;

        } catch (SQLException sqle) {
          handleSQLException(sqle, i);
          conn.rollback();
        }
      }
    }

    /*
     * Special handling for certain files - this is mainly for files which
     * have been renamed in Mercurial repository.
     * This ensures that their complete history (follow) will be saved.
     */
    for (String filename : history.getIgnoredFiles()) {
      String file_path = repository.getDirectoryName() + File.separatorChar + filename;
      File file = new File(file_path);
      String repo_path = file_path.substring(env.getSourceRootPath().length());
      History hist;
      try {
        hist = repository.getHistory(file);
      } catch (HistoryException ex) {
        Logger.getLogger(JDBCHistoryCache.class.getName()).log(Level.SEVERE, null, ex);
        continue;
      }

      int fileId = files.get(repo_path);
      for (HistoryEntry entry : hist.getHistoryEntries()) {
        retry:
        for (int i = 0; ; i++) {
          try {
            int changesetId = getIdForRevision(entry.getRevision());

            /*
             * If the file exists in the changeset, store it in
             * the table tracking moves of the file when it had
             * one of its precedent names so it can be found
             * when performing historyget on directory.
             */
            if (entry.getFiles().contains(repo_path)) {
              addFilechange.setInt(1, changesetId);
              addFilechange.setInt(2, fileId);
              addFilechange.executeUpdate();
            } else {
              addFilemove.setInt(1, changesetId);
              addFilemove.setInt(2, fileId);
              addFilemove.executeUpdate();
            }

            conn.commit();
            break retry;
          } catch (SQLException sqle) {
            handleSQLException(sqle, i);
            conn.rollback();
          }
        }
      }
    }
  }
  /** Test of parse method, of class ClearCaseHistoryParser. */
  @Test
  public void parseDirHistory() throws Exception {
    String author1 = "First Last (username)";
    String author2 = "First2 Last2 (username2)";
    String output =
        "create directory version\n"
            + "20050401.162902\n"
            + author1
            + "\n"
            + "/main/3\n"
            + "Removed directory element \"prototype\".\n"
            + ".\n"
            + "create directory version\n"
            + "20020618.215917\n"
            + author2
            + "\n"
            + "/main/2\n"
            + "Merge from wwwww for dddddd\n"
            + ".\n"
            + "create directory version\n"
            + "20010228.174617\n"
            + author1
            + "\n"
            + "/main/1\n"
            + "New structure.\n"
            + "\n"
            + "Added directory element \"generic\".\n"
            + "Added directory element \"prototype\".\n"
            + "Added directory element \"install\".\n"
            + "Added directory element \"service\".\n"
            + ".\n"
            + "create directory version\n"
            + "20001215.092522\n"
            + author2
            + "\n"
            + "/main/0\n"
            + "\n"
            + ".\n"
            + "create branch\n"
            + "20001215.092522\n"
            + author1
            + "\n"
            + "/main\n"
            + "\n"
            + ".\n"
            + "create directory element\n"
            + "20001215.092522\n"
            + author1
            + "\n"
            + "\n"
            + "\n"
            + ".";
    History result = instance.parse(output);
    assertNotNull(result);
    assertEquals(4, result.getHistoryEntries().size());

    HistoryEntry e1 = result.getHistoryEntries().get(0);
    assertEquals("/main/3", e1.getRevision());
    assertEquals(author1, e1.getAuthor());
    assertEquals(0, e1.getFiles().size());
    assertTrue(e1.getMessage().contains("prototype"));

    HistoryEntry e4 = result.getHistoryEntries().get(3);
    assertEquals("/main/0", e4.getRevision());
    assertEquals(author2, e4.getAuthor());
    assertEquals(0, e4.getFiles().size());
  }