Implement conditional <set-property> module directives.

Patch by: bobv
Review by: bruce

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5970 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/SelectionProperty.java b/dev/core/src/com/google/gwt/core/ext/linker/SelectionProperty.java
index e0f17bf..8940edd 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/SelectionProperty.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/SelectionProperty.java
@@ -23,8 +23,8 @@
  * 
  * SelectionProperty implementations must support object identity comparisons.
  * 
- * @see com.google.gwt.core.ext.SelectionProperty A similarly-named interface used
- * in generators.
+ * @see com.google.gwt.core.ext.SelectionProperty A similarly-named interface
+ *      used in generators.
  */
 public interface SelectionProperty {
   /**
@@ -44,6 +44,13 @@
   String getPropertyProvider();
 
   /**
+   * Returns <code>true</code> if the value of the SelectionProperty is always
+   * derived from other SelectionProperties and, as a consequence, the property
+   * provider never needs to be evaluated.
+   */
+  boolean isDerived();
+
+  /**
    * Returns the defined value for the deferred binding property or
    * <code>null</code> if the value of the property is not constant.
    * 
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 62a8cf9..cad65aa 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
@@ -176,7 +176,7 @@
   protected String generatePropertyProvider(SelectionProperty prop) {
     StringBuffer toReturn = new StringBuffer();
 
-    if (prop.tryGetValue() == null) {
+    if (prop.tryGetValue() == null && !prop.isDerived()) {
       toReturn.append("providers['" + prop.getName() + "'] = function()");
       toReturn.append(prop.getPropertyProvider());
       toReturn.append(";");
@@ -315,6 +315,8 @@
         for (SelectionProperty p : context.getProperties()) {
           if (p.tryGetValue() != null) {
             continue;
+          } else if (p.isDerived()) {
+            continue;
           }
           if (needsIndexMarkers) {
             text.append("][");
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardSelectionProperty.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardSelectionProperty.java
index b3ed31f..92b8670 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardSelectionProperty.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardSelectionProperty.java
@@ -31,20 +31,18 @@
   private static final String FALLBACK_TOKEN = "/*-FALLBACK-*/";
 
   private final String activeValue;
+  private final boolean isDerived;
   private final String name;
   private final String provider;
   private final SortedSet<String> values;
 
   public StandardSelectionProperty(BindingProperty p) {
-    if (p.getAllowedValues().length == 1) {
-      activeValue = p.getAllowedValues()[0];
-    } else {
-      activeValue = null;
-    }
+    activeValue = p.getConstrainedValue();
+    isDerived = p.isDerived();
     name = p.getName();
     String fallback = p.getFallback();
-    provider = p.getProvider() == null ? null : 
-        p.getProvider().getBody().replace(FALLBACK_TOKEN, fallback);
+    provider = p.getProvider() == null ? null
+        : p.getProvider().getBody().replace(FALLBACK_TOKEN, fallback);
     values = Collections.unmodifiableSortedSet(new TreeSet<String>(
         Arrays.asList(p.getDefinedValues())));
   }
@@ -61,6 +59,10 @@
     return provider;
   }
 
+  public boolean isDerived() {
+    return isDerived;
+  }
+
   @Override
   public String toString() {
     StringBuffer b = new StringBuffer();
diff --git a/dev/core/src/com/google/gwt/dev/Link.java b/dev/core/src/com/google/gwt/dev/Link.java
index 2501a60..1305ccf 100644
--- a/dev/core/src/com/google/gwt/dev/Link.java
+++ b/dev/core/src/com/google/gwt/dev/Link.java
@@ -253,6 +253,12 @@
            * values.
            */
           continue;
+        } else if (key.isDerived()) {
+          /*
+           * The property provider does not need to be invoked, because the
+           * value is determined entirely by other properties.
+           */
+          continue;
         }
         unboundProperties.put(key, orderedPropValues[i]);
       }
