content: /**
   * Copyright 2007-2011 Jordi Hernández Sellés, Ibrahim Chaehoi 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.model.resource.processor.support;
  
  import java.util.ArrayList;
  import java.util.Iterator;
  import java.util.List;
  import java.util.regex.Matcher;
  import java.util.regex.Pattern;
  
  
  /**
   * Minifies CSS files by removing expendable whitespace and comments.
   *
   * @author Jordi Hernández Sellés
   */
  public class JawrCssMinifier {
    // This regex captures comments
    // private static final String COMMENT_REGEX = "(?is)(/\\*[^*]*\\*+([^/][^*]*\\*+)*/)";
    private static final String COMMENT_REGEX = "(?is)(/\\*.*?\\*/)";
  
    // Captures CSS strings
    // Prefer possessive quantifier (*+) instead of lazy one (*?) to avoid infinite recursion
    private static final String QUOTED_CONTENT_REGEX = "(?s)(([\"'])(?!data:|(\\s*\\)))(?:\\\\?+.)*+\\2)";
  
    // A placeholder string to replace and restore CSS strings
    private static final String STRING_PLACEHOLDER = "______'JAWR_STRING'______";
  
    // Captured CSS rules (requires replacing CSS strings with a placeholder, or
    // quoted braces will fool it.
    private static final String RULES_REGEX = "([^\\{\\}]*)(\\{[^\\{\\}]*})";
  
    // This regex captures, in order:
    // (\\s*\\{\\s*)|(\\s*\\}\\s*)|(\\s*\\(\\s*)|(\\s*;\\s*)|(\\s*\\))
    // brackets, parentheses,colons and semicolons and any spaces around them
    // (except spaces AFTER a parentheses closing
    // symbol),
    // and ( +) occurrences of one or more spaces.
    /**
     * There is a special case when the space should not be removed when preceeded by and keyword. Ex: <code>
     *
     * @media only screen and (max-width:767px){ } </code>
     */
    private static final String SPACES_REGEX = "(?ims)(\\s*\\{\\s*)|(\\s*\\}\\s*)|((?<!\\sand)\\s*\\(\\s*)|(\\s*;\\s*)|(\\s*:\\s*)|(\\s*\\))|( +)";
  
    private static final Pattern COMMENTS_PATTERN = Pattern.compile(COMMENT_REGEX);
  
    private static final Pattern SPACES_PATTERN = Pattern.compile(SPACES_REGEX, Pattern.DOTALL);
  
    private static final Pattern QUOTED_CONTENT_PATTERN = Pattern.compile(QUOTED_CONTENT_REGEX);
    private static final Pattern RULES_PATTERN = Pattern.compile(RULES_REGEX, Pattern.DOTALL);
    private static final Pattern STRING_PLACE_HOLDER_PATTERN = Pattern.compile(STRING_PLACEHOLDER, Pattern.DOTALL);
  
    private static final String SPACE = " ";
    private static final String BRACKET_OPEN = "{";
    private static final String BRACKET_CLOSE = "}";
    private static final String PAREN_OPEN = "(";
    private static final String PAREN_CLOSE = ")";
  
    private static final String COLON = ":";
    private static final String SEMICOLON = ";";
  
    /**
     * Template class to abstract the pattern of iterating over a Matcher and performing string replacement.
     */
    public abstract class MatcherProcessorCallback {
      String processWithMatcher(final Matcher matcher) {
        final StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
          matcher.appendReplacement(sb, adaptReplacementToMatcher(matchCallback(matcher)));
        }
        matcher.appendTail(sb);
        return sb.toString();
      }
  
      abstract String matchCallback(Matcher matcher);
    }
  
    /**
     * @param data
     *          CSS to minify
     * @return StringBuffer Minified CSS.
     */
    public StringBuffer minifyCSS(final StringBuffer data) {
      // Remove comments and carriage returns
      String compressed = COMMENTS_PATTERN.matcher(data.toString()).replaceAll("");
  
      // Temporarily replace the strings with a placeholder
      final List<String> strings = new ArrayList<String>();
      final Matcher stringMatcher = QUOTED_CONTENT_PATTERN.matcher(compressed);
  
      compressed = new MatcherProcessorCallback() {
        @Override
        String matchCallback(final Matcher matcher) {
          final String match = matcher.group();
          strings.add(match);
          return STRING_PLACEHOLDER;
        }
      }.processWithMatcher(stringMatcher);
  
      // Grab all rules and replace whitespace in selectors
      final Matcher rulesmatcher = RULES_PATTERN.matcher(compressed);
      compressed = new MatcherProcessorCallback() {
        @Override
        String matchCallback(final Matcher matcher) {
          return matcher.group(1) + matcher.group(2);
        }
      }.processWithMatcher(rulesmatcher);
  
      /**
       * At this point original minifier replaced the new lines with empty space. This was removed, since it causes issues
       * related to "constant string too long". More details {@see http://code.google.com/p/wro4j/issues/detail?id=603}
       */
  
      // Match all empty space we can minify
      final Matcher matcher = SPACES_PATTERN.matcher(compressed);
      compressed = new MatcherProcessorCallback() {
        @Override
        String matchCallback(final Matcher matcher) {
          String replacement = SPACE;
          final String match = matcher.group();
  
          if (match.indexOf(BRACKET_OPEN) != -1) {
            replacement = BRACKET_OPEN;
          } else if (match.indexOf(BRACKET_CLOSE) != -1) {
            replacement = BRACKET_CLOSE;
          } else if (match.indexOf(PAREN_OPEN) != -1) {
            replacement = PAREN_OPEN;
          } else if (match.indexOf(COLON) != -1) {
            replacement = COLON;
          } else if (match.indexOf(SEMICOLON) != -1) {
            replacement = SEMICOLON;
          } else if (match.indexOf(PAREN_CLOSE) != -1) {
            replacement = PAREN_CLOSE;
          }
  
          return replacement;
        }
      }.processWithMatcher(matcher);
  
      // Restore all Strings
      final Matcher restoreMatcher = STRING_PLACE_HOLDER_PATTERN.matcher(compressed);
      final Iterator<String> it = strings.iterator();
      compressed = new MatcherProcessorCallback() {
        @Override
        String matchCallback(final Matcher matcher) {
          final String replacement = it.next();
          return replacement;
        }
      }.processWithMatcher(restoreMatcher);
  
      return new StringBuffer(compressed);
    }
  
    /**
     * Fixes a bad problem with regular expression replacement strings. Replaces \ and $ for escaped versions for regex
     * replacement. This was somehow fixed in java 5 (a similar method was added). Since Jawr supports 1.4, this method is
     * used instead.
     */
    private static String adaptReplacementToMatcher(final String replacement) {
      // Double the backslashes, so they are left as they are after replacement.
      String result = replacement.replaceAll("\\\\", "\\\\\\\\");
      // Add backslashes after dollar signs
      result = result.replaceAll("\\$", "\\\\\\$");
      return result;
    }
  }
  