content: package ro.isdc.wro.maven.plugin.support;
  
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.io.OutputStream;
  import java.util.Properties;
  
  import org.apache.commons.io.FileUtils;
  import org.apache.commons.io.IOUtils;
  import org.apache.commons.io.input.AutoCloseInputStream;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.sonatype.plexus.build.incremental.BuildContext;
  
  
  /**
   * Encapsulate the details about state persisted across multiple build runs. This class hides the details about the
   * storage used to persist the data produced during a build. By default the {@link BuildContext} is used, but if it is
   * not available - the alternate storage (a properties file stored in the file system) is used.
   *
   * @author Alex Objelean
   * @created 1 Sep 2013
   * @since 1.7.1
   */
  public class BuildContextHolder {
    private static final Logger LOG = LoggerFactory.getLogger(BuildContextHolder.class);
    private static final String ROOT_FOLDER_NAME = ".wro4j";
    private static final String FALLBACK_STORAGE_FILE_NAME = "buildContext.properties";
    /**
     * Responsible for build storage persistence. Uses configured {@link BuildContext} as a primary storage object.
     */
    private final BuildContext buildContext;
    private final File buildDirectory;
    private Properties fallbackStorage;
    private File fallbackStorageFile;
    private boolean incrementalBuildEnabled;
  
    /**
     * @return an instance of {@link BuildContextHolder} which uses temp folder as root location of persisted data.
     */
    BuildContextHolder() {
      this(null, null);
    }
  
    /**
     * @param buildContext
     *          - The context provided by maven. If this is not provided, the default mechanism for persisting build data
     *          is used.
     * @param buildDirectory
     *          the folder where the build output is created. If this value is null, the temp folder will be used.
     */
    public BuildContextHolder(final BuildContext buildContext, final File buildDirectory) {
      this.buildContext = buildContext;
      this.buildDirectory = buildDirectory == null ? FileUtils.getTempDirectory() : buildDirectory;
  
      try {
        initFallbackStorage();
      } catch (final IOException e) {
        LOG.warn("Cannot use fallback storage: {}.", fallbackStorageFile, e);
      }
    }
  
    private void initFallbackStorage()
        throws IOException {
      fallbackStorage = new Properties();
      final File rootFolder = new File(buildDirectory, ROOT_FOLDER_NAME);
      fallbackStorageFile = newFallbackStorageFile(rootFolder);
      if (!fallbackStorageFile.exists()) {
        fallbackStorageFile.getParentFile().mkdirs();
        fallbackStorageFile.createNewFile();
        LOG.debug("created fallback storage: {}", fallbackStorageFile);
      } else {
        fallbackStorage.load(new AutoCloseInputStream(new FileInputStream(fallbackStorageFile)));
        LOG.debug("loaded fallback storage: {}", fallbackStorage);
      }
    }
  
    /**
     * @VisibleForTesting
     */
    File newFallbackStorageFile(final File rootFolder) {
      return new File(rootFolder, FALLBACK_STORAGE_FILE_NAME);
    }
  
    /**
     * @VisibleForTesting
     * @return the File where the fallback storage is persisted.
     */
    File getFallbackStorageFile() {
      return fallbackStorageFile;
    }
  
    /**
     * @param key
     *          of the value to retrieve.
     * @return the persisted value stored under the provided key. If no value exist, the returned result will be null.
     */
    public String getValue(final String key) {
      String value = null;
      if (key != null) {
        if (buildContext != null) {
          value = (String) buildContext.getValue(key);
        }
        if (value == null) {
          value = fallbackStorage.getProperty(key);
        }
      }
      return value;
    }
  
    /**
     * @param key
     *          to associate with the value to be persisted.
     * @param value
     *          to persist.
     */
    public void setValue(final String key, final String value) {
      LOG.debug("storing key: '{}' and value: '{}'", key, value);
      if (key != null) {
        if (buildContext != null) {
          buildContext.setValue(key, value);
        }
        // always use fallback
        if (value != null) {
          fallbackStorage.setProperty(key, value);
        } else {
          fallbackStorage.remove(key);
        }
      } else {
        LOG.debug("Cannot store null key");
      }
    }
  
    public void setIncrementalBuildEnabled(final boolean incrementalBuildEnabled) {
      this.incrementalBuildEnabled = incrementalBuildEnabled;
    }
  
    /**
     * @return the flag indicating the incremental build change. A build is incremental, when the modified resources
     *         should be processed only. Useful to avoid unnecessary processing when there is actually no change detected.
     */
    public boolean isIncrementalBuild() {
      return buildContext != null ? buildContext.isIncremental() || incrementalBuildEnabled : incrementalBuildEnabled;
    }
  
    /**
     * Destroy the persisted storage and all stored data.
     */
    public void destroy() {
      fallbackStorage.clear();
      FileUtils.deleteQuietly(fallbackStorageFile);
    }
  
    /**
     * Persist the fallbackStorage to the fallbackStorageFile. This method should be invoked only once during build, since
     * it is relatively expensive. Not invoking it, would break the incremental build feature.
     */
    public void persist() {
      OutputStream os = null;
      try {
        os = new FileOutputStream(fallbackStorageFile);
        fallbackStorage.store(os, "Generated");
        LOG.debug("fallback storage written to {}", fallbackStorageFile);
      } catch (final IOException e) {
        LOG.warn("Cannot persist fallback storage: {}.", fallbackStorageFile, e);
      } finally {
        IOUtils.closeQuietly(os);
      }
    }
  }
  