1) Adds the concept of module "configuration" properties which do not affect deferred-binding decisions and may be set to any string value.  These configuration properties are exposed to Generators and Linkers as a means of providing global configuration information.

Example:
 <set-configuration-property name="someName" value="Any string value" />

Rules:
 - There is no need to pre-define the property, they can simply be declared.
 - Last-one-wins.
 - It is an error to have a <define-property> and <set-configuration-property> with the same name.
 - The name must be a valid identifier, but the value may be any string value.

2) Expands the scope of <set-property> to allow multiple values to be specified instead of only one value; only the specified value(s) will be permuted over.

Patch by: bobv (+scottb pair prog)
Review by: scottb (pair prog)


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@3652 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/LinkerContext.java b/dev/core/src/com/google/gwt/core/ext/LinkerContext.java
index 253a8c1..120de31 100644
--- a/dev/core/src/com/google/gwt/core/ext/LinkerContext.java
+++ b/dev/core/src/com/google/gwt/core/ext/LinkerContext.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.core.ext;
 
+import com.google.gwt.core.ext.linker.ConfigurationProperty;
 import com.google.gwt.core.ext.linker.SelectionProperty;
 
 import java.util.SortedSet;
@@ -27,6 +28,13 @@
  */
 public interface LinkerContext {
   /**
+   * Returns all configuration properties defined in the module. Configuration
+   * properties do not have any impact on deferred-binding decisions, but may
+   * affect the behaviors of Generators and Linkers.
+   */
+  SortedSet<ConfigurationProperty> getConfigurationProperties();
+
+  /**
    * Returns the name of the module's bootstrap function.
    */
   String getModuleFunctionName();
@@ -62,4 +70,4 @@
    */
   String optimizeJavaScript(TreeLogger logger, String jsProgram)
       throws UnableToCompleteException;
-}
\ No newline at end of file
+}
diff --git a/dev/core/src/com/google/gwt/core/ext/PropertyOracle.java b/dev/core/src/com/google/gwt/core/ext/PropertyOracle.java
index 188cc5a..314cd2e 100644
--- a/dev/core/src/com/google/gwt/core/ext/PropertyOracle.java
+++ b/dev/core/src/com/google/gwt/core/ext/PropertyOracle.java
@@ -21,10 +21,10 @@
 public interface PropertyOracle {
 
   /**
-   * Attempts to get a named deferred binding property. Throws
-   * <code>BadPropertyValueException</code> if the property is either
-   * undefined or has a value that is unsupported. The result of invoking this
-   * method with the same <code>propertyName</code> must be stable.
+   * Attempts to get a named deferred binding property or configuration
+   * property. Throws <code>BadPropertyValueException</code> if the property is
+   * either undefined or has a value that is unsupported. The result of invoking
+   * this method with the same <code>propertyName</code> must be stable.
    * 
    * @param logger the current logger
    * @param propertyName the name of the property
@@ -36,8 +36,9 @@
   /**
    * Attempts to get a named deferred binding property and returns the list of
    * possible values. Throws <code>BadPropertyValueException</code> if the
-   * property is undefined. The result of invoking this method with the same
-   * <code>propertyName</code> must be stable.
+   * property is a configuration property or is undefined. The result of
+   * invoking this method with the same <code>propertyName</code> must be
+   * stable.
    * 
    * @param logger the current logger
    * @param propertyName the name of the property
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/ConfigurationProperty.java b/dev/core/src/com/google/gwt/core/ext/linker/ConfigurationProperty.java
new file mode 100644
index 0000000..ca4faad
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/linker/ConfigurationProperty.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.core.ext.linker;
+
+/**
+ * Represents a configuration property. These properties do not affect
+ * deferred-binding decisions, but may affect the behavior of Linkers and
+ * Generators.
+ */
+public interface ConfigurationProperty {
+  /*
+   * NB: This is not a super-interface of SelectionProperty, since these kinds
+   * of properties are always guaranteed to have a defined value. The only
+   * commonality is getName() which doesn't seem all that useful to extract.
+   */
+
+  /**
+   * Returns the name of the configuration property.
+   */
+  String getName();
+
+  /**
+   * Returns the defined value for the configuration property.
+   */
+  String getValue();
+}
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardConfigurationProperty.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardConfigurationProperty.java
new file mode 100644
index 0000000..72851f6
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardConfigurationProperty.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * 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.ConfigurationProperty;
+
+/**
+ * The standard implementation of {@link ConfigurationProperty} from a
+ * {@link com.google.gwt.dev.cfg.ConfigurationProperty}.
+ */
+public class StandardConfigurationProperty implements ConfigurationProperty {
+
+  private final String name;
+  private final String value;
+
+  public StandardConfigurationProperty(
+      com.google.gwt.dev.cfg.ConfigurationProperty p) {
+    this.name = p.getName();
+    this.value = p.getValue();
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public String getValue() {
+    return value;
+  }
+}
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
index 9aedc88..a652c72 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
@@ -20,12 +20,14 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.linker.ArtifactSet;
+import com.google.gwt.core.ext.linker.ConfigurationProperty;
 import com.google.gwt.core.ext.linker.EmittedArtifact;
 import com.google.gwt.core.ext.linker.LinkerOrder;
 import com.google.gwt.core.ext.linker.PublicResource;
 import com.google.gwt.core.ext.linker.SelectionProperty;
 import com.google.gwt.core.ext.linker.LinkerOrder.Order;
 import com.google.gwt.dev.GWTCompiler;
+import com.google.gwt.dev.cfg.BindingProperty;
 import com.google.gwt.dev.cfg.ModuleDef;
 import com.google.gwt.dev.cfg.Property;
 import com.google.gwt.dev.cfg.Script;
@@ -90,6 +92,12 @@
     }
   }
 
