Adds RuntimePropertyRegistry for runtime rebinding.

The RuntimePropertyRegistry is necessary because runtime rebinding
depends on the execution of condition checks embedded in rebind rules,
and these condition checks depend on property value lookups which it
provides.

Runtime rebinding is needed for separate compilation since static
compile time rebinding is only possible when in possession of complete
global knowledge and thus can only be done in monolithic compiles.

Separate compilation is wanted because it makes things faster by
reducing the amount of work necessary when only a couple of files
have changed and also by making it possible to run some work in
parallel.

Change-Id: I5f688e679a1dab2fa95f6a451a779b7201aee93d
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 99a3b0c..0dbc561 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ConditionWhenPropertyIs.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ConditionWhenPropertyIs.java
@@ -49,7 +49,7 @@
   @Override
   public String toSource() {
     return String.format(
-        "BindingPropertiesProvider.getPropertyValue(\"%s\").equals(\"%s\")", propName, value);
+        "RuntimePropertyRegistry.getPropertyValue(\"%s\").equals(\"%s\")", propName, value);
   }
 
   @Override
diff --git a/dev/core/src/com/google/gwt/dev/cfg/PropertyProviderRegistratorGenerator.java b/dev/core/src/com/google/gwt/dev/cfg/PropertyProviderRegistratorGenerator.java
index a3792e7..0334210 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/PropertyProviderRegistratorGenerator.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/PropertyProviderRegistratorGenerator.java
@@ -91,20 +91,22 @@
 
     if (out != null) {
       out.println("package " + PACKAGE_PATH + ";");
-      out.println("import com.google.gwt.core.client.BindingPropertiesProvider;");
+      out.println("import com.google.gwt.core.client.RuntimePropertyRegistry;");
       out.println(
-          "import com.google.gwt.core.client.BindingPropertiesProvider.BindingPropertyProvider;");
+          "import com.google.gwt.core.client.RuntimePropertyRegistry.PropertyValueProvider;");
       out.println("public class " + typeName + " {");
 
       for (BindingProperty bindingProperty : newBindingProperties) {
         createPropertyProviderClass(logger, out, bindingProperty);
       }
