Add soft permutations to the GWT compiler. http://gwt-code-reviews.appspot.com/160801/show Patch by: bobv Review by: scottb, spoon git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7790 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/CompilationResult.java b/dev/core/src/com/google/gwt/core/ext/linker/CompilationResult.java index cd0d96a..a83a59a 100644 --- a/dev/core/src/com/google/gwt/core/ext/linker/CompilationResult.java +++ b/dev/core/src/com/google/gwt/core/ext/linker/CompilationResult.java
@@ -57,6 +57,12 @@ public abstract SortedSet<SortedMap<SelectionProperty, String>> getPropertyMap(); /** + * Returns the permutations of the collapsed deferred-binding property values + * that are compiled into the CompilationResult. + */ + public abstract SoftPermutation[] getSoftPermutations(); + + /** * Returns the statement ranges for the JavaScript returned by * {@link #getJavaScript()}. Some subclasses return <code>null</code>, in * which case there is no statement range information available.
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/SoftPermutation.java b/dev/core/src/com/google/gwt/core/ext/linker/SoftPermutation.java new file mode 100644 index 0000000..08c9410 --- /dev/null +++ b/dev/core/src/com/google/gwt/core/ext/linker/SoftPermutation.java
@@ -0,0 +1,41 @@ +/* + * 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.core.ext.linker; + +import java.io.Serializable; +import java.util.SortedMap; + +/** + * Represents a permutation of collapsed deferred-binding property values. + */ +public abstract class SoftPermutation implements Serializable { + + /** + * Returns the soft permutation id that should be passed into + * <code>gwtOnLoad</code>. The range of ids used for a compilation's soft + * permutations may be disjoint and may not correspond to the index of the + * SoftPermutation within the array returned from + * {@link CompilationResult#getSoftPermutations()}. + */ + public abstract int getId(); + + /** + * Returns only the collapsed selection properties that resulted in the + * particular soft permutation. The SelectionProperties used may be disjoint + * from the properties returned by {@link CompilationResult#getPropertyMap()}. + */ + public abstract SortedMap<SelectionProperty, String> getPropertyMap(); +} \ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/SelectionInformation.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/SelectionInformation.java index f0980a6..f00eb51 100644 --- a/dev/core/src/com/google/gwt/core/ext/linker/impl/SelectionInformation.java +++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/SelectionInformation.java
@@ -32,19 +32,27 @@ public class SelectionInformation extends Artifact<SelectionInformation> { private final int hashCode; private final TreeMap<String, String> propMap; + private final int softPermutationId; private final String strongName; - public SelectionInformation(String strongName, TreeMap<String, String> propMap) { + public SelectionInformation(String strongName, int softPermutationId, + TreeMap<String, String> propMap) { super(SelectionScriptLinker.class); this.strongName = strongName; + this.softPermutationId = softPermutationId; this.propMap = propMap; - hashCode = strongName.hashCode() + propMap.hashCode() * 17 + 11; + hashCode = strongName.hashCode() + softPermutationId * 19 + + propMap.hashCode() * 17 + 11; } public TreeMap<String, String> getPropMap() { return propMap; } + public int getSoftPermutationId() { + return softPermutationId; + } + public String getStrongName() { return strongName; } @@ -62,6 +70,11 @@ return cmp; } + cmp = getSoftPermutationId() - o.getSoftPermutationId(); + if (cmp != 0) { + return cmp; + } + // compare the size of the property maps if (getPropMap().size() != o.getPropMap().size()) { return getPropMap().size() - o.getPropMap().size();
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/SelectionScriptLinker.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/SelectionScriptLinker.java index d78ee8d..7a805d0 100644 --- a/dev/core/src/com/google/gwt/core/ext/linker/impl/SelectionScriptLinker.java +++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/SelectionScriptLinker.java
@@ -25,7 +25,9 @@ import com.google.gwt.core.ext.linker.EmittedArtifact; import com.google.gwt.core.ext.linker.ScriptReference; import com.google.gwt.core.ext.linker.SelectionProperty; +import com.google.gwt.core.ext.linker.SoftPermutation; import com.google.gwt.core.ext.linker.StylesheetReference; +import com.google.gwt.dev.util.StringKey; import com.google.gwt.dev.util.Util; import com.google.gwt.dev.util.collect.HashSet; import com.google.gwt.dev.util.collect.Lists; @@ -55,6 +57,30 @@ */ /** + * This represents the combination of a unique content hash (i.e. the MD5 of + * the bytes to be written into the cache.html file) and a soft permutation + * id. + */ + protected static class PermutationId extends StringKey { + private final int softPermutationId; + private final String strongName; + + public PermutationId(String strongName, int softPermutationId) { + super(strongName + ":" + softPermutationId); + this.strongName = strongName; + this.softPermutationId = softPermutationId; + } + + public int getSoftPermutationId() { + return softPermutationId; + } + + public String getStrongName() { + return strongName; + } + } + + /** * The extension added to demand-loaded fragment files. */ protected static final String FRAGMENT_EXTENSION = ".cache.js"; @@ -111,11 +137,11 @@ } /** - * This maps each compilation strong name to the property settings for that + * This maps each unique permutation to the property settings for that * compilation. A single compilation can have multiple property settings if * the compiles for those settings yielded the exact same compiled output. */ - private final SortedMap<String, List<Map<String, String>>> propMapsByStrongName = new TreeMap<String, List<Map<String, String>>>(); + private final SortedMap<PermutationId, List<Map<String, String>>> propMapsByPermutation = new TreeMap<PermutationId, List<Map<String, String>>>(); /** * This method is left in place for existing subclasses of @@ -321,21 +347,21 @@ startPos = selectionScript.indexOf("// __PERMUTATIONS_END__"); if (startPos != -1) { StringBuffer text = new StringBuffer(); - if (propMapsByStrongName.size() == 0) { + if (propMapsByPermutation.size() == 0) { // Hosted mode link. text.append("alert(\"GWT module '" + context.getModuleName() + "' may need to be (re)compiled\");"); text.append("return;"); - } else if (propMapsByStrongName.size() == 1) { + } else if (propMapsByPermutation.size() == 1) { // Just one distinct compilation; no need to evaluate properties text.append("strongName = '" - + propMapsByStrongName.keySet().iterator().next() + "';"); + + propMapsByPermutation.keySet().iterator().next() + "';"); } else { Set<String> propertiesUsed = new HashSet<String>(); - for (String strongName : propMapsByStrongName.keySet()) { - for (Map<String, String> propertyMap : propMapsByStrongName.get(strongName)) { - // unflatten([v1, v2, v3], 'strongName'); + for (PermutationId permutationId : propMapsByPermutation.keySet()) { + for (Map<String, String> propertyMap : propMapsByPermutation.get(permutationId)) { + // unflatten([v1, v2, v3], 'strongName' + ':softPermId'); text.append("unflattenKeylistIntoAnswers(["); boolean needsComma = false; for (SelectionProperty p : context.getProperties()) { @@ -353,7 +379,11 @@ text.append("'" + propertyMap.get(p.getName()) + "'"); propertiesUsed.add(p.getName()); } - text.append("], '").append(strongName).append("');\n"); + + // Concatenate the soft permutation id to improve string interning + text.append("], '").append(permutationId.getStrongName()).append( + "' + ':").append(permutationId.getSoftPermutationId()).append( + "');\n"); } } @@ -457,7 +487,17 @@ for (Map.Entry<SelectionProperty, String> entry : propertyMap.entrySet()) { propMap.put(entry.getKey().getName(), entry.getValue()); } - emitted.add(new SelectionInformation(strongName, propMap)); + + // The soft properties may not be a subset of the existing set + for (SoftPermutation soft : result.getSoftPermutations()) { + // Make a copy we can add add more properties to + TreeMap<String, String> softMap = new TreeMap<String, String>(propMap); + // Make sure this SelectionInformation contains the soft properties + for (Map.Entry<SelectionProperty, String> entry : soft.getPropertyMap().entrySet()) { + softMap.put(entry.getKey().getName(), entry.getValue()); + } + emitted.add(new SelectionInformation(strongName, soft.getId(), softMap)); + } } return emitted; @@ -466,13 +506,14 @@ private Map<String, String> processSelectionInformation( SelectionInformation selInfo) { TreeMap<String, String> entries = selInfo.getPropMap(); - String strongName = selInfo.getStrongName(); - if (!propMapsByStrongName.containsKey(strongName)) { - propMapsByStrongName.put(strongName, + PermutationId permutationId = new PermutationId(selInfo.getStrongName(), + selInfo.getSoftPermutationId()); + if (!propMapsByPermutation.containsKey(permutationId)) { + propMapsByPermutation.put(permutationId, Lists.<Map<String, String>> create(entries)); } else { - propMapsByStrongName.put(strongName, Lists.add( - propMapsByStrongName.get(strongName), entries)); + propMapsByPermutation.put(permutationId, Lists.add( + propMapsByPermutation.get(permutationId), entries)); } return entries; }
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardCompilationResult.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardCompilationResult.java index c8776a0..b3fa907 100644 --- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardCompilationResult.java +++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardCompilationResult.java
@@ -17,16 +17,19 @@ import com.google.gwt.core.ext.linker.CompilationResult; import com.google.gwt.core.ext.linker.SelectionProperty; +import com.google.gwt.core.ext.linker.SoftPermutation; import com.google.gwt.core.ext.linker.StatementRanges; import com.google.gwt.core.ext.linker.SymbolData; import com.google.gwt.dev.jjs.PermutationResult; import com.google.gwt.dev.util.DiskCache; import com.google.gwt.dev.util.Util; +import com.google.gwt.dev.util.collect.Lists; import java.io.Serializable; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.SortedMap; import java.util.SortedSet; @@ -64,6 +67,8 @@ } } + private static final SoftPermutation[] EMPTY_SOFT_PERMUTATION_ARRAY = {}; + /** * Smaller maps come before larger maps, then we compare the concatenation of * every value. @@ -77,6 +82,8 @@ private final SortedSet<SortedMap<SelectionProperty, String>> propertyValues = new TreeSet<SortedMap<SelectionProperty, String>>( MAP_COMPARATOR); + private List<SoftPermutation> softPermutations = Lists.create(); + private final StatementRanges[] statementRanges; private final String strongName; @@ -110,6 +117,11 @@ propertyValues.add(Collections.unmodifiableSortedMap(map)); } + public void addSoftPermutation(Map<SelectionProperty, String> propertyMap) { + softPermutations = Lists.add(softPermutations, new StandardSoftPermutation( + softPermutations.size(), propertyMap)); + } + @Override public String[] getJavaScript() { String[] js = new String[jsToken.length]; @@ -130,6 +142,11 @@ } @Override + public SoftPermutation[] getSoftPermutations() { + return softPermutations.toArray(new SoftPermutation[softPermutations.size()]); + } + + @Override public StatementRanges[] getStatementRanges() { return statementRanges; }
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardSoftPermutation.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardSoftPermutation.java new file mode 100644 index 0000000..3a61569 --- /dev/null +++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardSoftPermutation.java
@@ -0,0 +1,64 @@ +/* + * 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.core.ext.linker.impl; + +import com.google.gwt.core.ext.linker.SelectionProperty; +import com.google.gwt.core.ext.linker.SoftPermutation; + +import java.util.Collections; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +/** + * The standard implementation of {@link SoftPermutation}. + */ +public class StandardSoftPermutation extends SoftPermutation { + private final int id; + private final SortedMap<SelectionProperty, String> propertyMap = new TreeMap<SelectionProperty, String>( + StandardLinkerContext.SELECTION_PROPERTY_COMPARATOR); + + public StandardSoftPermutation(int id, + Map<SelectionProperty, String> propertyMap) { + this.id = id; + this.propertyMap.putAll(propertyMap); + } + + @Override + public int getId() { + return id; + } + + @Override + public SortedMap<SelectionProperty, String> getPropertyMap() { + return Collections.unmodifiableSortedMap(propertyMap); + } + + /** + * For debugging use only. + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("ID ").append(getId()).append(" = {"); + for (Map.Entry<SelectionProperty, String> entry : propertyMap.entrySet()) { + sb.append(" ").append(entry.getKey().getName()).append(":").append( + entry.getValue()); + } + sb.append(" }"); + return sb.toString(); + } +} \ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/core/linker/IFrameTemplate.js b/dev/core/src/com/google/gwt/core/linker/IFrameTemplate.js index d4cb5e1..4da339a 100644 --- a/dev/core/src/com/google/gwt/core/linker/IFrameTemplate.js +++ b/dev/core/src/com/google/gwt/core/linker/IFrameTemplate.js
@@ -42,6 +42,9 @@ // the strong name of the cache.js file to load. ,answers = [] + // Provides the module with the soft permutation id + ,softPermutationId = 0 + // Error functions. Default unset in compiled mode, may be set by meta props. ,onLoadErrorFunc, propertyErrorFunc @@ -98,7 +101,7 @@ // remove this whole function from the global namespace to allow GC __MODULE_FUNC__ = null; // JavaToJavaScriptCompiler logs onModuleLoadStart for each EntryPoint. - frameWnd.gwtOnLoad(onLoadErrorFunc, '__MODULE_NAME__', base); + frameWnd.gwtOnLoad(onLoadErrorFunc, '__MODULE_NAME__', base, softPermutationId); // Record when the module EntryPoints return. $stats && $stats({ moduleName: '__MODULE_NAME__', @@ -267,6 +270,11 @@ // __PERMUTATIONS_BEGIN__ // Permutation logic // __PERMUTATIONS_END__ + var idx = strongName.indexOf(':'); + if (idx != -1) { + softPermutationId = Number(strongName.substring(idx + 1)); + strongName = strongName.substring(0, idx); + } initialHtml = strongName + ".cache.html"; } catch (e) { // intentionally silent on property failure
diff --git a/dev/core/src/com/google/gwt/core/linker/XSTemplate.js b/dev/core/src/com/google/gwt/core/linker/XSTemplate.js index f432d27..5200a69 100644 --- a/dev/core/src/com/google/gwt/core/linker/XSTemplate.js +++ b/dev/core/src/com/google/gwt/core/linker/XSTemplate.js
@@ -41,6 +41,9 @@ // the strong name of the cache.js file to load. ,answers = [] + // Provides the module with the soft permutation id + ,softPermutationId = 0 + // Error functions. Default unset in compiled mode, may be set by meta props. ,onLoadErrorFunc, propertyErrorFunc @@ -82,7 +85,7 @@ function maybeStartModule() { // TODO: it may not be necessary to check gwtOnLoad here. if (gwtOnLoad && bodyDone) { - gwtOnLoad(onLoadErrorFunc, '__MODULE_NAME__', base); + gwtOnLoad(onLoadErrorFunc, '__MODULE_NAME__', base, softPermutationId); // Record when the module EntryPoints return. $stats && $stats({ moduleName: '__MODULE_NAME__', @@ -194,6 +197,11 @@ // __PERMUTATIONS_BEGIN__ // Permutation logic // __PERMUTATIONS_END__ + var idx = strongName.indexOf(':'); + if (idx != -1) { + softPermutationId = Number(strongName.substring(idx + 1)); + strongName = strongName.substring(0, idx); + } } catch (e) { // intentionally silent on property failure return;
diff --git a/dev/core/src/com/google/gwt/dev/CompilePerms.java b/dev/core/src/com/google/gwt/dev/CompilePerms.java index 0a0a5b6..8b7eb50 100644 --- a/dev/core/src/com/google/gwt/dev/CompilePerms.java +++ b/dev/core/src/com/google/gwt/dev/CompilePerms.java
@@ -334,9 +334,10 @@ ModuleDef module = ModuleDefLoader.loadFromClassPath(logger, moduleName); PropertyPermutations allPermutations = new PropertyPermutations( module.getProperties(), module.getActiveLinkerNames()); + List<PropertyPermutations> collapsedPermutations = allPermutations.collapseProperties(); int[] perms = options.getPermsToCompile(); if (perms == null) { - perms = new int[allPermutations.size()]; + perms = new int[collapsedPermutations.size()]; for (int i = 0; i < perms.length; ++i) { perms[i] = i; } @@ -347,12 +348,10 @@ for (int permId : perms) { /* * TODO(spoon,scottb): move Precompile out of the loop to run only once - * per shard. We'll need a new PropertyPermutations constructor that can - * take a precise list. Then figure out a way to avoid copying the - * generated artifacts into every perm result on a shard. + * per shard. Then figure out a way to avoid copying the generated + * artifacts into every perm result on a shard. */ - PropertyPermutations onePerm = new PropertyPermutations(allPermutations, - permId, 1); + PropertyPermutations onePerm = collapsedPermutations.get(permId); assert (precompilationOptions.getDumpSignatureFile() == null); Precompilation precompilation = Precompile.precompile(logger,
diff --git a/dev/core/src/com/google/gwt/dev/Link.java b/dev/core/src/com/google/gwt/dev/Link.java index b585ccc..cbaf113 100644 --- a/dev/core/src/com/google/gwt/dev/Link.java +++ b/dev/core/src/com/google/gwt/dev/Link.java
@@ -301,6 +301,8 @@ for (StaticPropertyOracle propOracle : perm.getPropertyOracles()) { compilation.addSelectionPermutation(computeSelectionPermutation( linkerContext, propOracle)); + compilation.addSoftPermutation(computeSoftPermutation(linkerContext, + propOracle)); } } @@ -352,6 +354,22 @@ return unboundProperties; } + private static Map<SelectionProperty, String> computeSoftPermutation( + StandardLinkerContext linkerContext, StaticPropertyOracle propOracle) { + BindingProperty[] orderedProps = propOracle.getOrderedProps(); + String[] orderedPropValues = propOracle.getOrderedPropValues(); + Map<SelectionProperty, String> softProperties = new HashMap<SelectionProperty, String>(); + for (int i = 0; i < orderedProps.length; i++) { + if (orderedProps[i].getCollapsedValues().isEmpty()) { + continue; + } + + SelectionProperty key = linkerContext.getProperty(orderedProps[i].getName()); + softProperties.put(key, orderedPropValues[i]); + } + return softProperties; + } + /** * Emit final output. */ @@ -584,7 +602,7 @@ ModuleDef module, JJSOptions precompileOptions) throws UnableToCompleteException { int numPermutations = new PropertyPermutations(module.getProperties(), - module.getActiveLinkerNames()).size(); + module.getActiveLinkerNames()).collapseProperties().size(); List<File> resultFiles = new ArrayList<File>(numPermutations); for (int i = 0; i < numPermutations; ++i) { File f = CompilePerms.makePermFilename(compilerWorkDir, i);
diff --git a/dev/core/src/com/google/gwt/dev/Permutation.java b/dev/core/src/com/google/gwt/dev/Permutation.java index c0319b1..8c9afef 100644 --- a/dev/core/src/com/google/gwt/dev/Permutation.java +++ b/dev/core/src/com/google/gwt/dev/Permutation.java
@@ -16,10 +16,9 @@ package com.google.gwt.dev; import com.google.gwt.dev.cfg.StaticPropertyOracle; +import com.google.gwt.dev.util.collect.Lists; import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.SortedMap; import java.util.SortedSet; @@ -29,54 +28,93 @@ * Represents the state of a single permutation for compile. */ public final class Permutation implements Serializable { + private final int id; - private final List<StaticPropertyOracle> propertyOracles = new ArrayList<StaticPropertyOracle>(); - private final SortedMap<String, String> rebindAnswers = new TreeMap<String, String>(); + + private List<StaticPropertyOracle> orderedPropertyOracles = Lists.create(); + private List<SortedMap<String, String>> orderedRebindAnswers = Lists.create(); /** * Clones an existing permutation, but with a new id. - * + * * @param id new permutation id * @param other Permutation to copy */ public Permutation(int id, Permutation other) { this.id = id; - this.propertyOracles.addAll(other.propertyOracles); - this.rebindAnswers.putAll(other.rebindAnswers); + orderedPropertyOracles = Lists.create(other.orderedPropertyOracles); + orderedRebindAnswers = Lists.create(other.orderedRebindAnswers); } public Permutation(int id, StaticPropertyOracle propertyOracle) { this.id = id; - this.propertyOracles.add(propertyOracle); + orderedPropertyOracles = Lists.add(orderedPropertyOracles, propertyOracle); + orderedRebindAnswers = Lists.add(orderedRebindAnswers, + new TreeMap<String, String>()); } public int getId() { return id; } + public SortedMap<String, String>[] getOrderedRebindAnswers() { + @SuppressWarnings("unchecked") + SortedMap<String, String>[] arr = new SortedMap[orderedRebindAnswers.size()]; + return orderedRebindAnswers.toArray(arr); + } + + /** + * Returns the property oracles, sorted by property values. + */ public StaticPropertyOracle[] getPropertyOracles() { - return propertyOracles.toArray(new StaticPropertyOracle[propertyOracles.size()]); + return orderedPropertyOracles.toArray(new StaticPropertyOracle[orderedPropertyOracles.size()]); } - public SortedMap<String, String> getRebindAnswers() { - return Collections.unmodifiableSortedMap(rebindAnswers); - } - + /** + * This is called to merge two permutations that either have identical rebind + * answers or were explicitly collapsed using <collapse-property> + */ public void mergeFrom(Permutation other, SortedSet<String> liveRebindRequests) { if (getClass().desiredAssertionStatus()) { - for (String rebindRequest : liveRebindRequests) { - String myAnswer = rebindAnswers.get(rebindRequest); - String otherAnswer = other.rebindAnswers.get(rebindRequest); - assert myAnswer.equals(otherAnswer); + for (SortedMap<String, String> myRebind : orderedRebindAnswers) { + for (SortedMap<String, String> otherRebind : other.orderedRebindAnswers) { + for (String rebindRequest : liveRebindRequests) { + String myAnswer = myRebind.get(rebindRequest); + String otherAnswer = otherRebind.get(rebindRequest); + assert myAnswer.equals(otherAnswer); + } + } } } - assert !propertyOracles.isEmpty(); - assert !other.propertyOracles.isEmpty(); - propertyOracles.addAll(other.propertyOracles); - other.propertyOracles.clear(); + mergeRebindsFromCollapsed(other); + } + + /** + * This is called to collapse one permutation into another where the rebinds + * vary between the two permutations. + */ + public void mergeRebindsFromCollapsed(Permutation other) { + assert other.orderedPropertyOracles.size() == other.orderedRebindAnswers.size(); + orderedPropertyOracles = Lists.addAll(orderedPropertyOracles, + other.orderedPropertyOracles); + orderedRebindAnswers = Lists.addAll(orderedRebindAnswers, + other.orderedRebindAnswers); + other.destroy(); } public void putRebindAnswer(String requestType, String resultType) { - rebindAnswers.put(requestType, resultType); + assert orderedRebindAnswers.size() == 1 : "Cannot add rebind to merged Permutation"; + SortedMap<String, String> answerMap = orderedRebindAnswers.get(0); + assert answerMap != null; + answerMap.put(requestType, resultType); + } + + /** + * Clear the state of the Permutation. This aids the correctness-checking code + * in {@link #mergeFrom}. + */ + private void destroy() { + orderedPropertyOracles = Lists.create(); + orderedRebindAnswers = Lists.create(); } }
diff --git a/dev/core/src/com/google/gwt/dev/Precompile.java b/dev/core/src/com/google/gwt/dev/Precompile.java index 7a41eff..d2aac7b 100644 --- a/dev/core/src/com/google/gwt/dev/Precompile.java +++ b/dev/core/src/com/google/gwt/dev/Precompile.java
@@ -42,6 +42,7 @@ import com.google.gwt.dev.shell.CheckForUpdates; import com.google.gwt.dev.shell.StandardRebindOracle; import com.google.gwt.dev.shell.CheckForUpdates.UpdateResult; +import com.google.gwt.dev.util.CollapsedPropertyKey; import com.google.gwt.dev.util.Memory; import com.google.gwt.dev.util.PerfLogger; import com.google.gwt.dev.util.Util; @@ -68,11 +69,17 @@ import com.google.gwt.dev.util.arg.OptionGenDir; import com.google.gwt.dev.util.arg.OptionMaxPermsPerPrecompile; import com.google.gwt.dev.util.arg.OptionValidateOnly; +import com.google.gwt.dev.util.collect.Lists; import java.io.File; import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; @@ -517,16 +524,18 @@ PerfLogger.end(); // Merge all identical permutations together. - Permutation[] permutations = rpo.getPermutations(); + List<Permutation> permutations = new ArrayList<Permutation>( + Arrays.asList(rpo.getPermutations())); + + mergeCollapsedPermutations(permutations); + // Sort the permutations by an ordered key to ensure determinism. - SortedMap<String, Permutation> merged = new TreeMap<String, Permutation>(); + SortedMap<RebindAnswersPermutationKey, Permutation> merged = new TreeMap<RebindAnswersPermutationKey, Permutation>(); SortedSet<String> liveRebindRequests = unifiedAst.getRebindRequests(); for (Permutation permutation : permutations) { - // Construct a key from the stringified map of live rebind answers. - SortedMap<String, String> rebindAnswers = new TreeMap<String, String>( - permutation.getRebindAnswers()); - rebindAnswers.keySet().retainAll(liveRebindRequests); - String key = rebindAnswers.toString(); + // Construct a key for the live rebind answers. + RebindAnswersPermutationKey key = new RebindAnswersPermutationKey( + permutation, liveRebindRequests); if (merged.containsKey(key)) { Permutation existing = merged.get(key); existing.mergeFrom(permutation, liveRebindRequests); @@ -534,6 +543,7 @@ merged.put(key, permutation); } } + return new Precompilation(unifiedAst, merged.values(), permutationBase, generatedArtifacts); } catch (UnableToCompleteException e) { @@ -565,6 +575,54 @@ + compilerClassName + "'", caught); } + /** + * This merges Permutations that can be considered equivalent by considering + * their collapsed properties. The list passed into this method may have + * elements removed from it. + */ + private static void mergeCollapsedPermutations(List<Permutation> permutations) { + if (permutations.size() < 2) { + return; + } + + // See the doc for CollapsedPropertyKey + SortedMap<CollapsedPropertyKey, List<Permutation>> mergedByCollapsedProperties = new TreeMap<CollapsedPropertyKey, List<Permutation>>(); + + // This loop creates the equivalence sets + for (Iterator<Permutation> it = permutations.iterator(); it.hasNext();) { + Permutation entry = it.next(); + CollapsedPropertyKey key = new CollapsedPropertyKey(entry); + + List<Permutation> equivalenceSet = mergedByCollapsedProperties.get(key); + if (equivalenceSet == null) { + equivalenceSet = Lists.create(); + } else { + // Mutate list + it.remove(); + equivalenceSet = Lists.add(equivalenceSet, entry); + } + mergedByCollapsedProperties.put(key, equivalenceSet); + } + + // This loop merges the Permutations together + for (Map.Entry<CollapsedPropertyKey, List<Permutation>> entry : mergedByCollapsedProperties.entrySet()) { + Permutation mergeInto = entry.getKey().getPermutation(); + + /* + * Merge the deferred-binding properties once we no longer need the + * PropertyOracle data from the extra permutations. + */ + for (Permutation mergeFrom : entry.getValue()) { + mergeInto.mergeRebindsFromCollapsed(mergeFrom); + } + } + + // Renumber the Permutations + for (int i = 0, j = permutations.size(); i < j; i++) { + permutations.set(i, new Permutation(i, permutations.get(i))); + } + } + private final PrecompileOptionsImpl options; public Precompile(PrecompileOptions options) { @@ -625,7 +683,7 @@ "Precompiling (minimal) module " + module.getName()); Util.writeObjectAsFile(logger, precompilationFile, options); int numPermutations = new PropertyPermutations(module.getProperties(), - module.getActiveLinkerNames()).size(); + module.getActiveLinkerNames()).collapseProperties().size(); Util.writeStringAsFile(logger, new File(compilerWorkDir, PERM_COUNT_FILENAME), String.valueOf(numPermutations)); branch.log(TreeLogger.INFO,
diff --git a/dev/core/src/com/google/gwt/dev/RebindAnswersPermutationKey.java b/dev/core/src/com/google/gwt/dev/RebindAnswersPermutationKey.java new file mode 100644 index 0000000..29e8860 --- /dev/null +++ b/dev/core/src/com/google/gwt/dev/RebindAnswersPermutationKey.java
@@ -0,0 +1,71 @@ +/* + * 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.dev; + +import com.google.gwt.dev.util.StringKey; + +import java.util.Map; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; + +/** + * Creates a string representation of live rebound types to all possible + * answers. + * + * <pre> + * { + * Foo = [ FooImpl ], // A "hard" rebind + * Bundle = [ Bundle_EN, Bundle_FR ] // A "soft" rebind + * } + * </pre> + */ +class RebindAnswersPermutationKey extends StringKey { + private static String collapse(Permutation permutation, + SortedSet<String> liveRebindRequests) { + // Accumulates state + SortedMap<String, SortedSet<String>> answers = new TreeMap<String, SortedSet<String>>(); + + // Iterate over each map of rebind answers + for (SortedMap<String, String> rebinds : permutation.getOrderedRebindAnswers()) { + for (Map.Entry<String, String> rebind : rebinds.entrySet()) { + if (!liveRebindRequests.contains(rebind.getKey())) { + // Ignore rebinds that aren't actually used + continue; + } + + // Get-or-put + SortedSet<String> set = answers.get(rebind.getKey()); + if (set == null) { + set = new TreeSet<String>(); + answers.put(rebind.getKey(), set); + } + + // Record rebind value + set.add(rebind.getValue()); + } + } + + // Create string + return answers.toString(); + } + + public RebindAnswersPermutationKey(Permutation permutation, + SortedSet<String> liveRebindRequests) { + super(collapse(permutation, liveRebindRequests)); + } +} \ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/dev/cfg/BindingProperty.java b/dev/core/src/com/google/gwt/dev/cfg/BindingProperty.java index 6eb07a5..5bea795 100644 --- a/dev/core/src/com/google/gwt/dev/cfg/BindingProperty.java +++ b/dev/core/src/com/google/gwt/dev/cfg/BindingProperty.java
@@ -15,15 +15,24 @@ */ package com.google.gwt.dev.cfg; +import com.google.gwt.dev.util.collect.IdentityHashSet; +import com.google.gwt.dev.util.collect.Lists; import com.google.gwt.dev.util.collect.Sets; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; +import java.util.regex.Pattern; /** * Represents a single named deferred binding or configuration property that can @@ -32,9 +41,10 @@ * of the defined set. */ public class BindingProperty extends Property { - + public static final String GLOB_STAR = "*"; private static final String EMPTY = ""; + private List<SortedSet<String>> collapsedValues = Lists.create(); private final Map<Condition, SortedSet<String>> conditionalValues = new LinkedHashMap<Condition, SortedSet<String>>(); private final SortedSet<String> definedValues = new TreeSet<String>(); private PropertyProvider provider; @@ -50,6 +60,27 @@ fallback = EMPTY; } + /** + * Add an equivalence set of property values. + */ + public void addCollapsedValues(String... values) { + + // Sanity check caller + for (String value : values) { + if (value.contains(GLOB_STAR)) { + // Expanded in normalizeCollapsedValues() + continue; + } else if (!definedValues.contains(value)) { + throw new IllegalArgumentException( + "Attempting to collapse unknown value " + value); + } + } + + // We want a mutable set, because it simplifies normalizeCollapsedValues + SortedSet<String> temp = new TreeSet<String>(Arrays.asList(values)); + collapsedValues = Lists.add(collapsedValues, temp); + } + public void addDefinedValue(Condition condition, String newValue) { definedValues.add(newValue); SortedSet<String> set = conditionalValues.get(condition); @@ -70,6 +101,10 @@ return allowedValues.toArray(new String[allowedValues.size()]); } + public List<SortedSet<String>> getCollapsedValues() { + return collapsedValues; + } + public Map<Condition, SortedSet<String>> getConditionalValues() { return Collections.unmodifiableMap(conditionalValues); } @@ -191,4 +226,82 @@ public void setProvider(PropertyProvider provider) { this.provider = provider; } + + /** + * Create a minimal number of equivalence sets, expanding any glob patterns. + */ + void normalizeCollapsedValues() { + if (collapsedValues.isEmpty()) { + return; + } + + // Expand globs + for (Set<String> set : collapsedValues) { + // Compile a regex that matches all glob expressions that we see + StringBuilder pattern = new StringBuilder(); + for (Iterator<String> it = set.iterator(); it.hasNext();) { + String value = it.next(); + if (value.contains(GLOB_STAR)) { + it.remove(); + if (pattern.length() > 0) { + pattern.append("|"); + } + + // a*b ==> (a.*b) + pattern.append("("); + // We know value is a Java ident, so no special escaping is needed + pattern.append(value.replace(GLOB_STAR, ".*")); + pattern.append(")"); + } + } + + if (pattern.length() == 0) { + continue; + } + + Pattern p = Pattern.compile(pattern.toString()); + for (String definedValue : definedValues) { + if (p.matcher(definedValue).matches()) { + set.add(definedValue); + } + } + } + + // Minimize number of sets + + // Maps a value to the set that contains that value + Map<String, SortedSet<String>> map = new HashMap<String, SortedSet<String>>(); + + // For each equivalence set we have + for (SortedSet<String> set : collapsedValues) { + // Examine each original value in the set + for (String value : new LinkedHashSet<String>(set)) { + // See if the value was previously assigned to another set + SortedSet<String> existing = map.get(value); + if (existing == null) { + map.put(value, set); + } else { + // If so, merge the existing set into this one and update pointers + set.addAll(existing); + for (String mergedValue : existing) { + map.put(mergedValue, set); + } + } + } + } + + // The values of the maps will now contain the minimal number of sets + collapsedValues = new ArrayList<SortedSet<String>>( + new IdentityHashSet<SortedSet<String>>(map.values())); + + // Sort the list + Lists.sort(collapsedValues, new Comparator<SortedSet<String>>() { + public int compare(SortedSet<String> o1, SortedSet<String> o2) { + String s1 = o1.toString(); + String s2 = o2.toString(); + assert !s1.equals(s2) : "Should not have seen equal sets"; + return s1.compareTo(s2); + } + }); + } }
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java b/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java index 5db5c05..ab7c61f 100644 --- a/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java +++ b/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java
@@ -77,6 +77,8 @@ private String activePrimaryLinker; + private boolean collapseAllProperties; + private final DefaultFilters defaultFilters; private final List<String> entryPointTypeNames = new ArrayList<String>(); @@ -400,6 +402,13 @@ } /** + * Mainly for testing and decreasing compile times. + */ + public void setCollapseAllProperties(boolean collapse) { + collapseAllProperties = collapse; + } + + /** * Override the module's apparent name. Setting this value to * <code>null<code> will disable the name override. */ @@ -435,6 +444,13 @@ for (Property current : getProperties()) { if (current instanceof BindingProperty) { BindingProperty prop = (BindingProperty) current; + + if (collapseAllProperties) { + prop.addCollapsedValues("*"); + } + + prop.normalizeCollapsedValues(); + /* * Create a default property provider for any properties with more than * one possible value and no existing provider.
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java b/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java index c6ab34f..bbfc2ab 100644 --- a/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java +++ b/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java
@@ -35,10 +35,12 @@ import java.io.IOException; import java.io.StringReader; import java.net.URL; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.regex.Pattern; // CHECKSTYLE_NAMING_OFF /** @@ -52,6 +54,12 @@ protected final String __clear_configuration_property_1_name = null; + protected final String __collapse_all_properties_1_value = "true"; + + protected final String __collapse_property_1_name = null; + + protected final String __collapse_property_2_values = null; + protected final String __define_configuration_property_1_name = null; protected final String __define_configuration_property_2_is_multi_valued = null; @@ -176,6 +184,52 @@ return null; } + protected Schema __collapse_all_properties_begin(boolean collapse) { + moduleDef.setCollapseAllProperties(collapse); + return null; + } + + protected Schema __collapse_property_begin(PropertyName name, + PropertyValueGlob[] values) throws UnableToCompleteException { + Property prop = moduleDef.getProperties().find(name.token); + if (prop == null) { + logger.log(TreeLogger.ERROR, "No property named " + name.token + + " has been defined"); + throw new UnableToCompleteException(); + } else if (!(prop instanceof BindingProperty)) { + logger.log(TreeLogger.ERROR, "The property " + name.token + + " is not a deferred-binding property"); + throw new UnableToCompleteException(); + } + + BindingProperty binding = (BindingProperty) prop; + List<String> allowed = Arrays.asList(binding.getDefinedValues()); + + String[] tokens = new String[values.length]; + boolean error = false; + for (int i = 0, j = values.length; i < j; i++) { + tokens[i] = values[i].token; + if (values[i].isGlob()) { + // Expanded later in BindingProperty + continue; + } else if (!allowed.contains(tokens[i])) { + logger.log(TreeLogger.ERROR, "The value " + tokens[i] + + " was not previously defined for the property " + + binding.getName()); + error = true; + } + } + + if (error) { + throw new UnableToCompleteException(); + } + + binding.addCollapsedValues(tokens); + + // No children + return null; + } + protected Schema __define_configuration_property_begin(PropertyName name, String is_multi_valued) throws UnableToCompleteException { boolean isMultiValued = toPrimitiveBoolean(is_multi_valued); @@ -1014,6 +1068,58 @@ } } + private static class PropertyValueGlob { + public final String token; + + public PropertyValueGlob(String token) { + this.token = token; + } + + public boolean isGlob() { + return token.contains(BindingProperty.GLOB_STAR); + } + } + + /** + * Converts a comma-separated string into an array of property value tokens. + */ + private final class PropertyValueGlobArrayAttrCvt extends AttributeConverter { + public Object convertToArg(Schema schema, int line, String elem, + String attr, String value) throws UnableToCompleteException { + String[] tokens = value.split(","); + PropertyValueGlob[] values = new PropertyValueGlob[tokens.length]; + + // Validate each token as we copy it over. + // + for (int i = 0; i < tokens.length; i++) { + values[i] = (PropertyValueGlob) propValueGlobAttrCvt.convertToArg( + schema, line, elem, attr, tokens[i]); + } + + return values; + } + } + + /** + * Converts a string into a property value glob, validating it in the process. + */ + private final class PropertyValueGlobAttrCvt extends AttributeConverter { + public Object convertToArg(Schema schema, int line, String elem, + String attr, String value) throws UnableToCompleteException { + + String token = value.trim(); + String tokenNoStar = token.replaceAll( + Pattern.quote(BindingProperty.GLOB_STAR), ""); + if (BindingProperty.GLOB_STAR.equals(token) + || Util.isValidJavaIdent(tokenNoStar)) { + return new PropertyValueGlob(token); + } else { + Messages.PROPERTY_VALUE_INVALID.log(logger, token, null); + throw new UnableToCompleteException(); + } + } + } + private static class ScriptSchema extends Schema { private StringBuffer script; @@ -1087,6 +1193,8 @@ private final PropertyNameAttrCvt propNameAttrCvt = new PropertyNameAttrCvt(); private final PropertyValueArrayAttrCvt propValueArrayAttrCvt = new PropertyValueArrayAttrCvt(); private final PropertyValueAttrCvt propValueAttrCvt = new PropertyValueAttrCvt(); + private final PropertyValueGlobArrayAttrCvt propValueGlobArrayAttrCvt = new PropertyValueGlobArrayAttrCvt(); + private final PropertyValueGlobAttrCvt propValueGlobAttrCvt = new PropertyValueGlobAttrCvt(); public ModuleDefSchema(TreeLogger logger, ModuleDefLoader loader, String moduleName, URL moduleURL, String modulePackageAsPath, @@ -1106,6 +1214,9 @@ configurationPropAttrCvt); registerAttributeConverter(PropertyValue.class, propValueAttrCvt); registerAttributeConverter(PropertyValue[].class, propValueArrayAttrCvt); + registerAttributeConverter(PropertyValueGlob.class, propValueGlobAttrCvt); + registerAttributeConverter(PropertyValueGlob[].class, + propValueGlobArrayAttrCvt); registerAttributeConverter(LinkerName.class, linkerNameAttrCvt); registerAttributeConverter(NullableName.class, nullableNameAttrCvt); registerAttributeConverter(Class.class, classAttrCvt);
diff --git a/dev/core/src/com/google/gwt/dev/cfg/PropertyPermutations.java b/dev/core/src/com/google/gwt/dev/cfg/PropertyPermutations.java index be1a05a..48dfef1 100644 --- a/dev/core/src/com/google/gwt/dev/cfg/PropertyPermutations.java +++ b/dev/core/src/com/google/gwt/dev/cfg/PropertyPermutations.java
@@ -18,6 +18,7 @@ import com.google.gwt.core.ext.PropertyOracle; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.UnableToCompleteException; +import com.google.gwt.dev.util.CollapsedPropertyKey; import java.util.ArrayList; import java.util.Iterator; @@ -26,6 +27,8 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; /** * Generates all possible permutations of properties in a module. Each @@ -161,6 +164,52 @@ values = allPermutations.values.subList(firstPerm, firstPerm + numPerms); } + /** + * Copy constructor that allows the list of property values to be reset. + */ + public PropertyPermutations(PropertyPermutations allPermutations, + List<String[]> values) { + this.properties = allPermutations.properties; + this.values = values; + } + + /** + * Return a list of PropertyPermutations that represent the hard permutations + * that result from collapsing the soft properties in the + * PropertyPermutation's Properties object. + */ + public List<PropertyPermutations> collapseProperties() { + // Collate property values in this map + SortedMap<CollapsedPropertyKey, List<String[]>> map = new TreeMap<CollapsedPropertyKey, List<String[]>>(); + + // Loop over all possible property value permutations + for (Iterator<String[]> it = iterator(); it.hasNext();) { + String[] propertyValues = it.next(); + assert propertyValues.length == getOrderedProperties().length; + + StaticPropertyOracle oracle = new StaticPropertyOracle( + getOrderedProperties(), propertyValues, new ConfigurationProperty[0]); + CollapsedPropertyKey key = new CollapsedPropertyKey( + oracle); + + List<String[]> list = map.get(key); + if (list == null) { + list = new ArrayList<String[]>(); + map.put(key, list); + } + list.add(propertyValues); + } + + // Return the collated values + List<PropertyPermutations> toReturn = new ArrayList<PropertyPermutations>( + map.size()); + for (List<String[]> list : map.values()) { + toReturn.add(new PropertyPermutations(this, list)); + } + + return toReturn; + } + public BindingProperty[] getOrderedProperties() { return getOrderedPropertiesOf(properties); }
diff --git a/dev/core/src/com/google/gwt/dev/cfg/StaticPropertyOracle.java b/dev/core/src/com/google/gwt/dev/cfg/StaticPropertyOracle.java index 3b57240..b7d2e7b 100644 --- a/dev/core/src/com/google/gwt/dev/cfg/StaticPropertyOracle.java +++ b/dev/core/src/com/google/gwt/dev/cfg/StaticPropertyOracle.java
@@ -158,4 +158,17 @@ throw new BadPropertyValueException(propertyName); } + + /** + * Dumps the binding property key/value pairs; For debugging use only. + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (int i = 0, j = orderedProps.length; i < j; i++) { + sb.append(orderedProps[i].getName()).append(" = ").append( + orderedPropValues[i]).append(" "); + } + return sb.toString(); + } }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java index 1fcdba2..bf07384 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java +++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -220,7 +220,6 @@ InternalCompilerException.preload(); PropertyOracle[] propertyOracles = permutation.getPropertyOracles(); int permutationId = permutation.getId(); - Map<String, String> rebindAnswers = permutation.getRebindAnswers(); logger.log(TreeLogger.INFO, "Compiling permutation " + permutationId + "..."); long permStart = System.currentTimeMillis(); @@ -238,7 +237,7 @@ Map<StandardSymbolData, JsName> symbolTable = new TreeMap<StandardSymbolData, JsName>( new SymbolData.ClassIdentComparator()); - ResolveRebinds.exec(jprogram, rebindAnswers); + ResolveRebinds.exec(jprogram, permutation.getOrderedRebindAnswers()); // (4) Optimize the normalized Java AST for each permutation. if (options.isDraftCompile()) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java index 59b74c5..9914d57 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
@@ -65,6 +65,7 @@ public static final Set<String> CODEGEN_TYPES_SET = new LinkedHashSet<String>( Arrays.asList(new String[] { "com.google.gwt.lang.Array", "com.google.gwt.lang.Cast", + "com.google.gwt.lang.CollapsedPropertyHolder", "com.google.gwt.lang.Exceptions", "com.google.gwt.lang.LongLib", "com.google.gwt.lang.Stats",}));
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java index dae6575..95863a2 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
@@ -1468,9 +1468,10 @@ /** * <pre> * var $entry = Impl.registerEntry(); - * function gwtOnLoad(errFn, modName, modBase){ + * function gwtOnLoad(errFn, modName, modBase, softPermutationId){ * $moduleName = modName; * $moduleBase = modBase; + * CollapsedPropertyHolder.permutationId = softPermutationId; * if (errFn) { * try { * $entry(init)(); @@ -1510,9 +1511,11 @@ JsName errFn = fnScope.declareName("errFn"); JsName modName = fnScope.declareName("modName"); JsName modBase = fnScope.declareName("modBase"); + JsName softPermutationId = fnScope.declareName("softPermutationId"); params.add(new JsParameter(sourceInfo, errFn)); params.add(new JsParameter(sourceInfo, modName)); params.add(new JsParameter(sourceInfo, modBase)); + params.add(new JsParameter(sourceInfo, softPermutationId)); JsExpression asg = createAssignment( topScope.findExistingUnobfuscatableName("$moduleName").makeRef( sourceInfo), modName.makeRef(sourceInfo)); @@ -1520,6 +1523,15 @@ asg = createAssignment(topScope.findExistingUnobfuscatableName( "$moduleBase").makeRef(sourceInfo), modBase.makeRef(sourceInfo)); body.getStatements().add(asg.makeStmt()); + + // Assignment to CollapsedPropertyHolder.permutationId only if it's used + JsName permutationIdFieldName = names.get(program.getIndexedField("CollapsedPropertyHolder.permutationId")); + if (permutationIdFieldName != null) { + asg = createAssignment(permutationIdFieldName.makeRef(sourceInfo), + softPermutationId.makeRef(sourceInfo)); + body.getStatements().add(asg.makeStmt()); + } + JsIf jsIf = new JsIf(sourceInfo); body.getStatements().add(jsIf); jsIf.setIfExpr(errFn.makeRef(sourceInfo));
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ResolveRebinds.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ResolveRebinds.java index 048a7b0..373c2bd 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/ResolveRebinds.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ResolveRebinds.java
@@ -16,15 +16,31 @@ package com.google.gwt.dev.jjs.impl; import com.google.gwt.dev.jjs.InternalCompilerException; +import com.google.gwt.dev.jjs.SourceInfo; import com.google.gwt.dev.jjs.ast.Context; +import com.google.gwt.dev.jjs.ast.JBlock; +import com.google.gwt.dev.jjs.ast.JCaseStatement; import com.google.gwt.dev.jjs.ast.JClassType; import com.google.gwt.dev.jjs.ast.JDeclaredType; +import com.google.gwt.dev.jjs.ast.JExpression; import com.google.gwt.dev.jjs.ast.JGwtCreate; +import com.google.gwt.dev.jjs.ast.JMethod; +import com.google.gwt.dev.jjs.ast.JMethodBody; +import com.google.gwt.dev.jjs.ast.JMethodCall; import com.google.gwt.dev.jjs.ast.JModVisitor; import com.google.gwt.dev.jjs.ast.JProgram; import com.google.gwt.dev.jjs.ast.JReboundEntryPoint; +import com.google.gwt.dev.jjs.ast.JReferenceType; +import com.google.gwt.dev.jjs.ast.JReturnStatement; +import com.google.gwt.dev.jjs.ast.JSwitchStatement; import com.google.gwt.dev.jjs.ast.JType; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -37,6 +53,15 @@ private class RebindVisitor extends JModVisitor { @Override public void endVisit(JGwtCreate x, Context ctx) { + + if (isSoftRebind(x.getSourceType())) { + JMethod method = rebindMethod(x.getSourceInfo(), x.getSourceType(), + x.getResultTypes(), x.getInstantiationExpressions()); + JMethodCall call = new JMethodCall(x.getSourceInfo(), null, method); + ctx.replaceMe(call); + return; + } + JClassType rebindResult = rebind(x.getSourceType()); List<JClassType> rebindResults = x.getResultTypes(); for (int i = 0; i < rebindResults.size(); ++i) { @@ -53,6 +78,15 @@ @Override public void endVisit(JReboundEntryPoint x, Context ctx) { + + if (isSoftRebind(x.getSourceType())) { + JMethod method = rebindMethod(x.getSourceInfo(), x.getSourceType(), + x.getResultTypes(), x.getEntryCalls()); + JMethodCall call = new JMethodCall(x.getSourceInfo(), null, method); + ctx.replaceMe(call.makeStatement()); + return; + } + JClassType rebindResult = rebind(x.getSourceType()); List<JClassType> rebindResults = x.getResultTypes(); for (int i = 0; i < rebindResults.size(); ++i) { @@ -68,22 +102,52 @@ } } - public static boolean exec(JProgram program, Map<String, String> rebindAnswers) { - return new ResolveRebinds(program, rebindAnswers).execImpl(); + public static boolean exec(JProgram program, + Map<String, String>[] orderedRebindAnswers) { + return new ResolveRebinds(program, orderedRebindAnswers).execImpl(); } - private final JProgram program; - private final Map<String, String> rebindAnswers; + /** + * Returns the rebind answers that do not vary across various maps of rebind + * answers. + */ + public static Map<String, String> getHardRebindAnswers( + Map<String, String>[] rebindAnswers) { + Iterator<Map<String, String>> it = Arrays.asList(rebindAnswers).iterator(); - private ResolveRebinds(JProgram program, Map<String, String> rebindAnswers) { + // Start with an arbitrary copy of a rebind answer map + Map<String, String> toReturn = new HashMap<String, String>(it.next()); + + while (it.hasNext()) { + Map<String, String> next = it.next(); + // Only keep key/value pairs present in the other rebind map + toReturn.entrySet().retainAll(next.entrySet()); + } + + return toReturn; + } + + private final Map<String, String> hardRebindAnswers; + private final JClassType holderType; + private final Map<String, String>[] orderedRebindAnswers; + private final JMethod permutationIdMethod; + private final JProgram program; + private final Map<JReferenceType, JMethod> rebindMethods = new IdentityHashMap<JReferenceType, JMethod>(); + + private ResolveRebinds(JProgram program, + Map<String, String>[] orderedRebindAnswers) { this.program = program; - this.rebindAnswers = rebindAnswers; + this.orderedRebindAnswers = orderedRebindAnswers; + + this.hardRebindAnswers = getHardRebindAnswers(orderedRebindAnswers); + this.holderType = (JClassType) program.getIndexedType("CollapsedPropertyHolder"); + this.permutationIdMethod = program.getIndexedMethod("CollapsedPropertyHolder.getPermutationId"); } public JClassType rebind(JType type) { // Rebinds are always on a source type name. String reqType = type.getName().replace('$', '.'); - String reboundClassName = rebindAnswers.get(reqType); + String reboundClassName = hardRebindAnswers.get(reqType); if (reboundClassName == null) { // The fact that we already compute every rebind permutation before // compiling should prevent this case from ever happening in real life. @@ -102,4 +166,109 @@ return rebinder.didChange(); } + private boolean isSoftRebind(JType type) { + String reqType = type.getName().replace('$', '.'); + return !hardRebindAnswers.containsKey(reqType); + } + + private JMethod rebindMethod(SourceInfo info, JReferenceType requestType, + List<JClassType> resultTypes, List<JExpression> instantiationExpressions) { + assert resultTypes.size() == instantiationExpressions.size(); + + JMethod toReturn = rebindMethods.get(requestType); + if (toReturn != null) { + return toReturn; + } + + info = info.makeChild(ResolveRebinds.class, "Rebind factory for " + + requestType.getName()); + + // Maps the result types to the various virtual permutation ids + Map<JClassType, List<Integer>> resultsToPermutations = new LinkedHashMap<JClassType, List<Integer>>(); + + for (int i = 0, j = orderedRebindAnswers.length; i < j; i++) { + Map<String, String> answerMap = orderedRebindAnswers[i]; + String answerTypeName = answerMap.get(requestType.getName().replace('$', + '.')); + // We take an answer class, e.g. DOMImplSafari ... + JClassType answerType = (JClassType) program.getFromTypeMap(answerTypeName); + + List<Integer> list = resultsToPermutations.get(answerType); + if (list == null) { + list = new ArrayList<Integer>(); + resultsToPermutations.put(answerType, list); + } + // and map it to the permutation ID for a particular set of values + list.add(i); + } + + // Pick the most-used result type to emit less code + JClassType mostUsed = null; + { + int max = 0; + for (Map.Entry<JClassType, List<Integer>> entry : resultsToPermutations.entrySet()) { + int size = entry.getValue().size(); + if (size > max) { + max = size; + mostUsed = entry.getKey(); + } + } + } + assert mostUsed != null; + + // c_g_g_d_c_i_DOMImpl + toReturn = program.createMethod(info, requestType.getName().replace("_", + "_1").replace('.', '_').toCharArray(), holderType, + program.getNonNullType(program.getTypeJavaLangObject()), false, true, + true, false, false); + toReturn.freezeParamTypes(); + rebindMethods.put(requestType, toReturn); + + // Used in the return statement at the end + JExpression mostUsedExpression = null; + + JBlock switchBody = new JBlock(info); + for (int i = 0, j = resultTypes.size(); i < j; i++) { + JClassType resultType = resultTypes.get(i); + JExpression instantiation = instantiationExpressions.get(i); + + List<Integer> permutations = resultsToPermutations.get(resultType); + if (permutations == null) { + // This rebind result is unused in this permutation + continue; + } else if (resultType == mostUsed) { + // Save off the fallback expression and go onto the next type + mostUsedExpression = instantiation; + continue; + } + + for (int permutationId : permutations) { + // case 33: + switchBody.addStmt(new JCaseStatement(info, + program.getLiteralInt(permutationId))); + } + + // return new FooImpl(); + JReturnStatement ret = new JReturnStatement(info, instantiation); + switchBody.addStmt(ret); + } + + assert switchBody.getStatements().size() > 0 : "No case statement emitted " + + "for supposedly soft-rebind type " + requestType.getName(); + + // switch (CollapsedPropertyHolder.getPermutationId()) { ... } + JSwitchStatement sw = new JSwitchStatement(info, new JMethodCall(info, + null, permutationIdMethod), switchBody); + + // return new FallbackImpl(); at the very end. + assert mostUsedExpression != null : "No most-used expression"; + JReturnStatement fallbackReturn = new JReturnStatement(info, + mostUsedExpression); + + JMethodBody body = (JMethodBody) toReturn.getBody(); + body.getBlock().addStmt(sw); + body.getBlock().addStmt(fallbackReturn); + + return toReturn; + } }
diff --git a/dev/core/src/com/google/gwt/dev/util/CollapsedPropertyKey.java b/dev/core/src/com/google/gwt/dev/util/CollapsedPropertyKey.java new file mode 100644 index 0000000..a942306 --- /dev/null +++ b/dev/core/src/com/google/gwt/dev/util/CollapsedPropertyKey.java
@@ -0,0 +1,103 @@ +/* + * 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.dev.util; + +import com.google.gwt.dev.Permutation; +import com.google.gwt.dev.cfg.BindingProperty; +import com.google.gwt.dev.cfg.StaticPropertyOracle; + +import java.util.Arrays; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; + +/** + * Creates a string representation of the binding property key/value pairs used + * in a Permutation. The value of a collapsed property will be represented by + * the set of equivalent values. + * <p> + * Assume that the <code>safari</code> and <code>ie8</code> + * <code>user.agent</code> values have been collapsed. Instead of printing + * <code>user.agent=safari</code>, this class will use + * <code>user.agent = { ie8, safari }</code>. + */ +public class CollapsedPropertyKey extends StringKey { + /** + * Create the string key for a collection of property oracles. + */ + private static String collapse(StaticPropertyOracle... oracles) { + // The map used to create the string key + SortedMap<String, SortedSet<String>> collapsedPropertyMap = new TreeMap<String, SortedSet<String>>(); + for (StaticPropertyOracle oracle : oracles) { + for (int i = 0, j = oracle.getOrderedProps().length; i < j; i++) { + BindingProperty prop = oracle.getOrderedProps()[i]; + String value = oracle.getOrderedPropValues()[i]; + boolean isCollapsed = false; + + // Iterate over the equivalence sets defined in the property + for (Set<String> equivalenceSet : prop.getCollapsedValues()) { + if (equivalenceSet.contains(value)) { + /* + * If we find a set that contains the current value, add all the + * values in the set. This accounts for the transitive nature of + * equality. + */ + SortedSet<String> toAdd = collapsedPropertyMap.get(prop.getName()); + if (toAdd == null) { + toAdd = new TreeSet<String>(); + collapsedPropertyMap.put(prop.getName(), toAdd); + isCollapsed = true; + } + toAdd.addAll(equivalenceSet); + } + } + if (!isCollapsed) { + // For "hard" properties, add the singleton value + collapsedPropertyMap.put(prop.getName(), new TreeSet<String>( + Arrays.asList(value))); + } + } + } + return collapsedPropertyMap.toString(); + } + + private final Permutation permutation; + + /** + * Constructor that constructs a key containing all collapsed property/value + * pairs used by a Permutation. The given Permutation can be retrieved later + * through {@link #getPermutation()}. + */ + public CollapsedPropertyKey(Permutation permutation) { + super(collapse(permutation.getPropertyOracles())); + this.permutation = permutation; + } + + /** + * Constructor that constructs a key based on all collapsed property/value + * pairs defined by the given property oracle. + */ + public CollapsedPropertyKey(StaticPropertyOracle oracle) { + super(collapse(oracle)); + this.permutation = null; + } + + public Permutation getPermutation() { + return permutation; + } +} \ No newline at end of file
diff --git a/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/CollapsedPropertyHolder.java b/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/CollapsedPropertyHolder.java new file mode 100644 index 0000000..a14ba9b --- /dev/null +++ b/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/CollapsedPropertyHolder.java
@@ -0,0 +1,34 @@ +/* + * 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.lang; + +/** + * This is a magic class the compiler uses to contain synthetic methods to + * support collapsed or "soft" properties. + */ +final class CollapsedPropertyHolder { + + /** + * This variable is initialized by the compiler in gwtOnLoad. + */ + public static volatile int permutationId = -1; + + public static int getPermutationId() { + assert permutationId != -1 : "The bootstrap linker did not provide a " + + "soft permutation id to the gwtOnLoad function"; + return permutationId; + } +}
diff --git a/dev/core/test/com/google/gwt/dev/cfg/ModuleDefTest.java b/dev/core/test/com/google/gwt/dev/cfg/ModuleDefTest.java index cd3baab..5be688c 100644 --- a/dev/core/test/com/google/gwt/dev/cfg/ModuleDefTest.java +++ b/dev/core/test/com/google/gwt/dev/cfg/ModuleDefTest.java
@@ -28,6 +28,8 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.List; +import java.util.SortedSet; /** * Runs tests directly on ModuleDef. @@ -84,6 +86,59 @@ static class FakeLinkerPrimary2 extends FakeLinker { } + public void testCollapsedProperties() { + ModuleDef def = new ModuleDef("fake"); + + Properties p = def.getProperties(); + BindingProperty b = p.createBinding("fake"); + b.addDefinedValue(b.getRootCondition(), "a"); + b.addDefinedValue(b.getRootCondition(), "b"); + b.addDefinedValue(b.getRootCondition(), "c"); + b.addDefinedValue(b.getRootCondition(), "d"); + b.addDefinedValue(b.getRootCondition(), "e"); + b.addDefinedValue(b.getRootCondition(), "f1"); + b.addDefinedValue(b.getRootCondition(), "f2"); + b.addDefinedValue(b.getRootCondition(), "f3"); + b.addDefinedValue(b.getRootCondition(), "g1a"); + b.addDefinedValue(b.getRootCondition(), "g2a"); + b.addDefinedValue(b.getRootCondition(), "g1b"); + b.addDefinedValue(b.getRootCondition(), "g2b"); + + // Check de-duplication + b.addCollapsedValues("a", "b"); + b.addCollapsedValues("b", "a"); + + // Check transitivity + b.addCollapsedValues("c", "d"); + b.addCollapsedValues("c", "e"); + + // Check globs + b.addCollapsedValues("f*"); + b.addCollapsedValues("g*a"); + + b.normalizeCollapsedValues(); + + List<SortedSet<String>> collapsedValues = b.getCollapsedValues(); + assertEquals(4, collapsedValues.size()); + assertEquals(Arrays.asList("a", "b"), new ArrayList<String>( + collapsedValues.get(0))); + assertEquals(Arrays.asList("c", "d", "e"), new ArrayList<String>( + collapsedValues.get(1))); + assertEquals(Arrays.asList("f1", "f2", "f3"), new ArrayList<String>( + collapsedValues.get(2))); + assertEquals(Arrays.asList("g1a", "g2a"), new ArrayList<String>( + collapsedValues.get(3))); + + // Collapse everything + b.addCollapsedValues("*"); + b.normalizeCollapsedValues(); + + collapsedValues = b.getCollapsedValues(); + assertEquals(1, collapsedValues.size()); + assertEquals(Arrays.asList(b.getDefinedValues()), new ArrayList<String>( + collapsedValues.get(0))); + } + public void testLinkerOrder() throws UnableToCompleteException { ModuleDef def = new ModuleDef("fake");
diff --git a/dev/core/test/com/google/gwt/dev/util/test/PropertyPermutationsTest.java b/dev/core/test/com/google/gwt/dev/util/test/PropertyPermutationsTest.java index de54d84..2c06394 100644 --- a/dev/core/test/com/google/gwt/dev/util/test/PropertyPermutationsTest.java +++ b/dev/core/test/com/google/gwt/dev/util/test/PropertyPermutationsTest.java
@@ -24,7 +24,9 @@ import junit.framework.TestCase; +import java.util.Arrays; import java.util.Iterator; +import java.util.List; import java.util.Set; /** @@ -90,6 +92,38 @@ assertEquals("true", perm[0]); } + public void testOneDimensionPermWithCollapse() { + ModuleDef md = new ModuleDef("testOneDimensionPerm"); + Properties props = md.getProperties(); + + { + BindingProperty prop = props.createBinding("debug"); + prop.addDefinedValue(prop.getRootCondition(), "false"); + prop.addDefinedValue(prop.getRootCondition(), "true"); + prop.addCollapsedValues("true", "false"); + } + + // Permutations and their values are in stable alphabetical order. + // + PropertyPermutations perms = new PropertyPermutations(md.getProperties(), + md.getActiveLinkerNames()); + + List<PropertyPermutations> collapsed = perms.collapseProperties(); + assertEquals("size", 1, collapsed.size()); + perms = collapsed.get(0); + + String[] perm; + Iterator<String[]> iter = perms.iterator(); + + assertTrue(iter.hasNext()); + perm = iter.next(); + assertEquals("false", perm[0]); + + assertTrue(iter.hasNext()); + perm = iter.next(); + assertEquals("true", perm[0]); + } + public void testTwoDimensionPerm() { ModuleDef md = new ModuleDef("testTwoDimensionPerm"); Properties props = md.getProperties(); @@ -145,6 +179,45 @@ assertEquals("opera", perm[1]); } + public void testTwoDimensionPermWithCollapse() { + ModuleDef md = new ModuleDef("testTwoDimensionPerm"); + Properties props = md.getProperties(); + + { + BindingProperty prop = props.createBinding("user.agent"); + prop.addDefinedValue(prop.getRootCondition(), "moz"); + prop.addDefinedValue(prop.getRootCondition(), "ie6"); + prop.addDefinedValue(prop.getRootCondition(), "opera"); + prop.addCollapsedValues("moz", "ie6", "opera"); + } + + { + BindingProperty prop = props.createBinding("debug"); + prop.addDefinedValue(prop.getRootCondition(), "false"); + prop.addDefinedValue(prop.getRootCondition(), "true"); + } + + // String[]s and their values are in stable alphabetical order. + // + PropertyPermutations perms = new PropertyPermutations(md.getProperties(), + md.getActiveLinkerNames()); + + List<PropertyPermutations> collapsed = perms.collapseProperties(); + assertEquals("size", 2, collapsed.size()); + + Iterator<String[]> it = collapsed.get(0).iterator(); + assertEquals(Arrays.asList("false", "ie6"), Arrays.asList(it.next())); + assertEquals(Arrays.asList("false", "moz"), Arrays.asList(it.next())); + assertEquals(Arrays.asList("false", "opera"), Arrays.asList(it.next())); + assertFalse(it.hasNext()); + + it = collapsed.get(1).iterator(); + assertEquals(Arrays.asList("true", "ie6"), Arrays.asList(it.next())); + assertEquals(Arrays.asList("true", "moz"), Arrays.asList(it.next())); + assertEquals(Arrays.asList("true", "opera"), Arrays.asList(it.next())); + assertFalse(it.hasNext()); + } + public void testTwoDimensionPermWithExpansion() { ModuleDef md = new ModuleDef("testTwoDimensionsWithExpansion"); Properties props = md.getProperties();
diff --git a/distro-source/core/src/gwt-module.dtd b/distro-source/core/src/gwt-module.dtd index 7916161..fd59093 100644 --- a/distro-source/core/src/gwt-module.dtd +++ b/distro-source/core/src/gwt-module.dtd
@@ -134,6 +134,17 @@ name CDATA #REQUIRED values CDATA #REQUIRED > +<!-- Collapse property values to produce soft permutations --> +<!ELEMENT collapse-property EMPTY> +<!ATTLIST collapse-property + name CDATA #REQUIRED + value CDATA #REQUIRED +> +<!-- Collapse all deferred-binding properties to produce a single permutation --> +<!ELEMENT collapse-all-properties EMPTY> +<!ATTLIST collapse-all-properties + value (true | false) "true" +> <!-- Add additional allowable values to a configuration property --> <!ELEMENT extend-configuration-property EMPTY> <!ATTLIST extend-configuration-property
diff --git a/user/src/com/google/gwt/junit/JUnit.gwt.xml b/user/src/com/google/gwt/junit/JUnit.gwt.xml index df4eadb..8963a34 100644 --- a/user/src/com/google/gwt/junit/JUnit.gwt.xml +++ b/user/src/com/google/gwt/junit/JUnit.gwt.xml
@@ -42,4 +42,6 @@ <inherits name="com.google.gwt.benchmarks.Benchmarks"/> + <!-- Speed up test compilations by producing one permutation --> + <collapse-all-properties /> </module>
diff --git a/user/test/com/google/gwt/core/ext/linker/impl/SelectionScriptLinkerUnitTest.java b/user/test/com/google/gwt/core/ext/linker/impl/SelectionScriptLinkerUnitTest.java index e6085cd..a1b3bf4 100644 --- a/user/test/com/google/gwt/core/ext/linker/impl/SelectionScriptLinkerUnitTest.java +++ b/user/test/com/google/gwt/core/ext/linker/impl/SelectionScriptLinkerUnitTest.java
@@ -34,6 +34,7 @@ import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; @@ -179,6 +180,7 @@ StandardCompilationResult result = new StandardCompilationResult( new MockPermutationResult()); result.addSelectionPermutation(new TreeMap<SelectionProperty, String>()); + result.addSoftPermutation(Collections.<SelectionProperty, String> emptyMap()); artifacts.add(result); ArtifactSet updated = new NonShardableSelectionScriptLinker().link(