Speed CssResource class selector rewriting.
Patch by: amirkashani, bobv
Review by: bobv, amirkashani
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@6218 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java b/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java
index 8295ad6..adbf29c 100644
--- a/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java
+++ b/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java
@@ -1,12 +1,12 @@
/*
* Copyright 2008 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
@@ -97,7 +97,6 @@
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.regex.Matcher;
-import java.util.regex.Pattern;
import java.util.zip.Adler32;
/**
@@ -105,21 +104,59 @@
*/
public final class CssResourceGenerator extends AbstractResourceGenerator {
static class ClassRenamer extends CssVisitor {
- private final Map<JMethod, String> actualReplacements = new IdentityHashMap<JMethod, String>();
/**
- * This is a map of local prefixes to the obfuscated names of imported
- * methods. If a CssResource makes use of the {@link Import} annotation, the
- * keys of this map will correspond to the {@link ImportedWithPrefix} value
- * defined on the imported CssResource. The zero-length string key holds the
- * obfuscated names for the CssResource that is being generated.
+ * A tag to indicate that an externally-defined CSS class has no JMethod
+ * that is used to access it.
*/
- private final Map<String, Map<JMethod, String>> classReplacementsWithPrefix;
+ private static final Replacement UNREFERENCED_EXTERNAL = new Replacement(
+ null, null);
+
+ /*
+ * TODO: Replace with Pair<A, B>.
+ */
+ private static class Replacement {
+
+ private JMethod method;
+ private String obfuscatedClassName;
+
+ public Replacement(JMethod method, String obfuscatedClassName) {
+ this.method = method;
+ this.obfuscatedClassName = obfuscatedClassName;
+ }
+
+ public JMethod getMethod() {
+ return method;
+ }
+
+ public String getObfuscatedClassName() {
+ return obfuscatedClassName;
+ }
+
+ /**
+ * For debugging use only.
+ */
+ public String toString() {
+ if (this == UNREFERENCED_EXTERNAL) {
+ return "Unreferenced external class name";
+ } else {
+ return method.getName() + "=" + obfuscatedClassName;
+ }
+ }
+ }
+
+ /**
+ * Records replacements that have actually been performed.
+ */
+ private final Map<JMethod, String> actualReplacements = new IdentityHashMap<JMethod, String>();
private final Set<String> cssDefs = new HashSet<String>();
- private final Set<String> externalClasses;
+
+ /**
+ * The task-list of replacements to perform in the stylesheet.
+ */
+ private final Map<String, Replacement> potentialReplacements;
private final TreeLogger logger;
private final Set<JMethod> missingClasses;
- private final Set<String> replacedClasses = new HashSet<String>();
private final boolean strict;
private final Set<String> unknownClasses = new HashSet<String>();
@@ -127,9 +164,10 @@
Map<String, Map<JMethod, String>> classReplacementsWithPrefix,
boolean strict, Set<String> externalClasses) {
this.logger = logger.branch(TreeLogger.DEBUG, "Replacing CSS class names");
- this.classReplacementsWithPrefix = classReplacementsWithPrefix;
this.strict = strict;
- this.externalClasses = externalClasses;
+
+ potentialReplacements = computeReplacements(classReplacementsWithPrefix,
+ externalClasses);
// Require a definition for all classes in the default namespace
assert classReplacementsWithPrefix.containsKey("");
@@ -144,56 +182,45 @@
@Override
public void endVisit(CssSelector x, Context ctx) {
+
String sel = x.getSelector();
+ int originalLength = sel.length();
- // TODO This would be simplified by having a class hierarchy for selectors
- for (Map.Entry<String, Map<JMethod, String>> outerEntry : classReplacementsWithPrefix.entrySet()) {
- String prefix = outerEntry.getKey();
- for (Map.Entry<JMethod, String> entry : outerEntry.getValue().entrySet()) {
- JMethod method = entry.getKey();
- String sourceClassName = method.getName();
- String obfuscatedClassName = entry.getValue();
+ Matcher ma = CssSelector.CLASS_SELECTOR_PATTERN.matcher(sel);
+ StringBuilder sb = new StringBuilder(originalLength);
+ int start = 0;
- ClassName className = method.getAnnotation(ClassName.class);
- if (className != null) {
- sourceClassName = className.value();
- }
+ while (ma.find()) {
+ String sourceClassName = ma.group(1);
- sourceClassName = prefix + sourceClassName;
+ Replacement entry = potentialReplacements.get(sourceClassName);
- Pattern p = Pattern.compile("(.*)\\.("
- + Pattern.quote(sourceClassName) + ")([ :>+#.].*|$)");
- Matcher m = p.matcher(sel);
- if (m.find()) {
- if (externalClasses.contains(sourceClassName)) {
- actualReplacements.put(method, sourceClassName);
- } else {
- sel = m.group(1) + "." + obfuscatedClassName + m.group(3);
- actualReplacements.put(method, obfuscatedClassName);
- }
+ if (entry == null) {
+ unknownClasses.add(sourceClassName);
+ continue;
- missingClasses.remove(method);
- if (strict) {
- replacedClasses.add(obfuscatedClassName);
- }
- }
+ } else if (entry == UNREFERENCED_EXTERNAL) {
+ // An @external without an accessor method. This is OK.
+ continue;
}
+
+ JMethod method = entry.getMethod();
+ String obfuscatedClassName = entry.getObfuscatedClassName();
+
+ // Consume the interstitial portion of the original selector
+ sb.append(sel.subSequence(start, ma.start(1)));
+ sb.append(obfuscatedClassName);
+ start = ma.end(1);
+
+ actualReplacements.put(method, obfuscatedClassName);
+ missingClasses.remove(method);
}
- sel = sel.trim();
-
- if (strict) {
- Matcher m = CssSelector.CLASS_SELECTOR_PATTERN.matcher(sel);
- while (m.find()) {
- String classSelector = m.group(1);
- if (!replacedClasses.contains(classSelector)
- && !externalClasses.contains(classSelector)) {
- unknownClasses.add(classSelector);
- }
- }
+ if (start != 0) {
+ // Consume the remainder and update the selector
+ sb.append(sel.subSequence(start, originalLength));
+ x.setSelector(sb.toString());
}
-
- x.setSelector(sel);
}
@Override
@@ -245,9 +272,66 @@
}
}
+ /**
+ * Reports the replacements that were actually performed by this visitor.
+ */
public Map<JMethod, String> getReplacements() {
return actualReplacements;
}
+
+ /**
+ * Flatten class name lookups to speed selector rewriting.
+ *
+ * @param classReplacementsWithPrefix a map of local prefixes to the
+ * obfuscated names of imported methods. If a CssResource makes use
+ * of the {@link Import} annotation, the keys of this map will
+ * correspond to the {@link ImportedWithPrefix} value defined on
+ * the imported CssResource. The zero-length string key holds the
+ * obfuscated names for the CssResource that is being generated.
+ * @return A flattened version of the classReplacementWithPrefix map, where
+ * the keys are the source class name (with prefix included), and
+ * values have the obfuscated class name and associated JMethod.
+ */
+ private Map<String, Replacement> computeReplacements(
+ Map<String, Map<JMethod, String>> classReplacementsWithPrefix,
+ Set<String> externalClasses) {
+
+ Map<String, Replacement> toReturn = new HashMap<String, Replacement>();
+
+ for (String externalClass : externalClasses) {
+ toReturn.put(externalClass, UNREFERENCED_EXTERNAL);
+ }
+
+ for (Map.Entry<String, Map<JMethod, String>> outerEntry : classReplacementsWithPrefix.entrySet()) {
+ String prefix = outerEntry.getKey();
+
+ for (Map.Entry<JMethod, String> entry : outerEntry.getValue().entrySet()) {
+ JMethod method = entry.getKey();
+ String sourceClassName = method.getName();
+ String obfuscatedClassName = entry.getValue();
+
+ ClassName className = method.getAnnotation(ClassName.class);
+ if (className != null) {
+ sourceClassName = className.value();
+ }
+
+ sourceClassName = prefix + sourceClassName;
+
+ if (externalClasses.contains(sourceClassName)) {
+ /*
+ * It simplifies the sanity-checking logic to treat external classes
+ * as though they were simply obfuscated to exactly the value the
+ * user wants.
+ */
+ obfuscatedClassName = sourceClassName;
+ }
+
+ toReturn.put(sourceClassName, new Replacement(method,
+ obfuscatedClassName));
+ }
+ }
+ return Collections.unmodifiableMap(toReturn);
+ }
}
static class DefsCollector extends CssVisitor {
@@ -1086,7 +1170,7 @@
* Very large concatenation expressions using '+' cause the GWT compiler to
* overflow the stack due to deep AST nesting. The workaround for now is to
* force it to be more balanced using intermediate concatenation groupings.
- *
+ *
* This variable is used to track the number of subexpressions within the
* current parenthetical expression.
*/
@@ -1159,7 +1243,7 @@
/**
* Check if number of concat expressions currently exceeds limit and either
* append '+' if the limit isn't reached or ') + (' if it is.
- *
+ *
* @return numExpressions + 1 or 0 if limit was exceeded.
*/
private static int concatOp(int numExpressions, StringBuilder b) {
@@ -1439,7 +1523,8 @@
name = classNameOverride.value();
}
- String obfuscatedClassName = classPrefix + makeIdent(classCounter.next());
+ String obfuscatedClassName = classPrefix
+ + makeIdent(classCounter.next());
if (prettyOutput) {
obfuscatedClassName += "-"
+ type.getQualifiedSourceName().replaceAll("[.$]", "-") + "-"
@@ -1571,7 +1656,7 @@
* result regardless of the order in which the generators fired. (It no
* longer behaves that way, as that scheme prevented the generation of new
* CssResource interfaces, but the complexity lives on.)
- *
+ *
* TODO(rjrjr,bobv) These days scottb tells us we're guaranteed that the
* recompiling the same code will fire the generators in a consistent order,
* so the old gymnastics aren't really justified anyway. It would probably
@@ -1657,7 +1742,7 @@
/**
* Create a Java expression that evaluates to the string representation of the
* stylesheet resource.
- *
+ *
* @param actualReplacements An out parameter that will be populated by the
* obfuscated class names that should be used for the particular
* instance of the CssResource, based on any substitution