Base Framework for Generator Result Caching

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


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9468 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/GeneratorContextExt.java b/dev/core/src/com/google/gwt/core/ext/GeneratorContextExt.java
new file mode 100644
index 0000000..5dfbea7
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/GeneratorContextExt.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2010 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.core.ext.typeinfo.JClassType;
+import com.google.gwt.dev.javac.rebind.CachedRebindResult;
+
+/**
+ * EXPERIMENTAL and subject to change. Do not use this in production code.
+ * <p>
+ * An extension to GeneratorContext which includes access to previously cached
+ * rebind results.
+ * <p> 
+ * TODO(jbrosenberg): Merge this into {@link GeneratorContext} directly, once 
+ * the api has stabilized and we can remove the "experimental" moniker.
+ */
+public interface GeneratorContextExt extends GeneratorContext {
+  
+  /**
+   * Get cached result from a previous run of the current generator, if available.
+   * 
+   * @return A {@link com.google.gwt.dev.javac.rebind.CachedRebindResult} object,
+   *         if one has been provided to the context.  Null is returned if there
+   *         is no previous result, or if generator result caching is not enabled.
+   */
+  CachedRebindResult getCachedGeneratorResult();
+  
+  /**
+   * Get source last modified time.
+   * <p>
+   * TODO(jbrosenberg): Implement in terms of a getVersion method yet to be
+   * added to TypeOracle, instead of looking for age of a java source file.
+   * This will soon be removed.
+   */
+  long getSourceLastModifiedTime(JClassType sourceType);
+  
+  /**
+   * Check whether generator result caching is currently enabled.
+   */
+  boolean isGeneratorResultCachingEnabled();
+  
+  /**
+   * Mark a type to be reused from the generator result cache.  Calling this
+   * method with a successful response indicates that the calling generator will 
+   * not re-generate this type.  A cached version of this type will be added
+   * to the context once the calling generator returns from 
+   * {@link GeneratorExt#generateIncrementally}, with a result containing 
+   * {@link com.google.gwt.dev.javac.rebind.RebindStatus#USE_PARTIAL_CACHED}.
+   * 
+   * @param typeName the fully qualified name of a type.
+   * @return true if the requested type is available from the generator result 
+   *         cache, false otherwise.
+   */
+  boolean reuseTypeFromCacheIfAvailable(String typeName);
+}
diff --git a/dev/core/src/com/google/gwt/core/ext/GeneratorExt.java b/dev/core/src/com/google/gwt/core/ext/GeneratorExt.java
new file mode 100644
index 0000000..2a1313c
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/GeneratorExt.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2010 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.dev.javac.rebind.RebindResult;
+import com.google.gwt.dev.javac.rebind.RebindStatus;
+
+/**
+ * EXPERIMENTAL and subject to change. Do not use this in production code.
+ * <p>
+ * Adds a new {@link #generateIfNecessary} method.
+ * <p> 
+ * TODO(jbrosenberg): Merge this into {@link Generator} directly, once the api
+ * has stabilized and we can remove the "experimental" moniker.
+ */
+public abstract class GeneratorExt extends Generator {
+  
+  /**
+   * A wrapper class for using old style {@link Generator} implementations where
+   * a GeneratorExt instance is needed.
+   */
+  private static class BaseGeneratorWrapper extends GeneratorExt {
+    final Generator baseGenerator;
+    
+    public BaseGeneratorWrapper(Generator baseGenerator) {
+      this.baseGenerator = baseGenerator;
+    }
+    
+    @Override
+    public String generate(TreeLogger logger, GeneratorContext context,
+        String typeName) throws UnableToCompleteException {
+      return this.baseGenerator.generate(logger, context, typeName);
+    }
+  }
+  
+  /**
+   * Get a new instance wrapped from an old style {@link Generator} 
+   * implementation.
+   */
+  public static GeneratorExt getWrappedInstance(Generator baseGenerator) {
+    return new BaseGeneratorWrapper(baseGenerator);
+  }
+  
+  /**
+   * A default implementation of the abstract method defined in the old style
+   * {@link Generator}.
+   * <p>
+   * Note, it is recommended that {@link #generateIncrementally} be used instead.
+   * 
+   * @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
+   */
+  @Override
+  public String generate(TreeLogger logger, GeneratorContext context,
+      String typeName) throws UnableToCompleteException {
+    // to override (implementing generateIncrementally instead is recommended)
+    return null;
+  }
+  
+  /**
+   * Incrementally generate a default constructible subclass of the requested 
+   * type.  The generator can use information from the context to determine
+   * whether it needs to regenerate everything, or whether it can selectively
+   * regenerate a subset of its output, or whether it can return quickly to
+   * allow use of all previously cached objects.  It will return a 
+   * {@link RebindResult}, which contains a {@link RebindStatus} field 
+   * indicating whether to use previously cached artifacts, newly generated 
+   * ones, or a partial mixture of both cached and newly generated objects.  
+   * <p>
+   * The result also includes a field for the name of the subclass to 
+   * substitute for the requested class.
+   * <p>
+   * For backwards compatibility, the default implementation calls the old-style
+   * generate() method, and doesn't attempt any generator result caching.
+   * <p>
+   * The generator throws an <code>UnableToCompleteException</code> if for 
+   * any reason it cannot complete successfully.
+   * 
+   * @return a GeneratorResult
+   */
+  public RebindResult generateIncrementally(TreeLogger logger, 
+      GeneratorContextExt context, String typeName) 
+      throws UnableToCompleteException {
+    
+    // to override (default implementation calls unconditional generate() method)
+    
+    RebindStatus status;
+    String resultTypeName = generate(logger, context, typeName);
+    if (resultTypeName == null) {
+      status = RebindStatus.USE_EXISTING;
+      resultTypeName = typeName;
+    } else {
+      status = RebindStatus.USE_ALL_NEW_WITH_NO_CACHING;
+    }
+    
+    return new RebindResult(status, resultTypeName);
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/DevModeBase.java b/dev/core/src/com/google/gwt/dev/DevModeBase.java
index cdec046..2bb3092 100644
--- a/dev/core/src/com/google/gwt/dev/DevModeBase.java
+++ b/dev/core/src/com/google/gwt/dev/DevModeBase.java
@@ -23,6 +23,7 @@
 import com.google.gwt.dev.cfg.ModuleDef;
 import com.google.gwt.dev.cfg.ModuleDefLoader;
 import com.google.gwt.dev.javac.CompilationState;
+import com.google.gwt.dev.javac.rebind.RebindCache;
 import com.google.gwt.dev.jjs.JJSOptions;
 import com.google.gwt.dev.shell.ArtifactAcceptor;
 import com.google.gwt.dev.shell.BrowserChannelServer;
@@ -38,6 +39,7 @@
 import com.google.gwt.dev.ui.DoneCallback;
 import com.google.gwt.dev.ui.DoneEvent;
 import com.google.gwt.dev.util.BrowserInfo;
+import com.google.gwt.dev.util.arg.ArgHandlerEnableGeneratorResultCaching;
 import com.google.gwt.dev.util.arg.ArgHandlerGenDir;
 import com.google.gwt.dev.util.arg.ArgHandlerLogLevel;
 import com.google.gwt.dev.util.arg.OptionGenDir;
@@ -662,6 +664,7 @@
       registerHandler(new ArgHandlerPort(options));
       registerHandler(new ArgHandlerWhitelist());
       registerHandler(new ArgHandlerBlacklist());
+      registerHandler(new ArgHandlerEnableGeneratorResultCaching(options));
       registerHandler(new ArgHandlerLogDir(options));
       registerHandler(new ArgHandlerLogLevel(options));
       registerHandler(new ArgHandlerGenDir(options));
@@ -737,6 +740,8 @@
 
   private boolean headlessMode = false;
 
+  private Map<String, RebindCache> rebindCaches = null;
+
   private boolean started;
 
   private TreeLogger topLogger;
@@ -838,7 +843,8 @@
     ArtifactAcceptor artifactAcceptor = createArtifactAcceptor(logger,
         moduleDef);
     return new ShellModuleSpaceHost(logger, compilationState, moduleDef,
-        options.getGenDir(), artifactAcceptor);
+        options.getGenDir(), artifactAcceptor, 
+        getRebindCache(moduleDef.getName()));
   }
 
   protected abstract void doShutDownServer();
