package ro.isdc.wro.model.resource.locator.wildcard;

import static java.util.Arrays.asList;
import static org.apache.commons.io.FilenameUtils.wildcardMatch;
import static org.apache.commons.lang3.StringUtils.substringAfter;
import static org.apache.commons.lang3.StringUtils.substringBeforeLast;
import static org.apache.commons.lang3.Validate.isTrue;
import static org.apache.commons.lang3.Validate.notNull;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

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

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

/**
 * Resolves classpath resources looking for wildcard patterns in both file system and in JAR files.
 *

 * The {@link #locateStream(String, File)} overrides the default strategy defined in
 * {@link DefaultWildcardStreamLocator} and it tries to open the provided file as a JAR. If that's successfully opened
 * all entries inside this container will be verified against the wildcard pattern. If the JAR-lookup strategy fails,
 * default strategy is invoked.
 *

 *

 * For the moment this {@link WildcardStreamLocator} only supports a single wildcard.
 *

 *
 * @author Matias Mirabelli <matias.mirabelli@globant.com>
 * @since 1.3.6
 */
public class JarWildcardStreamLocator
    extends DefaultWildcardStreamLocator {
  private static final Logger LOG = LoggerFactory.getLogger(JarWildcardStreamLocator.class);
  /**
   * A {@link List} of file extensions including the final dot. Valid examples are: .jar, .war. By default it only
   * supports .jar extension.
   */
  private static final List<String> SUPPORTED_EXTENSIONS = asList(".jar");
  private static final String EMBEDDED_JAR_PREFIX = "!" + File.separatorChar;
  private static final String PREFIX_FILE = "file:";

  /**
   * Finds the specified URI pattern inside a JAR file. If the specified file isn't a valid JAR default strategy will be
   * used instead.
   */
  @Override
  public InputStream locateStream(final String uri, final File folder)
      throws IOException {
    notNull(folder);
    final File jarPath = getJarFile(folder);
    if (isSupported(jarPath)) {
      return locateStreamFromJar(uri, jarPath);
    }
    return super.locateStream(uri, folder);
  }

  /**
   * @return true if the file is of a certain supported type.
   */
  private boolean isSupported(final File jarPath) {
    LOG.debug("jarPath: {}", jarPath);
    for (final String supportedExtension : SUPPORTED_EXTENSIONS) {
      if (jarPath.getPath().endsWith(supportedExtension) && !jarPath.getPath().contains(EMBEDDED_JAR_PREFIX)) {
        return true;
      }
    }
    return false;
  }

  /**
   * @VisibleForTestOnly
   * @return the File corresponding to the folder from inside the jar.
   */
  File getJarFile(final File folder) {
    return new File(substringAfter(substringBeforeLast(folder.getPath(), "!"), PREFIX_FILE));
  }

  /**
   * Validates an entry against a wildcard and determines whether the pattern matches or not. If the entry is accepted
   * this will be included in the result {@link InputStream}.
   *
   * @param entry
   *          Entry to evaluate. It cannot be null.
   * @param wildcard
   *          Wildcard to match. It cannot be null or empty.
   * @return true if the expression matches, false otherwise.
   */
  private boolean accept(final String entryName, final String wildcard) {
    return wildcardMatch(entryName, wildcard);
  }

  /**
   * Opens the specified JAR file and returns a valid handle.
   *
   * @param jarFile
   *          Location of the valid JAR file to read. It cannot be null.
   * @return A valid {@link JarFile} to read resources.
   * @throws IllegalArgumentException
   *           If the file cannot be opened because an {@link IOException}.
   *  @VisibleForTestOnly
   */
  JarFile open(final File jarFile) throws IOException {
    isTrue(jarFile.exists(), "The JAR file must exists.");
    return new JarFile(jarFile);
  }

  /**
   * Finds the specified wildcard-URI resource(s) inside a JAR file and returns an {@link InputStream} to read a bundle
   * of matching resources.
   *
   * @param uri
   *          Resource(s) URI to match. It cannot be null or empty.
   * @param jarPath
   *          A valid JAR file. It cannot be null.
   * @return A valid {@link InputStream} to read the bundle. Clients are responsible of closing this {@link InputStream}
   * @throws IOException
   *           If there's any error reading the JAR file.
   */
  private InputStream locateStreamFromJar(final String uri, final File jarPath)
      throws IOException {
    JarFile jarFile = null;
    try {
      LOG.debug("Locating stream from jar: {}", jarPath);
      final WildcardContext wildcardContext = new WildcardContext(uri, jarPath);
      String classPath = FilenameUtils.getPath(uri);

      if (classPath.startsWith(ClasspathUriLocator.PREFIX)) {
        classPath = substringAfter(classPath, ClasspathUriLocator.PREFIX);
      }

      jarFile = open(jarPath);
      final List<JarEntry> jarEntryList = Collections.list(jarFile.entries());
      final List<JarEntry> filteredJarEntryList = new ArrayList<JarEntry>();
      final List<File> allFiles = new ArrayList<File>();
      for (final JarEntry entry : jarEntryList) {
        final String entryName = entry.getName();
        // ignore the parent folder itself and accept only child resources
        final boolean isSupportedEntry = entryName.startsWith(classPath) && !entryName.equals(classPath)
            && accept(entryName, wildcardContext.getWildcard());
        if (isSupportedEntry) {
          allFiles.add(new File(entryName));
          LOG.debug("\tfound jar entry: {}", entryName);
          filteredJarEntryList.add(entry);
        }
      }
      final ByteArrayOutputStream out = new ByteArrayOutputStream();

      triggerWildcardExpander(allFiles, wildcardContext);
      for (final JarEntry entry : filteredJarEntryList) {
        final InputStream is = jarFile.getInputStream(entry);
        IOUtils.copy(is, out);
        is.close();
      }
      return new BufferedInputStream(new ByteArrayInputStream(out.toByteArray()));
    } finally {
      IOUtils.closeQuietly(jarFile);
    }
  }
}


package ro.isdc.wro.extensions.locator;

import static java.lang.String.format;
import static org.apache.commons.lang3.Validate.notNull;

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

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

import ro.isdc.wro.model.resource.locator.ClasspathUriLocator;
import ro.isdc.wro.model.resource.locator.UriLocator;
import ro.isdc.wro.model.resource.locator.support.LocatorProvider;
import ro.isdc.wro.model.resource.locator.wildcard.DefaultWildcardStreamLocator;
import ro.isdc.wro.util.WroUtil;

/**
 * Similar to {@link WebjarUriLocator}, but uses "/webjars/" prefix to identify a webjar resource. Useful when resources
 * are located using build-time solution (maven plugin). The only disadvantage of this locator, is that it will not make
 * it possible to use resources located inside a folder named "webjars" (inside the application context).
 *
 * @author Alex Objelean
 * @created 5 Oct 2015
 * @since 1.8.0
 */
public class WebjarsUriLocator
    implements UriLocator {
  private static final Logger LOG = LoggerFactory.getLogger(WebjarsUriLocator.class);
  private static final String PATTERN = ".*";
  /**
   * Alias used to register this locator with {@link LocatorProvider}.
   */
  public static final String ALIAS = "webjars";
  /**
   * Prefix of the resource uri used to check if the resource can be read by this {@link UriLocator} implementation.
   */
  private static final String PREFIX = format("/%s/", ALIAS);
  private final UriLocator classpathLocator = new ClasspathUriLocator();
  private final WebJarAssetLocator webjarAssetLocator = newWebJarAssetLocator();

  /**
   * @return an instance of {@link WebJarAssetLocator} to be used for identifying the fully qualified name of resources
   *         based on provided partial path.
   */
  private WebJarAssetLocator newWebJarAssetLocator() {
    return new WebJarAssetLocator(WebJarAssetLocator.getFullPathIndex(
        Pattern.compile(PATTERN), Thread.currentThread().getContextClassLoader()));
  }

  /**
   * @return the uri which is acceptable by this locator.
   */
  public static String createUri(final String path) {
    notNull(path);
    return WroUtil.joinPath(PREFIX, path);
  }

  @Override
  public InputStream locate(final String uri)
      throws IOException {
    LOG.debug("locating: {}", uri);
    try {
      final String fullpath = webjarAssetLocator.getFullPath(extractPath(uri));
      return classpathLocator.locate(ClasspathUriLocator.createUri(fullpath));
    } catch (final Exception e) {
      throw new IOException("No webjar with uri: " + uri + " available.", e);
    }
  }

  /**
   * Replaces the protocol specific prefix and removes the query path if it exist, since it should not be accepted.
   */
  private String extractPath(final String uri) {
    return DefaultWildcardStreamLocator.stripQueryPath(uri.replace(PREFIX, StringUtils.EMPTY));
  }

  @Override
  public boolean accept(final String uri) {
    return uri.trim().startsWith(PREFIX);
  }
}


package ro.isdc.wro.cache.factory;

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

import javax.servlet.http.HttpServletRequest;

import ro.isdc.wro.cache.CacheKey;
import ro.isdc.wro.config.ReadOnlyContext;
import ro.isdc.wro.config.jmx.WroConfiguration;
import ro.isdc.wro.model.group.GroupExtractor;
import ro.isdc.wro.model.group.Inject;
import ro.isdc.wro.model.resource.ResourceType;

/**
 * Default implementation of {@link CacheKeyFactory} which builds the {@link CacheKey} by setting all required types
 * after inspecting the request. The {@link GroupExtractor} is used to retrieve required fields. The minimize flag will
 * be computed based on {@link WroConfiguration#isMinimizeEnabled()} configuration.
 *
 * @author Alex Objelean
 * @since 1.6.0
 * @created 19 Oct 2012
 */
public class DefaultCacheKeyFactory
    implements CacheKeyFactory {
  @Inject
  private GroupExtractor groupExtractor;
  @Inject
  private ReadOnlyContext context;

  /**
   * {@inheritDoc}
   */
  public CacheKey create(final HttpServletRequest request) {
    notNull(request);
    CacheKey key = null;
    final String groupName = groupExtractor.getGroupName(request);
    final ResourceType resourceType = groupExtractor.getResourceType(request);
    final boolean minimize = isMinimized(request);
    if (groupName != null && resourceType != null) {
      key = new CacheKey(groupName, resourceType, minimize);
    }
    return key;
  }

  /**
   * Uses isMinimizeEnabled configuration to compute minimize value.
   */
  private boolean isMinimized(final HttpServletRequest request) {
    return context.getConfig().isMinimizeEnabled() ? groupExtractor.isMinimized(request) : false;
  }
}


