/**
 * Copyright wro4j@2011
 */
package ro.isdc.wro.extensions.processor.support.linter;

import java.util.Arrays;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.WroRuntimeException;

/**
 * Responsible for building json representations of the options consumed by linter code.
 *
 * @author Alex Objelean
 * @created 22 Nov 2011
 * @since 1.4.2
 */
public class OptionsBuilder {
  private static final Logger LOG = LoggerFactory.getLogger(OptionsBuilder.class);
  /**
   * Split multiple options into an array of options.
   *
   * @param optionAsString string representation of multiple options.
   * @return an array of options.
   */
  public String[] splitOptions(final String optionAsString) {
    return optionAsString == null ? ArrayUtils.EMPTY_STRING_ARRAY : optionAsString.split("(?ims),(?![^\\[\\]]*\\])");
  }

  /**
   * Builds options json representation from a set of options encoded in a string, each of them being separated by a
   * comma.
   *
   * @param optionsAsCsv
   * @return json representation of options.
   */
  public String buildFromCsv(final String optionsAsCsv) {
    return build(splitOptions(optionsAsCsv));
  }

  /**
   * @param options
   *          an array of options as provided by user.
   * @return the json object containing options to be used by linter code.
   */
  public String build(final String... options) {
    LOG.debug("options: {}", Arrays.toString(options));
    final StringBuffer sb = new StringBuffer("{");
    if (options != null) {
      for (int i = 0; i < options.length; i++) {
        final String option = options[i];
        if (!StringUtils.isEmpty(option)) {
          sb.append(processSingleOption(option));
          if (i < options.length - 1) {
            sb.append(",");
          }
        }
      }
    }
    sb.append("}");
    LOG.debug("options: {}", sb);
    return sb.toString();
  }

  private String processSingleOption(final String option) {
    String optionName = option;
    String optionValue = Boolean.TRUE.toString();
    if (option.contains(":")) {
      final String message = String.format("Invalid option: '%s'. Expected format: key=value or key=['v1','v2']",
          option);
      LOG.error(message);
      throw new WroRuntimeException(message);
    }
    if (option.contains("=")) {
      final String[] optionEntry = option.split("=");
      if (optionEntry.length != 2) {
        throw new IllegalArgumentException("Invalid option provided: " + option);
      }
      optionName = optionEntry[0];
      optionValue = option.split("=")[1];
    }
    return String.format("\"%s\": %s", optionName.trim(), optionValue.trim());
  }

}


/*
 *  Copyright 2010.
 */
package ro.isdc.wro.extensions.processor.support.cjson;

import java.io.InputStream;

import org.mozilla.javascript.ScriptableObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.WroRuntimeException;
import ro.isdc.wro.extensions.script.RhinoScriptBuilder;
import ro.isdc.wro.util.StopWatch;
import ro.isdc.wro.util.WroUtil;

/**
 * The underlying implementation use the cjson project: {@link http://stevehanov.ca/blog/index.php?id=104}.
 *
 * @author Alex Objelean
 * @since 1.3.8
 */
public class CJson {
  private static final Logger LOG = LoggerFactory.getLogger(CJson.class);
  private ScriptableObject scope;

  /**
   * Initialize script builder for evaluation.
   */
  private RhinoScriptBuilder initScriptBuilder() {
    try {
      RhinoScriptBuilder builder = null;
      if (scope == null) {
        builder = RhinoScriptBuilder.newClientSideAwareChain().addJSON().evaluateChain(
          getScriptAsStream(), "cjson.js");
        scope = builder.getScope();
      } else {
        builder = RhinoScriptBuilder.newChain(scope);
      }
      return builder;
    } catch (final Exception e) {
      LOG.error("Processing error:" + e.getMessage(), e);
      throw new WroRuntimeException("Processing error", e);
    }
  }

  /**
   * @return stream of the less.js script.
   */
  protected InputStream getScriptAsStream() {
    return CJson.class.getResourceAsStream("cjson.min.js");
  }

  /**
   * @param data css content to process.
   * @return processed css content.
   */
  public String pack(final String data) {
    final StopWatch stopWatch = new StopWatch();
    stopWatch.start("initContext");
    final RhinoScriptBuilder builder = initScriptBuilder();
    stopWatch.stop();

    stopWatch.start("cjson.pack");
    try {
      final String execute = "CJSON.stringify(JSON.parse(" + WroUtil.toJSMultiLineString(data) + "));";
      final Object result = builder.evaluate(execute, "pack");
      return String.valueOf(result);
    } finally {
      stopWatch.stop();
      LOG.debug(stopWatch.prettyPrint());
    }
  }

  public String unpack(final String data) {
    final StopWatch stopWatch = new StopWatch();
    stopWatch.start("initContext");
    final RhinoScriptBuilder builder = initScriptBuilder();
    stopWatch.stop();

    stopWatch.start("json.unpack");
    try {
      final String execute = "JSON.stringify(CJSON.parse(" + WroUtil.toJSMultiLineString(data) + "));";
      final Object result = builder.evaluate(execute, "unpack");
      return String.valueOf(result);
    } finally {
      stopWatch.stop();
      LOG.debug(stopWatch.prettyPrint());
    }
  }
}


/*
 *  Copyright wro4j@2011.
 */
package ro.isdc.wro.extensions.processor.support.less;

import java.io.IOException;
import java.io.InputStream;

import org.mozilla.javascript.ScriptableObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.WroRuntimeException;
import ro.isdc.wro.extensions.locator.WebjarUriLocator;
import ro.isdc.wro.extensions.script.RhinoScriptBuilder;
import ro.isdc.wro.util.StopWatch;
import ro.isdc.wro.util.WroUtil;

/**
 * This class is not thread-safe.<br/>
 * The underlying implementation uses the webjar containing less.js library.
 *
 * @author Alex Objelean
 * @since 1.3.0
 */
public class LessCss {
  private static final Logger LOG = LoggerFactory.getLogger(LessCss.class);
  /**
   * The name of the sass script to be used by default.
   */
  public static final String DEFAULT_LESS_JS = "less-1.3.3.min.js";
  private static final String SCRIPT_INIT = "init.js";
  private WebjarUriLocator webjarLocator;
  private ScriptableObject scope;

  /**
   * Initialize script builder for evaluation.
   */
  private RhinoScriptBuilder initScriptBuilder() {
    try {
      RhinoScriptBuilder builder = null;
      if (scope == null) {
        final InputStream initStream = LessCss.class.getResourceAsStream(SCRIPT_INIT);
        builder = RhinoScriptBuilder.newClientSideAwareChain().evaluateChain(initStream, SCRIPT_INIT).evaluateChain(
            getScriptAsStream(), DEFAULT_LESS_JS);
        scope = builder.getScope();
      } else {
        builder = RhinoScriptBuilder.newChain(scope);
      }
      return builder;
    } catch (final IOException ex) {
      throw new IllegalStateException("Failed reading javascript less.js", ex);
    } catch (final Exception e) {
      LOG.error("Processing error:" + e.getMessage(), e);
      throw new WroRuntimeException("Processing error", e);
    }
  }

  /**
   * @return stream of the less.js script.
   */
  protected InputStream getScriptAsStream() throws IOException {
    return getWebjarLocator().locate(WebjarUriLocator.createUri("less.min.js"));
  }

  /**
   * @return {@link WebjarUriLocator} instance to retrieve webjars.
   */
  private WebjarUriLocator getWebjarLocator() {
    if (webjarLocator == null) {
      webjarLocator = new WebjarUriLocator();
    }
    return webjarLocator;
  }

  /**
   * @param data css content to process.
   * @return processed css content.
   */
  public String less(final String data) {
    final StopWatch stopWatch = new StopWatch();
    stopWatch.start("initContext");
    final RhinoScriptBuilder builder = initScriptBuilder();
    stopWatch.stop();

    stopWatch.start("lessify");
    try {
      final String execute = "lessIt(" + WroUtil.toJSMultiLineString(data) + ");";
      final Object result = builder.evaluate(execute, "lessIt");
      return String.valueOf(result);
    } finally {
      stopWatch.stop();
      LOG.debug(stopWatch.prettyPrint());
    }
  }
}


package ro.isdc.wro.util;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.CharEncoding;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Used to overcome the limitation of {@link Properties} class when dealing with regular expressions read from
 * properties file. The main benefit of using {@link RegexpProperties} over simple {@link Properties} is that the regexp
 * doesn't have to be escaped.
 *
 * @author Alex Objelean
 * @since 1.4.7
 * @created 11 Jun 2012
 */
public class RegexpProperties {
  private static final Logger LOG = LoggerFactory.getLogger(RegexpProperties.class);
  private final Properties properties;
  private static final String REGEX_KEY_VALUE = "(?m)\\s*(.*?)\\s*=\\s*(.*)\\s*$";
  private static final String REGEX_COMMENTS = "#.*";
  private static final Pattern PATTERN_KEY_VALUE = Pattern.compile(REGEX_KEY_VALUE);

  public RegexpProperties() {
    this.properties = new Properties();
  }

  /**
   * Load the properties from the stream. The implementation will handle comments properly by removing them before
   * properties are loaded.
   *
   * @param inputStream
   * @return {@link Properties} containing properties parsed from the stream.
   * @throws IOException
   */
  public Properties load(final InputStream inputStream) throws IOException {
    Validate.notNull(inputStream);
    final String rawContent = IOUtils.toString(inputStream, CharEncoding.UTF_8);
    parseProperties(rawContent.replaceAll(REGEX_COMMENTS, ""));
    return this.properties;
  }

  /**
   * parse the properties from the provided string containing a raw properties
   */
  private void parseProperties(final String propertiesAsString) {
    //should work also \r?\n
    final String[] propertyEntries = propertiesAsString.split("\\r?\\n");
    for (final String entry : propertyEntries) {
      readPropertyEntry(entry);
    }
  }

  private void readPropertyEntry(final String entry) {
    Matcher matcher = PATTERN_KEY_VALUE.matcher(entry);
    while(matcher.find()) {
      final String key = matcher.group(1);
      final String value = matcher.group(2);
      this.properties.put(key, value);
    }
  }
}


/**
 * Copyright Alex Objelean
 */
package ro.isdc.wro.maven.plugin;

import static org.apache.commons.lang3.Validate.notNull;

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.codehaus.classworlds.ClassRealm;
import org.sonatype.plexus.build.incremental.BuildContext;

import com.google.common.annotations.VisibleForTesting;