+  static final Comparator<ConfigurationProperty> CONFIGURATION_PROPERTY_COMPARATOR = new Comparator<ConfigurationProperty>() {
+    public int compare(ConfigurationProperty o1, ConfigurationProperty o2) {
+      return o1.getName().compareTo(o2.getName());
+    }
+  };
+
   static final Comparator<SelectionProperty> SELECTION_PROPERTY_COMPARATOR = new Comparator<SelectionProperty>() {
     public int compare(SelectionProperty o1, SelectionProperty o2) {
       return o1.getName().compareTo(o2.getName());
@@ -98,6 +106,7 @@
 
   private final ArtifactSet artifacts = new ArtifactSet();
   private final File compilationsDir;
+  private final SortedSet<ConfigurationProperty> configurationProperties;
   private final JJSOptions jjsOptions;
   private final List<Class<? extends Linker>> linkerClasses;
   private final Map<Class<? extends Linker>, String> linkerShortNames = new HashMap<Class<? extends Linker>, String>();
@@ -114,9 +123,9 @@
    * compilation.
    */
   private final File moduleOutDir;
-  private final SortedSet<SelectionProperty> properties;
   private final Map<String, StandardSelectionProperty> propertiesByName = new HashMap<String, StandardSelectionProperty>();
   private final Map<String, StandardCompilationResult> resultsByStrongName = new HashMap<String, StandardCompilationResult>();
+  private final SortedSet<SelectionProperty> selectionProperties;
 
   public StandardLinkerContext(TreeLogger logger, ModuleDef module,
       File moduleOutDir, File generatorDir, JJSOptions jjsOptions) {
@@ -163,17 +172,35 @@
      */
     linkerShortNames.put(this.getClass(), "");
 
-    // Always return the properties in the same order as a convenience
-    SortedSet<SelectionProperty> mutableProperties = new TreeSet<SelectionProperty>(
-        SELECTION_PROPERTY_COMPARATOR);
-    for (Property p : module.getProperties()) {
-      // Create a new view
-      StandardSelectionProperty newProp = new StandardSelectionProperty(p);
-      mutableProperties.add(newProp);
-      propertiesByName.put(newProp.getName(), newProp);
-      logger.log(TreeLogger.SPAM, "Added property " + newProp, null);
+    // Break ModuleDef properties out into LinkerContext interfaces
+    {
+      SortedSet<ConfigurationProperty> mutableConfigurationProperties = new TreeSet<ConfigurationProperty>(
+          CONFIGURATION_PROPERTY_COMPARATOR);
+      SortedSet<SelectionProperty> mutableSelectionProperties = new TreeSet<SelectionProperty>(
+          SELECTION_PROPERTY_COMPARATOR);
+      for (Property p : module.getProperties()) {
+        // Create a new view
+        if (p instanceof com.google.gwt.dev.cfg.ConfigurationProperty) {
+          StandardConfigurationProperty newProp = new StandardConfigurationProperty(
+              (com.google.gwt.dev.cfg.ConfigurationProperty) p);
+          mutableConfigurationProperties.add(newProp);
+          logger.log(TreeLogger.SPAM,
+              "Added configuration property " + newProp, null);
+        } else if (p instanceof BindingProperty) {
+          StandardSelectionProperty newProp = new StandardSelectionProperty(
+              (BindingProperty) p);
+          mutableSelectionProperties.add(newProp);
+          propertiesByName.put(newProp.getName(), newProp);
+          logger.log(TreeLogger.SPAM, "Added selection property " + newProp,
+              null);
+        } else {
+          logger.log(TreeLogger.ERROR, "Unknown property type "
+              + p.getClass().getName());
+        }
+      }
+      selectionProperties = Collections.unmodifiableSortedSet(mutableSelectionProperties);
+      configurationProperties = Collections.unmodifiableSortedSet(mutableConfigurationProperties);
     }
-    properties = Collections.unmodifiableSortedSet(mutableProperties);
 
     {
       int index = 0;
@@ -236,6 +263,10 @@
     return result;
   }
 
+  public SortedSet<ConfigurationProperty> getConfigurationProperties() {
+    return configurationProperties;
+  }
+
   @Override
   public String getDescription() {
     return "Root Linker";
@@ -250,7 +281,7 @@
   }
 
   public SortedSet<SelectionProperty> getProperties() {
-    return properties;
+    return selectionProperties;
   }
 
   public StandardSelectionProperty getProperty(String name) {
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 b8474f9..14972c1 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
@@ -16,7 +16,7 @@
 package com.google.gwt.core.ext.linker.impl;
 
 import com.google.gwt.core.ext.linker.SelectionProperty;
-import com.google.gwt.dev.cfg.Property;
+import com.google.gwt.dev.cfg.BindingProperty;
 
 import java.util.Arrays;
 import java.util.Collections;
@@ -25,7 +25,7 @@
 
 /**
  * The standard implementation of {@link SelectionProperty} from a
- * {@link Property}.
+ * {@link BindingProperty}.
  */
 public class StandardSelectionProperty implements SelectionProperty {
   private final String activeValue;
@@ -33,13 +33,17 @@
   private final String provider;
   private final SortedSet<String> values;
 
-  public StandardSelectionProperty(Property p) {
-    activeValue = p.getActiveValue();
+  public StandardSelectionProperty(BindingProperty p) {
+    if (p.getAllowedValues().length == 1) {
+      activeValue = p.getAllowedValues()[0];
+    } else {
+      activeValue = null;
+    }
     name = p.getName();
     provider = p.getProvider() == null ? null
         : p.getProvider().getBody().toSource();
     values = Collections.unmodifiableSortedSet(new TreeSet<String>(
-        Arrays.asList(p.getKnownValues())));
+        Arrays.asList(p.getDefinedValues())));
   }
 
   public String getName() {
diff --git a/dev/core/src/com/google/gwt/dev/GWTCompiler.java b/dev/core/src/com/google/gwt/dev/GWTCompiler.java
index beb2f7f..de85f00 100644
--- a/dev/core/src/com/google/gwt/dev/GWTCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/GWTCompiler.java
@@ -20,9 +20,10 @@
 import com.google.gwt.core.ext.TreeLogger.Type;
 import com.google.gwt.core.ext.linker.ArtifactSet;
 import com.google.gwt.core.ext.linker.impl.StandardLinkerContext;
+import com.google.gwt.dev.cfg.BindingProperty;
+import com.google.gwt.dev.cfg.ConfigurationProperty;
 import com.google.gwt.dev.cfg.ModuleDef;
 import com.google.gwt.dev.cfg.ModuleDefLoader;
-import com.google.gwt.dev.cfg.Property;
 import com.google.gwt.dev.cfg.PropertyPermutations;
 import com.google.gwt.dev.cfg.Rules;
 import com.google.gwt.dev.cfg.StaticPropertyOracle;
@@ -54,6 +55,7 @@
 import java.io.File;
 import java.util.HashSet;
 import java.util.Set;
+import java.util.SortedSet;
 
 /**
  * The main executable entry point for the GWT Java to JavaScript compiler.
@@ -113,11 +115,13 @@
         PropertyPermutations perms) {
       propertyOracles = new StaticPropertyOracle[perms.size()];
       rebindOracles = new RebindOracle[perms.size()];
-      Property[] orderedProps = perms.getOrderedProperties();
+      BindingProperty[] orderedProps = perms.getOrderedProperties();
+      SortedSet<ConfigurationProperty> configPropSet = module.getProperties().getConfigurationProperties();
+      ConfigurationProperty[] configProps = configPropSet.toArray(new ConfigurationProperty[configPropSet.size()]);
       for (int i = 0; i < rebindOracles.length; ++i) {
         String[] orderedPropValues = perms.getOrderedPropertyValues(i);
         propertyOracles[i] = new StaticPropertyOracle(orderedProps,
-            orderedPropValues);
+            orderedPropValues, configProps);
         rebindOracles[i] = new StandardRebindOracle(compilationState,
             propertyOracles[i], module, rules, genDir, generatorResourcesDir,
             generatorArtifacts);
diff --git a/dev/core/src/com/google/gwt/dev/PermutationCompiler.java b/dev/core/src/com/google/gwt/dev/PermutationCompiler.java
index 6069588..7513e1c 100644
--- a/dev/core/src/com/google/gwt/dev/PermutationCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/PermutationCompiler.java
@@ -20,7 +20,7 @@
 import com.google.gwt.core.ext.linker.SelectionProperty;
 import com.google.gwt.core.ext.linker.impl.StandardCompilationResult;
 import com.google.gwt.core.ext.linker.impl.StandardLinkerContext;
-import com.google.gwt.dev.cfg.Property;
+import com.google.gwt.dev.cfg.BindingProperty;
 import com.google.gwt.dev.cfg.StaticPropertyOracle;
 import com.google.gwt.dev.jjs.JavaToJavaScriptCompiler;
 import com.google.gwt.dev.jjs.ast.JProgram;
@@ -61,7 +61,7 @@
   private static final class PermutationTask implements Callable<String> {
     private static void logProperties(TreeLogger logger,
         StaticPropertyOracle propOracle) {
-      Property[] props = propOracle.getOrderedProps();
+      BindingProperty[] props = propOracle.getOrderedProps();
       String[] values = propOracle.getOrderedPropValues();
       if (logger.isLoggable(TreeLogger.DEBUG)) {
         logger = logger.branch(TreeLogger.DEBUG, "Setting properties", null);
@@ -422,7 +422,7 @@
     StandardCompilationResult compilation = linkerContext.getCompilation(
         logger, result.getJs());
     StaticPropertyOracle propOracle = perm.getPropertyOracle();
-    Property[] orderedProps = propOracle.getOrderedProps();
+    BindingProperty[] orderedProps = propOracle.getOrderedProps();
     String[] orderedPropValues = propOracle.getOrderedPropValues();
     Map<SelectionProperty, String> unboundProperties = new HashMap<SelectionProperty, String>();
     for (int i = 0; i < orderedProps.length; i++) {
diff --git a/dev/core/src/com/google/gwt/dev/cfg/BindingProperty.java b/dev/core/src/com/google/gwt/dev/cfg/BindingProperty.java
new file mode 100644
index 0000000..e62a644
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/cfg/BindingProperty.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.cfg;
+
+import java.util.Arrays;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * Represents a single named deferred binding or configuration property that can
+ * answer with its value. The BindingProperty maintains two sets of values, the
+ * "defined" set and the "allowed" set. The allowed set must always be a subset
+ * of the defined set.
+ */
+public class BindingProperty extends Property {
+
+  private SortedSet<String> allowedValues;
+  private final SortedSet<String> definedValues = new TreeSet<String>();
+  private PropertyProvider provider;
+
+  {
+    /*
+     * This is initially an alias for definedValues and is only set differently
+     * if the user calls setAllowedValues().
+     */
+    allowedValues = definedValues;
+  }
+
+  public BindingProperty(String name) {
+    super(name);
+  }
+
+  public void addDefinedValue(String newValue) {
+    definedValues.add(newValue);
+  }
+
+  public String[] getAllowedValues() {
+    return allowedValues.toArray(new String[allowedValues.size()]);
+  }
+
+  public String[] getDefinedValues() {
+    return definedValues.toArray(new String[definedValues.size()]);
+  }
+
+  public PropertyProvider getProvider() {
+    return provider;
+  }
+
+  public boolean isAllowedValue(String value) {
+    return allowedValues.contains(value);
+  }
+
+  /**
+   * Returns <code>true</code> if the value was previously provided to
+   * {@link #addDefinedValue(String)} since the last time {@link #clearValues()}
+   * was called.
+   */
+  public boolean isDefinedValue(String value) {
+    return definedValues.contains(value);
+  }
+
+  /**
+   * 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) {
+    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;
+  }
+
+  public void setProvider(PropertyProvider provider) {
+    this.provider = provider;
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ConfigurationProperty.java b/dev/core/src/com/google/gwt/dev/cfg/ConfigurationProperty.java
new file mode 100644
index 0000000..e211499
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/cfg/ConfigurationProperty.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.cfg;
+
+/**
+ * Represents a module property which does not impact deferred-binding
+ * decisions.
+ */
+public class ConfigurationProperty extends Property {
+  private String value;
+
+  public ConfigurationProperty(String name) {
+    this(name, null);
+  }
+
+  public ConfigurationProperty(String name, String value) {
+    super(name);
+    setValue(value);
+  }
+
+  public String getValue() {
+    return value;
+  }
+
+  public void setValue(String value) {
+    this.value = value;
+  }
+}
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 b136f57..405e93c 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java
@@ -400,22 +400,15 @@
 
     // Normalize property providers.
     //
-    for (Iterator<Property> iter = getProperties().iterator(); iter.hasNext();) {
-      Property prop = iter.next();
-      if (prop.getActiveValue() == null) {
-        // If there are more than one possible values, then create a provider.
-        // Otherwise, pretend the one value is an active value.
-        //
-        String[] knownValues = prop.getKnownValues();
-        assert (knownValues.length > 0);
-        if (knownValues.length > 1) {
-          if (prop.getProvider() == null) {
-            // Create a default provider.
-            //
-            prop.setProvider(new DefaultPropertyProvider(this, prop));
-          }
-        } else {
-          prop.setActiveValue(knownValues[0]);
+    for (Property current : getProperties()) {
+      if (current instanceof BindingProperty) {
+        BindingProperty prop = (BindingProperty) current;
+        /*
+         * 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) {
+          prop.setProvider(new DefaultPropertyProvider(this, prop));
         }
       }
     }
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 00c8ea6..b7b9a5d 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java
@@ -88,6 +88,10 @@
 
     protected final String __servlet_2_class = null;
 
+    protected final String __set_configuration_property_1_name = null;
+
+    protected final String __set_configuration_property_2_value = null;
+
     protected final String __set_property_1_name = null;
 
     protected final String __set_property_2_value = null;
@@ -144,16 +148,30 @@
 
     protected Schema __define_property_begin(PropertyName name,
         PropertyValue[] values) throws UnableToCompleteException {
-      if (moduleDef.getProperties().find(name.token) != null) {
-        // Disallow redefinition.
-        String msg = "Attempt to redefine property '" + name.token + "'";
-        logger.log(TreeLogger.ERROR, msg, null);
+
+      Property existingProperty = moduleDef.getProperties().find(name.token);
+      if (existingProperty != null) {
+        // Disallow redefinition of properties, but provide a type-sensitive
+        // error message to aid in diagnosis.
+        if (existingProperty instanceof BindingProperty) {
+          logger.log(TreeLogger.ERROR, "The deferred-binding property named "
+              + name.token + " may not be redefined.");
+        } else if (existingProperty instanceof ConfigurationProperty) {
+          logger.log(TreeLogger.ERROR, "A configuration property named "
+              + name.token + " has already been set.");
+        } else {
+          // Future proofing if other subclasses are added.
+          logger.log(TreeLogger.ERROR, "May not replace property named "
+              + name.token + " of unknown type "
+              + existingProperty.getClass().getName());
+        }
         throw new UnableToCompleteException();
       }
 
-      Property prop = moduleDef.getProperties().create(name.token);
+      BindingProperty prop = moduleDef.getProperties().createBinding(name.token);
+
       for (int i = 0; i < values.length; i++) {
-        prop.addKnownValue(values[i].token);
+        prop.addDefinedValue(values[i].token);
       }
 
       // No children.
@@ -165,10 +183,10 @@
       return null;
     }
 
-    protected Schema __extend_property_begin(Property property,
+    protected Schema __extend_property_begin(BindingProperty property,
         PropertyValue[] values) {
       for (int i = 0; i < values.length; i++) {
-        property.addKnownValue(values[i].token);
+        property.addDefinedValue(values[i].token);
       }
 
       // No children.
@@ -195,12 +213,12 @@
       return null;
     }
 
-    protected Schema __property_provider_begin(Property property) {
+    protected Schema __property_provider_begin(BindingProperty property) {
       property.setProvider(new PropertyProvider(moduleDef, property));
       return fChild = new PropertyProviderBodySchema();
     }
 
-    protected void __property_provider_end(Property property)
+    protected void __property_provider_end(BindingProperty property)
         throws UnableToCompleteException {
       PropertyProviderBodySchema childSchema = ((PropertyProviderBodySchema) fChild);
       String script = childSchema.getScript();
@@ -295,8 +313,42 @@
       return null;
     }
 
-    protected Schema __set_property_begin(Property prop, PropertyValue value) {
-      prop.setActiveValue(value.token);
+    protected Schema __set_configuration_property_begin(PropertyName name,
+        String value) throws UnableToCompleteException {
+
+      // Don't allow configuration properties with the same name as a
+      // deferred-binding property.
+      Property prop = moduleDef.getProperties().find(name.token);
+      if (prop != null && !(prop instanceof ConfigurationProperty)) {
+        logger.log(TreeLogger.ERROR, "A deferred-binding property named "
+            + name.token + " has already been defined");
+        throw new UnableToCompleteException();
+      } else {
+        prop = moduleDef.getProperties().createConfiguration(name.token);
+      }
+
+      ((ConfigurationProperty) prop).setValue(value);
+
+      // No children.
+      return null;
+    }
+
+    protected Schema __set_property_begin(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++) {
+        if (!prop.isDefinedValue(stringValues[i] = value[i].token)) {
+          logger.log(TreeLogger.ERROR, "The value " + stringValues[i]
+              + " was not previously defined.");
+          error = true;
+        }
+      }
+      if (error) {
+        throw new UnableToCompleteException();
+      }
+
+      prop.setAllowedValues(stringValues);
 
       // No children.
       return null;
@@ -513,11 +565,13 @@
       return new ConditionSchema(cond);
     }
 
-    // We intentionally use the Property 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(Property prop, PropertyValue value) {
+    /*
+     * 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);
 
@@ -691,6 +745,12 @@
    * Converts property names into their corresponding property objects.
    */
   private final class PropertyAttrCvt extends AttributeConverter {
+    private Class<? extends Property> concreteType;
+
+    public PropertyAttrCvt(Class<? extends Property> concreteType) {
+      this.concreteType = concreteType;
+    }
+
     public Object convertToArg(Schema schema, int line, String elem,
         String attr, String value) throws UnableToCompleteException {
       // Find the named property.
@@ -700,13 +760,19 @@
       if (prop != null) {
         // Found it.
         //
-        return prop;
+        if (concreteType.isInstance(prop)) {
+          return prop;
+        }
+        logger.log(TreeLogger.ERROR, "The specified property '" + attr
+            + "' is not of the correct type; found '"
+            + prop.getClass().getSimpleName() + "' expecting '"
+            + concreteType.getSimpleName() + "'");
       } else {
         // Property not defined. This is a problem.
         //
         Messages.PROPERTY_NOT_FOUND.log(logger, value, null);
-        throw new UnableToCompleteException();
       }
+      throw new UnableToCompleteException();
     }
   }
 
@@ -857,10 +923,12 @@
 
   protected final String __module_1_rename_to = "";
 
+  private final PropertyAttrCvt bindingPropAttrCvt = new PropertyAttrCvt(
+      BindingProperty.class);
   private final BodySchema bodySchema;
-
   private final ClassAttrCvt classAttrCvt = new ClassAttrCvt();
-
+  private final PropertyAttrCvt configurationPropAttrCvt = new PropertyAttrCvt(
+      ConfigurationProperty.class);
   private boolean foundAnyPublic;
   private boolean foundExplicitSourceOrSuperSource;
   private final ObjAttrCvt<Generator> genAttrCvt = new ObjAttrCvt<Generator>(
@@ -874,7 +942,6 @@
   private final String modulePackageAsPath;
   private final URL moduleURL;
   private final NullableNameAttrCvt nullableNameAttrCvt = new NullableNameAttrCvt();
-  private final PropertyAttrCvt propAttrCvt = new PropertyAttrCvt();
   private final PropertyNameAttrCvt propNameAttrCvt = new PropertyNameAttrCvt();
   private final PropertyValueArrayAttrCvt propValueArrayAttrCvt = new PropertyValueArrayAttrCvt();
   private final PropertyValueAttrCvt propValueAttrCvt = new PropertyValueAttrCvt();
@@ -890,7 +957,9 @@
     this.bodySchema = new BodySchema();
 
     registerAttributeConverter(PropertyName.class, propNameAttrCvt);
-    registerAttributeConverter(Property.class, propAttrCvt);
+    registerAttributeConverter(BindingProperty.class, bindingPropAttrCvt);
+    registerAttributeConverter(ConfigurationProperty.class,
+        configurationPropAttrCvt);
     registerAttributeConverter(PropertyValue.class, propValueAttrCvt);
     registerAttributeConverter(PropertyValue[].class, propValueArrayAttrCvt);
     registerAttributeConverter(Generator.class, genAttrCvt);
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 92ac8f8..e52bfca 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/Properties.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/Properties.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 Google Inc.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -15,57 +15,96 @@
  */
 package com.google.gwt.dev.cfg;
 
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashMap;
+import java.lang.reflect.InvocationTargetException;
 import java.util.Iterator;
-import java.util.Map;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
 
 /**
  * A typed map of deferred binding properties.
  */
 public class Properties implements Iterable<Property> {
 
-  private final Map<String, Property> map = new HashMap<String, Property>();
+  private final SortedSet<BindingProperty> bindingProps = new TreeSet<BindingProperty>();
 
-  private Property[] propertiesLazyArray;
+  private final SortedSet<ConfigurationProperty> configProps = new TreeSet<ConfigurationProperty>();
+
+  private final SortedMap<String, Property> map = new TreeMap<String, Property>();
 
   /**
-   * Creates the specified property, or returns an existing one by the specified
-   * name if present.
+   * Creates the specified deferred-binding property, or returns an existing one
+   * by the specified name if present.
    */
-  public Property create(String name) {
-    if (name == null) {
-      throw new NullPointerException("name");
-    }
+  public BindingProperty createBinding(String name) {
+    BindingProperty prop = create(name, BindingProperty.class);
+    bindingProps.add(prop);
+    return prop;
+  }
 
-    Property property = find(name);
-    if (property == null) {
-      property = new Property(name);
-      map.put(name, property);
-    }
-
-    return property;
+  /**
+   * Creates the specified configuration property, or returns an existing one by
+   * the specified name if present.
+   */
+  public ConfigurationProperty createConfiguration(String name) {
+    ConfigurationProperty prop = create(name, ConfigurationProperty.class);
+    configProps.add(prop);
+    return prop;
   }
 
   public Property find(String name) {
     return map.get(name);
   }
 
+  /**
+   * Gets all deferred binding properties in sorted order.
+   */
+  public SortedSet<BindingProperty> getBindingProperties() {
+    return bindingProps;
+  }
+
+  public SortedSet<ConfigurationProperty> getConfigurationProperties() {
+    return configProps;
+  }
+
   public Iterator<Property> iterator() {
     return map.values().iterator();
   }
 
-  /**
-   * Lists all properties in sorted order.
-   */
-  public Property[] toArray() {
-    if (propertiesLazyArray == null) {
-      Collection<Property> properties = map.values();
-      int n = properties.size();
-      propertiesLazyArray = properties.toArray(new Property[n]);
-      Arrays.sort(propertiesLazyArray);
+  private <T extends Property> T create(String name, Class<T> clazz) {
+    if (clazz == null) {
+      throw new NullPointerException("clazz");
+    } else if (name == null) {
+      throw new NullPointerException("name");
     }
-    return propertiesLazyArray;
+
+    Property property = find(name);
+    if (property != null) {
+      try {
+        return clazz.cast(property);
+      } catch (ClassCastException e) {
+        throw new IllegalArgumentException("Cannot create property " + name
+            + " because one of another type ("
+            + property.getClass().getSimpleName() + ") already exists.");
+      }
+    }
+
+    Exception ex = null;
+    try {
+      T newInstance = clazz.getConstructor(String.class).newInstance(name);
+      map.put(name, newInstance);
+      return newInstance;
+    } catch (NoSuchMethodException e) {
+      ex = e;
+    } catch (InstantiationException e) {
+      ex = e;
+    } catch (IllegalAccessException e) {
+      ex = e;
+    } catch (InvocationTargetException e) {
+      ex = e;
+    }
+
+    throw new RuntimeException("Unable to create Property instance", ex);
   }
 }
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 ec48e62..ad30a8b 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/Property.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/Property.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2006 Google Inc.
+ * Copyright 2008 Google Inc.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -15,82 +15,25 @@
  */
 package com.google.gwt.dev.cfg;
 
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-
 /**
- * Represents a single named deferred binding property that can answer with its
- * value.
+ * Represents an abstract module property.
  */
-public class Property implements Comparable {
+public abstract class Property implements Comparable<Property> {
 
-  private String activeValue;
+  protected final String name;
 
-  private Set knownValues = new HashSet();
-
-  private String[] knownValuesLazyArray;
-
-  private final String name;
-
-  private PropertyProvider provider;
-
-  public Property(String name) {
+  protected Property(String name) {
     this.name = name;
   }
 
-  public void addKnownValue(String knownValue) {
-    knownValues.add(knownValue);
-    knownValuesLazyArray = null;
-  }
-
-  public int compareTo(Object other) {
-    return name.compareTo(((Property) other).name);
-  }
-
-  /**
-   * Gets the property value or <code>null</code> if the property has no
-   * value.
-   */
-  public String getActiveValue() {
-    return activeValue;
-  }
-
-  /**
-   * Lists all the known values for this property in sorted order.
-   */
-  public String[] getKnownValues() {
-    if (knownValuesLazyArray == null) {
-      int n = knownValues.size();
-      knownValuesLazyArray = (String[]) knownValues.toArray(new String[n]);
-      Arrays.sort(knownValuesLazyArray);
-    }
-    return knownValuesLazyArray;
+  public int compareTo(Property o) {
+    return name.compareTo(o.name);
   }
 
   public String getName() {
     return name;
   }
 
-  public PropertyProvider getProvider() {
-    return provider;
-  }
-
-  public boolean isKnownValue(String value) {
-    return knownValues.contains(value);
-  }
-
-  /**
-   * Sets the property value, or clears it by specifying <code>null</code>.
-   */
-  public void setActiveValue(String value) {
-    this.activeValue = value;
-  }
-
-  public void setProvider(PropertyProvider provider) {
-    this.provider = provider;
-  }
-
   public String toString() {
     return name;
   }
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 b9ce882..36c8139 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/PropertyPermutations.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/PropertyPermutations.java
@@ -17,6 +17,7 @@
 
 import java.util.Arrays;
 import java.util.Iterator;
+import java.util.SortedSet;
 
 /**
  * Generates all possible permutations of properties in a module.
@@ -27,12 +28,13 @@
 
   private final int lastProp;
 
-  private final Property[] properties;
+  private final BindingProperty[] properties;
 
   private final String[][] values;
 
   public PropertyPermutations(Properties properties) {
-    this.properties = properties.toArray();
+    SortedSet<BindingProperty> bindingProps = properties.getBindingProperties();
+    this.properties = bindingProps.toArray(new BindingProperty[bindingProps.size()]);
     lastProp = this.properties.length - 1;
     int permCount = countPermutations();
     values = new String[permCount][];
@@ -44,7 +46,7 @@
     }
   }
 
-  public Property[] getOrderedProperties() {
+  public BindingProperty[] getOrderedProperties() {
     return properties;
   }
 
@@ -68,7 +70,7 @@
   private int countPermutations() {
     int count = 1;
     for (int i = 0; i < properties.length; i++) {
-      Property prop = properties[i];
+      BindingProperty prop = properties[i];
       String[] options = getPossibilities(prop);
       assert (options.length > 0);
       count *= options.length;
@@ -76,21 +78,12 @@
     return count;
   }
 
-  private String[] getPossibilities(Property prop) {
-    String activeValue = prop.getActiveValue();
-    if (activeValue != null) {
-      // This property is fixed.
-      //
-      return new String[] {activeValue};
-    } else {
-      // This property is determined on the client.
-      //
-      return prop.getKnownValues();
-    }
+  private String[] getPossibilities(BindingProperty prop) {
+    return prop.getAllowedValues();
   }
 
   private void permute(String[] soFar, int whichProp) {
-    Property prop = properties[whichProp];
+    BindingProperty prop = properties[whichProp];
     String[] options = getPossibilities(prop);
 
     for (int i = 0; i < options.length; i++) {
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 460ef95..3fa969a 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/StaticPropertyOracle.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/StaticPropertyOracle.java
@@ -25,17 +25,20 @@
  */
 public class StaticPropertyOracle implements PropertyOracle {
 
-  private final Property[] orderedProps;
+  private final ConfigurationProperty[] configProps;
+
+  private final BindingProperty[] orderedProps;
 
   private final String[] orderedPropValues;
 
-  public StaticPropertyOracle(Property[] orderedProps,
-      String[] orderedPropValues) {
+  public StaticPropertyOracle(BindingProperty[] orderedProps,
+      String[] orderedPropValues, ConfigurationProperty[] configProps) {
     this.orderedProps = orderedProps;
     this.orderedPropValues = orderedPropValues;
+    this.configProps = configProps;
   }
 
-  public Property[] getOrderedProps() {
+  public BindingProperty[] getOrderedProps() {
     return orderedProps;
   }
 
@@ -51,10 +54,10 @@
     // name-to-index map.
     //
     for (int i = 0; i < orderedProps.length; i++) {
-      Property prop = orderedProps[i];
+      BindingProperty prop = orderedProps[i];
       if (prop.getName().equals(propertyName)) {
         String value = orderedPropValues[i];
-        if (prop.isKnownValue(value)) {
+        if (prop.isAllowedValue(value)) {
           return value;
         } else {
           throw new BadPropertyValueException(propertyName, value);
@@ -62,6 +65,12 @@
       }
     }
 
+    for (ConfigurationProperty configProp : configProps) {
+      if (configProp.getName().equals(propertyName)) {
+        return configProp.getValue();
+      }
+    }
+
     // Didn't find it.
     //
     throw new BadPropertyValueException(propertyName);
@@ -70,12 +79,14 @@
   public String[] getPropertyValueSet(TreeLogger logger, String propertyName)
       throws BadPropertyValueException {
     for (int i = 0; i < orderedProps.length; i++) {
-      Property prop = orderedProps[i];
+      BindingProperty prop = orderedProps[i];
       if (prop.getName().equals(propertyName)) {
-        return prop.getKnownValues();
+        return prop.getAllowedValues();
       }
     }
 
+    // Configuration properties throw exception per javadoc.
+
     // Didn't find it.
     //
     throw new BadPropertyValueException(propertyName);
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 a22c4f3..a079b20 100644
--- a/dev/core/src/com/google/gwt/dev/shell/ModuleSpacePropertyOracle.java
+++ b/dev/core/src/com/google/gwt/dev/shell/ModuleSpacePropertyOracle.java
@@ -18,6 +18,8 @@
 import com.google.gwt.core.ext.BadPropertyValueException;
 import com.google.gwt.core.ext.PropertyOracle;
 import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.dev.cfg.BindingProperty;
+import com.google.gwt.dev.cfg.ConfigurationProperty;
 import com.google.gwt.dev.cfg.Properties;
 import com.google.gwt.dev.cfg.Property;
 
@@ -56,7 +58,15 @@
     if (prevAnswers.containsKey(propertyName)) {
       return prevAnswers.get(propertyName);
     } else {
-      String value = computePropertyValue(logger, propertyName, prop);
+      String value;
+      if (prop instanceof ConfigurationProperty) {
+        value = ((ConfigurationProperty) prop).getValue();
+      } else if (prop instanceof BindingProperty) {
+        value = computePropertyValue(logger, propertyName,
+            (BindingProperty) prop);
+      } else {
+        throw new BadPropertyValueException(propertyName);
+      }
       prevAnswers.put(propertyName, value);
       return value;
     }
@@ -68,7 +78,10 @@
   public String[] getPropertyValueSet(TreeLogger logger, String propertyName)
       throws BadPropertyValueException {
     Property prop = getProperty(propertyName);
-    return prop.getKnownValues();
+    if (prop instanceof BindingProperty) {
+      return ((BindingProperty) prop).getAllowedValues();
+    }
+    throw new BadPropertyValueException(propertyName);
   }
 
   /**
@@ -79,33 +92,32 @@
    *           property.
    */
   private String computePropertyValue(TreeLogger logger, String propertyName,
-      Property prop) throws BadPropertyValueException {
-    String value;
-    // If there is an active value, use that.
-    //
-    value = prop.getActiveValue();
+      BindingProperty prop) throws BadPropertyValueException {
 
-    // In case there isn't an active value...
-    if (value == null) {
-      // Invokes the script function.
+    if (prop.getAllowedValues().length == 1) {
+      // If there is only one legal value, use that.
+      return prop.getAllowedValues()[0];
+    }
+
+    String value;
+    // Invokes the script function.
+    //
+    try {
+      // Invoke the property provider function in JavaScript.
       //
-      try {
-        // Invoke the property provider function in JavaScript.
-        //
-        value = (String) space.invokeNativeObject("__gwt_getProperty", null,
-            new Class[] {String.class}, new Object[] {prop.getName()});
-      } catch (Throwable e) {
-        // Treat as an unknown value.
-        //
-        String msg = "Error while executing the JavaScript provider for property '"
-            + propertyName + "'";
-        logger.log(TreeLogger.ERROR, msg, e);
-        throw new BadPropertyValueException(propertyName, "<failed to compute>");
-      }
+      value = (String) space.invokeNativeObject("__gwt_getProperty", null,
+          new Class[] {String.class}, new Object[] {prop.getName()});
+    } catch (Throwable e) {
+      // Treat as an unknown value.
+      //
+      String msg = "Error while executing the JavaScript provider for property '"
+          + propertyName + "'";
+      logger.log(TreeLogger.ERROR, msg, e);
+      throw new BadPropertyValueException(propertyName, "<failed to compute>");
     }
 
     // value may be null if the provider returned an unknown property value.
-    if (prop.isKnownValue(value)) {
+    if (prop.isAllowedValue(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 d7a7be8..d8774df 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
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 Google Inc.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -17,7 +17,7 @@
 
 import com.google.gwt.dev.cfg.ModuleDef;
 import com.google.gwt.dev.cfg.Properties;
-import com.google.gwt.dev.cfg.Property;
+import com.google.gwt.dev.cfg.BindingProperty;
 import com.google.gwt.dev.cfg.PropertyPermutations;
 
 import junit.framework.TestCase;
@@ -31,9 +31,9 @@
     Properties props = md.getProperties();
 
     {
-      Property prop = props.create("debug");
-      prop.addKnownValue("false");
-      prop.addKnownValue("true");
+      BindingProperty prop = props.createBinding("debug");
+      prop.addDefinedValue("false");
+      prop.addDefinedValue("true");
     }
 
     // Permutations and their values are in stable alphabetical order.
@@ -56,16 +56,16 @@
     Properties props = md.getProperties();
 
     {
-      Property prop = props.create("user.agent");
-      prop.addKnownValue("moz");
-      prop.addKnownValue("ie6");
-      prop.addKnownValue("opera");
+      BindingProperty prop = props.createBinding("user.agent");
+      prop.addDefinedValue("moz");
+      prop.addDefinedValue("ie6");
+      prop.addDefinedValue("opera");
     }
 
     {
-      Property prop = props.create("debug");
-      prop.addKnownValue("false");
-      prop.addKnownValue("true");
+      BindingProperty prop = props.createBinding("debug");
+      prop.addDefinedValue("false");
+      prop.addDefinedValue("true");
     }
 
     // String[]s and their values are in stable alphabetical order.
diff --git a/distro-source/core/src/gwt-module.dtd b/distro-source/core/src/gwt-module.dtd
index 1baa502..9f83e3e 100644
--- a/distro-source/core/src/gwt-module.dtd
+++ b/distro-source/core/src/gwt-module.dtd
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!--                                                                        -->
-<!-- Copyright 2007 Google Inc.                                             -->
+<!-- Copyright 2008 Google Inc.                                             -->
 <!-- Licensed under the Apache License, Version 2.0 (the "License"); you    -->
 <!-- may not use this file except in compliance with the License. You may   -->
 <!-- may obtain a copy of the License at                                    -->
@@ -16,8 +16,8 @@
 <!-- The module root element -->
 <!ELEMENT module (inherits | source | public | super-source | entry-point | 
   stylesheet | script | servlet | replace-with | generate-with |
-  define-property | extend-property | set-property | property-provider |
-  define-linker | add-linker)*>
+  define-property | extend-property | set-property | set-configuration-property |
+  property-provider | define-linker | add-linker)*>
 <!ATTLIST module
 	rename-to CDATA #IMPLIED
 >
@@ -89,14 +89,14 @@
 <!ELEMENT add-linker EMPTY>
 <!-- A comma-separated list of linker names -->
 <!ATTLIST add-linker
-  name CDATA #REQUIRED
+	name CDATA #REQUIRED
 >
 
 <!-- Defines a Linker type to package compiler output -->
 <!ELEMENT define-linker EMPTY>
 <!ATTLIST define-linker
-  class CDATA #REQUIRED
-  name CDATA #REQUIRED
+	class CDATA #REQUIRED
+	name CDATA #REQUIRED
 >
 
 <!--                 ^^^ Commonly-used elements ^^^                -->
@@ -114,6 +114,12 @@
 	name CDATA #REQUIRED
 	value CDATA #REQUIRED
 >
+<!-- Set the value of a configuration property -->
+<!ELEMENT set-configuration-property EMPTY>
+<!ATTLIST set-configuration-property
+	name CDATA #REQUIRED
+	value CDATA #REQUIRED
+>
 <!-- Add additional allowable values to a property -->
 <!ELEMENT extend-property EMPTY>
 <!ATTLIST extend-property
diff --git a/user/src/com/google/gwt/junit/JUnitShell.java b/user/src/com/google/gwt/junit/JUnitShell.java
index a9161e5..d485cc5 100644
--- a/user/src/com/google/gwt/junit/JUnitShell.java
+++ b/user/src/com/google/gwt/junit/JUnitShell.java
@@ -22,6 +22,8 @@
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.dev.BootStrapPlatform;
 import com.google.gwt.dev.GWTShell;
+import com.google.gwt.dev.cfg.BindingProperty;
+import com.google.gwt.dev.cfg.ConfigurationProperty;
 import com.google.gwt.dev.cfg.ModuleDef;
 import com.google.gwt.dev.cfg.ModuleDefLoader;
 import com.google.gwt.dev.cfg.Properties;
@@ -578,8 +580,8 @@
     if (userAgentString != null) {
       Properties props = module.getProperties();
       Property userAgent = props.find("user.agent");
-      if (userAgent != null) {
-        userAgent.setActiveValue(userAgentString);
+      if (userAgent instanceof BindingProperty) {
+        ((BindingProperty) userAgent).setAllowedValues(userAgentString);
       }
     }
     BrowserWidgetHost browserHost = getBrowserHost();
@@ -618,10 +620,9 @@
       currentModule.clearEntryPoints();
       currentModule.addEntryPointTypeName(GWTRunner.class.getName());
       // Squirrel away the name of the active module for GWTRunnerGenerator
-      Property moduleNameProp = currentModule.getProperties().create(
+      ConfigurationProperty moduleNameProp = currentModule.getProperties().createConfiguration(
           "junit.moduleName");
-      moduleNameProp.addKnownValue(moduleName);
-      moduleNameProp.setActiveValue(moduleName);
+      moduleNameProp.setValue(moduleName);
       runStyle.maybeCompileModule(syntheticModuleName);
     }
 
@@ -711,7 +712,7 @@
       Pattern pattern = Pattern.compile("[^\\s\"]+|\"[^\"\\\\]*(\\\\.[^\"\\\\]*)*\"");
       Matcher matcher = pattern.matcher(args);
       Pattern quotedArgsPattern = Pattern.compile("^([\"'])(.*)([\"'])$");
-      
+
       while (matcher.find()) {
         // Strip leading and trailing quotes from the arg
         String arg = matcher.group();
diff --git a/user/test/com/google/gwt/dev/cfg/PropertyTest.gwt.xml b/user/test/com/google/gwt/dev/cfg/PropertyTest.gwt.xml
new file mode 100644
index 0000000..5f61ad5
--- /dev/null
+++ b/user/test/com/google/gwt/dev/cfg/PropertyTest.gwt.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--                                                                        -->
+<!-- Copyright 2008 Google Inc.                                             -->
+<!-- Licensed under the Apache License, Version 2.0 (the "License"); you    -->
+<!-- may not use this file except in compliance with the License. You may   -->
+<!-- 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. License for the specific language governing permissions and   -->
+<!-- limitations under the License.                                         -->
+<module>
+  <define-property name="restricted" values="a,b,c" />
+  <extend-property name="restricted" values="d,e,f" />
+  <set-property name="restricted" value="a,b,c" />
+
+  <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" />
+    
+  <set-configuration-property name="configProperty" value="Hello World!" />
+
+  <set-configuration-property name="configRedefined" value="foo" />
+  <set-configuration-property name="configRedefined" value="bar" />
+
+</module>
diff --git a/user/test/com/google/gwt/dev/cfg/PropertyTest.java b/user/test/com/google/gwt/dev/cfg/PropertyTest.java
new file mode 100644
index 0000000..08f4786
--- /dev/null
+++ b/user/test/com/google/gwt/dev/cfg/PropertyTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.cfg;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
+
+import junit.framework.TestCase;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+
+/**
+ * Checks the behaviors of ModuleDefLoader and Properties.
+ */
+public class PropertyTest extends TestCase {
+
+  private static TreeLogger getRootLogger() {
+    PrintWriterTreeLogger logger = new PrintWriterTreeLogger(new PrintWriter(
+        System.err, true));
+    logger.setMaxDetail(TreeLogger.ERROR);
+    return logger;
+  }
+
+  private final ModuleDef moduleDef;
+
+  public PropertyTest() throws UnableToCompleteException {
+    // Module has the same name as this class.
+    String moduleName = getClass().getCanonicalName();
+    moduleDef = ModuleDefLoader.loadFromClassPath(getRootLogger(), moduleName);
+  }
+
+  public void testModule() {
+    Properties p = moduleDef.getProperties();
+
+    {
+      BindingProperty restricted = (BindingProperty) p.find("restricted");
+      assertNotNull(restricted);
+      assertEquals(3, restricted.getAllowedValues().length);
+      assertEquals(Arrays.asList("a", "b", "c"),
+          Arrays.asList(restricted.getAllowedValues()));
+      assertTrue(restricted.isDefinedValue("d"));
+      assertFalse(restricted.isAllowedValue("d"));
+    }
+
+    {
+      BindingProperty restricted1s = (BindingProperty) p.find("restricted1s");
+      assertNotNull(restricted1s);
+      assertTrue(restricted1s.isAllowedValue("a"));
+      assertEquals(1, restricted1s.getAllowedValues().length);
+      assertEquals("a", restricted1s.getAllowedValues()[0]);
+    }
+
+    {
+      Property configProperty = p.find("configProperty");
+      assertEquals("Hello World!",
+          ((ConfigurationProperty) configProperty).getValue());
+    }
+
+    {
+      Property configRedefined = p.find("configRedefined");
+      assertEquals("bar", ((ConfigurationProperty) configRedefined).getValue());
+    }
+  }
+
+  public void testModuleInvalidOverride() {
+    String moduleName = getClass().getCanonicalName();
+
+    for (String name : Arrays.asList("A", "B", "C", "D")) {
+      try {
+        ModuleDefLoader.loadFromClassPath(TreeLogger.NULL, moduleName + "Bad"
+            + name);
+        fail("Test " + name + " should have thrown UnableToCompleteException");
+      } catch (UnableToCompleteException e) {
+        // OK
+      }
+    }
+  }
+
+  public void testProperty() {
+    Properties properties = new Properties();
+
+    assertNull(properties.find("deferred"));
+    BindingProperty d = properties.createBinding("deferred");
+    assertSame(d, properties.createBinding("deferred"));
+    try {
+      properties.createConfiguration("deferred");
+      fail("Should have thrown an IllegalArgumentException");
+    } catch (IllegalArgumentException e) {
+      // OK
+    }
+
+    assertNull(properties.find("config"));
+    ConfigurationProperty c = properties.createConfiguration("config");
+    assertSame(c, properties.createConfiguration("config"));
+    try {
+      properties.createBinding("config");
+      fail("Should have thrown an IllegalArgumentException");
+    } catch (IllegalArgumentException e) {
+      // OK
+    }
+  }
+}
diff --git a/user/test/com/google/gwt/dev/cfg/PropertyTestBadA.gwt.xml b/user/test/com/google/gwt/dev/cfg/PropertyTestBadA.gwt.xml
new file mode 100644
index 0000000..cd068e4
--- /dev/null
+++ b/user/test/com/google/gwt/dev/cfg/PropertyTestBadA.gwt.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--                                                                        -->
+<!-- Copyright 2008 Google Inc.                                             -->
+<!-- Licensed under the Apache License, Version 2.0 (the "License"); you    -->
+<!-- may not use this file except in compliance with the License. You may   -->
+<!-- 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. License for the specific language governing permissions and   -->
+<!-- limitations under the License.                                         -->
+<module>
+ <define-property name="testProp" values="a,b,c" />
+ <set-configuration-property name="testProp" value="bad" />
+</module>
diff --git a/user/test/com/google/gwt/dev/cfg/PropertyTestBadB.gwt.xml b/user/test/com/google/gwt/dev/cfg/PropertyTestBadB.gwt.xml
new file mode 100644
index 0000000..3d0cc6a
--- /dev/null
+++ b/user/test/com/google/gwt/dev/cfg/PropertyTestBadB.gwt.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--                                                                        -->
+<!-- Copyright 2008 Google Inc.                                             -->
+<!-- Licensed under the Apache License, Version 2.0 (the "License"); you    -->
+<!-- may not use this file except in compliance with the License. You may   -->
+<!-- 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. License for the specific language governing permissions and   -->
+<!-- limitations under the License.                                         -->
+<module>
+ <set-configuration-property name="testProp" value="bad" />
+ <define-property name="testProp" values="a,b,c" />
+</module>
diff --git a/user/test/com/google/gwt/dev/cfg/PropertyTestBadC.gwt.xml b/user/test/com/google/gwt/dev/cfg/PropertyTestBadC.gwt.xml
new file mode 100644
index 0000000..8c4b953
--- /dev/null
+++ b/user/test/com/google/gwt/dev/cfg/PropertyTestBadC.gwt.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--                                                                        -->
+<!-- Copyright 2008 Google Inc.                                             -->
+<!-- Licensed under the Apache License, Version 2.0 (the "License"); you    -->
+<!-- may not use this file except in compliance with the License. You may   -->
+<!-- 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. License for the specific language governing permissions and   -->
+<!-- limitations under the License.                                         -->
+<module>
+ <define-property name="testProp" values="a,b,c" />
+ <set-property name="testProp" value="a,b,Z" />
+</module>
diff --git a/user/test/com/google/gwt/dev/cfg/PropertyTestBadD.gwt.xml b/user/test/com/google/gwt/dev/cfg/PropertyTestBadD.gwt.xml
new file mode 100644
index 0000000..ba5bb32
--- /dev/null
+++ b/user/test/com/google/gwt/dev/cfg/PropertyTestBadD.gwt.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--                                                                        -->
+<!-- Copyright 2008 Google Inc.                                             -->
+<!-- Licensed under the Apache License, Version 2.0 (the "License"); you    -->
+<!-- may not use this file except in compliance with the License. You may   -->
+<!-- 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. License for the specific language governing permissions and   -->
+<!-- limitations under the License.                                         -->
+<module>
+ <define-property name="testProp" values="a,b,c" />
+ <set-property name="testProp" value="Z" />
+</module>
diff --git a/user/test/com/google/gwt/dev/cfg/TagSuite.java b/user/test/com/google/gwt/dev/cfg/TagSuite.java
index 35642fb..a4108c5 100644
--- a/user/test/com/google/gwt/dev/cfg/TagSuite.java
+++ b/user/test/com/google/gwt/dev/cfg/TagSuite.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 Google Inc.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -21,7 +21,7 @@
 import junit.framework.Test;
 
 /**
- * Tests all classes in GWT JRE emulation library.
+ * Tests various tags in gwt.xml files.
  */
 public class TagSuite {
 
@@ -33,12 +33,13 @@
      */
     BootStrapPlatform.applyPlatformHacks();
   }
-  
+
   public static Test suite() {
     GWTTestSuite suite = new GWTTestSuite(
         "Tests for public, source, and super-source tags");
 
     // $JUnit-BEGIN$
+    suite.addTestSuite(PropertyTest.class);
     suite.addTestSuite(PublicTagTest.class);
     suite.addTestSuite(SourceTagTest.class);
     suite.addTestSuite(SuperSourceTagTest.class);
diff --git a/user/test/com/google/gwt/module/ConfigurationProperties.gwt.xml b/user/test/com/google/gwt/module/ConfigurationProperties.gwt.xml
new file mode 100644
index 0000000..0d6a6af
--- /dev/null
+++ b/user/test/com/google/gwt/module/ConfigurationProperties.gwt.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--                                                                        -->
+<!-- Copyright 2008 Google Inc.                                             -->
+<!-- Licensed under the Apache License, Version 2.0 (the "License"); you    -->
+<!-- may not use this file except in compliance with the License. You may   -->
+<!-- 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. License for the specific language governing permissions and   -->
+<!-- limitations under the License.                                         -->
+<module>
+  <set-configuration-property name="testProperty"
+    value="Hello World!" />
+  <generate-with
+    class="com.google.gwt.module.rebind.ConfigurationPropertiesGenerator">
+    <when-type-assignable
+      class="com.google.gwt.module.client.ConfigurationPropertiesTest.TestHook" />
+  </generate-with>
+</module>
diff --git a/user/test/com/google/gwt/module/ModuleSuite.java b/user/test/com/google/gwt/module/ModuleSuite.java
index ce6c509..ab4852b 100644
--- a/user/test/com/google/gwt/module/ModuleSuite.java
+++ b/user/test/com/google/gwt/module/ModuleSuite.java
@@ -16,6 +16,7 @@
 package com.google.gwt.module;
 
 import com.google.gwt.junit.tools.GWTTestSuite;
+import com.google.gwt.module.client.ConfigurationPropertiesTest;
 import com.google.gwt.module.client.DoubleScriptInjectionTest;
 import com.google.gwt.module.client.NoDeployTest;
 import com.google.gwt.module.client.SingleScriptInjectionTest;
@@ -29,6 +30,7 @@
   public static Test suite() {
     GWTTestSuite suite = new GWTTestSuite();
 
+    suite.addTestSuite(ConfigurationPropertiesTest.class);
     suite.addTestSuite(SingleScriptInjectionTest.class);
     suite.addTestSuite(DoubleScriptInjectionTest.class);
     suite.addTestSuite(NoDeployTest.class);
diff --git a/user/test/com/google/gwt/module/client/ConfigurationPropertiesTest.java b/user/test/com/google/gwt/module/client/ConfigurationPropertiesTest.java
new file mode 100644
index 0000000..a147e52
--- /dev/null
+++ b/user/test/com/google/gwt/module/client/ConfigurationPropertiesTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.module.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * Ensures that Generators are able to access module configuration properties.
+ */
+public class ConfigurationPropertiesTest extends GWTTestCase {
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.module.ConfigurationProperties";
+  }
+
+  /**
+   * Marker interface for deferred binding to run
+   * {@link com.google.gwt.module.rebind.ConfigurationPropertiesGenerator}.
+   */
+  public interface TestHook {
+    String getConfigProperty();
+  }
+
+  public void testConfigProperty() {
+    assertEquals("Hello World!",
+        GWT.<TestHook> create(TestHook.class).getConfigProperty());
+  }
+}
diff --git a/user/test/com/google/gwt/module/rebind/ConfigurationPropertiesGenerator.java b/user/test/com/google/gwt/module/rebind/ConfigurationPropertiesGenerator.java
new file mode 100644
index 0000000..9e5fdf4
--- /dev/null
+++ b/user/test/com/google/gwt/module/rebind/ConfigurationPropertiesGenerator.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.module.rebind;
+
+import com.google.gwt.core.ext.BadPropertyValueException;
+import com.google.gwt.core.ext.Generator;
+import com.google.gwt.core.ext.GeneratorContext;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+
+import java.io.PrintWriter;
+
+/**
+ * A test generator to dump the contents of the "testProperty" configuration
+ * property.
+ */
+public class ConfigurationPropertiesGenerator extends Generator {
+
+  @Override
+  public String generate(TreeLogger logger, GeneratorContext context,
+      String typeName) throws UnableToCompleteException {
+    JClassType type = context.getTypeOracle().findType(typeName);
+    assert type != null;
+
+    PrintWriter out = context.tryCreate(logger, type.getPackage().getName(),
+        "TestHookImpl");
+    if (out != null) {
+      String propertyValue;
+      try {
+        propertyValue = context.getPropertyOracle().getPropertyValue(logger,
+            "testProperty");
+      } catch (BadPropertyValueException e) {
+        logger.log(TreeLogger.ERROR, "testProperty not set", e);
+        throw new UnableToCompleteException();
+      }
+
+      try {
+        context.getPropertyOracle().getPropertyValue(TreeLogger.NULL,
+            "bad_property");
+        logger.log(TreeLogger.ERROR,
+            "Did not get an exception trying to access fake property");
+        throw new UnableToCompleteException();
+      } catch (BadPropertyValueException e) {
+        // OK
+      }
+
+      out.println("package " + type.getPackage().getName() + ";");
+      out.println("public class TestHookImpl implements ConfigurationPropertiesTest.TestHook {");
+      out.println("public String getConfigProperty() {");
+      out.println("return \"" + escape(propertyValue) + "\";");
+      out.println("}}");
+
+      context.commit(logger, out);
+    }
+    return type.getPackage().getName() + ".TestHookImpl";
+  }
+}
diff --git a/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java b/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java
index b5bc215..a3d9d13 100644
--- a/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java
+++ b/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java
@@ -28,9 +28,10 @@
 import com.google.gwt.core.ext.typeinfo.NotFoundException;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.core.ext.typeinfo.JWildcardType.BoundType;
+import com.google.gwt.dev.cfg.BindingProperty;
+import com.google.gwt.dev.cfg.ConfigurationProperty;
 import com.google.gwt.dev.cfg.ModuleDef;
 import com.google.gwt.dev.cfg.ModuleDefLoader;
-import com.google.gwt.dev.cfg.Property;
 import com.google.gwt.dev.cfg.StaticPropertyOracle;
 import com.google.gwt.dev.javac.CompilationUnit;
 import com.google.gwt.dev.javac.JavaSourceCodeBase;
@@ -236,7 +237,7 @@
       TreeLogger logger, TypeOracle to) throws UnableToCompleteException {
     // Make an empty property oracle.
     StaticPropertyOracle propertyOracle = new StaticPropertyOracle(
-        new Property[0], new String[0]);
+        new BindingProperty[0], new String[0], new ConfigurationProperty[0]);
     return new SerializableTypeOracleBuilder(logger, propertyOracle, to);
   }