Generator Result Caching implementation for ClientBundle

Review at http://gwt-code-reviews.appspot.com/1236801


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9704 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/JRealClassType.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/JRealClassType.java
index ad1f909..e207a5f 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/JRealClassType.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/JRealClassType.java
@@ -19,4 +19,12 @@
  * Type representing a Java class or interface type that a user would declare.
  */
 public interface JRealClassType extends JClassType {
+  
+  /**
+   * EXPERIMENTAL and subject to change. Do not use this in production code.
+   * 
+   * Generate a hash to be used as a signature for comparing versions of the 
+   * structure of a type.  
+   */
+  String getTypeStrongHash();
 }
diff --git a/dev/core/src/com/google/gwt/dev/javac/TypeOracleMediator.java b/dev/core/src/com/google/gwt/dev/javac/TypeOracleMediator.java
index 3df184c..6593aba 100644
--- a/dev/core/src/com/google/gwt/dev/javac/TypeOracleMediator.java
+++ b/dev/core/src/com/google/gwt/dev/javac/TypeOracleMediator.java
@@ -510,6 +510,11 @@
       // Always add implicit modifiers on interfaces.
       resultType.addModifierBits(Shared.MOD_STATIC | Shared.MOD_ABSTRACT);
     }
+    
+    /*
+     * Add a reference to the byteCode
+     */
+    resultType.addByteCode(typeData.byteCode);
 
     return resultType;
   }
