Revision: 6218 Author: b...@google.com Date: Fri Sep 25 12:26:41 2009 Log: Speed CssResource class selector rewriting.
Patch by: amirkashani, bobv Review by: bobv, amirkashani http://code.google.com/p/google-web-toolkit/source/detail?r=6218 Modified: /trunk/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java ======================================= --- /trunk/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java Wed Sep 9 10:35:52 2009 +++ /trunk/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java Fri Sep 25 12:26:41 2009 @@ -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(); - - // 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(); - - ClassName className = method.getAnnotation(ClassName.class); - if (className != null) { - sourceClassName = className.value(); - } - - sourceClassName = prefix + 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); - } - - missingClasses.remove(method); - if (strict) { - replacedClasses.add(obfuscatedClassName); - } - } - } - } - - 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); - } - } + int originalLength = sel.length(); + + Matcher ma = CssSelector.CLASS_SELECTOR_PATTERN.matcher(sel); + StringBuilder sb = new StringBuilder(originalLength); + int start = 0; + + while (ma.find()) { + String sourceClassName = ma.group(1); + + Replacement entry = potentialReplacements.get(sourceClassName); + + if (entry == null) { + unknownClasses.add(sourceClassName); + continue; + + } 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); } - x.setSelector(sel); + if (start != 0) { + // Consume the remainder and update the selector + sb.append(sel.subSequence(start, originalLength)); + x.setSelector(sb.toString()); + } } @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 --~--~---------~--~----~------------~-------~--~----~ http://groups.google.com/group/Google-Web-Toolkit-Contributors -~----------~----~----~----~------~----~------~--~---