gt:         StringBuilder sb = new StringBuilder();
context: /*
   * (C) Copyright 2017 Netcentric AG.
   *
   * All rights reserved. This program and the accompanying materials
   * are made available under the terms of the Eclipse Public License v1.0
   * which accompanies this distribution, and is available at
   * http://www.eclipse.org/legal/epl-v10.html
   */
  package biz.netcentric.cq.tools.actool.history;
  
  import java.sql.Timestamp;
  import java.text.DateFormat;
  import java.text.NumberFormat;
  import java.text.SimpleDateFormat;
  import java.util.Date;
  import java.util.HashSet;
  import java.util.Locale;
  import java.util.Map;
  import java.util.Set;
  import java.util.TreeSet;
  
  import org.slf4j.Logger;
  
  import biz.netcentric.cq.tools.actool.api.InstallationLog;
  import biz.netcentric.cq.tools.actool.comparators.HistoryEntryComparator;
  import biz.netcentric.cq.tools.actool.installationhistory.AcInstallationHistoryPojo;
  
  public class PersistableInstallationLogger implements InstallationLogger, InstallationLog, AcInstallationHistoryPojo {
  
      protected static final String MSG_IDENTIFIER_ERROR = "ERROR: ";
      protected static final String MSG_IDENTIFIER_WARNING = "WARNING: ";
  
      private Set<HistoryEntry> warnings = new HashSet<HistoryEntry>();
      private Set<HistoryEntry> messages = new HashSet<HistoryEntry>();
      private Set<HistoryEntry> errors = new HashSet<HistoryEntry>();
  
      private Set<HistoryEntry> verboseMessages = new HashSet<HistoryEntry>();
  
      private boolean success = true;
      private final Date installationDate;
      private long executionTime;
      private long msgIndex = 0;
  
      private String mergedAndProcessedConfig;
  
      private Map<String, String> configFileContentsByName;
  
      // only for install hook case
      private String crxPackageName;
  
      private int countAclsNoChange = 0;
      private int countAclsChanged = 0;
      private int countAclsPathDoesNotExist = 0;
  
      private int countActionCacheHit = 0;
      private int countActionCacheMiss = 0;
  
      private int missingParentPathsForInitialContent = 0;
  
      private DateFormat timestampFormat = new SimpleDateFormat("HH:mm:ss.SSS");
  
      public PersistableInstallationLogger() {
          installationDate = new Date();
      }
  
      public Date getInstallationDate() {
          return installationDate;
      }
  
      public long getExecutionTime() {
          return executionTime;
      }
  
      public void setExecutionTime(final long time) {
          executionTime = time;
      }
  
      public Set<HistoryEntry> getWarnings() {
          return warnings;
      }
  
  
      public String getMergedAndProcessedConfig() {
          return mergedAndProcessedConfig;
      }
  
      public void setMergedAndProcessedConfig(String mergedAndProcessedConfig) {
          this.mergedAndProcessedConfig = mergedAndProcessedConfig;
      }
  
      public Map<String, String> getConfigFileContentsByName() {
          return configFileContentsByName;
      }
  
      public void setConfigFileContentsByName(Map<String, String> configFileContentsByName) {
          this.configFileContentsByName = configFileContentsByName;
      }
  
      /* (non-Javadoc)
       * @see biz.netcentric.cq.tools.actool.history.AcInstallationLo#getCrxPackageName()
       */
      @Override
      public String getCrxPackageName() {
          return crxPackageName;
      }
  
      public void setCrxPackageName(String crxPackageName) {
          this.crxPackageName = crxPackageName;
      }
  
      @Override
      public void addWarning(Logger log, String warning) {
          log.warn(warning);
          addWarning(warning);
      }
  
      protected void addWarning(String warning) {
          warnings.add(new HistoryEntry(msgIndex, new Timestamp(
                  new Date().getTime()), MSG_IDENTIFIER_WARNING + warning));
          msgIndex++;
      }
  
      @Override
      public void addMessage(Logger log, String message) {
          log.info(message);
          addMessage(message);
      }
  
      protected void addMessage(String message) {
          messages.add(new HistoryEntry(msgIndex, new Timestamp(new Date()
                  .getTime()), " " + message));
          msgIndex++;
      }
  
      @Override
      public void addError(Logger log, String error, Throwable e) {
          log.error(error, e);
          addError(error, e);
      }
  
      @Override
      public void addError(Logger log, String error) {
          log.error(error);
          addError(error);
      }
  
      public void addError(final String error, Throwable e) {
          addError(error + " / e=" + e);
      }
  
      @Override
      public void addError(final String error) {
          errors.add(new HistoryEntry(msgIndex, new Timestamp(
                  new Date().getTime()), MSG_IDENTIFIER_ERROR + error));
          success = false;
          msgIndex++;
      }
  
      @Override
      public void addVerboseMessage(Logger log, String message) {
          log.debug(message);
          addVerboseMessage(message);
      }
  
      protected void addVerboseMessage(String message) {
          verboseMessages.add(new HistoryEntry(msgIndex, new Timestamp(
                  new Date().getTime()), " " + message));
          msgIndex++;
      }
  
      public Set<HistoryEntry> getMessages() {
          return messages;
      }
  
      public Set<HistoryEntry> getErrors() {
          return errors;
      }
  
      public boolean isSuccess() {
          return success;
      }
  
      public void setSuccess(final boolean success) {
          this.success = success;
      }
  
      @Override
      public String toString() {
          StringBuilder sb = new StringBuilder();
  
          sb.append("\n" + getMessageHistory() + "\n");
  
          sb.append("\n" + "Execution time: " + msHumanReadable(executionTime) + "\n");
  
          sb.append("\n" + "Success: " + success);
  
          return sb.toString();
      }
  
      /* (non-Javadoc)
       * @see biz.netcentric.cq.tools.actool.history.AcInstallationLo#getMessageHistory()
       */
      @Override
      @SuppressWarnings("unchecked")
      public String getMessageHistory() {
          return getMessageString(getMessageSet(warnings, messages,
                  errors));
      }
  
      /* (non-Javadoc)
       * @see biz.netcentric.cq.tools.actool.history.AcInstallationLo#getVerboseMessageHistory()
       */
      @Override
      @SuppressWarnings("unchecked")
      public String getVerboseMessageHistory() {
          return getMessageString(getMessageSet(warnings, messages,
                  verboseMessages, errors));
      }
  
      private Set<HistoryEntry> getMessageSet(Set<HistoryEntry>... sets) {
          Set<HistoryEntry> resultSet = new TreeSet<HistoryEntry>(
                  new HistoryEntryComparator());
  
          for (Set<HistoryEntry> set : sets) {
              for (HistoryEntry entry : set) {
                  resultSet.add(entry);
              }
          }
          return resultSet;
      }
  
      private String getMessageString(Set<HistoryEntry> messageHistorySet) {
  