package ro.isdc.wro.extensions.support.lint;

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

import java.io.OutputStream;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;

import ro.isdc.wro.WroRuntimeException;
import ro.isdc.wro.extensions.processor.support.linter.LinterError;

/**
 * Builds an XML report for lint errors based on provided collection of {@link LinterError}'s.
 *
 * @author Alex Objelean
 * @since 1.5.0
 * @created 15 Sep 2012
 */
public abstract class AbstractReportXmlFormatter<T> {
  private Document doc;
  private LintReport<T> lintReport;
  /**
   * @param lintReport
   *          a not null collection of {@link LinterError} used to build an XML report from.
   */
  protected AbstractReportXmlFormatter(final LintReport<T> lintReport) {
    notNull(lintReport);
    this.lintReport = lintReport;
    initDocument();
  }

  private void initDocument() {
    try {
      final DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
      final DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
      this.doc = docBuilder.newDocument();
    } catch (ParserConfigurationException e) {
      throw new WroRuntimeException("Unexpected problem while XML Document", e);
    }
  }

  /**
   * @param outputStream
   *          the {@link OutputStream} where the xml representation of the report is written.
   */
  public final void write(final OutputStream outputStream) {
    buildDocument();
    writeReport(outputStream);
  }

  /**
   * Populates document with required DOM elements.
   */
  protected abstract void buildDocument();

  /**
   * @return {@link LintReport} used to build the xml lint report from.
   */
  protected final LintReport<T> getLintReport() {
    return this.lintReport;
  }

  /**
   * @return the {@link Document} used to build xml report.
   */
  protected final Document getDocument() {
    return doc;
  }

  /**
   * write the content to the {@link OutputStream}
   */
  private void writeReport(final OutputStream outputStream) {
    Transformer transformer;
    try {
      transformer = TransformerFactory.newInstance().newTransformer();
      transformer.transform(new DOMSource(doc), new StreamResult(outputStream));
    } catch (Exception e) {
      throw WroRuntimeException.wrap(e, "Problem during Document transformation").logError();
    }
  }
}


/*
 * Copyright 2011 wro4j
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package ro.isdc.wro.extensions.model.factory;

import groovy.lang.GroovyShell;
import groovy.lang.Script;

import java.io.IOException;
import java.io.InputStreamReader;

import org.apache.commons.io.input.AutoCloseInputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.WroRuntimeException;
import ro.isdc.wro.model.WroModel;
import ro.isdc.wro.model.factory.AbstractWroModelFactory;
import ro.isdc.wro.util.StopWatch;

/**
 * Creates {@link ro.isdc.wro.model.WroModel} from a groovy DSL.
 * <p/>
 * This class is thread-safe because it doesn't have any state.
 *
 * @author Romain Philibert
 * @created 19 Jul 2011
 * @since 1.4.0
 */