@@ -1034,7 +1040,6 @@
 
     Event startupEvent = SpeedTracerLogger.start(DevModeEventType.STARTUP);
     try {
-      boolean result = false;
       // See if there was a UI specified by command-line args
       ui = createUI();
   
@@ -1147,6 +1152,24 @@
     return newUI;
   }
 
+  private RebindCache getRebindCache(String moduleName) {
+    
+    if (!options.isGeneratorResultCachingEnabled()) {
+      return null;
+    }
+    
+    if (rebindCaches == null) {
+      rebindCaches = new HashMap<String, RebindCache>();
+    }
+    
+    RebindCache cache = rebindCaches.get(moduleName);
+    if (cache == null) {
+      cache = new RebindCache();
+      rebindCaches.put(moduleName, cache);
+    }
+    return cache;
+  }
+
   /**
    * Perform hosted mode relink when new artifacts are generated, without
    * overwriting newer or unmodified files in the output folder.
diff --git a/dev/core/src/com/google/gwt/dev/Precompile.java b/dev/core/src/com/google/gwt/dev/Precompile.java
index 830e240..cb9efac 100644
--- a/dev/core/src/com/google/gwt/dev/Precompile.java
+++ b/dev/core/src/com/google/gwt/dev/Precompile.java
@@ -202,6 +202,10 @@
       return enableGeneratingOnShards;
     }
 
+    public boolean isGeneratorResultCachingEnabled() {
+      return jjsOptions.isGeneratorResultCachingEnabled();
+    }
+
     public boolean isOptimizePrecompile() {
       return jjsOptions.isOptimizePrecompile();
     }
@@ -261,6 +265,10 @@
     public void setGenDir(File genDir) {
       this.genDir = genDir;
     }
+    
+    public void setGeneratorResultCachingEnabled(boolean enabled) {
+      jjsOptions.setGeneratorResultCachingEnabled(enabled);
+    }
 
     public void setMaxPermsPerPrecompile(int maxPermsPerPrecompile) {
       this.maxPermsPerPrecompile = maxPermsPerPrecompile;
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java b/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java
index d90ac5f6..6fccfee 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java
@@ -264,6 +264,17 @@
     return null;
   }
 
+  /**
+   * Returns the Resource for a source file if it is found; <code>null</code>
+   * otherwise.
+   *
+   * @param partialPath the partial path of the source file
+   * @return the resource for the requested source file
+   */
+  public synchronized Resource findSourceFile(String partialPath) {
+    return lazySourceOracle.getResourceMap().get(partialPath);
+  }
+
   public Set<String> getActiveLinkerNames() {
     return new LinkedHashSet<String>(activeLinkers);
   }
