Add CSS3 and GSS support in InterfaceGenerator

Change-Id: Ifecfec053c851582b3d1c6a06f721c289aa3c5b6
Bug-Link: https://github.com/gwtproject/gwt/issues/9105
diff --git a/user/src/com/google/gwt/resources/css/InterfaceGenerator.java b/user/src/com/google/gwt/resources/css/InterfaceGenerator.java
index 430e591..434cdb3 100644
--- a/user/src/com/google/gwt/resources/css/InterfaceGenerator.java
+++ b/user/src/com/google/gwt/resources/css/InterfaceGenerator.java
@@ -21,7 +21,20 @@
 import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
 import com.google.gwt.resources.client.CssResource;
 import com.google.gwt.resources.client.CssResource.ClassName;
-import com.google.gwt.resources.css.ast.CssStylesheet;
+import com.google.gwt.resources.gss.ClassNamesCollector;
+import com.google.gwt.resources.rg.GssResourceGenerator;
+import com.google.gwt.resources.rg.GssResourceGenerator.LoggerErrorManager;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssTree;
+import com.google.gwt.thirdparty.common.css.compiler.ast.GssParser;
+import com.google.gwt.thirdparty.common.css.compiler.ast.GssParserException;
+import com.google.gwt.thirdparty.common.css.compiler.passes.CollectConstantDefinitions;
+import com.google.gwt.thirdparty.common.css.compiler.passes.CreateDefinitionNodes;
+import com.google.gwt.thirdparty.common.css.compiler.passes.CreateForLoopNodes;
+import com.google.gwt.thirdparty.common.css.compiler.passes.UnrollLoops;
+import com.google.gwt.thirdparty.guava.common.base.CaseFormat;
+import com.google.gwt.thirdparty.guava.common.base.Function;
+import com.google.gwt.thirdparty.guava.common.collect.Iterables;
+import com.google.gwt.thirdparty.guava.common.collect.Lists;
 import com.google.gwt.user.rebind.SourceWriter;
 import com.google.gwt.user.rebind.StringSourceWriter;
 import com.google.gwt.util.tools.ArgHandlerFile;
@@ -32,6 +45,8 @@
 import java.io.File;
 import java.io.PrintWriter;
 import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Collection;
 import java.util.Comparator;
 import java.util.HashSet;
 import java.util.Set;
@@ -228,14 +243,19 @@
   private String process() throws MalformedURLException,
       UnableToCompleteException {
     // Create AST
-    CssStylesheet sheet = GenerateCssAst.exec(logger, inputFile.toURI().toURL());
+    CssTree cssTree = createAst(inputFile.toURI().toURL(), logger);
 
     // Sort all names
     Set<String> names = new TreeSet<String>(NAME_COMPARATOR);
-    names.addAll(ExtractClassNamesVisitor.exec(sheet));
-    DefsCollector defs = new DefsCollector();
-    defs.accept(sheet);
-    names.addAll(defs.getDefs());
+
+    names.addAll(new ClassNamesCollector().getClassNames(cssTree));
+
+    CollectConstantDefinitions collectConstantDefinitionsPass = new CollectConstantDefinitions(
+            cssTree);
+    collectConstantDefinitionsPass.runPass();
+    Collection<String> renamedDefs = renameDefs(collectConstantDefinitionsPass
+            .getConstantDefinitions().getConstantsNames());
+    names.addAll(renamedDefs);
 
     // Deduplicate method names
     Set<String> methodNames = new HashSet<String>();
@@ -275,4 +295,40 @@
 
     return sw.toString();
   }
+
+  /**
+   * In GSS, constant names are defined in upper case but a method name to access a constant in
+   * a CssResource interface can be written in lower camel case.
+   * <p>
+   * This method converts all constant names in a lower camel case identifier.
+   */
+  private Collection<String> renameDefs(Iterable<String> constantsNames) {
+    return Lists.newArrayList(Iterables.transform(constantsNames, new Function<String, String>() {
+      @Override
+      public String apply(String constantName) {
+        return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, constantName);
+      }
+    }));
+  }
+
+  private CssTree createAst(URL sourceFileUrl, TreeLogger logger)
+          throws UnableToCompleteException {
+    LoggerErrorManager errorManager = new LoggerErrorManager(logger);
+
+    CssTree cssTree;
+
+    try {
+      cssTree = new GssParser(GssResourceGenerator.readUrlContent(sourceFileUrl, logger)).parse();
+    } catch (GssParserException e) {
+      logger.log(TreeLogger.ERROR, "Unable to parse CSS", e);
+      throw new UnableToCompleteException();
+    }
+
+    new CreateDefinitionNodes(cssTree.getMutatingVisitController(), errorManager).runPass();
+    // Can create new style classes
+    new CreateForLoopNodes(cssTree.getMutatingVisitController(), errorManager).runPass();
+    new UnrollLoops(cssTree.getMutatingVisitController(), errorManager).runPass();
+
+    return cssTree;
+  }
 }
diff --git a/user/src/com/google/gwt/resources/gss/ClassNamesCollector.java b/user/src/com/google/gwt/resources/gss/ClassNamesCollector.java
index 610ef26..ded019e 100644
--- a/user/src/com/google/gwt/resources/gss/ClassNamesCollector.java
+++ b/user/src/com/google/gwt/resources/gss/ClassNamesCollector.java
@@ -35,6 +35,13 @@
   private SortedSet<String> excludedPrefixes;
 
   /**
+   * Extract all CSS class names in the provided stylesheet.
+   */
+  public Set<String> getClassNames(CssTree tree) {
+    return getClassNames(tree, new HashSet<JClassType>());
+  }
+
+  /**
    * Extract all CSS class names in the provided stylesheet, modulo those
    * imported from another context.
    */