+      // TODO(stalcup): create configuration property providers.
 
       out.println("  public static void register() {");
       for (String propertyProviderClassName : propertyProviderClassNames) {
-        out.println("    BindingPropertiesProvider.registerBindingPropertyProvider(new "
+        out.println("    RuntimePropertyRegistry.registerPropertyValueProvider(new "
             + propertyProviderClassName + "());");
       }
+      // TODO(stalcup): register configuration property providers.
       out.println("  }");
       out.println("}");
 
@@ -152,11 +154,11 @@
 
   private void createPropertyProviderClass(
       TreeLogger logger, PrintWriter out, BindingProperty bindingProperty) {
-    String bindingPropertyClassName = "BindingPropertyProvider" + propertyProviderClassNames.size();
+    String bindingPropertyClassName = "PropertyValueProvider" + propertyProviderClassNames.size();
     propertyProviderClassNames.add(bindingPropertyClassName);
 
-    out.println("  private static class " + bindingPropertyClassName
-        + " extends BindingPropertyProvider {");
+    out.println(
+        "  private static class " + bindingPropertyClassName + " extends PropertyValueProvider {");
     out.println("  public String getName() {");
     out.println("    return \"" + bindingProperty.getName() + "\";");
     out.println("  }");
@@ -182,13 +184,13 @@
       TreeLogger logger, PrintWriter out, BindingProperty bindingProperty) {
     out.print("  public native String getValue() /*-");
     out.print(generateValue(logger, bindingProperty).trim());
-    out.println("  -*/;");
+    out.println("-*/;");
   }
 
   private void createProviderGetter(PrintWriter out, BindingProperty bindingProperty) {
     out.print("  public native String getValue() /*-");
     out.print(bindingProperty.getProvider().getBody().trim());
-    out.print("  -*/;");
+    out.print("-*/;");
   }
 
   private String generateValue(TreeLogger logger, BindingProperty bindingProperty) {
diff --git a/dev/core/test/com/google/gwt/dev/cfg/ConditionTest.java b/dev/core/test/com/google/gwt/dev/cfg/ConditionTest.java
index 410fe41..f8372a8 100644
--- a/dev/core/test/com/google/gwt/dev/cfg/ConditionTest.java
+++ b/dev/core/test/com/google/gwt/dev/cfg/ConditionTest.java
@@ -119,9 +119,9 @@
 
     // Exercise it all.
     assertEquals(
-        "!(((((requestTypeName.equals(\"com.google.gwt.Foo\")) && (BindingPropertiesProvider."
+        "!(((((requestTypeName.equals(\"com.google.gwt.Foo\")) && (RuntimePropertyRegistry."
         + "getPropertyValue(\"user.agent\").equals(\"webkit\")))) || (((requestTypeName.equals"
-        + "(\"com.google.gwt.HasFocus\")) && (BindingPropertiesProvider.getPropertyValue("
+        + "(\"com.google.gwt.HasFocus\")) && (RuntimePropertyRegistry.getPropertyValue("
         + "\"user.agent\").equals(\"ie9\"))))))", new ConditionNone(new ConditionAll(
         new ConditionWhenTypeIs("com.google.gwt.Foo"),
         new ConditionWhenPropertyIs("user.agent", "webkit")), new ConditionAll(
diff --git a/user/src/com/google/gwt/core/client/RuntimePropertyRegistry.java b/user/src/com/google/gwt/core/client/RuntimePropertyRegistry.java
new file mode 100644
index 0000000..387a7df
--- /dev/null
+++ b/user/src/com/google/gwt/core/client/RuntimePropertyRegistry.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2013 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.client;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A queryable runtime registry of binding property providers and configuration property
+ * values.<br />
+ *
+ * Supports the execution of runtime rebinding by providing property value lookup to executing
+ * runtime rebind rules.
+ */
+public class RuntimePropertyRegistry {
+
+  /**
+   * A base for classes that can return or calculate a value for a property.
+   */
+  public abstract static class PropertyValueProvider {
+
+    /**
+     * Returns the name of the property for which a value is being provided.
+     */
+    public abstract String getName();
+
+    /**
+     * Returns a value for the intended property. The value might or might not be calculated on the
+     * fly based on the browser environment.
+     */
+    public abstract String getValue();
+  }
+
+  /**
+   * A cache of previously calculated values for requested property names.
+   */
+  private static Map<String, String> cachedPropertyValuesByName = new HashMap<String, String>();
+
+  /**
+   * The registry of property value provider classes, registered by name.
+   */
+  private static Map<String, PropertyValueProvider> propertyValueProvidersByName =
+      new HashMap<String, PropertyValueProvider>();
+
+  /**
+   * Returns the value for the given property name. On first access the matching property value
+   * provider is found and executed while subsequent access are served from a cache.
+   */
+  public static String getPropertyValue(String propertyName) {
+    if (cachedPropertyValuesByName.containsKey(propertyName)) {
+      return cachedPropertyValuesByName.get(propertyName);
+    }
+
+    if (propertyValueProvidersByName.containsKey(propertyName)) {
+      PropertyValueProvider propertyProvider = propertyValueProvidersByName.get(propertyName);
+      String propertyValue = propertyProvider.getValue();
+      cachedPropertyValuesByName.put(propertyName, propertyValue);
+      return propertyValue;
+    }
+
+    throw new RuntimeException("Can't get a value for property '" + propertyName
+        + "' since it does not have a registered value generator.");
+  }
+
+  /**
+   * Registers the given property value provider. Registered providers are indexed by property name
+   * for fast retrieval.
+   */
+  public static void registerPropertyValueProvider(PropertyValueProvider propertyValueProvider) {
+    propertyValueProvidersByName.put(propertyValueProvider.getName(), propertyValueProvider);
+  }
+}
diff --git a/user/src/com/google/gwt/core/client/RuntimeRebindRule.java b/user/src/com/google/gwt/core/client/RuntimeRebindRule.java
new file mode 100644
index 0000000..50a79e4
--- /dev/null
+++ b/user/src/com/google/gwt/core/client/RuntimeRebindRule.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2013 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.client;
+
+/**
+ * A base for rule classes that can judge conditions and create object instances as part of the
+ * runtime rebind rule framework.<br />
+ *
+ * Instances are registered with the RuntimeRebinder as part of module bootstrapping and are queried
+ * and creation invoked to service GWT.create() invocations.<br />
+ *
+ * Subclasses are dynamically generated during compilation to match replacement, generator output
+ * and fallback rebind rules.
+ */
+public abstract class RuntimeRebindRule {
+
+  /**
+   * Returns a newly created instance of the requested type or some replacement.
+   */
+  public abstract Object createInstance();
+
+  /**
+   * Returns whether the requested type name along with the current browser environment satisfies
+   * the condition embedded in this rule.
+   */
+  public abstract boolean matches(String requestTypeName);
+}
diff --git a/user/test/com/google/gwt/core/CoreSuite.java b/user/test/com/google/gwt/core/CoreSuite.java
index 3318edf..4cb67ba 100644
--- a/user/test/com/google/gwt/core/CoreSuite.java
+++ b/user/test/com/google/gwt/core/CoreSuite.java
@@ -23,6 +23,7 @@
 import com.google.gwt.core.client.JsArrayMixedTest;
 import com.google.gwt.core.client.JsArrayTest;
 import com.google.gwt.core.client.JsIdentityTest;
+import com.google.gwt.core.client.RuntimePropertyRegistryTest;
 import com.google.gwt.core.client.SchedulerTest;
 import com.google.gwt.core.client.ScriptInjectorTest;
 import com.google.gwt.core.client.impl.AsyncFragmentLoaderTest;
@@ -54,6 +55,7 @@
     suite.addTestSuite(JsArrayTest.class);
     suite.addTestSuite(JsArrayMixedTest.class);
     suite.addTestSuite(RunAsyncCodeTest.class);
+    suite.addTestSuite(RuntimePropertyRegistryTest.class);
     suite.addTestSuite(SchedulerImplTest.class);
     suite.addTestSuite(SchedulerTest.class);
     suite.addTestSuite(ScriptInjectorTest.class);
diff --git a/user/test/com/google/gwt/core/client/RuntimePropertyRegistryTest.java b/user/test/com/google/gwt/core/client/RuntimePropertyRegistryTest.java
new file mode 100644
index 0000000..7e011e8
--- /dev/null
+++ b/user/test/com/google/gwt/core/client/RuntimePropertyRegistryTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2013 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.client;
+
+import com.google.gwt.core.client.RuntimePropertyRegistry.PropertyValueProvider;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for the RuntimePropertyRegistry.
+ */
+public class RuntimePropertyRegistryTest extends TestCase {
+
+  private static class SimplePropertyValueProvider extends PropertyValueProvider {
+
+    private int accessCount;
+    private String name;
+    private String value;
+
+    public SimplePropertyValueProvider(String name, String value) {
+      this.name = name;
+      this.value = value;
+    }
+
+    @Override
+    public String getName() {
+      return name;
+    }
+
+    @Override
+    public String getValue() {
+      accessCount++;
+      return value;
+    }
+  }
+
+  public void testRegisterAndGet() {
+    // Sets up.
+    SimplePropertyValueProvider propertyValueProvider =
+        new SimplePropertyValueProvider("user.agent", "webkit");
+    RuntimePropertyRegistry.registerPropertyValueProvider(propertyValueProvider);
+
+    // Verifies lookup and caching.
+    assertEquals("webkit", RuntimePropertyRegistry.getPropertyValue("user.agent"));
+    assertEquals("webkit", RuntimePropertyRegistry.getPropertyValue("user.agent"));
+    assertEquals("webkit", RuntimePropertyRegistry.getPropertyValue("user.agent"));
+    assertEquals(1, propertyValueProvider.accessCount);
+  }
+}