Adds intelligent rerun metadata to generators.

This is metadata (related property names and property/type output
instability) that can be used to determine when and on what scope
of input to run a particular generator during separate compilation.

Without this metadata the only safe way to run generators would
be to defer all of them to a global pass at the end of separate
compilation, which would eliminate any generator result caching and
generator execution parallelism.

As a result, having and using this generator metadata is an
important part of making separate compilation fast.

Change-Id: I763a02a41a5abfd522852b0ad29e200548316c6f
Review-Link: https://gwt-review.googlesource.com/#/c/5243/
diff --git a/dev/core/src/com/google/gwt/core/ext/Generator.java b/dev/core/src/com/google/gwt/core/ext/Generator.java
index 524dddb..05b408b 100644
--- a/dev/core/src/com/google/gwt/core/ext/Generator.java
+++ b/dev/core/src/com/google/gwt/core/ext/Generator.java
@@ -1,31 +1,35 @@
 /*
  * Copyright 2006 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
- * 
+ *
+ * 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
+ *
+ * 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;
 
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet;
+
 /**
- * Generates source code for subclasses during deferred binding requests.
- * Subclasses must be thread-safe.
+ * Generates source code for subclasses during deferred binding requests. Subclasses must be
+ * thread-safe.<br />
+ *
+ * Well-behaved generators can speed up the separate compiles by overriding @{link
+ * #getAccessedPropertyNames}, @{link #contentDependsOnProperties}, and @{contentDependsOnTypes}".
+ * The compiler will use this information to run generators less often and cache their outputs.
  */
 public abstract class Generator {
 
   /**
    * Escapes string content to be a valid string literal.
-   * 
-   * @return an escaped version of <code>unescaped</code>, suitable for being
-   *         enclosed in double quotes in Java source
+   *
+   * @return an escaped version of <code>unescaped</code>, suitable for being enclosed in double
+   *         quotes in Java source
    */
   public static String escape(String unescaped) {
     int extra = 0;
@@ -78,15 +82,45 @@
   }
 
   /**
-   * Generate a default constructible subclass of the requested type. The
-   * generator throws <code>UnableToCompleteException</code> if for any reason
-   * it cannot provide a substitute class
-   * 
-   * @return the name of a subclass to substitute for the requested class, or
-   *         return <code>null</code> to cause the requested type itself to be
-   *         used
-   * 
+   * Generate a default constructible subclass of the requested type. The generator throws
+   * <code>UnableToCompleteException</code> if for any reason it cannot provide a substitute class
+   *
+   * @return the name of a subclass to substitute for the requested class, or return
+   *         <code>null</code> to cause the requested type itself to be used
    */
-  public abstract String generate(TreeLogger logger, GeneratorContext context,
-      String typeName) throws UnableToCompleteException;
+  public abstract String generate(TreeLogger logger, GeneratorContext context, String typeName)
+      throws UnableToCompleteException;
+
+  /**
+   * Returns the set of names of all properties that are accessed by generator execution and affect
+   * its behavior. Returning a null indicates that *all* properties are considered relevant.<br />
+   *
+   * Generators that don't need access to every property can override this method to speed up
+   * separate compiles.
+   */
+  public ImmutableSet<String> getAccessedPropertyNames() {
+    return null;
+  }
+
+  /**
+   * Whether the *content* of created files (not the list of files created) changes as the set value
+   * of configuration properties or the list of legal values of binding properties changes.<br />
+   *
+   * Generators whose output content is stable even when property values change can override this
+   * method to speed up separate compiles.
+   */
+  public boolean contentDependsOnProperties() {
+    return true;
+  }
+
+  /**
+   * Whether the *content* of created files (not the list of files created) changes as more types
+   * are created that match some subtype query.<br />
+   *
+   * Generators whose output content is stable even as new types are created can override this
+   * method to speed up separate compiles.
+   */
+  public boolean contentDependsOnTypes() {
+    return true;
+  }
 }
