content: /**
   * Copyright Alex Objelean
   */
  package ro.isdc.wro.model.resource.processor.impl.css;
  
  import java.io.File;
  import java.io.IOException;
  import java.io.Reader;
  import java.io.Writer;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.Map;
  import java.util.Stack;
  import java.util.concurrent.ConcurrentHashMap;
  
  import org.apache.commons.io.IOUtils;
  import org.apache.commons.lang3.Validate;
  import org.apache.commons.lang3.tuple.ImmutablePair;
  import org.apache.commons.lang3.tuple.Pair;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import ro.isdc.wro.config.Context;
  import ro.isdc.wro.config.jmx.WroConfiguration;
  import ro.isdc.wro.model.group.Inject;
  import ro.isdc.wro.model.resource.Resource;
  import ro.isdc.wro.model.resource.ResourceType;
  import ro.isdc.wro.model.resource.SupportedResourceType;
  import ro.isdc.wro.model.resource.locator.UrlUriLocator;
  import ro.isdc.wro.model.resource.locator.factory.UriLocatorFactory;
  import ro.isdc.wro.model.resource.processor.ImportAware;
  import ro.isdc.wro.model.resource.processor.ResourcePreProcessor;
  import ro.isdc.wro.model.resource.processor.support.CssImportInspector;
  import ro.isdc.wro.util.StringUtils;
  import ro.isdc.wro.util.WroUtil;
  
  
  /**
   * CssImport Processor responsible for handling css <code>@import</code> statement. It is implemented as both:
   * preProcessor & postProcessor. It is necessary because preProcessor is responsible for updating model with found
   * imported resources, while post processor removes import occurrences.
   * <p/>
   * When processor finds an import which is not valid, it will check the
   * {@link WroConfiguration#isIgnoreMissingResources()} flag. If it is set to false, the processor will fail.
   *
   * @author Alex Objelean
   */
  @SupportedResourceType(ResourceType.CSS)
  public abstract class AbstractCssImportPreProcessor
    implements ResourcePreProcessor, ImportAware {
    private static final Logger LOG = LoggerFactory.getLogger(AbstractCssImportPreProcessor.class);
    /**
     * Contains a {@link UriLocatorFactory} reference injected externally.
     */
    @Inject
    private UriLocatorFactory uriLocatorFactory;
    /**
     * A map useful for detecting deep recursion. The key (correlationId) - identifies a processing unit, while the value
     * contains a pair between the list o processed resources and a stack holding recursive calls (value contained on this
     * stack is not important). This map is used to ensure that the processor is thread-safe and doesn't erroneously
     * detect recursion when running in concurrent environment (when processor is invoked from within the processor for
     * child resources).
     */
    private final Map<String, Pair<List<String>, Stack<String>>> contextMap = new ConcurrentHashMap<String, Pair<List<String>, Stack<String>>>() {
      /**
       * Make sure that the get call will always return a not null object. To avoid growth of this map, it is important to
       * call remove for each invoked get.
       */
      @Override
      public Pair<List<String>, Stack<String>> get(final Object key) {
        Pair<List<String>, Stack<String>> result = super.get(key);
        if (result == null) {
          final List<String> list = new ArrayList<String>();
          result = ImmutablePair.of(list, new Stack<String>());
          put(key.toString(), result);
        }
        return result;
      };
    };
  
  
    /**
     * Useful to check that there is no memory leak after processing completion.
     * @VisibleForTesting
     */
    protected final Map<String, Pair<List<String>, Stack<String>>> getContextMap() {
      return contextMap;
    }
  
    /**
     * {@inheritDoc}
     */
    public final void process(final Resource resource, final Reader reader, final Writer writer)
      throws IOException {
      LOG.debug("Applying {} processor", toString());
      validate();
      try {
        final String result = parseCss(resource, IOUtils.toString(reader));
        writer.write(result);
      } finally {
        //imkportant to avoid memory leak
        clearProcessedImports();
        reader.close();
        writer.close();
      }
    }
  
    /**
     * Checks if required fields were injected.
     */
    private void validate() {
      Validate.notNull(uriLocatorFactory);
    }
  
    /**
     * @param resource {@link Resource} to process.
     * @param cssContent Reader for processed resource.
     * @return css content with all imports processed.
     */
    private String parseCss(final Resource resource, final String cssContent)
      throws IOException {
      if (isImportProcessed(resource.getUri())) {
        LOG.debug("[WARN] Recursive import detected: {}", resource);
        onRecursiveImportDetected();
        return "";
      }
      final String importedUri = resource.getUri().replace(File.separatorChar,'/');
      addProcessedImport(importedUri);
      final List<Resource> importedResources = findImportedResources(resource.getUri(), cssContent);
      return doTransform(cssContent, importedResources);
    }
  
    private boolean isImportProcessed(final String uri) {
      return getProcessedImports().contains(uri);
    }
  
    private void addProcessedImport(final String importedUri) {
      final String correlationId = Context.getCorrelationId();
      contextMap.get(correlationId).getValue().push(importedUri);
      getProcessedImports().add(importedUri);
    }
  
    private List<String> getProcessedImports() {
      return contextMap.get(Context.getCorrelationId()).getKey();
    }
  
    private void clearProcessedImports() {
      final String correlationId = Context.getCorrelationId();
      final Stack<String> stack = contextMap.get(correlationId).getValue();
      if (!stack.isEmpty()) {
        stack.pop();
      }
      if (stack.isEmpty()) {
        contextMap.remove(correlationId);
      }
    }
  
    /**
     * Find a set of imported resources inside a given resource.
     */
    private List<Resource> findImportedResources(final String resourceUri, final String cssContent)
      throws IOException {
      // it should be sorted
      final List<Resource> imports = new ArrayList<Resource>();
      final String css = cssContent;
      final List<String> foundImports = findImports(css);
      for (final String importUrl : foundImports) {
        final Resource importedResource = createImportedResource(resourceUri, importUrl);
        // check if already exist
        if (imports.contains(importedResource)) {
          LOG.debug("[WARN] Duplicate imported resource: {}", importedResource);
        } else {
          imports.add(importedResource);
          onImportDetected(importedResource.getUri());
        }
      }
      return imports;
    }
  
    /**
     * Extracts a list of imports from css content.
     *
     * @return a list of found imports.
     */
    protected List<String> findImports(final String css) {
      return new CssImportInspector(css).findImports();
    }
  
    /**
     * Build a {@link Resource} object from a found importedResource inside a given resource.
     */
    private Resource createImportedResource(final String resourceUri, final String importUrl) {
      final String absoluteUrl = UrlUriLocator.isValid(importUrl) ? importUrl
          : computeAbsoluteUrl(resourceUri, importUrl);
      return Resource.create(absoluteUrl, ResourceType.CSS);
    }
  
    /**
     * Computes absolute url of the imported resource.
     *
     * @param relativeResourceUri uri of the resource containing the import statement.
     * @param importUrl found import url.
     * @return absolute url of the resource to import.
     */
    private String computeAbsoluteUrl(final String relativeResourceUri, final String importUrl) {
      final String folder = WroUtil.getFullPath(relativeResourceUri);
      // remove '../' & normalize the path.
      final String absoluteImportUrl = StringUtils.cleanPath(folder + importUrl);
      return absoluteImportUrl;
    }
  
    /**
     * Perform actual transformation of provided cssContent and the list of found import resources.
     *
     * @param cssContent
     *          the css to transform.
     * @param importedResources
     *          the list of found imports.
     */
    protected abstract String doTransform(final String cssContent, final List<Resource> importedResources)
        throws IOException;
  
  
    /**
     * Invoked when an import is detected. By default this method does nothing.
     * @param foundImportUri the uri of the detected imported resource
     */
    protected void onImportDetected(final String foundImportUri) {
    }
  
    /**
     * Invoked when a recursive import is detected. Used to assert the recursive import detection correct behavior. By
     * default this method does nothing.
     *
     * @VisibleForTesting
     */
    protected void onRecursiveImportDetected() {
    }
  
    /**
     * {@inheritDoc}
     */
    public boolean isImportAware() {
      //We want this processor to be applied when processing resources referred with @import directive
      return true;
    }
  }
  