import ro.isdc.wro.WroRuntimeException;
import ro.isdc.wro.config.Context;
import ro.isdc.wro.manager.WroManager;
import ro.isdc.wro.manager.factory.WroManagerFactory;
import ro.isdc.wro.manager.factory.standalone.StandaloneContext;
import ro.isdc.wro.manager.factory.standalone.StandaloneContextAware;
import ro.isdc.wro.maven.plugin.manager.factory.ConfigurableWroManagerFactory;
import ro.isdc.wro.maven.plugin.support.ExtraConfigFileAware;
import ro.isdc.wro.maven.plugin.support.ResourceChangeHandler;
import ro.isdc.wro.model.WroModel;
import ro.isdc.wro.model.WroModelInspector;
import ro.isdc.wro.model.group.Group;
import ro.isdc.wro.model.resource.Resource;
import ro.isdc.wro.util.concurrent.TaskExecutor;

/**
 * Defines most common properties used by wro4j build-time solution infrastructure.
 *
 * @author Alex Objelean
 */
public abstract class AbstractWro4jMojo
    extends AbstractMojo {
  /**
   * File containing the groups definitions.
   *
   * @parameter default-value="${basedir}/src/main/webapp/WEB-INF/wro.xml" property="wroFile"
   * @optional
   */
  private File wroFile;
  /**
   * Allows clients to pass a build-time parameter to skip the plugin execution..
   *
   * @parameter default-value=false
   * @optional
   */
  private boolean skip;
  /**
   * The folder where web application context resides useful for locating resources relative to servletContext. It is
   * possible to provide multiple context folders using a CSV. When multiple contextFolders are provided, the
   * servletContext locator will try to search in next contextFolder when a resource could not be located. By default, a
   * single context folder is configured.
   *
   * @parameter default-value="${basedir}/src/main/webapp/" property="contextFolder"
   * @optional
   */
  private String contextFolder;
  /**
   * @parameter default-value="true" property="minimize"
   * @optional
   */
  private boolean minimize;
  /**
   * @parameter property="ignoreMissingResources"
   * @optional
   */
  private String ignoreMissingResources;
  /**
   * Comma separated group names. This field is optional. If no value is provided, a file for each group will be
   * created.
   *
   * @parameter property="targetGroups"
   * @optional
   */
  private String targetGroups;
  /**
   * @parameter default-value="${project}"
   */
  private MavenProject mavenProject;
  /**
   * @parameter property="wroManagerFactory"
   * @optional
   */
  private String wroManagerFactory;
  /**
   * An instance of {@link StandaloneContextAware}.
   */
  private WroManagerFactory managerFactory;
  /**
   * The path to configuration file.
   *
   * @parameter default-value="${basedir}/src/main/webapp/WEB-INF/wro.properties" property="extraConfigFile"
   * @optional
   */
  private File extraConfigFile;
  /**
   * Responsible for identifying the resources changed during incremental build.
   *

   * Read more about it here
   *
   * @component
   */
  private BuildContext buildContext;
  /**
   * This parameter is not meant to be used. The only purpose is to hold project build directory
   *
   * @parameter default-value="${project.build.directory}"
   * @optional
   */
  private File buildDirectory;
  /**
   * When this flag is enabled and there are more than one group to be processed, these will be processed in parallel,
   * resulting in faster overall plugin execution time.
   *
   * @parameter default-value="false" property="parallelProcessing"
   * @optional
   */
  private boolean parallelProcessing;
  /**
   * Flag which allows to enable incremental build (experimental feature). It is false by default, but probably can be
   * changed to true if no unexpected problems are detected..
   *
   * @parameter default-value="false" property="incrementalBuildEnabled"
   * @optional
   */
  private boolean incrementalBuildEnabled;
  private TaskExecutor<Void> taskExecutor;
  private ResourceChangeHandler resourceChangeHandler;

  public final void execute()
      throws MojoExecutionException {
    if (skip) {
      getLog().info("Skipping execution.");
    } else {
      validate();
      if (buildDirectory == null) {
        buildDirectory = new File(mavenProject.getModel().getBuild().getDirectory());
      }

      getLog().info(contextFolder);
      getLog().info("Executing the mojo: ");
      getLog().info("Wro4j Model path: " + wroFile.getPath());
      getLog().info("targetGroups: " + getTargetGroups());
      getLog().info("minimize: " + isMinimize());
      getLog().info("ignoreMissingResources: " + isIgnoreMissingResources());
      getLog().info("parallelProcessing: " + isParallelProcessing());
      getLog().info("buildDirectory: " + buildDirectory);
      getLog().debug("wroManagerFactory: " + wroManagerFactory);
      getLog().debug("incrementalBuildEnabled: " + incrementalBuildEnabled);
      getLog().debug("extraConfig: " + extraConfigFile);

      extendPluginClasspath();
      Context.set(Context.standaloneContext());
      try {
        onBeforeExecute();
        doExecute();
      } catch (final Exception e) {
        final String message = "Exception occured while processing: " + e.toString() + ", class: "
            + e.getClass().getName() + ",caused by: " + (e.getCause() != null ? e.getCause().getClass().getName() : "");
        getLog().error(message, e);
        if (e instanceof WroRuntimeException) {
          // Do not keep resources which cause the exception. This is helpful for linter processors.
          final Resource resource = ((WroRuntimeException) e).getResource();
          forgetResource(resource);
        }
        throw new MojoExecutionException(message, e);
      } finally {
        try {
          onAfterExecute();
        } catch (final Exception e) {
          throw new MojoExecutionException("Exception in onAfterExecute", e);
        }
        finally {
            getWroManager().destroy();
        }
      }
    }
  }

  /**
   * Safely invoke {@link ResourceChangeHandler#forget(Resource)}. The safety is required because invoking
   * {@link #getResourceChangeHandler()} can throw an exception during initialization.
   */
  private void forgetResource(final Resource resource) {
    if (resourceChangeHandler != null) {
      resourceChangeHandler.forget(resource);
    }
  }

  /**
   * Creates a {@link StandaloneContext} by setting properties passed after mojo is initialized.
   */
  private StandaloneContext createStandaloneContext() {
    final StandaloneContext runContext = new StandaloneContext();
    runContext.setContextFoldersAsCSV(getContextFoldersAsCSV());
    runContext.setMinimize(isMinimize());
    runContext.setWroFile(getWroFile());
    runContext.setIgnoreMissingResourcesAsString(isIgnoreMissingResources());
    return runContext;
  }

  /**
   * Perform actual plugin processing.
   */
  protected abstract void doExecute()
      throws Exception;

  /**
   * This method will ensure that you have a right and initialized instance of {@link StandaloneContextAware}.
   *
   * @return {@link WroManagerFactory} implementation.
   */
  protected final WroManagerFactory getManagerFactory() {
    if (managerFactory == null) {
      try {
        managerFactory = wroManagerFactory != null ? createCustomManagerFactory() : newWroManagerFactory();
        onAfterCreate(managerFactory);
      } catch (final MojoExecutionException e) {
        throw WroRuntimeException.wrap(e);
      }
    }
    return managerFactory;
  }

  /**
   * Creates a custom instance of Manager factory. The wroManagerFactory parameter value is used to identify the manager class.
   */
  private WroManagerFactory createCustomManagerFactory()
      throws MojoExecutionException {
    WroManagerFactory factory = null;
    try {
      final Class<?> wroManagerFactoryClass = Thread.currentThread().getContextClassLoader().loadClass(
          wroManagerFactory.trim());
      factory = (WroManagerFactory) wroManagerFactoryClass.newInstance();
    } catch (final Exception e) {
      throw new MojoExecutionException("Invalid wroManagerFactoryClass, called: " + wroManagerFactory, e);
    }
    return factory;
  }

  /**
   * Allows explicitly to override the default implementation of the factory, assuming {@link #wroManagerFactory}
   * configuration is not set. When overriding this method, make sure that {@link #onAfterCreate(WroManagerFactory)} is
   * invoked on newly created factory for proper initialization.
   */
  protected WroManagerFactory newWroManagerFactory()
      throws MojoExecutionException {
    WroManagerFactory factory = null;
    if (wroManagerFactory == null) {
      factory = new ConfigurableWroManagerFactory();
    }
    getLog().info("wroManagerFactory class: " + factory.getClass().getName());
    return factory;
  }

  /**
   * Initialize the created {@link WroManagerFactory} with additional configurations which are not available during
   * creation. Make sure this method is invoked on a custom factory when {@link #newWroManagerFactory()} is overridden.
   */
  private void onAfterCreate(final WroManagerFactory factory)
      throws MojoExecutionException {
    if (factory instanceof ExtraConfigFileAware) {
      if (extraConfigFile == null) {
        throw new MojoExecutionException("The " + factory.getClass() + " requires a valid extraConfigFile!");
      }
      getLog().debug("Using extraConfigFile: " + extraConfigFile.getAbsolutePath());
      ((ExtraConfigFileAware) factory).setExtraConfigFile(extraConfigFile);
    }
    // initialize before process.
    if (factory instanceof StandaloneContextAware) {
      ((StandaloneContextAware) factory).initialize(createStandaloneContext());
    }
  }

  /**
   * @return a list of groups which will be processed.
   */
  protected final List<String> getTargetGroupsAsList()
      throws Exception {
    List<String> result = null;
    if (isIncrementalCheckRequired()) {
      result = getIncrementalGroupNames();
    } else if (getTargetGroups() == null) {
      result = getAllModelGroupNames();
    } else {
      result = Arrays.asList(getTargetGroups().split(","));
    }
    persistResourceFingerprints(result);
    if (result.isEmpty()) {
      getLog().info("Nothing to process (nothing configured or nothing changed since last build).");
    } else {
      getLog().info("The following groups will be processed: " + result);
    }
    return result;
  }

  /**
   * Store digest for all resources contained inside the list of provided groups.
   */
  private void persistResourceFingerprints(final List<String> groupNames) {
    final WroModelInspector modelInspector = new WroModelInspector(getModel());
    for (final String groupName : groupNames) {
      final Group group = modelInspector.getGroupByName(groupName);
      if (group != null) {
        for (final Resource resource : group.getResources()) {
          getResourceChangeHandler().remember(resource);
        }
      }
    }
  }

  /**
   * @return a list of groups changed by incremental builds.
   */
  private List<String> getIncrementalGroupNames()
      throws Exception {
    final List<String> changedGroupNames = new ArrayList<String>();
    for (final Group group : getModel().getGroups()) {
      // skip processing non target groups
      if (isTargetGroup(group)) {
        for (final Resource resource : group.getResources()) {
          getLog().debug("checking delta for resource: " + resource);
          if (getResourceChangeHandler().isResourceChanged(resource)) {
            getLog().debug("detected change for resource: " + resource + " and group: " + group.getName());
            changedGroupNames.add(group.getName());
            // no need to check rest of resources from this group
            break;
          }
        }
      }
    }
    return changedGroupNames;
  }

  /**
   * Check if the provided group is a target group.
   */
  private boolean isTargetGroup(final Group group) {
    notNull(group);
    final String targetGroups = getTargetGroups();
    // null, means all groups are target groups
    return targetGroups == null || targetGroups.contains(group.getName());
  }

  /**
   * Checks if all required fields are configured.
   */
  protected void validate()
      throws MojoExecutionException {
    if (wroFile == null) {
      throw new MojoExecutionException("contextFolder was not set!");
    }
    if (contextFolder == null) {
      throw new MojoExecutionException("no contextFolder was set!");
    }
  }

  /**
   * Update the classpath.
   */
  protected final void extendPluginClasspath()
      throws MojoExecutionException {
    // this code is inspired from http://teleal.org/weblog/Extending%20the%20Maven%20plugin%20classpath.html
    final List<String> classpathElements = new ArrayList<String>();
    try {
      classpathElements.addAll(mavenProject.getRuntimeClasspathElements());
    } catch (final DependencyResolutionRequiredException e) {
      throw new MojoExecutionException("Could not get compile classpath elements", e);
    }
    final ClassLoader classLoader = createClassLoader(classpathElements);
    Thread.currentThread().setContextClassLoader(classLoader);
  }

  /**
   * @return {@link ClassRealm} based on project dependencies.
   */
  private ClassLoader createClassLoader(final List<String> classpathElements) {
    getLog().debug("Classpath elements:");
    final List<URL> urls = new ArrayList<URL>();
    try {
      for (final String element : classpathElements) {
        final File elementFile = new File(element);
        getLog().debug("Adding element to plugin classpath: " + elementFile.getPath());
        urls.add(elementFile.toURI().toURL());
      }
    } catch (final Exception e) {
      getLog().error("Error retreiving URL for artifact", e);
      throw new RuntimeException(e);
    }
    return new URLClassLoader(urls.toArray(new URL[] {}), Thread.currentThread().getContextClassLoader());
  }

  /**
   * @return The {@link TaskExecutor} responsible for running multiple tasks in parallel.
   */
  protected final TaskExecutor<Void> getTaskExecutor() {
    if (taskExecutor == null) {
      taskExecutor = new TaskExecutor<Void>() {
        @Override
        protected void onException(final Exception e) {
          // propagate exception
          throw WroRuntimeException.wrap(e);
        }
      };
    }
    return taskExecutor;
  }

  /**
   * @return true if the only incremental changed group should be used as target groups for next processing.
   */
  protected boolean isIncrementalCheckRequired() {
    return isIncrementalBuild();
  }

  /**
   * Invoked before execution is performed.
   */
  protected void onBeforeExecute() {
  }

  /**
   * Invoked right after execution completion. This method is invoked also if the execution failed with an exception.
   */
  protected void onAfterExecute() {
    resourceChangeHandler.persist();
  }

  /**
   * @return true if the build was triggered by an incremental change.
   */
  protected final boolean isIncrementalBuild() {
    return getResourceChangeHandler().isIncrementalBuild();
  }

  private List<String> getAllModelGroupNames() {
    return new WroModelInspector(getModel()).getGroupNames();
  }

  private WroModel getModel() {
    return getWroManager().getModelFactory().create();
  }

  private WroManager getWroManager() {
    try {
      return getManagerFactory().create();
    } catch (final Exception e) {
      throw WroRuntimeException.wrap(e);
    }
  }

  private ResourceChangeHandler getResourceChangeHandler() {
    if (resourceChangeHandler == null) {
      resourceChangeHandler = ResourceChangeHandler.create(getManagerFactory(), getLog()).setBuildContext(buildContext).setBuildDirectory(
          buildDirectory).setIncrementalBuildEnabled(incrementalBuildEnabled);
    }
    return resourceChangeHandler;
  }

  @VisibleForTesting
  void setTaskExecutor(final TaskExecutor<Void> taskExecutor) {
    this.taskExecutor = taskExecutor;
  }

  /**
   * @param contextFolder
   *          the servletContextFolder to set
   * @VisibleForTesting
   */
  String getContextFoldersAsCSV() {
    return contextFolder;
  }

  /**
   * @param contextFolders
   *          a CSV representing contextFolders to use.
   * @VisibleForTesting
   */
  void setContextFolder(final String contextFolder) {
    this.contextFolder = contextFolder;
  }

  /**
   * @param wroFile
   *          the wroFile to set
   * @VisibleForTesting
   */
  void setWroFile(final File wroFile) {
    this.wroFile = wroFile;
  }

  /**
   * @return the wroFile
   * @VisibleForTesting
   */
  File getWroFile() {
    return this.wroFile;
  }

  /**
   * @param minimize
   *          flag for minimization.
   * @VisibleForTesting
   */
  void setMinimize(final boolean minimize) {
    this.minimize = minimize;
  }

  /**
   * @param ignoreMissingResourcesAsString
   *          the ignoreMissingResources to set
   * @VisibleForTesting
   */
  void setIgnoreMissingResources(final String ignoreMissingResourcesAsString) {
    this.ignoreMissingResources = ignoreMissingResourcesAsString;
  }

  void setIgnoreMissingResources(final boolean ignoreMissingResources) {
    setIgnoreMissingResources(Boolean.toString(ignoreMissingResources));
  }

  /**
   * @VisibleForTesting
   */
  protected final boolean isParallelProcessing() {
    return parallelProcessing;
  }

  /**
   * @VisibleForTesting
   */
  final void setParallelProcessing(final boolean parallelProcessing) {
    this.parallelProcessing = parallelProcessing;
  }

  /**
   * @VisibleForTesting
   */
  void setIncrementalBuildEnabled(final boolean incrementalBuildEnabled) {
    this.incrementalBuildEnabled = incrementalBuildEnabled;
  }

  /**
   * @return the minimize
   * @VisibleForTesting
   */
  boolean isMinimize() {
    return this.minimize;
  }

  /**
   * @return the ignoreMissingResources
   * @VisibleForTesting
   */
  String isIgnoreMissingResources() {
    return this.ignoreMissingResources;
  }

  /**
   * Used for testing.
   *
   * @param mavenProject
   *          the mavenProject to set
   */
  void setMavenProject(final MavenProject mavenProject) {
    this.mavenProject = mavenProject;
  }

  /**
   * @return the targetGroups
   * @VisibleForTesting
   */
  String getTargetGroups() {
    return this.targetGroups;
  }

  /**
   * @param versionEncoder
   *          (targetGroups) comma separated group names.
   * @VisibleForTesting
   */
  void setTargetGroups(final String targetGroups) {
    this.targetGroups = targetGroups;
  }

  /**
   * @param wroManagerFactory
   *          fully qualified name of the {@link WroManagerFactory} class.
   * @VisibleForTesting
   */
  void setWroManagerFactory(final String wroManagerFactory) {
    this.wroManagerFactory = wroManagerFactory;
  }

  /**
   * @param extraConfigFile
   *          the extraConfigFile to set
   * @VisibleForTesting
   */
  void setExtraConfigFile(final File extraConfigFile) {
    this.extraConfigFile = extraConfigFile;
  }

  /**
   * @VisibleForTesting
   */
  void setBuildContext(final BuildContext buildContext) {
    this.buildContext = buildContext;
  }

  /**
   * @VisibleForTesting
   */
  BuildContext getBuildContext() {
    return buildContext;
  }

  /**
   * @VisibleForTesting
   */
  void setSkip(final boolean skip) {
    this.skip = skip;
  }

  /**
   * Removes any persisted data creating during the build.
   *
   * @VisibleForTesting
   */
  void clean() {
    try {
      getResourceChangeHandler().destroy();
    } catch (final Exception e) {
      // do not propagate the error during cleanup
      getLog().error("Failed to destroy resourceChangeHandler", e);
    }
  }
}


