Add tail-globs to CssResource's @external declarations.
http://gwt-code-reviews.appspot.com/243803
Suggested by: gak
Patch by: bobv
Review by: rjrjr


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7774 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/resources/css/ExternalClassesCollector.java b/user/src/com/google/gwt/resources/css/ExternalClassesCollector.java
index d6cb7bf..7a15f5e 100644
--- a/user/src/com/google/gwt/resources/css/ExternalClassesCollector.java
+++ b/user/src/com/google/gwt/resources/css/ExternalClassesCollector.java
@@ -17,23 +17,73 @@
 
 import com.google.gwt.resources.css.ast.Context;
 import com.google.gwt.resources.css.ast.CssExternalSelectors;
+import com.google.gwt.resources.css.ast.CssSelector;
 import com.google.gwt.resources.css.ast.CssVisitor;
 
 import java.util.HashSet;
 import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.regex.Matcher;
 
 /**
- * Collects all {@code @external} declarations in the stylesheet.
+ * Collects all {@code @external} declarations in the stylesheet. This visitor
+ * will expand tail-globs.
  */
 public class ExternalClassesCollector extends CssVisitor {
-  private final Set<String> classes = new HashSet<String>();
+  public static final String GLOB_STRING = "*";
+
+  private final SortedSet<String> allClasses = new TreeSet<String>();
+  private final SortedSet<String> externalClasses = new TreeSet<String>();
+  private final Set<String> globs = new HashSet<String>();
+
+  /**
+   * This is a short-circuit for <code>{@literal @external} *</code>.
+   */
+  private boolean matchAll;
 
   @Override
   public void endVisit(CssExternalSelectors x, Context ctx) {
-    classes.addAll(x.getClasses());
+    if (matchAll) {
+      return;
+    }
+
+    for (String selector : x.getClasses()) {
+      if (selector.equals(GLOB_STRING)) {
+        matchAll = true;
+        return;
+      } else if (selector.endsWith(GLOB_STRING)) {
+        globs.add(selector.substring(0, selector.length() - 1));
+      } else {
+        externalClasses.add(selector);
+      }
+    }
   }
 
-  public Set<String> getClasses() {
-    return classes;
+  @Override
+  public void endVisit(CssSelector x, Context ctx) {
+    Matcher m = CssSelector.CLASS_SELECTOR_PATTERN.matcher(x.getSelector());
+
+    while (m.find()) {
+      allClasses.add(m.group(1));
+    }
   }
-}
\ No newline at end of file
+
+  public SortedSet<String> getClasses() {
+    if (matchAll) {
+      return allClasses;
+    }
+
+    glob : for (String glob : globs) {
+      for (String clazz : allClasses.tailSet(glob)) {
+        if (clazz.startsWith(glob)) {
+          externalClasses.add(clazz);
+        } else {
+          continue glob;
+        }
+      }
+    }
+
+    return externalClasses;
+  }
+}
diff --git a/user/test/com/google/gwt/resources/ResourcesSuite.java b/user/test/com/google/gwt/resources/ResourcesSuite.java
index 7ab12b6..35ecb35 100644
--- a/user/test/com/google/gwt/resources/ResourcesSuite.java
+++ b/user/test/com/google/gwt/resources/ResourcesSuite.java
@@ -21,6 +21,7 @@
 import com.google.gwt.resources.client.ImageResourceTest;
 import com.google.gwt.resources.client.NestedBundleTest;
 import com.google.gwt.resources.client.TextResourceTest;
+import com.google.gwt.resources.css.CssExternalTest;
 import com.google.gwt.resources.css.CssNodeClonerTest;
 import com.google.gwt.resources.css.CssReorderTest;
 import com.google.gwt.resources.css.CssRtlTest;
@@ -37,6 +38,7 @@
   public static Test suite() {
 
     GWTTestSuite suite = new GWTTestSuite("Test for com.google.gwt.resources");
+    suite.addTestSuite(CssExternalTest.class);
     suite.addTestSuite(CSSResourceTest.class);
     suite.addTestSuite(CssReorderTest.class);
     suite.addTestSuite(CssRtlTest.class);
diff --git a/user/test/com/google/gwt/resources/css/CssExternalTest.java b/user/test/com/google/gwt/resources/css/CssExternalTest.java
new file mode 100644
index 0000000..8e76008
--- /dev/null
+++ b/user/test/com/google/gwt/resources/css/CssExternalTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * 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 com.google.gwt.resources.css;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.resources.css.ast.CssStylesheet;
+import com.google.gwt.resources.rg.CssTestCase;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Tests {@link ExternalClassesCollector}.
+ */
+public class CssExternalTest extends CssTestCase {
+
+  public void testExternal() throws UnableToCompleteException {
+    CssStylesheet sheet = GenerateCssAst.exec(TreeLogger.NULL,
+        getClass().getClassLoader().getResource(
+            "com/google/gwt/resources/css/external.css"));
+    assertNotNull(sheet);
+
+    ExternalClassesCollector v = new ExternalClassesCollector();
+    v.accept(sheet);
+
+    assertEquals(Arrays.asList("a", "b", "c", "glob-a", "glob-b", "no*effect"),
+        new ArrayList<String>(v.getClasses()));
+  }
+
+  /**
+   * Make sure the short-circuit logic for <code>{@literal @external} *</code> works correctly.
+   */
+  public void testExternalStar() throws UnableToCompleteException {
+    CssStylesheet sheet = GenerateCssAst.exec(TreeLogger.NULL,
+        getClass().getClassLoader().getResource(
+            "com/google/gwt/resources/css/external_star.css"),
+        getClass().getClassLoader().getResource(
+            "com/google/gwt/resources/css/external.css"));
+    assertNotNull(sheet);
+
+    ExternalClassesCollector v = new ExternalClassesCollector();
+    v.accept(sheet);
+
+    assertEquals(Arrays.asList("a", "c", "d", "glob-a", "glob-b", "no-effect"),
+        new ArrayList<String>(v.getClasses()));
+  }
+}
diff --git a/user/test/com/google/gwt/resources/css/external.css b/user/test/com/google/gwt/resources/css/external.css
new file mode 100644
index 0000000..35a3528
--- /dev/null
+++ b/user/test/com/google/gwt/resources/css/external.css
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * 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.
+ */
+
+/* Always collected */
+@external a .b, c;
+
+.a{}
+/* Ignore b */
+.c:content{}
+/* d should also be ignored */
+.d{}
+
+/* Will be expanded */
+@external glob-*;
+.glob-a {}
+.glob-b {}
+
+/* No effect */
+@external nothing-*;
+
+/* Internal stars don't do anything */
+@external no*effect;
+.no-effect {}
diff --git a/user/test/com/google/gwt/resources/css/external_star.css b/user/test/com/google/gwt/resources/css/external_star.css
new file mode 100644
index 0000000..696a71d
--- /dev/null
+++ b/user/test/com/google/gwt/resources/css/external_star.css
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * 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.
+ */
+ 
+ /* Collect all */
+@external *;