diff --git a/dev/core/src/com/google/gwt/dev/Precompile.java b/dev/core/src/com/google/gwt/dev/Precompile.java
index e137fea..a529f16 100644
--- a/dev/core/src/com/google/gwt/dev/Precompile.java
+++ b/dev/core/src/com/google/gwt/dev/Precompile.java
@@ -402,8 +402,9 @@
   public static Precompilation precompile(TreeLogger logger,
       JJSOptions jjsOptions, ModuleDef module, File genDir,
       File generatorResourcesDir, File dumpSignatureFile) {
-    return precompile(logger, jjsOptions, module, 0, 0,
-        module.getProperties().numPermutations(), genDir,
+    PropertyPermutations allPermutations = new PropertyPermutations(
+        module.getProperties());
+    return precompile(logger, jjsOptions, module, 0, allPermutations, genDir,
         generatorResourcesDir, dumpSignatureFile);
   }
 
@@ -458,8 +459,8 @@
 
   private static Precompilation precompile(TreeLogger logger,
       JJSOptions jjsOptions, ModuleDef module, int permutationBase,
-      int firstPerm, int numPerms, File genDir, File generatorResourcesDir,
-      File dumpSignatureFile) {
+      PropertyPermutations allPermutations, File genDir,
+      File generatorResourcesDir, File dumpSignatureFile) {
 
     try {
       CompilationState compilationState = module.getCompilationState(logger);
@@ -477,10 +478,7 @@
 
       ArtifactSet generatedArtifacts = new ArtifactSet();
       DistillerRebindPermutationOracle rpo = new DistillerRebindPermutationOracle(
-          module,
-          compilationState,
-          generatedArtifacts,
-          new PropertyPermutations(module.getProperties(), firstPerm, numPerms),
+          module, compilationState, generatedArtifacts, allPermutations,
           genDir, generatorResourcesDir);
       PerfLogger.start("Precompile");
       UnifiedAst unifiedAst = JavaToJavaScriptCompiler.precompile(logger,
@@ -556,7 +554,9 @@
       } else {
         TreeLogger branch = logger.branch(TreeLogger.INFO,
             "Precompiling module " + module.getName());
-        int potentialPermutations = module.getProperties().numPermutations();
+        PropertyPermutations allPermutations = new PropertyPermutations(
+            module.getProperties());
+        int potentialPermutations = allPermutations.size();
         int permutationsPerIteration = options.getMaxPermsPerPrecompile();
 
         if (permutationsPerIteration <= 0) {
@@ -594,10 +594,13 @@
             options.setCompilationStateRetained(originalCompilationStateRetained);
           }
 
+          // Select only the range of property permutations that we want
+          PropertyPermutations localPermutations = new PropertyPermutations(
+              allPermutations, potentialFirstPerm, numPermsToPrecompile);
+
           Precompilation precompilation = precompile(branch, options, module,
-              actualPermutations, potentialFirstPerm, numPermsToPrecompile,
-              options.getGenDir(), compilerWorkDir,
-              options.getDumpSignatureFile());
+              actualPermutations, localPermutations, options.getGenDir(),
+              compilerWorkDir, options.getDumpSignatureFile());
           if (precompilation == null) {
             branch.log(TreeLogger.ERROR, "Precompilation failed");
             return false;
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 57a03c3..6eb07a5 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/BindingProperty.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/BindingProperty.java
@@ -15,7 +15,13 @@
  */
 package com.google.gwt.dev.cfg;
 
+import com.google.gwt.dev.util.collect.Sets;
+
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
 import java.util.SortedSet;
 import java.util.TreeSet;
 
@@ -28,18 +34,15 @@
 public class BindingProperty extends Property {
 
   private static final String EMPTY = "";
-  
-  private SortedSet<String> allowedValues;
+
+  private final Map<Condition, SortedSet<String>> conditionalValues = new LinkedHashMap<Condition, SortedSet<String>>();
   private final SortedSet<String> definedValues = new TreeSet<String>();
   private PropertyProvider provider;
   private String fallback;
+  private final ConditionAll rootCondition = new ConditionAll();
 
   {
-    /*
-     * This is initially an alias for definedValues and is only set differently
-     * if the user calls setAllowedValues().
-     */
-    allowedValues = definedValues;
+    conditionalValues.put(rootCondition, new TreeSet<String>());
   }
 
   public BindingProperty(String name) {
@@ -47,17 +50,48 @@
     fallback = EMPTY;
   }
 
-  public void addDefinedValue(String newValue) {
+  public void addDefinedValue(Condition condition, String newValue) {
     definedValues.add(newValue);
+    SortedSet<String> set = conditionalValues.get(condition);
+    if (set == null) {
+      set = new TreeSet<String>();
+      set.addAll(conditionalValues.get(rootCondition));
+      conditionalValues.put(condition, set);
+    }
+    set.add(newValue);
   }
 
   /**
-   * Returns the set of allowed values in sorted order.
+   * Returns the set of allowed values in sorted order when a certain condition
+   * is satisfied.
    */
-  public String[] getAllowedValues() {
+  public String[] getAllowedValues(Condition condition) {
+    Set<String> allowedValues = conditionalValues.get(condition);
     return allowedValues.toArray(new String[allowedValues.size()]);
   }
 
+  public Map<Condition, SortedSet<String>> getConditionalValues() {
+    return Collections.unmodifiableMap(conditionalValues);
+  }
+
+  /**
+   * If the BindingProperty has exactly one value across all conditions and
+   * permutations, return that value otherwise return <code>null</code>.
+   */
+  public String getConstrainedValue() {
+    String constrainedValue = null;
+    for (SortedSet<String> allowedValues : conditionalValues.values()) {
+      if (allowedValues.size() != 1) {
+        return null;
+      } else if (constrainedValue == null) {
+        constrainedValue = allowedValues.iterator().next();
+      } else if (!constrainedValue.equals(allowedValues.iterator().next())) {
+        return null;
+      }
+    }
+    return constrainedValue;
+  }
+
   /**
    * Returns the set of defined values in sorted order.
    */
@@ -68,12 +102,33 @@
   public String getFallback() {
     return fallback;
   }
+
   public PropertyProvider getProvider() {
     return provider;
   }
 
+  public Set<String> getRequiredProperties() {
+    Set<String> toReturn = Sets.create();
+    for (Condition cond : conditionalValues.keySet()) {
+      toReturn = Sets.addAll(toReturn, cond.getRequiredProperties());
+    }
+    return toReturn;
+  }
+
+  public ConditionAll getRootCondition() {
+    return rootCondition;
+  }
+
+  /**
+   * Returns true if the supplied value is legal under some condition.
+   */
   public boolean isAllowedValue(String value) {
-    return allowedValues.contains(value);
+    for (Set<String> values : conditionalValues.values()) {
+      if (values.contains(value)) {
+        return true;
+      }
+    }
+    return false;
   }
 
   /**
@@ -86,20 +141,47 @@
   }
 
   /**
+   * Returns <code>true</code> if the value of this BindingProperty is always
+   * derived from other BindingProperties. That is, for each Condition in the
+   * BindingProperty, there is exactly one allowed value.
+   */
+  public boolean isDerived() {
+    for (Set<String> allowedValues : conditionalValues.values()) {
+      if (allowedValues.size() != 1) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /**
    * Set the currently allowed values. The values provided must be a subset of
    * the currently-defined values.
    * 
    * @throws IllegalArgumentException if any of the provided values were not
    *           provided to {@link #addDefinedValue(String)}.
    */
-  public void setAllowedValues(String... values) {
+  public void setAllowedValues(Condition condition, String... values) {
     SortedSet<String> temp = new TreeSet<String>(Arrays.asList(values));
     if (!definedValues.containsAll(temp)) {
       throw new IllegalArgumentException(
           "Attempted to set an allowed value that was not previously defined");
     }
 
-    allowedValues = temp;
+    // XML has a last-one-wins semantic which we reflect in our evaluation order
+    if (condition == rootCondition) {
+      /*
+       * An unconditional set-property would undo any previous conditional
+       * setters, so we can just clear out this map.
+       */
+      conditionalValues.clear();
+    } else {
+      /*
+       * Otherwise, we'll just ensure that this condition is moved to the end.
+       */
+      conditionalValues.remove(condition);
+    }
+    conditionalValues.put(condition, temp);
   }
 
   public void setFallback(String token) {
diff --git a/dev/core/src/com/google/gwt/dev/cfg/CompoundCondition.java b/dev/core/src/com/google/gwt/dev/cfg/CompoundCondition.java
index b9ff8de..08aecd6 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/CompoundCondition.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/CompoundCondition.java
@@ -15,6 +15,11 @@
  */
 package com.google.gwt.dev.cfg;
 
+import com.google.gwt.dev.util.collect.Sets;
+
+import java.util.Iterator;
+import java.util.Set;
+
 /**
  * Abstract base class for various kinds of compound deferred binding
  * conditions.
@@ -26,4 +31,13 @@
   public Conditions getConditions() {
     return conditions;
   }
+
+  @Override
+  public Set<String> getRequiredProperties() {
+    Set<String> toReturn = Sets.create();
+    for (Iterator<Condition> it = conditions.iterator(); it.hasNext();) {
+      toReturn = Sets.addAll(toReturn, it.next().getRequiredProperties());
+    }
+    return toReturn;
+  }
 }
diff --git a/dev/core/src/com/google/gwt/dev/cfg/Condition.java b/dev/core/src/com/google/gwt/dev/cfg/Condition.java
index e93c1cc..0ff89e7 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/Condition.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/Condition.java
@@ -15,16 +15,28 @@
  */
 package com.google.gwt.dev.cfg;
 
-import com.google.gwt.core.ext.GeneratorContext;
+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.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.dev.util.collect.Sets;
+
+import java.util.Set;
 
 /**
  * Abstract base class for various kinds of deferred binding conditions.
  */
 public abstract class Condition {
-  public final boolean isTrue(TreeLogger logger, GeneratorContext context,
-      String testType) throws UnableToCompleteException {
+  /**
+   * Returns the set of property names that the Condition requires in order to
+   * be evaluated.
+   */
+  public Set<String> getRequiredProperties() {
+    return Sets.create();
+  }
+
+  public final boolean isTrue(TreeLogger logger, PropertyOracle propertyOracle,
+      TypeOracle typeOracle, String testType) throws UnableToCompleteException {
 
     boolean logDebug = logger.isLoggable(TreeLogger.DEBUG);
 
@@ -33,7 +45,7 @@
       logger = logger.branch(TreeLogger.DEBUG, startMsg, null);
     }
 
-    boolean result = doEval(logger, context, testType);
+    boolean result = doEval(logger, propertyOracle, typeOracle, testType);
 
     if (logDebug) {
       String afterMsg = getEvalAfterMessage(testType, result);
@@ -44,7 +56,7 @@
   }
 
   protected abstract boolean doEval(TreeLogger logger,
-      GeneratorContext context, String testType)
+      PropertyOracle propertyOracle, TypeOracle typeOracle, String testType)
       throws UnableToCompleteException;
 
   protected abstract String getEvalAfterMessage(String testType, boolean result);
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ConditionAll.java b/dev/core/src/com/google/gwt/dev/cfg/ConditionAll.java
index e8f948e..94a47cb 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ConditionAll.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ConditionAll.java
@@ -15,9 +15,10 @@
  */
 package com.google.gwt.dev.cfg;
 
-import com.google.gwt.core.ext.GeneratorContext;
+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.core.ext.typeinfo.TypeOracle;
 
 import java.util.Iterator;
 
@@ -30,11 +31,11 @@
   public ConditionAll() {
   }
 
-  protected boolean doEval(TreeLogger logger, GeneratorContext context,
-      String testType) throws UnableToCompleteException {
-    for (Iterator iter = getConditions().iterator(); iter.hasNext();) {
-      Condition condition = (Condition) iter.next();
-      if (!condition.isTrue(logger, context, testType)) {
+  protected boolean doEval(TreeLogger logger, PropertyOracle propertyOracle,
+      TypeOracle typeOracle, String testType) throws UnableToCompleteException {
+    for (Iterator<Condition> iter = getConditions().iterator(); iter.hasNext();) {
+      Condition condition = iter.next();
+      if (!condition.isTrue(logger, propertyOracle, typeOracle, testType)) {
         return false;
       }
     }
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ConditionAny.java b/dev/core/src/com/google/gwt/dev/cfg/ConditionAny.java
index 7273f12..edd34c0 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ConditionAny.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ConditionAny.java
@@ -15,9 +15,10 @@
  */
 package com.google.gwt.dev.cfg;
 
-import com.google.gwt.core.ext.GeneratorContext;
+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.core.ext.typeinfo.TypeOracle;
 
 import java.util.Iterator;
 
@@ -29,11 +30,11 @@
   public ConditionAny() {
   }
 
-  protected boolean doEval(TreeLogger logger, GeneratorContext context,
-      String testType) throws UnableToCompleteException {
-    for (Iterator iter = getConditions().iterator(); iter.hasNext();) {
-      Condition condition = (Condition) iter.next();
-      if (condition.isTrue(logger, context, testType)) {
+  protected boolean doEval(TreeLogger logger, PropertyOracle propertyOracle,
+      TypeOracle typeOracle, String testType) throws UnableToCompleteException {
+    for (Iterator<Condition> iter = getConditions().iterator(); iter.hasNext();) {
+      Condition condition = iter.next();
+      if (condition.isTrue(logger, propertyOracle, typeOracle, testType)) {
         return true;
       }
     }
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ConditionNone.java b/dev/core/src/com/google/gwt/dev/cfg/ConditionNone.java
index 5134914..5199a7c 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ConditionNone.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ConditionNone.java
@@ -15,9 +15,10 @@
  */
 package com.google.gwt.dev.cfg;
 
-import com.google.gwt.core.ext.GeneratorContext;
+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.core.ext.typeinfo.TypeOracle;
 
 import java.util.Iterator;
 
@@ -30,11 +31,11 @@
   public ConditionNone() {
   }
 
-  protected boolean doEval(TreeLogger logger, GeneratorContext context,
-      String testType) throws UnableToCompleteException {
-    for (Iterator iter = getConditions().iterator(); iter.hasNext();) {
+  protected boolean doEval(TreeLogger logger, PropertyOracle propertyOracle,
+      TypeOracle typeOracle, String testType) throws UnableToCompleteException {
+    for (Iterator<Condition> iter = getConditions().iterator(); iter.hasNext();) {
       Condition condition = (Condition) iter.next();
-      if (condition.isTrue(logger, context, testType)) {
+      if (condition.isTrue(logger, propertyOracle, typeOracle, testType)) {
         return false;
       }
     }
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ConditionWhenPropertyIs.java b/dev/core/src/com/google/gwt/dev/cfg/ConditionWhenPropertyIs.java
index 566c24d..ac8205d 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ConditionWhenPropertyIs.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ConditionWhenPropertyIs.java
@@ -17,11 +17,14 @@
 
 import com.google.gwt.core.ext.BadPropertyValueException;
 import com.google.gwt.core.ext.ConfigurationProperty;
-import com.google.gwt.core.ext.GeneratorContext;
 import com.google.gwt.core.ext.PropertyOracle;
 import com.google.gwt.core.ext.SelectionProperty;
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.dev.util.collect.Sets;
+
+import java.util.Set;
 
 /**
  * A deferred binding condition to determine whether a named property has a
@@ -38,22 +41,25 @@
     this.value = value;
   }
 
+  @Override
+  public Set<String> getRequiredProperties() {
+    return Sets.create(propName);
+  }
+
   public String toString() {
     return "<when-property-is name='" + propName + "' value='" + value + "'/>";
   }
 
-  protected boolean doEval(TreeLogger logger, GeneratorContext context,
-      String testType) throws UnableToCompleteException {
+  protected boolean doEval(TreeLogger logger, PropertyOracle propertyOracle,
+      TypeOracle typeOracle, String testType) throws UnableToCompleteException {
     String testValue;
     try {
-      PropertyOracle propertyOracle = context.getPropertyOracle();
       try {
-        SelectionProperty prop
-            = propertyOracle.getSelectionProperty(logger, propName);
+        SelectionProperty prop = propertyOracle.getSelectionProperty(logger,
+            propName);
         testValue = prop.getCurrentValue();
       } catch (BadPropertyValueException e) {
-        ConfigurationProperty prop
-            = propertyOracle.getConfigurationProperty(propName);
+        ConfigurationProperty prop = propertyOracle.getConfigurationProperty(propName);
         testValue = prop.getValues().get(0);
       }
       logger.log(TreeLogger.DEBUG, "Property value is '" + testValue + "'",
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ConditionWhenTypeAssignableTo.java b/dev/core/src/com/google/gwt/dev/cfg/ConditionWhenTypeAssignableTo.java
index bc8e472..04d1df4 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ConditionWhenTypeAssignableTo.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ConditionWhenTypeAssignableTo.java
@@ -15,7 +15,7 @@
  */
 package com.google.gwt.dev.cfg;
 
-import com.google.gwt.core.ext.GeneratorContext;
+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.core.ext.typeinfo.JClassType;
@@ -42,10 +42,8 @@
     return "<when-assignable class='" + assignableToTypeName + "'/>";
   }
 
-  protected boolean doEval(TreeLogger logger, GeneratorContext context,
-      String testType) throws UnableToCompleteException {
-    TypeOracle typeOracle = context.getTypeOracle();
-
+  protected boolean doEval(TreeLogger logger, PropertyOracle propertyOracle,
+      TypeOracle typeOracle, String testType) throws UnableToCompleteException {
     JClassType fromType = typeOracle.findType(testType);
     if (fromType == null) {
       Util.logMissingTypeErrorWithHints(logger, testType);
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ConditionWhenTypeIs.java b/dev/core/src/com/google/gwt/dev/cfg/ConditionWhenTypeIs.java
index 732d78c..68204b5 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ConditionWhenTypeIs.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ConditionWhenTypeIs.java
@@ -15,8 +15,9 @@
  */
 package com.google.gwt.dev.cfg;
 
-import com.google.gwt.core.ext.GeneratorContext;
+import com.google.gwt.core.ext.PropertyOracle;
 import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
 
 /**
  * A deferred binding condition to determine whether the type being rebound is
@@ -34,8 +35,8 @@
     return "<when-type-is class='" + exactTypeName + "'/>";
   }
 
-  protected boolean doEval(TreeLogger logger, GeneratorContext context,
-      String testType) {
+  protected boolean doEval(TreeLogger logger, PropertyOracle propertyOracle,
+      TypeOracle typeOracle, String testType) {
     return exactTypeName.equals(testType);
   }
 
diff --git a/dev/core/src/com/google/gwt/dev/cfg/Conditions.java b/dev/core/src/com/google/gwt/dev/cfg/Conditions.java
index e18dd95..8993fae 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/Conditions.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/Conditions.java
@@ -24,7 +24,7 @@
  */
 public class Conditions {
 
-  private final List list = new ArrayList();
+  private final List<Condition> list = new ArrayList<Condition>();
 
   /**
    * Appends a condition.
@@ -33,7 +33,7 @@
     list.add(condition);
   }
 
-  public Iterator iterator() {
+  public Iterator<Condition> iterator() {
     return list.iterator();
   }
 }
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 3431ef6..2947db1 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java
@@ -449,7 +449,7 @@
          * Create a default property provider for any properties with more than
          * one possible value and no existing provider.
          */
-        if (prop.getProvider() == null && prop.getAllowedValues().length > 1) {
+        if (prop.getProvider() == null && prop.getConstrainedValue() == null) {
           String src = "{";
           src += "return __gwt_getMetaProperty(\"";
           src += prop.getName();
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 888cd49..fb63205 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java
@@ -46,7 +46,7 @@
  * Configures a module definition object using XML.
  */
 public class ModuleDefSchema extends Schema {
-  
+
   private final class BodySchema extends Schema {
 
     protected final String __add_linker_1_name = null;
@@ -133,6 +133,12 @@
 
     protected final String __super_source_5_casesensitive = "true";
 
+    /**
+     * Used to accumulate binding property conditions before recording the
+     * newly-allowed values.
+     */
+    private ConditionAll bindingPropertyCondition;
+
     private Schema fChild;
 
     protected Schema __add_linker_begin(LinkerName name)
@@ -144,7 +150,7 @@
       moduleDef.addLinker(name.name);
       return null;
     }
-    
+
     protected Schema __clear_configuration_property_begin(PropertyName name)
         throws UnableToCompleteException {
       // Don't allow configuration properties with the same name as a
@@ -174,14 +180,13 @@
     protected Schema __define_configuration_property_begin(PropertyName name,
         String is_multi_valued) throws UnableToCompleteException {
       boolean isMultiValued = toPrimitiveBoolean(is_multi_valued);
-      
+
       // Don't allow configuration properties with the same name as a
       // deferred-binding property.
       Property existingProperty = moduleDef.getProperties().find(name.token);
       if (existingProperty == null) {
         // Create the property
-        moduleDef.getProperties().createConfiguration(name.token,
-            isMultiValued);
+        moduleDef.getProperties().createConfiguration(name.token, isMultiValued);
         if (!propertyDefinitions.containsKey(name.token)) {
           propertyDefinitions.put(name.token, moduleName);
         }
@@ -189,36 +194,38 @@
         // Allow redefinition only if the 'is-multi-valued' setting is identical
         // The previous definition may have been explicit, via
         // <define-configuration-property>, or implicit, via
-        // <set-configuration-property>.  In the latter case, the
+        // <set-configuration-property>. In the latter case, the
         // value of the 'is-multi-valued' attribute was taken as false.
         String originalDefinition = propertyDefinitions.get(name.token);
-        if (((ConfigurationProperty) existingProperty).allowsMultipleValues()
-            != isMultiValued) {
+        if (((ConfigurationProperty) existingProperty).allowsMultipleValues() != isMultiValued) {
           if (originalDefinition != null) {
-            logger.log(TreeLogger.ERROR, "The configuration property named "
-                + name.token
-                + " is already defined with a different 'is-multi-valued' setting");
+            logger.log(
+                TreeLogger.ERROR,
+                "The configuration property named "
+                    + name.token
+                    + " is already defined with a different 'is-multi-valued' setting");
           } else {
-            logger.log(TreeLogger.ERROR, "The configuration property named "
-                + name.token
-                + " is already defined implicitly by 'set-configuration-property'"
-                + " in " + propertySettings.get(name.token)
-                + " with 'is-multi-valued' set to 'false'");
+            logger.log(
+                TreeLogger.ERROR,
+                "The configuration property named "
+                    + name.token
+                    + " is already defined implicitly by 'set-configuration-property'"
+                    + " in " + propertySettings.get(name.token)
+                    + " with 'is-multi-valued' set to 'false'");
           }
           throw new UnableToCompleteException();
         } else {
           if (originalDefinition != null) {
             logger.log(TreeLogger.WARN,
                 "Ignoring identical definition of the configuration property named "
-                + name.token + " (originally defined in "
-                + originalDefinition
-                + ", redefined in " + moduleName + ")");
+                    + name.token + " (originally defined in "
+                    + originalDefinition + ", redefined in " + moduleName + ")");
           } else {
             logger.log(TreeLogger.WARN,
                 "Definition of already set configuration property named "
-                + name.token + " in " + moduleName
-                + " (set in " + propertySettings.get(name.token)
-                + ").  This may be disallowed in the future.");
+                    + name.token + " in " + moduleName + " (set in "
+                    + propertySettings.get(name.token)
+                    + ").  This may be disallowed in the future.");
           }
         }
       } else {
@@ -233,7 +240,7 @@
         }
         throw new UnableToCompleteException();
       }
-      
+
       // No children.
       return null;
     }
@@ -279,7 +286,7 @@
       BindingProperty prop = moduleDef.getProperties().createBinding(name.token);
 
       for (int i = 0; i < values.length; i++) {
-        prop.addDefinedValue(values[i].token);
+        prop.addDefinedValue(prop.getRootCondition(), values[i].token);
       }
 
       // No children.
@@ -316,7 +323,7 @@
     protected Schema __extend_property_begin(BindingProperty property,
         PropertyValue[] values) {
       for (int i = 0; i < values.length; i++) {
-        property.addDefinedValue(values[i].token);
+        property.addDefinedValue(property.getRootCondition(), values[i].token);
       }
 
       // No children.
@@ -326,13 +333,13 @@
     protected Schema __fail_begin() {
       RuleFail rule = new RuleFail();
       moduleDef.getRules().prepend(rule);
-      return new ConditionSchema(rule.getRootCondition());
+      return new FullConditionSchema(rule.getRootCondition());
     }
 
     protected Schema __generate_with_begin(Generator gen) {
       RuleGenerateWith rule = new RuleGenerateWith(gen);
       moduleDef.getRules().prepend(rule);
-      return new ConditionSchema(rule.getRootCondition());
+      return new FullConditionSchema(rule.getRootCondition());
     }
 
     protected Schema __inherits_begin(String name)
@@ -393,7 +400,7 @@
     protected Schema __replace_with_begin(String className) {
       RuleReplaceWith rule = new RuleReplaceWith(className);
       moduleDef.getRules().prepend(rule);
-      return new ConditionSchema(rule.getRootCondition());
+      return new FullConditionSchema(rule.getRootCondition());
     }
 
     /**
@@ -444,7 +451,7 @@
 
     protected Schema __set_configuration_property_begin(PropertyName name,
         String value) throws UnableToCompleteException {
-      
+
       Property existingProperty = moduleDef.getProperties().find(name.token);
       if (existingProperty == null) {
         // If a property is created by "set-configuration-property" without
@@ -456,10 +463,8 @@
           propertySettings.put(name.token, moduleName);
         }
 
-        logger.log(TreeLogger.WARN,
-            "Setting configuration property named "
-            + name.token + " in "
-            + moduleName
+        logger.log(TreeLogger.WARN, "Setting configuration property named "
+            + name.token + " in " + moduleName
             + " that has not been previously defined."
             + "  This may be disallowed in the future.");
       } else if (!(existingProperty instanceof ConfigurationProperty)) {
@@ -482,6 +487,12 @@
 
     protected Schema __set_property_begin(BindingProperty prop,
         PropertyValue[] value) throws UnableToCompleteException {
+      bindingPropertyCondition = new ConditionAll();
+      return new PropertyConditionSchema(bindingPropertyCondition);
+    }
+
+    protected void __set_property_end(BindingProperty prop,
+        PropertyValue[] value) throws UnableToCompleteException {
       boolean error = false;
       String[] stringValues = new String[value.length];
       for (int i = 0, len = stringValues.length; i < len; i++) {
@@ -495,16 +506,18 @@
         throw new UnableToCompleteException();
       }
 
-      prop.setAllowedValues(stringValues);
+      // No conditions were specified, so use the property's root condition
+      if (!bindingPropertyCondition.getConditions().iterator().hasNext()) {
+        bindingPropertyCondition = prop.getRootCondition();
+      }
 
-      // No children.
-      return null;
+      prop.setAllowedValues(bindingPropertyCondition, stringValues);
     }
 
     protected Schema __set_property_fallback_begin(BindingProperty prop,
         PropertyValue value) throws UnableToCompleteException {
       boolean error = true;
-      for (String possibleValue : prop.getAllowedValues()) {
+      for (String possibleValue : prop.getAllowedValues(prop.getRootCondition())) {
         if (possibleValue.equals(value.token)) {
           error = false;
           break;
@@ -512,8 +525,8 @@
       }
       if (error) {
         logger.log(TreeLogger.ERROR, "The fallback value '" + value.token
-            + "' was not previously defined for property '"
-            + prop.getName() + "'");
+            + "' was not previously defined for property '" + prop.getName()
+            + "'");
         throw new UnableToCompleteException();
       }
       prop.setFallback(value.token);
@@ -697,52 +710,17 @@
     }
   }
 
-  private final class ConditionSchema extends Schema {
-
-    protected final String __when_property_is_1_name = null;
-
-    protected final String __when_property_is_2_value = null;
+  /**
+   * All conditional expressions, including those based on types.
+   */
+  private final class FullConditionSchema extends PropertyConditionSchema {
 
     protected final String __when_type_assignable_1_class = null;
 
     protected final String __when_type_is_1_class = null;
 
-    private final CompoundCondition parentCondition;
-
-    public ConditionSchema(CompoundCondition parentCondition) {
-      this.parentCondition = parentCondition;
-    }
-
-    protected Schema __all_begin() {
-      CompoundCondition cond = new ConditionAll();
-      parentCondition.getConditions().add(cond);
-      return new ConditionSchema(cond);
-    }
-
-    protected Schema __any_begin() {
-      CompoundCondition cond = new ConditionAny();
-      parentCondition.getConditions().add(cond);
-      return new ConditionSchema(cond);
-    }
-
-    protected Schema __none_begin() {
-      CompoundCondition cond = new ConditionNone();
-      parentCondition.getConditions().add(cond);
-      return new ConditionSchema(cond);
-    }
-
-    /*
-     * We intentionally use the BindingProperty type here for tough-love on
-     * module writers. It prevents them from trying to create property providers
-     * for unknown properties.
-     */
-    protected Schema __when_property_is_begin(BindingProperty prop,
-        PropertyValue value) {
-      Condition cond = new ConditionWhenPropertyIs(prop.getName(), value.token);
-      parentCondition.getConditions().add(cond);
-
-      // No children allowed.
-      return null;
+    public FullConditionSchema(CompoundCondition parentCondition) {
+      super(parentCondition);
     }
 
     protected Schema __when_type_assignable_begin(String className) {
@@ -760,6 +738,11 @@
       // No children allowed.
       return null;
     }
+
+    @Override
+    protected Schema subSchema(CompoundCondition cond) {
+      return new FullConditionSchema(cond);
+    }
   }
 
   private static final class IncludeExcludeSchema extends Schema {
@@ -942,6 +925,57 @@
     }
   }
 
+  /**
+   * A limited number of conditional predicates based only on properties.
+   */
+  private class PropertyConditionSchema extends Schema {
+    protected final String __when_property_is_1_name = null;
+
+    protected final String __when_property_is_2_value = null;
+
+    protected final CompoundCondition parentCondition;
+
+    public PropertyConditionSchema(CompoundCondition parentCondition) {
+      this.parentCondition = parentCondition;
+    }
+
+    protected Schema __all_begin() {
+      CompoundCondition cond = new ConditionAll();
+      parentCondition.getConditions().add(cond);
+      return subSchema(cond);
+    }
+
+    protected Schema __any_begin() {
+      CompoundCondition cond = new ConditionAny();
+      parentCondition.getConditions().add(cond);
+      return subSchema(cond);
+    }
+
+    protected Schema __none_begin() {
+      CompoundCondition cond = new ConditionNone();
+      parentCondition.getConditions().add(cond);
+      return subSchema(cond);
+    }
+
+    /*
+     * We intentionally use the BindingProperty type here for tough-love on
+     * module writers. It prevents them from trying to create property providers
+     * for unknown properties.
+     */
+    protected Schema __when_property_is_begin(BindingProperty prop,
+        PropertyValue value) {
+      Condition cond = new ConditionWhenPropertyIs(prop.getName(), value.token);
+      parentCondition.getConditions().add(cond);
+
+      // No children allowed.
+      return null;
+    }
+
+    protected Schema subSchema(CompoundCondition cond) {
+      return new PropertyConditionSchema(cond);
+    }
+  }
+
   private static class PropertyName {
     public final String token;
 
@@ -1075,15 +1109,13 @@
    * Map of property names to the modules that defined them explicitly using
    * <define-configuration-property>, used to generate warning messages.
    */
-  private static final HashMap<String,String> propertyDefinitions
-      = new HashMap<String,String>();
- 
+  private static final HashMap<String, String> propertyDefinitions = new HashMap<String, String>();
+
   /**
    * Map of property names to the modules that defined them implicitly using
    * <set-configuration-property>, used to generate warning messages.
    */
-  private static final HashMap<String,String> propertySettings
-      = new HashMap<String,String>();
+  private static final HashMap<String, String> propertySettings = new HashMap<String, String>();
 
   private static final Map<String, Object> singletonsByName = new HashMap<String, Object>();
 
@@ -1100,7 +1132,7 @@
   private static boolean toPrimitiveBoolean(String s) {
     return "yes".equalsIgnoreCase(s) || "true".equalsIgnoreCase(s);
   }
-  
+
   protected final String __module_1_rename_to = "";
   private final PropertyAttrCvt bindingPropAttrCvt = new PropertyAttrCvt(
       BindingProperty.class);
@@ -1126,7 +1158,8 @@
   private final PropertyValueAttrCvt propValueAttrCvt = new PropertyValueAttrCvt();
 
   public ModuleDefSchema(TreeLogger logger, ModuleDefLoader loader,
-      String moduleName, URL moduleURL, String modulePackageAsPath, ModuleDef toConfigure) {
+      String moduleName, URL moduleURL, String modulePackageAsPath,
+      ModuleDef toConfigure) {
     this.logger = logger;
     this.loader = loader;
     this.moduleName = moduleName;
diff --git a/dev/core/src/com/google/gwt/dev/cfg/Properties.java b/dev/core/src/com/google/gwt/dev/cfg/Properties.java
index 37979ff..3a8a278 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/Properties.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/Properties.java
@@ -47,8 +47,10 @@
    * Creates the specified configuration property, or returns an existing one by
    * the specified name if present.
    */
-  public ConfigurationProperty createConfiguration(String name, boolean allowMultipleValues) {
-    ConfigurationProperty prop = create(name, allowMultipleValues, ConfigurationProperty.class);
+  public ConfigurationProperty createConfiguration(String name,
+      boolean allowMultipleValues) {
+    ConfigurationProperty prop = create(name, allowMultipleValues,
+        ConfigurationProperty.class);
     configProps.add(prop);
     return prop;
   }
@@ -72,35 +74,17 @@
     return map.values().iterator();
   }
 
-  /**
-   * Count the total number of permutations that this property set supports.
-   */
-  public int numPermutations() {
-    BindingProperty[] bindingPropsArray = bindingProps.toArray(
-        new BindingProperty[bindingProps.size()]);
-
-    int count = 1;
-
-    for (BindingProperty prop : bindingPropsArray) {
-      String[] options = prop.getAllowedValues();
-      assert (options.length > 0);
-      count *= options.length;
-    }
-
-    return count;
-  }
-
   private <T extends Property> T create(String name, Class<T> clazz) {
     return create(name, false, false, clazz);
   }
-  
-  private <T extends Property> T create(String name, boolean flag, Class<T> clazz) {
+
+  private <T extends Property> T create(String name, boolean flag,
+      Class<T> clazz) {
     return create(name, flag, true, clazz);
   }
-  
-  private <T extends Property> T create(String name,
-      boolean flag, boolean useFlagArgument,
-      Class<T> clazz) {
+
+  private <T extends Property> T create(String name, boolean flag,
+      boolean useFlagArgument, Class<T> clazz) {
     if (clazz == null) {
       throw new NullPointerException("clazz");
     } else if (name == null) {
@@ -122,8 +106,8 @@
     try {
       T newInstance;
       if (useFlagArgument) {
-        newInstance = clazz.getConstructor(String.class,
-            boolean.class).newInstance(name, flag);
+        newInstance = clazz.getConstructor(String.class, boolean.class).newInstance(
+            name, flag);
       } else {
         newInstance = clazz.getConstructor(String.class).newInstance(name);
       }
diff --git a/dev/core/src/com/google/gwt/dev/cfg/Property.java b/dev/core/src/com/google/gwt/dev/cfg/Property.java
index 7c1f7b4..8019ae8 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/Property.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/Property.java
@@ -31,7 +31,7 @@
   public int compareTo(Property o) {
     return name.compareTo(o.name);
   }
-  
+
   @Override
   public boolean equals(Object o) {
     if (!(o instanceof Property)) {
@@ -43,7 +43,7 @@
   public String getName() {
     return name;
   }
-  
+
   @Override
   public int hashCode() {
     return name.hashCode();
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 1e77ee2..682b2bf 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/PropertyPermutations.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/PropertyPermutations.java
@@ -15,10 +15,17 @@
  */
 package com.google.gwt.dev.cfg;
 
+import com.google.gwt.core.ext.PropertyOracle;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+
 import java.util.ArrayList;
 import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
 import java.util.List;
-import java.util.SortedSet;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * Generates all possible permutations of properties in a module. Each
@@ -36,12 +43,9 @@
   private static List<String[]> allPermutationsOf(Properties properties) {
     BindingProperty[] bindingProperties = getOrderedPropertiesOf(properties);
 
-    int permCount = properties.numPermutations();
-
-    List<String[]> permutations = new ArrayList<String[]>(permCount);
+    List<String[]> permutations = new ArrayList<String[]>();
     if (bindingProperties.length > 0) {
       permute(bindingProperties, null, 0, permutations);
-      assert (permCount == permutations.size());
     } else {
       permutations.add(new String[0]);
     }
@@ -49,8 +53,44 @@
   }
 
   private static BindingProperty[] getOrderedPropertiesOf(Properties properties) {
-    SortedSet<BindingProperty> bindingProps = properties.getBindingProperties();
-    return bindingProps.toArray(new BindingProperty[bindingProps.size()]);
+    /*
+     * 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());
+
+    // Accumulates the order in which the properties should be evaluated
+    Map<String, BindingProperty> evaluationOrder = new LinkedHashMap<String, BindingProperty>(
+        bindingProps.size());
+
+    /*
+     * Insert a property after all of the properties that it depends upon have
+     * been inserted.
+     */
+    while (!bindingProps.isEmpty()) {
+      boolean changed = false;
+
+      for (Iterator<BindingProperty> it = bindingProps.iterator(); it.hasNext();) {
+        BindingProperty prop = it.next();
+
+        Set<String> deps = prop.getRequiredProperties();
+        if (evaluationOrder.keySet().containsAll(deps)) {
+          it.remove();
+          evaluationOrder.put(prop.getName(), prop);
+          changed = true;
+        }
+      }
+
+      if (!changed) {
+        throw new IllegalStateException(
+            "Cycle detected within remaining property dependencies "
+                + bindingProps.toString());
+      }
+    }
+
+    return evaluationOrder.values().toArray(
+        new BindingProperty[evaluationOrder.size()]);
   }
 
   private static void permute(BindingProperty[] properties, String[] soFar,
@@ -58,8 +98,32 @@
     int lastProp = properties.length - 1;
 
     BindingProperty prop = properties[whichProp];
-    String[] options = prop.getAllowedValues();
 
+    // Find the last-one-wins Condition
+    Condition winner = null;
+    if (prop.getConditionalValues().size() == 1) {
+      winner = prop.getRootCondition();
+    } else {
+      BindingProperty[] answerable = new BindingProperty[soFar.length];
+      System.arraycopy(properties, 0, answerable, 0, soFar.length);
+      PropertyOracle propertyOracle = new StaticPropertyOracle(answerable,
+          soFar, new ConfigurationProperty[0]);
+
+      for (Condition cond : prop.getConditionalValues().keySet()) {
+        try {
+          if (cond.isTrue(TreeLogger.NULL, propertyOracle, null, null)) {
+            winner = cond;
+          }
+        } catch (UnableToCompleteException e) {
+          throw new IllegalStateException(
+              "Should never get here for simple properties", e);
+        }
+      }
+    }
+
+    assert winner != null;
+
+    String[] options = prop.getAllowedValues(winner);
     for (int i = 0; i < options.length; i++) {
       String knownValue = options[i];
 
@@ -92,6 +156,12 @@
         firstPerm + numPerms);
   }
 
+  public PropertyPermutations(PropertyPermutations allPermutations,
+      int firstPerm, int numPerms) {
+    this.properties = allPermutations.properties;
+    values = allPermutations.values.subList(firstPerm, firstPerm + numPerms);
+  }
+
   public BindingProperty[] getOrderedProperties() {
     return getOrderedPropertiesOf(properties);
   }
diff --git a/dev/core/src/com/google/gwt/dev/cfg/Rule.java b/dev/core/src/com/google/gwt/dev/cfg/Rule.java
index 8096193..d92146f 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/Rule.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/Rule.java
@@ -32,7 +32,8 @@
 
   public boolean isApplicable(TreeLogger logger, GeneratorContext context,
       String typeName) throws UnableToCompleteException {
-    return rootCondition.isTrue(logger, context, typeName);
+    return rootCondition.isTrue(logger, context.getPropertyOracle(),
+        context.getTypeOracle(), typeName);
   }
 
   public abstract String realize(TreeLogger logger, GeneratorContext context,
diff --git a/dev/core/src/com/google/gwt/dev/shell/ModuleSpacePropertyOracle.java b/dev/core/src/com/google/gwt/dev/shell/ModuleSpacePropertyOracle.java
index b744f20..c709487 100644
--- a/dev/core/src/com/google/gwt/dev/shell/ModuleSpacePropertyOracle.java
+++ b/dev/core/src/com/google/gwt/dev/shell/ModuleSpacePropertyOracle.java
@@ -19,11 +19,14 @@
 import com.google.gwt.core.ext.PropertyOracle;
 import com.google.gwt.core.ext.SelectionProperty;
 import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.dev.cfg.BindingProperty;
+import com.google.gwt.dev.cfg.Condition;
 import com.google.gwt.dev.cfg.ConfigurationProperty;
 import com.google.gwt.dev.cfg.Properties;
 import com.google.gwt.dev.cfg.Property;
 
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -47,9 +50,8 @@
     this.props = props;
   }
 
-  public com.google.gwt.core.ext.ConfigurationProperty
-      getConfigurationProperty(String propertyName)
-      throws BadPropertyValueException {
+  public com.google.gwt.core.ext.ConfigurationProperty getConfigurationProperty(
+      String propertyName) throws BadPropertyValueException {
     Property prop = getProperty(propertyName);
     if (prop instanceof ConfigurationProperty) {
       final ConfigurationProperty cprop = (ConfigurationProperty) prop;
@@ -118,14 +120,20 @@
     if (prop instanceof BindingProperty) {
       final BindingProperty cprop = (BindingProperty) prop;
       final String name = cprop.getName();
-      final String value = computePropertyValue(logger, propertyName, cprop);
+      final String value;
+      if (prevAnswers.containsKey(propertyName)) {
+        value = prevAnswers.get(propertyName);
+      } else {
+        value = computePropertyValue(logger, propertyName, cprop);
+        prevAnswers.put(propertyName, value);
+      }
       final String fallback = cprop.getFallback();
       final SortedSet<String> possibleValues = new TreeSet<String>();
       for (String v : cprop.getDefinedValues()) {
         possibleValues.add(v);
       }
       return new com.google.gwt.core.ext.SelectionProperty() {
-        
+
         public String getCurrentValue() {
           return value;
         }
@@ -147,6 +155,26 @@
     }
   }
 
+  private Condition computeActiveCondition(TreeLogger logger,
+      BindingProperty prop) throws BadPropertyValueException {
+    // Last-one-wins
+    Condition winner = null;
+    for (Condition cond : prop.getConditionalValues().keySet()) {
+      try {
+        if (cond.isTrue(logger, this, null, null)) {
+          winner = cond;
+        }
+      } catch (UnableToCompleteException e) {
+        BadPropertyValueException t = new BadPropertyValueException(
+            prop.getName());
+        t.initCause(e);
+        throw t;
+      }
+    }
+    assert winner != null : "No active Condition for " + prop.getName();
+    return winner;
+  }
+
   /**
    * Returns the value of the specified property.
    * 
@@ -157,12 +185,18 @@
   private String computePropertyValue(TreeLogger logger, String propertyName,
       BindingProperty prop) throws BadPropertyValueException {
 
-    if (prop.getAllowedValues().length == 1) {
+    String value = prop.getConstrainedValue();
+    if (value != null) {
       // If there is only one legal value, use that.
-      return prop.getAllowedValues()[0];
+      return value;
     }
 
-    String value;
+    Condition winner = computeActiveCondition(logger, prop);
+    String[] values = prop.getAllowedValues(winner);
+    if (values.length == 1) {
+      return values[0];
+    }
+
     // Invokes the script function.
     //
     try {
@@ -180,7 +214,7 @@
     }
 
     // value may be null if the provider returned an unknown property value.
-    if (prop.isAllowedValue(value)) {
+    if (Arrays.asList(values).contains(value)) {
       return value;
     } else {
       // Bad value due to the provider returning an unknown value.
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 d8774df..97aa1c6 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
@@ -15,25 +15,62 @@
  */
 package com.google.gwt.dev.util.test;
 
+import com.google.gwt.dev.cfg.BindingProperty;
+import com.google.gwt.dev.cfg.ConditionAny;
+import com.google.gwt.dev.cfg.ConditionWhenPropertyIs;
 import com.google.gwt.dev.cfg.ModuleDef;
 import com.google.gwt.dev.cfg.Properties;
-import com.google.gwt.dev.cfg.BindingProperty;
 import com.google.gwt.dev.cfg.PropertyPermutations;
 
 import junit.framework.TestCase;
 
 import java.util.Iterator;
 
+/**
+ * Tests the PropertyPermutations code.
+ */
 public class PropertyPermutationsTest extends TestCase {
 
+  /**
+   * Make sure that a cycle doesn't cause an infinite loop.
+   */
+  public void testCycle() {
+    // This is what you'd get with a conditional <set-property value="false">
+    ModuleDef md = new ModuleDef("testCycle");
+    Properties props = md.getProperties();
+
+    {
+      BindingProperty prop = props.createBinding("A");
+      prop.addDefinedValue(prop.getRootCondition(), "a1");
+      prop.addDefinedValue(prop.getRootCondition(), "a2");
+
+      prop.addDefinedValue(new ConditionWhenPropertyIs("B", "b3"), "a3");
+    }
+
+    {
+      BindingProperty prop = props.createBinding("B");
+      prop.addDefinedValue(prop.getRootCondition(), "b1");
+      prop.addDefinedValue(prop.getRootCondition(), "b2");
+
+      prop.addDefinedValue(new ConditionWhenPropertyIs("A", "a3"), "b3");
+    }
+
+    try {
+      new PropertyPermutations(props);
+      fail();
+    } catch (IllegalStateException e) {
+      // OK
+    }
+  }
+
   public void testOneDimensionPerm() {
     ModuleDef md = new ModuleDef("testOneDimensionPerm");
     Properties props = md.getProperties();
 
     {
       BindingProperty prop = props.createBinding("debug");
-      prop.addDefinedValue("false");
-      prop.addDefinedValue("true");
+      prop.addDefinedValue(prop.getRootCondition(), "false");
+      prop.addDefinedValue(prop.getRootCondition(), "true");
     }
 
     // Permutations and their values are in stable alphabetical order.
@@ -57,15 +94,15 @@
 
     {
       BindingProperty prop = props.createBinding("user.agent");
-      prop.addDefinedValue("moz");
-      prop.addDefinedValue("ie6");
-      prop.addDefinedValue("opera");
+      prop.addDefinedValue(prop.getRootCondition(), "moz");
+      prop.addDefinedValue(prop.getRootCondition(), "ie6");
+      prop.addDefinedValue(prop.getRootCondition(), "opera");
     }
 
     {
       BindingProperty prop = props.createBinding("debug");
-      prop.addDefinedValue("false");
-      prop.addDefinedValue("true");
+      prop.addDefinedValue(prop.getRootCondition(), "false");
+      prop.addDefinedValue(prop.getRootCondition(), "true");
     }
 
     // String[]s and their values are in stable alphabetical order.
@@ -104,4 +141,134 @@
     assertEquals("true", perm[0]);
     assertEquals("opera", perm[1]);
   }
+
+  public void testTwoDimensionPermWithExpansion() {
+    ModuleDef md = new ModuleDef("testTwoDimensionsWithExpansion");
+    Properties props = md.getProperties();
+
+    {
+      BindingProperty prop = props.createBinding("user.agent");
+      prop.addDefinedValue(prop.getRootCondition(), "moz");
+      prop.addDefinedValue(prop.getRootCondition(), "ie6");
+      prop.addDefinedValue(prop.getRootCondition(), "ie8");
+      prop.addDefinedValue(prop.getRootCondition(), "opera");
+    }
+
+    {
+      BindingProperty prop = props.createBinding("stackTraces");
+      prop.addDefinedValue(prop.getRootCondition(), "false");
+      prop.addDefinedValue(prop.getRootCondition(), "true");
+      // <set-property name="stackTraces" value="false" />
+      prop.setAllowedValues(prop.getRootCondition(), "false");
+
+      /*
+       * <set-property name="stackTraces" value="true,false"> <when user.agent
+       * is ie6 or ie 8> </set-property>
+       */
+      ConditionAny cond = new ConditionAny();
+      cond.getConditions().add(new ConditionWhenPropertyIs("user.agent", "ie6"));
+      cond.getConditions().add(new ConditionWhenPropertyIs("user.agent", "ie8"));
+      prop.setAllowedValues(cond, "true", "false");
+    }
+
+    validateTwoDimensionPerm(props);
+  }
+
+  public void testTwoDimensionPermWithExtension() {
+    // This is what you'd get with a conditional <extend-property>
+    ModuleDef md = new ModuleDef("testTwoDimensionsWithConditions");
+    Properties props = md.getProperties();
+
+    {
+      BindingProperty prop = props.createBinding("user.agent");
+      prop.addDefinedValue(prop.getRootCondition(), "moz");
+      prop.addDefinedValue(prop.getRootCondition(), "ie6");
+      prop.addDefinedValue(prop.getRootCondition(), "ie8");
+      prop.addDefinedValue(prop.getRootCondition(), "opera");
+    }
+
+    {
+      BindingProperty prop = props.createBinding("stackTraces");
+      prop.addDefinedValue(prop.getRootCondition(), "false");
+
+      ConditionAny cond = new ConditionAny();
+      cond.getConditions().add(new ConditionWhenPropertyIs("user.agent", "ie6"));
+      cond.getConditions().add(new ConditionWhenPropertyIs("user.agent", "ie8"));
+
+      prop.addDefinedValue(cond, "true");
+    }
+
+    validateTwoDimensionPerm(props);
+  }
+
+  public void testTwoDimensionPermWithRestriction() {
+    // This is what you'd get with a conditional <set-property value="false">
+    ModuleDef md = new ModuleDef("testTwoDimensionsWithRestriction");
+    Properties props = md.getProperties();
+
+    {
+      BindingProperty prop = props.createBinding("user.agent");
+      prop.addDefinedValue(prop.getRootCondition(), "moz");
+      prop.addDefinedValue(prop.getRootCondition(), "ie6");
+      prop.addDefinedValue(prop.getRootCondition(), "ie8");
+      prop.addDefinedValue(prop.getRootCondition(), "opera");
+    }
+
+    {
+      BindingProperty prop = props.createBinding("stackTraces");
+      prop.addDefinedValue(prop.getRootCondition(), "false");
+      prop.addDefinedValue(prop.getRootCondition(), "true");
+
+      ConditionAny cond = new ConditionAny();
+      cond.getConditions().add(new ConditionWhenPropertyIs("user.agent", "moz"));
+      cond.getConditions().add(
+          new ConditionWhenPropertyIs("user.agent", "opera"));
+
+      prop.setAllowedValues(cond, "false");
+    }
+
+    validateTwoDimensionPerm(props);
+  }
+
+  private void validateTwoDimensionPerm(Properties props) {
+    PropertyPermutations perms = new PropertyPermutations(props);
+
+    assertEquals(6, perms.size());
+
+    // Order is alphabetical in dependency order
+    String[] perm;
+    Iterator<String[]> iter = perms.iterator();
+
+    assertTrue(iter.hasNext());
+    perm = iter.next();
+    assertEquals("ie6", perm[0]);
+    assertEquals("false", perm[1]);
+
+    assertTrue(iter.hasNext());
+    perm = iter.next();
+    assertEquals("ie6", perm[0]);
+    assertEquals("true", perm[1]);
+
+    assertTrue(iter.hasNext());
+    perm = iter.next();
+    assertEquals("ie8", perm[0]);
+    assertEquals("false", perm[1]);
+
+    assertTrue(iter.hasNext());
+    perm = iter.next();
+    assertEquals("ie8", perm[0]);
+    assertEquals("true", perm[1]);
+
+    assertTrue(iter.hasNext());
+    perm = iter.next();
+    assertEquals("moz", perm[0]);
+    assertEquals("false", perm[1]);
+
+    assertTrue(iter.hasNext());
+    perm = iter.next();
+    assertEquals("opera", perm[0]);
+    assertEquals("false", perm[1]);
+
+    assertFalse(iter.hasNext());
+  }
 }
diff --git a/user/src/com/google/gwt/junit/JUnitShell.java b/user/src/com/google/gwt/junit/JUnitShell.java
index 9ab5c7a..54a6c58 100644
--- a/user/src/com/google/gwt/junit/JUnitShell.java
+++ b/user/src/com/google/gwt/junit/JUnitShell.java
@@ -706,7 +706,9 @@
       Properties props = module.getProperties();
       Property userAgent = props.find("user.agent");
       if (userAgent instanceof BindingProperty) {
-        ((BindingProperty) userAgent).setAllowedValues(userAgents);
+        BindingProperty bindingProperty = (BindingProperty) userAgent;
+        bindingProperty.setAllowedValues(bindingProperty.getRootCondition(),
+            userAgents);
       }
     }
     super.compile(getTopLogger(), module);
diff --git a/user/test/com/google/gwt/dev/cfg/PropertyTest.gwt.xml b/user/test/com/google/gwt/dev/cfg/PropertyTest.gwt.xml
index 5f61ad5..00695f5 100644
--- a/user/test/com/google/gwt/dev/cfg/PropertyTest.gwt.xml
+++ b/user/test/com/google/gwt/dev/cfg/PropertyTest.gwt.xml
@@ -20,7 +20,30 @@
   <define-property name="restricted1s" values="a,b,c,d,e,f" />
   <set-property name="restricted1s" value="d,e,f" />
   <set-property name="restricted1s" value="a" />
-    
+
+  <define-property name="conditional" values="a,b,c" />
+  <set-property name="conditional" value="a,b">
+    <when-property-is name="restricted" value="a" />
+  </set-property>
+  <set-property name="conditional" value="c">
+    <when-property-is name="restricted" value="c" />
+  </set-property>
+
+  <define-property name="derived" values="a,b,c" />
+  <set-property name="derived" value="a" />
+  <set-property name="derived" value="b" >
+    <when-property-is name="restricted" value="b" />
+  </set-property>
+  <set-property name="derived" value="c">
+    <when-property-is name="restricted" value="c" />
+  </set-property>
+
+  <define-property name="reset" values="a,b,c" />
+  <set-property name="reset" value="a,b" >
+    <when-property-is name="restricted" value="a" />
+  </set-property>
+  <set-property name="reset" value="a" />
+
   <set-configuration-property name="configProperty" value="Hello World!" />
 
   <set-configuration-property name="configRedefined" value="foo" />
diff --git a/user/test/com/google/gwt/dev/cfg/PropertyTest.java b/user/test/com/google/gwt/dev/cfg/PropertyTest.java
index bc85804..892427c 100644
--- a/user/test/com/google/gwt/dev/cfg/PropertyTest.java
+++ b/user/test/com/google/gwt/dev/cfg/PropertyTest.java
@@ -23,6 +23,8 @@
 
 import java.io.PrintWriter;
 import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Set;
 
 /**
  * Checks the behaviors of ModuleDefLoader and Properties.
@@ -50,9 +52,11 @@
     {
       BindingProperty restricted = (BindingProperty) p.find("restricted");
       assertNotNull(restricted);
-      assertEquals(3, restricted.getAllowedValues().length);
-      assertEquals(Arrays.asList("a", "b", "c"),
-          Arrays.asList(restricted.getAllowedValues()));
+      assertEquals(3,
+          restricted.getAllowedValues(restricted.getRootCondition()).length);
+      assertEquals(
+          Arrays.asList("a", "b", "c"),
+          Arrays.asList(restricted.getAllowedValues(restricted.getRootCondition())));
       assertTrue(restricted.isDefinedValue("d"));
       assertFalse(restricted.isAllowedValue("d"));
     }
@@ -61,8 +65,30 @@
       BindingProperty restricted1s = (BindingProperty) p.find("restricted1s");
       assertNotNull(restricted1s);
       assertTrue(restricted1s.isAllowedValue("a"));
-      assertEquals(1, restricted1s.getAllowedValues().length);
-      assertEquals("a", restricted1s.getAllowedValues()[0]);
+      assertEquals(1,
+          restricted1s.getAllowedValues(restricted1s.getRootCondition()).length);
+      assertEquals("a",
+          restricted1s.getAllowedValues(restricted1s.getRootCondition())[0]);
+    }
+
+    {
+      BindingProperty conditional = (BindingProperty) p.find("conditional");
+      assertNotNull(conditional);
+      assertFalse(conditional.isDerived());
+
+      Set<String> required = conditional.getRequiredProperties();
+      assertEquals(required.size(), 1);
+      assertTrue(required.contains("restricted"));
+
+      assertEquals(3, conditional.getConditionalValues().size());
+      Iterator<Condition> it = conditional.getConditionalValues().keySet().iterator();
+
+      assertEquals(Arrays.asList("a", "b", "c"),
+          Arrays.asList(conditional.getAllowedValues(it.next())));
+      assertEquals(Arrays.asList("a", "b"),
+          Arrays.asList(conditional.getAllowedValues(it.next())));
+      assertEquals(Arrays.asList("c"),
+          Arrays.asList(conditional.getAllowedValues(it.next())));
     }
 
     {
@@ -75,6 +101,31 @@
       Property configRedefined = p.find("configRedefined");
       assertEquals("bar", ((ConfigurationProperty) configRedefined).getValue());
     }
+
+    {
+      BindingProperty derived = (BindingProperty) p.find("derived");
+      assertNotNull(derived);
+      assertTrue(derived.isDerived());
+
+      Set<String> required = derived.getRequiredProperties();
+      assertEquals(required.size(), 1);
+      assertTrue(required.contains("restricted"));
+
+      assertEquals(3, derived.getConditionalValues().size());
+    }
+
+    {
+      BindingProperty reset = (BindingProperty) p.find("reset");
+      assertNotNull(reset);
+      assertTrue(reset.isDerived());
+
+      Set<String> required = reset.getRequiredProperties();
+      assertEquals(0, required.size());
+
+      assertEquals(1, reset.getConditionalValues().size());
+      assertSame(reset.getRootCondition(),
+          reset.getConditionalValues().keySet().iterator().next());
+    }
   }
 
   public void testModuleInvalidOverride() {