diff --git a/dev/core/src/com/google/gwt/dev/javac/rebind/CachedClientDataMap.java b/dev/core/src/com/google/gwt/dev/javac/rebind/CachedClientDataMap.java
new file mode 100644
index 0000000..c7b0d3d
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/javac/rebind/CachedClientDataMap.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2011 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.javac.rebind;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A simple map class for storing cached rebind client data.  It ensures that
+ * all stored data implement Serializable.
+ */
+public class CachedClientDataMap implements Serializable {
+  
+  private final Map<String, Serializable> dataMap;
+  
+  public CachedClientDataMap() {
+    dataMap = new HashMap<String, Serializable>();
+  }
+  
+  public Object get(String key) {
+    return dataMap.get(key);
+  }
+  
+  public void put(String key, Object value) {
+    dataMap.put(key, (Serializable) value);
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/javac/rebind/CachedPropertyInformation.java b/dev/core/src/com/google/gwt/dev/javac/rebind/CachedPropertyInformation.java
index fd20b0f..b18a3d9 100644
--- a/dev/core/src/com/google/gwt/dev/javac/rebind/CachedPropertyInformation.java
+++ b/dev/core/src/com/google/gwt/dev/javac/rebind/CachedPropertyInformation.java
@@ -21,6 +21,7 @@
 import com.google.gwt.core.ext.SelectionProperty;
 import com.google.gwt.core.ext.TreeLogger;
 
+import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
@@ -29,7 +30,7 @@
  * A container for saving lists of deferred-binding and configuration properties
  * to be compared subsequently with a PropertyOracle.
  */
-public class CachedPropertyInformation {
+public class CachedPropertyInformation implements Serializable {
   
   private final List<SelectionProperty> selectionProperties;
   private final List<ConfigurationProperty> configProperties;
diff --git a/dev/core/src/com/google/gwt/dev/javac/rebind/CachedRebindResult.java b/dev/core/src/com/google/gwt/dev/javac/rebind/CachedRebindResult.java
index cd8e3c8..dc74223 100644
--- a/dev/core/src/com/google/gwt/dev/javac/rebind/CachedRebindResult.java
+++ b/dev/core/src/com/google/gwt/dev/javac/rebind/CachedRebindResult.java
@@ -18,6 +18,7 @@
 import com.google.gwt.core.ext.linker.ArtifactSet;
 import com.google.gwt.dev.javac.GeneratedUnit;
 
+import java.io.Serializable;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
@@ -27,21 +28,21 @@
  * cached and presented to subsequent rebind operations, providing the generator
  * information needed to decide whether full or partial re-generation is required.
  */
-public class CachedRebindResult {
+public class CachedRebindResult implements Serializable {
   private final ArtifactSet artifacts;
   private final Map<String, GeneratedUnit> generatedUnitMap;
   private final String returnedTypeName;
   private final long timeGenerated;
-  private final Object clientData;
+  private final CachedClientDataMap clientDataMap;
 
   public CachedRebindResult(String resultTypeName, ArtifactSet artifacts, 
       Map<String, GeneratedUnit> generatedUnitMap, 
-      long timeGenerated, Object clientData) {
+      long timeGenerated, CachedClientDataMap clientDataMap) {
     this.returnedTypeName = resultTypeName;
     this.artifacts = new ArtifactSet(artifacts);
     this.generatedUnitMap = new HashMap<String, GeneratedUnit>(generatedUnitMap);
     this.timeGenerated = timeGenerated;
-    this.clientData = clientData;
+    this.clientDataMap = clientDataMap;
   }
   
   public CachedRebindResult(String resultTypeName, ArtifactSet artifacts, 
@@ -53,8 +54,16 @@
     return artifacts;
   }
   
-  public Object getClientData() {
-    return clientData;
+  public Object getClientData(String key) {
+    if (clientDataMap == null) {
+      return null;
+    } else {
+      return clientDataMap.get(key);
+    }
+  }
+  
+  public CachedClientDataMap getClientDataMap() {
+    return clientDataMap;
   }
   
   public GeneratedUnit getGeneratedUnit(String typeName) {
diff --git a/dev/core/src/com/google/gwt/dev/javac/rebind/RebindCache.java b/dev/core/src/com/google/gwt/dev/javac/rebind/RebindCache.java
index ae59a51..b1e7caa 100644
--- a/dev/core/src/com/google/gwt/dev/javac/rebind/RebindCache.java
+++ b/dev/core/src/com/google/gwt/dev/javac/rebind/RebindCache.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.dev.cfg.Rule;
 
+import java.io.Serializable;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -24,7 +25,7 @@
  * A cache for storing {@link CachedRebindResult} entries.  Entries are keyed
  * by rebind Rule and queryTypeName.
  */
-public class RebindCache {
+public class RebindCache implements Serializable {
  
   private final Map<String, Map<String, CachedRebindResult>> rebindResults;
   
diff --git a/dev/core/src/com/google/gwt/dev/javac/rebind/RebindResult.java b/dev/core/src/com/google/gwt/dev/javac/rebind/RebindResult.java
index 5ec7df0..377c679 100644
--- a/dev/core/src/com/google/gwt/dev/javac/rebind/RebindResult.java
+++ b/dev/core/src/com/google/gwt/dev/javac/rebind/RebindResult.java
@@ -19,13 +19,12 @@
  * A class for returning the result of a rebind operation.
  */
 public class RebindResult {
-
   private final RebindStatus resultStatus;
   private final String returnedTypeName;
-  private final Object clientData;
-  
+  private final CachedClientDataMap clientData;
+ 
   public RebindResult(RebindStatus resultStatus, 
-      String returnedType, Object clientData) {
+      String returnedType, CachedClientDataMap clientData) {
     this.resultStatus = resultStatus;
     this.returnedTypeName = returnedType;
     this.clientData = clientData;
@@ -34,11 +33,19 @@
   public RebindResult(RebindStatus resultStatus, String returnedType) {
     this(resultStatus, returnedType, null);
   }
-  
-  public Object getClientData() {
+ 
+  public Object getClientData(String key) {
+    if (clientData == null) {
+      return null;
+    } else {
+      return clientData.get(key);
+    }
+  }
+ 
+  public CachedClientDataMap getClientDataMap() {
     return clientData;
   }
-  
+ 
   public RebindStatus getResultStatus() {
     return resultStatus;
   }
diff --git a/dev/core/src/com/google/gwt/dev/javac/typemodel/JRealClassType.java b/dev/core/src/com/google/gwt/dev/javac/typemodel/JRealClassType.java
index 9be9b78..66d9ebe 100644
--- a/dev/core/src/com/google/gwt/dev/javac/typemodel/JRealClassType.java
+++ b/dev/core/src/com/google/gwt/dev/javac/typemodel/JRealClassType.java
@@ -19,6 +19,7 @@
 import com.google.gwt.core.ext.typeinfo.JType;
 import com.google.gwt.core.ext.typeinfo.NotFoundException;
 import com.google.gwt.dev.util.StringInterner;
+import com.google.gwt.dev.util.Util;
 import com.google.gwt.dev.util.collect.IdentitySets;
 import com.google.gwt.dev.util.collect.Lists;
 
@@ -51,6 +52,8 @@
   private String lazyQualifiedBinaryName;
 
   private String lazyQualifiedName;
+  
+  private String lazyTypeStrongHash;
 
   private final Members members = new Members(this);
 
@@ -63,6 +66,8 @@
   private final TypeOracle oracle;
 
   private JClassType superclass;
+  
+  private byte[] byteCode;
 
   /**
    * Create a class type that reflects an actual type.
@@ -72,7 +77,6 @@
    * @param enclosingTypeName the fully qualified source name of the enclosing
    *          class or null if a top-level class - setEnclosingType must be
    *          called later with the proper enclosing type if this is non-null
-   * @param isLocalType
    * @param name
    * @param isInterface
    */
@@ -99,6 +103,10 @@
     }
     oracle.addNewType(this);
   }
+  
+  public void addByteCode(byte[] byteCode) {
+    this.byteCode = byteCode;
+  }
 
   @Override
   public void addModifierBits(int bits) {
@@ -268,6 +276,27 @@
   public JClassType getSuperclass() {
     return superclass;
   }
+  
+  /**
+   * EXPERIMENTAL and subject to change. Do not use this in production code.
+   * 
+   * Generate a hash to be used as a signature for comparing versions of the 
+   * structure of a type.
+   * 
+   * TODO(jbrosenberg): Note, this implementation is based on the entire byte 
+   * code for a class, which is probably overkill, since we only need a hash 
+   * based on the type's structure (but not all of its code).  Need to come up
+   * with an efficient way to compute a hash of a type's structure.  For now,
+   * using the raw bytes directly is quick relative to making multiple api calls
+   * into type oracle to determine the type's structure.
+   */ 
+  public String getTypeStrongHash() {
+    if (lazyTypeStrongHash != null) {
+      return lazyTypeStrongHash;
+    }
+    lazyTypeStrongHash = Util.computeStrongName(byteCode);
+    return lazyTypeStrongHash;
+  }
 
   @Override
   public boolean isAbstract() {
diff --git a/dev/core/src/com/google/gwt/dev/shell/StandardRebindOracle.java b/dev/core/src/com/google/gwt/dev/shell/StandardRebindOracle.java
index 502b221..ee7e9f8 100644
--- a/dev/core/src/com/google/gwt/dev/shell/StandardRebindOracle.java
+++ b/dev/core/src/com/google/gwt/dev/shell/StandardRebindOracle.java
@@ -181,7 +181,7 @@
           // use all new results, add a new cache entry
           cachedResult = new CachedRebindResult(newResult.getReturnedTypeName(),
               genCtx.getArtifacts(), genCtx.getGeneratedUnitMap(), 
-              System.currentTimeMillis(), newResult.getClientData());
+              System.currentTimeMillis(), newResult.getClientDataMap());
           rebindCachePut(rule, typeName, cachedResult);
           break;
           
@@ -210,7 +210,7 @@
            */
           cachedResult = new CachedRebindResult(newResult.getReturnedTypeName(),
               genCtx.getArtifacts(), genCtx.getGeneratedUnitMap(), 
-              System.currentTimeMillis(), newResult.getClientData());
+              System.currentTimeMillis(), newResult.getClientDataMap());
           rebindCachePut(rule, typeName, cachedResult);
           break;
       }
@@ -237,7 +237,7 @@
 
   /**
    * Invalidates the given source type name, so the next rebind request will
-   * generate type again.
+   * generate the type again.
    */
   public void invalidateRebind(String sourceTypeName) {
     typeNameBindingMap.remove(sourceTypeName);
diff --git a/user/src/com/google/gwt/resources/ext/ClientBundleRequirements.java b/user/src/com/google/gwt/resources/ext/ClientBundleRequirements.java
index caea394..a54ade8 100644
--- a/user/src/com/google/gwt/resources/ext/ClientBundleRequirements.java
+++ b/user/src/com/google/gwt/resources/ext/ClientBundleRequirements.java
@@ -16,6 +16,9 @@
 package com.google.gwt.resources.ext;
 
 import com.google.gwt.core.ext.BadPropertyValueException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+
+import java.net.URL;
 
 /**
  * Allows ResourceGenerators to indicate how their generated resources may be
@@ -24,6 +27,18 @@
  * method.
  */
 public interface ClientBundleRequirements {
+  
+  /**
+   * Indicates that the ResourcePrototype implementation generated by a
+   * ResourceGenerator is sensitive to the values of the specified
+   * configuration property.
+   * 
+   * @param propertyName the name of the configuration property
+   * @throws BadPropertyValueException
+   */
+  void addConfigurationProperty(String propertyName) 
+      throws BadPropertyValueException;
+  
   /**
    * Indicates that the ResourcePrototype implementation generated by a
    * ResourceGenerator is sensitive to the value of the specified
@@ -32,10 +47,49 @@
    * output. For example, some resource implementations may be sensitive to the
    * <code>user.agent</code> deferred-binding property, and would call this
    * method with the literal string <code>user.agent</code>.
+   * <p>
+   * If a deferred-binding property does not exist, an attempt is made to check
+   * whether a configuration property by the same name exists.
    * 
    * @param propertyName the name of the deferred-binding property
-   * @throws BadPropertyValueException if <code>propertyName</code> is not a
-   *           valid deferred-binding property.
+   * @throws BadPropertyValueException if <code>propertyName</code> is neither a
+   *           valid deferred-binding property nor a valid configuration
+   *           property.
    */
   void addPermutationAxis(String propertyName) throws BadPropertyValueException;
+  
+  /**
+   * Indicates that the ResourcePrototype implementation generated by a
+   * ResourceGenerator is sensitive to a dependent resource.  This method takes
+   * both an unresolved <param>partialPath</param> and a located
+   * <param>resolvedResourceUrl</param>, since the resolved location of a
+   * resource can change dynamically at run time.  So, by calling this method,
+   * the requirement is being declared for both the resolution of the resource's
+   * URL, as well as its content.
+   * <p>
+   * The implementation for resolving a resource url from a partial path is
+   * contained in {@link ResourceGeneratorUtil}, and is based on an ordered set 
+   * of 'locator' implementations, which are tried in sequence.  Example
+   * 'locator' implementations include looking up a resource file by name, which
+   * usually amounts to a freshly generated temporary file (see 
+   * {@link ResourceGeneratorUtil.addNamedFile}), or by using the partial path
+   * as a classpath resource used by a class loader, which can be affected by
+   * classpath shadowing.
+   * <p>
+   * The current resolution for a resource partial path can be checked via
+   * {@link ResourceGeneratorUtil.tryFindResource}.
+   * 
+   * @param partialPath a partial path representing a dependent resource.
+   * @param resolvedResourceUrl a located resolved URL for a dependent resource.
+   */
+  void addResolvedResource(String partialPath, URL resolvedResourceUrl);
+  
+  /**
+   * Indicates that the ResourcePrototype implementation generated by a
+   * ResourceGenerator is sensitive to structural changes to the given type, as
+   * well as any of it's super type hierarchy.
+   * 
+   * @param type a type
+   */
+  void addTypeHierarchy(JClassType type);
 }
diff --git a/user/src/com/google/gwt/resources/ext/ResourceContext.java b/user/src/com/google/gwt/resources/ext/ResourceContext.java
index 7c5f2ff..afde4b2 100644
--- a/user/src/com/google/gwt/resources/ext/ResourceContext.java
+++ b/user/src/com/google/gwt/resources/ext/ResourceContext.java
@@ -133,7 +133,14 @@
    *           {@link ResourceGenerator#prepare} methods.
    */
   String getImplementationSimpleSourceName() throws IllegalStateException;
-
+  
+  /**
+   * Returns a {@link ClientBundleRequirements} object, which can be used to
+   * track deferred-binding and configuration properties that are relevant to a
+   * resource context.
+   */
+  ClientBundleRequirements getRequirements();
+  
   /**
    * Store data in the ResourceContext. ResourceGenerators may reduce the amount
    * of recomputation performed by caching data the ResourceContext. This cache
diff --git a/user/src/com/google/gwt/resources/ext/ResourceGeneratorUtil.java b/user/src/com/google/gwt/resources/ext/ResourceGeneratorUtil.java
index baed540..5e632a5 100644
--- a/user/src/com/google/gwt/resources/ext/ResourceGeneratorUtil.java
+++ b/user/src/com/google/gwt/resources/ext/ResourceGeneratorUtil.java
@@ -16,6 +16,7 @@
 package com.google.gwt.resources.ext;
 
 import com.google.gwt.core.ext.BadPropertyValueException;
+import com.google.gwt.core.ext.GeneratorContext;
 import com.google.gwt.core.ext.PropertyOracle;
 import com.google.gwt.core.ext.SelectionProperty;
 import com.google.gwt.core.ext.TreeLogger;
@@ -61,10 +62,10 @@
    * A locator which will use files published via
    * {@link ResourceGeneratorUtil#addNamedFile(String, File)}.
    */
-  private static class FileLocator implements Locator {
-    public static final FileLocator INSTANCE = new FileLocator();
+  private static class NamedFileLocator implements Locator {
+    public static final NamedFileLocator INSTANCE = new NamedFileLocator();
 
-    private FileLocator() {
+    private NamedFileLocator() {
     }
 
     public URL locate(String resourceName) {
@@ -285,12 +286,7 @@
   public static URL[] findResources(TreeLogger logger, ResourceContext context,
       JMethod method, String[] defaultSuffixes)
       throws UnableToCompleteException {
-    Locator[] locators = {
-        FileLocator.INSTANCE,
-        new ResourceOracleLocator(
-            context.getGeneratorContext().getResourcesOracle()),
-        new ClassLoaderLocator(Thread.currentThread().getContextClassLoader())};
-
+    Locator[] locators = getDefaultLocators(context.getGeneratorContext());
     URL[] toReturn = findResources(logger, locators, context, method,
         defaultSuffixes);
     return toReturn;
@@ -370,8 +366,47 @@
 
     return currentMethod;
   }
+  
+  /**
+   * Try to find a resource with the given resourceName.  It will use the default
+   * search order to locate the resource as is used by {@link #findResources}.
+   * 
+   * @param logger
+   * @param context
+   * @param resourceName
+   * @return a URL for the resource, if found
+   */
+  public static URL tryFindResource(TreeLogger logger,
+      GeneratorContext genContext, ResourceContext resourceContext,
+      String resourceName) {
+    String locale = getLocale(logger, genContext);
+    Locator[] locators = getDefaultLocators(genContext);
+    for (Locator locator : locators) {
+      URL toReturn = tryFindResource(locator, resourceContext, resourceName,
+          locale);
+      if (toReturn != null) {
+        return toReturn;
+      }
+    }
+    return null;
+  }
 
   /**
+   * Add the type dependency requirements for a method, to the context.
+   * 
+   * @param context
+   * @param method
+   */
+  private static void addTypeRequirementsForMethod(ResourceContext context, 
+      JMethod method) {
+    ClientBundleRequirements reqs = context.getRequirements();
+    if (reqs != null) {
+      reqs.addTypeHierarchy(method.getEnclosingType());
+      reqs.addTypeHierarchy((JClassType) method.getReturnType());
+    }
+  }
+  
+  /**
    * We want to warn the user about any annotations from ImageBundle or the old
    * incubator code.
    */
@@ -387,7 +422,7 @@
       }
     }
   }
-
+  
   /**
    * Main implementation of findResources.
    */
@@ -396,14 +431,7 @@
       throws UnableToCompleteException {
     logger = logger.branch(TreeLogger.DEBUG, "Finding resources");
 
-    String locale;
-    try {
-      PropertyOracle oracle = context.getGeneratorContext().getPropertyOracle();
-      SelectionProperty prop = oracle.getSelectionProperty(logger, "locale");
-      locale = prop.getCurrentValue();
-    } catch (BadPropertyValueException e) {
-      locale = null;
-    }
+    String locale = getLocale(logger, context.getGeneratorContext());
 
     checkForDeprecatedAnnotations(logger, method);
 
@@ -416,13 +444,13 @@
         for (String extension : defaultSuffixes) {
           logger.log(TreeLogger.SPAM, "Trying default extension " + extension);
           for (Locator locator : locators) {
-            URL resourceUrl = tryFindResource(locator,
-                getPathRelativeToPackage(
-                    method.getEnclosingType().getPackage(), method.getName()
-                        + extension), locale);
+            URL resourceUrl = tryFindResource(locator, context,
+                getPathRelativeToPackage(method.getEnclosingType().getPackage(), 
+                    method.getName() + extension), locale);
 
+            // Take the first match
             if (resourceUrl != null) {
-              // Take the first match
+              addTypeRequirementsForMethod(context, method);
               return new URL[] {resourceUrl};
             }
           }
@@ -446,15 +474,16 @@
         URL resourceURL = null;
 
         for (Locator locator : locators) {
-          resourceURL = tryFindResource(locator, getPathRelativeToPackage(
-              method.getEnclosingType().getPackage(), resource), locale);
+          resourceURL = tryFindResource(locator, context,
+              getPathRelativeToPackage(method.getEnclosingType().getPackage(),
+                  resource), locale);
 
           /*
            * If we didn't find the resource relative to the package, assume it
            * is absolute.
            */
           if (resourceURL == null) {
-            resourceURL = tryFindResource(locator, resource, locale);
+            resourceURL = tryFindResource(locator, context, resource, locale);
           }
 
           // If we have found a resource, take the first match
@@ -478,9 +507,44 @@
       throw new UnableToCompleteException();
     }
 
+    addTypeRequirementsForMethod(context, method);
     return toReturn;
   }
-
+ 
+  /**
+   * Get default list of resource Locators, in the default order.
+   * 
+   * @param context
+   * @return an ordered array of Locator[]
+   */
+  private static Locator[] getDefaultLocators(GeneratorContext genContext) {
+    Locator[] locators = {
+      NamedFileLocator.INSTANCE,
+      new ResourceOracleLocator(genContext.getResourcesOracle()),
+      new ClassLoaderLocator(Thread.currentThread().getContextClassLoader())};
+    
+    return locators;
+  }
+ 
+  /**
+   * Get the current locale string.
+   * 
+   * @param logger
+   * @param context
+   * @return the current locale
+   */
+  private static String getLocale(TreeLogger logger, GeneratorContext genContext) {
+    String locale;
+    try {
+      PropertyOracle oracle = genContext.getPropertyOracle();
+      SelectionProperty prop = oracle.getSelectionProperty(logger, "locale");
+      locale = prop.getCurrentValue();
+    } catch (BadPropertyValueException e) {
+      locale = null;
+    }
+    return locale;
+  }
+ 
   /**
    * Converts a package relative path into an absolute path.
    *
@@ -531,6 +595,31 @@
 
     return toReturn;
   }
+ 
+  /**
+   * Performs the locale lookup function for a given resource name.  Will also
+   * add the located resource to the requirements object for the context.
+   *
+   * @param locator the Locator to use to load the resources
+   * @param context the ResourceContext
+   * @param resourceName the string name of the desired resource
+   * @param locale the locale of the current rebind permutation
+   * @return a URL by which the resource can be loaded, <code>null</code> if one
+   *         cannot be found
+   */
+  private static URL tryFindResource(Locator locator, ResourceContext context,
+      String resourceName, String locale) {
+    
+    URL toReturn = tryFindResource(locator, resourceName, locale);
+    if (toReturn != null && context != null) {
+      ClientBundleRequirements reqs = context.getRequirements();
+      if (reqs != null) {
+        reqs.addResolvedResource(resourceName, toReturn);
+      }
+    }
+    
+    return toReturn;
+  }
 
   /**
    * Utility class.
diff --git a/user/src/com/google/gwt/resources/ext/SupportsGeneratorResultCaching.java b/user/src/com/google/gwt/resources/ext/SupportsGeneratorResultCaching.java
new file mode 100644
index 0000000..430dfe4
--- /dev/null
+++ b/user/src/com/google/gwt/resources/ext/SupportsGeneratorResultCaching.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2011 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.resources.ext;
+
+/**
+ * A marker interface that a {@link ResourceGenerator} can implement to indicate 
+ * it supports generator result caching.
+ */
+public interface SupportsGeneratorResultCaching {
+}
diff --git a/user/src/com/google/gwt/resources/rebind/context/AbstractClientBundleGenerator.java b/user/src/com/google/gwt/resources/rebind/context/AbstractClientBundleGenerator.java
index 94cc9bf..8be3874 100644
--- a/user/src/com/google/gwt/resources/rebind/context/AbstractClientBundleGenerator.java
+++ b/user/src/com/google/gwt/resources/rebind/context/AbstractClientBundleGenerator.java
@@ -17,8 +17,9 @@
 
 import com.google.gwt.core.client.GWT;
 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.GeneratorContextExt;
+import com.google.gwt.core.ext.GeneratorExt;
 import com.google.gwt.core.ext.PropertyOracle;
 import com.google.gwt.core.ext.SelectionProperty;
 import com.google.gwt.core.ext.TreeLogger;
@@ -27,9 +28,15 @@
 import com.google.gwt.core.ext.typeinfo.JGenericType;
 import com.google.gwt.core.ext.typeinfo.JMethod;
 import com.google.gwt.core.ext.typeinfo.JParameterizedType;
+import com.google.gwt.core.ext.typeinfo.JRealClassType;
 import com.google.gwt.core.ext.typeinfo.JType;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.dev.generator.NameFactory;
+import com.google.gwt.dev.javac.rebind.CachedClientDataMap;
+import com.google.gwt.dev.javac.rebind.CachedPropertyInformation;
+import com.google.gwt.dev.javac.rebind.CachedRebindResult;
+import com.google.gwt.dev.javac.rebind.RebindResult;
+import com.google.gwt.dev.javac.rebind.RebindStatus;
 import com.google.gwt.dev.util.Util;
 import com.google.gwt.resources.client.ClientBundle;
 import com.google.gwt.resources.client.ClientBundleWithLookup;
@@ -39,12 +46,19 @@
 import com.google.gwt.resources.ext.ResourceContext;
 import com.google.gwt.resources.ext.ResourceGenerator;
 import com.google.gwt.resources.ext.ResourceGeneratorType;
+import com.google.gwt.resources.ext.ResourceGeneratorUtil;
+import com.google.gwt.resources.ext.SupportsGeneratorResultCaching;
 import com.google.gwt.resources.rg.BundleResourceGenerator;
 import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
 import com.google.gwt.user.rebind.SourceWriter;
 import com.google.gwt.user.rebind.StringSourceWriter;
 
+import java.io.File;
+import java.io.IOException;
 import java.io.PrintWriter;
+import java.net.JarURLConnection;
+import java.net.URISyntaxException;
+import java.net.URL;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -52,6 +66,7 @@
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 
 /**
@@ -97,7 +112,13 @@
  * of an instance of the ClientBundle type so that resources can refer to one
  * another by simply emitting a call to <code>resource()</code>.
  */
-public abstract class AbstractClientBundleGenerator extends Generator {
+public abstract class AbstractClientBundleGenerator extends GeneratorExt {
+
+  private static final String FILE_PROTOCOL = "file";
+  private static final String JAR_PROTOCOL = "jar";
+  private static final String CACHED_PROPERTY_INFORMATION = "cpi";
+  private static final String CACHED_RESOURCE_INFORMATION = "cri";
+  private static final String CACHED_TYPE_INFORMATION = "cti";
 
   /**
    * An implementation of ClientBundleFields.
@@ -176,31 +197,145 @@
   }
 
   private static class RequirementsImpl implements ClientBundleRequirements {
-    private final Set<String> axes = new HashSet<String>();
-    private final PropertyOracle oracle;
+    private final Set<String> axes;
+    private boolean axesLocked = false;
+    private final boolean canBeCacheable;
+    private final Set<String> configProps;
+    private final PropertyOracle propertyOracle;
+    private final Map<String, URL> resolvedResources;
+    private final Set<JClassType> types;
 
-    public RequirementsImpl(PropertyOracle oracle) {
-      this.oracle = oracle;
+    public RequirementsImpl(PropertyOracle propertyOracle, boolean canBeCacheable) {
+      this.propertyOracle = propertyOracle;
+      this.canBeCacheable = canBeCacheable;
+      
+      // always need to track permuationAxes
+      axes = new HashSet<String>();
+      
+      // only need to track these if generator caching is a possibility
+      if (canBeCacheable) {
+        configProps = new HashSet<String>();
+        types = new HashSet<JClassType>();
+        resolvedResources = new HashMap<String, URL>();
+      } else {
+        configProps = null;
+        types = null;
+        resolvedResources = null;
+      }
+    }
+
+    public void addConfigurationProperty(String propertyName) 
+        throws BadPropertyValueException {
+      
+      if (!canBeCacheable) {
+        return;
+      }
+      // Ensure the property exists
+      propertyOracle.getConfigurationProperty(propertyName).getValues();
+      configProps.add(propertyName);
     }
 
     public void addPermutationAxis(String propertyName)
         throws BadPropertyValueException {
+
+      if (axes.contains(propertyName)) {
+        return;
+      }
+
+      // Ensure adding of permutationAxes has not been locked
+      if (axesLocked) {
+        throw new IllegalStateException(
+            "addPermutationAxis failed, axes have been locked");
+      }
+
       // Ensure the property exists and add a permutation axis if the
       // property is a deferred binding property.
       try {
-        oracle.getSelectionProperty(TreeLogger.NULL, propertyName).getCurrentValue();
+        propertyOracle.getSelectionProperty(
+            TreeLogger.NULL, propertyName).getCurrentValue();
         axes.add(propertyName);
       } catch (BadPropertyValueException e) {
-        oracle.getConfigurationProperty(propertyName).getValues();
+        addConfigurationProperty(propertyName);
       }
     }
+
+    public void addResolvedResource(String partialPath, URL resolvedResourceUrl) {
+      if (!canBeCacheable) {
+        return;
+      }
+      resolvedResources.put(partialPath, resolvedResourceUrl);
+    }
+
+    public void addTypeHierarchy(JClassType type) {
+      if (!canBeCacheable) {
+        return;
+      }
+      if (!types.add(type)) {
+        return;
+      }
+      Set<? extends JClassType> superTypes = type.getFlattenedSupertypeHierarchy();
+      types.addAll(superTypes);
+    }
+
+    public Collection<String> getConfigurationPropertyNames() {
+      if (!canBeCacheable) {
+        return null;
+      }
+      return configProps;
+    }
+
+    public Collection<String> getPermutationAxes() {
+      return axes;
+    }
+
+    public Map<String, URL> getResolvedResources() {
+      if (!canBeCacheable) {
+        return null;
+      }
+      return resolvedResources;
+    }
+
+    public Map<String, String> getTypeSignatures() {
+      if (!canBeCacheable) {
+        return null;
+      }
+      Map<String, String> typeSignatures = new HashMap<String, String>();
+      for (JClassType type : types) {
+        String typeName = type.getQualifiedSourceName();
+        if (type instanceof JRealClassType) {
+          JRealClassType sourceRealType = (JRealClassType) type;
+          String typeSignature = sourceRealType.getTypeStrongHash();
+          typeSignatures.put(typeName, typeSignature);
+        } else {
+          typeSignatures.put(typeName, "");
+        }
+      }
+      return typeSignatures;
+    }
+
+    /*
+     * No further permutation axes can be added after this is called
+     */
+    public void lockPermutationAxes() {
+      axesLocked = true;
+    }
   }
 
   @Override
-  public final String generate(TreeLogger logger,
-      GeneratorContext generatorContext, String typeName)
+  public RebindResult generateIncrementally(TreeLogger logger,
+      GeneratorContextExt generatorContext, String typeName)
       throws UnableToCompleteException {
 
+    /*
+     * Do a series of checks to see if we can use a previously cached result,
+     * and if so, we can skip further execution and return immediately.
+     */
+    if (checkPropertyCacheability(logger, generatorContext)
+        && checkSourceTypeCacheability(generatorContext)
+        && checkDependentResourceCacheability(logger, generatorContext, null)) {
+      return new RebindResult(RebindStatus.USE_ALL_CACHED, typeName);
+    }
+      
     // The TypeOracle knows about all types in the type system
     TypeOracle typeOracle = generatorContext.getTypeOracle();
 
@@ -227,16 +362,30 @@
         logger, typeOracle, sourceType);
 
     /*
+     * Check the resource generators associated with our taskList, and see if
+     * they all support generator result caching.
+     */
+    boolean canBeCacheable = checkResourceGeneratorCacheability(
+        generatorContext, taskList);
+
+    /*
      * Additional objects that hold state during the generation process.
      */
     AbstractResourceContext resourceContext = createResourceContext(logger,
         generatorContext, sourceType);
     FieldsImpl fields = new FieldsImpl();
     RequirementsImpl requirements = new RequirementsImpl(
-        generatorContext.getPropertyOracle());
+        generatorContext.getPropertyOracle(), canBeCacheable);
+    resourceContext.setRequirements(requirements);
     doAddFieldsAndRequirements(logger, generatorContext, fields, requirements);
 
     /*
+     * Add our source type (and it's supertypes) as a requirement.  Note further
+     * types may be added during the processing of the taskList.
+     */
+    requirements.addTypeHierarchy(sourceType);
+
+    /*
      * Initialize the ResourceGenerators and prepare them for subsequent code
      * generation.
      */
@@ -309,8 +458,32 @@
     finish(logger, resourceContext, generators.keySet());
     doFinish(logger);
 
-    // Return the name of the concrete class
-    return createdClassName;
+    if (canBeCacheable) {
+      // remember the current set of required properties, and their values
+      CachedPropertyInformation cpi = new CachedPropertyInformation(logger, 
+          generatorContext.getPropertyOracle(), 
+          requirements.getPermutationAxes(), 
+          requirements.getConfigurationPropertyNames());
+
+      // remember the type signatures for required source types
+      Map<String, String> cti = requirements.getTypeSignatures();
+
+      // remember the required resources
+      Map<String, URL> cri = requirements.getResolvedResources();
+
+      // create data map to be returned the next time the generator is run
+      CachedClientDataMap data = new CachedClientDataMap();
+      data.put(CACHED_PROPERTY_INFORMATION, cpi);
+      data.put(CACHED_RESOURCE_INFORMATION, cri);
+      data.put(CACHED_TYPE_INFORMATION, cti);
+
+      // Return a new cacheable result
+      return new RebindResult(RebindStatus.USE_ALL_NEW, createdClassName, data);
+    } else {
+      // If we can't be cacheable, don't return a cacheable result
+      return new RebindResult(RebindStatus.USE_ALL_NEW_WITH_NO_CACHING,
+          createdClassName);
+    }
   }
 
   /**
@@ -365,6 +538,200 @@
   }
 
   /**
+   * Check that the map of cached type signatures matches those from the current
+   * typeOracle.
+   */
+  private boolean checkCachedTypeSignatures(
+      GeneratorContextExt generatorContext, Map<String, String> typeSignatures) {
+    
+    TypeOracle oracle = generatorContext.getTypeOracle();
+    
+    for (String sourceTypeName : typeSignatures.keySet()) {
+      JClassType sourceType = oracle.findType(sourceTypeName);
+      if (sourceType == null || !(sourceType instanceof JRealClassType)) {
+        return false;
+      }
+      JRealClassType sourceRealType = (JRealClassType) sourceType;
+      
+      String signature = sourceRealType.getTypeStrongHash();
+      if (!signature.equals(typeSignatures.get(sourceTypeName))) {
+        return false;
+      }
+    }
+    
+    return true;
+  }
+
+  /**
+   * Check dependent resources for cacheability.
+   */
+  private boolean checkDependentResourceCacheability(TreeLogger logger,
+      GeneratorContextExt genContext, ResourceContext resourceContext) {
+
+    CachedRebindResult lastRebindResult = genContext.getCachedGeneratorResult();
+
+    if (lastRebindResult == null 
+        || !genContext.isGeneratorResultCachingEnabled()) {
+      return false;
+    }
+    long lastTimeGenerated = lastRebindResult.getTimeGenerated();
+
+    // check that resource URL's haven't moved, and haven't been modified
+    @SuppressWarnings("unchecked")
+    Map<String, URL> cachedResolvedResources = (Map<String, URL>)
+      lastRebindResult.getClientData(CACHED_RESOURCE_INFORMATION);
+    
+    if (cachedResolvedResources == null) {
+      return false;
+    }
+    
+    for (Entry<String, URL> entry : cachedResolvedResources.entrySet()) {
+      String resourceName = entry.getKey();
+      URL resolvedUrl = entry.getValue();
+      URL currentUrl = ResourceGeneratorUtil.tryFindResource(logger, genContext,
+          resourceContext, resourceName);
+      if (currentUrl == null || resolvedUrl == null
+          || !resolvedUrl.toExternalForm().equals(currentUrl.toExternalForm())) {
+        return false;
+      }
+      
+      if (!checkDependentResourceUpToDate(lastTimeGenerated, resolvedUrl)) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+  /**
+   * Checks whether a dependent resource referenced by the provided URL is
+   * up to date, based on the provided referenceTime.
+   */
+  private boolean checkDependentResourceUpToDate(long referenceTime, URL url) {
+    try {
+      if (url.getProtocol().equals(JAR_PROTOCOL)) {
+        /*
+         * If this resource is contained inside a jar file, such as can
+         * happen if it's bundled in a 3rd-party library, we use the jar 
+         * file itself to test whether it's up to date.  We don't want
+         * to call JarURLConnection.getLastModified(), as this is much
+         * slower than using the jar File resource directly.
+         */
+        JarURLConnection jarConn = (JarURLConnection) url.openConnection();
+        url = jarConn.getJarFileURL();
+      }
+
+      long lastModified;
+      if (url.getProtocol().equals(FILE_PROTOCOL)) {
+        /*
+         * Need to handle possibly wonky syntax in a file URL resource.
+         * Modeled after suggestion in this blog entry:
+         * http://weblogs.java.net/blog/2007/04/25/how-convert-javaneturl-javaiofile
+         */
+        File file;
+        try {
+          file = new File(url.toURI());
+        } catch (URISyntaxException uriEx) {
+          file = new File(url.getPath());
+        }
+        lastModified = file.lastModified();
+      } else {
+        /*
+         * Don't attempt to handle any other protocol
+         */
+        return false;
+      }
+      if (lastModified == 0L ||
+          lastModified > referenceTime) {
+        return false;
+      }
+    } catch (IOException ioEx) {
+      return false;
+    } catch (RuntimeException ruEx) {
+      /*
+       * Return false for any RuntimeException (e.g. a SecurityException), and 
+       * allow the cacheability check to fail, since we don't want to change the 
+       * behavior that would be encountered if no cache is available.  Force 
+       * resource generators to utilize their own exception handling.
+       */
+      return false;
+    }
+    return true;
+  }
+
+  /*
+   * Check properties for cacheability
+   */
+  private boolean checkPropertyCacheability(TreeLogger logger,
+      GeneratorContextExt genContext) {
+
+    CachedRebindResult lastRebindResult = genContext.getCachedGeneratorResult();
+
+    if (lastRebindResult == null 
+        || !genContext.isGeneratorResultCachingEnabled()) {
+      return false;
+    }
+
+    /*
+     * Do a check of deferred-binding and configuration properties, comparing
+     * the cached values saved previously with the current properties.
+     */
+    CachedPropertyInformation cpi = (CachedPropertyInformation) 
+        lastRebindResult.getClientData(CACHED_PROPERTY_INFORMATION);
+      
+    return cpi != null && cpi.checkPropertiesWithPropertyOracle(logger, 
+        genContext.getPropertyOracle());
+  }
+  
+  /**
+   * Check cacheability for resource generators in taskList.
+   */
+  private boolean checkResourceGeneratorCacheability(
+      GeneratorContextExt genContext,
+      Map<Class<? extends ResourceGenerator>, List<JMethod>> taskList) {
+    
+    if (!genContext.isGeneratorResultCachingEnabled()) {
+      return false;
+    }
+
+    /*
+     * Loop through each of our ResouceGenerator classes, and check those that
+     * implement the SupportsGeneratorResultCaching interface.
+     */
+    for (Class<? extends ResourceGenerator> rgClass : taskList.keySet()) {
+      if (!SupportsGeneratorResultCaching.class.isAssignableFrom(rgClass)) {
+        return false;
+      }
+    } 
+    return true;
+  }
+
+  /*
+   * Check source types for cacheability
+   */
+  private boolean checkSourceTypeCacheability(GeneratorContextExt genContext) {
+
+    CachedRebindResult lastRebindResult = genContext.getCachedGeneratorResult();
+
+    if (lastRebindResult == null 
+        || !genContext.isGeneratorResultCachingEnabled()) {
+      return false;
+    }
+
+    /*
+     * Do a check over the cached list of types that were previously flagged as
+     * required.  Check that none of these types has undergone a version change
+     * since the previous cached result was generated.
+     */
+    @SuppressWarnings("unchecked")
+    Map<String, String> cachedTypeSignatures = (Map<String, String>)
+      lastRebindResult.getClientData(CACHED_TYPE_INFORMATION);
+
+    return cachedTypeSignatures != null 
+      && checkCachedTypeSignatures(genContext, cachedTypeSignatures);
+  }
+
+  /**
    * Create fields and assignments for a single ResourceGenerator.
    */
   private boolean createFieldsAndAssignments(TreeLogger logger,
@@ -567,19 +934,24 @@
   /**
    * Given a user-defined type name, determine the type name for the generated
    * class based on accumulated requirements.
-   * 
-   * @throws UnableToCompleteException if an error occurs.
    */
   private String generateSimpleSourceName(TreeLogger logger,
       ResourceContext context, RequirementsImpl requirements) {
     StringBuilder toReturn = new StringBuilder(
         context.getClientBundleType().getName().replaceAll("[.$]", "_"));
-    Set<String> permutationAxes = new HashSet<String>(requirements.axes);
-    permutationAxes.add("locale");
 
     try {
+      // always add the locale property
+      requirements.addPermutationAxis("locale");
+    } catch (BadPropertyValueException e) {
+    }
+
+    try {
+      // no further additions to the permutation axes allowed after this point
+      requirements.lockPermutationAxes();
+      
       PropertyOracle oracle = context.getGeneratorContext().getPropertyOracle();
-      for (String property : permutationAxes) {
+      for (String property : requirements.getPermutationAxes()) {
         SelectionProperty prop = oracle.getSelectionProperty(logger, property);
         String value = prop.getCurrentValue();
         toReturn.append("_" + value);
diff --git a/user/src/com/google/gwt/resources/rebind/context/AbstractResourceContext.java b/user/src/com/google/gwt/resources/rebind/context/AbstractResourceContext.java
index 477c0ff..be8eea5 100644
--- a/user/src/com/google/gwt/resources/rebind/context/AbstractResourceContext.java
+++ b/user/src/com/google/gwt/resources/rebind/context/AbstractResourceContext.java
@@ -20,6 +20,7 @@
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.dev.util.Util;
+import com.google.gwt.resources.ext.ClientBundleRequirements;
 import com.google.gwt.resources.ext.ResourceContext;
 import com.google.gwt.resources.ext.ResourceGenerator;
 import com.google.gwt.resources.ext.ResourceGeneratorUtil;
@@ -46,8 +47,9 @@
 
   private final TreeLogger logger;
   private final ClientBundleContext clientBundleCtx;
-  private String currentResourceGeneratorType;
   private final GeneratorContext context;
+  private String currentResourceGeneratorType;
+  private ClientBundleRequirements requirements = null;
   private final JClassType resourceBundleType;
   private String simpleSourceName;
 
@@ -100,11 +102,19 @@
     }
     return simpleSourceName;
   }
+  
+  public ClientBundleRequirements getRequirements() {
+    return requirements;
+  }
 
   public <T> boolean putCachedData(String key, T value) {
     key = currentResourceGeneratorType + ":" + key;
     return value != clientBundleCtx.putCachedData(key, value);
   }
+  
+  public void setRequirements(ClientBundleRequirements requirements) {
+    this.requirements = requirements;
+  }
 
   protected GeneratorContext getContext() {
     return context;
diff --git a/user/src/com/google/gwt/resources/rebind/context/StaticResourceContext.java b/user/src/com/google/gwt/resources/rebind/context/StaticResourceContext.java
index 5ff0c31..1788c72 100644
--- a/user/src/com/google/gwt/resources/rebind/context/StaticResourceContext.java
+++ b/user/src/com/google/gwt/resources/rebind/context/StaticResourceContext.java
@@ -49,6 +49,7 @@
     String enableRenaming = null;
     try {
       ConfigurationProperty prop = propertyOracle.getConfigurationProperty(ENABLE_RENAMING);
+      getRequirements().addConfigurationProperty(ENABLE_RENAMING);
       enableRenaming = prop.getValues().get(0);
     } catch (BadPropertyValueException e) {
       logger.log(TreeLogger.ERROR, "Bad value for " + ENABLE_RENAMING, e);
diff --git a/user/src/com/google/gwt/resources/rg/BundleResourceGenerator.java b/user/src/com/google/gwt/resources/rg/BundleResourceGenerator.java
index 3ccc0fa..a7ae3e5 100644
--- a/user/src/com/google/gwt/resources/rg/BundleResourceGenerator.java
+++ b/user/src/com/google/gwt/resources/rg/BundleResourceGenerator.java
@@ -22,11 +22,13 @@
 import com.google.gwt.core.ext.typeinfo.JMethod;
 import com.google.gwt.resources.ext.AbstractResourceGenerator;
 import com.google.gwt.resources.ext.ResourceContext;
+import com.google.gwt.resources.ext.SupportsGeneratorResultCaching;
 
 /**
  * This is a special case of ResourceGenerator that handles nested bundles.
  */
-public final class BundleResourceGenerator extends AbstractResourceGenerator {
+public final class BundleResourceGenerator extends AbstractResourceGenerator 
+    implements SupportsGeneratorResultCaching {
 
   @Override
   public String createAssignment(TreeLogger logger, ResourceContext context,
diff --git a/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java b/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java
index f1cca0f..0075f45 100644
--- a/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java
+++ b/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java
@@ -68,6 +68,7 @@
 import com.google.gwt.resources.ext.ClientBundleRequirements;
 import com.google.gwt.resources.ext.ResourceContext;
 import com.google.gwt.resources.ext.ResourceGeneratorUtil;
+import com.google.gwt.resources.ext.SupportsGeneratorResultCaching;
 import com.google.gwt.user.rebind.SourceWriter;
 import com.google.gwt.user.rebind.StringSourceWriter;
 
@@ -88,7 +89,8 @@
 /**
  * Provides implementations of CSSResources.
  */
-public final class CssResourceGenerator extends AbstractResourceGenerator {
+public final class CssResourceGenerator extends AbstractResourceGenerator 
+    implements SupportsGeneratorResultCaching {
 
   @SuppressWarnings("serial")
   static class JClassOrderComparator implements Comparator<JClassType>,
@@ -125,8 +127,11 @@
   private static final String KEY_CLASS_PREFIX = "prefix";
   private static final String KEY_CLASS_COUNTER = "counter";
   private static final String KEY_HAS_CACHED_DATA = "hasCachedData";
-  private static final String KEY_SHARED_METHODS = "sharedMethods";
+  private static final String KEY_MERGE_ENABLED = "CssResource.mergeEnabled";
+  private static final String KEY_OBFUSCATION_PREFIX = "CssResource.obfuscationPrefix";
   private static final String KEY_RESERVED_PREFIXES = "CssResource.reservedClassPrefixes";
+  private static final String KEY_SHARED_METHODS = "sharedMethods";
+  private static final String KEY_STYLE = "CssResource.style";
 
   /**
    * This character must not appear in {@link #BASE32_CHARS}.
@@ -451,6 +456,9 @@
             '$', '.'));
         assert importType != null : "TypeOracle does not have type "
             + clazz.getName();
+        
+        // add this import type as a requirement for this generator
+        context.getRequirements().addTypeHierarchy(importType);
 
         String prefix = getImportPrefix(importType);
 
@@ -499,17 +507,27 @@
       throws UnableToCompleteException {
     String classPrefix;
     try {
-      PropertyOracle propertyOracle = context.getGeneratorContext().getPropertyOracle();
-      ConfigurationProperty styleProp = propertyOracle.getConfigurationProperty("CssResource.style");
+      PropertyOracle propertyOracle = 
+        context.getGeneratorContext().getPropertyOracle();
+      ConfigurationProperty styleProp = 
+        propertyOracle.getConfigurationProperty(KEY_STYLE);
       String style = styleProp.getValues().get(0);
       prettyOutput = style.equals("pretty");
 
-      ConfigurationProperty mergeProp = propertyOracle.getConfigurationProperty("CssResource.mergeEnabled");
+      ConfigurationProperty mergeProp = 
+        propertyOracle.getConfigurationProperty(KEY_MERGE_ENABLED);
       String merge = mergeProp.getValues().get(0);
       enableMerge = merge.equals("true");
 
-      ConfigurationProperty classPrefixProp = propertyOracle.getConfigurationProperty("CssResource.obfuscationPrefix");
+      ConfigurationProperty classPrefixProp = 
+        propertyOracle.getConfigurationProperty(KEY_OBFUSCATION_PREFIX);
       classPrefix = classPrefixProp.getValues().get(0);
+      
+      // add these configuration properties to our requirements
+      ClientBundleRequirements requirements = context.getRequirements();
+      requirements.addConfigurationProperty(KEY_STYLE);
+      requirements.addConfigurationProperty(KEY_MERGE_ENABLED);
+      requirements.addConfigurationProperty(KEY_OBFUSCATION_PREFIX);
     } catch (BadPropertyValueException e) {
       logger.log(TreeLogger.ERROR, "Unable to query module property", e);
       throw new UnableToCompleteException();
@@ -553,7 +571,7 @@
     // Create the AST and do a quick scan for requirements
     CssStylesheet sheet = GenerateCssAst.exec(logger, resources);
     stylesheetMap.put(method, sheet);
-    (new RequirementsCollector(logger, requirements)).accept(sheet);
+    (new RequirementsCollector(logger, context.getRequirements())).accept(sheet);
   }
 
   /**
@@ -773,8 +791,12 @@
       ConfigurationProperty prop;
       TreeSet<String> reservedPrefixes = new TreeSet<String>();
       try {
-        prop = context.getGeneratorContext().getPropertyOracle().getConfigurationProperty(
-            KEY_RESERVED_PREFIXES);
+        prop = context.getGeneratorContext().getPropertyOracle()
+            .getConfigurationProperty(KEY_RESERVED_PREFIXES);
+
+        // add this configuration property to our requirements
+        context.getRequirements().addConfigurationProperty(KEY_RESERVED_PREFIXES);
+
         for (String value : prop.getValues()) {
           value = value.trim();
           if (value.length() == 0) {
diff --git a/user/src/com/google/gwt/resources/rg/DataResourceGenerator.java b/user/src/com/google/gwt/resources/rg/DataResourceGenerator.java
index 8fda74f..416ca28 100644
--- a/user/src/com/google/gwt/resources/rg/DataResourceGenerator.java
+++ b/user/src/com/google/gwt/resources/rg/DataResourceGenerator.java
@@ -24,6 +24,7 @@
 import com.google.gwt.resources.ext.AbstractResourceGenerator;
 import com.google.gwt.resources.ext.ResourceContext;
 import com.google.gwt.resources.ext.ResourceGeneratorUtil;
+import com.google.gwt.resources.ext.SupportsGeneratorResultCaching;
 import com.google.gwt.user.rebind.SourceWriter;
 import com.google.gwt.user.rebind.StringSourceWriter;
 
@@ -32,7 +33,8 @@
 /**
  * Provides implementations of DataResource.
  */
-public final class DataResourceGenerator extends AbstractResourceGenerator {
+public final class DataResourceGenerator extends AbstractResourceGenerator
+    implements SupportsGeneratorResultCaching {
   @Override
   public String createAssignment(TreeLogger logger, ResourceContext context,
       JMethod method) throws UnableToCompleteException {
diff --git a/user/src/com/google/gwt/resources/rg/ExternalTextResourceGenerator.java b/user/src/com/google/gwt/resources/rg/ExternalTextResourceGenerator.java
index 928f989..04bc77b 100644
--- a/user/src/com/google/gwt/resources/rg/ExternalTextResourceGenerator.java
+++ b/user/src/com/google/gwt/resources/rg/ExternalTextResourceGenerator.java
@@ -32,6 +32,7 @@
 import com.google.gwt.resources.ext.ClientBundleRequirements;
 import com.google.gwt.resources.ext.ResourceContext;
 import com.google.gwt.resources.ext.ResourceGeneratorUtil;
+import com.google.gwt.resources.ext.SupportsGeneratorResultCaching;
 import com.google.gwt.user.rebind.SourceWriter;
 import com.google.gwt.user.rebind.StringSourceWriter;
 
@@ -43,7 +44,7 @@
  * Adds {@link ExternalTextResourcePrototype} objects to the bundle.
  */
 public final class ExternalTextResourceGenerator extends
-    AbstractResourceGenerator {
+    AbstractResourceGenerator implements SupportsGeneratorResultCaching {
   /**
    * The name of a deferred binding property that determines whether or not this
    * generator will use JSONP to fetch the files.
@@ -177,6 +178,9 @@
       ConfigurationProperty prop = context.getGeneratorContext()
         .getPropertyOracle().getConfigurationProperty(USE_JSONP);
       useJsonpProp = prop.getValues().get(0);
+      
+      // add this configuration property to our requirements
+      context.getRequirements().addConfigurationProperty(USE_JSONP);
     } catch (BadPropertyValueException e) {
       logger.log(TreeLogger.ERROR, "Bad value for " + USE_JSONP, e);
       return false;
diff --git a/user/src/com/google/gwt/resources/rg/GwtCreateResourceGenerator.java b/user/src/com/google/gwt/resources/rg/GwtCreateResourceGenerator.java
index dd2e199..254aa43 100644
--- a/user/src/com/google/gwt/resources/rg/GwtCreateResourceGenerator.java
+++ b/user/src/com/google/gwt/resources/rg/GwtCreateResourceGenerator.java
@@ -24,11 +24,13 @@
 import com.google.gwt.resources.client.GwtCreateResource.ClassType;
 import com.google.gwt.resources.ext.AbstractResourceGenerator;
 import com.google.gwt.resources.ext.ResourceContext;
+import com.google.gwt.resources.ext.SupportsGeneratorResultCaching;
 
 /**
  * Provides implementations of GwtCreateResource.
  */
-public final class GwtCreateResourceGenerator extends AbstractResourceGenerator {
+public final class GwtCreateResourceGenerator extends AbstractResourceGenerator 
+    implements SupportsGeneratorResultCaching {
 
   @Override
   public String createAssignment(TreeLogger logger, ResourceContext context,
diff --git a/user/src/com/google/gwt/resources/rg/ImageResourceGenerator.java b/user/src/com/google/gwt/resources/rg/ImageResourceGenerator.java
index ee835c3..8707e98 100644
--- a/user/src/com/google/gwt/resources/rg/ImageResourceGenerator.java
+++ b/user/src/com/google/gwt/resources/rg/ImageResourceGenerator.java
@@ -32,6 +32,7 @@
 import com.google.gwt.resources.ext.ClientBundleRequirements;
 import com.google.gwt.resources.ext.ResourceContext;
 import com.google.gwt.resources.ext.ResourceGeneratorUtil;
+import com.google.gwt.resources.ext.SupportsGeneratorResultCaching;
 import com.google.gwt.resources.rg.ImageBundleBuilder.Arranger;
 import com.google.gwt.resources.rg.ImageBundleBuilder.ImageRect;
 import com.google.gwt.user.rebind.SourceWriter;
@@ -48,7 +49,8 @@
 /**
  * Builds an image strip for all ImageResources defined within an ClientBundle.
  */
-public final class ImageResourceGenerator extends AbstractResourceGenerator {
+public final class ImageResourceGenerator extends AbstractResourceGenerator
+    implements SupportsGeneratorResultCaching {
   /**
    * Represents a file that contains multiple image regions.
    */
@@ -602,6 +604,10 @@
     try {
       String locale = context.getGeneratorContext().getPropertyOracle().getSelectionProperty(
           TreeLogger.NULL, "locale").getCurrentValue();
+
+      // add the locale selection property as a permuation axis for our requirements
+      context.getRequirements().addPermutationAxis("locale");
+
       sb.append(locale);
     } catch (BadPropertyValueException e) {
       // OK, locale isn't defined
diff --git a/user/src/com/google/gwt/resources/rg/TextResourceGenerator.java b/user/src/com/google/gwt/resources/rg/TextResourceGenerator.java
index e40ccd6..95c1d4e 100644
--- a/user/src/com/google/gwt/resources/rg/TextResourceGenerator.java
+++ b/user/src/com/google/gwt/resources/rg/TextResourceGenerator.java
@@ -24,6 +24,7 @@
 import com.google.gwt.resources.ext.AbstractResourceGenerator;
 import com.google.gwt.resources.ext.ResourceContext;
 import com.google.gwt.resources.ext.ResourceGeneratorUtil;
+import com.google.gwt.resources.ext.SupportsGeneratorResultCaching;
 import com.google.gwt.user.rebind.SourceWriter;
 import com.google.gwt.user.rebind.StringSourceWriter;
 
@@ -32,7 +33,8 @@
 /**
  * Provides implementations of TextResource.
  */
-public final class TextResourceGenerator extends AbstractResourceGenerator {
+public final class TextResourceGenerator extends AbstractResourceGenerator
+    implements SupportsGeneratorResultCaching {
   /**
    * Java compiler has a limit of 2^16 bytes for encoding string constants in a
    * class file. Since the max size of a character is 4 bytes, we'll limit the
diff --git a/user/test/com/google/gwt/resources/rg/CssTestCase.java b/user/test/com/google/gwt/resources/rg/CssTestCase.java
index a4c07ae..6b46164 100644
--- a/user/test/com/google/gwt/resources/rg/CssTestCase.java
+++ b/user/test/com/google/gwt/resources/rg/CssTestCase.java
@@ -24,6 +24,7 @@
 import com.google.gwt.resources.css.ast.CssStylesheet;
 import com.google.gwt.resources.css.ast.CssVisitor;
 import com.google.gwt.resources.css.ast.HasNodes;
+import com.google.gwt.resources.ext.ClientBundleRequirements;
 import com.google.gwt.resources.ext.ResourceContext;
 
 import junit.framework.TestCase;
@@ -110,6 +111,10 @@
         throws IllegalStateException {
       return null;
     }
+    
+    public ClientBundleRequirements getRequirements() {
+      return null;
+    }
 
     public <T> boolean putCachedData(String key, T value) {
       return false;