Collapse deferred-binding property values that depend on collapsed property values.
Patch by: bobv
Review by: rjrjr
Review at http://gwt-code-reviews.appspot.com/1525804
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10565 8db76d5a-ed1c-0410-87a9-c151d255dfc7
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 36cb777..039bcc2 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/BindingProperty.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/BindingProperty.java
@@ -31,10 +31,10 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
-import java.util.Map.Entry;
import java.util.regex.Pattern;
/**
@@ -48,13 +48,15 @@
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 Map<Condition, SortedSet<String>> conditionalValues =
+ new LinkedHashMap<Condition, SortedSet<String>>();
private final SortedSet<String> definedValues = new TreeSet<String>();
private PropertyProvider provider;
private Class<? extends PropertyProviderGenerator> providerGenerator;
private String fallback;
- private HashMap<String,LinkedList<LinkedHashSet<String>>> fallbackValueMap;
- private HashMap<String,LinkedList<String>> fallbackValues = new HashMap<String,LinkedList<String>>();
+ private HashMap<String, LinkedList<LinkedHashSet<String>>> fallbackValueMap;
+ private HashMap<String, LinkedList<String>> fallbackValues =
+ new HashMap<String, LinkedList<String>>();
private final ConditionAll rootCondition = new ConditionAll();
{
@@ -77,8 +79,7 @@
// Expanded in normalizeCollapsedValues()
continue;
} else if (!definedValues.contains(value)) {
- throw new IllegalArgumentException(
- "Attempting to collapse unknown value " + value);
+ throw new IllegalArgumentException("Attempting to collapse unknown value " + value);
}
}
@@ -99,7 +100,8 @@
}
/**
- * Adds fall back value to given property name.
+ * Adds fall back value to given property name.
+ *
* @param value the property value.
* @param fallbackValue the fall back value for given property value.
*/
@@ -164,13 +166,15 @@
}
/**
- * Returns the map of values to fall back values. the list of fall
- * back values is in decreasing order of preference.
+ * Returns the map of values to fall back values. the list of fall back values
+ * is in decreasing order of preference.
+ *
* @return map of property value to fall back values.
*/
- public Map<String,? extends List<? extends Set<String>>> getFallbackValuesMap() {
+ public Map<String, ? extends List<? extends Set<String>>> getFallbackValuesMap() {
if (fallbackValueMap == null) {
- HashMap<String,LinkedList<LinkedHashSet<String>>> valuesMap = new HashMap<String,LinkedList<LinkedHashSet<String>>>();
+ HashMap<String, LinkedList<LinkedHashSet<String>>> valuesMap =
+ new HashMap<String, LinkedList<LinkedHashSet<String>>>();
// compute closure of fall back values preserving order
for (Entry<String, LinkedList<String>> e : fallbackValues.entrySet()) {
String from = e.getKey();
@@ -179,8 +183,7 @@
LinkedList<String> childList = fallbackValues.get(from);
LinkedHashSet<String> children = new LinkedHashSet<String>();
children.addAll(childList);
- while (children != null && children.size() > 0)
- {
+ while (children != null && children.size() > 0) {
alternates.add(children);
LinkedHashSet<String> newChildren = new LinkedHashSet<String>();
for (String child : children) {
@@ -199,7 +202,7 @@
}
return fallbackValueMap;
}
-
+
public PropertyProvider getProvider() {
return provider;
}
@@ -262,7 +265,7 @@
* the currently-defined values.
*
* @throws IllegalArgumentException if any of the provided values were not
- * provided to {@link #addDefinedValue(Condition,String)}.
+ * provided to {@link #addDefinedValue(Condition,String)}.
*/
public void setAllowedValues(Condition condition, String... values) {
SortedSet<String> temp = new TreeSet<String>(Arrays.asList(values));
@@ -308,6 +311,29 @@
* Create a minimal number of equivalence sets, expanding any glob patterns.
*/
void normalizeCollapsedValues() {
+ /*
+ * Properties that depend upon a collapsed property value must be recombined
+ * into an equivalence set.
+ */
+ if (!getRequiredProperties().isEmpty()) {
+ // This defines additional equivalence sets
+ Map<String, SortedSet<String>> requiredPropertyNamesToDependentValues =
+ new HashMap<String, SortedSet<String>>();
+ for (Map.Entry<Condition, SortedSet<String>> entry : getConditionalValues().entrySet()) {
+ for (String requiredPropertyName : entry.getKey().getRequiredProperties()) {
+ SortedSet<String> set = requiredPropertyNamesToDependentValues.get(requiredPropertyName);
+ if (set == null) {
+ set = new TreeSet<String>();
+ requiredPropertyNamesToDependentValues.put(requiredPropertyName, set);
+ }
+ set.addAll(entry.getValue());
+ }
+ }
+ for (SortedSet<String> valuesToCollapse : requiredPropertyNamesToDependentValues.values()) {
+ addCollapsedValues(valuesToCollapse.toArray(new String[valuesToCollapse.size()]));
+ }
+ }
+
if (collapsedValues.isEmpty()) {
return;
}
@@ -368,8 +394,8 @@
}
// The values of the maps will now contain the minimal number of sets
- collapsedValues = new ArrayList<SortedSet<String>>(
- new IdentityHashSet<SortedSet<String>>(map.values()));
+ collapsedValues =
+ new ArrayList<SortedSet<String>>(new IdentityHashSet<SortedSet<String>>(map.values()));
// Sort the list
Lists.sort(collapsedValues, new Comparator<SortedSet<String>>() {
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 48dfef1..58a4018 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/PropertyPermutations.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/PropertyPermutations.java
@@ -61,12 +61,12 @@
* We delete items from this set, but want to retain the original order as
* much as possible.
*/
- Set<BindingProperty> bindingProps = new LinkedHashSet<BindingProperty>(
- properties.getBindingProperties());
+ Set<BindingProperty> bindingProps =
+ new LinkedHashSet<BindingProperty>(properties.getBindingProperties());
// Accumulates the order in which the properties should be evaluated
- Map<String, BindingProperty> evaluationOrder = new LinkedHashMap<String, BindingProperty>(
- bindingProps.size());
+ Map<String, BindingProperty> evaluationOrder =
+ new LinkedHashMap<String, BindingProperty>(bindingProps.size());
/*
* Insert a property after all of the properties that it depends upon have
@@ -87,19 +87,16 @@
}
if (!changed) {
- throw new IllegalStateException(
- "Cycle detected within remaining property dependencies "
- + bindingProps.toString());
+ throw new IllegalStateException("Cycle detected within remaining property dependencies "
+ + bindingProps.toString());
}
}
- return evaluationOrder.values().toArray(
- new BindingProperty[evaluationOrder.size()]);
+ return evaluationOrder.values().toArray(new BindingProperty[evaluationOrder.size()]);
}
- private static void permute(BindingProperty[] properties,
- Set<String> activeLinkerNames, String[] soFar, int whichProp,
- List<String[]> permutations) {
+ private static void permute(BindingProperty[] properties, Set<String> activeLinkerNames,
+ String[] soFar, int whichProp, List<String[]> permutations) {
int lastProp = properties.length - 1;
BindingProperty prop = properties[whichProp];
@@ -111,18 +108,17 @@
} else {
BindingProperty[] answerable = new BindingProperty[soFar.length];
System.arraycopy(properties, 0, answerable, 0, soFar.length);
- PropertyOracle propertyOracle = new StaticPropertyOracle(answerable,
- soFar, new ConfigurationProperty[0]);
+ PropertyOracle propertyOracle =
+ new StaticPropertyOracle(answerable, soFar, new ConfigurationProperty[0]);
for (Condition cond : prop.getConditionalValues().keySet()) {
try {
- if (cond.isTrue(TreeLogger.NULL, new DeferredBindingQuery(
- propertyOracle, activeLinkerNames))) {
+ if (cond.isTrue(TreeLogger.NULL, new DeferredBindingQuery(propertyOracle,
+ activeLinkerNames))) {
winner = cond;
}
} catch (UnableToCompleteException e) {
- throw new IllegalStateException(
- "Should never get here for simple properties", e);
+ throw new IllegalStateException("Should never get here for simple properties", e);
}
}
}
@@ -140,8 +136,7 @@
nextStep[whichProp] = knownValue;
if (whichProp < lastProp) {
- permute(properties, activeLinkerNames, nextStep, whichProp + 1,
- permutations);
+ permute(properties, activeLinkerNames, nextStep, whichProp + 1, permutations);
} else {
// Finished this permutation.
permutations.add(nextStep);
@@ -152,14 +147,12 @@
private final Properties properties;
private final List<String[]> values;
- public PropertyPermutations(Properties properties,
- Set<String> activeLinkerNames) {
+ public PropertyPermutations(Properties properties, Set<String> activeLinkerNames) {
this.properties = properties;
this.values = allPermutationsOf(properties, activeLinkerNames);
}
- public PropertyPermutations(PropertyPermutations allPermutations,
- int firstPerm, int numPerms) {
+ public PropertyPermutations(PropertyPermutations allPermutations, int firstPerm, int numPerms) {
this.properties = allPermutations.properties;
values = allPermutations.values.subList(firstPerm, firstPerm + numPerms);
}
@@ -167,8 +160,7 @@
/**
* Copy constructor that allows the list of property values to be reset.
*/
- public PropertyPermutations(PropertyPermutations allPermutations,
- List<String[]> values) {
+ public PropertyPermutations(PropertyPermutations allPermutations, List<String[]> values) {
this.properties = allPermutations.properties;
this.values = values;
}
@@ -180,17 +172,18 @@
*/
public List<PropertyPermutations> collapseProperties() {
// Collate property values in this map
- SortedMap<CollapsedPropertyKey, List<String[]>> map = new TreeMap<CollapsedPropertyKey, List<String[]>>();
+ 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);
+ StaticPropertyOracle oracle =
+ new StaticPropertyOracle(getOrderedProperties(), propertyValues,
+ new ConfigurationProperty[0]);
+ CollapsedPropertyKey key = new CollapsedPropertyKey(oracle);
List<String[]> list = map.get(key);
if (list == null) {
@@ -201,8 +194,7 @@
}
// Return the collated values
- List<PropertyPermutations> toReturn = new ArrayList<PropertyPermutations>(
- map.size());
+ List<PropertyPermutations> toReturn = new ArrayList<PropertyPermutations>(map.size());
for (List<String[]> list : map.values()) {
toReturn.add(new PropertyPermutations(this, list));
}
@@ -230,4 +222,34 @@
public int size() {
return values.size();
}
+
+ /**
+ * For debugging use only.
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("{");
+ boolean needsComma = false;
+ for (String[] strings : values) {
+ if (needsComma) {
+ sb.append(", ");
+ } else {
+ needsComma = true;
+ }
+ sb.append("[");
+ boolean innerNeedsComma = false;
+ for (String string : strings) {
+ if (innerNeedsComma) {
+ sb.append(", ");
+ } else {
+ innerNeedsComma = true;
+ }
+ sb.append(string);
+ }
+ sb.append("]");
+ }
+ sb.append("}");
+ return sb.toString();
+ }
}
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 9827bd4..8fcf69a 100644
--- a/dev/core/test/com/google/gwt/dev/cfg/ModuleDefTest.java
+++ b/dev/core/test/com/google/gwt/dev/cfg/ModuleDefTest.java
@@ -21,13 +21,14 @@
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.linker.ArtifactSet;
import com.google.gwt.core.ext.linker.LinkerOrder;
-import com.google.gwt.core.ext.linker.Shardable;
import com.google.gwt.core.ext.linker.LinkerOrder.Order;
+import com.google.gwt.core.ext.linker.Shardable;
import junit.framework.TestCase;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.SortedSet;
@@ -44,14 +45,13 @@
}
@Override
- public ArtifactSet link(TreeLogger logger, LinkerContext context,
- ArtifactSet artifacts, boolean onePermutation) {
+ public ArtifactSet link(TreeLogger logger, LinkerContext context, ArtifactSet artifacts,
+ boolean onePermutation) {
return null;
}
@Override
- public ArtifactSet relink(TreeLogger logger, LinkerContext context,
- ArtifactSet newArtifacts) {
+ public ArtifactSet relink(TreeLogger logger, LinkerContext context, ArtifactSet newArtifacts) {
return null;
}
}
@@ -120,14 +120,10 @@
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)));
+ 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("*");
@@ -135,8 +131,79 @@
collapsedValues = b.getCollapsedValues();
assertEquals(1, collapsedValues.size());
- assertEquals(Arrays.asList(b.getDefinedValues()), new ArrayList<String>(
- collapsedValues.get(0)));
+ assertEquals(Arrays.asList(b.getDefinedValues()), new ArrayList<String>(collapsedValues.get(0)));
+ }
+
+ /**
+ * This tests the behavior of properties that depend on other properties with
+ * collapsed values. A dependency on a collapsed value forms an equivalent set
+ * for the dependent values. In other words, collapsing is contagious.
+ */
+ public void testCollapsedPropertiesWithDependencies() {
+ ModuleDef def = new ModuleDef("fake");
+ Properties p = def.getProperties();
+ // <define-property name="p1" values="a,b,c" />
+ BindingProperty p1 = p.createBinding("p1");
+ p1.addDefinedValue(p1.getRootCondition(), "a");
+ p1.addDefinedValue(p1.getRootCondition(), "b");
+ p1.addDefinedValue(p1.getRootCondition(), "c");
+ // <collapse-property name="p1" values="b,c" />
+ p1.addCollapsedValues("b", "c");
+
+ BindingProperty p2 = p.createBinding("p2");
+ // <define-property name="p2" values="d,e,f" />
+ p2.addDefinedValue(p2.getRootCondition(), "d");
+ p2.addDefinedValue(p2.getRootCondition(), "e");
+ p2.addDefinedValue(p2.getRootCondition(), "f");
+ /*-
+ * <set-property name="p2" values="e,f">
+ * <when-property-is name="p1" value="c" />
+ * </set-property>
+ */
+ p2.setAllowedValues(new ConditionWhenPropertyIs("p1", "c"), "e", "f");
+
+ /*
+ * The net effect of the above is to delete the [c,d] permutation and to
+ * make the e and f values dependent on the c value. Because c is a
+ * collapsed value, e and f are then automatically collapsed together when
+ * p2 is normalized.
+ */
+
+ p1.normalizeCollapsedValues();
+ p2.normalizeCollapsedValues();
+
+ PropertyPermutations perms = new PropertyPermutations(p, Collections.<String> emptySet());
+ List<PropertyPermutations> hardPerms = perms.collapseProperties();
+
+ assertEquals(4, hardPerms.size());
+ {
+ // {[a,d]}
+ PropertyPermutations perm = hardPerms.get(0);
+ assertEquals(1, perm.size());
+ assertEquals(Arrays.asList("a", "d"), Arrays.asList(perm.getOrderedPropertyValues(0)));
+ }
+ {
+ // {[a,e], [a,f]}
+ PropertyPermutations perm = hardPerms.get(1);
+ assertEquals(2, perm.size());
+ assertEquals(Arrays.asList("a", "e"), Arrays.asList(perm.getOrderedPropertyValues(0)));
+ assertEquals(Arrays.asList("a", "f"), Arrays.asList(perm.getOrderedPropertyValues(1)));
+ }
+ {
+ // {[b,d]}
+ PropertyPermutations perm = hardPerms.get(2);
+ assertEquals(1, perm.size());
+ assertEquals(Arrays.asList("b", "d"), Arrays.asList(perm.getOrderedPropertyValues(0)));
+ }
+ {
+ // {[b,e], [b,f], [c,e], [c,f]}
+ PropertyPermutations perm = hardPerms.get(3);
+ assertEquals(4, perm.size());
+ assertEquals(Arrays.asList("b", "e"), Arrays.asList(perm.getOrderedPropertyValues(0)));
+ assertEquals(Arrays.asList("b", "f"), Arrays.asList(perm.getOrderedPropertyValues(1)));
+ assertEquals(Arrays.asList("c", "e"), Arrays.asList(perm.getOrderedPropertyValues(2)));
+ assertEquals(Arrays.asList("c", "f"), Arrays.asList(perm.getOrderedPropertyValues(3)));
+ }
}
public void testLinkerOrder() throws UnableToCompleteException {
@@ -154,13 +221,14 @@
def.addLinker("post2");
def.addLinker("primary");
- Class<?>[] expectedClasses = {
- FakeLinkerPre2.class, FakeLinkerPre.class, FakeLinkerPost.class,
- FakeLinkerPost2.class, FakeLinkerPrimary.class};
+ Class<?>[] expectedClasses =
+ {
+ FakeLinkerPre2.class, FakeLinkerPre.class, FakeLinkerPost.class, FakeLinkerPost2.class,
+ FakeLinkerPrimary.class};
assertEquals(FakeLinkerPrimary.class, def.getActivePrimaryLinker());
// Test iteration order
- assertEquals(Arrays.asList(expectedClasses),
- new ArrayList<Class<? extends Linker>>(def.getActiveLinkers()));
+ assertEquals(Arrays.asList(expectedClasses), new ArrayList<Class<? extends Linker>>(def
+ .getActiveLinkers()));
}
public void testLinkerRedefinition() throws UnableToCompleteException {
@@ -179,12 +247,12 @@
// Intentional duplication
def.addLinker("post");
- Class<?>[] expectedClasses = {
- FakeLinkerPre2.class, FakeLinkerPost2.class, FakeLinkerPrimary2.class};
+ Class<?>[] expectedClasses =
+ {FakeLinkerPre2.class, FakeLinkerPost2.class, FakeLinkerPrimary2.class};
assertEquals(FakeLinkerPrimary2.class, def.getActivePrimaryLinker());
// Test iteration order
- assertEquals(Arrays.asList(expectedClasses),
- new ArrayList<Class<? extends Linker>>(def.getActiveLinkers()));
+ assertEquals(Arrays.asList(expectedClasses), new ArrayList<Class<? extends Linker>>(def
+ .getActiveLinkers()));
}
public void testLinkerRedefinitionErrors() throws UnableToCompleteException {
@@ -230,12 +298,12 @@
def.addLinker("primary");
def.addLinker("primary2");
- Class<?>[] expectedClasses = {
- FakeLinkerPre.class, FakeLinkerPost.class, FakeLinkerPrimary2.class};
+ Class<?>[] expectedClasses =
+ {FakeLinkerPre.class, FakeLinkerPost.class, FakeLinkerPrimary2.class};
assertEquals(FakeLinkerPrimary2.class, def.getActivePrimaryLinker());
// Test iteration order
- assertEquals(Arrays.asList(expectedClasses),
- new ArrayList<Class<? extends Linker>>(def.getActiveLinkers()));
+ assertEquals(Arrays.asList(expectedClasses), new ArrayList<Class<? extends Linker>>(def
+ .getActiveLinkers()));
}
public void testValidModuleName() {
@@ -244,15 +312,15 @@
assertFalse(ModuleDef.isValidModuleName("com..Foo"));
assertFalse(ModuleDef.isValidModuleName("com.7.Foo"));
assertFalse(ModuleDef.isValidModuleName("com.7foo.Foo"));
-
+
assertTrue(ModuleDef.isValidModuleName("com.foo.Foo"));
assertTrue(ModuleDef.isValidModuleName("com.$foo.Foo"));
assertTrue(ModuleDef.isValidModuleName("com._foo.Foo"));
assertTrue(ModuleDef.isValidModuleName("com.foo7.Foo"));
-
- // For legacy reasons, allow the last part of the name is not
- // required to be a valid ident. In the past, naming rules
- // were enforced for top level modules, but not nested modules.
+
+ // For legacy reasons, allow the last part of the name is not
+ // required to be a valid ident. In the past, naming rules
+ // were enforced for top level modules, but not nested modules.
assertTrue(ModuleDef.isValidModuleName("com.foo.F-oo"));
assertTrue(ModuleDef.isValidModuleName("com.foo.7Foo"));
assertTrue(ModuleDef.isValidModuleName("com.foo.+Foo"));