public class GroovyModelFactory
  extends AbstractWroModelFactory {
  private static final Logger LOG = LoggerFactory.getLogger(GroovyModelFactory.class);
  /**
   * Default name of the file used to retrieve the model.
   */
  private static final String DEFAULT_FILE_NAME = "wro.groovy";
  /**
   * Alias for this model factory used by provider.
   */
  public static final String ALIAS = "groovy";

  /**
   * {@inheritDoc}
   */
  @Override
  public WroModel create() {
    final StopWatch stopWatch = new StopWatch("Create Wro Model from Groovy");
    final Script script;
    try {
      stopWatch.start("parseStream");
      script = new GroovyShell().parse(new InputStreamReader(new AutoCloseInputStream(getModelResourceAsStream())));
      LOG.debug("Parsing groovy script to build the model");
      stopWatch.stop();

      stopWatch.start("parseScript");
      final WroModel model = GroovyModelParser.parse(script);
      stopWatch.stop();
      LOG.debug("groovy model: {}", model);
      if (model == null) {
        throw new WroRuntimeException("Invalid content provided, cannot build model!");
      }
      return model;
    } catch (final IOException e) {
      throw new WroRuntimeException("Invalid model found!", e);
    } finally {
      LOG.debug(stopWatch.prettyPrint());
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected String getDefaultModelFilename() {
    return DEFAULT_FILE_NAME;
  }
}


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

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import ro.isdc.wro.model.resource.locator.UriLocator;

/**
 * Holds a list of uri locators. The uriLocator will be created based on the first
 * uriLocator from the supplied list which will accept the url.
 *
 * @author Alex Objelean
 * @created 4 Nov 2008
 */
public class SimpleUriLocatorFactory extends AbstractUriLocatorFactory {
  private final List<UriLocator> uriLocators = new ArrayList<UriLocator>();

  /**
   * @param uri to handle by the locator.
   * @return an instance of {@link UriLocator} which is capable of handling provided uri. Returns null if no locator
   *         found.
   */
  public UriLocator getInstance(final String uri) {
    for (final UriLocator uriLocator : uriLocators) {
      if (uriLocator.accept(uri)) {
        return uriLocator;
      }
    }
    return null;
  }

  /**
   * Allow adding more than one uriLocators.
   *
   * @param locators list of {@link UriLocator} arguments.
   */
  public final SimpleUriLocatorFactory addLocator(final UriLocator... locators) {
    for (final UriLocator locator : locators) {
      uriLocators.add(locator);
    }
    return this;
  }

  /**
   * @param locators {@link Collection} of locators to add.
   */
  public final SimpleUriLocatorFactory addLocators(final Collection<UriLocator> locators) {
    uriLocators.addAll(locators);
    return this;
  }

  /**
   * @return the list of currently configured locators.
   * @VisibleForTesting
   */
  public List<UriLocator> getUriLocators() {
    return Collections.unmodifiableList(this.uriLocators);
  }
}


package ro.isdc.wro.model.factory;

import java.util.List;

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

import ro.isdc.wro.config.ReadOnlyContext;
import ro.isdc.wro.manager.callback.LifecycleCallbackRegistry;
import ro.isdc.wro.model.WroModel;
import ro.isdc.wro.model.WroModelInspector;
import ro.isdc.wro.model.group.Inject;
import ro.isdc.wro.model.group.processor.Injector;
import ro.isdc.wro.model.resource.Resource;
import ro.isdc.wro.model.resource.support.MutableResourceAuthorizationManager;
import ro.isdc.wro.model.resource.support.ResourceAuthorizationManager;
import ro.isdc.wro.util.AbstractDecorator;
import ro.isdc.wro.util.DestroyableLazyInitializer;
import ro.isdc.wro.util.StopWatch;
import ro.isdc.wro.util.Transformer;

/**
 * Decorates the model factory with callback registry calls & other useful factories. Another responsibility of this
 * decorator is make model creation thread safe.
 * <p/>
 * This class doesn't extend {@link AbstractDecorator} because we have to enhance the decorated object with new
 * decorators.
 *
 * @author Alex Objelean
 * @created 13 Mar 2011
 * @since 1.4.6
 */
public final class DefaultWroModelFactoryDecorator
    extends AbstractDecorator<WroModelFactory>
    implements WroModelFactory {
  private static final Logger LOG = LoggerFactory.getLogger(DefaultWroModelFactoryDecorator.class);
  @Inject
  private LifecycleCallbackRegistry callbackRegistry;
  @Inject
  private ResourceAuthorizationManager authorizationManager;
  @Inject
  private ReadOnlyContext context;
  @Inject
  private Injector injector;
  /**
   * Responsible for model caching
   */
  private final DestroyableLazyInitializer<WroModel> modelInitializer = new DestroyableLazyInitializer<WroModel>() {
    @Override
    protected WroModel initialize() {
      callbackRegistry.onBeforeModelCreated();
      final StopWatch watch = new StopWatch("Create Model");
      watch.start("createModel");
      WroModel model = null;
      try {
        final WroModelFactory modelFactory = decorate(getDecoratedObject());
        injector.inject(modelFactory);
        model = modelFactory.create();
        return model;
      } finally {
        authorizeModelResources(model);
        callbackRegistry.onAfterModelCreated();
        watch.stop();
        LOG.debug(watch.prettyPrint());
      }
    }

    /**
     * Decorate with several useful aspects, like: fallback, caching & model transformer ability.
     */
    private WroModelFactory decorate(final WroModelFactory decorated) {
      return new ModelTransformerFactory(new FallbackAwareWroModelFactory(decorated)).setTransformers(modelTransformers);
    }

    /**
     * Authorizes all resources of the model to be accessed as proxy resources (only in dev mode).
     *
     * @param model
     *          {@link WroModel} created by decorated factory.
     */
    private void authorizeModelResources(final WroModel model) {
      if (model != null && context.getConfig().isDebug()) {
        if (authorizationManager instanceof MutableResourceAuthorizationManager) {
          for (final Resource resource : new WroModelInspector(model).getAllUniqueResources()) {
            ((MutableResourceAuthorizationManager) authorizationManager).add(resource.getUri());
          }
        }
      }
    }
  };

  private final List<Transformer<WroModel>> modelTransformers;

  /**
   * Factory method which takes care of redundant decoration.
   */
  public static WroModelFactory decorate(final WroModelFactory decorated,
      final List<Transformer<WroModel>> modelTransformers) {
    return decorated instanceof DefaultWroModelFactoryDecorator ? decorated : new DefaultWroModelFactoryDecorator(
        decorated, modelTransformers);
  }

  private DefaultWroModelFactoryDecorator(final WroModelFactory decorated,
      final List<Transformer<WroModel>> modelTransformers) {
    super(decorated);
    Validate.notNull(modelTransformers);

    this.modelTransformers = modelTransformers;
  }

  public WroModel create() {
    return modelInitializer.get();
  }

  public void destroy() {
    LOG.debug("Destroy model");
    modelInitializer.destroy();
    getDecoratedObject().destroy();
    if (authorizationManager instanceof MutableResourceAuthorizationManager) {
      ((MutableResourceAuthorizationManager) authorizationManager).clear();
    }
  }
}


/**
 * Copyright Alex Objelean
 */
package ro.isdc.wro.model.resource.processor.impl.css;

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

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.ResourcePostProcessor;
import ro.isdc.wro.model.resource.processor.ResourcePreProcessor;
import ro.isdc.wro.model.resource.processor.support.Lessify;

/**
 * @author Alex Objelean
 */
@SupportedResourceType(ResourceType.CSS)
public class VariablizeColorsCssProcessor
  implements ResourcePreProcessor, ResourcePostProcessor {
  public static final String ALIAS = "variablizeColors";

  /**
   * {@inheritDoc}
   */
  public void process(final Resource resource, final Reader reader, final Writer writer)
    throws IOException {
    try {
      final String result = new Lessify().variablizeColors(IOUtils.toString(reader));
      writer.write(result);
    } finally {
      reader.close();
      writer.close();
    }
  }

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

}


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

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.LinkedHashSet;
import java.util.Set;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

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

import ro.isdc.wro.WroRuntimeException;
import ro.isdc.wro.util.LazyInitializer;

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

/**
 * A Sass processor using ruby gems.
 *
 * @author Dmitry Erman
 * @created 12 Feb 2012
 * @since 1.4.4
 */
public class RubySassEngine {
  private static final Logger LOG = LoggerFactory.getLogger(RubySassEngine.class);
  private static final Object GLOBAL_LOCK = new Object();
  private static final String RUBY_GEM_REQUIRE = "rubygems";
  private static final String SASS_PLUGIN_REQUIRE = "sass/plugin";
  private static final String SASS_ENGINE_REQUIRE = "sass/engine";
  private final Set<String> requires;
  private final LazyInitializer<ScriptEngine> engineInitializer = new LazyInitializer<ScriptEngine>() {
    @Override
    protected ScriptEngine initialize() {
      try {
        //use global initializer to avoid initialization failure in multi-threaded environment.
        synchronized (GLOBAL_LOCK) {
          final ScriptEngine rubyEngine = new ScriptEngineManager().getEngineByName("jruby");
          rubyEngine.eval("0").toString();
          return rubyEngine;
        }
      } catch (final ScriptException e) {
        throw new WroRuntimeException(e.getMessage(), e);
      }
    }
  };

  public RubySassEngine() {
    System.setProperty("org.jruby.embed.compat.version", "JRuby1.9");
    // Below properties are just for performance improvement. Document is here:
    // https://github.com/jruby/jruby/wiki/RedBridge#CompileMode
    // https://github.com/jruby/jruby/wiki/RedBridge#Disabling_Sharing_Variables
    System.setProperty("org.jruby.embed.compilemode", "jit");
    System.setProperty("org.jruby.embed.sharing.variables", "false");
    requires = new LinkedHashSet<String>();
    requires.add(RUBY_GEM_REQUIRE);
    requires.add(SASS_PLUGIN_REQUIRE);
    requires.add(SASS_ENGINE_REQUIRE);
  }

  /**
   * Adds a ruby require to the ruby script to be run by this RubySassEngine. It's safe to add the same require twice.
   *
   * @param require
   *          The name of the require, e.g. bourbon
   */
  public void addRequire(final String require) {
    if (require != null && require.trim().length() > 0) {
      requires.add(require.trim());
    }
  }

  /**
   * Transforms a sass content into css using Sass ruby engine. This method is synchronized because the engine itself is
   * not thread-safe.
   *
   * @param content
   *          the Sass content to process.
   */
  public String process(final String content) {
    if (isEmpty(content)) {
      return StringUtils.EMPTY;
    }
    try {
      synchronized(this) {
        return engineInitializer.get().eval(buildUpdateScript(content)).toString();
      }
    } catch (final ScriptException e) {
      throw new WroRuntimeException(e.getMessage(), e);
    }
  }

  private String buildUpdateScript(final String content) {
    notNull(content);
    final StringWriter raw = new StringWriter();
    final PrintWriter script = new PrintWriter(raw);
    final StringBuilder sb = new StringBuilder();
    final StringBuilder cb = new StringBuilder();
    sb.append(":syntax => :scss");

    for (final String require : requires) {
      script.println("  require '" + require + "'                                   ");
    }
    final int BACKSLASH = 0x5c;
    for (int i = 0; i < content.length(); i++) {
      final int code = content.codePointAt(i);
      if (code < 0x80) {
        // We leave only ASCII unchanged.
        if (code == BACKSLASH) {
          // escape backslash
          cb.append("\\");
        }
        cb.append(content.charAt(i));
      } else {
        // Non-ASCII String may cause invalid multibyte char (US-ASCII) error with Ruby 1.9
        // because Ruby 1.9 expects you to use ASCII characters in your source code.
        // Instead we use Unicode code point representation which is usable with
        // Ruby 1.9 and later. Inspired from
        // http://www.stefanwille.com/2010/08/ruby-on-rails-fix-for-invalid-multibyte-char-us-ascii/
        cb.append(String.format("\\u%04x", code));
      }
    }
    final String scriptAsString = String.format("result = Sass::Engine.new(\"%s\", {%s}).render",
        cb.toString().replace("\"", "\\\"").replace("#", "\\#"), // escape ", #
        sb.toString());
    LOG.debug("scriptAsString: {}", scriptAsString);
    script.println(scriptAsString);
    script.flush();
    return raw.toString();
  }
}


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

import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.util.zip.CRC32;
import java.util.zip.Checksum;

import org.apache.commons.io.IOUtils;

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

/**
 * Uses CRC32 algorithm for creating fingerprint.
 *
 * @author Alex Objelean
 * @since 1.4.7
 * @created 17 Jun 2012
 */
public class CRC32HashStrategy
    implements HashStrategy {
  private static final Logger LOG = LoggerFactory.getLogger(CRC32HashStrategy.class);
  /**
   * A short name of this hashBuilder.
   */
  public static final String ALIAS = "CRC32";
  /**
   * {@inheritDoc}
   */
  public String getHash(final InputStream input)
    throws IOException {
    if (input == null) {
      throw new IllegalArgumentException("Content cannot be null!");
    }
    try{
      LOG.debug("creating hash using CRC32 algorithm");
      final Checksum checksum = new CRC32();
      final byte[] bytes = new byte[1024];
      int len = 0;
      while ((len = input.read(bytes)) >= 0) {
        checksum.update(bytes, 0, len);
      }

      final String hash = new BigInteger(Long.toString(checksum.getValue())).toString(16);
      LOG.debug("CRC32 hash: {}", hash);
      return hash;
    }finally{
      IOUtils.closeQuietly(input);
    }
  }
}


package ro.isdc.wro.maven.plugin.support;

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

import org.apache.maven.plugin.logging.Log;

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

/**
 * Responsible for logging progress related details. Useful to find the best balance between "to much" vs "none"
 * details. This implementation will indicate the state of the progress after a given period of time (ex: each 5
 * seconds).
 *
 * @author Alex Objelean
 * @since 1.7.0
 * @created 27 May 2013
 */
public class ProgressIndicator {
  private static final int DEFAULT_TIMEOUT_DELAY = 5000;
  /**
   * Counts total number of processed resources.
   */
  private int totalResources = 0;
  /**
   * Counts total number of resources with errors.
   */
  private int totalResourcesWithErrors = 0;
  /**
   * Counts total number of jshint errors.
   */
  private int totalFoundErrors = 0;
  /**
   * Number of milliseconds indicating how often an info log will be invoked.
   */
  private long timeoutDelay = DEFAULT_TIMEOUT_DELAY;
  private long lastInvocationTimestamp;
  private final Log log;

  public ProgressIndicator(final Log log) {
    this.log = log;
    updateLastInvocation();
  }

  private void updateLastInvocation() {
    lastInvocationTimestamp = System.currentTimeMillis();
  }

  /**
   * resets all counters to zero.
   */
  public void reset() {
    totalFoundErrors = 0;
    totalResources = 0;
    totalResourcesWithErrors = 0;
  }

  /**
   * Logs the summary as it was collected at this point.
   */
  public void logSummary() {
    final String message = totalFoundErrors == 0 ? "No lint errors found." : String.format(
        "Found %s errors in %s files.", totalFoundErrors, totalResourcesWithErrors);
    log.info("----------------------------------------");
    log.info(String.format("Total resources: %s", totalResources));
    log.info(message);
    log.info("----------------------------------------\n");
  }

  /**
   * A method which should be invoked on each new resource processing, having as a side effect an increment of the
   * counter holding the number of total processed resources.
   */
  public synchronized void onProcessingResource(final Resource resource) {
    totalResources++;
    log.debug("processing resource: " + resource.getUri());
    if (isLogRequired()) {
      log.info("Processed until now: " + getTotalResources() + ". Last processed: " + resource.getUri());
      updateLastInvocation();
    }
  }

  private boolean isLogRequired() {
    return System.currentTimeMillis() - lastInvocationTimestamp > timeoutDelay;
  }

  /**
   * This method has a side effect of incrementing the number of resources containing errors.
   *
   * @param errorsToAdd
   *          number of errors found during processing. This number will be added to the counter holding total number of
   *          found errors.
   */
  public void addFoundErrors(final int errorsToAdd) {
    isTrue(errorsToAdd > 0, "Cannot add negative number of errors");
    totalResourcesWithErrors++;
    totalFoundErrors += errorsToAdd;
  }

  /**
   * @return a total number of found errors.
   */
  public final int getTotalFoundErrors() {
    return totalFoundErrors;
  }

  /**
   * @VisibleForTesting
   */
  final int getTotalResources() {
    return totalResources;
  }

  /**
   * @VisibleForTesting
   */
  final int getTotalResourcesWithErrors() {
    return totalResourcesWithErrors;
  }

  /**
   * @param timeoutDelay
   *          Number of milliseconds to not log info to avoid verbose logging. Useful when running the plugin in large
   *          projects.
   */
  final void setTimeoutDelay(final long timeoutDelay) {
    this.timeoutDelay = timeoutDelay;
  }
}


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

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

import ro.isdc.wro.WroRuntimeException;
import ro.isdc.wro.model.resource.Resource;
import ro.isdc.wro.model.resource.processor.ResourcePreProcessor;
import ro.isdc.wro.model.resource.processor.SupportAware;

/**
 * Check if the decorated processor implements {@link SupportAware} interface and throws the {@link WroRuntimeException}
 * with specific message when processor is not supported.
 *
 * @author Alex Objelean
 * @created 7 Oct 2012
 * @since 1.6.0
 */
public class SupportAwareProcessorDecorator
    extends ProcessorDecorator {
  public SupportAwareProcessorDecorator(final Object processor) {
    super(processor);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void process(final Resource resource, final Reader reader, final Writer writer)
      throws IOException {
    final ResourcePreProcessor decoratedProcessor = getDecoratedObject();
    if (decoratedProcessor instanceof SupportAware) {
      if (!((SupportAware) decoratedProcessor).isSupported()) {
        throw new WroRuntimeException(toString() + " processor is not supported on this environment");
      }
    }
    super.process(resource, reader, writer);
  }
}


package ro.isdc.wro.cache.support;

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

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

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

import ro.isdc.wro.cache.CacheStrategy;

/**
 * Ensure that the {@link AbstractSynchronizedCacheStrategyDecorator#loadValue(Object)} will be called only once for the same
 * key. This behavior is important for avoiding redundant execution of expensive computation in concurrent environment
 * which cause high memory and CPU consumption.
 *
 * @author Alex Objelean
 * @created 30 Apr 2012
 * @since 1.4.6
 */
public abstract class AbstractSynchronizedCacheStrategyDecorator<K, V>
    extends CacheStrategyDecorator<K, V> {
  private static final Logger LOG = LoggerFactory.getLogger(AbstractSynchronizedCacheStrategyDecorator.class);
  private final ConcurrentMap<K, ReadWriteLock> locks = new ConcurrentHashMap<K, ReadWriteLock>();

  public AbstractSynchronizedCacheStrategyDecorator(final CacheStrategy<K, V> decorated) {
    super(decorated);
  }

  @Override
  public final V get(final K key) {
    notNull(key);
    LOG.debug("Searching cache key: {}", key);
    V value = null;
    //invoke this callback method before the lock is acquired to avoid dead-lock
    onBeforeGet(key);
    final ReadWriteLock lock = getLockForKey(key);
    lock.readLock().lock();
    try {
      value = getDecoratedObject().get(key);
    } finally {
      lock.readLock().unlock();
    }
    if (value == null) {
      lock.writeLock().lock();
      try {
        // this is necessary to ensure that the load wasn't invoked
        value = getDecoratedObject().get(key);
        if (value == null) {
          LOG.debug("Cache is empty. Loading new value...");
          value = loadValue(key);
          put(key, value);
        }
      } finally {
        lock.writeLock().unlock();
      }
    }
    return value;
  }

  /**
   * Invoked just before the get method is invoked. Can be useful for checking if resources are stale and invalidating
   * the cache.
   */
  protected void onBeforeGet(final K key) {
  }

  @Override
  public final void put(final K key, final V value) {
    final ReadWriteLock lock = getLockForKey(key);
    lock.writeLock().lock();
    try {
      getDecoratedObject().put(key, value);
    } finally {
      lock.writeLock().unlock();
    }
  }

  /**
   * Ensure that the returned lock will never be null.
   */
  private ReadWriteLock getLockForKey(final K key) {
    final ReadWriteLock lock = locks.putIfAbsent(key, new ReentrantReadWriteLock());
    return lock == null ? locks.get(key) : lock;
  }

  /**
   * Loads the value associated with the key. This is a potential slow operation which will be prevented to be called
   * multiple times for the same key.
   */
  protected abstract V loadValue(final K key);
}


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

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

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

import ro.isdc.wro.WroRuntimeException;
import ro.isdc.wro.extensions.processor.support.ObjectPoolHelper;
import ro.isdc.wro.extensions.processor.support.csslint.CssLint;
import ro.isdc.wro.extensions.processor.support.csslint.CssLintError;
import ro.isdc.wro.extensions.processor.support.csslint.CssLintException;
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.Destroyable;
import ro.isdc.wro.model.resource.processor.ResourcePostProcessor;
import ro.isdc.wro.model.resource.processor.ResourcePreProcessor;
import ro.isdc.wro.util.ObjectFactory;

/**
 * Processor which analyze the css code and warns you found problems. The processing result won't change no matter if the processed script
 * contains errors or not. The underlying implementation uses CSSLint script utility {@link https ://github.com/stubbornella/csslint}.
 *
 * @author Alex Objelean
 * @since 1.3.8
 * @created 19 Jun 2011
 */
@SupportedResourceType(ResourceType.CSS)
public class CssLintProcessor implements ResourcePreProcessor, ResourcePostProcessor, Destroyable {
    private static final Logger LOG = LoggerFactory.getLogger(CssLintProcessor.class);
    public static final String ALIAS = "cssLint";
    /**
     * CSV Options.
     */
    private String options;

    private ObjectPoolHelper<CssLint> enginePool;

    public CssLintProcessor() {
        enginePool = new ObjectPoolHelper<CssLint>(new ObjectFactory<CssLint>() {
            @Override
            public CssLint create() {
                return newCssLint();
            }
        });
    }

    @Override
    public void process(final Resource resource, final Reader reader, final Writer writer) throws IOException {
        final String content = IOUtils.toString(reader);
        final CssLint cssLint = enginePool.getObject();
        try {
            cssLint.setOptions(getOptions()).validate(content);
        } catch (final CssLintException e) {
            onCssLintException(e, resource);
        } catch (final WroRuntimeException e) {
            final String resourceUri = resource == null ? StringUtils.EMPTY : "[" + resource.getUri() + "]";
            LOG.error("Exception while applying " + ALIAS + " processor on the " + resourceUri
                    + " resource, no processing applied...", e);
            onException(e);
        } finally {
            // don't change the processed content no matter what happens.
            writer.write(content);
            reader.close();
            writer.close();
            enginePool.returnObject(cssLint);
        }
    }

    /**
     * Invoked when an unexpected exception occurred during processing. By default the exception is thrown further.
     */
    protected void onException(final WroRuntimeException e) {
        throw e;
    }

    /**
     * @return {@link CssLint} instance.
     */
    protected CssLint newCssLint() {
        return new CssLint();
    }

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

    /**
     * Called when {@link CssLintException} is thrown. Allows subclasses to re-throw this exception as a {@link RuntimeException} or handle
     * it differently.
     *
     * @param e {@link CssLintException} which has occurred.
     * @param resource the processed resource which caused the exception.
     */
    protected void onCssLintException(final CssLintException e, final Resource resource) {
        final String uri = resource == null ? StringUtils.EMPTY : resource.getUri();
        LOG.error("The following resource: " + uri + " has " + e.getErrors().size() + " errors.");
        for (final CssLintError x : e.getErrors()) {
            LOG.error(uri + " line " + x.getLine() + " column " + x.getCol() + ": " + x.getType() + " "
                    + x.getMessage());
        }
    }

    /**
     * @param options a CSV representation of options.
     */
    public CssLintProcessor setOptionsAsString(final String options) {
        this.options = options;
        return this;
    }

    /**
     * @return an options as CSV.
     */
    private String getOptions() {
        if (options == null) {
            options = createDefaultOptions();
        }
        return options;
    }

    /**
     * @return default options to use for linting.
     */
    protected String createDefaultOptions() {
        return "";
    }

    @Override
    public void destroy() throws Exception {
        enginePool.destroy();
    }
}


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

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

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.Writer;
import java.text.MessageFormat;
import java.util.Arrays;

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 ro.isdc.wro.WroRuntimeException;
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;
import ro.isdc.wro.model.resource.processor.SupportAware;
import ro.isdc.wro.util.WroUtil;

/**
 * Important node: this processor is not cross platform and has some pre-requesites in order to work.
 *

 * Same as {@link RhinoLessCssProcessor} but uses lessc shell utility to process the less.
 *

 * Installation instructions: Install the libnode-less package (Unix OS)
 *
 *

 *   sudo apt-get install node-less
 *

 *
 * It is possible to test whether the lessc utility is available using {@link NodeLessCssProcessor#isSupported()}
 *

 *
 * @author Alex Objelean
 * @since 1.5.0
 * @created 10 Sep 2012
 */
@SupportedResourceType(ResourceType.CSS)
public class NodeLessCssProcessor
    implements ResourcePreProcessor, ResourcePostProcessor, SupportAware {
  private static final String OPTION_NO_COLOR = "--no-color";
  private static final String SHELL_COMMAND = "lessc";
  private static final Logger LOG = LoggerFactory.getLogger(NodeLessCssProcessor.class);
  public static final String ALIAS = "nodeLessCss";
  /**
   * Flag indicating that we are running on Windows platform. This will be initialized only once in constructor.
   */
  private final boolean isWindows;

  public NodeLessCssProcessor() {
    // initialize this field at construction.
    final String osName = System.getProperty("os.name");
    LOG.debug("OS Name: {}", osName);
    isWindows = osName != null && osName.contains("Windows");
  }

  @Override
  public void process(final Resource resource, final Reader reader, final Writer writer)
      throws IOException {
    final String content = IOUtils.toString(reader);
    final String resourceUri = resource == null ? "unknown.less" : resource.getUri();
    try {
      writer.write(process(resourceUri, content));
    } catch (final Exception e) {
      LOG.warn("Exception while applying " + getClass().getSimpleName() + " processor on the " + resourceUri
          + " resource, no processing applied...", e);
      LOG.error(e.getMessage(), e);
      onException(e, content);
    } finally {
      // return for later reuse
      reader.close();
      writer.close();
    }
  }

  private String process(final String resourceUri, final String content) {
    final InputStream shellIn = null;
    // the file holding the input file to process
    File temp = null;
    try {
      temp = WroUtil.createTempFile();
      final String encoding = "UTF-8";
      IOUtils.write(content, new FileOutputStream(temp), encoding);
      LOG.debug("absolute path: {}", temp.getAbsolutePath());

      final Process process = createProcess(temp);
      /**
       * It is important to read before waitFor is invoked because read stream is blocking stdout while Java application
       * doesn't read the whole buffer. It hangs when processing large files. The lessc isn't closing till all STDOUT
       * flushed. This blocks io and Node does not exit because of that.
       */
      final String result = IOUtils.toString(new AutoCloseInputStream(process.getInputStream()), encoding);
      final int exitStatus = process.waitFor();// this won't return till `out' stream being flushed!

      if (exitStatus != 0) {
        LOG.error("exitStatus: {}", exitStatus);
        // find a way to get rid of escape character found at the end (minor issue)
        final String errorMessage = MessageFormat.format("Error in LESS: \n{0}",
            result.replace(temp.getPath(), resourceUri));
        throw new WroRuntimeException(errorMessage);
      }
      return result;
    } catch (final Exception e) {
      throw WroRuntimeException.wrap(e);
    } finally {
      IOUtils.closeQuietly(shellIn);
      // always cleanUp
      FileUtils.deleteQuietly(temp);
    }
  }

  /**
   * Invoked when a processing exception occurs. Default implementation wraps the original exception into
   * {@link WroRuntimeException}.
   *
   * @param e
   *          the {@link Exception} thrown during processing
   * @param content
   *          the resource content being processed.
   */
  protected void onException(final Exception e, final String content) {
    throw WroRuntimeException.wrap(e);
  }

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

  /**
   * Creates process responsible for running lessc shell command by reading the file content from the sourceFilePath
   *
   * @param sourceFile
   *          the source path of the file from where the lessc will read the less file.
   * @throws IOException
   *           when the process execution fails.
   */
  private Process createProcess(final File sourceFile)
      throws IOException {
    notNull(sourceFile);
    final String[] commandLine = getCommandLine(sourceFile.getPath());
    LOG.debug("CommandLine arguments: {}", Arrays.asList(commandLine));
    return new ProcessBuilder(commandLine).redirectErrorStream(true).start();
  }

  /**
   * @return true if the processor is supported on this environment. The implementation check if the required shell
   *         utility is available.
   */
  @Override
  public boolean isSupported() {
    File temp = null;
    try {
      temp = WroUtil.createTempFile();
      final Process process = createProcess(temp);
      final int exitValue = process.waitFor();
      LOG.debug("exitValue {}. ErrorMessage: {}", exitValue, IOUtils.toString(process.getInputStream()));
      if (exitValue != 0) {
        throw new UnsupportedOperationException("Lessc is not a supported operation on this platform");
      }
      LOG.debug("The {} processor is supported.", getClass().getName());
      return true;
    } catch (final Exception e) {
      LOG.debug("The {} processor is not supported. Because: {}", getClass().getName(), e.getMessage());
      return false;
    } finally {
      FileUtils.deleteQuietly(temp);
    }
  }

  /**
   * Creates the platform specific arguments to run the lessc shell utility. Default implementation handles
   * windows and unix platforms.
   *
   * @return arguments for command line. The implementation will take care of OS differences.
   */
  protected String[] getCommandLine(final String filePath) {
    return isWindows ? buildArgumentsForWindows(filePath) : buildArgumentsForUnix(filePath);
  }

  /**
   * @return arguments required to run lessc on non Windows platform.
   */
  private String[] buildArgumentsForUnix(final String filePath) {
    return new String[] {
      SHELL_COMMAND, OPTION_NO_COLOR, filePath
    };
  }

  /**
   * @return arguments required to run lessc on Windows platform.
   */
  private String[] buildArgumentsForWindows(final String filePath) {
    return new String[] {
      "cmd", "/c", SHELL_COMMAND, OPTION_NO_COLOR, filePath
    };
  }
}


package ro.isdc.wro.extensions.model.spi;

import java.util.HashMap;
import java.util.Map;

import ro.isdc.wro.extensions.model.factory.GroovyModelFactory;
import ro.isdc.wro.extensions.model.factory.JsonModelFactory;
import ro.isdc.wro.extensions.model.factory.SmartWroModelFactory;
import ro.isdc.wro.model.factory.WroModelFactory;
import ro.isdc.wro.model.spi.ModelFactoryProvider;

/**
 * Default provider of {@link WroModelFactory} for the core module.
 *
 * @author Alex Objelean
 * @created 4 Apr 2013
 * @since 1.6.3
 */
public class DefaultModelFactoryProvider
    implements ModelFactoryProvider {

  /**
   * {@inheritDoc}
   */
  @Override
  public Map<String, WroModelFactory> provideModelFactories() {
    final Map<String, WroModelFactory> map = new HashMap<String, WroModelFactory>();
    map.put(GroovyModelFactory.ALIAS, new GroovyModelFactory());
    map.put(JsonModelFactory.ALIAS, new JsonModelFactory());
    map.put(SmartWroModelFactory.ALIAS, new SmartWroModelFactory());
    return map;
  }
}


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

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

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

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

/**
 * Dust is a JavaScript templating engine designed to provide a clean separation between presentation and logic without
 * sacrificing ease of use. It is particularly well-suited for asynchronous and streaming applications.
 *
 * @author Eivind Barstad Waaler
 * @since 1.4.5
 * @created 8 Mar 2012
 */
public class DustJs extends AbstractJsTemplateCompiler {
  private static final Logger LOG = LoggerFactory.getLogger(DustJs.class);

  /**
   * The name of the system property used to specify the path of the dust compiler. If this is not specified, the default
   * compiler is used.
   */
  static final String PARAM_COMPILER_PATH = "dustJs.file.path";
  private static final String DEFAULT_DUST_JS = "dust-full-1.1.1.min.js";

  /**
   * {@inheritDoc}
   */
  @Override
  protected InputStream getCompilerAsStream() throws IOException {
    final String compilerPath = System.getProperty(PARAM_COMPILER_PATH);
    LOG.debug("compilerPath: {}", compilerPath);
    return StringUtils.isEmpty(compilerPath) ? getDefaultCompilerStream() : new FileInputStream(
        compilerPath);
  }

  /**
   * @return default stream of the compiler.
   */
  protected InputStream getDefaultCompilerStream() {
    return DustJs.class.getResourceAsStream(DEFAULT_DUST_JS);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected String getCompileCommand() {
    return "dust.compile";
  }
}


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

import java.io.InputStream;

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

/**
 * Hogan.js: http://twitter.github.com/hogan.js/
 *
 * @author Eivind Barstad Waaler
 * @since 1.4.7
 * @created 10 May 2012
 */
public class HoganJs
    extends AbstractJsTemplateCompiler {
  private static final String DEFAULT_HOGAN_JS = "hogan-2.0.0.min.js";
  /**
   * {@inheritDoc}
   */
  @Override
  public String compile(final String content, final String optionalArgument) {
    return String.format("Hogan.cache['%s'] = %s;", optionalArgument, super.compile(content, ""));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected InputStream getCompilerAsStream() {
    return HoganJs.class.getResourceAsStream(DEFAULT_HOGAN_JS);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected String getCompileCommand() {
    return "Hogan.compile";
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected String getArguments() {
    return "{asString: true}";
  }
}


package ro.isdc.wro.model.spi;

import java.util.HashMap;
import java.util.Map;

import ro.isdc.wro.model.factory.WroModelFactory;
import ro.isdc.wro.model.factory.XmlModelFactory;

/**
 * Default provider of {@link WroModelFactory} for the core module.
 *
 * @author Alex Objelean
 * @created 4 Apr 2013
 * @since 1.6.3
 */
public class DefaultModelFactoryProvider
    implements ModelFactoryProvider {

  /**
   * {@inheritDoc}
   */
  public Map<String, WroModelFactory> provideModelFactories() {
    final Map<String, WroModelFactory> map = new HashMap<String, WroModelFactory>();
    map.put(XmlModelFactory.ALIAS, new XmlModelFactory());
    return map;
  }
}


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

import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.PatternSyntaxException;

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

/**
 * Css minify barryvan implementation.
 */
public class CSSMin {
  private static final Logger LOG = LoggerFactory.getLogger(CSSMin.class);

  public void formatFile(final String input, final Writer writer)
    throws Exception {
    try {
      final StringBuilder sb = new StringBuilder(input.replaceAll("[\t\n\r]", "").replaceAll("  ", " "));
      int k, n;

      // Find the start of the comment
      while ((n = sb.indexOf("/*")) != -1) {
        k = sb.indexOf("*/", n + 2);
        if (k == -1) {
          throw new Exception("Error: Unterminated comment.");
        }
        sb.delete(n, k + 2);
      }

      final List<Selector> selectors = new LinkedList<Selector>();
      n = 0;
      while ((k = sb.indexOf("}", n)) != -1) {
        try {
          selectors.add(new Selector(sb.substring(n, k + 1)));
        } catch (final Exception e) {
          // skip
        }
        n = k + 1;
      }

        for (Selector selector : selectors) {
            writer.write(selector.toString());
        }
      writer.write("\r\n");
      writer.flush();
    } catch (final Exception e) {
      LOG.error(e.getMessage(), e);
    }

  }
}

class Selector {
  private static final Logger LOG = LoggerFactory.getLogger(CSSMin.class);
  private final Property[] properties;
  private final String selector;

  /**
   * Creates a new Selector using the supplied strings.
   *
   * @param selector The selector; for example, "div { border: solid 1px red; color: blue; }"
   */
  public Selector(final String selector) throws Exception {
    final String[] parts = selector.split("\\{"); // We have to escape the { with a \ for the regex, which itself
    // requires escaping for the string. Sigh.
    if (parts.length < 2) {
      throw new Exception("Warning: Incomplete selector: " + selector);
    }
    this.selector = parts[0].trim();
    String contents = parts[1].trim();
    if (contents.length() <= 1) {
      throw new Exception("Warning: Empty selector body: " + selector);
    }
    if (contents.charAt(contents.length() - 1) != '}') { // Ensure that we have a leading and trailing brace.
      throw new Exception("Warning: Unterminated selector: " + selector);
    }
    contents = StringUtils.substringBefore(contents, "}");
    properties = parseProperties(contents);
    sortProperties(properties);
  }

  /**
   * Prints out this selector and its contents nicely, with the contents sorted alphabetically.
   */
  @Override
  public String toString() {
    final StringBuilder sb = new StringBuilder();
    sb.append(selector).append("{");
    for (final Property property : properties) {
      if (property != null) {
        sb.append(property.toString());
      }
    }
    sb.append("}");
    return sb.toString();
  }

  /**
   * Parses out the properties of a selector's body.
   *
   * @param contents The body; for example, "border: solid 1px red; color: blue;"
   */
  private Property[] parseProperties(final String contents) {
    final String[] parts = contents.split(";");
    final List<Property> resultsAsList = new ArrayList<Property>();

      for (String part : parts) {
          try {
              // ignore empty parts
              if (!StringUtils.isEmpty(part.trim())) {
                  resultsAsList.add(new Property(part));
              }
          } catch (final Exception e) {
              LOG.warn(e.getMessage(), e);
          }
      }
    return resultsAsList.toArray(new Property[resultsAsList.size()]);
  }

  private void sortProperties(final Property[] propertiesToSort) {
    Arrays.sort(propertiesToSort);
  }
}

class Property
  implements Comparable {
  private static final Logger LOG = LoggerFactory.getLogger(CSSMin.class);
  protected String property;
  protected Value[] values;

  /**
   * Creates a new Property using the supplied strings. Parses out the values of the property selector.
   *
   * @param property The property; for example, "border: solid 1px red;" or
   *        "-moz-box-shadow: 3px 3px 3px rgba(255, 255, 0, 0.5);".
   */
  public Property(final String property) throws Exception {
    try {
      // Parse the property.
      //final String[] parts = property.split(":"); // Split "color: red" to ["color", " red"]
      final List<String> parts = Arrays.asList(property.split(":", 2)); // Split "color: red" to ["color", " red"]
      final List<String> nonEmptyParts = new ArrayList<String>();
      for (String part : parts) {
        if (!StringUtils.isEmpty(part.trim())) {
          nonEmptyParts.add(part.trim());
        }
      }
      if (nonEmptyParts.size() < 2) {
        throw new Exception("Warning: Incomplete property: " + property);
      }
      this.property = nonEmptyParts.get(0).toLowerCase();

      values = parseValues(nonEmptyParts.get(1).replaceAll(", ", ","));

    } catch (final PatternSyntaxException e) {
      // Invalid regular expression used.
    }
  }

  /**
   * Prints out this property nicely, with the contents sorted in a standardised order.
   */
  @Override
  public String toString() {
    final StringBuilder sb = new StringBuilder();
    sb.append(property).append(":");
    for (final Value v : values) {
      sb.append(v.toString()).append(",");
    }
    sb.deleteCharAt(sb.length() - 1); // Delete the trailing comma.
    sb.append(";");
    return sb.toString();
  }

  public int compareTo(final Object other) {
    return property.compareTo(((Property)other).property);
  }

  private Value[] parseValues(final String contents) {
    final String[] parts = contents.split(",");
    final Value[] results = new Value[parts.length];

    for (int i = 0; i < parts.length; i++) {
      try {
        results[i] = new Value(parts[i]);
      } catch (final Exception e) {
        LOG.error(e.getMessage(), e);
        results[i] = null;
      }
    }

    return results;
  }
}

class Value {

  String[] parts;

  public Value(final String value) throws Exception {
    // Parse the value.
    parts = value.split(" "); // Split "solid 1px red" to ["solid","1px","red"] and sort them.
  }

  @Override
  public String toString() {
    final StringBuilder sb = new StringBuilder();
    for (final String part : parts) {
      sb.append(part).append(" ");
    }
    return sb.toString().trim();
  }
}


/**
 * Copyright Alex Objelean
 */
package ro.isdc.wro.util;

import java.text.NumberFormat;
import java.util.LinkedList;
import java.util.List;

/**
 * Simple stop watch, allowing for timing of a number of tasks, exposing total running time and running time for each
 * named task.
 *
 *

 * Conceals use of System.currentTimeMillis(), improving the readability of application code and reducing
 * the likelihood of calculation errors.
 *
 *

 * Note that this object is not designed to be thread-safe and does not use synchronization.
 *
 *

 * This class is normally used to verify performance during proof-of-concepts and in development, rather than as part of
 * production applications.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @since May 2, 2001
 */
public class StopWatch {

  /**
   * Identifier of this stop watch. Handy when we have output from multiple stop watches and need to distinguish between
   * them in log or console output.
   */
  private final String id;

  private boolean keepTaskList = true;

  /** List of TaskInfo objects */
  private final List taskList = new LinkedList();

  /** Start time of the current task */
  private long startTimeMillis;

  /** Is the stop watch currently running? */
  private boolean running;

  /** Name of the current task */
  private String currentTaskName;

  private TaskInfo lastTaskInfo;

  private int taskCount;

  /** Total running time */
  private long totalTimeMillis;

  /**
   * Construct a new stop watch. Does not start any task.
   */
  public StopWatch() {
    this.id = "";
  }

  /**
   * Construct a new stop watch with the given id. Does not start any task.
   *
   * @param id identifier for this stop watch. Handy when we have output from multiple stop watches and need to
   *        distinguish between them.
   */
  public StopWatch(final String id) {
    this.id = id;
  }

  /**
   * Determine whether the TaskInfo array is built over time. Set this to "false" when using a StopWatch for millions of
   * intervals, or the task info structure will consume excessive memory. Default is "true".
   */
  public void setKeepTaskList(final boolean keepTaskList) {
    this.keepTaskList = keepTaskList;
  }

  /**
   * Start an unnamed task. The results are undefined if {@link #stop()} or timing methods are called without invoking
   * this method.
   *
   * @see #stop()
   */
  public void start()
    throws IllegalStateException {
    start("");
  }

  /**
   * Start a named task. The results are undefined if {@link #stop()} or timing methods are called without invoking this
   * method.
   *
   * @param taskName the name of the task to start
   * @see #stop()
   */
  public void start(final String taskName)
    throws IllegalStateException {
    if (this.running) {
      throw new IllegalStateException("Can't start StopWatch: it's already running");
    }
    this.startTimeMillis = System.currentTimeMillis();
    this.running = true;
    this.currentTaskName = taskName;
  }

  /**
   * Stop the current task. The results are undefined if timing methods are called without invoking at least one pair
   * {@link #start()} / {@link #stop()} methods.
   *
   * @see #start()
   */
  public void stop()
    throws IllegalStateException {
    if (!this.running) {
      throw new IllegalStateException("Can't stop StopWatch: it's not running");
    }
    final long lastTime = System.currentTimeMillis() - this.startTimeMillis;
    this.totalTimeMillis += lastTime;
    this.lastTaskInfo = new TaskInfo(this.currentTaskName, lastTime);
    if (this.keepTaskList) {
      this.taskList.add(lastTaskInfo);
    }
    ++this.taskCount;
    this.running = false;
    this.currentTaskName = null;
  }

  /**
   * Return whether the stop watch is currently running.
   */
  public boolean isRunning() {
    return this.running;
  }

  /**
   * Return the time taken by the last task.
   */
  public long getLastTaskTimeMillis()
    throws IllegalStateException {
    if (this.lastTaskInfo == null) {
      throw new IllegalStateException("No tests run: can't get last interval");
    }
    return this.lastTaskInfo.getTimeMillis();
  }

  /**
   * Return the total time in milliseconds for all tasks.
   */
  public long getTotalTimeMillis() {
    return totalTimeMillis;
  }

  /**
   * Return the total time in seconds for all tasks.
   */
  public double getTotalTimeSeconds() {
    return totalTimeMillis / 1000.0;
  }

  /**
   * Return the number of tasks timed.
   */
  public int getTaskCount() {
    return taskCount;
  }

  /**
   * Return an array of the data for tasks performed.
   */
  public TaskInfo[] getTaskInfo() {
    if (!this.keepTaskList) {
      throw new UnsupportedOperationException("Task info is not being kept!");
    }
    return (TaskInfo[])this.taskList.toArray(new TaskInfo[this.taskList.size()]);
  }

  /**
   * Return a short description of the total running time.
   */
  public String shortSummary() {
    return "StopWatch '" + this.id + "': running time (millis) = " + getTotalTimeMillis();
  }

  /**
   * Return a string with a table describing all tasks performed. For custom reporting, call getTaskInfo() and use the
   * task info directly.
   */
  public String prettyPrint() {
    final StringBuffer sb = new StringBuffer(shortSummary());
    sb.append('\n');
    if (!this.keepTaskList) {
      sb.append("No task info kept");
    } else {
      final TaskInfo[] tasks = getTaskInfo();
      sb.append("-----------------------------------------\n");
      sb.append("ms     %     Task name\n");
      sb.append("-----------------------------------------\n");
      final NumberFormat nf = NumberFormat.getNumberInstance();
      nf.setMinimumIntegerDigits(5);
      nf.setGroupingUsed(false);
      final NumberFormat pf = NumberFormat.getPercentInstance();
      pf.setMinimumIntegerDigits(3);
      pf.setGroupingUsed(false);
      for (final TaskInfo task : tasks) {
        sb.append(nf.format(task.getTimeMillis()) + "  ");
        final double totalTimeSeconds = getTotalTimeSeconds();
        final double percentage = totalTimeSeconds == 0 ? 0 : task.getTimeSeconds() / totalTimeSeconds;
        sb.append(pf.format(percentage) + "  ");
        sb.append(task.getTaskName() + "\n");
      }
    }
    return sb.toString();
  }

  /**
   * Inner class to hold data about one task executed within the stop watch.
   */
  public static class TaskInfo {

    private final String taskName;

    private final long timeMillis;

    private TaskInfo(final String taskName, final long timeMillis) {
      this.taskName = taskName;
      this.timeMillis = timeMillis;
    }

    /**
     * Return the name of this task.
     */
    public String getTaskName() {
      return taskName;
    }

    /**
     * Return the time in milliseconds this task took.
     */
    public long getTimeMillis() {
      return timeMillis;
    }

    /**
     * Return the time in seconds this task took.
     */
    public double getTimeSeconds() {
      return timeMillis / 1000.0;
    }
  }

}


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

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

import org.mozilla.javascript.NativeArray;
import org.mozilla.javascript.NativeObject;
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;

/**
 * Uses rhino to compile a TypeScript into javascript.
 *
 * @author Alex Objelean
 * @since 1.6.3
 * @created 18 Jan 2013
 */
public class TypeScriptCompiler {
  private static final String PARAM_ERRORS = "errors";
  private static final String PARAM_SOURCE = "source";
  private static final Logger LOG = LoggerFactory.getLogger(TypeScriptCompiler.class);
  private static final String TYPESCRIPT_JS = "typescript-0.8.js";
  private static final String TYPESCRIPT_COMPILE_JS = "typescript.compile-0.3.js";
  private final String ecmaScriptVersion = "TypeScript.CodeGenTarget.ES5";
  private ScriptableObject scope;

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

    stopWatch.start("compile");
    try {
      final String execute = getCompilationCommand(typeScript);
      final NativeObject compilationResult = (NativeObject) builder.evaluate(execute, "compile");
      final NativeArray errors = (NativeArray) compilationResult.get(PARAM_ERRORS);
      if (errors.size() > 0) {
        throwCompilationError(errors);
      }
      return compilationResult.get(PARAM_SOURCE).toString();
    } finally {
      stopWatch.stop();
      LOG.debug(stopWatch.prettyPrint());
    }
  }

  private void throwCompilationError(final NativeArray errors) {
    final StringBuilder sb = new StringBuilder();
    for (final Object error : errors) {
      sb.append(error.toString()).append("\n");
    }
    LOG.debug("Compilation errors: {}", sb);
    throw new WroRuntimeException(sb.toString());
  }

  /**
   * Creates compilation command for provided typescript input.
   *
   * @param input
   *          the TypeScript to compile into js.
   * @return compilation command.
   */
  private String getCompilationCommand(final String input) {
    return String.format("compilerWrapper.compile(%s, %s)", WroUtil.toJSMultiLineString(input),
        ecmaScriptVersion);
  }

  /**
   * @return the stream of the compiler resource (javascript) used to compile templates.
   * @throws IOException
   *           if the stream could not be located.
   */
  private InputStream getCompilerAsStream()
      throws IOException {
    return new SequenceInputStream(getCompilerStream(), getCompilerWrapperStream());
  }

  /**
   * @return the stream for the script performing actual compilation wrapper.
   */
  protected InputStream getCompilerWrapperStream() {
    return TypeScriptCompiler.class.getResourceAsStream(TYPESCRIPT_COMPILE_JS);
  }

  /**
   * @return the stream for the script for TypeScript compiler.
   */
  protected InputStream getCompilerStream() {
    return TypeScriptCompiler.class.getResourceAsStream(TYPESCRIPT_JS);
  }

  private RhinoScriptBuilder initScriptBuilder() {
    try {
      RhinoScriptBuilder builder;
      if (scope == null) {
        builder = RhinoScriptBuilder.newClientSideAwareChain().evaluateChain(getCompilerAsStream(), "templateCompiler.js");
        scope = builder.getScope();
      } else {
        builder = RhinoScriptBuilder.newChain(scope);
      }
      return builder;
    } catch (final IOException ex) {
      throw new WroRuntimeException("Failed reading init script", ex);
    }
  }
}


package ro.isdc.wro.http.support;

import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;

import javax.servlet.http.HttpServletResponse;

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

import ro.isdc.wro.WroRuntimeException;
import ro.isdc.wro.config.jmx.WroConfiguration;
import ro.isdc.wro.util.WroUtil;

/**
 * Responsible for configuring response headers. The configured headers contains the default headers overridden by those
 * set through "headers" property.
 *
 * @author Alex Objelean
 * @since 1.4.9
 * @created 27 Aug 2012
 */
public class ResponseHeadersConfigurer {
  private static final Logger LOG = LoggerFactory.getLogger(ResponseHeadersConfigurer.class);
  /**
   * Default value used by Cache-control header.
   */
  private static final String DEFAULT_CACHE_CONTROL_VALUE = "public, max-age=315360000";
  /**
   * String representation of headers to set. Each header is separated by a | character.
   */
  private final String headersAsString;
  private final Long lastModifiedTimestamp = initTimestamp();

  /**
   * Map containing header values used to control caching. The keys from this values are trimmed and lower-cased when
   * put, in order to avoid duplicate keys. This is done, because according to RFC 2616 Message Headers field names are
   * case-insensitive.
   */
  @SuppressWarnings("serial")
  private final Map<String, String> headersMap = new LinkedHashMap<String, String>() {
    @Override
    public String put(final String key, final String value) {
      return super.put(key.trim().toLowerCase(), value);
    }

    @Override
    public String get(final Object key) {
      return super.get(((String) key).toLowerCase());
    }
  };

  /**
   * Factory method which creates a {@link ResponseHeadersConfigurer} containing headers used to disable cache.
   */
  public static ResponseHeadersConfigurer noCache() {
    return new ResponseHeadersConfigurer() {
      @Override
      public void configureDefaultHeaders(final Map<String, String> map) {
        addNoCacheHeaders(map);
      }
    };
  }

  /**
   * Factory method which creates a {@link ResponseHeadersConfigurer} containing no headers set.
   */
  public static ResponseHeadersConfigurer emptyHeaders() {
    return new ResponseHeadersConfigurer();
  }

  /**
   * Factory method which creates a {@link ResponseHeadersConfigurer} containing headers used to disable cache in debug
   * mode.
   */
  public static ResponseHeadersConfigurer fromConfig(final WroConfiguration config) {
    return new ResponseHeadersConfigurer(config.getHeader()) {
      @Override
      public void configureDefaultHeaders(final Map<String, String> map) {
        if (config.isDebug()) {
          // prevent caching when in development mode
          addNoCacheHeaders(map);
        } else {
          final Calendar cal = Calendar.getInstance();
          cal.roll(Calendar.YEAR, 1);
          map.put(HttpHeader.CACHE_CONTROL.toString(), DEFAULT_CACHE_CONTROL_VALUE);
          map.put(HttpHeader.EXPIRES.toString(), WroUtil.toDateAsString(cal.getTimeInMillis()));
          // TODO probably this is not a good idea to set this field which will have a different value when there will be
          // more than one instance of wro4j.
          map.put(HttpHeader.LAST_MODIFIED.toString(), WroUtil.toDateAsString(getLastModifiedTimestamp()));
        }
      };
    };
  }

  /**
   * Factory method which creates a {@link ResponseHeadersConfigurer} containing headers provided as string (separated
   * by | character).
   */
  public static ResponseHeadersConfigurer withHeadersSet(final String headersAsString) {
    return new ResponseHeadersConfigurer(headersAsString);
  }

  public ResponseHeadersConfigurer() {
    this(null);
  }

  /**
   * @param headersAsString
   *          string representation of the headers to add separated by | character.
   */
  public ResponseHeadersConfigurer(final String headersAsString) {
    this.headersAsString = headersAsString;
    headersMap.clear();
    initHeaderValues();
  }

  /**
   * Initialize header values.
   */
  private void initHeaderValues() {
    configureDefaultHeaders(headersMap);
    configureFromHeadersAsString();
    LOG.debug("Header Values: {}", headersMap);
  }

  private void configureFromHeadersAsString() {
    if (!StringUtils.isEmpty(headersAsString)) {
      try {
        if (headersAsString.contains("|")) {
          final String[] headerAsArray = headersAsString.split("[|]");
          for (final String header : headerAsArray) {
            parseHeader(header);
          }
        } else {
          parseHeader(headersAsString);
        }
      } catch (final Exception e) {
        throw new WroRuntimeException("Invalid header init-param value: " + headersAsString
            + ". A correct value should have the following format: "
            + "<HEADER_NAME1>: <VALUE1> | <HEADER_NAME2>: <VALUE2>. " + "Ex: <look like this: "
            + "Expires: Thu, 15 Apr 2010 20:00:00 GMT | cache-control: public", e);
      }
    }
  }

  /**
   * Parse header value & puts the found values in headersMap field.
   *
   * @param header
   *          value to parse.
   */
  private void parseHeader(final String header) {
    LOG.debug("parseHeader: {}", header);
    final String headerName = header.substring(0, header.indexOf(":"));
    if (!headersMap.containsKey(headerName)) {
      final String value = header.substring(header.indexOf(":") + 1);
      headersMap.put(headerName, StringUtils.trim(value));
    }
  }

  /**
   * Allow configuration of default headers. This is useful when you need to set custom expires headers.
   *
   * @param map
   *          the {@link Map} where key represents the header name, and value - header value.
   */
  public void configureDefaultHeaders(final Map<String, String> map) {
  }

  /**
   * Populates the map with headers used to disable cache.
   */
  private static void addNoCacheHeaders(final Map<String, String> map) {
    map.put(HttpHeader.PRAGMA.toString(), "no-cache");
    map.put(HttpHeader.CACHE_CONTROL.toString(), "no-cache");
    map.put(HttpHeader.EXPIRES.toString(), "0");
  }

  /**
   * Method called for each request and responsible for setting response headers, used mostly for cache control.
   * Override this method if you want to change the way headers are set.<br>
   *
   * @param response
   *          {@link HttpServletResponse} object.
   */
  public void setHeaders(final HttpServletResponse response) {
    // Force resource caching as best as possible
    for (final Map.Entry<String, String> entry : headersMap.entrySet()) {
      response.setHeader(entry.getKey(), entry.getValue());
    }
  }

  /**
   * @VisibleForTesting
   */
  final Map<String, String> getHeadersMap() {
    return Collections.unmodifiableMap(headersMap);
  }

  /**
   * @return the timestamp of the last modification.
   */
  public Long getLastModifiedTimestamp() {
    return lastModifiedTimestamp;
  }

  /**
   * @return the timestamp value milliseconds stripped. Strip operation is important, because when timestamp is
   *         extracted from response header, it ends with 000 (milliseconds are not applied).
   */
  private long initTimestamp() {
    long timestamp = new Date().getTime();
    timestamp = timestamp - (timestamp % 1000);
    return timestamp;
  }
}


/*
 * Copyright (C) 2010.
 * All rights reserved.
 */
package ro.isdc.wro.extensions.processor.js;

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

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

import ro.isdc.wro.WroRuntimeException;
import ro.isdc.wro.extensions.processor.support.ObjectPoolHelper;
import ro.isdc.wro.extensions.processor.support.coffeescript.CoffeeScript;
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.Destroyable;
import ro.isdc.wro.model.resource.processor.ResourcePostProcessor;
import ro.isdc.wro.model.resource.processor.ResourcePreProcessor;
import ro.isdc.wro.util.ObjectFactory;

/**
 * Uses coffee script library loaded from the webjar to compile to javascript code.
 *
 * @author Alex Objelean
 * @created 26 Mar 2011
 * @since 1.3.6
 */
@SupportedResourceType(ResourceType.JS)
public class RhinoCoffeeScriptProcessor
  implements ResourcePreProcessor, ResourcePostProcessor, Destroyable {
  private static final Logger LOG = LoggerFactory.getLogger(RhinoCoffeeScriptProcessor.class);
  public static final String ALIAS = "rhinoCoffeeScript";
  private ObjectPoolHelper<CoffeeScript> enginePool;

  public RhinoCoffeeScriptProcessor() {
    enginePool = new ObjectPoolHelper<CoffeeScript>(new ObjectFactory<CoffeeScript>() {
      @Override
      public CoffeeScript create() {
        return newCoffeeScript();
      }
    });
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void process(final Resource resource, final Reader reader, final Writer writer)
    throws IOException {
    final String content = IOUtils.toString(reader);
    final CoffeeScript coffeeScript = enginePool.getObject();
    try {
      writer.write(coffeeScript.compile(content));
    } catch (final Exception e) {
      onException(e);
      final String resourceUri = resource == null ? StringUtils.EMPTY : "[" + resource.getUri() + "]";
      LOG.error("Exception while applying " + getClass().getSimpleName() + " processor on the " + resourceUri
          + " resource, no processing applied...", e);
    } finally {
      reader.close();
      writer.close();
      enginePool.returnObject(coffeeScript);
    }
  }

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

  /**
   * @return the {@link CoffeeScript} engine implementation. Override it to provide a different version of the coffeeScript.js
   *         library. Useful for upgrading the processor outside the wro4j release.
   */
  protected CoffeeScript newCoffeeScript() {
    return new CoffeeScript();
  }

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

  @Override
  public void destroy() throws Exception {
    enginePool.destroy();
  }
}


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

import java.util.Properties;

import javax.servlet.FilterConfig;

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

import ro.isdc.wro.config.jmx.WroConfiguration;

/**
 * Uses a default Properties file location (under <code>/WEB-INF/wro.properties</code>) for creating
 * {@link WroConfiguration} and overrides them with properties defined in {@link FilterConfig} object. This will allow
 * user to easily switch from old style of configuring {@link WroConfiguration} to the new style (by defining a property
 * file).
 *
 * @author Alex Objelean
 * @created 17 Jun 2011
 * @since 1.3.8
 */
public class PropertiesAndFilterConfigWroConfigurationFactory
  extends FilterConfigWroConfigurationFactory {
  private static final Logger LOG = LoggerFactory.getLogger(PropertiesAndFilterConfigWroConfigurationFactory.class);
  /**
   * The default factory used to load configuration from a default location.
   */
  private final ServletContextPropertyWroConfigurationFactory defaultFactory;

  public PropertiesAndFilterConfigWroConfigurationFactory(final FilterConfig filterConfig) {
    super(filterConfig);
    defaultFactory = new ServletContextPropertyWroConfigurationFactory(filterConfig.getServletContext());
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Properties createProperties() {
    //Merge Properties file content with the filterConfig content.
    final Properties merged = new Properties();
    try {
      final Properties props = newDefaultProperties();
      if (props != null) {
        merged.putAll(props);
      }
    } catch (final Exception e) {
      LOG.warn("Cannot load properties from default location. Load propertis from filterConfig", e);
    }
    final Properties props = createPropertiesFromFilterConfig();
    merged.putAll(props);
    return merged;
  }

  /**
   * @return the {@link Properties} built from the default location.
   */
  protected Properties newDefaultProperties() {
    return defaultFactory.createProperties();
  }
}


/*
 * Copyright (C) 2011. All rights reserved.
 */
package ro.isdc.wro.extensions.script;

import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.mozilla.javascript.EcmaError;
import org.mozilla.javascript.JavaScriptException;
import org.mozilla.javascript.RhinoException;
import org.mozilla.javascript.ScriptableObject;

/**
 * Direct conversion between native Rhino objects and JSON.
 *

 * This class can be used directly in Rhino.
 *

 * This class was inspired from {@link http
 * ://mongodb-rhino.googlecode.com/svn/trunk/modules/com.mongodb.rhino/src/com/mongodb/rhino/JSON.java}
 *
 * @author Alex Objelean
 * @since 1.3.6
 */
public class RhinoUtils {
  private static final Pattern[] ESCAPE_PATTERNS = new Pattern[] {
    Pattern.compile("\\\\"), Pattern.compile("\\n"), Pattern.compile("\\r"), Pattern.compile("\\t"),
    Pattern.compile("\\f"), Pattern.compile("\\\"")
  };

  private static final String[] ESCAPE_REPLACEMENTS = new String[] {
    Matcher.quoteReplacement("\\\\"), Matcher.quoteReplacement("\\n"), Matcher.quoteReplacement("\\r"),
    Matcher.quoteReplacement("\\t"), Matcher.quoteReplacement("\\f"), Matcher.quoteReplacement("\\\"")
  };

  /**
   * Prevent instantiation.
   */
  private RhinoUtils() {
  }

  /**
   * Creates a more detailed message based on {@link RhinoException} thrown by rhino execution. The message will contain
   * a detailed description of the problem by inspecting the JSON value provided by exception.
   *
   * @param e
   *          {@link RhinoException} thrown by rhino execution.
   * @return detailed string message.
   */
  public static String createExceptionMessage(final RhinoException e) {
    StringBuffer message = new StringBuffer("Could not execute the script because: \n");
    if (e instanceof JavaScriptException) {
      message.append(toJson(((JavaScriptException) e).getValue()));
    } else if (e instanceof EcmaError) {
      final EcmaError ecmaError = (EcmaError) e;
      message.append(String.format("Error message: %s at line: %s. \nSource: %s", ecmaError.getErrorMessage(),
          ecmaError.lineNumber(), ecmaError.lineSource()));
    } else {
      message.append(e.getMessage());
    }
    return message.toString();
  }

  /**
   * Recursively convert from native Rhino to JSON.
   *

   * Recognizes JavaScript objects, arrays and primitives.
   *

   * Special support for JavaScript dates: converts to {"$date": timestamp} in JSON.
   *

   * Special support for MongoDB ObjectId: converts to {"$oid": "objectid"} in JSON.
   *

   * Also recognizes JVM types: java.util.Map, java.util.Collection, java.util.Date.
   *
   * @param object
   *          A Rhino native object
   * @return The JSON string
   * @see RhinoUtils#convertSpecial(Object)
   */
  public static String toJson(final Object object) {
    return toJson(object, true);
  }

  /**
   * Recursively convert from native Rhino to JSON.
   *

   * Recognizes JavaScript objects, arrays and primitives.
   *

   * Special support for JavaScript dates: converts to {"$date": timestamp} in JSON.
   *

   * Special support for MongoDB ObjectId: converts to {"$oid": "objectid"} in JSON.
   *

   * Also recognizes JVM types: java.util.Map, java.util.Collection, java.util.Date.
   *
   * @param object
   *          A Rhino native object
   * @param indent
   *          Whether to indent the JSON for human readability
   * @return The JSON string
   */
  public static String toJson(final Object object, final boolean indent) {
    final StringBuilder s = new StringBuilder();
    encode(s, object, indent, indent ? 0 : -1);
    return s.toString();
  }

  private static void encode(final StringBuilder s, final Object object, final boolean indent, final int depth) {
    if (indent)
      indent(s, depth);

    if (object == null) {
      s.append("null");
    } else if (object instanceof Number) {
      s.append(object);
    } else if (object instanceof Boolean) {
      s.append(object);
    } else if (object instanceof Date) {
      final HashMap<String, Long> map = new HashMap<String, Long>();
      map.put("$date", ((Date) object).getTime());
      encode(s, map, depth);
    } else if (object instanceof Collection) {
      encode(s, (Collection<?>) object, depth);
    } else if (object instanceof Map) {
      encode(s, (Map<?, ?>) object, depth);
      // } else if (object instanceof NativeArray) {
      // final NativeArray nativeArray = (NativeArray) object;
      // encode(s, nativeArray, depth);
    } else if (object instanceof ScriptableObject) {
      final ScriptableObject scriptable = (ScriptableObject) object;
      if (scriptable.getClassName().equals("Date")) {
        // (The NativeDate class is private in Rhino, but we can access
        // it like a regular object.)

        final Object time = ScriptableObject.callMethod(scriptable, "getTime", null);
        if (time instanceof Number) {
          encode(s, new Date(((Number) time).longValue()), false, depth);
          return;
        }
      }

      encode(s, scriptable, depth);
    } else {
      s.append('\"');
      s.append(escape(object.toString()));
      s.append('\"');
    }
  }

  private static void encode(final StringBuilder s, final Collection<?> collection, final int depth) {
    s.append('[');

    final Iterator<?> i = collection.iterator();
    if (i.hasNext()) {
      if (depth > -1) {
        s.append('\n');
      }

      while (true) {
        final Object value = i.next();

        encode(s, value, true, depth > -1 ? depth + 1 : -1);

        if (i.hasNext()) {
          s.append(',');
          if (depth > -1) {
            s.append('\n');
          }
        } else
          break;
      }

      if (depth > -1) {
        s.append('\n');
      }
    }

    if (depth > -1) {
      indent(s, depth);
    }
    s.append(']');
  }

  private static void encode(final StringBuilder s, final Map<?, ?> map, final int depth) {
    s.append('{');

    final Iterator<?> i = map.entrySet().iterator();
    if (i.hasNext()) {
      if (depth > -1) {
        s.append('\n');
      }

      while (true) {
        final Map.Entry<?, ?> entry = (Map.Entry<?, ?>) i.next();
        final String key = entry.getKey().toString();
        final Object value = entry.getValue();

        if (depth > -1) {
          indent(s, depth + 1);
        }

        s.append('\"');
        s.append(escape(key));
        s.append("\":");

        if (depth > -1) {
          s.append(' ');
        }

        encode(s, value, false, depth > -1 ? depth + 1 : -1);

        if (i.hasNext()) {
          s.append(',');
          if (depth > -1) {
            s.append('\n');
          }
        } else
          break;
      }

      if (depth > -1) {
        s.append('\n');
      }
    }

    if (depth > -1) {
      indent(s, depth);
    }
    s.append('}');
  }

  private static void encode(final StringBuilder s, final ScriptableObject object, final int depth) {
    s.append('{');

    final Object[] ids = object.getAllIds();
    if (ids.length > 0) {
      if (depth > -1) {
        s.append('\n');
      }

      final int length = ids.length;
      for (int i = 0; i < length; i++) {
        final String key = ids[i].toString();
        final Object value = ScriptableObject.getProperty(object, key);

        if (depth > -1) {
          indent(s, depth + 1);
        }

        s.append('\"');
        s.append(escape(key));
        s.append("\":");

        if (depth > -1) {
          s.append(' ');
        }

        encode(s, value, false, depth > -1 ? depth + 1 : -1);

        if (i < length - 1) {
          s.append(',');
          if (depth > -1) {
            s.append('\n');
          }
        }
      }

      if (depth > -1) {
        s.append('\n');
      }
    }

    if (depth > -1) {
      indent(s, depth);
    }
    s.append('}');
  }

  private static void indent(final StringBuilder s, final int depth) {
    for (int i = depth - 1; i >= 0; i--) {
      s.append("  ");
    }
  }

  private static String escape(final String string) {
    String result = null;
    for (int i = 0, length = ESCAPE_PATTERNS.length; i < length; i++) {
      result = ESCAPE_PATTERNS[i].matcher(string).replaceAll(ESCAPE_REPLACEMENTS[i]);
    }
    return result;
  }
}