Improve UiBinder+CssResource compile speed by caching results of GenerateCssAst.exec()

Patch by: bobv
Review by: rjrjr

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@6504 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/resources/css/GenerateCssAst.java b/user/src/com/google/gwt/resources/css/GenerateCssAst.java
index 9ddb439..8336034 100644
--- a/user/src/com/google/gwt/resources/css/GenerateCssAst.java
+++ b/user/src/com/google/gwt/resources/css/GenerateCssAst.java
@@ -68,11 +68,13 @@
 
 import java.io.IOException;
 import java.io.StringReader;
+import java.lang.ref.SoftReference;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.net.URISyntaxException;
 import java.net.URL;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -641,7 +643,14 @@
   }
 
   private static final String LITERAL_FUNCTION_NAME = "literal";
-
+  /**
+   * We cache the stylesheets to prevent repeated parsing of the same source
+   * material. This is a common case if the user is using UiBinder's implicit
+   * stylesheets. It is necessary to use the list of URLs passed to exec because
+   * of the eager variable expansion performed by
+   * {@link GenerationHandler#parseDef(String)}.
+   */
+  private static final Map<List<URL>, SoftReference<CssStylesheet>> SHEETS = new HashMap<List<URL>, SoftReference<CssStylesheet>>();
   private static final String VALUE_FUNCTION_NAME = "value";
 
   /**
@@ -651,13 +660,22 @@
    */
   public static CssStylesheet exec(TreeLogger logger, URL... stylesheets)
       throws UnableToCompleteException {
+
+    List<URL> sheets = Arrays.asList(stylesheets);
+    SoftReference<CssStylesheet> ref = SHEETS.get(sheets);
+    CssStylesheet toReturn = ref == null ? null : ref.get();
+    if (toReturn != null) {
+      logger.log(TreeLogger.DEBUG, "Using cached result");
+      return new CssStylesheet(toReturn);
+    }
+
     Parser p = new Parser();
     Errors errors = new Errors(logger);
     GenerationHandler g = new GenerationHandler(errors);
     p.setDocumentHandler(g);
     p.setErrorHandler(errors);
 
-    for (URL stylesheet : stylesheets) {
+    for (URL stylesheet : sheets) {
       TreeLogger branchLogger = logger.branch(TreeLogger.DEBUG,
           "Parsing CSS stylesheet " + stylesheet.toExternalForm());
       try {
@@ -678,7 +696,10 @@
       throw new UnableToCompleteException();
     }
 
-    return g.css;
+    toReturn = g.css;
+    SHEETS.put(new ArrayList<URL>(sheets), new SoftReference<CssStylesheet>(
+        new CssStylesheet(toReturn)));
+    return toReturn;
   }
 
   /**
diff --git a/user/src/com/google/gwt/resources/css/ast/CssStylesheet.java b/user/src/com/google/gwt/resources/css/ast/CssStylesheet.java
index 743d54e..f9948c3 100644
--- a/user/src/com/google/gwt/resources/css/ast/CssStylesheet.java
+++ b/user/src/com/google/gwt/resources/css/ast/CssStylesheet.java
@@ -19,11 +19,30 @@
 import java.util.List;
 
 /**
- * 
+ * An abstract representation of a CSS stylesheet.
  */
 public class CssStylesheet extends CssNode implements HasNodes {
   private List<CssNode> rules = new ArrayList<CssNode>();
 
+  public CssStylesheet() {
+  }
+
+  /**
+   * A copy constructor that will clone the contents of an existing
+   * CssStylesheet.
+   */
+  public CssStylesheet(CssStylesheet other) {
+    append(other);
+  }
+
+  /**
+   * Append the given stylesheet. The contents of the other stylesheet will be
+   * cloned.
+   */
+  public void append(CssStylesheet other) {
+    rules.addAll(CssNodeCloner.clone(CssNode.class, other.rules));
+  }
+
   public List<CssNode> getNodes() {
     return rules;
   }
diff --git a/user/src/com/google/gwt/resources/rebind/context/AbstractClientBundleGenerator.java b/user/src/com/google/gwt/resources/rebind/context/AbstractClientBundleGenerator.java
index d668a59..0b13be3 100644
--- a/user/src/com/google/gwt/resources/rebind/context/AbstractClientBundleGenerator.java
+++ b/user/src/com/google/gwt/resources/rebind/context/AbstractClientBundleGenerator.java
@@ -186,28 +186,28 @@
      */
     String generatedSimpleSourceName = generateSimpleSourceName(logger,
         resourceContext, requirements);
+    String packageName = sourceType.getPackage().getName();
+    String createdClassName = packageName + "." + generatedSimpleSourceName;
 
-    // Begin writing the generated source.
-    ClassSourceFileComposerFactory f = new ClassSourceFileComposerFactory(
-        sourceType.getPackage().getName(), generatedSimpleSourceName);
-
-    // The generated class needs to be able to determine the module base URL
-    f.addImport(GWT.class.getName());
-
-    // Used by the map methods
-    f.addImport(ResourcePrototype.class.getName());
-
-    // The whole point of this exercise
-    f.addImplementedInterface(sourceType.getQualifiedSourceName());
-
-    String createdClassName = f.getCreatedClassName();
-
-    // All source gets written through this Writer
-    PrintWriter out = generatorContext.tryCreate(logger,
-        sourceType.getPackage().getName(), generatedSimpleSourceName);
+    PrintWriter out = generatorContext.tryCreate(logger, packageName,
+        generatedSimpleSourceName);
 
     // If an implementation already exists, we don't need to do any work
     if (out != null) {
+      // Begin writing the generated source.
+      ClassSourceFileComposerFactory f = new ClassSourceFileComposerFactory(
+          packageName, generatedSimpleSourceName);
+
+      // The generated class needs to be able to determine the module base URL
+      f.addImport(GWT.class.getName());
+
+      // Used by the map methods
+      f.addImport(ResourcePrototype.class.getName());
+
+      // The whole point of this exercise
+      f.addImplementedInterface(sourceType.getQualifiedSourceName());
+
+      // All source gets written through this Writer
       SourceWriter sw = f.createSourceWriter(generatorContext, out);
 
       // Set the now-calculated simple source name