diff --git a/user/src/com/google/gwt/resources/rg/GssResourceGenerator.java b/user/src/com/google/gwt/resources/rg/GssResourceGenerator.java
index bc853a8..b00b2eb 100644
--- a/user/src/com/google/gwt/resources/rg/GssResourceGenerator.java
+++ b/user/src/com/google/gwt/resources/rg/GssResourceGenerator.java
@@ -220,6 +220,43 @@
     }
   }
 
+  public static SourceCode readUrlContent(URL fileUrl, TreeLogger logger) throws UnableToCompleteException {
+    TreeLogger branchLogger = logger.branch(TreeLogger.DEBUG,
+            "Reading GSS stylesheet " + fileUrl.toExternalForm());
+    try {
+      ByteSource byteSource = Resources.asByteSource(fileUrl);
+      // default charset
+      Charset charset = Charsets.UTF_8;
+
+      // check if the stylesheet doesn't include a @charset at-rule
+      String styleSheetCharset = extractCharset(byteSource);
+      if (styleSheetCharset != null) {
+        try {
+          charset = Charset.forName(styleSheetCharset);
+        } catch (UnsupportedCharsetException e) {
+          logger.log(Type.ERROR, "Unsupported charset found: " + styleSheetCharset);
+          throw new UnableToCompleteException();
+        }
+      }
+
+      String fileContent = byteSource.asCharSource(charset).read();
+      // If the stylesheet specified a charset, we have to remove the at-rule otherwise the GSS
+      // compiler will fail.
+      if (styleSheetCharset != null) {
+        int charsetAtRuleLength = CHARSET_MIN_LENGTH + styleSheetCharset.length();
+        // replace charset at-rule by blanks to keep correct source location of the rest of
+        // the stylesheet.
+        fileContent = Strings.repeat(" ", charsetAtRuleLength) +
+                fileContent.substring(charsetAtRuleLength);
+      }
+      return new SourceCode(fileUrl.getFile(), fileContent);
+
+    } catch (IOException e) {
+      branchLogger.log(TreeLogger.ERROR, "Unable to parse CSS", e);
+    }
+    throw new UnableToCompleteException();
+  }
+
   public static GssOptions getGssOptions(PropertyOracle propertyOracle, TreeLogger logger) throws UnableToCompleteException {
     boolean gssEnabled;
     boolean gssDefaultInUiBinder;
@@ -267,11 +304,11 @@
    * {@link ErrorManager} used to log the errors and warning messages produced by the different
    * {@link com.google.gwt.thirdparty.common.css.compiler.ast.CssCompilerPass}.
    */
-  private static class LoggerErrorManager implements ErrorManager {
+  public static class LoggerErrorManager implements ErrorManager {
     private final TreeLogger logger;
     private boolean hasErrors;
 
-    private LoggerErrorManager(TreeLogger logger) {
+    public LoggerErrorManager(TreeLogger logger) {
       this.logger = logger;
     }
 
@@ -1048,42 +1085,7 @@
       constantNameMappingBuilder.putAll(result.defNameMapping);
     } else {
       for (URL stylesheet : resources) {
-        TreeLogger branchLogger = logger.branch(TreeLogger.DEBUG,
-            "Parsing GSS stylesheet " + stylesheet.toExternalForm());
-        try {
-          ByteSource byteSource = Resources.asByteSource(stylesheet);
-          // default charset
-          Charset charset = Charsets.UTF_8;
-
-          // check if the stylesheet doesn't include a @charset at-rule
-          String styleSheetCharset = extractCharset(byteSource);
-          if (styleSheetCharset != null) {
-            try {
-              charset = Charset.forName(styleSheetCharset);
-            } catch (UnsupportedCharsetException e) {
-              logger.log(Type.ERROR, "Unsupported charset found: " + styleSheetCharset);
-              throw new UnableToCompleteException();
-            }
-          }
-
-          String fileContent = byteSource.asCharSource(charset).read();
-          // If the stylesheet specified a charset, we have to remove the at-rule otherwise the GSS
-          // compiler will fail.
-          if (styleSheetCharset != null) {
-            int charsetAtRuleLength = CHARSET_MIN_LENGTH + styleSheetCharset.length();
-            // replace charset at-rule by blanks to keep correct source location of the rest of
-            // the stylesheet.
-            fileContent = Strings.repeat(" ", charsetAtRuleLength) +
-                fileContent.substring(charsetAtRuleLength);
-          }
-
-          sourceCodes.add(new SourceCode(stylesheet.getFile(), fileContent));
-          continue;
-
-        } catch (IOException e) {
-          branchLogger.log(TreeLogger.ERROR, "Unable to parse CSS", e);
-        }
-        throw new UnableToCompleteException();
+          sourceCodes.add(readUrlContent(stylesheet, logger));
       }
     }
 
@@ -1115,7 +1117,7 @@
         booleanConditionCollector.getBooleanConditions(), constantNameMappingBuilder.build());
   }
 
-  private String extractCharset(ByteSource byteSource) throws IOException {
+  private static String extractCharset(ByteSource byteSource) throws IOException {
     String firstLine = byteSource.asCharSource(Charsets.UTF_8).readFirstLine();
 
     if (firstLine != null) {