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(