diff --git a/dev/core/src/com/google/gwt/core/ext/SubsetFilteringPropertyOracle.java b/dev/core/src/com/google/gwt/core/ext/SubsetFilteringPropertyOracle.java
new file mode 100644
index 0000000..6d95c4b
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/SubsetFilteringPropertyOracle.java
@@ -0,0 +1,58 @@
+/*
+ * 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.ext;
+
+import com.google.gwt.thirdparty.guava.common.base.Preconditions;
+
+import java.util.Set;
+
+/**
+ * A property oracle that prevents access to any properties not named in its predefined set.<br />
+ *
+ * Used by the generator driver framework to limit property access for the purpose of
+ * forcing generators to accurately declare their property dependencies.
+ */
+public class SubsetFilteringPropertyOracle implements PropertyOracle {
+
+  private final Set<String> accessiblePropertyNames;
+  private final String accessViolationMessage;
+  private final PropertyOracle wrappedPropertyOracle;
+
+  public SubsetFilteringPropertyOracle(Set<String> accessiblePropertyNames,
+      PropertyOracle wrappedPropertyOracle, String accessViolationMessage) {
+    this.accessiblePropertyNames = accessiblePropertyNames;
+    this.wrappedPropertyOracle = wrappedPropertyOracle;
+    this.accessViolationMessage = accessViolationMessage;
+  }
+
+  @Override
+  public ConfigurationProperty getConfigurationProperty(String propertyName)
+      throws BadPropertyValueException {
+    Preconditions.checkState(
+        accessiblePropertyNames == null || accessiblePropertyNames.contains(propertyName),
+            "Access to configuration property '" + propertyName + "' is not allowed. "
+            + accessViolationMessage);
+    return wrappedPropertyOracle.getConfigurationProperty(propertyName);
+  }
+
+  @Override
+  public SelectionProperty getSelectionProperty(TreeLogger logger, String propertyName)
+      throws BadPropertyValueException {
+    Preconditions.checkState(
+        accessiblePropertyNames == null || accessiblePropertyNames.contains(propertyName),
+            "Access to binding property '" + propertyName + "' is not allowed. "
+            + accessViolationMessage);
+    return wrappedPropertyOracle.getSelectionProperty(logger, propertyName);
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/javac/StandardGeneratorContext.java b/dev/core/src/com/google/gwt/dev/javac/StandardGeneratorContext.java
index 18d0b91..65cbde2 100644
--- a/dev/core/src/com/google/gwt/dev/javac/StandardGeneratorContext.java
+++ b/dev/core/src/com/google/gwt/dev/javac/StandardGeneratorContext.java
@@ -22,6 +22,7 @@
 import com.google.gwt.core.ext.PropertyOracle;
 import com.google.gwt.core.ext.RebindResult;
 import com.google.gwt.core.ext.RebindRuleResolver;
+import com.google.gwt.core.ext.SubsetFilteringPropertyOracle;
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.linker.Artifact;
@@ -292,7 +293,7 @@
   private final Map<String, PendingResource> pendingResources =
       new HashMap<String, PendingResource>();
 
-  private transient PropertyOracle propOracle;
+  private transient PropertyOracle propertyOracle;
 
   private RebindRuleResolver rebindRuleResolver;
 
@@ -561,7 +562,7 @@
 
   @Override
   public final PropertyOracle getPropertyOracle() {
-    return propOracle;
+    return propertyOracle;
   }
 
   @Override
@@ -648,8 +649,15 @@
     Event generatorEvent =
         SpeedTracerLogger.start(type, "class", generatorClassName, "type", typeName);
 
+    PropertyOracle originalPropertyOracle = propertyOracle;
     try {
       RebindResult result;
+      // TODO(stalcup): refactor the Generator/PropertyOracle system (in a potentially backwards
+      // incompatible way) so that all Generators are forced to accurately declare the names of
+      // properties they care about.
+      propertyOracle = new SubsetFilteringPropertyOracle(
+          generator.getAccessedPropertyNames(), originalPropertyOracle,
+          generatorClassName + ".getAccessedPropertyNames() may need to be updated.");
       if (generator instanceof IncrementalGenerator) {
         IncrementalGenerator incGenerator = (IncrementalGenerator) generator;
 
@@ -694,6 +702,7 @@
           + "' threw an exception while rebinding '" + typeName + "'", e);
       throw new UnableToCompleteException();
     } finally {
+      propertyOracle = originalPropertyOracle;
       generatorEvent.end();
     }
   }
@@ -717,8 +726,8 @@
    * Sets the current transient property oracle to answer current property
    * questions.
    */
-  public void setPropertyOracle(PropertyOracle propOracle) {
-    this.propOracle = propOracle;
+  public void setPropertyOracle(PropertyOracle propertyOracle) {
+    this.propertyOracle = propertyOracle;
   }
 
   public void setRebindRuleResolver(RebindRuleResolver resolver) {
diff --git a/user/src/com/google/gwt/editor/rebind/SimpleBeanEditorDriverGenerator.java b/user/src/com/google/gwt/editor/rebind/SimpleBeanEditorDriverGenerator.java
index 53f1a5e..85c4a91 100644
--- a/user/src/com/google/gwt/editor/rebind/SimpleBeanEditorDriverGenerator.java
+++ b/user/src/com/google/gwt/editor/rebind/SimpleBeanEditorDriverGenerator.java
@@ -19,6 +19,7 @@
 import com.google.gwt.editor.client.impl.AbstractSimpleBeanEditorDriver;
 import com.google.gwt.editor.client.impl.SimpleBeanEditorDelegate;
 import com.google.gwt.editor.rebind.model.EditorData;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet;
 
 /**
  * Generates implementations of {@link SimpleBeanEditorDriver}.
@@ -26,6 +27,23 @@
 public class SimpleBeanEditorDriverGenerator extends
     AbstractEditorDriverGenerator {
 
+  private static ImmutableSet<String> relevantPropertyNames = ImmutableSet.of();
+
+  @Override
+  public ImmutableSet<String> getAccessedPropertyNames() {
+    return relevantPropertyNames;
+  }
+
+  @Override
+  public boolean contentDependsOnProperties() {
+    return false;
+  }
+
+  @Override
+  public boolean contentDependsOnTypes() {
+    return false;
+  }
+
   @Override
   protected Class<?> getDriverInterfaceType() {
     return SimpleBeanEditorDriver.class;
diff --git a/user/src/com/google/gwt/i18n/rebind/CurrencyListGenerator.java b/user/src/com/google/gwt/i18n/rebind/CurrencyListGenerator.java
index 214e1bb..d486ac6 100644
--- a/user/src/com/google/gwt/i18n/rebind/CurrencyListGenerator.java
+++ b/user/src/com/google/gwt/i18n/rebind/CurrencyListGenerator.java
@@ -27,6 +27,7 @@
 import com.google.gwt.i18n.client.CurrencyList;
 import com.google.gwt.i18n.client.impl.CurrencyDataImpl;
 import com.google.gwt.i18n.shared.GwtLocale;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet;
 import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
 import com.google.gwt.user.rebind.SourceWriter;
 
@@ -42,9 +43,9 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 import java.util.TreeMap;
-import java.util.Map.Entry;
 import java.util.regex.Pattern;
 
 /**
@@ -72,10 +73,10 @@
 
     private final String portableSymbol;
 
-    private final String symbol;
-
     private String simpleSymbol;
 
+    private final String symbol;
+
     /**
      * Create an instance.
      * 
@@ -262,6 +263,9 @@
     return str.replace("\"", "\\\"");
   }
 
+  private static ImmutableSet<String> relevantPropertyNames =
+      ImmutableSet.of("locale.queryparam", "locale", "runtime.locales", "locale.cookie");
+
   /**
    * Generate an implementation for the given type.
    * 
@@ -298,6 +302,16 @@
         runtimeLocales);
   }
 
+  @Override
+  public ImmutableSet<String> getAccessedPropertyNames() {
+    return relevantPropertyNames;
+  }
+
+  @Override
+  public boolean contentDependsOnTypes() {
+    return false;
+  }
+
   /**
    * Generate an implementation class for the requested locale, including all
    * parent classes along the inheritance chain. The data will be kept at the
diff --git a/user/src/com/google/gwt/i18n/rebind/CustomDateTimeFormatGenerator.java b/user/src/com/google/gwt/i18n/rebind/CustomDateTimeFormatGenerator.java
index d69f8b3..9dcea38 100644
--- a/user/src/com/google/gwt/i18n/rebind/CustomDateTimeFormatGenerator.java
+++ b/user/src/com/google/gwt/i18n/rebind/CustomDateTimeFormatGenerator.java
@@ -29,6 +29,7 @@
 import com.google.gwt.i18n.shared.CustomDateTimeFormat.Pattern;
 import com.google.gwt.i18n.shared.DateTimeFormat;
 import com.google.gwt.i18n.shared.GwtLocale;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet;
 import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
 import com.google.gwt.user.rebind.SourceWriter;
 
@@ -41,6 +42,9 @@
  */
 public class CustomDateTimeFormatGenerator extends Generator {
 
+  private static ImmutableSet<String> relevantPropertyNames =
+      ImmutableSet.of("locale.queryparam", "locale", "runtime.locales", "locale.cookie");
+
   /**
    * Generate an implementation for the given type.
    * 
@@ -142,4 +146,14 @@
     }
     return packageName + "." + className;
   }
+
+  @Override
+  public ImmutableSet<String> getAccessedPropertyNames() {
+    return relevantPropertyNames;
+  }
+
+  @Override
+  public boolean contentDependsOnProperties() {
+    return false;
+  }
 }
diff --git a/user/src/com/google/gwt/i18n/rebind/LocaleInfoGenerator.java b/user/src/com/google/gwt/i18n/rebind/LocaleInfoGenerator.java
index a73f0e3..0efd8cb 100644
--- a/user/src/com/google/gwt/i18n/rebind/LocaleInfoGenerator.java
+++ b/user/src/com/google/gwt/i18n/rebind/LocaleInfoGenerator.java
@@ -29,6 +29,7 @@
 import com.google.gwt.i18n.client.impl.LocaleInfoImpl;
 import com.google.gwt.i18n.server.GwtLocaleImpl;
 import com.google.gwt.i18n.shared.GwtLocale;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet;
 import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
 import com.google.gwt.user.rebind.SourceWriter;
 
@@ -83,6 +84,9 @@
     RTL_LOCALES.add("ur");
   }
 
+  private static ImmutableSet<String> relevantPropertyNames =
+      ImmutableSet.of("locale.queryparam", "locale", "runtime.locales", "locale.cookie");
+
   /**
    * Generate an implementation for the given type.
    * 
@@ -313,6 +317,16 @@
     return packageName + "." + className;
   }
 
+  @Override
+  public ImmutableSet<String> getAccessedPropertyNames() {
+    return relevantPropertyNames;
+  }
+
+  @Override
+  public boolean contentDependsOnTypes() {
+    return false;
+  }
+
   /**
    * @param logger
    * @param context
diff --git a/user/src/com/google/gwt/i18n/rebind/LocalizableGenerator.java b/user/src/com/google/gwt/i18n/rebind/LocalizableGenerator.java
index 756afd4..a0ac13b 100644
--- a/user/src/com/google/gwt/i18n/rebind/LocalizableGenerator.java
+++ b/user/src/com/google/gwt/i18n/rebind/LocalizableGenerator.java
@@ -37,6 +37,7 @@
 import com.google.gwt.i18n.client.LocaleInfo;
 import com.google.gwt.i18n.client.Messages;
 import com.google.gwt.i18n.shared.GwtLocale;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -89,20 +90,23 @@
     }
   }
 
-   /**
+  public static final String CONSTANTS_NAME = Constants.class.getName();
+
+  public static final String CONSTANTS_WITH_LOOKUP_NAME = ConstantsWithLookup.class.getName();
+
+  /**
     * Obsolete comment for GWT metadata - needs to be removed once all
     * references have been removed.
     */
   public static final String GWT_KEY = "gwt.key";
 
-  public static final String CONSTANTS_NAME = Constants.class.getName();
-
-  public static final String CONSTANTS_WITH_LOOKUP_NAME = ConstantsWithLookup.class.getName();
-
   public static final String MESSAGES_NAME = Messages.class.getName();
 
   private LocalizableLinkageCreator linkageCreator = new LocalizableLinkageCreator();
 
+  private static ImmutableSet<String> relevantPropertyNames =
+      ImmutableSet.of("locale.queryparam", "locale", "runtime.locales", "locale.cookie");
+
   /**
    * Generate an implementation for the given type.
    * 
@@ -182,6 +186,21 @@
     return returnedClass;
   }
 
+  @Override
+  public ImmutableSet<String> getAccessedPropertyNames() {
+    return relevantPropertyNames;
+  }
+
+  @Override
+  public boolean contentDependsOnProperties() {
+    return false;
+  }
+
+  @Override
+  public boolean contentDependsOnTypes() {
+    return false;
+  }
+
   /**
    * Generate a runtime-selection implementation of the target class if needed,
    * delegating all overridable methods to an instance chosen at runtime based
diff --git a/user/src/com/google/gwt/resources/rebind/context/InlineClientBundleGenerator.java b/user/src/com/google/gwt/resources/rebind/context/InlineClientBundleGenerator.java
index baa3e97..222abe7 100644
--- a/user/src/com/google/gwt/resources/rebind/context/InlineClientBundleGenerator.java
+++ b/user/src/com/google/gwt/resources/rebind/context/InlineClientBundleGenerator.java
@@ -18,6 +18,7 @@
 import com.google.gwt.core.ext.GeneratorContext;
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet;
 
 /**
  * This is a refinement that will use data urls for browsers that support them.
@@ -29,9 +30,25 @@
 public final class InlineClientBundleGenerator extends
     AbstractClientBundleGenerator {
 
+  private static ImmutableSet<String> relevantPropertyNames = ImmutableSet.of(
+      "CssResource.obfuscationPrefix", "user.agent", "CssResource.mergeEnabled",
+      "ClientBundle.enableInlining", "locale", "CssResource.style",
+      "CssResource.reservedClassPrefixes", "ClientBundle.enableRenaming",
+      "ExternalTextResource.useJsonp");
+
   private final ClientBundleContext clientBundleCtx = new ClientBundleContext();
 
   @Override
+  public ImmutableSet<String> getAccessedPropertyNames() {
+    return relevantPropertyNames;
+  }
+
+  @Override
+  public boolean contentDependsOnTypes() {
+    return false;
+  }
+
+  @Override
   protected AbstractResourceContext createResourceContext(TreeLogger logger,
       GeneratorContext context, JClassType resourceBundleType) {
     return new InlineResourceContext(logger.branch(TreeLogger.DEBUG,
diff --git a/user/src/com/google/gwt/safehtml/rebind/SafeHtmlTemplatesGenerator.java b/user/src/com/google/gwt/safehtml/rebind/SafeHtmlTemplatesGenerator.java
index 1f0ec8a..1d90979 100644
--- a/user/src/com/google/gwt/safehtml/rebind/SafeHtmlTemplatesGenerator.java
+++ b/user/src/com/google/gwt/safehtml/rebind/SafeHtmlTemplatesGenerator.java
@@ -22,6 +22,7 @@
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.NotFoundException;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet;
 import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
 import com.google.gwt.user.rebind.SourceWriter;
 
@@ -33,6 +34,8 @@
  */
 public class SafeHtmlTemplatesGenerator extends Generator {
 
+  private static ImmutableSet<String> relevantPropertyNames = ImmutableSet.of();
+
   @Override
   public String generate(TreeLogger logger, GeneratorContext genCtx,
       String fqInterfaceName) throws UnableToCompleteException {
@@ -66,4 +69,19 @@
     }
     return packageName + "." + implName;
   }
+
+  @Override
+  public ImmutableSet<String> getAccessedPropertyNames() {
+    return relevantPropertyNames;
+  }
+
+  @Override
+  public boolean contentDependsOnProperties() {
+    return false;
+  }
+
+  @Override
+  public boolean contentDependsOnTypes() {
+    return false;
+  }
 }
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderGenerator.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderGenerator.java
index 1beb0b3..9420c9f 100644
--- a/user/src/com/google/gwt/uibinder/rebind/UiBinderGenerator.java
+++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderGenerator.java
@@ -27,6 +27,7 @@
 import com.google.gwt.dev.resource.Resource;
 import com.google.gwt.dev.resource.ResourceOracle;
 import com.google.gwt.dev.util.Util;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet;
 import com.google.gwt.uibinder.client.UiTemplate;
 import com.google.gwt.uibinder.rebind.messages.MessagesWriter;
 import com.google.gwt.uibinder.rebind.model.ImplicitClientBundle;
@@ -46,13 +47,13 @@
 
   private static final String BINDER_URI = "urn:ui:com.google.gwt.uibinder";
 
-  private static final String TEMPLATE_SUFFIX = ".ui.xml";
+  private static boolean gaveLazyBuildersWarning;
 
-  private static final String XSS_SAFE_CONFIG_PROPERTY = "UiBinder.useSafeHtmlTemplates";
+  private static boolean gaveSafeHtmlWarning;
   private static final String LAZY_WIDGET_BUILDERS_PROPERTY = "UiBinder.useLazyWidgetBuilders";
   
-  private static boolean gaveSafeHtmlWarning;
-  private static boolean gaveLazyBuildersWarning;
+  private static final String TEMPLATE_SUFFIX = ".ui.xml";
+  private static final String XSS_SAFE_CONFIG_PROPERTY = "UiBinder.useSafeHtmlTemplates";
 
   /**
    * Given a UiBinder interface, return the path to its ui.xml file, suitable
@@ -95,6 +96,9 @@
     return s.replace(".", "/").replace("$", ".");
   }
 
+  private static ImmutableSet<String> relevantPropertyNames =
+      ImmutableSet.of("UiBinder.useSafeHtmlTemplates", "UiBinder.useLazyWidgetBuilders");
+
   private final UiBinderContext uiBinderCtx = new UiBinderContext();
 
   @Override
@@ -132,6 +136,16 @@
     return packageName + "." + implName;
   }
 
+  @Override
+  public ImmutableSet<String> getAccessedPropertyNames() {
+    return relevantPropertyNames;
+  }
+
+  @Override
+  public boolean contentDependsOnTypes() {
+    return false;
+  }
+
   private Boolean extractConfigProperty(MortalLogger logger,
       PropertyOracle propertyOracle, String configProperty, boolean defaultValue) {
     List<String> values;
diff --git a/user/src/com/google/gwt/user/rebind/AsyncProxyGenerator.java b/user/src/com/google/gwt/user/rebind/AsyncProxyGenerator.java
index 1ecddb4..d598ebb 100644
--- a/user/src/com/google/gwt/user/rebind/AsyncProxyGenerator.java
+++ b/user/src/com/google/gwt/user/rebind/AsyncProxyGenerator.java
@@ -28,6 +28,7 @@
 import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
 import com.google.gwt.core.ext.typeinfo.JType;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet;
 import com.google.gwt.user.client.AsyncProxy;
 import com.google.gwt.user.client.AsyncProxy.AllowNonVoid;
 import com.google.gwt.user.client.AsyncProxy.ConcreteType;
@@ -43,6 +44,8 @@
  */
 public class AsyncProxyGenerator extends Generator {
 
+  private static ImmutableSet<String> relevantPropertyNames = ImmutableSet.of();
+
   @Override
   public String generate(TreeLogger logger, GeneratorContext generatorContext,
       String typeName) throws UnableToCompleteException {
@@ -208,6 +211,21 @@
     return createdClassName;
   }
 
+  @Override
+  public ImmutableSet<String> getAccessedPropertyNames() {
+    return relevantPropertyNames;
+  }
+
+  @Override
+  public boolean contentDependsOnProperties() {
+    return false;
+  }
+
+  @Override
+  public boolean contentDependsOnTypes() {
+    return false;
+  }
+
   private JClassType getConcreteType(TreeLogger logger, TypeOracle typeOracle,
       JClassType sourceType) throws UnableToCompleteException {
     JClassType concreteType;
diff --git a/user/src/com/google/gwt/user/rebind/DocumentModeGenerator.java b/user/src/com/google/gwt/user/rebind/DocumentModeGenerator.java
index 18f58fa..4518a26 100644
--- a/user/src/com/google/gwt/user/rebind/DocumentModeGenerator.java
+++ b/user/src/com/google/gwt/user/rebind/DocumentModeGenerator.java
@@ -26,6 +26,7 @@
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.NotFoundException;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet;
 import com.google.gwt.user.client.DocumentModeAsserter;
 import com.google.gwt.user.client.DocumentModeAsserter.Severity;
 
@@ -38,6 +39,9 @@
  */
 public class DocumentModeGenerator extends Generator {
 
+  private static ImmutableSet<String> relevantPropertyNames =
+      ImmutableSet.of("document.compatMode", "document.compatMode.severity");
+
   @Override
   public String generate(TreeLogger logger, GeneratorContext context, String typeName)
       throws UnableToCompleteException {
@@ -126,4 +130,14 @@
     }
     return composerFactory.getCreatedClassName();
   }
+
+  @Override
+  public ImmutableSet<String> getAccessedPropertyNames() {
+    return relevantPropertyNames;
+  }
+
+  @Override
+  public boolean contentDependsOnTypes() {
+    return false;
+  }
 }
diff --git a/user/src/com/google/gwt/user/rebind/rpc/ServiceInterfaceProxyGenerator.java b/user/src/com/google/gwt/user/rebind/rpc/ServiceInterfaceProxyGenerator.java
index 2e9026a..a5a32de 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/ServiceInterfaceProxyGenerator.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/ServiceInterfaceProxyGenerator.java
@@ -22,6 +22,7 @@
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet;
 
 /**
  * Generator for producing the asynchronous version of a
@@ -38,6 +39,10 @@
    */
   private static final long GENERATOR_VERSION_ID = 1L;
 
+  private static ImmutableSet<String> relevantPropertyNames = ImmutableSet.of(
+      "gwt.elideTypeNamesFromRPC", "rpc.enhancedClasses", "gwt.suppressNonStaticFinalFieldWarnings",
+      "rpc.blacklist");
+
   @Override
   public RebindResult generateIncrementally(TreeLogger logger, GeneratorContext ctx,
       String requestedClass) throws UnableToCompleteException {
@@ -68,6 +73,11 @@
   }
 
   @Override
+  public ImmutableSet<String> getAccessedPropertyNames() {
+    return relevantPropertyNames;
+  }
+
+  @Override
   public long getVersionId() {
     return GENERATOR_VERSION_ID;
   }
diff --git a/user/src/com/google/gwt/user/rebind/ui/ImageBundleGenerator.java b/user/src/com/google/gwt/user/rebind/ui/ImageBundleGenerator.java
index eefb238..85e5147 100644
--- a/user/src/com/google/gwt/user/rebind/ui/ImageBundleGenerator.java
+++ b/user/src/com/google/gwt/user/rebind/ui/ImageBundleGenerator.java
@@ -23,6 +23,7 @@
 import com.google.gwt.core.ext.typeinfo.JMethod;
 import com.google.gwt.core.ext.typeinfo.NotFoundException;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet;
 import com.google.gwt.user.client.ui.ImageBundle;
 import com.google.gwt.user.client.ui.ImageBundle.Resource;
 import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
@@ -107,6 +108,8 @@
 
   private static final String IMAGEBUNDLE_QNAME = "com.google.gwt.user.client.ui.ImageBundle";
 
+  private static ImmutableSet<String> relevantPropertyNames = ImmutableSet.of();
+
   /* private */static String msgCannotFindImageFromMetaData(String imgResName) {
     return "Unable to find image resource '" + imgResName + "'";
   }
@@ -151,6 +154,21 @@
     return resultName;
   }
 
+  @Override
+  public ImmutableSet<String> getAccessedPropertyNames() {
+    return relevantPropertyNames;
+  }
+
+  @Override
+  public boolean contentDependsOnProperties() {
+    return false;
+  }
+
+  @Override
+  public boolean contentDependsOnTypes() {
+    return false;
+  }
+
   /**
    * Gets the resource name of the image associated with the specified image
    * bundle method in a form that can be passed to
diff --git a/user/src/com/google/gwt/useragent/rebind/UserAgentGenerator.java b/user/src/com/google/gwt/useragent/rebind/UserAgentGenerator.java
index 8e3ef85..622573d 100644
--- a/user/src/com/google/gwt/useragent/rebind/UserAgentGenerator.java
+++ b/user/src/com/google/gwt/useragent/rebind/UserAgentGenerator.java
@@ -26,6 +26,7 @@
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.NotFoundException;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet;
 import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
 import com.google.gwt.user.rebind.SourceWriter;
 
@@ -37,6 +38,9 @@
 public class UserAgentGenerator extends Generator {
   static final String PROPERTY_USER_AGENT = "user.agent";
 
+  private static ImmutableSet<String> relevantPropertyNames =
+      ImmutableSet.of("user.agent", "user.agent.runtimeWarning");
+
   @Override
   public String generate(TreeLogger logger, GeneratorContext context, String typeName)
       throws UnableToCompleteException {
@@ -102,4 +106,14 @@
     }
     return composerFactory.getCreatedClassName();
   }
+
+  @Override
+  public ImmutableSet<String> getAccessedPropertyNames() {
+    return relevantPropertyNames;
+  }
+
+  @Override
+  public boolean contentDependsOnTypes() {
+    return false;
+  }
 }
diff --git a/user/test/com/google/gwt/core/ext/test/GeneratorTest.java b/user/test/com/google/gwt/core/ext/test/GeneratorTest.java
new file mode 100644
index 0000000..af4b0ab
--- /dev/null
+++ b/user/test/com/google/gwt/core/ext/test/GeneratorTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.ext.test;
+
+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 junit.framework.TestCase;
+
+/**
+ * Tests the Generator base class.
+ */
+public class GeneratorTest extends TestCase {
+
+  private class SimpleGenerator extends Generator {
+
+    @Override
+    public String generate(TreeLogger logger, GeneratorContext context, String typeName)
+        throws UnableToCompleteException {
+      return null;
+    }
+  }
+
+  public void testDefaultPropertyValueStability() {
+    SimpleGenerator simpleGenerator = new SimpleGenerator();
+    // Defaults to the worst case of claiming that generator output content is unstable and will
+    // change as property values change.
+    assertTrue(simpleGenerator.contentDependsOnProperties());
+  }
+
+  public void testDefaultRelevantPropertyNames() {
+    SimpleGenerator simpleGenerator = new SimpleGenerator();
+    // Defaults to the worst case of claiming that generator output content is affected by all
+    // properties (that is the meaning of returning null as opposed to a specific list of property
+    // names).
+    assertNull(simpleGenerator.getAccessedPropertyNames());
+  }
+
+  public void testDefaultTypeStability() {
+    SimpleGenerator simpleGenerator = new SimpleGenerator();
+    // Defaults to the worst case of claiming that generator output content is unstable and will
+    // change as the list of available types changes.
+    assertTrue(simpleGenerator.contentDependsOnTypes());
+  }
+}