package ro.isdc.wro.model.resource.processor.decorator;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.model.resource.Resource;

/**
 * Enhance the decorated processor with the ability to skip processing based on minimize aware state of the processor.
 * In other words, if the processor is minimize aware and the minimize flag is set to false, the processor won't be
 * applied and the content will remain unchanged.
 *
 * @author Alex Objelean
 * @since 1.4.7
 * @created 20 May 2012
 */
public class MinimizeAwareProcessorDecorator
    extends ProcessorDecorator {
  private static final Logger LOG = LoggerFactory.getLogger(MinimizeAwareProcessorDecorator.class);
  /**
   * Flag indicating if minimize aware processing is allowed.
   */
  private boolean minimize = true;

  /**
   * Uses minimize flag as true by default.
   *
   * @param processor
   */
  public MinimizeAwareProcessorDecorator(final Object processor) {
    this(processor, true);
  }

  /**
   * Decorates a pre or post processor.
   */
  public MinimizeAwareProcessorDecorator(final Object processor, final boolean minimize) {
    super(processor);
    this.minimize = minimize;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected boolean isEnabled(final Resource resource) {
    // apply processor only when minimize is required or the processor is not minimize aware
    final boolean applyProcessor = (resource != null && resource.isMinimize() && minimize)
        || (resource == null && minimize) || !isMinimize();
    return super.isEnabled(resource) && applyProcessor;
  }
}


/**
 * Copyright Alex Objelean
 */
package ro.isdc.wro.model.factory;

import java.io.IOException;
import java.io.InputStream;

import javax.servlet.ServletContext;

import ro.isdc.wro.WroRuntimeException;
import ro.isdc.wro.config.ReadOnlyContext;
import ro.isdc.wro.model.group.Inject;

/**
 * To be used by the implementations which load the model from a resource provided as stream.
 *
 * @author Alex Objelean
 * @created 9 Aug 2011
 * @since 1.4.0
 */
public abstract class AbstractWroModelFactory
    implements WroModelFactory {
  @Inject
  private ReadOnlyContext context;

  /**
   * Override this method, in order to provide different xml definition file name.
   *
   * @return stream of the xml representation of the model.
   * @throws IOException
   *           if the stream couldn't be read.
   */
  protected InputStream getModelResourceAsStream()
      throws IOException {
    final ServletContext servletContext = context.getServletContext();
    // Don't allow NPE, throw a more detailed exception
    if (servletContext == null) {
      throw new WroRuntimeException(
          "No servletContext is available. Probably you are running this code outside of the request cycle!");
    }
    final String resourceLocation = "/WEB-INF/" + getDefaultModelFilename();
    final InputStream stream = servletContext.getResourceAsStream(resourceLocation);
    if (stream == null) {
      throw new IOException("Invalid resource requested: " + resourceLocation);
    }
    return stream;
  }

  /**
   * @return the default name of the file describing the wro model.
   */
  protected abstract String getDefaultModelFilename();

  /**
   * {@inheritDoc}
   */
  public void destroy() {
  }

}


/*
 * Copyright (c) 2008. All rights reserved.
 */
package ro.isdc.wro.model.resource;

import static org.apache.commons.lang3.Validate.notNull;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

/**
 * Encapsulates information about a resource. This class is not final because we need to mock it in unit tests.
 *
 * @author Alex Objelean
 * @created Created on Oct 30, 2008
 */
public class Resource {
  /**
   * The type of resource.
   */
  private ResourceType type;
  /**
   * Resource identifier.
   */
  private String uri;
  /**
   * Used to skip minimization during pre processing. Useful when you know that the resource is already minimized and no
   * minimization is needed. Default value is true.
   */
  private boolean minimize = true;

  /**
   * Empty constructor. Used by json deserializer.
   */
  public Resource() {
  }

  /**
   * Constructor.
   *
   * @param uri of the resource.
   * @param type of the resource.
   */
  private Resource(final String uri, final ResourceType type) {
    Validate.notNull(uri, "URI cannot be null!");
    Validate.notNull(type, "ResourceType cannot be null!");
    this.uri = cleanUri(uri);
    this.type = type;
  }

  /**
   * Factory method for Resource creation. A factory method is preferred instead of public constructor, in order to
   * avoid possibilities for clients to extend Resource class.
   *
   * @return an instance of {@link Resource} object.
   */
  public static Resource create(final String uri, final ResourceType type) {
    return new Resource(uri, type);
  }

  /**
   * Creates a resource and set the correct {@link ResourceType} based on uri extension. If resourceType cannot be
   * identified an exception is thrown.
   *
   * @param uri of the resource to create.
   * @return Resource with the provided uri and correct type.
   */
  public static Resource create(final String uri) {
    final String resourceExtension = FilenameUtils.getExtension(uri);
    final ResourceType type = ResourceType.get(resourceExtension);
    return new Resource(uri, type);
  }

  /**
   * Perform a cleaning of the uri by trimming it and removing last '/' character if exists.
   *
   * @param uri to clean.
   * @return cleaned uri.
   */
  private static String cleanUri(final String uri) {
    String result = uri.trim();
    //handle empty uri
    if (!StringUtils.isEmpty(uri)) {
      final int endIndex = result.length() - 1;
      if (result.lastIndexOf('/') == endIndex) {
        result = result.substring(0, endIndex);
      }
    }
    return result;
  }

  /**
   * @param type the type to set
   */
  public void setType(final ResourceType type) {
    notNull(type, "ResourceType cannot be null!");
    this.type = type;
  }

  /**
   * @param uri the uri to set
   */
  public void setUri(final String uri) {
    notNull(uri, "Uri cannot be null!");
    this.uri = cleanUri(uri);
  }

  /**
   * @return the type
   */
  public ResourceType getType() {
    return type;
  }

  /**
   * @return the uri associated with this resource.
   */
  public String getUri() {
    return uri;
  }

  /**
   * @return the minimize
   */
  public boolean isMinimize() {
    return this.minimize;
  }

  /**
   * @param minimize the minimize to set
   */
  public void setMinimize(final boolean minimize) {
    this.minimize = minimize;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean equals(final Object obj) {
    if (obj instanceof Resource) {
      final Resource resource = (Resource)obj;
      return getUri().equals(resource.getUri()) && getType().equals(resource.getType());
    }
    return false;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int hashCode() {
    int hash = 7;
    //handle null, it can happen when empty constructor is used.
    hash = 31 * hash + (this.type != null ? this.type.hashCode() : 0);
    hash = 31 * hash + (this.uri != null ? this.uri.hashCode() : 0);
    return hash;
  }

  /**
   * @see java.lang.Object#toString()
   */
  @Override
  public String toString() {
    return ToStringBuilder.reflectionToString(this, ToStringStyle.NO_FIELD_NAMES_STYLE);
  }
}


/*
 * Copyright (c) 2008. All rights reserved.
 */
package ro.isdc.wro.model.resource.locator;

import static org.apache.commons.lang3.Validate.notNull;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.Validate;

import ro.isdc.wro.config.Context;
import ro.isdc.wro.config.jmx.WroConfiguration;
import ro.isdc.wro.model.resource.locator.support.LocatorProvider;
import ro.isdc.wro.model.resource.locator.wildcard.WildcardUriLocatorSupport;

/**
 * UriLocator capable to read the resources from some URL. Usually, this uriLocator will be the last in the chain of
 * uriLocators.
 *
 * @author Alex Objelean
 * @created Created on Nov 10, 2008
 */
public class UrlUriLocator extends WildcardUriLocatorSupport {
  /**
   * Alias used to register this locator with {@link LocatorProvider}.
   */
  public static final String ALIAS = "uri";
  private int timeout = WroConfiguration.DEFAULT_CONNECTION_TIMEOUT;
  /**
   * {@inheritDoc}
   */
  public boolean accept(final String uri) {
    return isValid(uri);
  }

  /**
   * Check if a uri is a URL resource.
   *
   * @param uri to check.
   * @return true if the uri is a URL resource.
   */
  public static boolean isValid(final String uri) {
    // if creation of URL object doesn't throw an exception, the uri can be
    // accepted.
    try {
      new URL(uri);
    } catch (final MalformedURLException e) {
      return false;
    }
    return true;
  }

  /**
   * {@inheritDoc}
   */
  public InputStream locate(final String uri)
    throws IOException {
    notNull(uri, "uri cannot be NULL!");
    if (getWildcardStreamLocator().hasWildcard(uri)) {
      final String fullPath = FilenameUtils.getFullPath(uri);
      final URL url = new URL(fullPath);
      return getWildcardStreamLocator().locateStream(uri, new File(URLDecoder.decode(url.getFile(), "UTF-8")));
    }
    final URL url = new URL(uri);
    final URLConnection connection = url.openConnection();
    // avoid jar file locking on Windows.
    connection.setUseCaches(false);
    //add explicit user agent header. This is required by some cdn resources which otherwise would return 403 status code.
    connection.setRequestProperty("User-Agent", "java");
    // setting these timeouts ensures the client does not deadlock indefinitely
    // when the server has problems.
    connection.setConnectTimeout(timeout);
    connection.setReadTimeout(timeout);

    return new BufferedInputStream(connection.getInputStream());
  }

  /**
   * The read & connect timeout value (in milliseconds) used to limit wait period to a reasonable value. By default
   * {@value WroConfiguration#DEFAULT_CONNECTION_TIMEOUT} ms is used.
   */
  public void setTimeout(int timeout) {
    this.timeout = timeout;
  }
}


package ro.isdc.wro.model.resource.processor.impl.js;

import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.regex.Pattern;

import org.apache.commons.io.IOUtils;

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.processor.ResourcePreProcessor;
import ro.isdc.wro.util.WroUtil;

/**
 * A preProcessor, responsible for removing console.log(..) and console.debug(..) statements.
 *
 * @author Ivar Conradi Østhus
 */
@SupportedResourceType(ResourceType.JS)
public class ConsoleStripperProcessor
  implements ResourcePreProcessor {
  public static final String ALIAS = "consoleStripper";
  /**
   * Matches console statements
   */
  public static final Pattern PATTERN = Pattern.compile(WroUtil.loadRegexpWithKey("javascript.consoleStripper"));

  /**
   * {@inheritDoc}
   */
  public void process(final Resource resource, final Reader reader, final Writer writer)
    throws IOException {
    try {
      final String content = IOUtils.toString(reader);
      String result = ConsoleStripperProcessor.PATTERN.matcher(content).replaceAll("");
      writer.write(result);
    } finally {
      reader.close();
      writer.close();
    }
  }
}


package ro.isdc.wro.manager.factory;

import static org.apache.commons.lang3.Validate.notNull;

import java.util.Properties;

import org.apache.commons.lang3.StringUtils;

import ro.isdc.wro.WroRuntimeException;
import ro.isdc.wro.config.jmx.ConfigConstants;
import ro.isdc.wro.config.jmx.WroConfiguration;
import ro.isdc.wro.config.support.PropertiesFactory;
import ro.isdc.wro.manager.WroManager;
import ro.isdc.wro.util.ObjectFactory;

/**
 * Load the WroManagerFactory configured in {@link WroConfiguration} or loads a default one if none is configured.
 *
 * @author Alex Objelean
 * @created 7 May 2012
 * @since 1.4.6
 */
public class DefaultWroManagerFactory
    implements WroManagerFactory {
  private final WroManagerFactory factory;

  /**
   * A factory method which uses {@link WroConfiguration} to get the configured wroManager className.
   *
   * @param configuration
   *          {@link WroConfiguration} to get the {@link ConfigConstants#managerFactoryClassName} from.
   */
  public static DefaultWroManagerFactory create(final WroConfiguration configuration) {
    return create(new ObjectFactory<WroConfiguration>() {
      public WroConfiguration create() {
        return configuration;
      }
    });
  }

  public static DefaultWroManagerFactory create(final ObjectFactory<WroConfiguration> configurationFactory) {
    notNull(configurationFactory);
    final Properties properties = configurationFactory instanceof PropertiesFactory
        ? ((PropertiesFactory) configurationFactory).createProperties() : new Properties();
    final String wroManagerClassName = configurationFactory.create().getWroManagerClassName();
    if (wroManagerClassName != null) {
      properties.setProperty(ConfigConstants.managerFactoryClassName.name(), wroManagerClassName);
    }
    return new DefaultWroManagerFactory(properties);
  }

  /**
   * Responsible for creating an instance of {@link WroManagerFactory} whose className is configured in provided
   * {@link Properties}. If managerFactoryClassName is missing, a default one is created.
   *
   * @param properties
   *          {@link Properties} from where the managerFactoryClassName will be read in order to load an instance of
   *          {@link WroManagerFactory}.
   */
  public DefaultWroManagerFactory(final Properties properties) {
    notNull(properties);
    factory = initFactory(properties);
  }

  /**
   * Initialized inner factory based on provided configuration.
   */
  private WroManagerFactory initFactory(final Properties properties) {
    WroManagerFactory factory = null;
    final String wroManagerClassName = properties.getProperty(ConfigConstants.managerFactoryClassName.name());
    if (StringUtils.isEmpty(wroManagerClassName)) {
      // If no context param was specified we return the default factory
      factory = newManagerFactory();
    } else {
      // Try to find the specified factory class
      Class<?> factoryClass = null;
      try {
        factoryClass = Thread.currentThread().getContextClassLoader().loadClass(wroManagerClassName);
        factory = (WroManagerFactory) factoryClass.newInstance();
      } catch (final Exception e) {
        throw new WroRuntimeException("Exception while loading WroManagerFactory class:" + wroManagerClassName, e);
      }
    }
    // add properties if required
    if (factory instanceof ConfigurableWroManagerFactory) {
      ((ConfigurableWroManagerFactory) factory).addConfigProperties(properties);
    }
    return factory;
  }

  /**
   * @VisibleForTesting
   * @return the default {@link WroManagerFactory} to be used when {@link WroConfiguration} doesn't specify any factory.
   *         Since 1.8.0, the default manager factory is {@link ConfigurableWroManagerFactory}.
   */
  WroManagerFactory newManagerFactory() {
    return new ConfigurableWroManagerFactory();
  }

  /**
   * {@inheritDoc}
   */
  public void onCachePeriodChanged(final long value) {
    factory.onCachePeriodChanged(value);
  }

  /**
   * {@inheritDoc}
   */
  public void onModelPeriodChanged(final long value) {
    factory.onModelPeriodChanged(value);
  }

  /**
   * {@inheritDoc}
   */
  public WroManager create() {
    return factory.create();
  }

  /**
   * {@inheritDoc}
   */
  public void destroy() {
    factory.destroy();
  }

  /**
   * @VisibleForTesting
   * @return the inner factory.
   */
  public final WroManagerFactory getFactory() {
    return factory;
  }

  @Override
  public String toString() {
    return factory.toString();
  }
}


/*
 * Copyright 2010 Alex Objelean.
 */
package ro.isdc.wro.extensions.processor.support.packer;

import java.io.IOException;
import java.io.InputStream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.extensions.script.RhinoScriptBuilder;
import ro.isdc.wro.util.StopWatch;
import ro.isdc.wro.util.WroUtil;

/**
 * The underlying implementation use the packer.js version 3.1 utility from base2 project: {@link http
 * ://code.google.com/p/base2/}.

 *
 * @author Alex Objelean
 * @since 1.3.0
 */
public class PackerJs {
  private static final Logger LOG = LoggerFactory.getLogger(PackerJs.class);

  /**
   * Initialize script builder for evaluation.
   */
  private RhinoScriptBuilder initScriptBuilder() {
    try {
      return RhinoScriptBuilder.newChain().evaluateChain(getStreamForBase2(), "base2.min.js").evaluateChain(
          getStreamForPacker(), "packer.min.js");
    } catch (final IOException ex) {
      throw new IllegalStateException("Failed reading init script", ex);
    }
  }

  /**
   * Override this method if you have a newer version of base2.js file.
   *
   * @return Stream for base2.js
   */
  protected InputStream getStreamForBase2() {
    return PackerJs.class.getResourceAsStream("base2.min.js");
  }

  /**
   * Override this method if you have a newer version of packer.js file.
   *
   * @return Stream for packer.js
   */
  protected InputStream getStreamForPacker() {
    return PackerJs.class.getResourceAsStream("packer.min.js");
  }

  /**
   * @param data
   *          js content to process.
   * @return packed js content.
   */
  public String pack(final String data)
      throws IOException {
    final StopWatch watch = new StopWatch();
    watch.start("init");
    final RhinoScriptBuilder builder = initScriptBuilder();
    watch.stop();
    watch.start("pack");

    final String packIt = buildPackScript(WroUtil.toJSMultiLineString(data));
    final Object result = builder.evaluate(packIt, "packerIt");
    watch.stop();
    LOG.debug(watch.prettyPrint());
    return String.valueOf(result);
  }

  /**
   * @param data
   *          script to pack.
   * @return Script used to pack and return the packed result.
   */
  protected String buildPackScript(final String data) {
    return "new Packer().pack(" + data + ", true, true);";
  }
}


/*
 * Copyright (C) 2011 wro4j.
 * All rights reserved.
 */
package ro.isdc.wro.model.factory;

import java.util.Collections;
import java.util.List;

import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.WroRuntimeException;
import ro.isdc.wro.model.WroModel;
import ro.isdc.wro.model.group.Inject;
import ro.isdc.wro.model.group.processor.Injector;
import ro.isdc.wro.util.Transformer;

/**
 * Applies transformation using supplied transformers to the decorated model.
 *
 * @author Alex Objelean
 * @created 23 Aug 2011
 * @since 1.4.0
 */
public class ModelTransformerFactory
    extends WroModelFactoryDecorator {
  private static final Logger LOG = LoggerFactory.getLogger(ModelTransformerFactory.class);
  private List<? extends Transformer<WroModel>> modelTransformers = Collections.emptyList();
  @Inject
  private Injector injector;

  /**
   * Decorates a model factory.
   */
  public ModelTransformerFactory(final WroModelFactory decorated) {
    super(decorated);
  }

  /**
   * Set a list of transformers to apply on decorated model factory.
   */
  public ModelTransformerFactory setTransformers(final List<Transformer<WroModel>> modelTransformers) {
    Validate.notNull(modelTransformers);
    this.modelTransformers = modelTransformers;
    return this;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public WroModel create() {
    WroModel model = super.create();
    LOG.debug("using {} transformers", modelTransformers);
    for (final Transformer<WroModel> transformer : modelTransformers) {
      injector.inject(transformer);
      LOG.debug("using transformer: {}", transformer.getClass());
      try {
        model = transformer.transform(model);
      } catch (final Exception e) {
        throw new WroRuntimeException("Exception during model transformation", e);
      }
    }
    return model;
  }
}


package ro.isdc.wro.extensions.processor.support.emberjs;

import java.io.IOException;
import java.io.InputStream;
import java.io.SequenceInputStream;
import java.util.Vector;

import ro.isdc.wro.extensions.locator.WebjarUriLocator;
import ro.isdc.wro.extensions.processor.support.template.AbstractJsTemplateCompiler;

/**
 * EmberJS is a framework which provide a templating engine built on top of Handlebars. The emberJs library and all its
 * dependencies are loaded from the webjar.
 *
 * @author blemoine
 */
public class EmberJs
    extends AbstractJsTemplateCompiler {
  /*
   * The Ember Template Compiler is built for CommonJs environment, but Rhino doesn't comply with CommonJs Standard
   * There is no 'exports' object in Rhino, so this file creates it, as well as an helper function
   */
  private static final String DEFAULT_HEADLESS_RHINO_JS = "headless-rhino.js";
  private WebjarUriLocator webjarLocator;

  /**
   * visible for testing, the init of a HandlebarsJs template
   */
  @Override
  public String compile(final String content, final String name) {
    final String precompiledFunction = super.compile(content, "");
    return String.format("(function() {Ember.TEMPLATES[%s] = Ember.Handlebars.template(%s)})();", name, precompiledFunction);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected String getCompileCommand() {
    // Function present in headless-ember
    return "precompile";
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected InputStream getCompilerAsStream() throws IOException {
    final Vector<InputStream> inputStreams = new Vector<InputStream>();
    inputStreams.add(getWebjarLocator().locate(WebjarUriLocator.createUri("jquery.js")));
    inputStreams.add(getWebjarLocator().locate(WebjarUriLocator.createUri("handlebars.js")));
    inputStreams.add(getWebjarLocator().locate(WebjarUriLocator.createUri("ember.js")));
    inputStreams.add(EmberJs.class.getResourceAsStream(DEFAULT_HEADLESS_RHINO_JS));

    return new SequenceInputStream(inputStreams.elements());
  }

  /**
   * @return {@link WebjarUriLocator} instance to retrieve webjars.
   */
  private WebjarUriLocator getWebjarLocator() {
    if (webjarLocator == null) {
      webjarLocator = new WebjarUriLocator();
    }
    return webjarLocator;
  }
}


/*
 * Copyright (c) 2010. All rights reserved.
 */
package ro.isdc.wro.model.resource.processor.impl.css;

import static ro.isdc.wro.util.WroUtil.cleanImageUrl;

import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.regex.Matcher;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.WroRuntimeException;
import ro.isdc.wro.config.ReadOnlyContext;
import ro.isdc.wro.http.handler.ResourceProxyRequestHandler;
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.processor.ImportAware;
import ro.isdc.wro.model.resource.processor.ResourcePostProcessor;
import ro.isdc.wro.model.resource.processor.ResourcePreProcessor;
import ro.isdc.wro.model.resource.processor.support.CssUrlInspector;
import ro.isdc.wro.model.resource.processor.support.CssUrlInspector.ItemHandler;
import ro.isdc.wro.model.resource.processor.support.DataUriGenerator;

/**
 * A processor responsible for rewriting url's from inside the css resources.
 *
 * @author Alex Objelean
 * @created Created on 9 May, 2010
 */
@SupportedResourceType(ResourceType.CSS)
public abstract class AbstractCssUrlRewritingProcessor
    implements ResourcePreProcessor, ResourcePostProcessor, ImportAware {
  private static final Logger LOG = LoggerFactory.getLogger(AbstractCssUrlRewritingProcessor.class);

  @Inject
  private ReadOnlyContext context;

  public void process(final Reader reader, final Writer writer)
      throws IOException {
    throw new WroRuntimeException("This processor: " + getClass().getSimpleName() + " cannot work as a postProcessor!");
  }

  public final void process(final Resource resource, final Reader reader, final Writer writer)
      throws IOException {
    LOG.debug("Applying {} processor", getClass().getSimpleName());
    try {
      final String cssUri = resource != null ? resource.getUri() : "";
      LOG.debug("cssUri: {}", cssUri);
      final String css = IOUtils.toString(reader);
      final String result = newCssUrlInspector().findAndReplace(css, createUrlItemHandler(cssUri));
      writer.write(result);
      onProcessCompleted();
    } finally {
      reader.close();
      writer.close();
    }
  }

  private ItemHandler createUrlItemHandler(final String cssUri) {
    return new ItemHandler() {
      public String replace(final String originalDeclaration, final String originalUrl) {
        Validate.notNull(originalUrl);
        String replacement = originalDeclaration;
        if (isReplaceNeeded(originalUrl)) {
          final String modifiedUrl = replaceImageUrl(cssUri.trim(), cleanImageUrl(originalUrl));
          LOG.debug("replaced old Url: [{}] with: [{}].", originalUrl, modifiedUrl);
          /**
           * prevent the IllegalArgumentException because of invalid characters like $ (@see issue381) The solution is
           * from stackoverflow: @see
           * http://stackoverflow.com/questions/947116/matcher-appendreplacement-with-literal-text
           */
          final String modifiedDeclaration = Matcher.quoteReplacement(originalDeclaration.replace(originalUrl,
              modifiedUrl));
          onUrlReplaced(modifiedUrl);
          replacement = replaceDeclaration(originalDeclaration.trim(), modifiedDeclaration);
        }
        return replacement;
      }
    };
  }

  protected CssUrlInspector newCssUrlInspector() {
    return new CssUrlInspector();
  }

  /**
   * Invoked when the process operation is completed. Useful to invoke some post processing logic or for custom logging.
   */
  protected void onProcessCompleted() {
  }

  /**
   * Invoked to replace the entire css declaration.
   *

   * An example of css declaration:
   *
   *

   * background: url(/image.png);
   *

   *
   * Useful when the css declaration should be changed. The use-case is: {@link FallbackCssDataUriProcessor}.
   *
   * @param originalDeclaration
   *          the original, unchanged declaration.
   * @param modifiedDeclaration
   *          the changed expression.
   * @return the expression to apply. By default the modifiedExpression will be returned.
   */
  protected String replaceDeclaration(final String originalDeclaration, final String modifiedDeclaration) {
    return modifiedDeclaration;
  }

  /**
   * Invoked when an url is replaced. Useful if you need to do something with newly replaced url.
   *
   * @param replacedUrl
   *          the newly computed url created as a result of url rewriting.
   */
  protected void onUrlReplaced(final String replacedUrl) {
  }

  /**
   * Replace provided url with the new url if needed.
   *
   * @param cssUri
   *          Uri of the parsed css.
   * @param imageUrl
   *          to replace.
   * @return replaced url.
   */
  protected abstract String replaceImageUrl(final String cssUri, final String imageUrl);

  /**
   * Check if url must be replaced or not. The replacement is not needed if the url of the image is absolute (can be
   * resolved by urlResourceLocator) or if the url is a data uri (base64 encoded value).
   *
   * @param url
   *          to check.
   * @return true if url needs to be replaced or remain unchanged.
   */
  protected boolean isReplaceNeeded(final String url) {
    return !(UrlUriLocator.isValid(url) || DataUriGenerator.isDataUri(url.trim()));
  }

  /**
   * This method has protected modifier in order to be accessed by unit test class.
   *
   * @return urlPrefix value.
   * @VisibleForTesting
   */
  protected String getUrlPrefix() {
    return ResourceProxyRequestHandler.createProxyPath(context.getRequest().getRequestURI(), "");
  }

  /**
   * {@inheritDoc}
   */
  public boolean isImportAware() {
    // We want this processor to be applied when processing resources referred with @import directive
    return true;
  }
}


package ro.isdc.wro.model.group.processor;

import static org.apache.commons.lang3.Validate.notNull;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.BOMInputStream;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.WroRuntimeException;
import ro.isdc.wro.config.Context;
import ro.isdc.wro.config.ReadOnlyContext;
import ro.isdc.wro.config.support.ContextPropagatingCallable;
import ro.isdc.wro.manager.callback.LifecycleCallbackRegistry;
import ro.isdc.wro.model.group.Inject;
import ro.isdc.wro.model.resource.Resource;
import ro.isdc.wro.model.resource.locator.factory.UriLocatorFactory;
import ro.isdc.wro.model.resource.processor.ResourcePreProcessor;
import ro.isdc.wro.model.resource.processor.decorator.DefaultProcessorDecorator;
import ro.isdc.wro.model.resource.processor.factory.ProcessorsFactory;
import ro.isdc.wro.model.resource.processor.support.ProcessingCriteria;
import ro.isdc.wro.model.resource.processor.support.ProcessingType;
import ro.isdc.wro.util.WroUtil;

/**
 * TODO: refactor this class. Apply all preProcessor on provided {@link Resource} and returns the result of execution as
 * String.
 * <p>
 * This is useful when you want to preProcess a resource which is not a part of the model (css import use-case).
 *
 * @author Alex Objelean
 */
public class PreProcessorExecutor {
  private static final Logger LOG = LoggerFactory.getLogger(PreProcessorExecutor.class);
  @Inject
  private UriLocatorFactory uriLocatorFactory;
  @Inject
  private ProcessorsFactory processorsFactory;
  @Inject
  private ReadOnlyContext context;
  @Inject
  private LifecycleCallbackRegistry callbackRegistry;
  @Inject
  private Injector injector;
  /**
   * Runs the preProcessing in parallel.
   */
  private ExecutorService executor;

  /**
   * Apply preProcessors on resources and merge them after all preProcessors are applied.
   *
   * @param resources
   *          what are the resources to merge.
   * @param minimize
   *          whether minimize aware processors must be applied or not.
   * @return preProcessed merged content.
   */
  public String processAndMerge(final List<Resource> resources, final boolean minimize)
      throws IOException {
    return processAndMerge(resources, ProcessingCriteria.create(ProcessingType.ALL, minimize));
  }

  /**
   * Apply preProcessors on resources and merge them.
   *
   * @param resources
   *          what are the resources to merge.
   * @param criteria
   *          {@link ProcessingCriteria} used to identify the processors to apply and those to skip.
   * @return preProcessed merged content.
   */
  public String processAndMerge(final List<Resource> resources, final ProcessingCriteria criteria)
      throws IOException {
    notNull(criteria);
    LOG.debug("criteria: {}", criteria);
    callbackRegistry.onBeforeMerge();
    try {
      notNull(resources);
      LOG.debug("process and merge resources: {}", resources);
      final StringBuffer result = new StringBuffer();
      if (shouldRunInParallel(resources)) {
        result.append(runInParallel(resources, criteria));
      } else {
        for (final Resource resource : resources) {
          LOG.debug("\tmerging resource: {}", resource);
          result.append(applyPreProcessors(resource, criteria));
        }
      }
      return result.toString();
    } finally {
      callbackRegistry.onAfterMerge();
    }
  }

  private boolean shouldRunInParallel(final List<Resource> resources) {
    final boolean isParallel = context.getConfig().isParallelPreprocessing();
    final int availableProcessors = Runtime.getRuntime().availableProcessors();
    return isParallel && resources.size() > 1 && availableProcessors > 1;
  }

  /**
   * runs the pre processors in parallel.
   *
   * @return merged and pre processed content.
   */
  private String runInParallel(final List<Resource> resources, final ProcessingCriteria criteria)
      throws IOException {
    LOG.debug("Running preProcessing in Parallel");
    final StringBuffer result = new StringBuffer();
    final List<Callable<String>> callables = new ArrayList<Callable<String>>();
    for (final Resource resource : resources) {
      callables.add(new Callable<String>() {
        public String call()
            throws Exception {
          LOG.debug("Callable started for resource: {} ...", resource);
          return applyPreProcessors(resource, criteria);
        }
      });
    }
    final ExecutorService exec = getExecutorService();
    final List<Future<String>> futures = new ArrayList<Future<String>>();
    for (final Callable<String> callable : callables) {
      // decorate with ContextPropagatingCallable in order to allow spawn threads to access the Context
      final Callable<String> decoratedCallable = new ContextPropagatingCallable<String>(callable);
      futures.add(exec.submit(decoratedCallable));
    }

    for (final Future<String> future : futures) {
      try {
        result.append(future.get());
      } catch (final Exception e) {
        // propagate original cause
        final Throwable cause = e.getCause();
        if (cause instanceof WroRuntimeException) {
          throw (WroRuntimeException) cause;
        } else if (cause instanceof IOException) {
          throw (IOException) cause;
        } else {
          throw new WroRuntimeException("Problem during parallel pre processing", e);
        }
      }
    }
    return result.toString();
  }

  private ExecutorService getExecutorService() {
    if (executor == null) {
      // use at most the number of available processors (true parallelism)
      final int threadPoolSize = Runtime.getRuntime().availableProcessors();
      LOG.debug("Parallel thread pool size: {}", threadPoolSize);
      executor = Executors.newFixedThreadPool(threadPoolSize,
          WroUtil.createDaemonThreadFactory("parallelPreprocessing"));
    }
    return executor;
  }

  /**
   * Apply a list of preprocessors on a resource.
   *
   * @param resource
   *          the {@link Resource} on which processors will be applied
   * @param processors
   *          the list of processor to apply on the resource.
   */
  private String applyPreProcessors(final Resource resource, final ProcessingCriteria criteria)
      throws IOException {
    final Collection<ResourcePreProcessor> processors = processorsFactory.getPreProcessors();
    LOG.debug("applying preProcessors: {}", processors);

    String resourceContent = null;
    try {
      resourceContent = getResourceContent(resource);
    } catch (final IOException e) {
      LOG.debug("Invalid resource found: {}", resource);
      if (Context.get().getConfig().isIgnoreMissingResources()) {
        return StringUtils.EMPTY;
      } else {
        LOG.error("Cannot ignore missing resource:  {}", resource);
        throw e;
      }
    }
    if (!processors.isEmpty()) {
      Writer writer = null;
      for (final ResourcePreProcessor processor : processors) {
        final ResourcePreProcessor decoratedProcessor = decoratePreProcessor(processor, criteria);

        writer = new StringWriter();
        final Reader reader = new StringReader(resourceContent);
        // decorate and process
        decoratedProcessor.process(resource, reader, writer);
        // use the outcome for next input
        resourceContent = writer.toString();
      }
    }
    // add explicitly new line at the end to avoid unexpected comment issue
    return String.format("%s%n", resourceContent);
  }

  /**
   * Decorates preProcessor with mandatory decorators.
   * This method is synchronized to ensure that processor is injected before it is being used by other thread.
   */
  private synchronized ResourcePreProcessor decoratePreProcessor(final ResourcePreProcessor processor,
      final ProcessingCriteria criteria) {
    final ResourcePreProcessor decorated = new DefaultProcessorDecorator(processor, criteria) {
      @Override
      public void process(final Resource resource, final Reader reader, final Writer writer)
          throws IOException {
        try {
          callbackRegistry.onBeforePreProcess();
          super.process(resource, reader, writer);
        } finally {
          callbackRegistry.onAfterPreProcess();
        }
      }
    };
    injector.inject(decorated);
    return decorated;
  }

  /**
   * @return a Reader for the provided resource.
   * @param resource
   *          {@link Resource} which content to return.
   * @param resources
   *          the list of all resources processed in this context, used for duplicate resource detection.
   */
  private String getResourceContent(final Resource resource)
      throws IOException {
    InputStream is = null;
    try {
      is = new BOMInputStream(uriLocatorFactory.locate(resource.getUri()));
      final String result = IOUtils.toString(is, context.getConfig().getEncoding());
      if (StringUtils.isEmpty(result)) {
        LOG.debug("Empty resource detected: {}", resource.getUri());
      }
      return result;
    } finally {
      IOUtils.closeQuietly(is);
    }
  }

  /**
   * Perform cleanUp on service shut down.
   */
  public void destroy() {
    getExecutorService().shutdownNow();
  }
}


/*
 * Copyright (c) 2011. All rights reserved.
 */
package ro.isdc.wro.manager.callback;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.config.Context;
import ro.isdc.wro.model.resource.Resource;
import ro.isdc.wro.util.Function;
import ro.isdc.wro.util.ObjectFactory;

/**
 * Register all available callbacks. The registry acts as a {@link LifecycleCallback} itself whose implementation
 * delegate the call to registered callbacks. The registry will handle any runtime exceptions thrown by callbacks, in
 * order to allow successful lifecycle execution.
 *
 * @author Alex Objelean
 * @created Created on 8 Dec 2011
 * @since 1.4.3
 */
public class LifecycleCallbackRegistry
  implements LifecycleCallback {
  private static final Logger LOG = LoggerFactory.getLogger(LifecycleCallbackRegistry.class);

  /**
   * The list of registered callbacks.
   */
  private final List<ObjectFactory<LifecycleCallback>> callbackFactoryList = new ArrayList<ObjectFactory<LifecycleCallback>>();
  private final Map<String, List<LifecycleCallback>> map = new ConcurrentHashMap<String, List<LifecycleCallback>>();

  /**
   * Register a callback using a factory responsible for callback instantiation.
   *
   * @param callbackFactory
   *          the factory used to instantiate callbacks.
   */
  public void registerCallback(final ObjectFactory<LifecycleCallback> callbackFactory) {
    callbackFactoryList.add(callbackFactory);
  }

  private List<LifecycleCallback> getCallbacks() {
    final String key = Context.getCorrelationId();
    List<LifecycleCallback> callbacks = map.get(key);
    if (callbacks == null) {
      callbacks = initCallbacks();
      map.put(key, callbacks);
    }
    return callbacks;
  }

  protected List<LifecycleCallback> initCallbacks() {
    final List<LifecycleCallback> callbacks = new ArrayList<LifecycleCallback>();
    for (final ObjectFactory<LifecycleCallback> callbackFactory : callbackFactoryList) {
      callbacks.add(callbackFactory.create());
    }
    return callbacks;
  }

  /**
   * {@inheritDoc}
   */
  public void onBeforeModelCreated() {
    forEachCallbackDo(new Function<LifecycleCallback, Void>() {
      public Void apply(final LifecycleCallback input)
          throws Exception {
        input.onBeforeModelCreated();
        return null;
      }
    });
  }

  /**
   * {@inheritDoc}
   */
  public void onAfterModelCreated() {
    forEachCallbackDo(new Function<LifecycleCallback, Void>() {
      public Void apply(final LifecycleCallback input)
          throws Exception {
        input.onAfterModelCreated();
        return null;
      }
    });
  }

  /**
   * {@inheritDoc}
   */
  public void onBeforePreProcess() {
    forEachCallbackDo(new Function<LifecycleCallback, Void>() {
      public Void apply(final LifecycleCallback input)
          throws Exception {
        input.onBeforePreProcess();
        return null;
      }
    });
  }

  /**
   * {@inheritDoc}
   */
  public void onAfterPreProcess() {
    forEachCallbackDo(new Function<LifecycleCallback, Void>() {
      public Void apply(final LifecycleCallback input)
          throws Exception {
        input.onAfterPreProcess();
        return null;
      }
    });
  }

  /**
   * {@inheritDoc}
   */
  public void onBeforePostProcess() {
    forEachCallbackDo(new Function<LifecycleCallback, Void>() {
      public Void apply(final LifecycleCallback input)
          throws Exception {
        input.onBeforePostProcess();
        return null;
      }
    });
  }

  /**
   * {@inheritDoc}
   */
  public void onAfterPostProcess() {
    forEachCallbackDo(new Function<LifecycleCallback, Void>() {
      public Void apply(final LifecycleCallback input)
          throws Exception {
        input.onAfterPostProcess();
        return null;
      }
    });
  }

  /**
   * {@inheritDoc}
   */
  public void onBeforeMerge() {
    forEachCallbackDo(new Function<LifecycleCallback, Void>() {
      public Void apply(final LifecycleCallback input)
          throws Exception {
        input.onBeforeMerge();
        return null;
      }
    });
  }

  public void onAfterMerge() {
    forEachCallbackDo(new Function<LifecycleCallback, Void>() {
      public Void apply(final LifecycleCallback input)
          throws Exception {
        input.onAfterMerge();
        return null;
      }
    });
  }

  /**
   * {@inheritDoc}
   */
  public void onProcessingComplete() {
    forEachCallbackDo(new Function<LifecycleCallback, Void>() {
      public Void apply(final LifecycleCallback input)
          throws Exception {
        input.onProcessingComplete();
        return null;
      }
    });
  }

  public void onResourceChanged(final Resource resource) {
    forEachCallbackDo(new Function<LifecycleCallback, Void>() {
      public Void apply(final LifecycleCallback input)
          throws Exception {
        input.onResourceChanged(resource);
        return null;
      }
    });
  }

  private void forEachCallbackDo(final Function<LifecycleCallback, Void> func) {
    for (final LifecycleCallback callback : getCallbacks()) {
      try {
        func.apply(callback);
      } catch (final Exception e) {
        LOG.error("Problem invoking callback", e);
        onException(e);
      } finally {
        map.remove(Context.getCorrelationId());
      }
    }
  }

  /**
   * Invoked when a callback fails. By default exception is ignored.
   *
   * @param e
   *          {@link Exception} thrown by the fallback.
   */
  protected void onException(final Exception e) {
  }
}


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);
    }
  }
}


/**
 * Copyright Alex Objelean
 */
package ro.isdc.wro.model.group.processor;

import static org.apache.commons.lang3.Validate.notNull;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.WroRuntimeException;
import ro.isdc.wro.config.Context;
import ro.isdc.wro.model.group.Inject;
import ro.isdc.wro.model.group.processor.InjectorBuilder.InjectorObjectFactory;
import ro.isdc.wro.util.ObjectDecorator;

/**
 * Injector scans some object fields and checks if a value can be provided to a field; Injector will ignore all non-null
 * fields.
 *
 * @author Alex Objelean
 * @created 20 Nov 2010
 */
public final class Injector {
  private static final Logger LOG = LoggerFactory.getLogger(Injector.class);
  private final Map<Class<?>, Object> map;
  private final Map<Object, Boolean> injectedObjects = Collections.synchronizedMap(new WeakHashMap<Object, Boolean>());
  /**
   * Mapping of classes to be annotated and the corresponding injected object.
   */
  Injector(final Map<Class<?>, Object> map) {
    notNull(map);
    this.map = map;
  }

  /**
   * Scans the object and inject the supported values into the fields having @Inject annotation present.
   *
   * @param object
   *          {@link Object} which will be scanned for @Inject annotation presence.
   * @return the injected object instance. Useful for fluent interface.
   */
  public <T> T inject(final T object) {
    notNull(object);
//    if (!Context.isContextSet()) {
//      throw new WroRuntimeException("No Context Set");
//    }
    if (!injectedObjects.containsKey(computeKey(object))) {
      injectedObjects.put(computeKey(object), true);
      processInjectAnnotation(object);
    }
    return object;
  }

  private <T> int computeKey(final T object) {
    return System.identityHashCode(object);
  }

  /**
   * Check for each field from the passed object if @Inject annotation is present & inject the required field if
   * supported, otherwise warns about invalid usage.
   *
   * @param object
   *          to check for annotation presence.
   */
  private void processInjectAnnotation(final Object object) {
    try {
      final Collection<Field> fields = getAllFields(object);
      for (final Field field : fields) {
        if (field.isAnnotationPresent(Inject.class)) {
          if (!acceptAnnotatedField(object, field)) {
            final String message = String.format(
                "@Inject cannot be applied on object: %s to field of type: %s using injector %s", object,
                field.getType(), this);
            LOG.error(message + ". Supported types are: {}", map.keySet());
            throw new WroRuntimeException(message);
          }
        }
      }
      // handle special cases like decorators. Perform recursive injection
      if (object instanceof ObjectDecorator) {
        processInjectAnnotation(((ObjectDecorator<?>) object).getDecoratedObject());
      }
    } catch (final Exception e) {
      LOG.error("Error while scanning @Inject annotation", e);
      throw WroRuntimeException.wrap(e, "Exception while trying to process @Inject annotation on object: " + object);
    }
  }

  /**
   * Return all fields for given object, also those from the super classes.
   */
  private Collection<Field> getAllFields(final Object object) {
    final Collection<Field> fields = new ArrayList<Field>();
    fields.addAll(Arrays.asList(object.getClass().getDeclaredFields()));
    // inspect super classes
    Class<?> superClass = object.getClass().getSuperclass();
    while (superClass != null) {
      fields.addAll(Arrays.asList(superClass.getDeclaredFields()));
      superClass = superClass.getSuperclass();
    }
    return fields;
  }

  /**
   * Analyze the field containing {@link Inject} annotation and set its value to appropriate value. Override this method
   * if you want to inject something else but uriLocatorFactory.
   *
   * @param object
   *          an object containing @Inject annotation.
   * @param field
   *          {@link Field} object containing {@link Inject} annotation.
   * @return true if field was injected with some not null value.
   * @throws IllegalAccessException
   */
  private boolean acceptAnnotatedField(final Object object, final Field field)
      throws IllegalAccessException {
    boolean accept = false;
    // accept private modifiers
    field.setAccessible(true);
    for (final Map.Entry<Class<?>, Object> entry : map.entrySet()) {
      if (entry.getKey().isAssignableFrom(field.getType())) {
        Object value = entry.getValue();
        // treat factories as a special case for lazy load of the objects.
        if (value instanceof InjectorObjectFactory) {
          value = ((InjectorObjectFactory<?>) value).create();
          inject(value);
        }
        field.set(object, value);
        accept = true;
        break;
      }
    }
    return accept;
  }
}


package ro.isdc.wro.model.resource.processor.support;

import static org.apache.commons.lang3.Validate.notNull;
import static ro.isdc.wro.util.StringUtils.cleanPath;
import static ro.isdc.wro.util.WroUtil.cleanImageUrl;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.WroRuntimeException;
import ro.isdc.wro.model.resource.locator.ClasspathUriLocator;
import ro.isdc.wro.model.resource.locator.ServletContextUriLocator;
import ro.isdc.wro.model.resource.locator.UrlUriLocator;
import ro.isdc.wro.util.WroUtil;

/**
 * Responsible for computing the url of the images from css based on the location of the css where they are located.
 *
 * @author Alex Objelean
 * @since 1.7.0
 */
public class ImageUrlRewriter {
  private static final Logger LOG = LoggerFactory.getLogger(ImageUrlRewriter.class);
  private static final String ROOT_CONTEXT_PATH = ServletContextUriLocator.PREFIX;
  private static final String FOLDER_PREFIX = "/..";
  private final RewriterContext context;

  /**
   * Holds the properties required by this class to perform rewrite operation.
   */
  public static final class RewriterContext {
    private String proxyPrefix;
    private String aggregatedFolderPath;
    private String contextPath;

    public RewriterContext setProxyPrefix(final String proxyPrefix) {
      this.proxyPrefix = proxyPrefix;
      return this;
    }

    public RewriterContext setAggregatedFolderPath(final String aggregatedFolderPath) {
      this.aggregatedFolderPath = aggregatedFolderPath;
      return this;
    }

    public RewriterContext setContextPath(final String contextPath) {
      this.contextPath = contextPath;
      return this;
    }
  }

  public ImageUrlRewriter(final RewriterContext context) {
    notNull(context);
    notNull(context.proxyPrefix);
    if (context.contextPath == null) {
      context.setContextPath(ROOT_CONTEXT_PATH);
    }
    this.context = context;
  }

  /**
   * Computes the url of the image to be replaced in a css resource.
   *
   * @param cssUri
   *          the uri of the css where the image is located.
   * @param imageUrl
   *          the url of the image (relative or absolute).
   * @return replaced url of the image.
   */
  public String rewrite(final String cssUri, final String imageUrl) {
    notNull(cssUri);
    notNull(imageUrl);
    if (StringUtils.isEmpty(imageUrl)) {
       return imageUrl;
    }
    if (ServletContextUriLocator.isValid(cssUri)) {
      if (ServletContextUriLocator.isValid(imageUrl)) {
        return prependContextPath(imageUrl);
      }
      // Treat WEB-INF special case
      if (ServletContextUriLocator.isProtectedResource(cssUri)) {
        return context.proxyPrefix + computeNewImageLocation(cssUri, imageUrl);
      }
      // Compute the folder where the final css is located. This is important for computing image location after url
      // rewriting.
      // Prefix of the path to the overwritten image url. This will be of the following type: "../" or "../.." depending
      // on the depth of the aggregatedFolderPath.
      final String aggregatedPathPrefix = computeAggregationPathPrefix(context.aggregatedFolderPath);
      LOG.debug("computed aggregatedPathPrefix {}", aggregatedPathPrefix);
      String newImageLocation = computeNewImageLocation(aggregatedPathPrefix + cssUri, imageUrl);
      if (newImageLocation.startsWith(ServletContextUriLocator.PREFIX)) {
        newImageLocation = prependContextPath(newImageLocation);
      }
      LOG.debug("newImageLocation: {}", newImageLocation);
      return newImageLocation;
    }
    if (ClasspathUriLocator.isValid(cssUri)) {
      final String proxyUrl = context.proxyPrefix + computeNewImageLocation(cssUri, imageUrl);
      final String contextRelativeUrl = prependContextPath(imageUrl);
      //final String contextRelativeUrl = context.contextPath + imageUrl;
      // leave imageUrl unchanged if it is a servlet context relative resource
      return (ServletContextUriLocator.isValid(imageUrl) ? contextRelativeUrl : proxyUrl);
    }
    if (UrlUriLocator.isValid(cssUri)) {
      final String computedCssUri = ServletContextUriLocator.isValid(imageUrl) ? computeCssUriForExternalServer(cssUri)
          : cssUri;
      return computeNewImageLocation(computedCssUri, imageUrl);
    }
    throw new WroRuntimeException("Could not replace imageUrl: " + imageUrl + ", contained at location: " + cssUri);
  }

  private String prependContextPath(final String imageUrl) {
    //avoid double slash
    return context.contextPath.endsWith(ROOT_CONTEXT_PATH) ? imageUrl
        : context.contextPath + imageUrl;
  }

  /**
   * @return the path to be prefixed after css aggregation. This depends on the aggregated css destination folder. This
   *         is a fix for the following issue: {@link http://code.google.com/p/wro4j/issues/detail?id=259}
   */
  private String computeAggregationPathPrefix(final String aggregatedFolderPath) {
    LOG.debug("aggregatedFolderPath: {}", aggregatedFolderPath);
    String computedPrefix = StringUtils.EMPTY;
    if (aggregatedFolderPath != null) {
      final StringBuffer result = new StringBuffer(StringUtils.EMPTY);
      final String[] depthFolders = WroUtil.normalize(aggregatedFolderPath).split(ROOT_CONTEXT_PATH);
      LOG.debug("subfolders {}", Arrays.toString(depthFolders));

      for (final String folder : depthFolders) {
        if (!StringUtils.isEmpty(folder)) {
          result.append(FOLDER_PREFIX);
        }
      }
      computedPrefix = result.toString().replaceFirst(ROOT_CONTEXT_PATH, StringUtils.EMPTY);
    }
    LOG.debug("computedPrefix: {}", computedPrefix);
    return computedPrefix;
  }

  /**
   * Css files hosted on external server, should use its host as the root context when rewriting image url's starting
   * with '/' character.
   */
  private String computeCssUriForExternalServer(final String cssUri) {
    String exernalServerCssUri = cssUri;
    try {
      // compute the host of the external server (with protocol & port).
      final String serverHost = cssUri.replace(new URL(cssUri).getPath(), StringUtils.EMPTY);
      // the uri should end mandatory with /
      exernalServerCssUri = serverHost + ServletContextUriLocator.PREFIX;
      LOG.debug("using {} host as cssUri", exernalServerCssUri);
    } catch (final MalformedURLException e) {
      // should never happen
    }
    return exernalServerCssUri;
  }

  /**
   * Concatenates cssUri and imageUrl after few changes are applied to both input parameters.
   *
   * @param cssUri
   *          the URI of css resource.
   * @param imageUrl
   *          the URL of image referred in css.
   * @return processed new location of image url.
   */
  private String computeNewImageLocation(final String cssUri, final String imageUrl) {
    LOG.debug("cssUri: {}, imageUrl {}", cssUri, imageUrl);
    final String cleanImageUrl = cleanImageUrl(imageUrl);
    // TODO move to ServletContextUriLocator as a helper method?
    // for the following input: /a/b/c/1.css => /a/b/c/
    int idxLastSeparator = cssUri.lastIndexOf(ServletContextUriLocator.PREFIX);
    if (idxLastSeparator == -1) {
      if (ClasspathUriLocator.isValid(cssUri)) {
        idxLastSeparator = cssUri.lastIndexOf(ClasspathUriLocator.PREFIX);
        // find the index of ':' character used by classpath prefix
        if (idxLastSeparator >= 0) {
          idxLastSeparator += ClasspathUriLocator.PREFIX.length() - 1;
        }
      }
      if (idxLastSeparator < 0) {
        throw new IllegalStateException("Invalid cssUri: " + cssUri + ". Should contain at least one '/' character!");
      }
    }
    final String cssUriFolder = cssUri.substring(0, idxLastSeparator + 1);
    // remove '/' from imageUrl if it starts with one.
    final String processedImageUrl = cleanImageUrl.startsWith(ServletContextUriLocator.PREFIX) ? cleanImageUrl.substring(1)
        : cleanImageUrl;
    final String computedImageLocation = cleanPath(cssUriFolder + processedImageUrl);
    LOG.debug("computedImageLocation: {}", computedImageLocation);
    return computedImageLocation;
  }
}


package ro.isdc.wro.cache;

import static org.apache.commons.lang3.Validate.notNull;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;

import ro.isdc.wro.model.resource.ResourceType;

/**
 * Used as an entry for the cache.
 */
@SuppressWarnings("serial")
public final class CacheKey implements Serializable {
	private final ResourceType type;
	private final String groupName;
	/**
	 * Produce minimized version.
	 */
	private final boolean minimize;
	/**
	 * A map of custom attributes.
	 */
	private final Map<String, String> map = new HashMap<String, String>();

  /**
   * Uses default minimize flag to build {@link CacheKey} (minimize = true). Useful when this flag is not important
   * (example resource watcher).
   *
   * @see #CacheKey(String, ResourceType, boolean)
   */
  public CacheKey(final String groupName, final ResourceType type) {
	  this(groupName, type, true);
	}

	/**
	 * @param groupName name of the group.
	 * @param type resource type (js or css)
	 * @param minimize true if the result should produce minimized version.
	 */
	public CacheKey(final String groupName, final ResourceType type, final boolean minimize) {
	  notNull(groupName);
	  notNull(type);
		this.groupName = groupName;
		this.type = type;
		this.minimize = minimize;
	}

	/**
	 * @return the type
	 */
	public ResourceType getType() {
		return this.type;
	}

	/**
	 * @return the groupName
	 */
	public String getGroupName() {
		return this.groupName;
	}

	/**
   * @return the minimize
   */
  public boolean isMinimize() {
    return this.minimize;
  }

  /**
   * Add a custom key-value pair attribute. Each pair is added to an internal map. The custom attributes can be used to
   * make the key more fine grained (Ex: based on browser version or a request parameter). Both elements of the
   * attribute (key & value) should be not null. If any of these are null, the attribute won't be added.
   *
   * @param key
   *          string representing the key of the attribute.
   * @param value
   *          string representing the value of the attribute.
   * @return reference to current {@link CacheKey} used for fluent interface.
   */
  public CacheKey addAttribute(final String key, final String value) {
    if (key != null && value != null) {
      map.put(key, value);
    }
    return this;
  }

  @Override
	public boolean equals(final Object obj) {
    return EqualsBuilder.reflectionEquals(this, obj);
	}

	@Override
	public int hashCode() {
	  return HashCodeBuilder.reflectionHashCode(this, false);
  }

	@Override
  public String toString() {
    return ToStringBuilder.reflectionToString(this);
  }
}


/*
 * Copyright (c) 2008. All rights reserved.
 */
package ro.isdc.wro.extensions.processor.css;

import java.io.IOException;
import java.io.Reader;
import java.io.Writer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.WroRuntimeException;
import ro.isdc.wro.extensions.processor.support.yui.YuiCssCompressor;
import ro.isdc.wro.model.group.processor.Minimize;
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.processor.ResourcePostProcessor;
import ro.isdc.wro.model.resource.processor.ResourcePreProcessor;

/**
 * YUICssCompressorProcessor. Use YUI css compression utility for processing a css resource.
 *
 * @author Alex Objelean
 * @created Created on Dec 4, 2008
 */
@Minimize
@SupportedResourceType(ResourceType.CSS)
public class YUICssCompressorProcessor
  implements ResourcePostProcessor, ResourcePreProcessor {
  private static final Logger LOG = LoggerFactory.getLogger(YUICssCompressorProcessor.class);
  public static final String ALIAS = "yuiCssMin";
  /**
   * An option of CssCompressor.
   */
  private static final int linebreakpos = -1;

  /**
   * {@inheritDoc}
   */
  @Override
  public void process(final Reader reader, final Writer writer)
    throws IOException {
    process(null, reader, writer);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void process(final Resource resource, final Reader reader, final Writer writer)
    throws IOException {
    try {
      final YuiCssCompressor compressor = new YuiCssCompressor(reader);
      compressor.compress(writer, linebreakpos);
    } catch(final Exception e) {
      LOG.error("Exception occured while processing resource: " + resource + " using processor: " + ALIAS);
      onException(e);
    } finally {
      reader.close();
      writer.close();
    }
  }

  /**
   * Invoked when a processing exception occurs.
   */
  protected void onException(final Exception e) {
    throw WroRuntimeException.wrap(e);
  }
}