@@ -435,19 +446,6 @@
   }
 
   /**
-   * Returns the URL for a source file if it is found; <code>false</code>
-   * otherwise.
-   *
-   * NOTE: this method is for testing only.
-   *
-   * @param partialPath the partial path of the source file
-   * @return the resource for the requested source file
-   */
-  synchronized Resource findSourceFile(String partialPath) {
-    return lazySourceOracle.getResourceMap().get(partialPath);
-  }
-
-  /**
    * The final method to call when everything is setup. Before calling this
    * method, several of the getter methods may not be called. After calling this
    * method, the add methods may not be called.
diff --git a/dev/core/src/com/google/gwt/dev/cfg/Rule.java b/dev/core/src/com/google/gwt/dev/cfg/Rule.java
index 65d66b0..a24e1ca 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/Rule.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/Rule.java
@@ -18,6 +18,7 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.dev.javac.StandardGeneratorContext;
+import com.google.gwt.dev.javac.rebind.RebindResult;
 
 /**
  * Abstract base class for various kinds of deferred binding rules.
@@ -38,7 +39,7 @@
         context.getTypeOracle(), typeName));
   }
 
-  public abstract String realize(TreeLogger logger,
+  public abstract RebindResult realize(TreeLogger logger,
       StandardGeneratorContext context, String typeName)
       throws UnableToCompleteException;
 
diff --git a/dev/core/src/com/google/gwt/dev/cfg/RuleFail.java b/dev/core/src/com/google/gwt/dev/cfg/RuleFail.java
index 87636ee..89c0fd0 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/RuleFail.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/RuleFail.java
@@ -18,14 +18,15 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.dev.javac.StandardGeneratorContext;
+import com.google.gwt.dev.javac.rebind.RebindResult;
 
 /**
- * A rule to explicitly fail during a deferred binding requrest.
+ * A rule to explicitly fail during a deferred binding request.
  */
 public class RuleFail extends Rule {
 
   @Override
-  public String realize(TreeLogger logger, StandardGeneratorContext context,
+  public RebindResult realize(TreeLogger logger, StandardGeneratorContext context,
       String typeName) throws UnableToCompleteException {
     logger.log(TreeLogger.ERROR, "Deferred binding request failed for type '"
         + typeName + "'", null);
diff --git a/dev/core/src/com/google/gwt/dev/cfg/RuleGenerateWith.java b/dev/core/src/com/google/gwt/dev/cfg/RuleGenerateWith.java
index f2b4d57..e41b51d 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/RuleGenerateWith.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/RuleGenerateWith.java
@@ -19,6 +19,7 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.dev.javac.StandardGeneratorContext;
+import com.google.gwt.dev.javac.rebind.RebindResult;
 
 /**
  * A rule to replace the type being rebound with a class whose name is
@@ -34,9 +35,10 @@
   }
 
   @Override
-  public String realize(TreeLogger logger, StandardGeneratorContext context,
-      String typeName) throws UnableToCompleteException {
-    return context.runGenerator(logger, generatorClass, typeName);
+  public RebindResult realize(TreeLogger logger,
+      StandardGeneratorContext context, String typeName)
+      throws UnableToCompleteException {
+    return context.runGeneratorIncrementally(logger, generatorClass, typeName);
   }
 
   @Override
diff --git a/dev/core/src/com/google/gwt/dev/cfg/RuleReplaceWith.java b/dev/core/src/com/google/gwt/dev/cfg/RuleReplaceWith.java
index 02766a4..4f4f273 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/RuleReplaceWith.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/RuleReplaceWith.java
@@ -18,6 +18,8 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.dev.javac.StandardGeneratorContext;
+import com.google.gwt.dev.javac.rebind.RebindResult;
+import com.google.gwt.dev.javac.rebind.RebindStatus;
 
 /**
  * A rule to replace the type being rebound with an explicitly named class.
@@ -35,9 +37,11 @@
   }
 
   @Override
-  public String realize(TreeLogger logger, StandardGeneratorContext context,
-      String typeName) throws UnableToCompleteException {
-    return replacementTypeName;
+  public RebindResult realize(TreeLogger logger,
+      StandardGeneratorContext context, String typeName)
+      throws UnableToCompleteException {
+    return new RebindResult(
+        RebindStatus.USE_EXISTING, replacementTypeName);
   }
 
   @Override
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 d46c927..bc7d51c 100644
--- a/dev/core/src/com/google/gwt/dev/javac/StandardGeneratorContext.java
+++ b/dev/core/src/com/google/gwt/dev/javac/StandardGeneratorContext.java
@@ -17,6 +17,8 @@
 
 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.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
@@ -24,9 +26,14 @@
 import com.google.gwt.core.ext.linker.ArtifactSet;
 import com.google.gwt.core.ext.linker.GeneratedResource;
 import com.google.gwt.core.ext.linker.impl.StandardGeneratedResource;
+import com.google.gwt.core.ext.typeinfo.JArrayType;
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.dev.cfg.ModuleDef;
+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.resource.Resource;
 import com.google.gwt.dev.resource.ResourceOracle;
 import com.google.gwt.dev.util.DiskCache;
 import com.google.gwt.dev.util.Util;
@@ -45,17 +52,18 @@
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 import java.util.SortedSet;
-import java.util.Map.Entry;
 
 /**
  * Manages generators and generated units during a single compilation.
  */
-public class StandardGeneratorContext implements GeneratorContext {
+public class StandardGeneratorContext implements GeneratorContextExt {
 
   /**
    * Extras added to {@link CompilationUnit}.
@@ -226,7 +234,8 @@
 
   private static DiskCache diskCache = new DiskCache();
 
-  private static final Map<String,CompilerEventType> eventsByGeneratorType = new HashMap<String,CompilerEventType>();
+  private static final Map<String,CompilerEventType> eventsByGeneratorType = 
+    new HashMap<String,CompilerEventType>();
   static {
     eventsByGeneratorType.put(
         "com.google.gwt.resources.rebind.context.InlineClientBundleGenerator",
@@ -255,7 +264,8 @@
 
   private final ArtifactSet allGeneratedArtifacts;
 
-  private final Set<GeneratedUnit> committedGeneratedCups = new HashSet<GeneratedUnit>();
+  private final Map<String, GeneratedUnit> committedGeneratedCups = 
+    new HashMap<String, GeneratedUnit>();
 
   private CompilationState compilationState;
 
@@ -263,7 +273,8 @@
 
   private final File genDir;
 
-  private final Map<Class<? extends Generator>, Generator> generators = new IdentityHashMap<Class<? extends Generator>, Generator>();
+  private final Map<Class<? extends Generator>, Generator> generators = 
+    new IdentityHashMap<Class<? extends Generator>, Generator>();
 
   private final ModuleDef module;
 
@@ -271,11 +282,19 @@
 
   private final Set<String> newlyGeneratedTypeNames = new HashSet<String>();
 
-  private final Map<String, PendingResource> pendingResources = new HashMap<String, PendingResource>();
+  private final Map<String, PendingResource> pendingResources = 
+    new HashMap<String, PendingResource>();
 
   private transient PropertyOracle propOracle;
 
-  private final Map<PrintWriter, Generated> uncommittedGeneratedCupsByPrintWriter = new IdentityHashMap<PrintWriter, Generated>();
+  private final Map<PrintWriter, Generated> uncommittedGeneratedCupsByPrintWriter = 
+    new IdentityHashMap<PrintWriter, Generated>();
+  
+  private CachedRebindResult cachedRebindResult = null;
+  
+  private boolean generatorResultCachingEnabled = false;
+  
+  private List<String> cachedTypeNamesToReuse = null;
 
   /**
    * Normally, the compiler host would be aware of the same types that are
@@ -288,6 +307,53 @@
     this.genDir = genDir;
     this.allGeneratedArtifacts = allGeneratedArtifacts;
   }
+  
+  /**
+   * Adds a generated unit to the context if not already present, but will not 
+   * overwrite an existing unit.
+   */
+  public void addGeneratedUnit(GeneratedUnit gu) {
+    if (!committedGeneratedCups.containsKey(gu.getTypeName())) {
+      committedGeneratedCups.put(gu.getTypeName(), gu);
+    }
+  }
+
+  /**
+   * Adds generated units to the context, but will not overwrite any existing 
+   * units that might already be present.
+   */
+  public void addGeneratedUnits(Collection<GeneratedUnit> generatedUnits) {
+    for (GeneratedUnit gu : generatedUnits) {
+      addGeneratedUnit(gu);
+    }
+  }
+  
+  /**
+   * Adds all available cached generated units to the context.  Existing units
+   * for a given type will not be overwritten.
+   */
+  public void addGeneratedUnitsFromCachedRebindResult() {
+    if (cachedRebindResult != null 
+        && cachedRebindResult.getGeneratedUnits() != null) {
+      addGeneratedUnits(cachedRebindResult.getGeneratedUnits());
+    }
+  }
+  
+  /**
+   * Adds cached generated units to the context that have been marked for reuse.
+   * Existing units for a given type will not be overwritten.
+   */
+  public void addGeneratedUnitsMarkedForReuseFromCache() {
+    if (cachedTypeNamesToReuse != null 
+        && cachedRebindResult != null) {
+      for (String typeName : cachedTypeNamesToReuse) {
+        GeneratedUnit gu = cachedRebindResult.getGeneratedUnit(typeName);
+        if (gu != null) {
+          addGeneratedUnit(gu);
+        }
+      }
+    }
+  }
 
   /**
    * Frees memory used up by compilation state.
@@ -305,7 +371,7 @@
     if (gcup != null) {
       gcup.commit();
       uncommittedGeneratedCupsByPrintWriter.remove(pw);
-      committedGeneratedCups.add(gcup);
+      committedGeneratedCups.put(gcup.getTypeName(), gcup);
     } else {
       logger.log(TreeLogger.WARN,
           "Generator attempted to commit an unknown PrintWriter", null);
@@ -313,14 +379,28 @@
   }
 
   /**
-   * Adds an Artifact to the ArtifactSet if one has been provided to the
-   * context.
+   * Adds an Artifact to the context's ArtifactSets.  This will replace a 
+   * pre-existing entry in allGeneratedArtifacts, but will not overwrite an 
+   * entry in the newlyGeneratedArtifacts (since it is assumed by convention 
+   * that only new entries will ever be inserted here for a given generator run).
    */
   public void commitArtifact(TreeLogger logger, Artifact<?> artifact) {
     allGeneratedArtifacts.replace(artifact);
     newlyGeneratedArtifacts.add(artifact);
   }
 
+  /**
+   * Commits all available cached Artifacts to the context.
+   */
+  public void commitArtifactsFromCachedRebindResult(TreeLogger logger) {
+    if (cachedRebindResult != null 
+        && cachedRebindResult.getArtifacts() != null) {
+      for (Artifact<?> art : cachedRebindResult.getArtifacts()) {
+        commitArtifact(logger, art);
+      }
+    }
+  }
+
   public GeneratedResource commitResource(TreeLogger logger, OutputStream os)
       throws UnableToCompleteException {
 
@@ -358,9 +438,6 @@
   public final ArtifactSet finish(TreeLogger logger) {
     abortUncommittedResources(logger);
 
-    // Process pending generated types.
-    List<String> genTypeNames = new ArrayList<String>();
-
     try {
       TreeLogger branch;
       if (!committedGeneratedCups.isEmpty()) {
@@ -375,16 +452,15 @@
               "Generated source files...", null);
         }
 
-        for (GeneratedUnit gcup : committedGeneratedCups) {
+        for (GeneratedUnit gcup : committedGeneratedCups.values()) {
           String qualifiedTypeName = gcup.getTypeName();
-          genTypeNames.add(qualifiedTypeName);
           if (subBranch != null) {
             subBranch.log(TreeLogger.DEBUG, qualifiedTypeName, null);
           }
         }
 
         compilationState.addGeneratedCompilationUnits(logger,
-            committedGeneratedCups);
+            committedGeneratedCups.values());
       }
       return newlyGeneratedArtifacts;
     } finally {
@@ -403,6 +479,8 @@
       committedGeneratedCups.clear();
       newlyGeneratedTypeNames.clear();
       newlyGeneratedArtifacts = new ArtifactSet();
+      cachedRebindResult = null;
+      cachedTypeNamesToReuse = null;
     }
   }
 
@@ -410,10 +488,31 @@
     return module.getActiveLinkerNames();
   }
 
+  /**
+   * Gets newly committed artifacts.
+   */
+  public ArtifactSet getArtifacts() {
+    return new ArtifactSet(newlyGeneratedArtifacts);
+  }
+
+  /**
+   * Gets the previously cached rebind result for the current generator.
+   */
+  public CachedRebindResult getCachedGeneratorResult() {
+    return cachedRebindResult;
+  }
+  
   public GeneratorContext getCanonicalContext() {
     return this;
   }
 
+  /**
+   * Gets all committed Java units.
+   */
+  public Map<String, GeneratedUnit> getGeneratedUnitMap() {
+    return committedGeneratedCups;
+  }
+  
   public final PropertyOracle getPropertyOracle() {
     return propOracle;
   }
@@ -421,14 +520,105 @@
   public ResourceOracle getResourcesOracle() {
     return module.getResourcesOracle();
   }
-
+ 
+  /**
+   * EXPERIMENTAL and subject to change. Do not use this in production code. 
+   * 
+   * Temporary solution to get last modified time for a sourceType.  Finds the
+   * the source file, if possible.  Note, this won't work for sources contained
+   * in jar files, or for recently generated source files.
+   * 
+   * TODO(jbrosenberg): Replace this method by using a getVersion() method from
+   * TypeOracle (still under development).
+   */
+  public long getSourceLastModifiedTime(JClassType sourceType) {
+    
+    while (sourceType instanceof JArrayType) {
+      sourceType = (JClassType) ((JArrayType) sourceType).getComponentType();
+    }
+    
+    JClassType enclosingType;
+    while ((enclosingType = sourceType.getEnclosingType()) != null) {
+        sourceType = enclosingType;
+    }
+    
+    String sourceName = sourceType.getQualifiedSourceName();
+    String sourcePath = sourceName.replace('.', '/') + ".java";
+    
+    Resource sourceResource = module.findSourceFile(sourcePath);
+    
+    if (sourceResource == null) {
+      return 0L;
+    } 
+    
+    return sourceResource.getLastModified();
+  }
+ 
   public final TypeOracle getTypeOracle() {
     return compilationState.getTypeOracle();
   }
-
+  
+  public boolean isGeneratorResultCachingEnabled() {
+    return generatorResultCachingEnabled;
+  }
+  
+  /**
+   * Adds a type name to the list of types to be reused from cache, if available.
+   * 
+   * @param typeName The fully qualified name of a type.
+   * 
+   * @return true, if the type is available in the cache and was successfully
+   *          added to the list for reuse, false otherwise.
+   */
+  public boolean reuseTypeFromCacheIfAvailable(String typeName) {
+    if (!isGeneratorResultCachingEnabled() ||
+        cachedRebindResult == null  || 
+        !cachedRebindResult.isTypeCached(typeName)) {
+      return false;
+    }
+    
+    if (cachedTypeNamesToReuse == null) {
+      cachedTypeNamesToReuse = new ArrayList<String>();
+    }
+    cachedTypeNamesToReuse.add(typeName);
+    return true;
+  }
+  
+  /**
+   * This method is maintained for backwards compatibility.
+   * {@link #runGeneratorIncrementally} should be used instead.
+   */
   public String runGenerator(TreeLogger logger,
       Class<? extends Generator> generatorClass, String typeName)
       throws UnableToCompleteException {
+    
+    RebindResult result = 
+        runGeneratorIncrementally(logger, generatorClass, typeName);
+    
+    return result.getReturnedTypeName();
+  }
+  
+  /**
+   * Runs a generator incrementally, with support for managing the returned
+   * {@link RebindResult} object, which can contain status and cached results.
+   * This is a replacement for the {@link #runGenerator} method.
+   * <p>
+   * If the passed in generatorClass is an instance of {@link GeneratorExt}, it's
+   * {@link GeneratorExt#generateIncrementally} method will be called.
+   * <p>
+   * Otherwise, for backwards compatibility, the generatorClass will be wrapped 
+   * in a {@link GeneratorExt} instance, and it's {@link Generator#generate}
+   * method will be called.
+   * 
+   * @param logger
+   * @param generatorClass
+   * @param typeName
+   * @return a RebindResult
+   * @throws UnableToCompleteException
+   */
+  public RebindResult runGeneratorIncrementally(TreeLogger logger,
+      Class<? extends Generator> generatorClass, String typeName)
+      throws UnableToCompleteException {
     String msg = "Invoking generator " + generatorClass.getName();
     logger = logger.branch(TreeLogger.DEBUG, msg, null);
 
@@ -459,16 +649,26 @@
         generatorClassName, "type", typeName);
     
     try {
-      String className = generator.generate(logger, this, typeName);
-      long after = System.currentTimeMillis();
-      if (className == null) {
-        msg = "Generator returned null, so the requested type will be used as is";
+      
+      GeneratorExt generatorExt;
+      if (generator instanceof GeneratorExt) {
+        generatorExt = (GeneratorExt) generator;
       } else {
-        msg = "Generator returned class '" + className + "'";
+        generatorExt = GeneratorExt.getWrappedInstance(generator);
+      }
+      
+      RebindResult result;
+      result = generatorExt.generateIncrementally(logger, this, typeName);
+      
+      long after = System.currentTimeMillis();
+      if (result.getResultStatus() == RebindStatus.USE_EXISTING) {
+        msg = "Generator did not return a new class, type will be used as is";
+      } else {
+        msg = "Generator returned class '" + result.getReturnedTypeName() + "'";
       }
       msg += "; in " + (after - before) + " ms";
       logger.log(TreeLogger.DEBUG, msg, null);
-      return className;
+      return result;
     } catch (AssertionError e) {
       // Catch and log the assertion as a convenience to the developer
       logger.log(TreeLogger.ERROR, "Generator '" + generatorClass.getName()
@@ -483,9 +683,20 @@
     }
   }
 
+  /**
+   * Set previously cached rebind result for currently active generator.
+   */
+  public void setCachedRebindResult(CachedRebindResult cachedRebindResult) {
+    this.cachedRebindResult = cachedRebindResult;
+  }
+  
   public void setCurrentGenerator(Class<? extends Generator> currentGenerator) {
     this.currentGenerator = currentGenerator;
   }
+  
+  public void setGeneratorResultCachingEnabled(boolean enabled) {
+    this.generatorResultCachingEnabled = enabled;
+  }
 
   /**
    * Sets the current transient property oracle to answer current property
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
new file mode 100644
index 0000000..fd20b0f
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/javac/rebind/CachedPropertyInformation.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2010 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 com.google.gwt.core.ext.BadPropertyValueException;
+import com.google.gwt.core.ext.ConfigurationProperty;
+import com.google.gwt.core.ext.PropertyOracle;
+import com.google.gwt.core.ext.SelectionProperty;
+import com.google.gwt.core.ext.TreeLogger;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * A container for saving lists of deferred-binding and configuration properties
+ * to be compared subsequently with a PropertyOracle.
+ */
+public class CachedPropertyInformation {
+  
+  private final List<SelectionProperty> selectionProperties;
+  private final List<ConfigurationProperty> configProperties;
+
+  public CachedPropertyInformation(TreeLogger logger, PropertyOracle oracle, 
+      Collection<String> selectionPropertyNames, 
+      Collection<String> configPropertyNames) {
+    
+    if (oracle == null) {
+      selectionProperties = null;
+      configProperties = null;
+      return;
+    } 
+    
+    if (selectionPropertyNames == null) {
+      selectionProperties = null;
+    } else {
+      selectionProperties = new ArrayList<SelectionProperty>();
+      for (String name : selectionPropertyNames) {
+        try { 
+          SelectionProperty selProp = oracle.getSelectionProperty(logger, name);
+          selectionProperties.add(selProp);
+        } catch (BadPropertyValueException e) {
+        }
+      }
+    }
+    
+    if (configPropertyNames == null) {
+      configProperties = null;
+    } else {
+      configProperties = new ArrayList<ConfigurationProperty>();
+      for (String name : configPropertyNames) {
+        try { 
+          ConfigurationProperty configProp = oracle.getConfigurationProperty(name);
+          configProperties.add(configProp);
+        } catch (BadPropertyValueException e) {
+        }
+      }
+    }
+  }
+  
+  /**
+   * Check a previously cached set of deferred-binding and configuration
+   * properties with the provided property oracle.
+   */
+  public boolean checkPropertiesWithPropertyOracle(TreeLogger logger,
+      PropertyOracle oracle) {
+    
+    if (selectionProperties != null) {
+      try {
+        for (SelectionProperty selProp : selectionProperties) {
+          SelectionProperty currProp = 
+            oracle.getSelectionProperty(logger, selProp.getName());
+          if (!currProp.getCurrentValue().equals(selProp.getCurrentValue())) {
+            return false;
+          }
+        }
+      } catch (BadPropertyValueException e) {
+        return false;
+      }
+    }
+    
+    if (configProperties != null) {
+      try {
+        for (ConfigurationProperty configProp : configProperties) {
+          ConfigurationProperty currProp = 
+            oracle.getConfigurationProperty(configProp.getName());
+          if (!currProp.equals(configProp)) {
+            return false;
+          }
+        }
+      } catch (BadPropertyValueException e) {
+        return false;
+      }
+    }
+    
+    return true;
+  } 
+}
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
new file mode 100644
index 0000000..cd8e3c8
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/javac/rebind/CachedRebindResult.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2010 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 com.google.gwt.core.ext.linker.ArtifactSet;
+import com.google.gwt.dev.javac.GeneratedUnit;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A class to represent the results from a rebind operation.  This can be
+ * 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 {
+  private final ArtifactSet artifacts;
+  private final Map<String, GeneratedUnit> generatedUnitMap;
+  private final String returnedTypeName;
+  private final long timeGenerated;
+  private final Object clientData;
+
+  public CachedRebindResult(String resultTypeName, ArtifactSet artifacts, 
+      Map<String, GeneratedUnit> generatedUnitMap, 
+      long timeGenerated, Object clientData) {
+    this.returnedTypeName = resultTypeName;
+    this.artifacts = new ArtifactSet(artifacts);
+    this.generatedUnitMap = new HashMap<String, GeneratedUnit>(generatedUnitMap);
+    this.timeGenerated = timeGenerated;
+    this.clientData = clientData;
+  }
+  
+  public CachedRebindResult(String resultTypeName, ArtifactSet artifacts, 
+      Map<String, GeneratedUnit> generatedUnitMap, long timeGenerated) {
+    this(resultTypeName, artifacts, generatedUnitMap, timeGenerated, null);
+  }
+  
+  public ArtifactSet getArtifacts() {
+    return artifacts;
+  }
+  
+  public Object getClientData() {
+    return clientData;
+  }
+  
+  public GeneratedUnit getGeneratedUnit(String typeName) {
+    return generatedUnitMap.get(typeName);
+  }
+  
+  public Collection<GeneratedUnit> getGeneratedUnits() {
+    return generatedUnitMap.values();
+  }
+  
+  public String getReturnedTypeName() {
+    return returnedTypeName;
+  }
+  
+  public long getTimeGenerated() {
+    return timeGenerated;
+  }
+  
+  public boolean isTypeCached(String typeName) {
+    return generatedUnitMap.containsKey(typeName);
+  }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..ae59a51
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/javac/rebind/RebindCache.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2010 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 com.google.gwt.dev.cfg.Rule;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A cache for storing {@link CachedRebindResult} entries.  Entries are keyed
+ * by rebind Rule and queryTypeName.
+ */
+public class RebindCache {
+ 
+  private final Map<String, Map<String, CachedRebindResult>> rebindResults;
+  
+  public RebindCache() {
+    rebindResults = new HashMap<String, Map<String, CachedRebindResult>>();
+  }
+
+  public CachedRebindResult get(Rule rule, String queryTypeName) {
+    Map<String, CachedRebindResult> ruleResults;
+    ruleResults = rebindResults.get(rule.toString());
+    if (ruleResults != null) {
+      return ruleResults.get(queryTypeName);
+    }
+
+    return null;
+  }
+  
+  public void invalidate() {
+    rebindResults.clear();
+  }
+  
+  public void put(Rule rule, String queryTypeName, CachedRebindResult results) {
+    Map<String, CachedRebindResult> ruleResults = rebindResults.get(rule.toString());
+    if (ruleResults == null) {
+      ruleResults = new HashMap<String, CachedRebindResult>();
+      rebindResults.put(rule.toString(), ruleResults);
+    }
+    ruleResults.put(queryTypeName, results);
+  }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..5ec7df0
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/javac/rebind/RebindResult.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2010 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;
+
+/**
+ * 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;
+  
+  public RebindResult(RebindStatus resultStatus, 
+      String returnedType, Object clientData) {
+    this.resultStatus = resultStatus;
+    this.returnedTypeName = returnedType;
+    this.clientData = clientData;
+  }
+
+  public RebindResult(RebindStatus resultStatus, String returnedType) {
+    this(resultStatus, returnedType, null);
+  }
+  
+  public Object getClientData() {
+    return clientData;
+  }
+  
+  public RebindStatus getResultStatus() {
+    return resultStatus;
+  }
+
+  public String getReturnedTypeName() {
+    return returnedTypeName;
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/javac/rebind/RebindStatus.java b/dev/core/src/com/google/gwt/dev/javac/rebind/RebindStatus.java
new file mode 100644
index 0000000..3506723
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/javac/rebind/RebindStatus.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2010 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;
+
+/**
+ * The returned status for a rebind result.  This status is used by the 
+ * {@link com.google.gwt.dev.shell.StandardRebindOracle} implementation to 
+ * determine how to integrate the result into the system.  In the descriptions 
+ * below, the "products" of a rebind result can include updated type 
+ * information, newly generated artifacts, and newly generated compilation units.
+ */
+public enum RebindStatus {
+ 
+  /**
+   * Indicates nothing new was created, use pre-existing type information.
+   */ 
+  USE_EXISTING,
+ 
+  /**
+   * Indicates only newly generated products should be used.
+   */
+  USE_ALL_NEW,
+  
+  /**
+   * Indicates only newly generated products should be used, and no results
+   * should be cached, such as in the case where no caching can be taken
+   * advantage of.
+   */
+  USE_ALL_NEW_WITH_NO_CACHING,
+  
+  /**
+   * Indicates only previously cached products should be used.
+   */
+  USE_ALL_CACHED,
+  
+  /**
+   * Indicates that a mixture of newly generated and previously cached products
+   * should be used.
+   */
+  USE_PARTIAL_CACHED
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JJSOptions.java b/dev/core/src/com/google/gwt/dev/jjs/JJSOptions.java
index 96226c3..7b2bd41 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JJSOptions.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JJSOptions.java
@@ -20,6 +20,7 @@
 import com.google.gwt.dev.util.arg.OptionDisableCastChecking;
 import com.google.gwt.dev.util.arg.OptionDisableClassMetadata;
 import com.google.gwt.dev.util.arg.OptionEnableAssertions;
+import com.google.gwt.dev.util.arg.OptionEnableGeneratorResultCaching;
 import com.google.gwt.dev.util.arg.OptionOptimize;
 import com.google.gwt.dev.util.arg.OptionOptimizePrecompile;
 import com.google.gwt.dev.util.arg.OptionRunAsyncEnabled;
@@ -33,7 +34,8 @@
  */
 public interface JJSOptions extends OptionOptimize, OptionAggressivelyOptimize,
     OptionDisableClassMetadata, OptionDisableCastChecking,
-    OptionEnableAssertions, OptionRunAsyncEnabled, OptionScriptStyle,
+    OptionEnableAssertions, OptionEnableGeneratorResultCaching, 
+    OptionRunAsyncEnabled, OptionScriptStyle,
     OptionSoycEnabled, OptionSoycDetailed, OptionOptimizePrecompile,
     OptionStrict, OptionCompilerMetricsEnabled {
 }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JJSOptionsImpl.java b/dev/core/src/com/google/gwt/dev/jjs/JJSOptionsImpl.java
index af63621..322915a 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JJSOptionsImpl.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JJSOptionsImpl.java
@@ -28,6 +28,7 @@
   private boolean disableCastChecking = false;
   private boolean disableClassMetadata = false;
   private boolean enableAssertions;
+  private boolean enableGeneratorResultCaching = false;
   private boolean optimizePrecompile = false;
   private JsOutputOption output = JsOutputOption.OBFUSCATED;
   private boolean runAsyncEnabled = true;
@@ -45,11 +46,12 @@
   }
 
   public void copyFrom(JJSOptions other) {
+    setAggressivelyOptimize(other.isAggressivelyOptimize());
     setCastCheckingDisabled(other.isCastCheckingDisabled());
     setClassMetadataDisabled(other.isClassMetadataDisabled());
     setCompilerMetricsEnabled(other.isCompilerMetricsEnabled());
     setEnableAssertions(other.isEnableAssertions());
-    setAggressivelyOptimize(other.isAggressivelyOptimize());
+    setGeneratorResultCachingEnabled(other.isGeneratorResultCachingEnabled());
     setOptimizationLevel(other.getOptimizationLevel());
     setOutput(other.getOutput());
     setRunAsyncEnabled(other.isRunAsyncEnabled());
@@ -89,6 +91,10 @@
   public boolean isEnableAssertions() {
     return enableAssertions;
   }
+  
+  public boolean isGeneratorResultCachingEnabled() {
+    return enableGeneratorResultCaching;
+  }
 
   public boolean isOptimizePrecompile() {
     return optimizePrecompile;
@@ -129,6 +135,10 @@
   public void setEnableAssertions(boolean enableAssertions) {
     this.enableAssertions = enableAssertions;
   }
+  
+  public void setGeneratorResultCachingEnabled(boolean enabled) {
+    this.enableGeneratorResultCaching = enabled;
+  }
 
   public void setOptimizationLevel(int level) {
     optimizationLevel = level;
diff --git a/dev/core/src/com/google/gwt/dev/shell/ShellModuleSpaceHost.java b/dev/core/src/com/google/gwt/dev/shell/ShellModuleSpaceHost.java
index 72d7305..2c1ddf4 100644
--- a/dev/core/src/com/google/gwt/dev/shell/ShellModuleSpaceHost.java
+++ b/dev/core/src/com/google/gwt/dev/shell/ShellModuleSpaceHost.java
@@ -22,6 +22,7 @@
 import com.google.gwt.dev.cfg.Rules;
 import com.google.gwt.dev.javac.CompilationState;
 import com.google.gwt.dev.javac.StandardGeneratorContext;
+import com.google.gwt.dev.javac.rebind.RebindCache;
 import com.google.gwt.dev.util.log.speedtracer.DevModeEventType;
 import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
 import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
@@ -53,18 +54,20 @@
 
   private ModuleSpace space;
 
+  private RebindCache rebindCache;
+
   /**
    * @param module the module associated with the hosted module space
-   * @param saveJsni
    */
   public ShellModuleSpaceHost(TreeLogger logger,
       CompilationState compilationState, ModuleDef module, File genDir,
-      ArtifactAcceptor artifactAcceptor) {
+      ArtifactAcceptor artifactAcceptor, RebindCache rebindCache) {
     this.logger = logger;
     this.compilationState = compilationState;
     this.module = module;
     this.genDir = genDir;
     this.artifactAcceptor = artifactAcceptor;
+    this.rebindCache = rebindCache;
   }
 
   public CompilingClassLoader getClassLoader() {
@@ -98,7 +101,12 @@
       Rules rules = module.getRules();
       StandardGeneratorContext genCtx = new StandardGeneratorContext(
           compilationState, module, genDir, new ArtifactSet());
+      
+      // Only enable generator result caching if we have a valid rebindCache
+      genCtx.setGeneratorResultCachingEnabled((rebindCache != null));
+      
       rebindOracle = new StandardRebindOracle(propOracle, rules, genCtx);
+      rebindOracle.setRebindCache(rebindCache);
 
       // Create a completely isolated class loader which owns all classes
       // associated with a particular module. This effectively builds a
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 23ee903..5736eb5 100644
--- a/dev/core/src/com/google/gwt/dev/shell/StandardRebindOracle.java
+++ b/dev/core/src/com/google/gwt/dev/shell/StandardRebindOracle.java
@@ -22,6 +22,10 @@
 import com.google.gwt.dev.cfg.Rule;
 import com.google.gwt.dev.cfg.Rules;
 import com.google.gwt.dev.javac.StandardGeneratorContext;
+import com.google.gwt.dev.javac.rebind.CachedRebindResult;
+import com.google.gwt.dev.javac.rebind.RebindCache;
+import com.google.gwt.dev.javac.rebind.RebindResult;
+import com.google.gwt.dev.javac.rebind.RebindStatus;
 import com.google.gwt.dev.jdt.RebindOracle;
 import com.google.gwt.dev.util.Util;
 import com.google.gwt.dev.util.log.speedtracer.DevModeEventType;
@@ -55,7 +59,27 @@
       Event rebindEvent = SpeedTracerLogger.start(DevModeEventType.REBIND, "Type Name", typeName);
       try {
         genCtx.setPropertyOracle(propOracle);
-        String result = tryRebind(logger, typeName);
+        Rule rule = getRebindRule(logger, typeName);
+
+        if (rule == null) {
+          return typeName;
+        }
+        
+        CachedRebindResult cachedResult = rebindCacheGet(rule, typeName);
+        if (cachedResult != null) {
+          genCtx.setCachedRebindResult(cachedResult);
+        }
+        
+        // realize the rule (call a generator, or do type replacement, etc.)
+        RebindResult result = rule.realize(logger, genCtx, typeName);
+        
+        // handle rebind result caching (if enabled)
+        String resultTypeName = processCacheableResult(logger, rule, typeName, 
+            cachedResult, result);
+        
+        /*
+         * Finalize new artifacts from the generator context
+         */
         if (artifactAcceptor != null) {
           // Go ahead and call finish() to accept new artifacts.
           ArtifactSet newlyGeneratedArtifacts = genCtx.finish(logger);
@@ -63,16 +87,15 @@
             artifactAcceptor.accept(logger, newlyGeneratedArtifacts);
           }
         }
-        if (result == null) {
-          result = typeName;
-        }
-        return result;
+
+        assert (resultTypeName != null);
+        return resultTypeName;
       } finally {
         rebindEvent.end();
       }
     }
 
-    private String tryRebind(TreeLogger logger, String typeName)
+    private Rule getRebindRule(TreeLogger logger, String typeName)
         throws UnableToCompleteException {
       if (usedTypeNames.contains(typeName)) {
         // Found a cycle.
@@ -110,10 +133,7 @@
             usedRules.add(rule);
             Messages.TRACE_RULE_MATCHED.log(logger, null);
 
-            // Invoke the rule.
-            //
-            return rule.realize(logger, genCtx, typeName);
-
+            return rule;
           } else {
             // We are skipping this rule because it has already been used
             // in a previous iteration.
@@ -128,14 +148,84 @@
       //
       return null;
     }
+
+    /*
+     * Decide how to handle integrating a previously cached result, and whether
+     * to cache the new result for the future.
+     */
+    private String processCacheableResult(TreeLogger logger, Rule rule, 
+        String typeName, CachedRebindResult cachedResult, RebindResult newResult) {
+      
+      String resultTypeName = newResult.getReturnedTypeName();
+      
+      if (!genCtx.isGeneratorResultCachingEnabled()) {
+        return resultTypeName;
+      }
+      
+      RebindStatus status = newResult.getResultStatus();
+      switch (status) {
+        
+        case USE_EXISTING:
+          // in this case, no newly generated or cached types are needed
+          break; 
+          
+        case USE_ALL_NEW_WITH_NO_CACHING:
+          /*
+           * in this case, new artifacts have been generated, but no need to
+           * cache results (as the generator is probably not able to take
+           * advantage of caching).
+           */
+          break;
+          
+        case USE_ALL_NEW:
+          // use all new results, add a new cache entry
+          cachedResult = new CachedRebindResult(newResult.getReturnedTypeName(),
+              genCtx.getArtifacts(), genCtx.getGeneratedUnitMap(), 
+              System.currentTimeMillis(), newResult.getClientData());
+          rebindCachePut(rule, typeName, cachedResult);
+          break;
+          
+        case USE_ALL_CACHED:
+          // use all cached results
+          assert (cachedResult != null);
+          
+          genCtx.commitArtifactsFromCachedRebindResult(logger);
+          genCtx.addGeneratedUnitsFromCachedRebindResult();
+          
+          // use cached type name
+          resultTypeName = cachedResult.getReturnedTypeName();
+          break;
+          
+        case USE_PARTIAL_CACHED:
+          /*
+           * Add cached generated units marked for reuse to the context.  
+           * TODO(jbrosenberg): add support for reusing artifacts as well
+           * as GeneratedUnits.
+           */
+          genCtx.addGeneratedUnitsMarkedForReuseFromCache();
+          
+          /*
+           * Create a new cache entry using the composite set of new and 
+           * reused cached results currently in genCtx.
+           */
+          cachedResult = new CachedRebindResult(newResult.getReturnedTypeName(),
+              genCtx.getArtifacts(), genCtx.getGeneratedUnitMap(), 
+              System.currentTimeMillis(), newResult.getClientData());
+          rebindCachePut(rule, typeName, cachedResult);
+          break;
+      }
+      return resultTypeName;
+    }
   }
 
-  private final Map<String, String> cache = new HashMap<String, String>();
+  private final Map<String, String> typeNameBindingMap = new HashMap<String, String>();
 
   private final StandardGeneratorContext genCtx;
 
   private final PropertyOracle propOracle;
 
+  private RebindCache rebindCache = null;
+
   private final Rules rules;
 
   public StandardRebindOracle(PropertyOracle propOracle, Rules rules,
@@ -153,16 +243,33 @@
   public String rebind(TreeLogger logger, String typeName,
       ArtifactAcceptor artifactAcceptor) throws UnableToCompleteException {
 
-    String result = cache.get(typeName);
-    if (result == null) {
+    String resultTypeName = typeNameBindingMap.get(typeName);
+    if (resultTypeName == null) {
       logger = Messages.TRACE_TOPLEVEL_REBIND.branch(logger, typeName, null);
 
       Rebinder rebinder = new Rebinder();
-      result = rebinder.rebind(logger, typeName, artifactAcceptor);
-      cache.put(typeName, result);
+      resultTypeName = rebinder.rebind(logger, typeName, artifactAcceptor);
+      typeNameBindingMap.put(typeName, resultTypeName);
 
-      Messages.TRACE_TOPLEVEL_REBIND_RESULT.log(logger, result, null);
+      Messages.TRACE_TOPLEVEL_REBIND_RESULT.log(logger, resultTypeName, null);
     }
-    return result;
+    return resultTypeName;
+  }
+
+  public void setRebindCache(RebindCache cache) {
+    this.rebindCache = cache;
+  }
+  
+  private CachedRebindResult rebindCacheGet(Rule rule, String typeName) {
+    if (rebindCache != null) {
+      return rebindCache.get(rule, typeName);
+    }
+    return null;
+  }
+  
+  private void rebindCachePut(Rule rule, String typeName, CachedRebindResult result) {
+    if (rebindCache != null) {
+      rebindCache.put(rule, typeName, result);
+    }
   }
 }
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerEnableGeneratorResultCaching.java b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerEnableGeneratorResultCaching.java
new file mode 100644
index 0000000..b9b8f29
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerEnableGeneratorResultCaching.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2010 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.util.arg;
+
+import com.google.gwt.util.tools.ArgHandlerFlag;
+
+/**
+ * An ArgHandler to provide the -XenableGeneratorResultCaching flag.
+ */
+public class ArgHandlerEnableGeneratorResultCaching extends ArgHandlerFlag {
+
+  private final OptionEnableGeneratorResultCaching option;
+
+  public ArgHandlerEnableGeneratorResultCaching(OptionEnableGeneratorResultCaching option) {
+    this.option = option;
+  }
+
+  @Override
+  public String getPurpose() {
+    return "EXPERIMENTAL: enables generator result caching, for those generators that implement it";
+  }
+
+  @Override
+  public String getTag() {
+    return "-XenableGeneratorResultCaching";
+  }
+  
+  @Override
+  public boolean isUndocumented() {
+    return true;
+  }
+ 
+  @Override
+  public boolean setFlag() {
+    option.setGeneratorResultCachingEnabled(true);
+    return true;
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/OptionEnableGeneratorResultCaching.java b/dev/core/src/com/google/gwt/dev/util/arg/OptionEnableGeneratorResultCaching.java
new file mode 100644
index 0000000..ef06ee4
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/arg/OptionEnableGeneratorResultCaching.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2010 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.util.arg;
+
+/**
+ * Encapsulates a compiler option to enable generator result caching.
+ */
+public interface OptionEnableGeneratorResultCaching {
+  boolean isGeneratorResultCachingEnabled();
+  void setGeneratorResultCachingEnabled(boolean enabled);
+}
diff --git a/user/src/com/google/gwt/junit/JUnitShell.java b/user/src/com/google/gwt/junit/JUnitShell.java
index cc8f9b4..e692f0b 100644
--- a/user/src/com/google/gwt/junit/JUnitShell.java
+++ b/user/src/com/google/gwt/junit/JUnitShell.java
@@ -40,6 +40,7 @@
 import com.google.gwt.dev.util.arg.ArgHandlerDisableRunAsync;
 import com.google.gwt.dev.util.arg.ArgHandlerDraftCompile;
 import com.google.gwt.dev.util.arg.ArgHandlerEnableAssertions;
+import com.google.gwt.dev.util.arg.ArgHandlerEnableGeneratorResultCaching;
 import com.google.gwt.dev.util.arg.ArgHandlerExtraDir;
 import com.google.gwt.dev.util.arg.ArgHandlerGenDir;
 import com.google.gwt.dev.util.arg.ArgHandlerLocalWorkers;
@@ -186,6 +187,7 @@
       registerHandler(new ArgHandlerDisableCastChecking(options));
       registerHandler(new ArgHandlerDisableRunAsync(options));
       registerHandler(new ArgHandlerDraftCompile(options));
+      registerHandler(new ArgHandlerEnableGeneratorResultCaching(options));
       registerHandler(new ArgHandlerMaxPermsPerPrecompile(options));
       registerHandler(new ArgHandlerLocalWorkers(options));