Merging from releases/1.6@3739:3876

svn merge -r3739:HEAD \
  https://google-web-toolkit.googlecode.com/svn/releases/1.6 .

Extensive merge conflicts in JavaToJavaScriptCompiler were resolved by
disabling SOYC -- BobV will re-enable it with his upcoming commit.

A patch for StandardGeneratorContext is also included to avoid a build break,
which will be committed to releases/1.6 at the same time branch-info is
updated, and will be skipped in the revision for the next merge.



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@3877 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/Artifact.java b/dev/core/src/com/google/gwt/core/ext/linker/Artifact.java
index f5fe286..a54eaea 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/Artifact.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/Artifact.java
@@ -17,6 +17,8 @@
 
 import com.google.gwt.core.ext.Linker;
 
+import java.io.Serializable;
+
 /**
  * A base type for all artifacts relating to the link process. In order to
  * ensure stable output between runs of the compiler, Artifact types must
@@ -27,8 +29,9 @@
  *          to.
  */
 public abstract class Artifact<C extends Artifact<C>> implements
-    Comparable<Artifact<?>> {
-  private final Class<? extends Linker> linker;
+    Comparable<Artifact<?>>, Serializable {
+  private final String linkerName;
+  private transient Class<? extends Linker> linker;
 
   /**
    * Constructor.
@@ -37,6 +40,7 @@
    */
   protected Artifact(Class<? extends Linker> linker) {
     assert linker != null;
+    this.linkerName = linker.getName();
     this.linker = linker;
   }
 
@@ -65,6 +69,17 @@
    * Returns the Linker that created the Artifact.
    */
   public final Class<? extends Linker> getLinker() {
+    // linker is null when deserialized.
+    if (linker == null) {
+      try {
+        Class<?> clazz = Class.forName(linkerName, false,
+            Thread.currentThread().getContextClassLoader());
+        linker = clazz.asSubclass(Linker.class);
+      } catch (ClassNotFoundException e) {
+        // The class may not be available.
+        linker = Linker.class;
+      }
+    }
     return linker;
   }
 
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/ArtifactSet.java b/dev/core/src/com/google/gwt/core/ext/linker/ArtifactSet.java
index c04ad6d..96435bc 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/ArtifactSet.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/ArtifactSet.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.core.ext.linker;
 
+import java.io.Serializable;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
@@ -25,7 +26,7 @@
 /**
  * Provides stable ordering and de-duplication of artifacts.
  */
-public final class ArtifactSet implements SortedSet<Artifact<?>> {
+public final class ArtifactSet implements SortedSet<Artifact<?>>, Serializable {
 
   private SortedSet<Artifact<?>> treeSet = new TreeSet<Artifact<?>>();
 
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/GeneratedResource.java b/dev/core/src/com/google/gwt/core/ext/linker/GeneratedResource.java
index 28c9400..3bb8000 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/GeneratedResource.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/GeneratedResource.java
@@ -24,11 +24,13 @@
  * during the compilation process.
  */
 public abstract class GeneratedResource extends EmittedArtifact {
-  private final Class<? extends Generator> generatorType;
+  private final String generatorTypeName;
+  private transient Class<? extends Generator> generatorType;
 
   protected GeneratedResource(Class<? extends Linker> linkerType,
       Class<? extends Generator> generatorType, String partialPath) {
     super(linkerType, partialPath);
+    this.generatorTypeName = generatorType.getName();
     this.generatorType = generatorType;
   }
 
@@ -36,6 +38,17 @@
    * The type of Generator that created the resource.
    */
   public final Class<? extends Generator> getGenerator() {
+    // generatorType is null when deserialized.
+    if (generatorType == null) {
+      try {
+        Class<?> clazz = Class.forName(generatorTypeName, false,
+            Thread.currentThread().getContextClassLoader());
+        generatorType = clazz.asSubclass(Generator.class);
+      } catch (ClassNotFoundException e) {
+        // The class may not be available.
+        generatorType = Generator.class;
+      }
+    }
     return generatorType;
   }
 }
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardCompilationResult.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardCompilationResult.java
index 9a99727..e8cdb0d 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardCompilationResult.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardCompilationResult.java
@@ -15,13 +15,12 @@
  */
 package com.google.gwt.core.ext.linker.impl;
 
-import com.google.gwt.core.ext.TreeLogger;
-import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.linker.CompilationResult;
 import com.google.gwt.core.ext.linker.SelectionProperty;
 import com.google.gwt.dev.util.Util;
 
 import java.io.File;
+import java.io.Serializable;
 import java.lang.ref.SoftReference;
 import java.util.Collections;
 import java.util.Comparator;
@@ -37,11 +36,8 @@
  */
 public class StandardCompilationResult extends CompilationResult {
 
-  /**
-   * Smaller maps come before larger maps, then we compare the concatenation of
-   * every value.
-   */
-  public static final Comparator<SortedMap<SelectionProperty, String>> MAP_COMPARATOR = new Comparator<SortedMap<SelectionProperty, String>>() {
+  private static final class MapComparator implements
+      Comparator<SortedMap<SelectionProperty, String>>, Serializable {
     public int compare(SortedMap<SelectionProperty, String> arg0,
         SortedMap<SelectionProperty, String> arg1) {
       int diff = arg0.size() - arg1.size();
@@ -64,21 +60,25 @@
 
       return sb0.toString().compareTo(sb1.toString());
     }
-  };
+  }
+
+  /**
+   * Smaller maps come before larger maps, then we compare the concatenation of
+   * every value.
+   */
+  public static final Comparator<SortedMap<SelectionProperty, String>> MAP_COMPARATOR = new MapComparator();
 
   private final File cacheFile;
-  private SoftReference<String> js;
+  private transient SoftReference<String> js;
   private final SortedSet<SortedMap<SelectionProperty, String>> propertyValues = new TreeSet<SortedMap<SelectionProperty, String>>(
       MAP_COMPARATOR);
   private final String strongName;
 
-  public StandardCompilationResult(TreeLogger logger, String js,
-      String strongName, File cacheDir) throws UnableToCompleteException {
+  public StandardCompilationResult(String js, String strongName, File cacheFile) {
     super(StandardLinkerContext.class);
     this.js = new SoftReference<String>(js);
     this.strongName = strongName;
-    cacheFile = new File(cacheDir, strongName);
-    Util.writeStringAsFile(logger, cacheFile, js);
+    this.cacheFile = cacheFile;
   }
 
   /**
@@ -93,9 +93,16 @@
   }
 
   public String getJavaScript() {
-    String toReturn = js.get();
+    String toReturn = null;
+    if (js != null) {
+      toReturn = js.get();
+    }
     if (toReturn == null) {
       toReturn = Util.readFileAsString(cacheFile);
+      if (toReturn == null) {
+        throw new RuntimeException("Unexpectedly unable to read JS file '"
+            + cacheFile.getAbsolutePath() + "'");
+      }
       js = new SoftReference<String>(toReturn);
     }
     return toReturn;
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
index fbc9675..fc6e785 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
@@ -26,7 +26,6 @@
 import com.google.gwt.core.ext.linker.PublicResource;
 import com.google.gwt.core.ext.linker.SelectionProperty;
 import com.google.gwt.core.ext.linker.LinkerOrder.Order;
-import com.google.gwt.dev.GWTCompiler;
 import com.google.gwt.dev.cfg.BindingProperty;
 import com.google.gwt.dev.cfg.ModuleDef;
 import com.google.gwt.dev.cfg.Property;
@@ -112,63 +111,31 @@
   };
 
   private final ArtifactSet artifacts = new ArtifactSet();
-  private final File compilationsDir;
+
   private final SortedSet<ConfigurationProperty> configurationProperties;
   private final JJSOptions jjsOptions;
   private final List<Class<? extends Linker>> linkerClasses;
   private final Map<Class<? extends Linker>, String> linkerShortNames = new HashMap<Class<? extends Linker>, String>();
 
-  /**
-   * This is the output directory for private files.
-   */
-  private final File moduleAuxDir;
   private final String moduleFunctionName;
   private final String moduleName;
 
-  /**
-   * This is the root directory for the output of a particular module's
-   * compilation.
-   */
-  private final File moduleOutDir;
   private final Map<String, StandardSelectionProperty> propertiesByName = new HashMap<String, StandardSelectionProperty>();
   private final Map<String, StandardCompilationResult> resultsByStrongName = new HashMap<String, StandardCompilationResult>();
   private final SortedSet<SelectionProperty> selectionProperties;
 
   public StandardLinkerContext(TreeLogger logger, ModuleDef module,
-      File moduleOutDir, File generatorDir, JJSOptions jjsOptions) {
+      JJSOptions jjsOptions) {
     logger = logger.branch(TreeLogger.DEBUG,
         "Constructing StandardLinkerContext", null);
 
     this.jjsOptions = jjsOptions;
     this.moduleFunctionName = module.getFunctionName();
     this.moduleName = module.getName();
-    this.moduleOutDir = moduleOutDir;
     this.linkerClasses = new ArrayList<Class<? extends Linker>>(
         module.getActiveLinkers());
     linkerClasses.add(module.getActivePrimaryLinker());
 
-    if (moduleOutDir != null) {
-      compilationsDir = new File(moduleOutDir.getParentFile(),
-          GWTCompiler.GWT_COMPILER_DIR + File.separator + moduleName
-              + File.separator + "compilations");
-
-      Util.recursiveDelete(compilationsDir, true);
-      compilationsDir.mkdirs();
-      logger.log(TreeLogger.SPAM, "compilationsDir: "
-          + compilationsDir.getPath(), null);
-
-      this.moduleAuxDir = new File(moduleOutDir.getParentFile(), moduleName
-          + "-aux");
-      if (moduleAuxDir.exists()) {
-        Util.recursiveDelete(moduleAuxDir, false);
-      }
-      logger.log(TreeLogger.SPAM, "mouduleAuxDir: " + moduleAuxDir.getPath(),
-          null);
-    } else {
-      compilationsDir = null;
-      moduleAuxDir = null;
-    }
-
     for (Map.Entry<String, Class<? extends Linker>> entry : module.getLinkers().entrySet()) {
       linkerShortNames.put(entry.getValue(), entry.getKey());
     }
@@ -255,15 +222,19 @@
   /**
    * Gets or creates a CompilationResult for the given JavaScript program.
    */
-  public StandardCompilationResult getCompilation(TreeLogger logger, String js)
+  public StandardCompilationResult getCompilation(TreeLogger logger, File jsFile)
       throws UnableToCompleteException {
-
-    byte[] bytes = Util.getBytes(js);
+    byte[] bytes = Util.readFileAsBytes(jsFile);
+    if (bytes == null) {
+      logger.log(TreeLogger.ERROR, "Unable to read file '"
+          + jsFile.getAbsolutePath() + "'");
+      throw new UnableToCompleteException();
+    }
     String strongName = Util.computeStrongName(bytes);
     StandardCompilationResult result = resultsByStrongName.get(strongName);
     if (result == null) {
-      result = new StandardCompilationResult(logger, js, strongName,
-          compilationsDir);
+      result = new StandardCompilationResult(Util.toString(bytes), strongName,
+          jsFile);
       resultsByStrongName.put(result.getStrongName(), result);
       artifacts.add(result);
     }
@@ -295,119 +266,10 @@
     return propertiesByName.get(name);
   }
 
-  public boolean isOutputCompact() {
-    return jjsOptions.getOutput().shouldMinimize();
-  }
-
-  @Override
-  public ArtifactSet link(TreeLogger logger, LinkerContext context,
-      ArtifactSet artifacts) throws UnableToCompleteException {
-
-    logger = logger.branch(TreeLogger.INFO, "Linking compilation into "
-        + moduleOutDir.getPath(), null);
-
-    artifacts = invokeLinkerStack(logger);
-
-    for (EmittedArtifact artifact : artifacts.find(EmittedArtifact.class)) {
-      TreeLogger artifactLogger = logger.branch(TreeLogger.DEBUG,
-          "Emitting resource " + artifact.getPartialPath(), null);
-
-      File outFile;
-      if (artifact.isPrivate()) {
-        outFile = new File(getLinkerAuxDir(artifact.getLinker()),
-            artifact.getPartialPath());
-      } else {
-        outFile = new File(moduleOutDir, artifact.getPartialPath());
-      }
-
-      assert !outFile.exists() : "Attempted to overwrite " + outFile.getPath();
-      Util.copy(logger, artifact.getContents(artifactLogger), outFile);
-    }
-
-    return artifacts;
-  }
-
-  public String optimizeJavaScript(TreeLogger logger, String program)
-      throws UnableToCompleteException {
-    logger = logger.branch(TreeLogger.DEBUG, "Attempting to optimize JS", null);
-    JsParser parser = new JsParser();
-    Reader r = new StringReader(program);
-    JsProgram jsProgram = new JsProgram();
-    JsScope topScope = jsProgram.getScope();
-    JsName funcName = topScope.declareName(getModuleFunctionName());
-    funcName.setObfuscatable(false);
-
-    try {
-      SourceInfo sourceInfo = jsProgram.createSourceInfoSynthetic(
-          StandardLinkerContext.class, "Linker-derived JS");
-      parser.setSourceInfo(sourceInfo);
-      parser.parseInto(topScope, jsProgram.getGlobalBlock(), r, 1);
-    } catch (IOException e) {
-      logger.log(TreeLogger.ERROR, "Unable to parse JavaScript", e);
-      throw new UnableToCompleteException();
-    } catch (JsParserException e) {
-      logger.log(TreeLogger.ERROR, "Unable to parse JavaScript", e);
-      throw new UnableToCompleteException();
-    }
-
-    JsSymbolResolver.exec(jsProgram);
-    JsUnusedFunctionRemover.exec(jsProgram);
-
-    switch (jjsOptions.getOutput()) {
-      case OBFUSCATED:
-        /*
-         * We can't apply the regular JsStringInterner to the JsProgram that
-         * we've just created. In the normal case, the JsStringInterner adds an
-         * additional statement to the program's global JsBlock, however we
-         * don't know exactly what the form and structure of our JsProgram are,
-         * so we'll limit the scope of the modifications to each top-level
-         * function within the program.
-         */
-        TopFunctionStringInterner.exec(jsProgram);
-        JsObfuscateNamer.exec(jsProgram);
-        break;
-      case PRETTY:
-        // We don't intern strings in pretty mode to improve readability
-        JsPrettyNamer.exec(jsProgram);
-        break;
-      case DETAILED:
-        // As above with OBFUSCATED
-        TopFunctionStringInterner.exec(jsProgram);
-        JsVerboseNamer.exec(jsProgram);
-        break;
-      default:
-        throw new InternalCompilerException("Unknown output mode");
-    }
-
-    DefaultTextOutput out = new DefaultTextOutput(
-        jjsOptions.getOutput().shouldMinimize());
-    JsSourceGenerationVisitor v = new JsSourceGenerationVisitor(out);
-    v.accept(jsProgram);
-    return out.toString();
-  }
-
-  /**
-   * Creates a linker-specific subdirectory in the module's auxiliary output
-   * directory.
-   */
-  private File getLinkerAuxDir(Class<? extends Linker> linkerType) {
-    // The auxiliary directory is create lazily
-    if (!moduleAuxDir.exists()) {
-      moduleAuxDir.mkdirs();
-    }
-    assert linkerShortNames.containsKey(linkerType) : linkerType.getName()
-        + " unknown";
-    File toReturn = new File(moduleAuxDir, linkerShortNames.get(linkerType));
-    if (!toReturn.exists()) {
-      toReturn.mkdirs();
-    }
-    return toReturn;
-  }
-
   /**
    * Run the linker stack.
    */
-  private ArtifactSet invokeLinkerStack(TreeLogger logger)
+  public ArtifactSet invokeLinkerStack(TreeLogger logger)
       throws UnableToCompleteException {
     ArtifactSet workingArtifacts = new ArtifactSet(artifacts);
     Stack<Linker> linkerStack = new Stack<Linker>();
@@ -480,4 +342,115 @@
 
     return workingArtifacts;
   }
+
+  public boolean isOutputCompact() {
+    return jjsOptions.getOutput().shouldMinimize();
+  }
+
+  @Override
+  public ArtifactSet link(TreeLogger logger, LinkerContext context,
+      ArtifactSet artifacts) throws UnableToCompleteException {
+    throw new UnsupportedOperationException();
+  }
+
+  public String optimizeJavaScript(TreeLogger logger, String program)
+      throws UnableToCompleteException {
+    logger = logger.branch(TreeLogger.DEBUG, "Attempting to optimize JS", null);
+    JsParser parser = new JsParser();
+    Reader r = new StringReader(program);
+    JsProgram jsProgram = new JsProgram();
+    JsScope topScope = jsProgram.getScope();
+    JsName funcName = topScope.declareName(getModuleFunctionName());
+    funcName.setObfuscatable(false);
+
+    try {
+      SourceInfo sourceInfo = jsProgram.createSourceInfoSynthetic(
+          StandardLinkerContext.class, "Linker-derived JS");
+      parser.setSourceInfo(sourceInfo);
+      parser.parseInto(topScope, jsProgram.getGlobalBlock(), r, 1);
+    } catch (IOException e) {
+      logger.log(TreeLogger.ERROR, "Unable to parse JavaScript", e);
+      throw new UnableToCompleteException();
+    } catch (JsParserException e) {
+      logger.log(TreeLogger.ERROR, "Unable to parse JavaScript", e);
+      throw new UnableToCompleteException();
+    }
+
+    JsSymbolResolver.exec(jsProgram);
+    JsUnusedFunctionRemover.exec(jsProgram);
+
+    switch (jjsOptions.getOutput()) {
+      case OBFUSCATED:
+        /*
+         * We can't apply the regular JsStringInterner to the JsProgram that
+         * we've just created. In the normal case, the JsStringInterner adds an
+         * additional statement to the program's global JsBlock, however we
+         * don't know exactly what the form and structure of our JsProgram are,
+         * so we'll limit the scope of the modifications to each top-level
+         * function within the program.
+         */
+        TopFunctionStringInterner.exec(jsProgram);
+        JsObfuscateNamer.exec(jsProgram);
+        break;
+      case PRETTY:
+        // We don't intern strings in pretty mode to improve readability
+        JsPrettyNamer.exec(jsProgram);
+        break;
+      case DETAILED:
+        // As above with OBFUSCATED
+        TopFunctionStringInterner.exec(jsProgram);
+        JsVerboseNamer.exec(jsProgram);
+        break;
+      default:
+        throw new InternalCompilerException("Unknown output mode");
+    }
+
+    DefaultTextOutput out = new DefaultTextOutput(
+        jjsOptions.getOutput().shouldMinimize());
+    JsSourceGenerationVisitor v = new JsSourceGenerationVisitor(out);
+    v.accept(jsProgram);
+    return out.toString();
+  }
+
+  public void produceOutputDirectory(TreeLogger logger, ArtifactSet artifacts,
+      File moduleOutDir, File moduleAuxDir) throws UnableToCompleteException {
+
+    logger = logger.branch(TreeLogger.INFO, "Linking compilation into "
+        + moduleOutDir.getPath(), null);
+
+    for (EmittedArtifact artifact : artifacts.find(EmittedArtifact.class)) {
+      TreeLogger artifactLogger = logger.branch(TreeLogger.DEBUG,
+          "Emitting resource " + artifact.getPartialPath(), null);
+
+      File outFile;
+      if (artifact.isPrivate()) {
+        outFile = new File(getLinkerAuxDir(moduleAuxDir, artifact.getLinker()),
+            artifact.getPartialPath());
+      } else {
+        outFile = new File(moduleOutDir, artifact.getPartialPath());
+      }
+
+      assert !outFile.exists() : "Attempted to overwrite " + outFile.getPath();
+      Util.copy(logger, artifact.getContents(artifactLogger), outFile);
+    }
+  }
+
+  /**
+   * Creates a linker-specific subdirectory in the module's auxiliary output
+   * directory.
+   */
+  private File getLinkerAuxDir(File moduleAuxDir,
+      Class<? extends Linker> linkerType) {
+    // The auxiliary directory is create lazily
+    if (!moduleAuxDir.exists()) {
+      moduleAuxDir.mkdirs();
+    }
+    assert linkerShortNames.containsKey(linkerType) : linkerType.getName()
+        + " unknown";
+    File toReturn = new File(moduleAuxDir, linkerShortNames.get(linkerType));
+    if (!toReturn.exists()) {
+      toReturn.mkdirs();
+    }
+    return toReturn;
+  }
 }
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardPublicResource.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardPublicResource.java
index 401b1b8..2a1a0bd 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardPublicResource.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardPublicResource.java
@@ -19,13 +19,37 @@
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.linker.PublicResource;
 import com.google.gwt.dev.resource.Resource;
+import com.google.gwt.dev.util.Util;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.io.InputStream;
+import java.io.Serializable;
 
 /**
  * The standard implementation of {@link PublicResource}.
  */
 public class StandardPublicResource extends PublicResource {
+
+  /**
+   * Serializes a public resource via a snapshot of the content.
+   */
+  private static final class SerializedPublicResource extends PublicResource {
+    private final byte[] data;
+
+    protected SerializedPublicResource(String partialPath, byte[] data) {
+      super(StandardLinkerContext.class, partialPath);
+      this.data = data;
+    }
+
+    @Override
+    public InputStream getContents(TreeLogger logger)
+        throws UnableToCompleteException {
+      return new ByteArrayInputStream(data);
+    }
+  }
+
   private final Resource resource;
 
   public StandardPublicResource(String partialPath, Resource resource) {
@@ -38,4 +62,18 @@
       throws UnableToCompleteException {
     return resource.openContents();
   }
+
+  private Object writeReplace() {
+    if (resource instanceof Serializable) {
+      return this;
+    }
+    // Resource is not serializable, must replace myself.
+    try {
+      ByteArrayOutputStream baos = new ByteArrayOutputStream();
+      Util.copy(resource.openContents(), baos);
+      return new SerializedPublicResource(getPartialPath(), baos.toByteArray());
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
 }
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardSelectionProperty.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardSelectionProperty.java
index 14972c1..5f71786 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardSelectionProperty.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardSelectionProperty.java
@@ -40,8 +40,7 @@
       activeValue = null;
     }
     name = p.getName();
-    provider = p.getProvider() == null ? null
-        : p.getProvider().getBody().toSource();
+    provider = p.getProvider() == null ? null : p.getProvider().getBody();
     values = Collections.unmodifiableSortedSet(new TreeSet<String>(
         Arrays.asList(p.getDefinedValues())));
   }
diff --git a/dev/core/src/com/google/gwt/dev/CompilePerms.java b/dev/core/src/com/google/gwt/dev/CompilePerms.java
new file mode 100644
index 0000000..17b1042
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/CompilePerms.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.CompileTaskRunner.CompileTask;
+import com.google.gwt.dev.PermutationCompiler.ResultsHandler;
+import com.google.gwt.dev.jjs.UnifiedAst;
+import com.google.gwt.dev.jjs.JavaToJavaScriptCompiler;
+import com.google.gwt.dev.util.Util;
+import com.google.gwt.util.tools.ArgHandlerString;
+
+import java.io.File;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * Performs the first phase of compilation, generating the set of permutations
+ * to compile, and a ready-to-compile AST.
+ */
+public class CompilePerms {
+
+  /**
+   * Options for CompilePerms.
+   */
+  public interface CompilePermsOptions extends CompileTaskOptions, OptionPerms {
+  }
+
+  /**
+   * Handles options for which permutations to compile.
+   */
+  public interface OptionPerms {
+    /**
+     * Gets the ordered set of permutations to compile. Returns a zero-length
+     * array if all permutations should be compiled.
+     */
+    int[] getPermsToCompile();
+
+    /**
+     * Adds another permutation to compile.
+     */
+    void setPermsToCompile(int[] permsToCompile);
+  }
+
+  /**
+   * Argument handler for specifying the which perms to run.
+   */
+  protected static final class ArgHandlerPerms extends ArgHandlerString {
+    private final OptionPerms option;
+
+    public ArgHandlerPerms(OptionPerms option) {
+      this.option = option;
+    }
+
+    @Override
+    public String getPurpose() {
+      return "Comma-delimited list of 0-based permutations to compile";
+    }
+
+    @Override
+    public String getTag() {
+      return "-perms";
+    }
+
+    @Override
+    public String[] getTagArgs() {
+      return new String[] {"permlist"};
+    }
+
+    @Override
+    public boolean setString(String str) {
+      String[] split = str.split(",");
+      if (split.length < 1) {
+        System.err.println(getTag()
+            + " requires a comma-delimited list of integers");
+        return false;
+      }
+
+      SortedSet<Integer> permSet = new TreeSet<Integer>();
+      for (String item : split) {
+        try {
+          int value = Integer.parseInt(item);
+          if (value < 0) {
+            System.err.println(getTag() + " error: negative value '" + value
+                + "' is not allowed");
+            return false;
+          }
+          permSet.add(value);
+        } catch (NumberFormatException e) {
+          System.err.println(getTag()
+              + " requires a comma-delimited list of integers; '" + item
+              + "' is not an integer");
+          return false;
+        }
+      }
+      int[] permsToCompile = new int[permSet.size()];
+      int i = 0;
+      for (int perm : permSet) {
+        permsToCompile[i++] = perm;
+      }
+      option.setPermsToCompile(permsToCompile);
+      return true;
+    }
+  }
+  static class ArgProcessor extends Link.ArgProcessor {
+    public ArgProcessor(CompilePermsOptions options) {
+      super(options);
+      registerHandler(new ArgHandlerPerms(options));
+    }
+
+    @Override
+    protected String getName() {
+      return CompilePerms.class.getName();
+    }
+  }
+
+  /**
+   * Concrete class to implement all compiler options.
+   */
+  static class CompilePermsOptionsImpl extends CompileTaskOptionsImpl implements
+      CompilePermsOptions {
+
+    private int[] permsToCompile = new int[0];
+
+    public CompilePermsOptionsImpl() {
+    }
+
+    public CompilePermsOptionsImpl(CompilePermsOptions other) {
+      copyFrom(other);
+    }
+
+    public void copyFrom(CompilePermsOptions other) {
+      super.copyFrom(other);
+
+      setPermsToCompile(other.getPermsToCompile());
+    }
+
+    public int[] getPermsToCompile() {
+      return permsToCompile.clone();
+    }
+
+    public void setPermsToCompile(int[] permsToCompile) {
+      this.permsToCompile = permsToCompile.clone();
+    }
+  }
+
+  public static String compile(TreeLogger logger, Permutation permutation,
+      UnifiedAst unifiedAst) {
+    try {
+      return JavaToJavaScriptCompiler.compilePermutation(logger, unifiedAst,
+          permutation.getRebindAnswers());
+    } catch (UnableToCompleteException e) {
+      // We intentionally don't pass in the exception here since the real
+      // cause has been logged.
+      return null;
+    }
+  }
+
+  public static void main(String[] args) {
+    /*
+     * NOTE: main always exits with a call to System.exit to terminate any
+     * non-daemon threads that were started in Generators. Typically, this is to
+     * shutdown AWT related threads, since the contract for their termination is
+     * still implementation-dependent.
+     */
+    final CompilePermsOptions options = new CompilePermsOptionsImpl();
+    if (new ArgProcessor(options).processArgs(args)) {
+      CompileTask task = new CompileTask() {
+        public boolean run(TreeLogger logger) throws UnableToCompleteException {
+          return new CompilePerms(options).run(logger);
+        }
+      };
+      if (CompileTaskRunner.runWithAppropriateLogger(options, task)) {
+        // Exit w/ success code.
+        System.exit(0);
+      }
+    }
+    // Exit w/ non-success code.
+    System.exit(1);
+  }
+
+  /**
+   * Return the filename corresponding to the given permutation number,
+   * one-based.
+   */
+  static File makePermFilename(File compilerWorkDir, int permNumber) {
+    return new File(compilerWorkDir, "permutation-" + permNumber + ".js");
+  }
+
+  private final CompilePermsOptionsImpl options;
+
+  public CompilePerms(CompilePermsOptions options) {
+    this.options = new CompilePermsOptionsImpl(options);
+  }
+
+  public boolean run(TreeLogger logger) throws UnableToCompleteException {
+    File precompilationFile = new File(options.getCompilerWorkDir(),
+        Precompile.PRECOMPILATION_FILENAME);
+    if (!precompilationFile.exists()) {
+      logger.log(TreeLogger.ERROR, "File not found '"
+          + precompilationFile.getAbsolutePath()
+          + "'; please run Precompile first");
+      return false;
+    }
+    Precompilation precompilation;
+    try {
+      /*
+       * TODO: don't bother deserializing the generated artifacts.
+       */
+      precompilation = Util.readFileAsObject(precompilationFile,
+          Precompilation.class);
+    } catch (ClassNotFoundException e) {
+      logger.log(TreeLogger.ERROR, "Unable to deserialize '"
+          + precompilationFile.getAbsolutePath() + "'", e);
+      return false;
+    }
+    Permutation[] perms = precompilation.getPermutations();
+    UnifiedAst unifiedAst = precompilation.getUnifiedAst();
+    int[] permsToRun = options.getPermsToCompile();
+
+    if (permsToRun.length == 0) {
+      // Compile them all.
+      permsToRun = new int[perms.length];
+      for (int i = 0; i < permsToRun.length; ++i) {
+        permsToRun[i] = i;
+      }
+    } else {
+      // Range check the supplied perms.
+      for (int permToRun : permsToRun) {
+        if (permToRun >= perms.length) {
+          logger.log(TreeLogger.ERROR, "The specified perm number '"
+              + permToRun + "' is too big; the maximum value is "
+              + (perms.length - 1) + "'");
+          return false;
+        }
+      }
+    }
+
+    final TreeLogger branch = logger.branch(TreeLogger.INFO, "Compiling "
+        + permsToRun.length + " permutations");
+    PermutationCompiler multiThread = new PermutationCompiler(branch,
+        unifiedAst, perms, permsToRun);
+    multiThread.go(new ResultsHandler() {
+      public void addResult(Permutation permutation, int permNum, String js)
+          throws UnableToCompleteException {
+        Util.writeStringAsFile(branch, makePermFilename(
+            options.getCompilerWorkDir(), permNum), js);
+      }
+    });
+    branch.log(TreeLogger.INFO, "Permutation compile succeeded");
+    return true;
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/CompileTaskOptions.java b/dev/core/src/com/google/gwt/dev/CompileTaskOptions.java
new file mode 100644
index 0000000..dd345fc
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/CompileTaskOptions.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev;
+
+import com.google.gwt.dev.util.arg.OptionGuiLogger;
+import com.google.gwt.dev.util.arg.OptionLogLevel;
+import com.google.gwt.dev.util.arg.OptionModuleName;
+import com.google.gwt.dev.util.arg.OptionOutDir;
+
+/**
+ * A common set of options for all compile tasks.
+ */
+public interface CompileTaskOptions extends OptionGuiLogger, OptionModuleName,
+    OptionLogLevel, OptionOutDir {
+}
diff --git a/dev/core/src/com/google/gwt/dev/CompileTaskOptionsImpl.java b/dev/core/src/com/google/gwt/dev/CompileTaskOptionsImpl.java
new file mode 100644
index 0000000..b2f0295
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/CompileTaskOptionsImpl.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev;
+
+import com.google.gwt.core.ext.TreeLogger.Type;
+
+import java.io.File;
+
+/**
+ * Concrete class to implement compiler task options.
+ */
+class CompileTaskOptionsImpl implements CompileTaskOptions {
+
+  public static final String GWT_COMPILER_DIR = ".gwt-tmp" + File.separatorChar
+      + "compiler";
+
+  private File compilerWorkDir;
+  private Type logLevel;
+  private String moduleName;
+  private File outDir;
+  private boolean useGuiLogger;
+
+  public CompileTaskOptionsImpl() {
+  }
+
+  public CompileTaskOptionsImpl(CompileTaskOptions other) {
+    copyFrom(other);
+  }
+
+  public void copyFrom(CompileTaskOptions other) {
+    setLogLevel(other.getLogLevel());
+    setModuleName(other.getModuleName());
+    setOutDir(other.getOutDir());
+    setUseGuiLogger(other.isUseGuiLogger());
+  }
+
+  public File getCompilerWorkDir() {
+    if (compilerWorkDir == null) {
+      compilerWorkDir = new File(getOutDir(), GWT_COMPILER_DIR + File.separator
+          + moduleName);
+    }
+    return compilerWorkDir;
+  }
+
+  public Type getLogLevel() {
+    return logLevel;
+  }
+
+  public String getModuleName() {
+    return moduleName;
+  }
+
+  public File getOutDir() {
+    return outDir;
+  }
+
+  public boolean isUseGuiLogger() {
+    return useGuiLogger;
+  }
+
+  public void setLogLevel(Type logLevel) {
+    this.logLevel = logLevel;
+  }
+
+  public void setModuleName(String moduleName) {
+    this.moduleName = moduleName;
+  }
+
+  public void setOutDir(File outDir) {
+    this.outDir = outDir;
+  }
+
+  public void setUseGuiLogger(boolean useGuiLogger) {
+    this.useGuiLogger = useGuiLogger;
+  }
+
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/dev/CompileTaskRunner.java b/dev/core/src/com/google/gwt/dev/CompileTaskRunner.java
new file mode 100644
index 0000000..a5efcfb
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/CompileTaskRunner.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.util.log.AbstractTreeLogger;
+import com.google.gwt.dev.util.log.DetachedTreeLoggerWindow;
+import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
+
+/**
+ * Used to run compiler tasks with the appropriate logger.
+ */
+public class CompileTaskRunner {
+
+  /**
+   * A task to run with a logger based on options.
+   */
+  public interface CompileTask {
+    boolean run(TreeLogger logger) throws UnableToCompleteException;
+  }
+
+  /**
+   * Runs the main action with an appropriate logger. If a gui-based TreeLogger
+   * is used, this method will not return until its window is closed by the
+   * user.
+   */
+  public static boolean runWithAppropriateLogger(CompileTaskOptions options,
+      final CompileTask task) {
+    // Set any platform specific system properties.
+    BootStrapPlatform.applyPlatformHacks();
+
+    if (options.isUseGuiLogger()) {
+      // Initialize a tree logger window.
+      DetachedTreeLoggerWindow loggerWindow = DetachedTreeLoggerWindow.getInstance(
+          "Build Output for " + options.getModuleName(), 800, 600, true);
+
+      // Eager AWT initialization for OS X to ensure safe coexistence with SWT.
+      BootStrapPlatform.maybeInitializeAWT();
+
+      final AbstractTreeLogger logger = loggerWindow.getLogger();
+      logger.setMaxDetail(options.getLogLevel());
+      final boolean[] success = new boolean[1];
+
+      // Compiler will be spawned onto a second thread, UI thread for tree
+      // logger will remain on the main.
+      Thread compilerThread = new Thread(new Runnable() {
+        public void run() {
+          success[0] = doRun(logger, task);
+        }
+      });
+
+      compilerThread.setName("GWTCompiler Thread");
+      compilerThread.start();
+      loggerWindow.run();
+
+      // Even if the tree logger window is closed, we wait for the compiler
+      // to finish.
+      waitForThreadToTerminate(compilerThread);
+
+      return success[0];
+    } else {
+      PrintWriterTreeLogger logger = new PrintWriterTreeLogger();
+      logger.setMaxDetail(options.getLogLevel());
+      return doRun(logger, task);
+    }
+  }
+
+  private static boolean doRun(TreeLogger logger, CompileTask task) {
+    try {
+      return task.run(logger);
+    } catch (UnableToCompleteException e) {
+      // Assume logged.
+    } catch (Throwable e) {
+      logger.log(TreeLogger.ERROR, "Unexpected", e);
+    }
+    return false;
+  }
+
+  /**
+   * Waits for a thread to terminate before it returns. This method is a
+   * non-cancellable task, in that it will defer thread interruption until it is
+   * done.
+   * 
+   * @param godot the thread that is being waited on.
+   */
+  private static void waitForThreadToTerminate(final Thread godot) {
+    // Goetz pattern for non-cancellable tasks.
+    // http://www-128.ibm.com/developerworks/java/library/j-jtp05236.html
+    boolean isInterrupted = false;
+    try {
+      while (true) {
+        try {
+          godot.join();
+          return;
+        } catch (InterruptedException e) {
+          isInterrupted = true;
+        }
+      }
+    } finally {
+      if (isInterrupted) {
+        Thread.currentThread().interrupt();
+      }
+    }
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/CompilerOptions.java b/dev/core/src/com/google/gwt/dev/CompilerOptions.java
new file mode 100644
index 0000000..85b4afc
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/CompilerOptions.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev;
+
+import com.google.gwt.dev.jjs.JJSOptions;
+import com.google.gwt.dev.util.arg.OptionGenDir;
+import com.google.gwt.dev.util.arg.OptionValidateOnly;
+
+/**
+ * The complete set of options for the GWT compiler.
+ */
+public interface CompilerOptions extends JJSOptions, CompileTaskOptions,
+    OptionGenDir, OptionValidateOnly {
+}
diff --git a/dev/core/src/com/google/gwt/dev/CompilerOptionsImpl.java b/dev/core/src/com/google/gwt/dev/CompilerOptionsImpl.java
new file mode 100644
index 0000000..dcc29bf
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/CompilerOptionsImpl.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev;
+
+import com.google.gwt.core.ext.TreeLogger.Type;
+import com.google.gwt.dev.jjs.JJSOptionsImpl;
+
+import java.io.File;
+
+/**
+ * Concrete class to implement all compiler options.
+ */
+public class CompilerOptionsImpl extends JJSOptionsImpl implements
+    CompilerOptions {
+  private File genDir;
+  private Type logLevel;
+  private String moduleName;
+  private File outDir;
+  private boolean useGuiLogger;
+  private boolean validateOnly;
+
+  public CompilerOptionsImpl() {
+  }
+
+  public CompilerOptionsImpl(CompilerOptions other) {
+    copyFrom(other);
+  }
+
+  public void copyFrom(CompilerOptions other) {
+    super.copyFrom(other);
+    setGenDir(other.getGenDir());
+    setLogLevel(other.getLogLevel());
+    setOutDir(other.getOutDir());
+    setUseGuiLogger(other.isUseGuiLogger());
+    setValidateOnly(false);
+  }
+
+  public File getGenDir() {
+    return genDir;
+  }
+
+  public Type getLogLevel() {
+    return logLevel;
+  }
+
+  public String getModuleName() {
+    return moduleName;
+  }
+
+  public File getOutDir() {
+    return outDir;
+  }
+
+  public boolean isUseGuiLogger() {
+    return useGuiLogger;
+  }
+
+  public boolean isValidateOnly() {
+    return validateOnly;
+  }
+
+  public void setGenDir(File genDir) {
+    this.genDir = genDir;
+  }
+
+  public void setLogLevel(Type logLevel) {
+    this.logLevel = logLevel;
+  }
+
+  public void setModuleName(String moduleName) {
+    this.moduleName = moduleName;
+  }
+
+  public void setOutDir(File outDir) {
+    this.outDir = outDir;
+  }
+
+  public void setUseGuiLogger(boolean useGuiLogger) {
+    this.useGuiLogger = useGuiLogger;
+  }
+
+  public void setValidateOnly(boolean validateOnly) {
+    this.validateOnly = validateOnly;
+  }
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/dev/GWTCompiler.java b/dev/core/src/com/google/gwt/dev/GWTCompiler.java
index b11b457..3f89bfd 100644
--- a/dev/core/src/com/google/gwt/dev/GWTCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/GWTCompiler.java
@@ -17,173 +17,26 @@
 
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
-import com.google.gwt.core.ext.TreeLogger.Type;
-import com.google.gwt.core.ext.linker.ArtifactSet;
-import com.google.gwt.core.ext.linker.impl.StandardLinkerContext;
-import com.google.gwt.dev.cfg.BindingProperty;
-import com.google.gwt.dev.cfg.ConfigurationProperty;
-import com.google.gwt.dev.cfg.ModuleDef;
-import com.google.gwt.dev.cfg.ModuleDefLoader;
-import com.google.gwt.dev.cfg.PropertyPermutations;
-import com.google.gwt.dev.cfg.Rules;
-import com.google.gwt.dev.cfg.StaticPropertyOracle;
-import com.google.gwt.dev.javac.CompilationState;
-import com.google.gwt.dev.javac.CompilationUnit;
-import com.google.gwt.dev.jdt.RebindOracle;
-import com.google.gwt.dev.jdt.RebindPermutationOracle;
-import com.google.gwt.dev.jdt.WebModeCompilerFrontEnd;
-import com.google.gwt.dev.jjs.JJSOptions;
-import com.google.gwt.dev.jjs.JavaToJavaScriptCompiler;
-import com.google.gwt.dev.jjs.JsOutputOption;
-import com.google.gwt.dev.shell.StandardRebindOracle;
-import com.google.gwt.dev.util.PerfLogger;
-import com.google.gwt.dev.util.Util;
-import com.google.gwt.dev.util.arg.ArgHandlerGenDir;
-import com.google.gwt.dev.util.arg.ArgHandlerLogLevel;
-import com.google.gwt.dev.util.arg.ArgHandlerScriptStyle;
-import com.google.gwt.dev.util.arg.ArgHandlerTreeLoggerFlag;
-import com.google.gwt.dev.util.log.AbstractTreeLogger;
-import com.google.gwt.dev.util.log.DetachedTreeLoggerWindow;
-import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
-import com.google.gwt.util.tools.ArgHandlerDisableAggressiveOptimization;
-import com.google.gwt.util.tools.ArgHandlerEnableAssertions;
-import com.google.gwt.util.tools.ArgHandlerExtra;
-import com.google.gwt.util.tools.ArgHandlerFlag;
-import com.google.gwt.util.tools.ArgHandlerOutDir;
-import com.google.gwt.util.tools.ArgHandlerString;
-import com.google.gwt.util.tools.ToolBase;
-
-import java.io.File;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.SortedSet;
+import com.google.gwt.dev.CompilePerms.CompilePermsOptionsImpl;
+import com.google.gwt.dev.CompileTaskRunner.CompileTask;
+import com.google.gwt.dev.Precompile.CompilerOptionsImpl;
 
 /**
  * The main executable entry point for the GWT Java to JavaScript compiler.
  */
-public class GWTCompiler extends ToolBase {
+public class GWTCompiler {
 
-  private class ArgHandlerModuleName extends ArgHandlerExtra {
-
-    @Override
-    public boolean addExtraArg(String arg) {
-      setModuleName(arg);
-      return true;
+  static final class ArgProcessor extends Precompile.ArgProcessor {
+    public ArgProcessor(CompilerOptions options) {
+      super(options);
     }
 
     @Override
-    public String getPurpose() {
-      return "Specifies the name of the module to compile";
-    }
-
-    @Override
-    public String[] getTagArgs() {
-      return new String[] {"module"};
-    }
-
-    @Override
-    public boolean isRequired() {
-      return true;
+    protected String getName() {
+      return GWTCompiler.class.getName();
     }
   }
 
-  private class ArgHandlerStoryOfYourCompile extends ArgHandlerString {
-    @Override
-    public String getPurpose() {
-      return "Generate the story of your compile";
-    }
-
-    @Override
-    public String getTag() {
-      return "-soyc";
-    }
-
-    @Override
-    public String[] getTagArgs() {
-      return new String[] {"/path/to/report/dir"};
-    }
-
-    @Override
-    public boolean setString(String str) {
-      jjsOptions.setSoycOutputDir(str);
-      return true;
-    }
-  }
-
-  /**
-   * Argument handler for making the compiler run in "validation" mode.
-   */
-  private class ArgHandlerValidateOnlyFlag extends ArgHandlerFlag {
-
-    public String getPurpose() {
-      return "Validate all source code, but do not compile";
-    }
-
-    public String getTag() {
-      return "-validateOnly";
-    }
-
-    public boolean setFlag() {
-      jjsOptions.setValidateOnly(true);
-      return true;
-    }
-  }
-
-  private class DistillerRebindPermutationOracle implements
-      RebindPermutationOracle {
-
-    private StaticPropertyOracle[] propertyOracles;
-    private RebindOracle[] rebindOracles;
-
-    public DistillerRebindPermutationOracle(ArtifactSet generatorArtifacts,
-        PropertyPermutations perms) {
-      propertyOracles = new StaticPropertyOracle[perms.size()];
-      rebindOracles = new RebindOracle[perms.size()];
-      BindingProperty[] orderedProps = perms.getOrderedProperties();
-      SortedSet<ConfigurationProperty> configPropSet = module.getProperties().getConfigurationProperties();
-      ConfigurationProperty[] configProps = configPropSet.toArray(new ConfigurationProperty[configPropSet.size()]);
-      for (int i = 0; i < rebindOracles.length; ++i) {
-        String[] orderedPropValues = perms.getOrderedPropertyValues(i);
-        propertyOracles[i] = new StaticPropertyOracle(orderedProps,
-            orderedPropValues, configProps);
-        rebindOracles[i] = new StandardRebindOracle(compilationState,
-            propertyOracles[i], module, rules, genDir, generatorResourcesDir,
-            generatorArtifacts);
-      }
-    }
-
-    public String[] getAllPossibleRebindAnswers(TreeLogger logger,
-        String requestTypeName) throws UnableToCompleteException {
-
-      String msg = "Computing all possible rebind results for '"
-          + requestTypeName + "'";
-      logger = logger.branch(TreeLogger.DEBUG, msg, null);
-
-      Set<String> answers = new HashSet<String>();
-
-      for (RebindOracle rebindOracle : rebindOracles) {
-        String resultTypeName = rebindOracle.rebind(logger, requestTypeName);
-        answers.add(resultTypeName);
-      }
-      return Util.toArray(String.class, answers);
-    }
-
-    public int getPermuationCount() {
-      return rebindOracles.length;
-    }
-
-    public StaticPropertyOracle getPropertyOracle(int permNumber) {
-      return propertyOracles[permNumber];
-    }
-
-    public RebindOracle getRebindOracle(int permNumber) {
-      return rebindOracles[permNumber];
-    }
-  }
-
-  public static final String GWT_COMPILER_DIR = ".gwt-tmp" + File.separatorChar
-      + "compiler";
-
   public static void main(String[] args) {
     /*
      * NOTE: main always exits with a call to System.exit to terminate any
@@ -191,9 +44,14 @@
      * shutdown AWT related threads, since the contract for their termination is
      * still implementation-dependent.
      */
-    GWTCompiler compiler = new GWTCompiler();
-    if (compiler.processArgs(args)) {
-      if (compiler.run()) {
+    final CompilerOptions options = new CompilerOptionsImpl();
+    if (new ArgProcessor(options).processArgs(args)) {
+      CompileTask task = new CompileTask() {
+        public boolean run(TreeLogger logger) throws UnableToCompleteException {
+          return new GWTCompiler(options).run(logger);
+        }
+      };
+      if (CompileTaskRunner.runWithAppropriateLogger(options, task)) {
         // Exit w/ success code.
         System.exit(0);
       }
@@ -202,305 +60,34 @@
     System.exit(1);
   }
 
-  private CompilationState compilationState;
+  private final CompilerOptionsImpl options;
 
-  private File genDir;
-
-  private File generatorResourcesDir;
-
-  private JJSOptions jjsOptions = new JJSOptions();
-
-  private Type logLevel;
-
-  private ModuleDef module;
-
-  private String moduleName;
-
-  private File outDir;
-
-  private Rules rules;
-
-  private boolean useGuiLogger;
-
-  public GWTCompiler() {
-    registerHandler(new ArgHandlerLogLevel() {
-      @Override
-      public void setLogLevel(Type level) {
-        logLevel = level;
-      }
-    });
-
-    registerHandler(new ArgHandlerGenDir() {
-      @Override
-      public void setDir(File dir) {
-        genDir = dir;
-      }
-    });
-
-    registerHandler(new ArgHandlerOutDir() {
-      @Override
-      public void setDir(File dir) {
-        outDir = dir;
-      }
-    });
-
-    registerHandler(new ArgHandlerTreeLoggerFlag() {
-      @Override
-      public boolean setFlag() {
-        useGuiLogger = true;
-        return true;
-      }
-    });
-
-    registerHandler(new ArgHandlerModuleName());
-
-    registerHandler(new ArgHandlerScriptStyle(jjsOptions));
-
-    registerHandler(new ArgHandlerStoryOfYourCompile());
-
-    registerHandler(new ArgHandlerEnableAssertions(jjsOptions));
-
-    registerHandler(new ArgHandlerDisableAggressiveOptimization() {
-      @Override
-      public boolean setFlag() {
-        GWTCompiler.this.setAggressivelyOptimize(false);
-        return true;
-      }
-    });
-
-    registerHandler(new ArgHandlerValidateOnlyFlag());
+  public GWTCompiler(CompilerOptions options) {
+    this.options = new CompilerOptionsImpl(options);
   }
 
-  public void distill(TreeLogger logger, ModuleDef moduleDef)
-      throws UnableToCompleteException {
-    this.module = moduleDef;
-    this.compilationState = moduleDef.getCompilationState();
-
-    // Set up all the initial state.
-    checkModule(logger);
-
-    // Place generated resources inside the out dir as a sibling to the module
-    generatorResourcesDir = new File(outDir, GWT_COMPILER_DIR + File.separator
-        + moduleDef.getName() + File.separator + "generated");
-
-    // Tweak the output directory so that output lives under the module name.
-    outDir = new File(outDir, module.getName());
-
-    // Clean the outDir.
-    Util.recursiveDelete(outDir, true);
-
-    // Clean out the generated resources directory and/or create it.
-    Util.recursiveDelete(generatorResourcesDir, true);
-    generatorResourcesDir.mkdirs();
-
-    // TODO: All JDT checks now before even building TypeOracle?
-    compilationState.compile(logger);
-
-    rules = module.getRules();
-    String[] declEntryPts;
-    if (jjsOptions.isValidateOnly()) {
-      // TODO: revisit this.. do we even need to run JJS?
-      logger.log(TreeLogger.INFO, "Validating compilation " + module.getName(),
-          null);
-      // Pretend that every single compilation unit is an entry point.
-      Set<CompilationUnit> compilationUnits = compilationState.getCompilationUnits();
-      declEntryPts = new String[compilationUnits.size()];
-      int i = 0;
-      for (CompilationUnit unit : compilationUnits) {
-        declEntryPts[i++] = unit.getTypeName();
-      }
+  public boolean run(TreeLogger logger) throws UnableToCompleteException {
+    if (options.isValidateOnly()) {
+      return new Precompile(options).run(logger);
     } else {
-      logger.log(TreeLogger.INFO, "Compiling module " + module.getName(), null);
-      // Use the real entry points.
-      declEntryPts = module.getEntryPointTypeNames();
-    }
-
-    ArtifactSet generatorArtifacts = new ArtifactSet();
-    DistillerRebindPermutationOracle rpo = new DistillerRebindPermutationOracle(
-        generatorArtifacts, new PropertyPermutations(module.getProperties()));
-
-    WebModeCompilerFrontEnd frontEnd = new WebModeCompilerFrontEnd(
-        compilationState, rpo);
-    JavaToJavaScriptCompiler jjs = new JavaToJavaScriptCompiler(logger,
-        frontEnd, declEntryPts, jjsOptions);
-
-    if (jjsOptions.isValidateOnly()) {
-      logger.log(TreeLogger.INFO, "Validation succeeded", null);
-      return;
-    }
-
-    StandardLinkerContext linkerContext = new StandardLinkerContext(logger,
-        module, outDir, generatorResourcesDir, jjsOptions);
-    compilePermutations(logger, jjs, rpo, linkerContext);
-
-    logger.log(TreeLogger.INFO, "Compilation succeeded", null);
-    linkerContext.addOrReplaceArtifacts(generatorArtifacts);
-    linkerContext.link(logger, linkerContext, null);
-  }
-
-  public File getGenDir() {
-    return genDir;
-  }
-
-  public Type getLogLevel() {
-    return logLevel;
-  }
-
-  public String getModuleName() {
-    return moduleName;
-  }
-
-  public boolean getUseGuiLogger() {
-    return useGuiLogger;
-  }
-
-  public void setAggressivelyOptimize(boolean aggressive) {
-    jjsOptions.setAggressivelyOptimize(aggressive);
-  }
-
-  public void setCompilerOptions(JJSOptions options) {
-    jjsOptions.copyFrom(options);
-  }
-
-  public void setGenDir(File dir) {
-    genDir = dir;
-  }
-
-  public void setLogLevel(Type level) {
-    this.logLevel = level;
-  }
-
-  public void setModuleName(String name) {
-    moduleName = name;
-  }
-
-  public void setOutDir(File outDir) {
-    this.outDir = outDir;
-  }
-
-  public void setStyleDetailed() {
-    jjsOptions.setOutput(JsOutputOption.DETAILED);
-  }
-
-  public void setStyleObfuscated() {
-    jjsOptions.setOutput(JsOutputOption.OBFUSCATED);
-  }
-
-  public void setStylePretty() {
-    jjsOptions.setOutput(JsOutputOption.PRETTY);
-  }
-
-  /**
-   * Ensure the module has at least one entry point (except in validation mode).
-   */
-  private void checkModule(TreeLogger logger) throws UnableToCompleteException {
-    if (!jjsOptions.isValidateOnly()
-        && module.getEntryPointTypeNames().length == 0) {
-      logger.log(TreeLogger.ERROR, "Module has no entry points defined", null);
-      throw new UnableToCompleteException();
-    }
-  }
-
-  private void compilePermutations(TreeLogger logger,
-      JavaToJavaScriptCompiler jjs, DistillerRebindPermutationOracle rpo,
-      StandardLinkerContext linkerContext) throws UnableToCompleteException {
-
-    int permCount = rpo.getPermuationCount();
-    PerfLogger.start("Compiling " + permCount + " permutations");
-    Permutation[] perms = new Permutation[permCount];
-    for (int i = 0; i < permCount; ++i) {
-      perms[i] = new Permutation(i, rpo.getRebindOracle(i),
-          rpo.getPropertyOracle(i));
-    }
-    PermutationCompiler permCompiler = new PermutationCompiler(logger, jjs,
-        perms);
-    permCompiler.go(linkerContext);
-  }
-
-  /**
-   * Runs the compiler. If a gui-based TreeLogger is used, this method will not
-   * return until its window is closed by the user.
-   * 
-   * @return success from the compiler, <code>true</code> if the compile
-   *         completed without errors, <code>false</code> otherwise.
-   */
-  private boolean run() {
-    // Set any platform specific system properties.
-    BootStrapPlatform.applyPlatformHacks();
-
-    if (useGuiLogger) {
-      // Initialize a tree logger window.
-      DetachedTreeLoggerWindow loggerWindow = DetachedTreeLoggerWindow.getInstance(
-          "Build Output for " + moduleName, 800, 600, true);
-
-      // Eager AWT initialization for OS X to ensure safe coexistence with SWT.
-      BootStrapPlatform.maybeInitializeAWT();
-
-      final AbstractTreeLogger logger = loggerWindow.getLogger();
-      final boolean[] success = new boolean[1];
-
-      // Compiler will be spawned onto a second thread, UI thread for tree
-      // logger will remain on the main.
-      Thread compilerThread = new Thread(new Runnable() {
-        public void run() {
-          success[0] = GWTCompiler.this.run(logger);
+      logger = logger.branch(TreeLogger.INFO, "Compiling module "
+          + options.getModuleName());
+      if (new Precompile(options).run(logger)) {
+        /*
+         * TODO: use the in-memory result of Precompile to run CompilePerms
+         * instead of serializing through the file system.
+         */
+        CompilePermsOptionsImpl permsOptions = new CompilePermsOptionsImpl();
+        permsOptions.copyFrom(options);
+        if (new CompilePerms(permsOptions).run(logger)) {
+          if (new Link(options).run(logger)) {
+            logger.log(TreeLogger.INFO, "Compilation succeeded");
+            return true;
+          }
         }
-      });
-
-      compilerThread.setName("GWTCompiler Thread");
-      compilerThread.start();
-      loggerWindow.run();
-
-      // Even if the tree logger window is closed, we wait for the compiler
-      // to finish.
-      waitForThreadToTerminate(compilerThread);
-
-      return success[0];
-    } else {
-      return run(new PrintWriterTreeLogger());
-    }
-  }
-
-  private boolean run(AbstractTreeLogger logger) {
-    try {
-      logger.setMaxDetail(logLevel);
-
-      ModuleDef moduleDef = ModuleDefLoader.loadFromClassPath(logger,
-          moduleName);
-      distill(logger, moduleDef);
-      return true;
-    } catch (UnableToCompleteException e) {
-      // We intentionally don't pass in the exception here since the real
-      // cause has been logged.
-      logger.log(TreeLogger.ERROR, "Build failed", null);
+      }
+      logger.log(TreeLogger.ERROR, "Compilation failed");
       return false;
     }
   }
-
-  /**
-   * Waits for a thread to terminate before it returns. This method is a
-   * non-cancellable task, in that it will defer thread interruption until it is
-   * done.
-   * 
-   * @param godot the thread that is being waited on.
-   */
-  private void waitForThreadToTerminate(final Thread godot) {
-    // Goetz pattern for non-cancellable tasks.
-    // http://www-128.ibm.com/developerworks/java/library/j-jtp05236.html
-    boolean isInterrupted = false;
-    try {
-      while (true) {
-        try {
-          godot.join();
-          return;
-        } catch (InterruptedException e) {
-          isInterrupted = true;
-        }
-      }
-    } finally {
-      if (isInterrupted) {
-        Thread.currentThread().interrupt();
-      }
-    }
-  }
 }
diff --git a/dev/core/src/com/google/gwt/dev/GWTShell.java b/dev/core/src/com/google/gwt/dev/GWTShell.java
index 663a4e8..7a56d3d 100644
--- a/dev/core/src/com/google/gwt/dev/GWTShell.java
+++ b/dev/core/src/com/google/gwt/dev/GWTShell.java
@@ -19,9 +19,9 @@
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.TreeLogger.Type;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.dev.Precompile.CompilerOptionsImpl;
 import com.google.gwt.dev.cfg.ModuleDef;
 import com.google.gwt.dev.cfg.ModuleDefLoader;
-import com.google.gwt.dev.jjs.JJSOptions;
 import com.google.gwt.dev.shell.BrowserWidget;
 import com.google.gwt.dev.shell.BrowserWidgetHost;
 import com.google.gwt.dev.shell.BrowserWidgetHostChecker;
@@ -32,15 +32,15 @@
 import com.google.gwt.dev.shell.ShellModuleSpaceHost;
 import com.google.gwt.dev.shell.tomcat.EmbeddedTomcatServer;
 import com.google.gwt.dev.util.PerfLogger;
+import com.google.gwt.dev.util.arg.ArgHandlerDisableAggressiveOptimization;
+import com.google.gwt.dev.util.arg.ArgHandlerEnableAssertions;
 import com.google.gwt.dev.util.arg.ArgHandlerGenDir;
 import com.google.gwt.dev.util.arg.ArgHandlerLogLevel;
+import com.google.gwt.dev.util.arg.ArgHandlerOutDir;
 import com.google.gwt.dev.util.arg.ArgHandlerScriptStyle;
 import com.google.gwt.dev.util.log.AbstractTreeLogger;
-import com.google.gwt.util.tools.ArgHandlerDisableAggressiveOptimization;
-import com.google.gwt.util.tools.ArgHandlerEnableAssertions;
 import com.google.gwt.util.tools.ArgHandlerExtra;
 import com.google.gwt.util.tools.ArgHandlerFlag;
-import com.google.gwt.util.tools.ArgHandlerOutDir;
 import com.google.gwt.util.tools.ArgHandlerString;
 import com.google.gwt.util.tools.ToolBase;
 
@@ -246,12 +246,12 @@
 
         // Create a sandbox for the module.
         //
-        File shellDir = new File(outDir, GWT_SHELL_PATH + File.separator
-            + moduleName);
+        File shellDir = new File(options.getOutDir(), GWT_SHELL_PATH
+            + File.separator + moduleName);
 
         TypeOracle typeOracle = moduleDef.getTypeOracle(logger);
         ShellModuleSpaceHost host = doCreateShellModuleSpaceHost(logger,
-            typeOracle, moduleDef, genDir, shellDir);
+            typeOracle, moduleDef, options.getGenDir(), shellDir);
         return host;
       } finally {
         Cursor normalCursor = display.getSystemCursor(SWT.CURSOR_ARROW);
@@ -365,8 +365,6 @@
    */
   protected final Display display = Display.getDefault();
 
-  protected File outDir;
-
   /**
    * Cheat on the first load's refresh by assuming the module loaded by
    * {@link com.google.gwt.dev.shell.GWTShellServlet} is still fresh. This
@@ -379,16 +377,12 @@
 
   private final List<Shell> browserShells = new ArrayList<Shell>();
 
-  private File genDir;
-
   private boolean headlessMode = false;
 
-  private final JJSOptions jjsOptions = new JJSOptions();
-
-  private TreeLogger.Type logLevel;
-
   private ShellMainWindow mainWnd;
 
+  private final CompilerOptionsImpl options = new CompilerOptionsImpl();
+
   private int port;
 
   private boolean runTomcat = true;
@@ -411,47 +405,21 @@
     registerHandler(new ArgHandlerWhitelist());
     registerHandler(new ArgHandlerBlacklist());
 
-    registerHandler(new ArgHandlerLogLevel() {
-      @Override
-      protected Type getDefaultLogLevel() {
-        return doGetDefaultLogLevel();
-      }
+    registerHandler(new ArgHandlerLogLevel(options));
 
-      @Override
-      public void setLogLevel(Type level) {
-        logLevel = level;
-      }
-    });
-
-    registerHandler(new ArgHandlerGenDir() {
-      @Override
-      public void setDir(File dir) {
-        genDir = dir;
-      }
-    });
+    registerHandler(new ArgHandlerGenDir(options));
 
     if (!noURLs) {
       registerHandler(new ArgHandlerStartupURLs());
     }
 
-    registerHandler(new ArgHandlerOutDir() {
-      @Override
-      public void setDir(File dir) {
-        outDir = dir;
-      }
-    });
+    registerHandler(new ArgHandlerOutDir(options));
 
-    registerHandler(new ArgHandlerScriptStyle(jjsOptions));
+    registerHandler(new ArgHandlerScriptStyle(options));
 
-    registerHandler(new ArgHandlerEnableAssertions(jjsOptions));
+    registerHandler(new ArgHandlerEnableAssertions(options));
 
-    registerHandler(new ArgHandlerDisableAggressiveOptimization() {
-      @Override
-      public boolean setFlag() {
-        jjsOptions.setAggressivelyOptimize(false);
-        return true;
-      }
-    });
+    registerHandler(new ArgHandlerDisableAggressiveOptimization(options));
   }
 
   public void addStartupURL(String url) {
@@ -464,16 +432,8 @@
     }
   }
 
-  public File getGenDir() {
-    return genDir;
-  }
-
-  public Type getLogLevel() {
-    return logLevel;
-  }
-
-  public File getOutDir() {
-    return outDir;
+  public CompilerOptions getCompilerOptions() {
+    return new CompilerOptionsImpl(options);
   }
 
   public int getPort() {
@@ -599,20 +559,8 @@
     }
   }
 
-  public void setCompilerOptions(JJSOptions options) {
-    jjsOptions.copyFrom(options);
-  }
-
-  public void setGenDir(File genDir) {
-    this.genDir = genDir;
-  }
-
-  public void setLogLevel(Type level) {
-    this.logLevel = level;
-  }
-
-  public void setOutDir(File outDir) {
-    this.outDir = outDir;
+  public void setCompilerOptions(CompilerOptions options) {
+    this.options.copyFrom(options);
   }
 
   public void setPort(int port) {
@@ -630,13 +578,9 @@
    */
   protected void compile(TreeLogger logger, ModuleDef moduleDef)
       throws UnableToCompleteException {
-    GWTCompiler compiler = new GWTCompiler();
-    compiler.setCompilerOptions(jjsOptions);
-    compiler.setGenDir(genDir);
-    compiler.setOutDir(outDir);
-    compiler.setModuleName(moduleDef.getName());
-    compiler.setLogLevel(logLevel);
-    compiler.distill(logger, moduleDef);
+    CompilerOptions newOptions = new CompilerOptionsImpl(options);
+    newOptions.setModuleName(moduleDef.getName());
+    new GWTCompiler(newOptions).run(logger);
   }
 
   /**
@@ -685,7 +629,7 @@
 
   protected void initializeLogger() {
     final AbstractTreeLogger logger = mainWnd.getLogger();
-    logger.setMaxDetail(logLevel);
+    logger.setMaxDetail(options.getLogLevel());
   }
 
   /**
@@ -776,7 +720,7 @@
 
       PerfLogger.start("GWTShell.startup (Tomcat launch)");
       String whyFailed = EmbeddedTomcatServer.start(getTopLogger(), serverPort,
-          outDir);
+          options.getOutDir());
       PerfLogger.end();
 
       if (whyFailed != null) {
diff --git a/dev/core/src/com/google/gwt/dev/Link.java b/dev/core/src/com/google/gwt/dev/Link.java
new file mode 100644
index 0000000..2bc55cd
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/Link.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.linker.ArtifactSet;
+import com.google.gwt.core.ext.linker.SelectionProperty;
+import com.google.gwt.core.ext.linker.impl.StandardCompilationResult;
+import com.google.gwt.core.ext.linker.impl.StandardLinkerContext;
+import com.google.gwt.dev.CompileTaskRunner.CompileTask;
+import com.google.gwt.dev.cfg.BindingProperty;
+import com.google.gwt.dev.cfg.ModuleDef;
+import com.google.gwt.dev.cfg.ModuleDefLoader;
+import com.google.gwt.dev.cfg.StaticPropertyOracle;
+import com.google.gwt.dev.util.Util;
+import com.google.gwt.dev.util.arg.ArgHandlerLogLevel;
+import com.google.gwt.dev.util.arg.ArgHandlerModuleName;
+import com.google.gwt.dev.util.arg.ArgHandlerOutDir;
+import com.google.gwt.dev.util.arg.ArgHandlerTreeLoggerFlag;
+import com.google.gwt.util.tools.ToolBase;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Performs the first phase of compilation, generating the set of permutations
+ * to compile, and a ready-to-compile AST.
+ */
+public class Link {
+
+  static class ArgProcessor extends ToolBase {
+    public ArgProcessor(CompileTaskOptions options) {
+      registerHandler(new ArgHandlerLogLevel(options));
+      registerHandler(new ArgHandlerTreeLoggerFlag(options));
+      registerHandler(new ArgHandlerOutDir(options));
+      registerHandler(new ArgHandlerModuleName(options));
+    }
+
+    /*
+     * Overridden to make public.
+     */
+    @Override
+    public boolean processArgs(String[] args) {
+      return super.processArgs(args);
+    }
+
+    @Override
+    protected String getName() {
+      return Link.class.getName();
+    }
+  }
+
+  public static ArtifactSet link(TreeLogger logger, ModuleDef module,
+      Precompilation precompilation, File[] jsFiles)
+      throws UnableToCompleteException {
+    StandardLinkerContext linkerContext = new StandardLinkerContext(logger,
+        module, precompilation.getUnifiedAst().getOptions());
+    return doLink(logger, linkerContext, precompilation, jsFiles);
+  }
+
+  public static void main(String[] args) {
+    /*
+     * NOTE: main always exits with a call to System.exit to terminate any
+     * non-daemon threads that were started in Generators. Typically, this is to
+     * shutdown AWT related threads, since the contract for their termination is
+     * still implementation-dependent.
+     */
+    final CompileTaskOptions options = new CompileTaskOptionsImpl();
+    if (new ArgProcessor(options).processArgs(args)) {
+      CompileTask task = new CompileTask() {
+        public boolean run(TreeLogger logger) throws UnableToCompleteException {
+          return new Link(options).run(logger);
+        }
+      };
+      if (CompileTaskRunner.runWithAppropriateLogger(options, task)) {
+        // Exit w/ success code.
+        System.exit(0);
+      }
+    }
+    // Exit w/ non-success code.
+    System.exit(1);
+  }
+
+  private static ArtifactSet doLink(TreeLogger logger,
+      StandardLinkerContext linkerContext, Precompilation precompilation,
+      File[] jsFiles) throws UnableToCompleteException {
+    Permutation[] perms = precompilation.getPermutations();
+    if (perms.length != jsFiles.length) {
+      throw new IllegalArgumentException(
+          "Mismatched jsFiles.length and permutation count");
+    }
+
+    for (int i = 0; i < perms.length; ++i) {
+      finishPermuation(logger, perms[i], jsFiles[i], linkerContext);
+    }
+
+    linkerContext.addOrReplaceArtifacts(precompilation.getGeneratedArtifacts());
+    return linkerContext.invokeLinkerStack(logger);
+  }
+
+  private static void finishPermuation(TreeLogger logger, Permutation perm,
+      File jsFile, StandardLinkerContext linkerContext)
+      throws UnableToCompleteException {
+    StandardCompilationResult compilation = linkerContext.getCompilation(
+        logger, jsFile);
+    StaticPropertyOracle[] propOracles = perm.getPropertyOracles();
+    for (StaticPropertyOracle propOracle : propOracles) {
+      BindingProperty[] orderedProps = propOracle.getOrderedProps();
+      String[] orderedPropValues = propOracle.getOrderedPropValues();
+      Map<SelectionProperty, String> unboundProperties = new HashMap<SelectionProperty, String>();
+      for (int i = 0; i < orderedProps.length; i++) {
+        SelectionProperty key = linkerContext.getProperty(orderedProps[i].getName());
+        if (key.tryGetValue() != null) {
+          /*
+           * The view of the Permutation doesn't include properties with defined
+           * values.
+           */
+          continue;
+        }
+        unboundProperties.put(key, orderedPropValues[i]);
+      }
+      compilation.addSelectionPermutation(unboundProperties);
+    }
+  }
+
+  private ModuleDef module;
+
+  /**
+   * This is the output directory for private files.
+   */
+  private File moduleAuxDir;
+
+  /**
+   * This is the output directory for public files.
+   */
+  private File moduleOutDir;
+
+  private final CompileTaskOptionsImpl options;
+
+  public Link(CompileTaskOptions options) {
+    this.options = new CompileTaskOptionsImpl(options);
+  }
+
+  public boolean run(TreeLogger logger) throws UnableToCompleteException {
+    init(logger);
+    File precompilationFile = new File(options.getCompilerWorkDir(),
+        Precompile.PRECOMPILATION_FILENAME);
+    if (!precompilationFile.exists()) {
+      logger.log(TreeLogger.ERROR, "File not found '"
+          + precompilationFile.getAbsolutePath()
+          + "'; please run Precompile first");
+      return false;
+    }
+
+    Precompilation precompilation;
+    try {
+      precompilation = Util.readFileAsObject(precompilationFile,
+          Precompilation.class);
+    } catch (ClassNotFoundException e) {
+      logger.log(TreeLogger.ERROR, "Unable to deserialize '"
+          + precompilationFile.getAbsolutePath() + "'", e);
+      return false;
+    }
+    Permutation[] perms = precompilation.getPermutations();
+    File[] jsFiles = new File[perms.length];
+    for (int i = 0; i < perms.length; ++i) {
+      jsFiles[i] = CompilePerms.makePermFilename(options.getCompilerWorkDir(),
+          i);
+      if (!jsFiles[i].exists()) {
+        logger.log(TreeLogger.ERROR, "File not found '"
+            + precompilationFile.getAbsolutePath()
+            + "'; please compile all permutations");
+        return false;
+      }
+    }
+
+    TreeLogger branch = logger.branch(TreeLogger.INFO, "Linking module "
+        + module.getName());
+    StandardLinkerContext linkerContext = new StandardLinkerContext(branch,
+        module, precompilation.getUnifiedAst().getOptions());
+    ArtifactSet artifacts = doLink(branch, linkerContext, precompilation,
+        jsFiles);
+    if (artifacts != null) {
+      linkerContext.produceOutputDirectory(branch, artifacts, moduleOutDir,
+          moduleAuxDir);
+      branch.log(TreeLogger.INFO, "Link succeeded");
+      return true;
+    }
+    branch.log(TreeLogger.ERROR, "Link failed");
+    return false;
+  }
+
+  private void init(TreeLogger logger) throws UnableToCompleteException {
+    module = ModuleDefLoader.loadFromClassPath(logger, options.getModuleName());
+    moduleOutDir = new File(options.getOutDir(), module.getName());
+    Util.recursiveDelete(moduleOutDir, true);
+    moduleAuxDir = new File(options.getOutDir(), module.getName() + "-aux");
+    Util.recursiveDelete(moduleAuxDir, false);
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/Permutation.java b/dev/core/src/com/google/gwt/dev/Permutation.java
index 64ae9f3..13ca881 100644
--- a/dev/core/src/com/google/gwt/dev/Permutation.java
+++ b/dev/core/src/com/google/gwt/dev/Permutation.java
@@ -16,34 +16,46 @@
 package com.google.gwt.dev;
 
 import com.google.gwt.dev.cfg.StaticPropertyOracle;
-import com.google.gwt.dev.jdt.RebindOracle;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
 
 /**
  * Represents the state of a single permutation for compile.
- * 
- * @see PermutationCompiler
  */
-public final class Permutation {
-  private final int number;
-  private final StaticPropertyOracle propertyOracle;
-  private final RebindOracle rebindOracle;
+public final class Permutation implements Serializable {
+  private final List<StaticPropertyOracle> propertyOracles = new ArrayList<StaticPropertyOracle>();
+  private final SortedMap<String, String> rebindAnswers = new TreeMap<String, String>();
 
-  public Permutation(int number, RebindOracle rebindOracle,
-      StaticPropertyOracle propertyOracle) {
-    this.number = number;
-    this.rebindOracle = rebindOracle;
-    this.propertyOracle = propertyOracle;
+  public Permutation(StaticPropertyOracle propertyOracle) {
+    this.propertyOracles.add(propertyOracle);
   }
 
-  public int getNumber() {
-    return number;
+  public StaticPropertyOracle[] getPropertyOracles() {
+    return propertyOracles.toArray(new StaticPropertyOracle[propertyOracles.size()]);
   }
 
-  public StaticPropertyOracle getPropertyOracle() {
-    return propertyOracle;
+  public SortedMap<String, String> getRebindAnswers() {
+    return rebindAnswers;
   }
 
-  public RebindOracle getRebindOracle() {
-    return rebindOracle;
+  public void mergeFrom(Permutation other) {
+    assert rebindAnswers.equals(other.rebindAnswers);
+    assert !propertyOracles.isEmpty();
+    assert !other.propertyOracles.isEmpty();
+    propertyOracles.addAll(other.propertyOracles);
+    other.propertyOracles.clear();
+  }
+
+  public void putRebindAnswer(String requestType, String resultType) {
+    rebindAnswers.put(requestType, resultType);
+  }
+
+  public void reduceRebindAnswers(Set<String> liveRebindRequests) {
+    rebindAnswers.keySet().retainAll(liveRebindRequests);
   }
 }
diff --git a/dev/core/src/com/google/gwt/dev/PermutationCompiler.java b/dev/core/src/com/google/gwt/dev/PermutationCompiler.java
index 7513e1c..f89b102 100644
--- a/dev/core/src/com/google/gwt/dev/PermutationCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/PermutationCompiler.java
@@ -17,17 +17,13 @@
 
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
-import com.google.gwt.core.ext.linker.SelectionProperty;
-import com.google.gwt.core.ext.linker.impl.StandardCompilationResult;
-import com.google.gwt.core.ext.linker.impl.StandardLinkerContext;
 import com.google.gwt.dev.cfg.BindingProperty;
 import com.google.gwt.dev.cfg.StaticPropertyOracle;
+import com.google.gwt.dev.jjs.UnifiedAst;
 import com.google.gwt.dev.jjs.JavaToJavaScriptCompiler;
 import com.google.gwt.dev.jjs.ast.JProgram;
 import com.google.gwt.dev.util.PerfLogger;
 
-import java.util.HashMap;
-import java.util.Map;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ConcurrentLinkedQueue;
@@ -40,13 +36,21 @@
 public class PermutationCompiler {
 
   /**
+   * Hands back results as they are finished.
+   */
+  public interface ResultsHandler {
+    void addResult(Permutation permutation, int permNum, String js)
+        throws UnableToCompleteException;
+  }
+
+  /**
    * A Result for a permutation that failed to compile.
    */
   private static final class FailedResult extends Result {
     private Throwable exception;
 
-    public FailedResult(Permutation perm, Throwable exception) {
-      super(perm);
+    public FailedResult(Permutation perm, int permNum, Throwable exception) {
+      super(perm, permNum);
       this.exception = exception;
     }
 
@@ -60,42 +64,51 @@
    */
   private static final class PermutationTask implements Callable<String> {
     private static void logProperties(TreeLogger logger,
-        StaticPropertyOracle propOracle) {
-      BindingProperty[] props = propOracle.getOrderedProps();
-      String[] values = propOracle.getOrderedPropValues();
-      if (logger.isLoggable(TreeLogger.DEBUG)) {
-        logger = logger.branch(TreeLogger.DEBUG, "Setting properties", null);
-        for (int i = 0; i < props.length; i++) {
-          String name = props[i].getName();
-          String value = values[i];
-          logger.log(TreeLogger.TRACE, name + " = " + value, null);
+        StaticPropertyOracle[] propOracles) {
+      for (StaticPropertyOracle propOracle : propOracles) {
+        BindingProperty[] props = propOracle.getOrderedProps();
+        String[] values = propOracle.getOrderedPropValues();
+        if (logger.isLoggable(TreeLogger.DEBUG)) {
+          logger = logger.branch(TreeLogger.DEBUG, "Setting properties", null);
+          for (int i = 0; i < props.length; i++) {
+            String name = props[i].getName();
+            String value = values[i];
+            logger.log(TreeLogger.TRACE, name + " = " + value, null);
+          }
         }
       }
     }
 
-    private final JavaToJavaScriptCompiler jjs;
+    private final UnifiedAst unifiedAst;
     private final TreeLogger logger;
     private final Permutation perm;
+    private final int permNum;
 
-    public PermutationTask(TreeLogger logger, JavaToJavaScriptCompiler jjs,
-        Permutation perm) {
+    public PermutationTask(TreeLogger logger, UnifiedAst unifiedAst,
+        Permutation perm, int permNum) {
       this.logger = logger;
-      this.jjs = jjs;
+      this.unifiedAst = unifiedAst;
       this.perm = perm;
+      this.permNum = permNum;
     }
 
     public String call() throws Exception {
-      PerfLogger.start("Permutation #" + (perm.getNumber() + 1));
+      PerfLogger.start("Permutation #" + permNum);
       try {
-        TreeLogger branch = logger.branch(TreeLogger.TRACE,
-            "Analyzing permutation #" + (perm.getNumber() + 1));
-        logProperties(branch, perm.getPropertyOracle());
-        return jjs.compile(branch, perm.getRebindOracle());
+        TreeLogger branch = logger.branch(TreeLogger.TRACE, "Permutation #"
+            + permNum);
+        logProperties(branch, perm.getPropertyOracles());
+        return JavaToJavaScriptCompiler.compilePermutation(branch, unifiedAst,
+            perm.getRebindAnswers());
       } finally {
         PerfLogger.end();
       }
     }
 
+    public int getPermNum() {
+      return permNum;
+    }
+
     public Permutation getPermutation() {
       return perm;
     }
@@ -106,9 +119,15 @@
    */
   private abstract static class Result {
     private final Permutation perm;
+    private final int permNum;
 
-    public Result(Permutation perm) {
+    public Result(Permutation perm, int permNum) {
       this.perm = perm;
+      this.permNum = permNum;
+    }
+
+    public int getPermNum() {
+      return permNum;
     }
 
     public Permutation getPermutation() {
@@ -122,8 +141,8 @@
   private static final class SuccessResult extends Result {
     private final String js;
 
-    public SuccessResult(Permutation perm, String js) {
-      super(perm);
+    public SuccessResult(Permutation perm, int permNum, String js) {
+      super(perm, permNum);
       this.js = js;
     }
 
@@ -183,7 +202,8 @@
       boolean definitelyFinalThread = (threadCount.get() == 1);
       try {
         String result = currentTask.call();
-        results.add(new SuccessResult(currentTask.getPermutation(), result));
+        results.add(new SuccessResult(currentTask.getPermutation(),
+            currentTask.getPermNum(), result));
       } catch (OutOfMemoryError e) {
         if (definitelyFinalThread) {
           // OOM on the final thread, this is a truly unrecoverable failure.
@@ -191,7 +211,7 @@
           exitFinalThread(new Runnable() {
             public void run() {
               results.add(new FailedResult(currentTask.getPermutation(),
-                  new UnableToCompleteException()));
+                  currentTask.getPermNum(), new UnableToCompleteException()));
             }
           });
         }
@@ -211,7 +231,8 @@
         outOfMemoryRetryAction.run();
       } catch (Throwable e) {
         // Unexpected error compiling, this is unrecoverable.
-        results.add(new FailedResult(currentTask.getPermutation(), e));
+        results.add(new FailedResult(currentTask.getPermutation(),
+            currentTask.getPermNum(), e));
         throw new ThreadDeath();
       }
     }
@@ -270,7 +291,7 @@
   /**
    * A marker Result that tells the main thread all work is done.
    */
-  private static final Result FINISHED_RESULT = new Result(null) {
+  private static final Result FINISHED_RESULT = new Result(null, -1) {
   };
 
   private static long getPotentialFreeMemory() {
@@ -305,18 +326,17 @@
 
   private final TreeLogger logger;
 
-  public PermutationCompiler(TreeLogger logger, JavaToJavaScriptCompiler jjs,
-      Permutation[] perms) {
-    this.logger = logger.branch(TreeLogger.DEBUG, "Compiling " + perms.length
-        + " permutations");
-    this.astMemoryUsage = jjs.getAstMemoryUsage();
-    for (Permutation perm : perms) {
-      tasks.add(new PermutationTask(this.logger, jjs, perm));
+  public PermutationCompiler(TreeLogger logger, UnifiedAst unifiedAst,
+      Permutation[] perms, int[] permsToRun) {
+    this.logger = logger;
+    this.astMemoryUsage = unifiedAst.getAstMemoryUsage();
+    for (int permToRun : permsToRun) {
+      tasks.add(new PermutationTask(logger, unifiedAst, perms[permToRun],
+          permToRun));
     }
   }
 
-  public void go(StandardLinkerContext linkerContext)
-      throws UnableToCompleteException {
+  public void go(ResultsHandler handler) throws UnableToCompleteException {
     int initialThreadCount = computeInitialThreadCount();
     Thread[] workerThreads = new Thread[initialThreadCount];
     for (int i = 0; i < initialThreadCount; ++i) {
@@ -333,7 +353,8 @@
           assert threadCount.get() == 0;
           return;
         } else if (result instanceof SuccessResult) {
-          finishPermuation(logger, linkerContext, (SuccessResult) result);
+          String js = ((SuccessResult) result).getJs();
+          handler.addResult(result.getPermutation(), result.getPermNum(), js);
         } else if (result instanceof FailedResult) {
           FailedResult failedResult = (FailedResult) result;
           throw logAndTranslateException(failedResult.getException());
@@ -415,30 +436,6 @@
     return result;
   }
 
-  private void finishPermuation(TreeLogger logger,
-      StandardLinkerContext linkerContext, SuccessResult result)
-      throws UnableToCompleteException {
-    Permutation perm = result.getPermutation();
-    StandardCompilationResult compilation = linkerContext.getCompilation(
-        logger, result.getJs());
-    StaticPropertyOracle propOracle = perm.getPropertyOracle();
-    BindingProperty[] orderedProps = propOracle.getOrderedProps();
-    String[] orderedPropValues = propOracle.getOrderedPropValues();
-    Map<SelectionProperty, String> unboundProperties = new HashMap<SelectionProperty, String>();
-    for (int i = 0; i < orderedProps.length; i++) {
-      SelectionProperty key = linkerContext.getProperty(orderedProps[i].getName());
-      if (key.tryGetValue() != null) {
-        /*
-         * The view of the Permutation doesn't include properties with defined
-         * values.
-         */
-        continue;
-      }
-      unboundProperties.put(key, orderedPropValues[i]);
-    }
-    compilation.addSelectionPermutation(unboundProperties);
-  }
-
   private UnableToCompleteException logAndTranslateException(Throwable e) {
     if (e instanceof UnableToCompleteException) {
       return (UnableToCompleteException) e;
diff --git a/dev/core/src/com/google/gwt/dev/Precompilation.java b/dev/core/src/com/google/gwt/dev/Precompilation.java
new file mode 100644
index 0000000..8eb7438
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/Precompilation.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev;
+
+import com.google.gwt.core.ext.linker.ArtifactSet;
+import com.google.gwt.dev.jjs.UnifiedAst;
+
+import java.io.Serializable;
+
+/**
+ * The result of compilation phase 1, includes a unified AST and metadata
+ * relevant to each permutation.
+ */
+public class Precompilation implements Serializable {
+  /*
+   * TODO: don't make this whole class serializable, instead dump the
+   * independent members out to a file so that the generated artifacts are
+   * optional to deserialize.
+   */
+  private ArtifactSet generatedArtifacts;
+  private final Permutation[] permutations;
+  private final UnifiedAst unifiedAst;
+
+  /**
+   * Constructs a new precompilation.
+   * 
+   * @param unifiedAst the unified AST used by
+   *          {@link com.google.gwt.dev.jjs.JavaToJavaScriptCompiler}
+   * @param permutations the set of permutations that can be run
+   * @param generatedArtifacts the set of artifacts created by generators
+   */
+  public Precompilation(UnifiedAst unifiedAst, Permutation[] permutations,
+      ArtifactSet generatedArtifacts) {
+    this.unifiedAst = unifiedAst;
+    this.permutations = permutations;
+    this.generatedArtifacts = generatedArtifacts;
+  }
+
+  /**
+   * Returns the set of generated artifacts from the precompile phase.
+   */
+  public ArtifactSet getGeneratedArtifacts() {
+    return generatedArtifacts;
+  }
+
+  /**
+   * Returns the set of permutations to run.
+   */
+  public Permutation[] getPermutations() {
+    return permutations;
+  }
+
+  /**
+   * Returns the unified AST used by
+   * {@link com.google.gwt.dev.jjs.JavaToJavaScriptCompiler}.
+   */
+  public UnifiedAst getUnifiedAst() {
+    return unifiedAst;
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/Precompile.java b/dev/core/src/com/google/gwt/dev/Precompile.java
new file mode 100644
index 0000000..8465ac5
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/Precompile.java
@@ -0,0 +1,392 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.linker.ArtifactSet;
+import com.google.gwt.dev.CompileTaskRunner.CompileTask;
+import com.google.gwt.dev.cfg.BindingProperty;
+import com.google.gwt.dev.cfg.ConfigurationProperty;
+import com.google.gwt.dev.cfg.ModuleDef;
+import com.google.gwt.dev.cfg.ModuleDefLoader;
+import com.google.gwt.dev.cfg.PropertyPermutations;
+import com.google.gwt.dev.cfg.Rules;
+import com.google.gwt.dev.cfg.StaticPropertyOracle;
+import com.google.gwt.dev.javac.CompilationUnit;
+import com.google.gwt.dev.jdt.RebindOracle;
+import com.google.gwt.dev.jdt.RebindPermutationOracle;
+import com.google.gwt.dev.jdt.WebModeCompilerFrontEnd;
+import com.google.gwt.dev.jjs.UnifiedAst;
+import com.google.gwt.dev.jjs.JJSOptions;
+import com.google.gwt.dev.jjs.JJSOptionsImpl;
+import com.google.gwt.dev.jjs.JavaToJavaScriptCompiler;
+import com.google.gwt.dev.jjs.JsOutputOption;
+import com.google.gwt.dev.shell.StandardRebindOracle;
+import com.google.gwt.dev.util.Util;
+import com.google.gwt.dev.util.arg.ArgHandlerDisableAggressiveOptimization;
+import com.google.gwt.dev.util.arg.ArgHandlerEnableAssertions;
+import com.google.gwt.dev.util.arg.ArgHandlerGenDir;
+import com.google.gwt.dev.util.arg.ArgHandlerScriptStyle;
+import com.google.gwt.dev.util.arg.ArgHandlerValidateOnlyFlag;
+
+import java.io.File;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
+
+/**
+ * Performs the first phase of compilation, generating the set of permutations
+ * to compile, and a ready-to-compile AST.
+ */
+public class Precompile {
+
+  static class ArgProcessor extends Link.ArgProcessor {
+    public ArgProcessor(CompilerOptions options) {
+      super(options);
+      registerHandler(new ArgHandlerGenDir(options));
+      registerHandler(new ArgHandlerScriptStyle(options));
+      registerHandler(new ArgHandlerEnableAssertions(options));
+      registerHandler(new ArgHandlerDisableAggressiveOptimization(options));
+      registerHandler(new ArgHandlerValidateOnlyFlag(options));
+    }
+
+    @Override
+    protected String getName() {
+      return Precompile.class.getName();
+    }
+  }
+  /**
+   * Concrete class to implement all compiler options.
+   */
+  static class CompilerOptionsImpl extends CompileTaskOptionsImpl implements
+      CompilerOptions {
+
+    private File genDir;
+    private final JJSOptionsImpl jjsOptions = new JJSOptionsImpl();
+    private boolean validateOnly;
+
+    public CompilerOptionsImpl() {
+    }
+
+    public CompilerOptionsImpl(CompilerOptions other) {
+      copyFrom(other);
+    }
+
+    public void copyFrom(CompilerOptions other) {
+      super.copyFrom(other);
+
+      setGenDir(other.getGenDir());
+      setValidateOnly(other.isValidateOnly());
+
+      setAggressivelyOptimize(other.isAggressivelyOptimize());
+      setEnableAssertions(other.isEnableAssertions());
+      setOutput(other.getOutput());
+    }
+
+    public File getGenDir() {
+      return genDir;
+    }
+
+    public JsOutputOption getOutput() {
+      return jjsOptions.getOutput();
+    }
+
+    public boolean isAggressivelyOptimize() {
+      return jjsOptions.isAggressivelyOptimize();
+    }
+
+    public boolean isEnableAssertions() {
+      return jjsOptions.isEnableAssertions();
+    }
+
+    public boolean isValidateOnly() {
+      return validateOnly;
+    }
+
+    public void setAggressivelyOptimize(boolean aggressivelyOptimize) {
+      jjsOptions.setAggressivelyOptimize(aggressivelyOptimize);
+    }
+
+    public void setEnableAssertions(boolean enableAssertions) {
+      jjsOptions.setEnableAssertions(enableAssertions);
+    }
+
+    public void setGenDir(File genDir) {
+      this.genDir = genDir;
+    }
+
+    public void setOutput(JsOutputOption output) {
+      jjsOptions.setOutput(output);
+    }
+
+    public void setValidateOnly(boolean validateOnly) {
+      this.validateOnly = validateOnly;
+    }
+  }
+
+  private static class DistillerRebindPermutationOracle implements
+      RebindPermutationOracle {
+
+    private Permutation[] permutations;
+    private StaticPropertyOracle[] propertyOracles;
+    private RebindOracle[] rebindOracles;
+
+    public DistillerRebindPermutationOracle(ModuleDef module,
+        ArtifactSet generatorArtifacts, PropertyPermutations perms,
+        File genDir, File generatorResourcesDir) {
+      permutations = new Permutation[perms.size()];
+      propertyOracles = new StaticPropertyOracle[perms.size()];
+      rebindOracles = new RebindOracle[perms.size()];
+      BindingProperty[] orderedProps = perms.getOrderedProperties();
+      SortedSet<ConfigurationProperty> configPropSet = module.getProperties().getConfigurationProperties();
+      ConfigurationProperty[] configProps = configPropSet.toArray(new ConfigurationProperty[configPropSet.size()]);
+      Rules rules = module.getRules();
+      for (int i = 0; i < rebindOracles.length; ++i) {
+        String[] orderedPropValues = perms.getOrderedPropertyValues(i);
+        propertyOracles[i] = new StaticPropertyOracle(orderedProps,
+            orderedPropValues, configProps);
+        rebindOracles[i] = new StandardRebindOracle(
+            module.getCompilationState(), propertyOracles[i], module, rules,
+            genDir, generatorResourcesDir, generatorArtifacts);
+        permutations[i] = new Permutation(propertyOracles[i]);
+      }
+    }
+
+    public String[] getAllPossibleRebindAnswers(TreeLogger logger,
+        String requestTypeName) throws UnableToCompleteException {
+
+      String msg = "Computing all possible rebind results for '"
+          + requestTypeName + "'";
+      logger = logger.branch(TreeLogger.DEBUG, msg, null);
+
+      Set<String> answers = new HashSet<String>();
+
+      for (int i = 0; i < getPermuationCount(); ++i) {
+        String resultTypeName = rebindOracles[i].rebind(logger, requestTypeName);
+        answers.add(resultTypeName);
+        // Record the correct answer into each permutation.
+        permutations[i].putRebindAnswer(requestTypeName, resultTypeName);
+      }
+      return Util.toArray(String.class, answers);
+    }
+
+    public int getPermuationCount() {
+      return rebindOracles.length;
+    }
+
+    public Permutation[] getPermutations() {
+      return permutations;
+    }
+
+    public StaticPropertyOracle getPropertyOracle(int permNumber) {
+      return propertyOracles[permNumber];
+    }
+
+    public RebindOracle getRebindOracle(int permNumber) {
+      return rebindOracles[permNumber];
+    }
+  }
+
+  static final String PERM_COUNT_FILENAME = "permCount.txt";
+
+  static final String PRECOMPILATION_FILENAME = "precompilation.ser";
+
+  /**
+   * Performs a command-line precompile.
+   */
+  public static void main(String[] args) {
+    /*
+     * NOTE: main always exits with a call to System.exit to terminate any
+     * non-daemon threads that were started in Generators. Typically, this is to
+     * shutdown AWT related threads, since the contract for their termination is
+     * still implementation-dependent.
+     */
+    final CompilerOptions options = new CompilerOptionsImpl();
+    if (new ArgProcessor(options).processArgs(args)) {
+      CompileTask task = new CompileTask() {
+        public boolean run(TreeLogger logger) throws UnableToCompleteException {
+          return new Precompile(options).run(logger);
+        }
+      };
+      if (CompileTaskRunner.runWithAppropriateLogger(options, task)) {
+        // Exit w/ success code.
+        System.exit(0);
+      }
+    }
+    // Exit w/ non-success code.
+    System.exit(1);
+  }
+
+  /**
+   * Precompiles the given module.
+   * 
+   * @param logger a logger to use
+   * @param jjsOptions a set of compiler options
+   * @param module the module to compile
+   * @param genDir optional directory to dump generated source, may be
+   *          <code>null</code>
+   * @param generatorResourcesDir required directory to dump generator resources
+   * @return the precompilation
+   */
+  public static Precompilation precompile(TreeLogger logger,
+      JJSOptions jjsOptions, ModuleDef module, File genDir,
+      File generatorResourcesDir) {
+    try {
+      String[] declEntryPts = module.getEntryPointTypeNames();
+      if (declEntryPts.length == 0) {
+        logger.log(TreeLogger.ERROR, "Module has no entry points defined", null);
+        throw new UnableToCompleteException();
+      }
+
+      ArtifactSet generatedArtifacts = new ArtifactSet();
+      DistillerRebindPermutationOracle rpo = new DistillerRebindPermutationOracle(
+          module, generatedArtifacts, new PropertyPermutations(
+              module.getProperties()), genDir, generatorResourcesDir);
+
+      WebModeCompilerFrontEnd frontEnd = new WebModeCompilerFrontEnd(
+          module.getCompilationState(), rpo);
+      UnifiedAst unifiedAst = JavaToJavaScriptCompiler.precompile(logger,
+          frontEnd, declEntryPts, jjsOptions, rpo.getPermuationCount() == 1);
+
+      // Merge all identical permutations together.
+      Permutation[] permutations = rpo.getPermutations();
+      // Sort the permutations by an ordered key to ensure determinism.
+      SortedMap<String, Permutation> merged = new TreeMap<String, Permutation>();
+      for (Permutation permutation : permutations) {
+        permutation.reduceRebindAnswers(unifiedAst.getRebindRequests());
+        // Arbitrarily choose as a key the stringified map of rebind answers.
+        String rebindResultsString = permutation.getRebindAnswers().toString();
+        if (merged.containsKey(rebindResultsString)) {
+          Permutation existing = merged.get(rebindResultsString);
+          existing.mergeFrom(permutation);
+        } else {
+          merged.put(rebindResultsString, permutation);
+        }
+      }
+      permutations = merged.values().toArray(new Permutation[merged.size()]);
+      return new Precompilation(unifiedAst, permutations, generatedArtifacts);
+    } catch (UnableToCompleteException e) {
+      // We intentionally don't pass in the exception here since the real
+      // cause has been logged.
+      return null;
+    }
+  }
+
+  /**
+   * Validates the given module can be compiled.
+   * 
+   * @param logger a logger to use
+   * @param jjsOptions a set of compiler options
+   * @param module the module to compile
+   * @param genDir optional directory to dump generated source, may be
+   *          <code>null</code>
+   * @param generatorResourcesDir required directory to dump generator resources
+   */
+  public static boolean validate(TreeLogger logger, JJSOptions jjsOptions,
+      ModuleDef module, File genDir, File generatorResourcesDir) {
+    try {
+      String[] declEntryPts = module.getEntryPointTypeNames();
+      if (declEntryPts.length == 0) {
+        // Pretend that every single compilation unit is an entry point.
+        Set<CompilationUnit> compilationUnits = module.getCompilationState().getCompilationUnits();
+        declEntryPts = new String[compilationUnits.size()];
+        int i = 0;
+        for (CompilationUnit unit : compilationUnits) {
+          declEntryPts[i++] = unit.getTypeName();
+        }
+      }
+
+      ArtifactSet generatorArtifacts = new ArtifactSet();
+      DistillerRebindPermutationOracle rpo = new DistillerRebindPermutationOracle(
+          module, generatorArtifacts, new PropertyPermutations(
+              module.getProperties()), genDir, generatorResourcesDir);
+
+      WebModeCompilerFrontEnd frontEnd = new WebModeCompilerFrontEnd(
+          module.getCompilationState(), rpo);
+      JavaToJavaScriptCompiler.precompile(logger, frontEnd, declEntryPts,
+          jjsOptions, true);
+      return true;
+    } catch (UnableToCompleteException e) {
+      // Already logged.
+      return false;
+    }
+  }
+
+  private File generatorResourcesDir;
+
+  private ModuleDef module;
+
+  private final CompilerOptionsImpl options;
+
+  public Precompile(CompilerOptions options) {
+    this.options = new CompilerOptionsImpl(options);
+  }
+
+  public boolean run(TreeLogger logger) throws UnableToCompleteException {
+    if (options.isValidateOnly()) {
+      init(logger);
+      TreeLogger branch = logger.branch(TreeLogger.INFO,
+          "Validating compilation " + module.getName());
+      if (validate(branch, options, module, options.getGenDir(),
+          generatorResourcesDir)) {
+        branch.log(TreeLogger.INFO, "Validation succeeded");
+        return true;
+      } else {
+        branch.log(TreeLogger.ERROR, "Validation failed");
+        return false;
+      }
+    } else {
+      init(logger);
+      TreeLogger branch = logger.branch(TreeLogger.INFO, "Precompiling module "
+          + module.getName());
+      Precompilation precompilation = precompile(branch, options, module,
+          options.getGenDir(), generatorResourcesDir);
+      if (precompilation != null) {
+        Util.writeObjectAsFile(branch, new File(options.getCompilerWorkDir(),
+            PRECOMPILATION_FILENAME), precompilation);
+        Util.writeStringAsFile(branch, new File(options.getCompilerWorkDir(),
+            PERM_COUNT_FILENAME),
+            String.valueOf(precompilation.getPermutations().length));
+        branch.log(TreeLogger.INFO,
+            "Precompilation succeeded, number of permutations: "
+                + precompilation.getPermutations().length);
+        return true;
+      }
+      branch.log(TreeLogger.ERROR, "Precompilation failed");
+      return false;
+    }
+  }
+
+  private void init(TreeLogger logger) throws UnableToCompleteException {
+    // Clean out the work dir and/or create it.
+    File compilerWorkDir = options.getCompilerWorkDir();
+    Util.recursiveDelete(compilerWorkDir, true);
+    compilerWorkDir.mkdirs();
+
+    this.module = ModuleDefLoader.loadFromClassPath(logger,
+        options.getModuleName());
+
+    // Place generated resources inside the work dir.
+    generatorResourcesDir = new File(compilerWorkDir, "generated");
+    generatorResourcesDir.mkdirs();
+
+    // TODO: All JDT checks now before even building TypeOracle?
+    module.getCompilationState().compile(logger);
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/cfg/DefaultPropertyProvider.java b/dev/core/src/com/google/gwt/dev/cfg/DefaultPropertyProvider.java
deleted file mode 100644
index ddb7c7b..0000000
--- a/dev/core/src/com/google/gwt/dev/cfg/DefaultPropertyProvider.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright 2008 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.dev.cfg;
-
-import com.google.gwt.dev.js.JsParser;
-import com.google.gwt.dev.js.JsParserException;
-import com.google.gwt.dev.js.ast.JsBlock;
-import com.google.gwt.dev.js.ast.JsExprStmt;
-import com.google.gwt.dev.js.ast.JsFunction;
-import com.google.gwt.dev.js.ast.JsProgram;
-import com.google.gwt.dev.js.ast.JsStatement;
-
-import java.io.IOException;
-import java.io.StringReader;
-import java.util.List;
-
-/**
- * A property provider that reports property values specified literally in a
- * host HTML page.
- */
-public class DefaultPropertyProvider extends PropertyProvider {
-
-  /*
-   * TODO: this references 'parent' literally, which could be a problem if you
-   * were to include the selector script in the host page itself rather than in
-   * an iframe.
-   */
-  public DefaultPropertyProvider(ModuleDef module, Property property) {
-    super(module, property);
-    String src = "function () {";
-    src += "return __gwt_getMetaProperty(\"";
-    src += property.getName();
-    src += "\"); }";
-    setBody(parseFunction(src));
-  }
-
-  private JsBlock parseFunction(String jsniSrc) {
-    Throwable caught = null;
-    try {
-      JsProgram jsPgm = new JsProgram();
-      JsParser jsParser = new JsParser();
-      StringReader r = new StringReader(jsniSrc);
-      jsParser.setSourceInfo(jsPgm.createSourceInfoSynthetic(
-          DefaultPropertyProvider.class, "Default property provider for "
-              + getProperty().getName()));
-      List<JsStatement> stmts = jsParser.parse(jsPgm.getScope(), r, 1);
-      JsFunction fn = (JsFunction) ((JsExprStmt) stmts.get(0)).getExpression();
-      return fn.getBody();
-    } catch (IOException e) {
-      caught = e;
-    } catch (JsParserException e) {
-      caught = e;
-    }
-    throw new RuntimeException(
-        "Internal error parsing source for default property provider", caught);
-  }
-}
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 405e93c..3d6efb6 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java
@@ -408,7 +408,11 @@
          * one possible value and no existing provider.
          */
         if (prop.getProvider() == null && prop.getAllowedValues().length > 1) {
-          prop.setProvider(new DefaultPropertyProvider(this, prop));
+          String src = "{";
+          src += "return __gwt_getMetaProperty(\"";
+          src += prop.getName();
+          src += "\"); }";
+          prop.setProvider(new PropertyProvider(src));
         }
       }
     }
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java b/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java
index 2477a25..ebde69d 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java
@@ -214,7 +214,6 @@
     }
 
     protected Schema __property_provider_begin(BindingProperty property) {
-      property.setProvider(new PropertyProvider(moduleDef, property));
       return fChild = new PropertyProviderBodySchema();
     }
 
@@ -233,7 +232,7 @@
       int lineNumber = childSchema.getStartLineNumber();
       JsFunction fn = parseJsBlock(lineNumber, script);
 
-      property.getProvider().setBody(fn.getBody());
+      property.setProvider(new PropertyProvider(fn.getBody().toSource()));
     }
 
     protected Schema __public_begin(String path, String includes,
diff --git a/dev/core/src/com/google/gwt/dev/cfg/Property.java b/dev/core/src/com/google/gwt/dev/cfg/Property.java
index ad30a8b..5615672 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/Property.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/Property.java
@@ -15,10 +15,12 @@
  */
 package com.google.gwt.dev.cfg;
 
+import java.io.Serializable;
+
 /**
  * Represents an abstract module property.
  */
-public abstract class Property implements Comparable<Property> {
+public abstract class Property implements Comparable<Property>, Serializable {
 
   protected final String name;
 
diff --git a/dev/core/src/com/google/gwt/dev/cfg/PropertyProvider.java b/dev/core/src/com/google/gwt/dev/cfg/PropertyProvider.java
index 13c44d6..55f5f17 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/PropertyProvider.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/PropertyProvider.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2006 Google Inc.
+ * Copyright 2008 Google Inc.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -15,36 +15,20 @@
  */
 package com.google.gwt.dev.cfg;
 
-import com.google.gwt.dev.js.ast.JsBlock;
+import java.io.Serializable;
 
 /**
  * Produces a deferred binding property value by executing JavaScript code.
  */
-public class PropertyProvider {
+public class PropertyProvider implements Serializable {
 
-  private JsBlock body;
+  private final String body;
 
-  private final ModuleDef module;
-  private final Property property;
-
-  public PropertyProvider(ModuleDef module, Property property) {
-    this.module = module;
-    this.property = property;
-  }
-
-  public JsBlock getBody() {
-    return body;
-  }
-
-  public ModuleDef getModule() {
-    return module;
-  }
-
-  public Property getProperty() {
-    return property;
-  }
-
-  public void setBody(JsBlock body) {
+  public PropertyProvider(String body) {
     this.body = body;
   }
+
+  public String getBody() {
+    return body;
+  }
 }
diff --git a/dev/core/src/com/google/gwt/dev/cfg/StaticPropertyOracle.java b/dev/core/src/com/google/gwt/dev/cfg/StaticPropertyOracle.java
index f501fd2..d0cb379 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/StaticPropertyOracle.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/StaticPropertyOracle.java
@@ -19,11 +19,13 @@
 import com.google.gwt.core.ext.PropertyOracle;
 import com.google.gwt.core.ext.TreeLogger;
 
+import java.io.Serializable;
+
 /**
  * An implementation of {@link PropertyOracle} that contains property values,
  * rather than computing them.
  */
-public class StaticPropertyOracle implements PropertyOracle {
+public class StaticPropertyOracle implements PropertyOracle, Serializable {
 
   private final ConfigurationProperty[] configProps;
 
diff --git a/dev/core/src/com/google/gwt/dev/jdt/RebindPermutationOracle.java b/dev/core/src/com/google/gwt/dev/jdt/RebindPermutationOracle.java
index 8108e76..a8b010c 100644
--- a/dev/core/src/com/google/gwt/dev/jdt/RebindPermutationOracle.java
+++ b/dev/core/src/com/google/gwt/dev/jdt/RebindPermutationOracle.java
@@ -29,6 +29,4 @@
    */
   String[] getAllPossibleRebindAnswers(TreeLogger logger, String sourceTypeName)
       throws UnableToCompleteException;
-
-  int getPermuationCount();
 }
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 c80e722..6e9e162 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JJSOptions.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JJSOptions.java
@@ -15,100 +15,13 @@
  */
 package com.google.gwt.dev.jjs;
 
+import com.google.gwt.dev.util.arg.OptionAggressivelyOptimize;
+import com.google.gwt.dev.util.arg.OptionEnableAssertions;
+import com.google.gwt.dev.util.arg.OptionScriptStyle;
+
 /**
  * Controls options for the {@link JavaToJavaScriptCompiler}.
  */
-public class JJSOptions {
-
-  private boolean aggressivelyOptimize = true;
-  private boolean enableAssertions = false;
-  private JsOutputOption output = JsOutputOption.OBFUSCATED;
-  private String soycOutputPath = null;
-  private boolean validateOnly = false;
-
-  public JJSOptions() {
-  }
-
-  public JJSOptions(JJSOptions other) {
-    copyFrom(other);
-  }
-
-  public void copyFrom(JJSOptions other) {
-    this.aggressivelyOptimize = other.aggressivelyOptimize;
-    this.enableAssertions = other.enableAssertions;
-    this.soycOutputPath = other.soycOutputPath;
-    this.output = other.output;
-    this.validateOnly = other.validateOnly;
-  }
-
-  /**
-   * Returns the output format setting.
-   */
-  public JsOutputOption getOutput() {
-    return output;
-  }
-
-  /**
-   * Returns the path of the SOYC output directory, if it has been set.
-   */
-  public String getSoycOutputDir() {
-    return soycOutputPath;
-  }
-
-  /**
-   * Returns true if the compiler should aggressively optimize.
-   */
-  public boolean isAggressivelyOptimize() {
-    return aggressivelyOptimize;
-  }
-
-  /**
-   * Returns true if the compiler should generate code to check assertions.
-   */
-  public boolean isEnableAssertions() {
-    return enableAssertions;
-  }
-
-  /**
-   * /** Returns true if the compiler should run in validation mode, not
-   * producing any output.
-   */
-  public boolean isValidateOnly() {
-    return validateOnly;
-  }
-
-  /**
-   * Sets whether or not the compiler should aggressively optimize.
-   */
-  public void setAggressivelyOptimize(boolean aggressivelyOptimize) {
-    this.aggressivelyOptimize = aggressivelyOptimize;
-  }
-
-  /**
-   * Sets whether or not the compiler should generate code to check assertions.
-   */
-  public void setEnableAssertions(boolean enableAssertions) {
-    this.enableAssertions = enableAssertions;
-  }
-
-  /**
-   * Sets the compiler output option.
-   */
-  public void setOutput(JsOutputOption output) {
-    this.output = output;
-  }
-
-  /**
-   * Sets the output path of the SOYC reports.
-   */
-  public void setSoycOutputDir(String path) {
-    this.soycOutputPath = path;
-  }
-
-  /**
-   * Sets whether or not the compiler should run in validation mode.
-   */
-  public void setValidateOnly(boolean validateOnly) {
-    this.validateOnly = validateOnly;
-  }
+public interface JJSOptions extends OptionAggressivelyOptimize,
+    OptionEnableAssertions, OptionScriptStyle {
 }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JJSOptionsImpl.java b/dev/core/src/com/google/gwt/dev/jjs/JJSOptionsImpl.java
new file mode 100644
index 0000000..8873c41
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/JJSOptionsImpl.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.jjs;
+
+import java.io.Serializable;
+
+/**
+ * Concrete class to implement all JJS options.
+ */
+public class JJSOptionsImpl implements JJSOptions, Serializable {
+
+  private boolean aggressivelyOptimize = true;
+  private boolean enableAssertions;
+  private JsOutputOption output = JsOutputOption.OBFUSCATED;
+
+  public JJSOptionsImpl() {
+  }
+
+  public JJSOptionsImpl(JJSOptions other) {
+    copyFrom(other);
+  }
+
+  public void copyFrom(JJSOptions other) {
+    setAggressivelyOptimize(other.isAggressivelyOptimize());
+    setEnableAssertions(other.isEnableAssertions());
+    setOutput(other.getOutput());
+  }
+
+  public JsOutputOption getOutput() {
+    return output;
+  }
+
+  public boolean isAggressivelyOptimize() {
+    return aggressivelyOptimize;
+  }
+
+  public boolean isEnableAssertions() {
+    return enableAssertions;
+  }
+
+  public void setAggressivelyOptimize(boolean aggressivelyOptimize) {
+    this.aggressivelyOptimize = aggressivelyOptimize;
+  }
+
+  public void setEnableAssertions(boolean enableAssertions) {
+    this.enableAssertions = enableAssertions;
+  }
+
+  public void setOutput(JsOutputOption output) {
+    this.output = output;
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
index edf1d27..10ef174 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -17,10 +17,10 @@
 
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
-import com.google.gwt.dev.jdt.RebindOracle;
 import com.google.gwt.dev.jdt.RebindPermutationOracle;
 import com.google.gwt.dev.jdt.WebModeCompilerFrontEnd;
 import com.google.gwt.dev.jjs.InternalCompilerException.NodeInfo;
+import com.google.gwt.dev.jjs.UnifiedAst.AST;
 import com.google.gwt.dev.jjs.ast.JBinaryOperation;
 import com.google.gwt.dev.jjs.ast.JBinaryOperator;
 import com.google.gwt.dev.jjs.ast.JClassType;
@@ -54,6 +54,7 @@
 import com.google.gwt.dev.jjs.impl.MethodInliner;
 import com.google.gwt.dev.jjs.impl.PostOptimizationCompoundAssignmentNormalizer;
 import com.google.gwt.dev.jjs.impl.Pruner;
+import com.google.gwt.dev.jjs.impl.RecordRebinds;
 import com.google.gwt.dev.jjs.impl.ReplaceRebinds;
 import com.google.gwt.dev.jjs.impl.ResolveRebinds;
 import com.google.gwt.dev.jjs.impl.TypeMap;
@@ -69,7 +70,6 @@
 import com.google.gwt.dev.js.JsSymbolResolver;
 import com.google.gwt.dev.js.JsUnusedFunctionRemover;
 import com.google.gwt.dev.js.JsVerboseNamer;
-import com.google.gwt.dev.js.SourceInfoHistogram;
 import com.google.gwt.dev.js.ast.JsProgram;
 import com.google.gwt.dev.util.DefaultTextOutput;
 import com.google.gwt.dev.util.PerfLogger;
@@ -80,23 +80,362 @@
 import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
 import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.TreeSet;
 
 /**
- * Compiles the Java <code>JProgram</code> representation into its corresponding
- * JavaScript source.
+ * Compiles the Java <code>JProgram</code> representation into its
+ * corresponding JavaScript source.
  */
 public class JavaToJavaScriptCompiler {
 
+  /**
+   * Compiles a particular permutation, based on a precompiled unified AST.
+   * 
+   * @param logger the logger to use
+   * @param unifiedAst the result of a
+   *          {@link #precompile(TreeLogger, WebModeCompilerFrontEnd, String[], JJSOptions, boolean)}
+   * @param rebindAnswers the set of rebind answers to resolve all outstanding
+   *          rebind decisions
+   * @return the output JavaScript
+   * @throws UnableToCompleteException if an error other than
+   *           {@link OutOfMemoryError} occurs
+   */
+  public static String compilePermutation(TreeLogger logger,
+      UnifiedAst unifiedAst, Map<String, String> rebindAnswers)
+      throws UnableToCompleteException {
+    try {
+      if (JProgram.isTracingEnabled()) {
+        System.out.println("------------------------------------------------------------");
+        System.out.println("|                     (new permuation)                     |");
+        System.out.println("------------------------------------------------------------");
+      }
+
+      AST ast = unifiedAst.getFreshAst();
+      JProgram jprogram = ast.getJProgram();
+      JsProgram jsProgram = ast.getJsProgram();
+      JJSOptions options = unifiedAst.getOptions();
+
+      ResolveRebinds.exec(jprogram, rebindAnswers);
+
+      // (4) Optimize the normalized Java AST for each permutation.
+      optimize(options, jprogram);
+
+      // (5) "Normalize" the high-level Java tree into a lower-level tree more
+      // suited for JavaScript code generation. Don't go reordering these
+      // willy-nilly because there are some subtle interdependencies.
+      LongCastNormalizer.exec(jprogram);
+      JsoDevirtualizer.exec(jprogram);
+      CatchBlockNormalizer.exec(jprogram);
+      PostOptimizationCompoundAssignmentNormalizer.exec(jprogram);
+      LongEmulationNormalizer.exec(jprogram);
+      CastNormalizer.exec(jprogram);
+      ArrayNormalizer.exec(jprogram);
+      EqualityNormalizer.exec(jprogram);
+
+      // (6) Perform further post-normalization optimizations
+      // Prune everything
+      Pruner.exec(jprogram, false);
+
+      // (7) Generate a JavaScript code DOM from the Java type declarations
+      jprogram.typeOracle.recomputeClinits();
+      GenerateJavaScriptAST.exec(jprogram, jsProgram, options.getOutput());
+
+      // Allow GC.
+      jprogram = null;
+
+      // (8) Normalize the JS AST.
+      // Fix invalid constructs created during JS AST gen.
+      JsNormalizer.exec(jsProgram);
+      // Resolve all unresolved JsNameRefs.
+      JsSymbolResolver.exec(jsProgram);
+
+      // (9) Optimize the JS AST.
+      if (options.isAggressivelyOptimize()) {
+        boolean didChange;
+        do {
+          if (Thread.interrupted()) {
+            throw new InterruptedException();
+          }
+
+          didChange = false;
+          // Remove unused functions, possible
+          didChange = JsStaticEval.exec(jsProgram) || didChange;
+          // Inline JavaScript function invocations
+          didChange = JsInliner.exec(jsProgram) || didChange;
+          // Remove unused functions, possible
+          didChange = JsUnusedFunctionRemover.exec(jsProgram) || didChange;
+        } while (didChange);
+      }
+
+      // (10) Obfuscate
+      switch (options.getOutput()) {
+        case OBFUSCATED:
+          JsStringInterner.exec(jsProgram);
+          JsObfuscateNamer.exec(jsProgram);
+          break;
+        case PRETTY:
+          // We don't intern strings in pretty mode to improve readability
+          JsPrettyNamer.exec(jsProgram);
+          break;
+        case DETAILED:
+          JsStringInterner.exec(jsProgram);
+          JsVerboseNamer.exec(jsProgram);
+          break;
+        default:
+          throw new InternalCompilerException("Unknown output mode");
+      }
+
+      // (11) Perform any post-obfuscation normalizations.
+
+      // Work around an IE7 bug,
+      // http://code.google.com/p/google-web-toolkit/issues/detail?id=1440
+      JsIEBlockSizeVisitor.exec(jsProgram);
+
+      // (12) Generate the final output text.
+      DefaultTextOutput out = new DefaultTextOutput(
+          options.getOutput().shouldMinimize());
+      JsSourceGenerationVisitor v = new JsSourceGenerationVisitor(out);
+      v.accept(jsProgram);
+      return out.toString();
+    } catch (Throwable e) {
+      throw logAndTranslateException(logger, e);
+    }
+  }
+
+  /**
+   * Performs a precompilation, returning a unified AST.
+   * 
+   * @param logger the logger to use
+   * @param compilerFrontEnd the compiler front ent
+   * @param declEntryPts the set of entry classes
+   * @param options the compiler options
+   * @param singlePermutation if true, do not pre-optimize the resulting AST or
+   *          allow serialization of the result
+   * @return the unified AST used to drive permutation compiles
+   * @throws UnableToCompleteException if an error other than
+   *           {@link OutOfMemoryError} occurs
+   */
+  public static UnifiedAst precompile(TreeLogger logger,
+      WebModeCompilerFrontEnd compilerFrontEnd, String[] declEntryPts,
+      JJSOptions options, boolean singlePermutation)
+      throws UnableToCompleteException {
+
+    if (declEntryPts.length == 0) {
+      throw new IllegalArgumentException("entry point(s) required");
+    }
+
+    RebindPermutationOracle rpo = compilerFrontEnd.getRebindPermutationOracle();
+
+    // Find all the possible rebound entry points.
+    Set<String> allEntryPoints = new TreeSet<String>();
+    for (String element : declEntryPts) {
+      String[] all = rpo.getAllPossibleRebindAnswers(logger, element);
+      Util.addAll(allEntryPoints, all);
+    }
+    allEntryPoints.addAll(JProgram.CODEGEN_TYPES_SET);
+    allEntryPoints.addAll(JProgram.INDEX_TYPES_SET);
+
+    // Compile the source and get the compiler so we can get the parse tree
+    //
+    CompilationUnitDeclaration[] goldenCuds = compilerFrontEnd.getCompilationUnitDeclarations(
+        logger, allEntryPoints.toArray(new String[0]));
+
+    // Check for compilation problems. We don't log here because any problems
+    // found here will have already been logged by AbstractCompiler.
+    //
+    checkForErrors(logger, goldenCuds, false);
+
+    PerfLogger.start("Build AST");
+    JProgram jprogram = new JProgram();
+    JsProgram jsProgram = new JsProgram();
+
+    try {
+      long usedMemoryBefore = singlePermutation ? 0 : getUsedMemory();
+
+      /*
+       * (1) Build a flattened map of TypeDeclarations => JType. The resulting
+       * map contains entries for all reference types. BuildTypeMap also parses
+       * all JSNI.
+       */
+      TypeMap typeMap = new TypeMap(jprogram);
+      TypeDeclaration[] allTypeDeclarations = BuildTypeMap.exec(typeMap,
+          goldenCuds, jsProgram);
+
+      // BuildTypeMap can uncover syntactic JSNI errors; report & abort
+      checkForErrors(logger, goldenCuds, true);
+
+      // Compute all super type/sub type info
+      jprogram.typeOracle.computeBeforeAST();
+
+      // (2) Create our own Java AST from the JDT AST.
+      GenerateJavaAST.exec(allTypeDeclarations, typeMap, jprogram, jsProgram,
+          options.isEnableAssertions());
+
+      long usedMemoryAfter = singlePermutation ? 0 : getUsedMemory();
+      long memoryDelta = usedMemoryAfter - usedMemoryBefore;
+      long astMemoryUsage = (long) (memoryDelta * 1.5);
+
+      // GenerateJavaAST can uncover semantic JSNI errors; report & abort
+      checkForErrors(logger, goldenCuds, true);
+
+      // Allow GC
+      goldenCuds = null;
+      typeMap = null;
+      allTypeDeclarations = null;
+
+      // (3) Perform Java AST normalizations.
+
+      FixAssignmentToUnbox.exec(jprogram);
+
+      /*
+       * TODO: If we defer this until later, we could maybe use the results of
+       * the assertions to enable more optimizations.
+       */
+      if (options.isEnableAssertions()) {
+        // Turn into assertion checking calls.
+        AssertionNormalizer.exec(jprogram);
+      } else {
+        // Remove all assert statements.
+        AssertionRemover.exec(jprogram);
+      }
+
+      // Replace GWT.create calls with JGwtCreate nodes.
+      ReplaceRebinds.exec(logger, jprogram, rpo);
+
+      // Resolve entry points, rebinding non-static entry points.
+      findEntryPoints(logger, rpo, declEntryPts, jprogram);
+
+      // Replace references to JSO subtypes with JSO itself.
+      JavaScriptObjectNormalizer.exec(jprogram);
+
+      /*
+       * (4) Optimize the normalized Java AST for the common AST. By doing
+       * optimizations early in the multiple permutation scenario, we're saving
+       * work. However, we can't fully optimize because we don't yet know the
+       * deferred binding decisions.
+       * 
+       * Don't bother optimizing early if there's only one permutation.
+       */
+      if (!singlePermutation) {
+        optimize(options, jprogram);
+      }
+
+      Set<String> rebindRequests = new HashSet<String>();
+      RecordRebinds.exec(jprogram, rebindRequests);
+
+      return new UnifiedAst(options, new AST(jprogram, jsProgram),
+          singlePermutation, astMemoryUsage, rebindRequests);
+    } catch (Throwable e) {
+      throw logAndTranslateException(logger, e);
+    } finally {
+      PerfLogger.end();
+    }
+  }
+
+  protected static void optimize(JJSOptions options, JProgram jprogram)
+      throws InterruptedException {
+    /*
+     * Record the beginning of optimizations; this turns on certain checks that
+     * guard against problematic late construction of things like class
+     * literals.
+     */
+    jprogram.beginOptimizations();
+
+    boolean didChange;
+    do {
+      if (Thread.interrupted()) {
+        throw new InterruptedException();
+      }
+
+      // Recompute clinits each time, they can become empty.
+      jprogram.typeOracle.recomputeClinits();
+
+      didChange = false;
+      // Remove unreferenced types, fields, methods, [params, locals]
+      didChange = Pruner.exec(jprogram, true) || didChange;
+      // finalize locals, params, fields, methods, classes
+      didChange = Finalizer.exec(jprogram) || didChange;
+      // rewrite non-polymorphic calls as static calls; update all call sites
+      didChange = MakeCallsStatic.exec(jprogram) || didChange;
+
+      // type flow tightening
+      // - fields, locals based on assignment
+      // - params based on assignment and call sites
+      // - method bodies based on return statements
+      // - polymorphic methods based on return types of all implementors
+      // - optimize casts and instance of
+      didChange = TypeTightener.exec(jprogram) || didChange;
+
+      // tighten method call bindings
+      didChange = MethodCallTightener.exec(jprogram) || didChange;
+
+      // dead code removal??
+      didChange = DeadCodeElimination.exec(jprogram) || didChange;
+
+      if (options.isAggressivelyOptimize()) {
+        // inlining
+        didChange = MethodInliner.exec(jprogram) || didChange;
+      }
+      // prove that any types that have been culled from the main tree are
+      // unreferenced due to type tightening?
+    } while (didChange);
+  }
+
+  private static void checkForErrors(TreeLogger logger,
+      CompilationUnitDeclaration[] cuds, boolean itemizeErrors)
+      throws UnableToCompleteException {
+    boolean compilationFailed = false;
+    if (cuds.length == 0) {
+      compilationFailed = true;
+    }
+    Set<IProblem> problemSet = new HashSet<IProblem>();
+    for (CompilationUnitDeclaration cud : cuds) {
+      CompilationResult result = cud.compilationResult();
+      if (result.hasErrors()) {
+        compilationFailed = true;
+        // Early out if we don't need to itemize.
+        if (!itemizeErrors) {
+          break;
+        }
+        TreeLogger branch = logger.branch(TreeLogger.ERROR, "Errors in "
+            + String.valueOf(result.getFileName()), null);
+        IProblem[] errors = result.getErrors();
+        for (IProblem problem : errors) {
+          if (problemSet.contains(problem)) {
+            continue;
+          }
+
+          problemSet.add(problem);
+
+          // Strip the initial code from each error.
+          //
+          String msg = problem.toString();
+          msg = msg.substring(msg.indexOf(' '));
+
+          // Append 'file (line): msg' to the error message.
+          //
+          int line = problem.getSourceLineNumber();
+          StringBuffer msgBuf = new StringBuffer();
+          msgBuf.append("Line ");
+          msgBuf.append(line);
+          msgBuf.append(": ");
+          msgBuf.append(msg);
+          branch.log(TreeLogger.ERROR, msgBuf.toString(), null);
+        }
+      }
+    }
+    if (compilationFailed) {
+      logger.log(TreeLogger.ERROR, "Cannot proceed due to previous errors",
+          null);
+      throw new UnableToCompleteException();
+    }
+  }
+
   private static JMethodCall createReboundModuleLoad(TreeLogger logger,
       JProgram program, JReferenceType reboundEntryType,
       String originalMainClassName) throws UnableToCompleteException {
@@ -127,9 +466,9 @@
               + originalMainClassName + "' must not be abstract", null);
       throw new UnableToCompleteException();
     }
-
     SourceInfo sourceInfo = reboundEntryType.getSourceInfo().makeChild(
         JavaToJavaScriptCompiler.class, "Rebound entry point");
+
     JExpression qualifier = null;
     if (!entryMethod.isStatic()) {
       qualifier = JGwtCreate.createInstantiationExpression(program, sourceInfo,
@@ -151,11 +490,10 @@
   private static void findEntryPoints(TreeLogger logger,
       RebindPermutationOracle rpo, String[] mainClassNames, JProgram program)
       throws UnableToCompleteException {
-    JMethod bootStrapMethod = program.createMethod(
-        program.createSourceInfoSynthetic(JavaToJavaScriptCompiler.class,
-            "Bootstrap method"), "init".toCharArray(),
-        program.getIndexedType("EntryMethodHolder"), program.getTypeVoid(),
-        false, true, true, false, false);
+    SourceInfo sourceInfo = program.createSourceInfoSynthetic(JavaToJavaScriptCompiler.class,
+        "Bootstrap method");
+    JMethod bootStrapMethod = program.createMethod(sourceInfo, "init".toCharArray(),
+        null, program.getTypeVoid(), false, true, true, false, false);
     bootStrapMethod.freezeParamTypes();
 
     JMethodBody body = (JMethodBody) bootStrapMethod.getBody();
@@ -232,466 +570,15 @@
   }
 
   private static long getUsedMemory() {
+    System.gc();
     long used = Runtime.getRuntime().totalMemory()
         - Runtime.getRuntime().freeMemory();
     assert (used > 0);
     return used;
   }
 
-  /**
-   * Create a variable assignment to invoke a call to the statistics collector.
-   * 
-   * <pre>
-   * Stats.isStatsAvailable() &&
-   *   Stats.onModuleStart("mainClassName");
-   * </pre>
-   */
-  private static JStatement makeStatsCalls(JProgram program,
-      String mainClassName) {
-    SourceInfo sourceInfo = program.createSourceInfoSynthetic(
-        JavaToJavaScriptCompiler.class, "onModuleStart() stats call");
-    JMethod isStatsAvailableMethod = program.getIndexedMethod("Stats.isStatsAvailable");
-    JMethod onModuleStartMethod = program.getIndexedMethod("Stats.onModuleStart");
-
-    JMethodCall availableCall = new JMethodCall(program, sourceInfo, null,
-        isStatsAvailableMethod);
-    JMethodCall onModuleStartCall = new JMethodCall(program, sourceInfo, null,
-        onModuleStartMethod);
-    onModuleStartCall.getArgs().add(
-        program.getLiteralString(sourceInfo, mainClassName));
-
-    JBinaryOperation amp = new JBinaryOperation(program, sourceInfo,
-        program.getTypePrimitiveBoolean(), JBinaryOperator.AND, availableCall,
-        onModuleStartCall);
-
-    return amp.makeStatement();
-  }
-
-  private final long astMemoryUsage;
-  private final String[] declEntryPoints;
-  private final Object myLockObject = new Object();
-  private final JJSOptions options;
-  private final Set<IProblem> problemSet = new HashSet<IProblem>();
-  private JProgram savedJProgram = null;
-  private JsProgram savedJsProgram = null;
-  private final byte[] serializedAst;
-
-  public JavaToJavaScriptCompiler(TreeLogger logger,
-      WebModeCompilerFrontEnd compiler, String[] declEntryPts)
-      throws UnableToCompleteException {
-    this(logger, compiler, declEntryPts, new JJSOptions());
-  }
-
-  public JavaToJavaScriptCompiler(TreeLogger logger,
-      WebModeCompilerFrontEnd compiler, String[] declEntryPts,
-      JJSOptions compilerOptions) throws UnableToCompleteException {
-
-    if (declEntryPts.length == 0) {
-      throw new IllegalArgumentException("entry point(s) required");
-    }
-
-    this.options = new JJSOptions(compilerOptions);
-
-    // Remember these for subsequent compiles.
-    //
-    this.declEntryPoints = declEntryPts;
-
-    RebindPermutationOracle rpo = compiler.getRebindPermutationOracle();
-
-    if (!options.isValidateOnly()) {
-      // Find all the possible rebound entry points.
-      Set<String> allEntryPoints = new TreeSet<String>();
-      for (String element : declEntryPts) {
-        String[] all = rpo.getAllPossibleRebindAnswers(logger, element);
-        Util.addAll(allEntryPoints, all);
-      }
-      allEntryPoints.addAll(JProgram.CODEGEN_TYPES_SET);
-      allEntryPoints.addAll(JProgram.INDEX_TYPES_SET);
-      declEntryPts = allEntryPoints.toArray(new String[0]);
-    }
-
-    // Compile the source and get the compiler so we can get the parse tree
-    //
-    CompilationUnitDeclaration[] goldenCuds = compiler.getCompilationUnitDeclarations(
-        logger, declEntryPts);
-
-    // Check for compilation problems. We don't log here because any problems
-    // found here will have already been logged by AbstractCompiler.
-    //
-    checkForErrors(logger, goldenCuds, false);
-
-    PerfLogger.start("Build AST");
-    boolean enableDescendants = compilerOptions.getSoycOutputDir() != null;
-    JProgram jprogram = savedJProgram = new JProgram();
-    jprogram.setEnableSourceInfoDescendants(enableDescendants);
-    JsProgram jsProgram = savedJsProgram = new JsProgram();
-    jsProgram.setEnableSourceInfoDescendants(enableDescendants);
-
-    long memoryDelta;
-    try {
-      System.gc();
-      long usedMemoryBefore = getUsedMemory();
-      /*
-       * (1) Build a flattened map of TypeDeclarations => JType. The resulting
-       * map contains entries for all reference types. BuildTypeMap also parses
-       * all JSNI.
-       */
-      TypeMap typeMap = new TypeMap(jprogram);
-      TypeDeclaration[] allTypeDeclarations = BuildTypeMap.exec(typeMap,
-          goldenCuds, jsProgram);
-
-      // BuildTypeMap can uncover syntactic JSNI errors; report & abort
-      checkForErrors(logger, goldenCuds, true);
-
-      // Compute all super type/sub type info
-      jprogram.typeOracle.computeBeforeAST();
-
-      // (2) Create our own Java AST from the JDT AST.
-      GenerateJavaAST.exec(allTypeDeclarations, typeMap, jprogram, jsProgram,
-          options.isEnableAssertions());
-
-      System.gc();
-      long usedMemoryAfter = getUsedMemory();
-      memoryDelta = usedMemoryAfter - usedMemoryBefore;
-      long localAstMemoryUsage = (long) (memoryDelta * 1.5);
-
-      // GenerateJavaAST can uncover semantic JSNI errors; report & abort
-      checkForErrors(logger, goldenCuds, true);
-
-      // Allow GC
-      goldenCuds = null;
-      typeMap = null;
-      allTypeDeclarations = null;
-
-      // (3) Perform Java AST normalizations.
-
-      FixAssignmentToUnbox.exec(jprogram);
-
-      /*
-       * TODO: If we defer this until later, we could maybe use the results of
-       * the assertions to enable more optimizations.
-       */
-      if (options.isEnableAssertions()) {
-        // Turn into assertion checking calls.
-        AssertionNormalizer.exec(jprogram);
-      } else {
-        // Remove all assert statements.
-        AssertionRemover.exec(jprogram);
-      }
-
-      // Replace GWT.create calls with JGwtCreate nodes.
-      ReplaceRebinds.exec(logger, jprogram, rpo);
-
-      // Resolve entry points, rebinding non-static entry points.
-      findEntryPoints(logger, rpo, declEntryPoints, jprogram);
-
-      // Replace references to JSO subtypes with JSO itself.
-      JavaScriptObjectNormalizer.exec(jprogram);
-
-      /*
-       * (4) Optimize the normalized Java AST for the common AST. By doing
-       * optimizations early in the multiple permutation scenario, we're saving
-       * work. However, we can't fully optimized because we don't yet know the
-       * deferred binding decisions.
-       * 
-       * Don't bother optimizing early if there's only one permutation.
-       */
-      if (rpo.getPermuationCount() > 1) {
-        optimize(jprogram);
-
-        PerfLogger.start("serialize");
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        ObjectOutputStream os = new ObjectOutputStream(baos);
-        os.writeObject(jprogram);
-        os.writeObject(jsProgram);
-        os.close();
-        serializedAst = baos.toByteArray();
-        PerfLogger.end();
-
-        // Very rough heuristic.
-        this.astMemoryUsage = Math.max(localAstMemoryUsage,
-            serializedAst.length * 4);
-        logger.log(TreeLogger.TRACE, "Estimated AST memory usage: "
-            + astMemoryUsage + " = Math.max(" + memoryDelta + " * 1.5, "
-            + serializedAst.length + " * 4)");
-      } else {
-        this.astMemoryUsage = localAstMemoryUsage;
-        this.serializedAst = null;
-      }
-    } catch (IOException e) {
-      throw new RuntimeException(
-          "Should be impossible to get an IOException reading an in-memory stream",
-          e);
-    } catch (Throwable e) {
-      throw logAndTranslateException(logger, e);
-    } finally {
-      PerfLogger.end();
-      synchronized (myLockObject) {
-        /*
-         * JLS 17.4.4: ensure all changes are visible to any other thread
-         * calling compile.
-         * 
-         * TODO: is this necessary?
-         */
-      }
-    }
-  }
-
-  /**
-   * Creates finished JavaScript source code from the specified Java compilation
-   * units.
-   */
-  public String compile(TreeLogger logger, RebindOracle rebindOracle)
-      throws UnableToCompleteException {
-
-    JProgram jprogram = null;
-    JsProgram jsProgram = null;
-
-    synchronized (myLockObject) {
-      if (savedJProgram != null && savedJsProgram != null) {
-        jprogram = savedJProgram;
-        jsProgram = savedJsProgram;
-        savedJProgram = null;
-        savedJsProgram = null;
-      } else {
-        if (serializedAst == null) {
-          throw new IllegalStateException("No serialized AST was cached.");
-        }
-        try {
-          /*
-           * Force all AST deserializations to occur in sequence; this reduces
-           * the chance of multiple threads going OOM at the same time.
-           */
-          synchronized (myLockObject) {
-            PerfLogger.start("deserialize");
-            ByteArrayInputStream bais = new ByteArrayInputStream(serializedAst);
-            ObjectInputStream is;
-            is = new ObjectInputStream(bais);
-            jprogram = (JProgram) is.readObject();
-            jsProgram = (JsProgram) is.readObject();
-            PerfLogger.end();
-          }
-        } catch (IOException e) {
-          throw new RuntimeException(
-              "Should be impossible for memory based streams", e);
-        } catch (ClassNotFoundException e) {
-          throw new RuntimeException(
-              "Should be impossible when deserializing in process", e);
-        }
-      }
-    }
-
-    try {
-      return doCompile(logger, jprogram, jsProgram, rebindOracle);
-    } catch (Throwable e) {
-      // Allow GC before logging exception in case it was an OOM.
-      jprogram = null;
-      jsProgram = null;
-      throw logAndTranslateException(logger, e);
-    }
-  }
-
-  public long getAstMemoryUsage() {
-    return astMemoryUsage;
-  }
-
-  protected String doCompile(TreeLogger logger, JProgram jprogram,
-      JsProgram jsProgram, RebindOracle rebindOracle)
-      throws InterruptedException {
-    if (JProgram.isTracingEnabled()) {
-      System.out.println("------------------------------------------------------------");
-      System.out.println("|                     (new permuation)                     |");
-      System.out.println("------------------------------------------------------------");
-    }
-
-    ResolveRebinds.exec(logger, jprogram, rebindOracle);
-
-    // (4) Optimize the normalized Java AST for each permutation.
-    optimize(jprogram);
-
-    // (5) "Normalize" the high-level Java tree into a lower-level tree more
-    // suited for JavaScript code generation. Don't go reordering these
-    // willy-nilly because there are some subtle interdependencies.
-    LongCastNormalizer.exec(jprogram);
-    JsoDevirtualizer.exec(jprogram);
-    CatchBlockNormalizer.exec(jprogram);
-    PostOptimizationCompoundAssignmentNormalizer.exec(jprogram);
-    LongEmulationNormalizer.exec(jprogram);
-    CastNormalizer.exec(jprogram);
-    ArrayNormalizer.exec(jprogram);
-    EqualityNormalizer.exec(jprogram);
-
-    // (6) Perform further post-normalization optimizations
-    // Prune everything
-    Pruner.exec(jprogram, false);
-
-    // (7) Generate a JavaScript code DOM from the Java type declarations
-    jprogram.typeOracle.recomputeClinits();
-    GenerateJavaScriptAST.exec(jprogram, jsProgram, options.getOutput());
-
-    // Allow GC.
-    jprogram = null;
-
-    // (8) Normalize the JS AST.
-    // Fix invalid constructs created during JS AST gen.
-    JsNormalizer.exec(jsProgram);
-    // Resolve all unresolved JsNameRefs.
-    JsSymbolResolver.exec(jsProgram);
-
-    // (9) Optimize the JS AST.
-    if (options.isAggressivelyOptimize()) {
-      boolean didChange;
-      do {
-        if (Thread.interrupted()) {
-          throw new InterruptedException();
-        }
-
-        didChange = false;
-        // Remove unused functions, possible
-        didChange = JsStaticEval.exec(jsProgram) || didChange;
-        // Inline JavaScript function invocations
-        didChange = JsInliner.exec(jsProgram) || didChange;
-        // Remove unused functions, possible
-        didChange = JsUnusedFunctionRemover.exec(jsProgram) || didChange;
-      } while (didChange);
-    }
-
-    // (10) Obfuscate
-    switch (options.getOutput()) {
-      case OBFUSCATED:
-        JsStringInterner.exec(jsProgram);
-        JsObfuscateNamer.exec(jsProgram);
-        break;
-      case PRETTY:
-        // We don't intern strings in pretty mode to improve readability
-        JsPrettyNamer.exec(jsProgram);
-        break;
-      case DETAILED:
-        JsStringInterner.exec(jsProgram);
-        JsVerboseNamer.exec(jsProgram);
-        break;
-      default:
-        throw new InternalCompilerException("Unknown output mode");
-    }
-
-    // (11) Perform any post-obfuscation normalizations.
-
-    // Work around an IE7 bug,
-    // http://code.google.com/p/google-web-toolkit/issues/detail?id=1440
-    JsIEBlockSizeVisitor.exec(jsProgram);
-
-    // Write the SOYC reports into the output
-    if (options.getSoycOutputDir() != null) {
-      SourceInfoHistogram.exec(jsProgram, options.getSoycOutputDir());
-    }
-
-    // (12) Generate the final output text.
-    DefaultTextOutput out = new DefaultTextOutput(
-        options.getOutput().shouldMinimize());
-    JsSourceGenerationVisitor v = new JsSourceGenerationVisitor(out);
-    v.accept(jsProgram);
-    return out.toString();
-  }
-
-  protected void optimize(JProgram jprogram) throws InterruptedException {
-    /*
-     * Record the beginning of optimations; this turns on certain checks that
-     * guard against problematic late construction of things like class
-     * literals.
-     */
-    jprogram.beginOptimizations();
-
-    boolean didChange;
-    do {
-      if (Thread.interrupted()) {
-        throw new InterruptedException();
-      }
-
-      // Recompute clinits each time, they can become empty.
-      jprogram.typeOracle.recomputeClinits();
-
-      didChange = false;
-      // Remove unreferenced types, fields, methods, [params, locals]
-      didChange = Pruner.exec(jprogram, true) || didChange;
-      // finalize locals, params, fields, methods, classes
-      didChange = Finalizer.exec(jprogram) || didChange;
-      // rewrite non-polymorphic calls as static calls; update all call sites
-      didChange = MakeCallsStatic.exec(jprogram) || didChange;
-
-      // type flow tightening
-      // - fields, locals based on assignment
-      // - params based on assignment and call sites
-      // - method bodies based on return statements
-      // - polymorphic methods based on return types of all implementors
-      // - optimize casts and instance of
-      didChange = TypeTightener.exec(jprogram) || didChange;
-
-      // tighten method call bindings
-      didChange = MethodCallTightener.exec(jprogram) || didChange;
-
-      // dead code removal??
-      didChange = DeadCodeElimination.exec(jprogram) || didChange;
-
-      if (options.isAggressivelyOptimize()) {
-        // inlining
-        didChange = MethodInliner.exec(jprogram) || didChange;
-      }
-      // prove that any types that have been culled from the main tree are
-      // unreferenced due to type tightening?
-    } while (didChange);
-  }
-
-  private void checkForErrors(TreeLogger logger,
-      CompilationUnitDeclaration[] cuds, boolean itemizeErrors)
-      throws UnableToCompleteException {
-    boolean compilationFailed = false;
-    if (cuds.length == 0) {
-      compilationFailed = true;
-    }
-    for (CompilationUnitDeclaration cud : cuds) {
-      CompilationResult result = cud.compilationResult();
-      if (result.hasErrors()) {
-        compilationFailed = true;
-        // Early out if we don't need to itemize.
-        if (!itemizeErrors) {
-          break;
-        }
-        TreeLogger branch = logger.branch(TreeLogger.ERROR, "Errors in "
-            + String.valueOf(result.getFileName()), null);
-        IProblem[] errors = result.getErrors();
-        for (IProblem problem : errors) {
-          if (problemSet.contains(problem)) {
-            continue;
-          }
-
-          problemSet.add(problem);
-
-          // Strip the initial code from each error.
-          //
-          String msg = problem.toString();
-          msg = msg.substring(msg.indexOf(' '));
-
-          // Append 'file (line): msg' to the error message.
-          //
-          int line = problem.getSourceLineNumber();
-          StringBuffer msgBuf = new StringBuffer();
-          msgBuf.append("Line ");
-          msgBuf.append(line);
-          msgBuf.append(": ");
-          msgBuf.append(msg);
-          branch.log(TreeLogger.ERROR, msgBuf.toString(), null);
-        }
-      }
-    }
-    if (compilationFailed) {
-      logger.log(TreeLogger.ERROR, "Cannot proceed due to previous errors",
-          null);
-      throw new UnableToCompleteException();
-    }
-  }
-
-  private UnableToCompleteException logAndTranslateException(TreeLogger logger,
-      Throwable e) {
+  private static UnableToCompleteException logAndTranslateException(
+      TreeLogger logger, Throwable e) {
     if (e instanceof UnableToCompleteException) {
       // just rethrow
       return (UnableToCompleteException) e;
@@ -732,4 +619,33 @@
       return new UnableToCompleteException();
     }
   }
+
+  /**
+   * Create a variable assignment to invoke a call to the statistics collector.
+   * 
+   * <pre>
+   * Stats.isStatsAvailable() &&
+   *   Stats.onModuleStart("mainClassName");
+   * </pre>
+   */
+  private static JStatement makeStatsCalls(JProgram program,
+      String mainClassName) {
+    SourceInfo sourceInfo = program.createSourceInfoSynthetic(
+        JavaToJavaScriptCompiler.class, "onModuleStart() stats call");
+    JMethod isStatsAvailableMethod = program.getIndexedMethod("Stats.isStatsAvailable");
+    JMethod onModuleStartMethod = program.getIndexedMethod("Stats.onModuleStart");
+
+    JMethodCall availableCall = new JMethodCall(program, sourceInfo, null,
+        isStatsAvailableMethod);
+    JMethodCall onModuleStartCall = new JMethodCall(program, sourceInfo, null,
+        onModuleStartMethod);
+    onModuleStartCall.getArgs().add(program.getLiteralString(sourceInfo, mainClassName));
+
+    JBinaryOperation amp = new JBinaryOperation(program, sourceInfo,
+        program.getTypePrimitiveBoolean(), JBinaryOperator.AND, availableCall,
+        onModuleStartCall);
+
+    return amp.makeStatement();
+  }
+
 }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/UnifiedAst.java b/dev/core/src/com/google/gwt/dev/jjs/UnifiedAst.java
new file mode 100644
index 0000000..a5e85b0
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/UnifiedAst.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.jjs;
+
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.js.ast.JsProgram;
+import com.google.gwt.dev.util.PerfLogger;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * Represents a unified, non-permutation specific AST. This AST is used to drive
+ * per-permutation compiles.
+ */
+public final class UnifiedAst implements Serializable {
+
+  /**
+   * Encapsulates the combined programs.
+   */
+  static final class AST {
+    private JProgram jProgram;
+    private JsProgram jsProgram;
+
+    public AST(JProgram jProgram, JsProgram jsProgram) {
+      this.jProgram = jProgram;
+      this.jsProgram = jsProgram;
+    }
+
+    JProgram getJProgram() {
+      return jProgram;
+    }
+
+    JsProgram getJsProgram() {
+      return jsProgram;
+    }
+  }
+
+  /**
+   * Estimated AST memory usage.
+   */
+  private long astMemoryUsage;
+
+  /**
+   * The original AST; nulled out once consumed (by the first call to
+   * {@link #getFreshAst()}.
+   */
+  private transient AST initialAst;
+
+  /**
+   * Used for internal synchronization.
+   */
+  private transient Object myLockObject = new Object();
+
+  /**
+   * The compilation options.
+   */
+  private final JJSOptions options;
+
+  /**
+   * The set of all live rebind request types in the AST.
+   */
+  private final SortedSet<String> rebindRequests;
+
+  /**
+   * The serialized form of savedAst.
+   */
+  private byte[] serializedAst;
+
+  /**
+   * If <code>true</code>, only one permutation will be run, so we don't need
+   * to serialize our AST (unless this whole object is about to be serialized).
+   */
+  private transient boolean singlePermutation;
+
+  public UnifiedAst(JJSOptions options, AST initialAst,
+      boolean singlePermutation, long astMemoryUsage, Set<String> rebindRequests) {
+    this.options = new JJSOptionsImpl(options);
+    this.initialAst = initialAst;
+    this.singlePermutation = singlePermutation;
+    this.astMemoryUsage = astMemoryUsage;
+    this.rebindRequests = Collections.unmodifiableSortedSet(new TreeSet<String>(
+        rebindRequests));
+  }
+
+  /**
+   * Returns a rough estimate of how much memory an AST will take up.
+   */
+  public long getAstMemoryUsage() {
+    return astMemoryUsage;
+  }
+
+  /**
+   * Returns the active set of JJS options associated with this compile.
+   */
+  public JJSOptions getOptions() {
+    return new JJSOptionsImpl(options);
+  }
+
+  /**
+   * Returns the set of live rebind requests in the AST.
+   */
+  public SortedSet<String> getRebindRequests() {
+    return rebindRequests;
+  }
+
+  AST getFreshAst() {
+    synchronized (myLockObject) {
+      if (initialAst != null) {
+        if (!singlePermutation && serializedAst == null) {
+          // Must preserve a serialized copy for future calls.
+          serializeAst();
+        }
+        AST result = initialAst;
+        initialAst = null;
+        return result;
+      } else {
+        if (serializedAst == null) {
+          throw new IllegalStateException("No serialized AST was cached.");
+        }
+        return deserializeAst();
+      }
+    }
+  }
+
+  private AST deserializeAst() {
+    try {
+      PerfLogger.start("deserialize");
+      ByteArrayInputStream bais = new ByteArrayInputStream(serializedAst);
+      ObjectInputStream is;
+      is = new ObjectInputStream(bais);
+      JProgram jprogram = (JProgram) is.readObject();
+      JsProgram jsProgram = (JsProgram) is.readObject();
+      return new AST(jprogram, jsProgram);
+    } catch (IOException e) {
+      throw new RuntimeException(
+          "Should be impossible for memory based streams", e);
+    } catch (ClassNotFoundException e) {
+      throw new RuntimeException(
+          "Should be impossible when deserializing in process", e);
+    } finally {
+      PerfLogger.end();
+    }
+  }
+
+  /**
+   * Re-initialize lock object.
+   */
+  private Object readResolve() {
+    myLockObject = new Object();
+    return this;
+  }
+
+  private void serializeAst() {
+    try {
+      assert (initialAst != null);
+      assert (serializedAst == null);
+      PerfLogger.start("serialize");
+      ByteArrayOutputStream baos = new ByteArrayOutputStream();
+      ObjectOutputStream os = new ObjectOutputStream(baos);
+      os.writeObject(initialAst.getJProgram());
+      os.writeObject(initialAst.getJsProgram());
+      os.close();
+      serializedAst = baos.toByteArray();
+
+      // Very rough heuristic.
+      astMemoryUsage = Math.max(astMemoryUsage, serializedAst.length * 4);
+    } catch (IOException e) {
+      throw new RuntimeException(
+          "Should be impossible for memory based streams", e);
+    } finally {
+      PerfLogger.end();
+    }
+  }
+
+  /**
+   * Force byte serialization of AST before writing.
+   */
+  private Object writeReplace() {
+    synchronized (myLockObject) {
+      if (serializedAst == null) {
+        serializeAst();
+      }
+    }
+    return this;
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/MethodInliner.java b/dev/core/src/com/google/gwt/dev/jjs/impl/MethodInliner.java
index eaab6ad..3a86ae0 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/MethodInliner.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/MethodInliner.java
@@ -116,6 +116,9 @@
             && !stmts.isEmpty()) {
           // clinit() calls cannot be inlined unless they are empty
           possibleToInline = false;
+        } else if (!body.locals.isEmpty()) {
+          // methods with local variables cannot be inlined
+          possibleToInline = false;
         } else {
           JMultiExpression multi = createMultiExpressionFromBody(body,
               ignoringReturnValueFor == x);
@@ -474,6 +477,7 @@
       this.method = method;
     }
 
+    @Override
     public void endVisit(JMethodCall x, Context ctx) {
       if (x.getTarget() == method) {
         isRecursive = true;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/Pruner.java b/dev/core/src/com/google/gwt/dev/jjs/impl/Pruner.java
index f3bb111..ce55dad 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/Pruner.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/Pruner.java
@@ -479,7 +479,7 @@
     @Override
     public boolean visit(JBinaryOperation x, Context ctx) {
       // special string concat handling
-      if (x.getOp() == JBinaryOperator.ADD
+      if ((x.getOp() == JBinaryOperator.ADD || x.getOp() == JBinaryOperator.ASG_ADD)
           && x.getType() == program.getTypeJavaLangString()) {
         rescueByConcat(x.getLhs().getType());
         rescueByConcat(x.getRhs().getType());
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/RecordRebinds.java b/dev/core/src/com/google/gwt/dev/jjs/impl/RecordRebinds.java
new file mode 100644
index 0000000..489043b
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/RecordRebinds.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.jjs.impl;
+
+import com.google.gwt.dev.jjs.ast.Context;
+import com.google.gwt.dev.jjs.ast.JGwtCreate;
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.jjs.ast.JReboundEntryPoint;
+import com.google.gwt.dev.jjs.ast.JVisitor;
+
+import java.util.Set;
+
+/**
+ * Records all live rebinds.
+ */
+public class RecordRebinds {
+
+  private class RebindVisitor extends JVisitor {
+    @Override
+    public void endVisit(JGwtCreate x, Context ctx) {
+      String reqType = x.getSourceType().getName().replace('$', '.');
+      liveRebindRequests.add(reqType);
+    }
+
+    @Override
+    public void endVisit(JReboundEntryPoint x, Context ctx) {
+      String reqType = x.getSourceType().getName().replace('$', '.');
+      liveRebindRequests.add(reqType);
+    }
+  }
+
+  public static void exec(JProgram program, Set<String> liveRebindRequests) {
+    new RecordRebinds(program, liveRebindRequests).execImpl();
+  }
+
+  private final JProgram program;
+  private final Set<String> liveRebindRequests;
+
+  private RecordRebinds(JProgram program, Set<String> liveRebindRequests) {
+    this.program = program;
+    this.liveRebindRequests = liveRebindRequests;
+  }
+
+  private void execImpl() {
+    RebindVisitor rebinder = new RebindVisitor();
+    rebinder.accept(program);
+  }
+
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ResolveRebinds.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ResolveRebinds.java
index d0cbdd7..841735a 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/ResolveRebinds.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ResolveRebinds.java
@@ -15,9 +15,6 @@
  */
 package com.google.gwt.dev.jjs.impl;
 
-import com.google.gwt.core.ext.TreeLogger;
-import com.google.gwt.core.ext.UnableToCompleteException;
-import com.google.gwt.dev.jdt.RebindOracle;
 import com.google.gwt.dev.jjs.InternalCompilerException;
 import com.google.gwt.dev.jjs.ast.Context;
 import com.google.gwt.dev.jjs.ast.JClassType;
@@ -29,6 +26,7 @@
 import com.google.gwt.dev.jjs.ast.JType;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * Replaces any "GWT.create()" calls with a new expression for the actual result
@@ -70,29 +68,23 @@
     }
   }
 
-  public static boolean exec(TreeLogger logger, JProgram program,
-      RebindOracle rebindOracle) {
-    return new ResolveRebinds(logger, program, rebindOracle).execImpl();
+  public static boolean exec(JProgram program, Map<String, String> rebindAnswers) {
+    return new ResolveRebinds(program, rebindAnswers).execImpl();
   }
 
-  private final TreeLogger logger;
   private final JProgram program;
-  private final RebindOracle rebindOracle;
+  private final Map<String, String> rebindAnswers;
 
-  private ResolveRebinds(TreeLogger logger, JProgram program,
-      RebindOracle rebindOracle) {
-    this.logger = logger;
+  private ResolveRebinds(JProgram program, Map<String, String> rebindAnswers) {
     this.program = program;
-    this.rebindOracle = rebindOracle;
+    this.rebindAnswers = rebindAnswers;
   }
 
   public JClassType rebind(JType type) {
     // Rebinds are always on a source type name.
     String reqType = type.getName().replace('$', '.');
-    String reboundClassName;
-    try {
-      reboundClassName = rebindOracle.rebind(logger, reqType);
-    } catch (UnableToCompleteException e) {
+    String reboundClassName = rebindAnswers.get(reqType);
+    if (reboundClassName == null) {
       // The fact that we already compute every rebind permutation before
       // compiling should prevent this case from ever happening in real life.
       //
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java b/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java
index a8262fb..749bd5a 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java
@@ -652,9 +652,13 @@
      * Find a replacement method. If the original method is abstract, this will
      * return the leaf, final implementation of the method. If the method is
      * already concrete, but enclosed by an abstract type, the overriding method
-     * from the leaf concrete type will be returned.
+     * from the leaf concrete type will be returned. If the method is static,
+     * return <code>null</code> no matter what.
      */
     private JMethod getSingleConcreteMethod(JMethod method) {
+      if (method.isStatic()) {
+        return null;
+      }
       if (getSingleConcreteType(method.getEnclosingType()) != null) {
         return getSingleConcrete(method, overriders);
       } else {
diff --git a/dev/core/src/com/google/gwt/dev/shell/GWTShellServlet.java b/dev/core/src/com/google/gwt/dev/shell/GWTShellServlet.java
index b36d31c..670af09 100644
--- a/dev/core/src/com/google/gwt/dev/shell/GWTShellServlet.java
+++ b/dev/core/src/com/google/gwt/dev/shell/GWTShellServlet.java
@@ -22,7 +22,7 @@
 import com.google.gwt.dev.GWTShell;
 import com.google.gwt.dev.cfg.ModuleDef;
 import com.google.gwt.dev.cfg.ModuleDefLoader;
-import com.google.gwt.dev.jjs.JJSOptions;
+import com.google.gwt.dev.jjs.JJSOptionsImpl;
 import com.google.gwt.dev.resource.Resource;
 import com.google.gwt.dev.util.HttpHeaders;
 import com.google.gwt.dev.util.Util;
@@ -547,7 +547,7 @@
         "Generating a script selection script for module " + moduleName);
 
     StandardLinkerContext context = new StandardLinkerContext(logger,
-        getModuleDef(logger, moduleName), null, null, new JJSOptions());
+        getModuleDef(logger, moduleName), new JJSOptionsImpl());
     HostedModeLinker linker = new HostedModeLinker();
     return linker.generateSelectionScript(logger, context,
         context.getArtifacts());
diff --git a/dev/core/src/com/google/gwt/dev/util/Util.java b/dev/core/src/com/google/gwt/dev/util/Util.java
index 6f3d637..aabd864 100644
--- a/dev/core/src/com/google/gwt/dev/util/Util.java
+++ b/dev/core/src/com/google/gwt/dev/util/Util.java
@@ -40,11 +40,14 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
 import java.io.RandomAccessFile;
 import java.io.Reader;
+import java.io.Serializable;
 import java.io.StringWriter;
 import java.io.UnsupportedEncodingException;
 import java.lang.reflect.Array;
@@ -140,6 +143,19 @@
     return toHexString(md5.digest());
   }
 
+  public static void copy(InputStream is, OutputStream os) throws IOException {
+    try {
+      byte[] buf = new byte[8 * 1024];
+      int i;
+      while ((i = is.read(buf)) != -1) {
+        os.write(buf, 0, i);
+      }
+    } finally {
+      Utility.close(is);
+      Utility.close(os);
+    }
+  }
+
   public static boolean copy(TreeLogger logger, File in, File out)
       throws UnableToCompleteException {
     try {
@@ -548,6 +564,43 @@
     return null;
   }
 
+  public static <T extends Serializable> T readFileAsObject(File file,
+      Class<T> type) throws ClassNotFoundException {
+    FileInputStream fileInputStream = null;
+    ObjectInputStream objectInputStream = null;
+    try {
+      fileInputStream = new FileInputStream(file);
+      objectInputStream = new ObjectInputStream(fileInputStream);
+      return type.cast(objectInputStream.readObject());
+    } catch (IOException e) {
+      return null;
+    } finally {
+      Utility.close(objectInputStream);
+      Utility.close(fileInputStream);
+    }
+  }
+
+  public static Serializable[] readFileAsObjects(File file,
+      Class<? extends Serializable>... types) throws ClassNotFoundException {
+    FileInputStream fileInputStream = null;
+    ObjectInputStream objectInputStream = null;
+    try {
+      fileInputStream = new FileInputStream(file);
+      objectInputStream = new ObjectInputStream(fileInputStream);
+      Serializable[] results = new Serializable[types.length];
+      for (int i = 0; i < results.length; ++i) {
+        Object object = objectInputStream.readObject();
+        results[i] = types[i].cast(object);
+      }
+      return results;
+    } catch (IOException e) {
+      return null;
+    } finally {
+      Utility.close(objectInputStream);
+      Utility.close(fileInputStream);
+    }
+  }
+
   public static String readFileAsString(File file) {
     byte[] bytes = readFileAsBytes(file);
     if (bytes != null) {
@@ -974,24 +1027,26 @@
     }
   }
 
-  public static void writeStringAsFile(TreeLogger logger, File file,
-      String string) throws UnableToCompleteException {
+  /**
+   * Serializes an object and writes it to a file.
+   */
+  public static void writeObjectAsFile(TreeLogger logger, File file,
+      Serializable... objects) throws UnableToCompleteException {
     FileOutputStream stream = null;
-    OutputStreamWriter writer = null;
-    BufferedWriter buffered = null;
+    ObjectOutputStream objectStream = null;
     try {
-      stream = new FileOutputStream(file);
-      writer = new OutputStreamWriter(stream, DEFAULT_ENCODING);
-      buffered = new BufferedWriter(writer);
       file.getParentFile().mkdirs();
-      buffered.write(string);
+      stream = new FileOutputStream(file);
+      objectStream = new ObjectOutputStream(stream);
+      for (Serializable object : objects) {
+        objectStream.writeObject(object);
+      }
     } catch (IOException e) {
       logger.log(TreeLogger.ERROR, "Unable to write file: "
           + file.getAbsolutePath(), e);
       throw new UnableToCompleteException();
     } finally {
-      Utility.close(buffered);
-      Utility.close(writer);
+      Utility.close(objectStream);
       Utility.close(stream);
     }
   }
@@ -1016,16 +1071,25 @@
     return true;
   }
 
-  private static void copy(InputStream is, OutputStream os) throws IOException {
+  public static void writeStringAsFile(TreeLogger logger, File file,
+      String string) throws UnableToCompleteException {
+    FileOutputStream stream = null;
+    OutputStreamWriter writer = null;
+    BufferedWriter buffered = null;
     try {
-      byte[] buf = new byte[8 * 1024];
-      int i;
-      while ((i = is.read(buf)) != -1) {
-        os.write(buf, 0, i);
-      }
+      stream = new FileOutputStream(file);
+      writer = new OutputStreamWriter(stream, DEFAULT_ENCODING);
+      buffered = new BufferedWriter(writer);
+      file.getParentFile().mkdirs();
+      buffered.write(string);
+    } catch (IOException e) {
+      logger.log(TreeLogger.ERROR, "Unable to write file: "
+          + file.getAbsolutePath(), e);
+      throw new UnableToCompleteException();
     } finally {
-      Utility.close(is);
-      Utility.close(os);
+      Utility.close(buffered);
+      Utility.close(writer);
+      Utility.close(stream);
     }
   }
 
diff --git a/dev/core/src/com/google/gwt/util/tools/ArgHandlerDisableAggressiveOptimization.java b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerDisableAggressiveOptimization.java
similarity index 69%
rename from dev/core/src/com/google/gwt/util/tools/ArgHandlerDisableAggressiveOptimization.java
rename to dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerDisableAggressiveOptimization.java
index f464ea4..b3b4579 100644
--- a/dev/core/src/com/google/gwt/util/tools/ArgHandlerDisableAggressiveOptimization.java
+++ b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerDisableAggressiveOptimization.java
@@ -13,13 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.util.tools;
+package com.google.gwt.dev.util.arg;
+
+import com.google.gwt.util.tools.ArgHandlerFlag;
 
 /**
  * Handles the -XdisableAggressiveOptimization command line flag.
  */
-public abstract class ArgHandlerDisableAggressiveOptimization extends
+public final class ArgHandlerDisableAggressiveOptimization extends
     ArgHandlerFlag {
+  private final OptionAggressivelyOptimize option;
+
+  public ArgHandlerDisableAggressiveOptimization(
+      OptionAggressivelyOptimize option) {
+    this.option = option;
+  }
+
   @Override
   public String getPurpose() {
     return "Troubleshooting: Prevent the web mode compiler from performing "
@@ -35,4 +44,10 @@
   public boolean isUndocumented() {
     return true;
   }
+
+  @Override
+  public boolean setFlag() {
+    option.setAggressivelyOptimize(false);
+    return true;
+  }
 }
diff --git a/dev/core/src/com/google/gwt/util/tools/ArgHandlerEnableAssertions.java b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerEnableAssertions.java
similarity index 72%
rename from dev/core/src/com/google/gwt/util/tools/ArgHandlerEnableAssertions.java
rename to dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerEnableAssertions.java
index f37fa97..9697b1c 100644
--- a/dev/core/src/com/google/gwt/util/tools/ArgHandlerEnableAssertions.java
+++ b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerEnableAssertions.java
@@ -13,19 +13,19 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.util.tools;
+package com.google.gwt.dev.util.arg;
 
-import com.google.gwt.dev.jjs.JJSOptions;
+import com.google.gwt.util.tools.ArgHandlerFlag;
 
 /**
  * Handles the -ea command line flag.
  */
-public class ArgHandlerEnableAssertions extends ArgHandlerFlag {
+public final class ArgHandlerEnableAssertions extends ArgHandlerFlag {
 
-  private final JJSOptions optionsToModify;
+  private final OptionEnableAssertions option;
 
-  public ArgHandlerEnableAssertions(JJSOptions optionsToModify) {
-    this.optionsToModify = optionsToModify;
+  public ArgHandlerEnableAssertions(OptionEnableAssertions option) {
+    this.option = option;
   }
 
   @Override
@@ -40,7 +40,7 @@
 
   @Override
   public boolean setFlag() {
-    optionsToModify.setEnableAssertions(true);
+    option.setEnableAssertions(true);
     return true;
   }
 }
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerGenDir.java b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerGenDir.java
index 07ed09c..e9bc414 100644
--- a/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerGenDir.java
+++ b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerGenDir.java
@@ -17,10 +17,18 @@
 
 import com.google.gwt.util.tools.ArgHandlerDir;
 
+import java.io.File;
+
 /**
- * Argument handler for processing the code generation directory flag. 
+ * Argument handler for processing the code generation directory flag.
  */
-public abstract class ArgHandlerGenDir extends ArgHandlerDir {
+public final class ArgHandlerGenDir extends ArgHandlerDir {
+
+  private final OptionGenDir option;
+
+  public ArgHandlerGenDir(OptionGenDir option) {
+    this.option = option;
+  }
 
   public String getPurpose() {
     return "The directory into which generated files will be written for review";
@@ -29,4 +37,9 @@
   public String getTag() {
     return "-gen";
   }
+
+  @Override
+  public void setDir(File dir) {
+    option.setGenDir(dir);
+  }
 }
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerLogLevel.java b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerLogLevel.java
index 0008eb9..f2bf710 100644
--- a/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerLogLevel.java
+++ b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerLogLevel.java
@@ -21,7 +21,7 @@
 /**
  * Argument handler for processing the log level flag.
  */
-public abstract class ArgHandlerLogLevel extends ArgHandler {
+public final class ArgHandlerLogLevel extends ArgHandler {
 
   private static final String OPTIONS_STRING = computeOptionsString();
 
@@ -40,7 +40,13 @@
     return sb.toString();
   }
 
-  public final String[] getDefaultArgs() {
+  private final OptionLogLevel options;
+
+  public ArgHandlerLogLevel(OptionLogLevel options) {
+    this.options = options;
+  }
+
+  public String[] getDefaultArgs() {
     return new String[] {getTag(), getDefaultLogLevel().name()};
   }
 
@@ -60,7 +66,7 @@
     if (startIndex + 1 < args.length) {
       try {
         Type level = Type.valueOf(args[startIndex + 1]);
-        setLogLevel(level);
+        options.setLogLevel(level);
         return 1;
       } catch (IllegalArgumentException e) {
         // Argument did not match any enum value; fall through to error case.
@@ -72,8 +78,6 @@
     return -1;
   }
 
-  public abstract void setLogLevel(Type level);
-
   protected Type getDefaultLogLevel() {
     return Type.INFO;
   }
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerModuleName.java b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerModuleName.java
new file mode 100644
index 0000000..3dab092
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerModuleName.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.util.arg;
+
+import com.google.gwt.util.tools.ArgHandlerExtra;
+
+/**
+ * Argument handler for module name, which has no option profix.
+ */
+public final class ArgHandlerModuleName extends ArgHandlerExtra {
+
+  private final OptionModuleName option;
+
+  public ArgHandlerModuleName(OptionModuleName option) {
+    this.option = option;
+  }
+
+  @Override
+  public boolean addExtraArg(String arg) {
+    option.setModuleName(arg);
+    return true;
+  }
+
+  @Override
+  public String getPurpose() {
+    return "Specifies the name of the module to compile";
+  }
+
+  @Override
+  public String[] getTagArgs() {
+    return new String[] {"module"};
+  }
+
+  @Override
+  public boolean isRequired() {
+    return true;
+  }
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerOutDir.java b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerOutDir.java
new file mode 100644
index 0000000..bf5a5b1
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerOutDir.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2006 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * 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.ArgHandlerDir;
+
+import java.io.File;
+
+/**
+ * Argument handler for processing the output directory flag.
+ */
+public final class ArgHandlerOutDir extends ArgHandlerDir {
+
+  private final OptionOutDir option;
+
+  public ArgHandlerOutDir(OptionOutDir option) {
+    this.option = option;
+  }
+
+  public String[] getDefaultArgs() {
+    return new String[] {"-out", System.getProperty("user.dir")};
+  }
+
+  public String getPurpose() {
+    return "The directory to write output files into (defaults to current)";
+  }
+
+  public String getTag() {
+    return "-out";
+  }
+
+  @Override
+  public void setDir(File dir) {
+    option.setOutDir(dir);
+  }
+
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerScriptStyle.java b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerScriptStyle.java
index fff73c9..3c9b5c0 100644
--- a/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerScriptStyle.java
+++ b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerScriptStyle.java
@@ -15,7 +15,6 @@
  */
 package com.google.gwt.dev.util.arg;
 
-import com.google.gwt.dev.jjs.JJSOptions;
 import com.google.gwt.dev.jjs.JsOutputOption;
 import com.google.gwt.util.tools.ArgHandler;
 
@@ -24,10 +23,10 @@
  */
 public final class ArgHandlerScriptStyle extends ArgHandler {
 
-  private final JJSOptions optionsToModify;
+  private final OptionScriptStyle option;
 
-  public ArgHandlerScriptStyle(JJSOptions optionsToModify) {
-    this.optionsToModify = optionsToModify;
+  public ArgHandlerScriptStyle(OptionScriptStyle option) {
+    this.option = option;
   }
 
   public String[] getDefaultArgs() {
@@ -50,13 +49,13 @@
     if (startIndex + 1 < args.length) {
       String style = args[startIndex + 1].toLowerCase();
       if (style.startsWith("obf")) {
-        optionsToModify.setOutput(JsOutputOption.OBFUSCATED);
+        option.setOutput(JsOutputOption.OBFUSCATED);
         return 1;
       } else if ("pretty".equals(style)) {
-        optionsToModify.setOutput(JsOutputOption.PRETTY);
+        option.setOutput(JsOutputOption.PRETTY);
         return 1;
       } else if ("detailed".equals(style)) {
-        optionsToModify.setOutput(JsOutputOption.DETAILED);
+        option.setOutput(JsOutputOption.DETAILED);
         return 1;
       }
     }
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerTreeLoggerFlag.java b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerTreeLoggerFlag.java
index a88facb..fe8331d 100644
--- a/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerTreeLoggerFlag.java
+++ b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerTreeLoggerFlag.java
@@ -18,9 +18,15 @@
 import com.google.gwt.util.tools.ArgHandlerFlag;
 
 /**
- * Argument handler for processing the tree logger boolean flag. 
+ * Argument handler for processing the GUI tree logger boolean flag.
  */
-public abstract class ArgHandlerTreeLoggerFlag extends ArgHandlerFlag {
+public final class ArgHandlerTreeLoggerFlag extends ArgHandlerFlag {
+
+  private final OptionGuiLogger option;
+
+  public ArgHandlerTreeLoggerFlag(OptionGuiLogger option) {
+    this.option = option;
+  }
 
   public String getPurpose() {
     return "Logs output in a graphical tree view";
@@ -30,5 +36,8 @@
     return "-treeLogger";
   }
 
-  public abstract boolean setFlag();
+  public boolean setFlag() {
+    option.setUseGuiLogger(true);
+    return true;
+  }
 }
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerValidateOnlyFlag.java b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerValidateOnlyFlag.java
new file mode 100644
index 0000000..7740786
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerValidateOnlyFlag.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.util.arg;
+
+import com.google.gwt.util.tools.ArgHandlerFlag;
+
+/**
+ * Handles the -validateOnly command line flag.
+ */
+public final class ArgHandlerValidateOnlyFlag extends ArgHandlerFlag {
+
+  private final OptionValidateOnly option;
+
+  public ArgHandlerValidateOnlyFlag(OptionValidateOnly option) {
+    this.option = option;
+  }
+
+  public String getPurpose() {
+    return "Validate all source code, but do not compile";
+  }
+
+  public String getTag() {
+    return "-validateOnly";
+  }
+
+  public boolean setFlag() {
+    option.setValidateOnly(true);
+    return true;
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/OptionAggressivelyOptimize.java b/dev/core/src/com/google/gwt/dev/util/arg/OptionAggressivelyOptimize.java
new file mode 100644
index 0000000..889a25f
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/arg/OptionAggressivelyOptimize.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.util.arg;
+
+/**
+ * Option to determine whether the compiler should aggressively optimize.
+ */
+public interface OptionAggressivelyOptimize {
+
+  /**
+   * Returns true if the compiler should aggressively optimize.
+   */
+  boolean isAggressivelyOptimize();
+
+  /**
+   * Sets whether or not the compiler should aggressively optimize.
+   */
+  void setAggressivelyOptimize(boolean aggressivelyOptimize);
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/OptionEnableAssertions.java b/dev/core/src/com/google/gwt/dev/util/arg/OptionEnableAssertions.java
new file mode 100644
index 0000000..609d95b
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/arg/OptionEnableAssertions.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.util.arg;
+
+/**
+ * Option to determine whether the compiler should generate code to check
+ * assertions.
+ */
+public interface OptionEnableAssertions {
+
+  /**
+   * Returns true if the compiler should generate code to check assertions.
+   */
+  boolean isEnableAssertions();
+
+  /**
+   * Sets whether or not the compiler should generate code to check assertions.
+   */
+  void setEnableAssertions(boolean enableAssertions);
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/OptionGenDir.java b/dev/core/src/com/google/gwt/dev/util/arg/OptionGenDir.java
new file mode 100644
index 0000000..ff2e253
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/arg/OptionGenDir.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.util.arg;
+
+import java.io.File;
+
+/**
+ * Option to set the generated resource directory.
+ */
+public interface OptionGenDir {
+
+  /**
+   * Returns the generated resource directory.
+   */
+  File getGenDir();
+
+  /**
+   * Sets the generated resource directory.
+   */
+  void setGenDir(File dir);
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/OptionGuiLogger.java b/dev/core/src/com/google/gwt/dev/util/arg/OptionGuiLogger.java
new file mode 100644
index 0000000..56205ba
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/arg/OptionGuiLogger.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.util.arg;
+
+/**
+ * Option to set whether to use a GUI logger instead of stdout.
+ */
+public interface OptionGuiLogger {
+
+  /**
+   * Returns true if a GUI logger should be used.
+   */
+  boolean isUseGuiLogger();
+
+  /**
+   * Sets whether or not to use a GUI logger.
+   */
+  void setUseGuiLogger(boolean useGuiLogger);
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/OptionLogLevel.java b/dev/core/src/com/google/gwt/dev/util/arg/OptionLogLevel.java
new file mode 100644
index 0000000..ebef78d
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/arg/OptionLogLevel.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.util.arg;
+
+import com.google.gwt.core.ext.TreeLogger;
+
+/**
+ * Option to set the tree logger log level.
+ */
+public interface OptionLogLevel {
+
+  /**
+   * Returns the tree logger level.
+   */
+  TreeLogger.Type getLogLevel();
+
+  /**
+   * Sets the tree logger level.
+   */
+  void setLogLevel(TreeLogger.Type logLevel);
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/OptionModuleName.java b/dev/core/src/com/google/gwt/dev/util/arg/OptionModuleName.java
new file mode 100644
index 0000000..688406e
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/arg/OptionModuleName.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.util.arg;
+
+/**
+ * Option to set the module name.
+ */
+public interface OptionModuleName {
+
+  /**
+   * Returns the name of the module.
+   */
+  String getModuleName();
+
+  /**
+   * Sets the name of the module.
+   */
+  void setModuleName(String moduleName);
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/OptionOutDir.java b/dev/core/src/com/google/gwt/dev/util/arg/OptionOutDir.java
new file mode 100644
index 0000000..25b2b12
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/arg/OptionOutDir.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.util.arg;
+
+import java.io.File;
+
+/**
+ * Option to set the output directory.
+ */
+public interface OptionOutDir {
+
+  /**
+   * Returns the output directory.
+   */
+  File getOutDir();
+
+  /**
+   * Sets the output directory.
+   */
+  void setOutDir(File dir);
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/OptionScriptStyle.java b/dev/core/src/com/google/gwt/dev/util/arg/OptionScriptStyle.java
new file mode 100644
index 0000000..736f7e3
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/arg/OptionScriptStyle.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.util.arg;
+
+import com.google.gwt.dev.jjs.JsOutputOption;
+
+/**
+ * Option for setting the compiler output style.
+ */
+public interface OptionScriptStyle {
+
+  /**
+   * Returns the compiler output style.
+   */
+  JsOutputOption getOutput();
+
+  /**
+   * Sets the compiler output style.
+   */
+  void setOutput(JsOutputOption obfuscated);
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/OptionValidateOnly.java b/dev/core/src/com/google/gwt/dev/util/arg/OptionValidateOnly.java
new file mode 100644
index 0000000..e402255
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/arg/OptionValidateOnly.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.util.arg;
+
+/**
+ * Option to set whether the compiler should validate and then quit.
+ */
+public interface OptionValidateOnly {
+
+  /**
+   * Returns true the compiler should only validate.
+   */
+  boolean isValidateOnly();
+
+  /**
+   * Sets whether or not the compiler should only validate.
+   */
+  void setValidateOnly(boolean validateOnly);
+}
diff --git a/dev/core/test/com/google/gwt/dev/shell/StandardGeneratorContextTest.java b/dev/core/test/com/google/gwt/dev/shell/StandardGeneratorContextTest.java
index 09c8f21..ea2625c 100644
--- a/dev/core/test/com/google/gwt/dev/shell/StandardGeneratorContextTest.java
+++ b/dev/core/test/com/google/gwt/dev/shell/StandardGeneratorContextTest.java
@@ -155,6 +155,7 @@
     tempOutDir = createTempDir("gwt-out-");
     genCtx = new StandardGeneratorContext(mockCompilationState, mockPropOracle,
         mockPublicOracle, tempGenDir, tempOutDir, artifactSet);
+    genCtx.setCurrentGenerator(Generator.class);
   }
 
   public void testTryCreateResource_badFileName() {
@@ -355,7 +356,8 @@
   protected void tearDown() throws Exception {
     for (int i = toDelete.size() - 1; i >= 0; --i) {
       File f = toDelete.get(i);
-      assertTrue(f.delete());
+      Util.recursiveDelete(f, false);
+      assertFalse("Unable to delete " + f.getAbsolutePath(), f.exists());
     }
   }
 
diff --git a/distro-source/core/src/release_notes.html b/distro-source/core/src/release_notes.html
index 37721ff..37c804b 100644
--- a/distro-source/core/src/release_notes.html
+++ b/distro-source/core/src/release_notes.html
@@ -29,6 +29,7 @@
       <h1>Google Web Toolkit Release Notes</h1>
       <ul>
 		    <li><a href="#Release_Notes_Current">@GWT_VERSION@</a></li>
+		    <li><a href="#Release_Notes_1_5_2">1.5.2</a></li>
 		    <li><a href="#Release_Notes_1_5_1">1.5.1 (RC2)</a></li>
 		    <li><a href="#Release_Notes_1_5_0">1.5.0 (RC)</a></li>
 		    <li><a href="#Release_Notes_1_4_60">1.4.60</a></li>
@@ -46,6 +47,25 @@
       <hr/>
       <a name="Release_Notes_Current"></a>
       <h2>Release Notes for @GWT_VERSION@</h2>
+      <h3>Fixed Issues</h3>
+      <ul>
+        <li>RPC requests no longer fail on the embedded Android web browser</li>
+        <li>Leaf <code>TreeItems</code> now line up with their non-leaf siblings</li>
+        <li>Removing the last child node from a <code>TreeItem</code> no longer creates extra margins on the left</li>
+        <li><code>HTTPRequest</code> no longer uses POST instead of GET on some IE installs because of incorrect XHR selection</li>
+        <li>Compiler now uses a more reliable check to prevent methods with local variables from being inlined</li>
+        <li><code>getAbsoluteTop()/Left()</code> can no longer return non-integral values</li>
+        <li><code>Time.valueOf()</code> no longer fails to parse <code>"08:00:00"</code> or incorrectly accepts <code>"0xC:0xB:0xA"</code>.</li>
+      </ul>
+      <p>
+        See the GWT issue tracker for
+        <a href="http://code.google.com/p/google-web-toolkit/issues/list?can=1&q=status%3AFixed%2CFixedNotReleased%20milestone%3A1_5_3&num=1000">
+        the complete list of bug fixes and enhancements</a> in this release.
+      </p>
+
+      <hr/>
+      <a name="Release_Notes_1_5_2"></a>
+      <h2>Release Notes for 1.5.2</h2>
       <h3>Potentially breaking changes and fixes</h3>
       <ul>
         <li><code>History.onHistoryChanged()</code> has been added back (it was missing from 1.5 RC2) but is now deprecated.  Application startup should be handled by calling the new <code>History.fireCurrentHistoryState()</code>.</li>
diff --git a/eclipse/samples/DynaTable2/.checkstyle b/eclipse/samples/DynaTable2/.checkstyle
new file mode 100644
index 0000000..0f1cbd9
--- /dev/null
+++ b/eclipse/samples/DynaTable2/.checkstyle
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<fileset-config file-format-version="1.2.0" simple-config="true">
+    <fileset name="all" enabled="true" check-config-name="GWT Checks" local="false">
+        <file-match-pattern match-pattern="." include-pattern="true"/>
+    </fileset>
+    <filter name="NonSrcDirs" enabled="true"/>
+</fileset-config>
diff --git a/eclipse/samples/DynaTable2/.classpath b/eclipse/samples/DynaTable2/.classpath
new file mode 100644
index 0000000..447a770
--- /dev/null
+++ b/eclipse/samples/DynaTable2/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="core/src"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/gwt-user"/>
+	<classpathentry kind="output" path="war/WEB-INF/classes"/>
+</classpath>
diff --git a/eclipse/samples/DynaTable2/.project b/eclipse/samples/DynaTable2/.project
new file mode 100644
index 0000000..5ece582
--- /dev/null
+++ b/eclipse/samples/DynaTable2/.project
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>DynaTable2</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>com.atlassw.tools.eclipse.checkstyle.CheckstyleBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>com.atlassw.tools.eclipse.checkstyle.CheckstyleNature</nature>
+	</natures>
+	<linkedResources>
+		<link>
+			<name>core</name>
+			<type>2</type>
+			<locationURI>GWT_ROOT/samples/dynatable</locationURI>
+		</link>
+	</linkedResources>
+</projectDescription>
diff --git a/eclipse/samples/DynaTable2/DynaTable2 compile.launch b/eclipse/samples/DynaTable2/DynaTable2 compile.launch
new file mode 100644
index 0000000..9d1d153
--- /dev/null
+++ b/eclipse/samples/DynaTable2/DynaTable2 compile.launch
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
+<listEntry value="/DynaTable2"/>
+</listAttribute>
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
+<listEntry value="4"/>
+</listAttribute>
+<booleanAttribute key="org.eclipse.debug.core.appendEnvironmentVariables" value="true"/>
+<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER&quot; javaProject=&quot;DynaTable2&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#13;&#10;"/>
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/DynaTable2/core/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/gwt-user/core/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/gwt-user/core/super&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/gwt-dev-windows/core/super&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#13;&#10;&lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;DynaTable2&quot;/&gt;&#13;&#10;&lt;/runtimeClasspathEntry&gt;&#13;&#10;"/>
+</listAttribute>
+<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
+<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.GWTCompiler"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-wardir war&#13;&#10;-style PRETTY&#13;&#10;-logLevel INFO&#13;&#10;com.google.gwt.sample.dynatable.DynaTable"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="DynaTable2"/>
+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-ea&#13;&#10;-Dgwt.devjar=C:\gwt\releases\1.6\build\staging\gwt-windows-0.0.0\gwt-dev-windows.jar"/>
+</launchConfiguration>
diff --git a/eclipse/samples/DynaTable2/DynaTable2 server.launch b/eclipse/samples/DynaTable2/DynaTable2 server.launch
new file mode 100644
index 0000000..4029b62
--- /dev/null
+++ b/eclipse/samples/DynaTable2/DynaTable2 server.launch
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
+<listEntry value="/DynaTable2"/>
+</listAttribute>
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
+<listEntry value="4"/>
+</listAttribute>
+<booleanAttribute key="org.eclipse.debug.core.appendEnvironmentVariables" value="true"/>
+<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER&quot; javaProject=&quot;DynaTable2&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#13;&#10;"/>
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/DynaTable2/jetty-6.1.11.jar&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
+</listAttribute>
+<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
+<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="org.mortbay.jetty.Main"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="8888 -webapp war"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="DynaTable2"/>
+</launchConfiguration>
diff --git a/eclipse/samples/DynaTable2/DynaTable2 shell.launch b/eclipse/samples/DynaTable2/DynaTable2 shell.launch
new file mode 100644
index 0000000..cca8023
--- /dev/null
+++ b/eclipse/samples/DynaTable2/DynaTable2 shell.launch
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
+<listEntry value="/DynaTable2"/>
+</listAttribute>
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
+<listEntry value="4"/>
+</listAttribute>
+<booleanAttribute key="org.eclipse.debug.core.appendEnvironmentVariables" value="true"/>
+<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER&quot; javaProject=&quot;DynaTable2&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#13;&#10;"/>
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/DynaTable2/core/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/gwt-user/core/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/gwt-user/core/super&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/gwt-dev-windows/core/super&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#13;&#10;&lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;DynaTable2&quot;/&gt;&#13;&#10;&lt;/runtimeClasspathEntry&gt;&#13;&#10;"/>
+</listAttribute>
+<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
+<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.GWTShell"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-wardir war&#13;&#10;-style PRETTY&#13;&#10;-logLevel INFO&#13;&#10;DynaTable.html"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="DynaTable2"/>
+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-ea&#13;&#10;-Dgwt.devjar=C:\gwt\releases\1.6\build\staging\gwt-windows-0.0.0\gwt-dev-windows.jar"/>
+</launchConfiguration>
diff --git a/eclipse/samples/DynaTable2/build.xml b/eclipse/samples/DynaTable2/build.xml
new file mode 100644
index 0000000..d810bd9
--- /dev/null
+++ b/eclipse/samples/DynaTable2/build.xml
@@ -0,0 +1,82 @@
+<project name="dynatable2" default="build" basedir=".">
+  <property name="gwt.install" location="../../../build/lib" />
+  <property name="wardir" location="war" />
+
+  <target name="javac" description="Compile project to WEB-INF/classes">
+    <mkdir dir="${wardir}/WEB-INF/classes" />
+    <javac srcdir="../../../samples/dynatable/src"
+        destdir="${wardir}/WEB-INF/classes"
+        debug="true"
+        debuglevel="lines,vars,source"
+        source="1.5"
+        target="1.5"
+        nowarn="true"
+        encoding="utf-8">
+      <classpath>
+        <pathelement location="${gwt.install}/gwt-user.jar" />
+        <pathelement location="${gwt.install}/gwt-dev-windows.jar" />
+      </classpath>
+    </javac>
+  </target>
+
+  <target name="deploy" description="Copy output to the war folder">
+    <mkdir dir="${wardir}/WEB-INF/lib" />
+    <copy todir="${wardir}/WEB-INF/lib" file="${gwt.install}/gwt-servlet.jar" />
+  </target>
+
+  <target name="gwtc" depends="javac" description="Compile to JavaScript">
+    <java classname="com.google.gwt.dev.GWTCompiler" fork="yes" failonerror="true">
+      <jvmarg value="-Xmx256M"/>
+      <arg value="-wardir" />
+      <arg file="war" />
+      <arg value="com.google.gwt.sample.dynatable.DynaTable" />
+      <classpath>
+        <pathelement location="../../../samples/dynatable/src" />
+        <pathelement location="${wardir}/WEB-INF/classes" />
+        <pathelement location="${gwt.install}/gwt-user.jar" />
+        <pathelement location="${gwt.install}/gwt-dev-windows.jar" />
+      </classpath>
+    </java>
+  </target>
+
+  <target name="server" depends="deploy" description="Run the deployed app in a Jetty server">
+    <echo message="PLEASE BROWSE TO: http://localhost:8888/DynaTable.html"/>
+    <java classname="org.mortbay.jetty.Main" fork="yes">
+      <arg value="8888" />
+      <arg value="-webapp" />
+      <arg file="war" />
+      <classpath>
+        <pathelement location="${gwt.install}/gwt-dev-windows.jar" />
+      </classpath>
+    </java>
+  </target>
+
+  <target name="shell" depends="javac" description="Run the deployed app in GWT hosted mode">
+    <java classname="com.google.gwt.dev.GWTShell" fork="yes" failonerror="true">
+      <jvmarg value="-Xmx256M"/>
+      <jvmarg value="-Dgwt.devjar=C:\gwt\releases\1.6\build\staging\gwt-windows-0.0.0\gwt-dev-windows.jar"/>
+      <arg value="-wardir" />
+      <arg file="war" />
+      <arg value="http://localhost:8888/DynaTable.html" />
+      <classpath>
+        <pathelement location="../../../samples/dynatable/src" />
+        <pathelement location="${wardir}/WEB-INF/classes" />
+        <pathelement location="${gwt.install}/gwt-user.jar" />
+        <pathelement location="${gwt.install}/gwt-dev-windows.jar" />
+      </classpath>
+    </java>
+  </target>
+
+  <target name="build" depends="javac, gwtc, deploy" description="Build this project" />
+
+  <target name="clean" description="Cleans this project's intermediate and output files">
+    <delete includeemptydirs="true" failonerror="false">
+      <fileset dir="${wardir}" includes="*" excludes="DynaTable.html"/>
+    </delete>
+    <delete dir="${wardir}/WEB-INF/classes" failonerror="false" />
+    <delete dir="${wardir}/WEB-INF/lib" failonerror="false" />
+    <delete dir="${wardir}/WEB-INF/gwt-aux" failonerror="false" />
+    <delete dir="${wardir}/WEB-INF/.gwt-tmp" failonerror="false" />
+    <delete dir="www" failonerror="false" />
+  </target>
+</project>
diff --git a/eclipse/samples/DynaTable2/war/WEB-INF/web.xml b/eclipse/samples/DynaTable2/war/WEB-INF/web.xml
new file mode 100644
index 0000000..983aac0
--- /dev/null
+++ b/eclipse/samples/DynaTable2/war/WEB-INF/web.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app>
+
+  <!-- GWT REGENERATED BEGIN -->
+  <servlet>
+    <servlet-name>calendar</servlet-name>
+    <servlet-class>com.google.gwt.sample.dynatable.server.SchoolCalendarServiceImpl</servlet-class>
+  </servlet>
+  
+  <servlet-mapping>
+    <servlet-name>calendar</servlet-name>
+    <url-pattern>/calendar</url-pattern>
+  </servlet-mapping>
+  <!-- GWT REGENERATED END -->
+
+</web-app>
diff --git a/user/src/com/google/gwt/dom/client/DOMImplMozilla.java b/user/src/com/google/gwt/dom/client/DOMImplMozilla.java
index 1fa5461..0d1d36b 100644
--- a/user/src/com/google/gwt/dom/client/DOMImplMozilla.java
+++ b/user/src/com/google/gwt/dom/client/DOMImplMozilla.java
@@ -26,9 +26,10 @@
     // so we use getBoundingClientRect() whenever possible (but it's not
     // supported on older versions). If changing this code, make sure to check
     // the museum entry for issue 1932.
+    // (x) | 0 is used to coerce the value to an integer
     if (Element.prototype.getBoundingClientRect) {
-      return elem.getBoundingClientRect().left +
-        @com.google.gwt.user.client.impl.DocumentRootImpl::documentRoot.scrollLeft;
+      return (elem.getBoundingClientRect().left +
+        @com.google.gwt.user.client.impl.DocumentRootImpl::documentRoot.scrollLeft) | 0;
     } else {
       // We cannot use DOMImpl here because offsetLeft/Top return erroneous
       // values when overflow is not visible.  We have to difference screenX
@@ -46,9 +47,10 @@
     // so we use getBoundingClientRect() whenever possible (but it's not
     // supported on older versions). If changing this code, make sure to check
     // the museum entry for issue 1932.
+    // (x) | 0 is used to coerce the value to an integer
     if (Element.prototype.getBoundingClientRect) {
-      return elem.getBoundingClientRect().top +
-        @com.google.gwt.user.client.impl.DocumentRootImpl::documentRoot.scrollTop;
+      return (elem.getBoundingClientRect().top +
+        @com.google.gwt.user.client.impl.DocumentRootImpl::documentRoot.scrollTop) | 0;
     } else {
       // We cannot use DOMImpl here because offsetLeft/Top return erroneous
       // values when overflow is not visible.  We have to difference screenX
diff --git a/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableImplCreator.java b/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableImplCreator.java
index 71b44e2..8852eb9 100644
--- a/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableImplCreator.java
+++ b/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableImplCreator.java
@@ -15,6 +15,8 @@
  */
 package com.google.gwt.i18n.rebind;
 
+import static com.google.gwt.i18n.rebind.AnnotationUtil.getClassAnnotation;
+
 import com.google.gwt.core.ext.GeneratorContext;
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
@@ -139,7 +141,7 @@
       context.commit(logger, pw);
     }
     // Generate a translatable output file if requested.
-    Generate generate = targetClass.getAnnotation(Generate.class);
+    Generate generate = getClassAnnotation(targetClass, Generate.class);
     if (generate != null) {
       String path = generate.fileName();
       if (Generate.DEFAULT.equals(path)) {
diff --git a/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableInterfaceCreator.java b/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableInterfaceCreator.java
index 7a98120..8d1d141 100644
--- a/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableInterfaceCreator.java
+++ b/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableInterfaceCreator.java
@@ -33,12 +33,10 @@
 import java.io.PrintWriter;
 import java.io.Writer;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
-import java.util.Map.Entry;
-import java.util.regex.Pattern;
 
 /**
  * Abstract base functionality for <code>MessagesInterfaceCreator</code> and
@@ -50,20 +48,32 @@
 
     @Override
     public String format(String key) {
-      if (methodNames.contains(key)) {
+      while (methodNames.contains(key)) {
         key += "_dup";
-        return format(key);
-      } else {
-        methodNames.add(key);
-        return key;
       }
+      methodNames.add(key);
+      return key;
     }
   }
 
   private static class ReplaceBadChars extends ResourceKeyFormatter {
     @Override
     public String format(String key) {
-      return DEFAULT_CHARS.matcher(key).replaceAll("_");
+      StringBuilder buf = new StringBuilder();
+      int keyLen = key == null ? 0 : key.length();
+      for (int i = 0; i < keyLen; i = key.offsetByCodePoints(i, 1)) {
+        int codePoint = key.codePointAt(i);
+        if (i == 0 ? Character.isJavaIdentifierStart(codePoint)
+            : Character.isJavaIdentifierPart(codePoint)) {
+          buf.appendCodePoint(codePoint);
+        } else {
+          buf.append('_');
+        }
+      }
+      if (buf.length() == 0) {
+        buf.append('_');
+      }
+      return buf.toString();
     }
   }
 
@@ -71,15 +81,53 @@
     public abstract String format(String key);
   }
 
-  private static Pattern DEFAULT_CHARS = Pattern.compile("[.-]");
+  /**
+   * Index into this array using a nibble, 4 bits, to get the corresponding
+   * hexadecimal character representation.
+   */
+  private static final char NIBBLE_TO_HEX_CHAR[] = {
+      '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D',
+      'E', 'F'};
+
+  private static boolean needsUnicodeEscape(char ch) {
+    if (ch == ' ') {
+      return false;
+    }
+    switch (Character.getType(ch)) {
+      case Character.COMBINING_SPACING_MARK:
+      case Character.ENCLOSING_MARK:
+      case Character.NON_SPACING_MARK:
+      case Character.UNASSIGNED:
+      case Character.PRIVATE_USE:
+      case Character.SPACE_SEPARATOR:
+      case Character.CONTROL:
+      case Character.LINE_SEPARATOR:
+      case Character.FORMAT:
+      case Character.PARAGRAPH_SEPARATOR:
+      case Character.SURROGATE:
+        return true;
+
+      default:
+        break;
+    }
+    return false;
+  }
+
+  private static void unicodeEscape(char ch, StringBuilder buf) {
+    buf.append('\\');
+    buf.append('u');
+    buf.append(NIBBLE_TO_HEX_CHAR[(ch >> 12) & 0x0F]);
+    buf.append(NIBBLE_TO_HEX_CHAR[(ch >> 8) & 0x0F]);
+    buf.append(NIBBLE_TO_HEX_CHAR[(ch >> 4) & 0x0F]);
+    buf.append(NIBBLE_TO_HEX_CHAR[ch & 0x0F]);
+  }
 
   /**
    * Composer for the current Constant.
    */
   protected SourceWriter composer;
 
-  private List<ResourceKeyFormatter> formatters = 
-    new ArrayList<ResourceKeyFormatter>();
+  private List<ResourceKeyFormatter> formatters = new ArrayList<ResourceKeyFormatter>();
 
   private File resourceFile;
 
@@ -147,7 +195,7 @@
    * Create an annotation to hold the default value.
    */
   protected abstract void genValueAnnotation(String defaultValue);
-  
+
   /**
    * Returns the javaDocComment for the class.
    * 
@@ -156,24 +204,55 @@
    */
   protected abstract String javaDocComment(String path);
 
-  @SuppressWarnings("unchecked") // use of raw type from LocalizedProperties
+  protected String makeJavaString(String value) {
+    StringBuilder buf = new StringBuilder();
+    buf.append('\"');
+    for (int i = 0; i < value.length(); ++i) {
+      char c = value.charAt(i);
+      switch (c) {
+        case '\r':
+          buf.append("\\r");
+          break;
+        case '\n':
+          buf.append("\\n");
+          break;
+        case '\"':
+          buf.append("\\\"");
+          break;
+        default:
+          if (needsUnicodeEscape(c)) {
+            unicodeEscape(c, buf);
+          } else {
+            buf.append(c);
+          }
+          break;
+      }
+    }
+    buf.append('\"');
+    return buf.toString();
+  }
+
+  @SuppressWarnings("unchecked")
+  // use of raw type from LocalizedProperties
   void generateFromPropertiesFile() throws IOException {
     InputStream propStream = new FileInputStream(resourceFile);
     LocalizedProperties p = new LocalizedProperties();
     p.load(propStream, Util.DEFAULT_ENCODING);
     addFormatters();
     // TODO: Look for a generic version of Tapestry's LocalizedProperties class
-    Iterator<Entry<String, String>> elements =
-      p.getPropertyMap().entrySet().iterator(); // suppress warnings
-    if (elements.hasNext() == false) {
+    Set<String> keySet = p.getPropertyMap().keySet();
+    // sort keys for deterministic results
+    String[] keys = keySet.toArray(new String[keySet.size()]);
+    Arrays.sort(keys);
+    if (keys.length == 0) {
       throw new IllegalStateException(
           "File '"
               + resourceFile
               + "' cannot be used to generate message classes, as it has no key/value pairs defined.");
     }
-    while (elements.hasNext()) {
-      Entry<String, String> s = elements.next();
-      genSimpleMethodDecl(s.getKey(), s.getValue());
+    for (String key : keys) {
+      String value = p.getProperty(key);
+      genSimpleMethodDecl(key, value);
     }
     composer.commit(new PrintWriterTreeLogger());
   }
@@ -190,23 +269,19 @@
     for (ResourceKeyFormatter formatter : formatters) {
       key = formatter.format(key);
     }
-    if (Util.isValidJavaIdent(key) == false) {
-      // TODO(jat): we could synthesize legal method names and add an
-      // @Key annotation to keep the matching key name.
-      throw new IllegalArgumentException(key
-          + " is not a legitimate method name.");
-    }
     return key;
   }
 
   private void genMethodDecl(String type, String defaultValue, String key) {
     composer.beginJavaDocComment();
-    composer.println("Translated \"" + defaultValue + "\".\n");
-    composer.println("@return translated \"" + defaultValue + "\"");
+    String escaped = makeJavaString(defaultValue);
+    composer.println("Translated " + escaped + ".\n");
+    composer.print("@return translated " + escaped);
     composer.endJavaDocComment();
     genValueAnnotation(defaultValue);
-    key = formatKey(key);
-    composer.print(type + " " + key);
+    composer.println("@Key(" + makeJavaString(key) + ")");
+    String methodName = formatKey(key);
+    composer.print(type + " " + methodName);
     composer.print("(");
     genMethodArgs(defaultValue);
     composer.print(");\n");
diff --git a/user/src/com/google/gwt/i18n/rebind/AnnotationUtil.java b/user/src/com/google/gwt/i18n/rebind/AnnotationUtil.java
new file mode 100644
index 0000000..21fbbd6
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/rebind/AnnotationUtil.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.i18n.rebind;
+
+import com.google.gwt.core.ext.typeinfo.JClassType;
+
+import java.lang.annotation.Annotation;
+
+/**
+ * Utility class for i18n-related annotation manipulation routines.
+ */
+public class AnnotationUtil {
+
+  /**
+   * Find an instance of the specified annotation, walking up the inheritance
+   * tree if necessary.  
+   * 
+   * <p>Note that i18n annotations may appear on classes as well as interfaces
+   * (a concrete implementation can be supplied rather than just an interface
+   * and this is the normal way of using generic Localizable interfaces), so
+   * we have to search the super chain as well as other interfaces.
+   * 
+   * <p>The super chain is walked first, so if an ancestor superclass has the
+   * requested annotation, it will be preferred over a directly implemented
+   * interface.
+   * 
+   * @param <T> Annotation type to search for
+   * @param clazz root class to search, may be null
+   * @param annotationClass class object of Annotation subclass to search for
+   * @return the requested annotation or null if none
+   */
+  static <T extends Annotation> T getClassAnnotation(JClassType clazz,
+      Class<T> annotationClass) {
+    if (clazz == null) {
+      return null;
+    }
+    T annot = clazz.getAnnotation(annotationClass);
+    if (annot == null) {
+      annot = getClassAnnotation(clazz.getSuperclass(), annotationClass);
+      if (annot != null) {
+        return annot;
+      }
+      for (JClassType intf : clazz.getImplementedInterfaces()) {
+        annot = getClassAnnotation(intf, annotationClass);
+        if (annot != null) {
+          return annot;
+        }
+      }
+    }
+    return annot;
+  }
+
+}
diff --git a/user/src/com/google/gwt/i18n/rebind/AnnotationsResource.java b/user/src/com/google/gwt/i18n/rebind/AnnotationsResource.java
index 87b9d05..9e5cc4e 100644
--- a/user/src/com/google/gwt/i18n/rebind/AnnotationsResource.java
+++ b/user/src/com/google/gwt/i18n/rebind/AnnotationsResource.java
@@ -13,9 +13,10 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-
 package com.google.gwt.i18n.rebind;
 
+import static com.google.gwt.i18n.rebind.AnnotationUtil.getClassAnnotation;
+
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.typeinfo.JArrayType;
 import com.google.gwt.core.ext.typeinfo.JClassType;
@@ -167,7 +168,7 @@
    */
   public static KeyGenerator getKeyGenerator(JClassType targetClass)
       throws AnnotationsError {
-    GenerateKeys generator = targetClass.getAnnotation(GenerateKeys.class);
+    GenerateKeys generator = getClassAnnotation(targetClass, GenerateKeys.class);
     if (generator != null) {
       String className = generator.value();
       try {
@@ -377,7 +378,7 @@
     KeyGenerator keyGenerator = getKeyGenerator(clazz);
     map = new HashMap<String, MethodEntry>();
     setPath(clazz.getQualifiedSourceName());
-    DefaultLocale defLocale = clazz.getAnnotation(DefaultLocale.class);
+    DefaultLocale defLocale = getClassAnnotation(clazz, DefaultLocale.class);
     if (defLocale != null && !ResourceFactory.DEFAULT_TOKEN.equals(locale)
         && !locale.equalsIgnoreCase(defLocale.value())) {
       logger.log(TreeLogger.WARN, "@DefaultLocale on "
diff --git a/user/src/com/google/gwt/i18n/rebind/ConstantsInterfaceCreator.java b/user/src/com/google/gwt/i18n/rebind/ConstantsInterfaceCreator.java
index ef7bcd9..de7685b 100644
--- a/user/src/com/google/gwt/i18n/rebind/ConstantsInterfaceCreator.java
+++ b/user/src/com/google/gwt/i18n/rebind/ConstantsInterfaceCreator.java
@@ -50,13 +50,13 @@
 
   @Override
   protected void genValueAnnotation(String defaultValue) {
-    composer.println("@DefaultStringValue(\"" + defaultValue.replace("\"", "\\\"")
-        + "\")");
+    composer.println("@DefaultStringValue(" + makeJavaString(defaultValue)
+        + ")");
   }
 
   @Override
   protected String javaDocComment(String path) {
-    return "Interface to represent the constants contained in resource  bundle:\n\t'"
+    return "Interface to represent the constants contained in resource bundle:\n\t'"
       + path + "'.";
   }
 }
diff --git a/user/src/com/google/gwt/i18n/rebind/MessagesInterfaceCreator.java b/user/src/com/google/gwt/i18n/rebind/MessagesInterfaceCreator.java
index 84ee86a..9c84209 100644
--- a/user/src/com/google/gwt/i18n/rebind/MessagesInterfaceCreator.java
+++ b/user/src/com/google/gwt/i18n/rebind/MessagesInterfaceCreator.java
@@ -92,8 +92,7 @@
 
   @Override
   protected void genValueAnnotation(String defaultValue) {
-    composer.println("@DefaultMessage(\"" + defaultValue.replace("\"", "\\\"")
-        + "\")");
+    composer.println("@DefaultMessage(" + makeJavaString(defaultValue) + ")");
   }
 
   @Override
diff --git a/user/src/com/google/gwt/i18n/rebind/ResourceFactory.java b/user/src/com/google/gwt/i18n/rebind/ResourceFactory.java
index ee5f501..f404f24 100644
--- a/user/src/com/google/gwt/i18n/rebind/ResourceFactory.java
+++ b/user/src/com/google/gwt/i18n/rebind/ResourceFactory.java
@@ -304,8 +304,15 @@
     String partialPath = localizedPath.replace('.', '/');
     for (int i = 0; i < loaders.size(); i++) {
       ResourceFactory element = loaders.get(i);
-      String path = partialPath + "." + element.getExt();
+      String ext = "." + element.getExt();
+      String path = partialPath + ext;
       InputStream m = loader.getResourceAsStream(path);
+      if (m == null && partialPath.contains("$")) {
+        // Also look for A_B for inner classes, as $ in path names
+        // can cause issues for some build tools.
+        path = partialPath.replace('$', '_') + ext;
+        m = loader.getResourceAsStream(path);
+      }
       if (m != null) {
         AbstractResource found = element.load(m);
         found.setPath(path);
diff --git a/user/src/com/google/gwt/i18n/tools/I18NSync.java b/user/src/com/google/gwt/i18n/tools/I18NSync.java
index 18da760..06bed9a 100644
--- a/user/src/com/google/gwt/i18n/tools/I18NSync.java
+++ b/user/src/com/google/gwt/i18n/tools/I18NSync.java
@@ -337,7 +337,11 @@
               + "'should not contain an extension. \"com.google.gwt.SomeClass\" is an example of a correctly formed class string");
     }
     String resourcePath = className.replace('.', '/') + ".properties";
-    URL r = ClassLoader.getSystemResource(resourcePath);
+    ClassLoader cl = Thread.currentThread().getContextClassLoader();
+    if (cl == null) {
+      cl = ClassLoader.getSystemClassLoader();
+    }
+    URL r = cl.getResource(resourcePath);
     if (r == null) {
       throw new FileNotFoundException("Could not find the resource '"
           + resourcePath + " matching '" + className
diff --git a/user/src/com/google/gwt/junit/JUnitShell.java b/user/src/com/google/gwt/junit/JUnitShell.java
index d485cc5..b2ed7605 100644
--- a/user/src/com/google/gwt/junit/JUnitShell.java
+++ b/user/src/com/google/gwt/junit/JUnitShell.java
@@ -511,7 +511,7 @@
   protected void initializeLogger() {
     if (isHeadless()) {
       consoleLogger = new PrintWriterTreeLogger();
-      consoleLogger.setMaxDetail(getLogLevel());
+      consoleLogger.setMaxDetail(getCompilerOptions().getLogLevel());
     } else {
       super.initializeLogger();
     }
diff --git a/user/src/com/google/gwt/user/client/rpc/core/java/util/TreeMap_CustomFieldSerializer.java b/user/src/com/google/gwt/user/client/rpc/core/java/util/TreeMap_CustomFieldSerializer.java
new file mode 100644
index 0000000..ddc24f2
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/rpc/core/java/util/TreeMap_CustomFieldSerializer.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.client.rpc.core.java.util;
+
+import com.google.gwt.user.client.rpc.SerializationException;
+import com.google.gwt.user.client.rpc.SerializationStreamReader;
+import com.google.gwt.user.client.rpc.SerializationStreamWriter;
+
+import java.util.Comparator;
+import java.util.TreeMap;
+
+/**
+ * Custom field serializer for {@link java.util.TreeMap}.
+ */
+@SuppressWarnings("unchecked")
+public class TreeMap_CustomFieldSerializer {
+
+  /* for now, build it entry by entry. Can optimize later via bulk loading */
+  public static void deserialize(SerializationStreamReader streamReader,
+      TreeMap instance) throws SerializationException {
+    Map_CustomFieldSerializerBase.deserialize(streamReader, instance);
+  }
+
+  public static TreeMap instantiate(SerializationStreamReader streamReader)
+      throws SerializationException {
+    return new TreeMap((Comparator) streamReader.readObject());
+  }
+
+  public static void serialize(SerializationStreamWriter streamWriter,
+      TreeMap instance) throws SerializationException {
+    streamWriter.writeObject(instance.comparator());
+    Map_CustomFieldSerializerBase.serialize(streamWriter, instance);
+  }
+}
diff --git a/user/src/com/google/gwt/user/client/rpc/core/java/util/TreeSet_CustomFieldSerializer.java b/user/src/com/google/gwt/user/client/rpc/core/java/util/TreeSet_CustomFieldSerializer.java
new file mode 100644
index 0000000..f48f95e
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/rpc/core/java/util/TreeSet_CustomFieldSerializer.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.client.rpc.core.java.util;
+
+import com.google.gwt.user.client.rpc.SerializationException;
+import com.google.gwt.user.client.rpc.SerializationStreamReader;
+import com.google.gwt.user.client.rpc.SerializationStreamWriter;
+
+import java.util.Comparator;
+import java.util.TreeSet;
+
+/**
+ * Custom field serializer for {@link java.util.TreeMap}.
+ */
+@SuppressWarnings("unchecked")
+public class TreeSet_CustomFieldSerializer {
+
+  /* for now, build it entry by entry. Can optimize later via bulk loading */
+  public static void deserialize(SerializationStreamReader streamReader,
+      TreeSet instance) throws SerializationException {
+    Collection_CustomFieldSerializerBase.deserialize(streamReader, instance);
+  }
+
+  public static TreeSet instantiate(SerializationStreamReader streamReader)
+      throws SerializationException {
+    return new TreeSet((Comparator) streamReader.readObject());
+  }
+
+  public static void serialize(SerializationStreamWriter streamWriter,
+      TreeSet instance) throws SerializationException {
+    streamWriter.writeObject(instance.comparator());
+    Collection_CustomFieldSerializerBase.serialize(streamWriter, instance);
+  }
+}
diff --git a/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStream.java b/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStream.java
index 51c4483..2177020 100644
--- a/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStream.java
+++ b/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStream.java
@@ -23,9 +23,21 @@
 public abstract class AbstractSerializationStream {
 
   /**
+   * The character used to separate fields in client->server RPC messages.
+   * 
+   * Note that this character is referenced in the following places not using
+   * this constant, and they must be changed if this is:
+   * <ul>
+   * <li>{@link ServerSerializationStreamWriter}.deserializeStringTable
+   * <li>{@link ClientSerializationStreamReader}.getQuotingRegex
+   * </ul>
+   */
+  public static final char RPC_SEPARATOR_CHAR = '|';
+
+  /**
    * This is the only supported RPC protocol version.
    */
-  public static final int SERIALIZATION_STREAM_VERSION = 4;
+  public static final int SERIALIZATION_STREAM_VERSION = 5;
 
   private int flags = 0;
   private int version = SERIALIZATION_STREAM_VERSION;
diff --git a/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamWriter.java b/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamWriter.java
index f44d1ec..f760109 100644
--- a/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamWriter.java
+++ b/user/src/com/google/gwt/user/client/rpc/impl/ClientSerializationStreamWriter.java
@@ -16,6 +16,7 @@
 package com.google.gwt.user.client.rpc.impl;
 
 import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.core.client.UnsafeNativeLong;
 import com.google.gwt.user.client.rpc.SerializationException;
 
@@ -27,18 +28,97 @@
 public final class ClientSerializationStreamWriter extends
     AbstractSerializationStreamWriter {
 
+  /**
+   * Used by JSNI, see {@link #quoteString(String)}.
+   */
+  @SuppressWarnings("unused")
+  private static JavaScriptObject regex = getQuotingRegex();
+
   private static void append(StringBuffer sb, String token) {
     assert (token != null);
     sb.append(token);
-    sb.append('\uffff');
+    sb.append(RPC_SEPARATOR_CHAR);
   }
 
+  /**
+   * Create the RegExp instance used for quoting dangerous characters in user
+   * payload strings.
+   * 
+   * Note that {@link AbstractSerializationStream#RPC_SEPARATOR_CHAR} is used in
+   * this expression, which must be updated if the separator character is
+   * changed.
+   * 
+   * For Android WebKit, we quote many more characters to keep them from being
+   * mangled.
+   * 
+   * @return RegExp object
+   */
+  private static native JavaScriptObject getQuotingRegex() /*-{
+    // "|" = AbstractSerializationStream.RPC_SEPARATOR_CHAR
+    var ua = navigator.userAgent.toLowerCase();
+    var webkitregex = /webkit\/([\d]+)/;
+    var webkit = 0;
+    var result = webkitregex.exec(ua);
+    if (result) {
+      webkit = parseInt(result[1]);
+    }
+    if (ua.indexOf("android") != -1) {
+      // initial version of Android WebKit has a double-encoding bug for UTF8,
+      // so we have to encode every non-ASCII character.
+      // TODO(jat): revisit when this bug is fixed in Android
+      return /[\u0000\|\\\u0080-\uFFFF]/g;
+    } else if (webkit < 522) {
+      // Safari 2 doesn't handle \\uXXXX in regexes
+      // TODO(jat): should iPhone be treated specially?
+      return /[\x00\|\\]/g;
+    } else if (webkit > 0) {
+      // other WebKit-based browsers need some additional quoting
+      return /[\u0000\|\\\u0300-\u036F\u0590-\u05FF\uD800-\uFFFF]/g;
+    } else {
+      return /[\u0000\|\\\uD800-\uFFFF]/g;
+    }
+  }-*/;
+
   @UnsafeNativeLong
   // Keep synchronized with LongLib
   private static native double[] makeLongComponents0(long value) /*-{
     return value;
   }-*/;
 
+  /**
+   * Quote characters in a user-supplied string to make sure they are safe to
+   * send to the server.
+   * 
+   * See {@link ServerSerializationStreamReader#deserializeStringTable} for the
+   * corresponding dequoting.
+   * 
+   * @param str string to quote
+   * @return quoted string
+   */
+  private static native String quoteString(String str) /*-{
+    var regex = @com.google.gwt.user.client.rpc.impl.ClientSerializationStreamWriter::regex;
+    var idx = 0;
+    var out = "";
+    var result;
+    while ((result = regex.exec(str)) != null) {
+       out += str.substring(idx, result.index);
+       idx = result.index + 1;
+       var ch = result[0].charCodeAt(0);
+       if (ch == 0) {
+         out += "\\0";
+       } else if (ch == 92) { // backslash
+         out += "\\\\";
+       } else if (ch == 124) { // vertical bar
+         // 124 = "|" = AbstractSerializationStream.RPC_SEPARATOR_CHAR
+         out += "\\!";
+       } else {
+         var hex = ch.toString(16);
+         out += "\\u0000".substring(0, 6 - hex.length) + hex;
+       }
+    }
+    return out + str.substring(idx);
+  }-*/;
+
   private StringBuffer encodeBuffer;
 
   private final String moduleBaseURL;
@@ -67,6 +147,7 @@
    * Call this method before attempting to append any tokens. This method
    * implementation <b>must</b> be called by any overridden version.
    */
+  @Override
   public void prepareToWrite() {
     super.prepareToWrite();
     encodeBuffer = new StringBuffer();
@@ -148,7 +229,7 @@
     List<String> stringTable = getStringTable();
     append(buffer, String.valueOf(stringTable.size()));
     for (String s : stringTable) {
-      append(buffer, s);
+      append(buffer, quoteString(s));
     }
     return buffer;
   }
diff --git a/user/src/com/google/gwt/user/client/ui/SuggestBox.java b/user/src/com/google/gwt/user/client/ui/SuggestBox.java
index 179f4ec..e4ba0ee 100644
--- a/user/src/com/google/gwt/user/client/ui/SuggestBox.java
+++ b/user/src/com/google/gwt/user/client/ui/SuggestBox.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.user.client.ui;
 
+import com.google.gwt.i18n.client.LocaleInfo;
 import com.google.gwt.user.client.Command;
 import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.Window;
@@ -23,7 +24,6 @@
 import com.google.gwt.user.client.ui.SuggestOracle.Request;
 import com.google.gwt.user.client.ui.SuggestOracle.Response;
 import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
-import com.google.gwt.i18n.client.LocaleInfo;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -370,6 +370,7 @@
   private static final String STYLENAME_DEFAULT = "gwt-SuggestBox";
 
   private int limit = 20;
+  private boolean selectsFirstItem = false;
   private SuggestOracle oracle;
   private String currentText;
   private final SuggestionMenu suggestionMenu;
@@ -500,6 +501,16 @@
   }
 
   /**
+   * Returns whether or not the first suggestion will be automatically
+   * selected. This behavior is off by default.
+   *
+   * @return true if the first suggestion will be automatically selected
+   */
+  public boolean getSelectsFirstItem() {
+    return selectsFirstItem;
+  }
+
+  /**
    * Gets the suggest box's {@link com.google.gwt.user.client.ui.SuggestOracle}.
    * 
    * @return the {@link SuggestOracle}
@@ -582,7 +593,18 @@
   public void setPopupStyleName(String style) {
     suggestionPopup.setStyleName(style);
   }
-  
+
+  /**
+   * Turns on or off the behavior that automatically selects the first suggested
+   * item. It defaults to off.
+   *
+   * @param selectsFirstItem Whether or not to automatically select the first
+   *          suggested
+   */
+  public void setSelectsFirstItem(boolean selectsFirstItem) {
+    this.selectsFirstItem = selectsFirstItem;
+  }
+
   public void setTabIndex(int index) {
     box.setTabIndex(index);
   }
@@ -638,8 +660,10 @@
         suggestionMenu.addItem(menuItem);
       }
 
-      // Select the first item in the suggestion menu.
-      suggestionMenu.selectItem(0);
+      if (selectsFirstItem) {
+        // Select the first item in the suggestion menu.
+        suggestionMenu.selectItem(0);
+      }
 
       suggestionPopup.showAlignedPopup();
       suggestionPopup.setAnimationEnabled(isAnimationEnabled);
@@ -665,7 +689,11 @@
               break;
             case KeyboardListener.KEY_ENTER:
             case KeyboardListener.KEY_TAB:
-              suggestionMenu.doSelectedItemAction();
+              if (suggestionMenu.getSelectedItemIndex() < 0) {
+                suggestionPopup.hide();
+              } else {
+                suggestionMenu.doSelectedItemAction();
+              }
               break;
           }
         }
diff --git a/user/src/com/google/gwt/user/rebind/ClassSourceFileComposer.java b/user/src/com/google/gwt/user/rebind/ClassSourceFileComposer.java
index 658c1a6..034949c 100644
--- a/user/src/com/google/gwt/user/rebind/ClassSourceFileComposer.java
+++ b/user/src/com/google/gwt/user/rebind/ClassSourceFileComposer.java
@@ -57,22 +57,24 @@
       throw new IllegalArgumentException("Cannot supply a null package name to"
           + targetClassShortName);
     }
-    // Inlined header to only have one method with a huge number of methods.
+    // TODO: support a user-specified file header
     if (targetPackageName.length() > 0) {
       println("package " + targetPackageName + ";");
     }
     
-    println();
     if (imports != null && imports.length > 0) {
+      println();
       for (int i = 0, n = imports.length; i < n; ++i) {
         println("import " + imports[i] + ";");
       }
-      println();
     }
     if (classJavaDocComment != null) {
       beginJavaDocComment();
       print(classJavaDocComment);
       endJavaDocComment();
+    } else {
+      // beginJavaDocComment adds its own leading newline, make up for it here.
+      println();
     }
     if (category == JavaSourceCategory.CLASS) {
       emitClassDecl(targetClassShortName, superClassName, interfaceNames);
diff --git a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamReader.java b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamReader.java
index 3e1d87e..92e1cd3 100644
--- a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamReader.java
+++ b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamReader.java
@@ -77,43 +77,50 @@
   private enum ValueReader {
     BOOLEAN {
       @Override
-      Object readValue(ServerSerializationStreamReader stream) {
+      Object readValue(ServerSerializationStreamReader stream)
+          throws SerializationException {
         return stream.readBoolean();
       }
     },
     BYTE {
       @Override
-      Object readValue(ServerSerializationStreamReader stream) {
+      Object readValue(ServerSerializationStreamReader stream)
+          throws SerializationException {
         return stream.readByte();
       }
     },
     CHAR {
       @Override
-      Object readValue(ServerSerializationStreamReader stream) {
+      Object readValue(ServerSerializationStreamReader stream)
+          throws SerializationException {
         return stream.readChar();
       }
     },
     DOUBLE {
       @Override
-      Object readValue(ServerSerializationStreamReader stream) {
+      Object readValue(ServerSerializationStreamReader stream)
+          throws SerializationException {
         return stream.readDouble();
       }
     },
     FLOAT {
       @Override
-      Object readValue(ServerSerializationStreamReader stream) {
+      Object readValue(ServerSerializationStreamReader stream)
+          throws SerializationException {
         return stream.readFloat();
       }
     },
     INT {
       @Override
-      Object readValue(ServerSerializationStreamReader stream) {
+      Object readValue(ServerSerializationStreamReader stream)
+          throws SerializationException {
         return stream.readInt();
       }
     },
     LONG {
       @Override
-      Object readValue(ServerSerializationStreamReader stream) {
+      Object readValue(ServerSerializationStreamReader stream)
+          throws SerializationException {
         return stream.readLong();
       }
     },
@@ -126,13 +133,15 @@
     },
     SHORT {
       @Override
-      Object readValue(ServerSerializationStreamReader stream) {
+      Object readValue(ServerSerializationStreamReader stream)
+          throws SerializationException {
         return stream.readShort();
       }
     },
     STRING {
       @Override
-      Object readValue(ServerSerializationStreamReader stream) {
+      Object readValue(ServerSerializationStreamReader stream)
+          throws SerializationException {
         return stream.readString();
       }
     };
@@ -323,6 +332,7 @@
   private final ArrayList<String> tokenList = new ArrayList<String>();
 
   private int tokenListIndex;
+
   {
     CLASS_TO_VECTOR_READER.put(boolean[].class, VectorReader.BOOLEAN_VECTOR);
     CLASS_TO_VECTOR_READER.put(byte[].class, VectorReader.BYTE_VECTOR);
@@ -374,11 +384,30 @@
     stringTable = null;
 
     int idx = 0, nextIdx;
-    while (-1 != (nextIdx = encodedTokens.indexOf('\uffff', idx))) {
+    while (-1 != (nextIdx = encodedTokens.indexOf(RPC_SEPARATOR_CHAR, idx))) {
       String current = encodedTokens.substring(idx, nextIdx);
       tokenList.add(current);
       idx = nextIdx + 1;
     }
+    if (idx == 0) {
+      // Didn't find any separator, assume an older version with different
+      // separators and get the version as the sequence of digits at the
+      // beginning of the encoded string.
+      while (idx < encodedTokens.length()
+          && Character.isDigit(encodedTokens.charAt(idx))) {
+        ++idx;
+      }
+      if (idx == 0) {
+        throw new IncompatibleRemoteServiceException(
+            "Malformed or old RPC message received - expecting version "
+            + SERIALIZATION_STREAM_VERSION);
+      } else {
+        int version = Integer.valueOf(encodedTokens.substring(0, idx));
+        throw new IncompatibleRemoteServiceException("Expecting version "
+            + SERIALIZATION_STREAM_VERSION + " from client, got " + version
+            + ".");
+      }
+    }
 
     super.prepareToRead(encodedTokens);
 
@@ -407,42 +436,42 @@
     }
   }
 
-  public boolean readBoolean() {
+  public boolean readBoolean() throws SerializationException {
     return !extract().equals("0");
   }
 
-  public byte readByte() {
+  public byte readByte() throws SerializationException {
     return Byte.parseByte(extract());
   }
 
-  public char readChar() {
+  public char readChar() throws SerializationException {
     // just use an int, it's more foolproof
     return (char) Integer.parseInt(extract());
   }
 
-  public double readDouble() {
+  public double readDouble() throws SerializationException {
     return Double.parseDouble(extract());
   }
 
-  public float readFloat() {
+  public float readFloat() throws SerializationException {
     return (float) Double.parseDouble(extract());
   }
 
-  public int readInt() {
+  public int readInt() throws SerializationException {
     return Integer.parseInt(extract());
   }
 
-  public long readLong() {
+  public long readLong() throws SerializationException {
     // Keep synchronized with LongLib. The wire format are the two component
     // parts of the double in the client code.
     return (long) readDouble() + (long) readDouble();
   }
 
-  public short readShort() {
+  public short readShort() throws SerializationException {
     return Short.parseShort(extract());
   }
 
-  public String readString() {
+  public String readString() throws SerializationException {
     return getString(readInt());
   }
 
@@ -587,7 +616,50 @@
     BoundedList<String> buffer = new BoundedList<String>(String.class,
         typeNameCount);
     for (int typeNameIndex = 0; typeNameIndex < typeNameCount; ++typeNameIndex) {
-      buffer.add(extract());
+      String str = extract();
+      // Change quoted characters back.
+      int idx = str.indexOf('\\');
+      if (idx >= 0) {
+        StringBuilder buf = new StringBuilder();
+        int pos = 0;
+        while (idx >= 0) {
+          buf.append(str.substring(pos, idx));
+          if (++idx == str.length()) {
+            throw new SerializationException("Unmatched backslash: \""
+                + str + "\"");
+          }
+          char ch = str.charAt(idx);
+          pos = idx + 1;
+          switch (ch) {
+            case '0':
+              buf.append('\u0000');
+              break;
+            case '!':
+              buf.append(RPC_SEPARATOR_CHAR);
+              break;
+            case '\\':
+              buf.append(ch);
+              break;
+            case 'u':
+              try {
+                ch = (char) Integer.parseInt(str.substring(idx + 1, idx + 5), 16);
+              } catch (NumberFormatException e) {
+                throw new SerializationException(
+                    "Invalid Unicode escape sequence in \"" + str + "\"");
+              }
+              buf.append(ch);
+              pos += 4;
+              break;
+            default:
+              throw new SerializationException("Unexpected escape character "
+                  + ch + " after backslash: \"" + str + "\"");
+          }
+          idx = str.indexOf('\\', pos);
+        }
+        buf.append(str.substring(pos));
+        str = buf.toString();
+      }
+      buffer.add(str);
     }
 
     if (buffer.size() != buffer.getExpectedSize()) {
@@ -613,14 +685,18 @@
     throw new NoSuchMethodException("deserialize");
   }
 
-  private String extract() {
-    return tokenList.get(tokenListIndex++);
+  private String extract() throws SerializationException {
+    try {
+      return tokenList.get(tokenListIndex++);
+    } catch (IndexOutOfBoundsException e) {
+      throw new SerializationException("Too few tokens in RPC request", e);
+    }
   }
 
   private Object instantiate(Class<?> customSerializer, Class<?> instanceClass)
       throws InstantiationException, IllegalAccessException,
       IllegalArgumentException, InvocationTargetException,
-      NoSuchMethodException {
+      NoSuchMethodException, SerializationException {
     if (customSerializer != null) {
       for (Method method : customSerializer.getMethods()) {
         if ("instantiate".equals(method.getName())) {
diff --git a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java
index f355f3c..8f837cd 100644
--- a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java
+++ b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java
@@ -371,11 +371,7 @@
 
     for (int i = 0, n = input.length; i < n; ++i) {
       char c = input[i];
-      if (c < NUMBER_OF_JS_ESCAPED_CHARS && JS_CHARS_ESCAPED[c] != 0) {
-        charVector.add(JS_ESCAPE_CHAR);
-        charVector.add(JS_CHARS_ESCAPED[c]);
-      } else if (needsUnicodeEscape(c)) {
-        charVector.add(JS_ESCAPE_CHAR);
+      if (needsUnicodeEscape(c)) {
         unicodeEscape(c, charVector);
       } else {
         charVector.add(c);
@@ -444,43 +440,57 @@
    * </ol>
    */
   private static boolean needsUnicodeEscape(char ch) {
-    switch (Character.getType(ch)) {
-      // Conservative
-      case Character.COMBINING_SPACING_MARK:
-      case Character.ENCLOSING_MARK:
-      case Character.NON_SPACING_MARK:
-      case Character.UNASSIGNED:
-      case Character.PRIVATE_USE:
-      case Character.SPACE_SEPARATOR:
-      case Character.CONTROL:
-
-        // Minimal
-      case Character.LINE_SEPARATOR:
-      case Character.FORMAT:
-      case Character.PARAGRAPH_SEPARATOR:
-      case Character.SURROGATE:
+    switch (ch) {
+      case ' ':
+        // ASCII space gets caught in SPACE_SEPARATOR below, but does not
+        // need to be escaped
+        return false;
+      case JS_QUOTE_CHAR:
+      case JS_ESCAPE_CHAR:
+        // these must be quoted or they will break the protocol
         return true;
-
-      default:
-        if (ch == NON_BREAKING_HYPHEN) {
+      case NON_BREAKING_HYPHEN:
           // This can be expanded into a break followed by a hyphen
           return true;
+      default:
+        switch (Character.getType(ch)) {
+          // Conservative
+          case Character.COMBINING_SPACING_MARK:
+          case Character.ENCLOSING_MARK:
+          case Character.NON_SPACING_MARK:
+          case Character.UNASSIGNED:
+          case Character.PRIVATE_USE:
+          case Character.SPACE_SEPARATOR:
+          case Character.CONTROL:
+
+            // Minimal
+          case Character.LINE_SEPARATOR:
+          case Character.FORMAT:
+          case Character.PARAGRAPH_SEPARATOR:
+          case Character.SURROGATE:
+            return true;
+
+          default:
+            break;
         }
         break;
     }
-
     return false;
   }
 
   /**
-   * Writes either the two or four character escape sequence for a character.
-   * 
+   * Writes a safe escape sequence for a character.  Some characters have a
+   * short form, such as \n for U+000D, while others are represented as \\xNN
+   * or \\uNNNN.
    * 
    * @param ch character to unicode escape
    * @param charVector char vector to receive the unicode escaped representation
    */
   private static void unicodeEscape(char ch, CharVector charVector) {
-    if (ch < 256) {
+    charVector.add(JS_ESCAPE_CHAR);
+    if (ch < NUMBER_OF_JS_ESCAPED_CHARS && JS_CHARS_ESCAPED[ch] != 0) {
+      charVector.add(JS_CHARS_ESCAPED[ch]);
+    } else if (ch < 256) {
       charVector.add('x');
       charVector.add(NIBBLE_TO_HEX_CHAR[(ch >> 4) & 0x0F]);
       charVector.add(NIBBLE_TO_HEX_CHAR[ch & 0x0F]);
diff --git a/user/super/com/google/gwt/emul/java/io/IOException.java b/user/super/com/google/gwt/emul/java/io/IOException.java
new file mode 100644
index 0000000..34362a6
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/io/IOException.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package java.io;
+
+/**
+ * See <a
+ * href="http://java.sun.com/javase/6/docs/api/java/io/IOException.html">the
+ * official Java API doc</a> for details.
+ */
+public class IOException extends Exception {
+
+  public IOException() {
+    super();
+  }
+
+  public IOException(String message) {
+    super(message);
+  }
+
+  public IOException(String message, Throwable throwable) {
+    super(message, throwable);
+  }
+
+  public IOException(Throwable throwable) {
+    super(throwable);
+  }
+}
diff --git a/user/super/com/google/gwt/emul/java/lang/Appendable.java b/user/super/com/google/gwt/emul/java/lang/Appendable.java
new file mode 100644
index 0000000..c0c4c85
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/lang/Appendable.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package java.lang;
+
+import java.io.IOException;
+
+/**
+ * See <a
+ * href="http://java.sun.com/javase/6/docs/api/java/lang/Appendable.html">the
+ * official Java API doc</a> for details.
+ */
+public interface Appendable {
+
+  Appendable append(char c) throws IOException;
+
+  Appendable append(CharSequence charSquence) throws IOException;
+
+  Appendable append(CharSequence charSquence, int start, int end)
+      throws IOException;
+}
diff --git a/user/super/com/google/gwt/emul/java/lang/StringBuffer.java b/user/super/com/google/gwt/emul/java/lang/StringBuffer.java
index f851bef..2b24557 100644
--- a/user/super/com/google/gwt/emul/java/lang/StringBuffer.java
+++ b/user/super/com/google/gwt/emul/java/lang/StringBuffer.java
@@ -30,7 +30,7 @@
  * This class is an exact clone of {@link StringBuilder} except for the name.
  * Any change made to one should be mirrored in the other.
  */
-public class StringBuffer implements CharSequence {
+public class StringBuffer implements CharSequence, Appendable {
   private final StringBufferImpl impl = GWT.create(StringBufferImpl.class);
   private final Object data = impl.createData();
 
diff --git a/user/super/com/google/gwt/emul/java/lang/StringBuilder.java b/user/super/com/google/gwt/emul/java/lang/StringBuilder.java
index 00f9d19..e6d495a 100644
--- a/user/super/com/google/gwt/emul/java/lang/StringBuilder.java
+++ b/user/super/com/google/gwt/emul/java/lang/StringBuilder.java
@@ -30,7 +30,7 @@
  * This class is an exact clone of {@link StringBuffer} except for the name. Any
  * change made to one should be mirrored in the other.
  */
-public class StringBuilder implements CharSequence {
+public class StringBuilder implements CharSequence, Appendable {
   private final StringBufferImpl impl = GWT.create(StringBufferImpl.class);
   private final Object data = impl.createData();
 
diff --git a/user/super/com/google/gwt/emul/java/sql/Time.java b/user/super/com/google/gwt/emul/java/sql/Time.java
index a3302af..81c81cd 100644
--- a/user/super/com/google/gwt/emul/java/sql/Time.java
+++ b/user/super/com/google/gwt/emul/java/sql/Time.java
@@ -27,9 +27,9 @@
     }
 
     try {
-      int hh = Integer.decode(split[0]);
-      int mm = Integer.decode(split[1]);
-      int ss = Integer.decode(split[2]);
+      int hh = Integer.parseInt(split[0]);
+      int mm = Integer.parseInt(split[1]);
+      int ss = Integer.parseInt(split[2]);
 
       return new Time(hh, mm, ss);
     } catch (NumberFormatException e) {
diff --git a/user/super/com/google/gwt/emul/java/util/TreeMap.java b/user/super/com/google/gwt/emul/java/util/TreeMap.java
index 2edb562..1c6ffcd 100644
--- a/user/super/com/google/gwt/emul/java/util/TreeMap.java
+++ b/user/super/com/google/gwt/emul/java/util/TreeMap.java
@@ -15,6 +15,8 @@
  */
 package java.util;
 
+import java.io.Serializable;
+
 /**
  * Implements a TreeMap using a red-black tree. This guarantees O(log n)
  * performance on lookups, inserts, and deletes while maintaining linear
@@ -24,7 +26,8 @@
  * @param <K> key type
  * @param <V> value type
  */
-public class TreeMap<K, V> extends AbstractMap<K, V> implements SortedMap<K, V> {
+public class TreeMap<K, V> extends AbstractMap<K, V> implements
+    SortedMap<K, V>, Serializable {
   /*
    * Implementation derived from public domain C implementation as of 5
    * September 2007 at:
@@ -32,8 +35,6 @@
    * written by Julienne Walker.
    * 
    * This version does not require a parent pointer kept in each node.
-   * 
-   * TODO: should this class be serializable? What to do about the comparator?
    */
 
   /**
@@ -494,14 +495,14 @@
 
   private enum SubMapType {
     All,
-    
+
     Head {
       @Override
       public boolean toKeyValid() {
         return true;
       }
     },
-    
+
     Range {
       @Override
       public boolean fromKeyValid() {
@@ -513,21 +514,21 @@
         return true;
       }
     },
-    
+
     Tail {
       @Override
       public boolean fromKeyValid() {
         return true;
       }
     };
-    
+
     /**
      * @return true if this submap type uses a from-key.
      */
     public boolean fromKeyValid() {
       return false;
     }
-    
+
     /**
      * @return true if this submap type uses a to-key.
      */
@@ -580,8 +581,17 @@
   // The comparator to use.
   private Comparator<? super K> cmp;
 
+  /*
+   * These two fields are just hints to STOB so that it generates serializers
+   * for K and V
+   */
+  @SuppressWarnings("unused")
+  private K exposeKeyType;
+  @SuppressWarnings("unused")
+  private V exposeValueType;
+
   // The root of the tree.
-  private Node<K, V> root;
+  private transient Node<K, V> root;
 
   // The number of nodes in the tree.
   private int size = 0;
@@ -843,14 +853,13 @@
   /**
    * Insert a node into a subtree, collecting state about the insertion.
    * 
-   * If the same key already exists, the value of the node is overwritten
-   * with the value from the new node instead.
+   * If the same key already exists, the value of the node is overwritten with
+   * the value from the new node instead.
    * 
    * @param tree subtree to insert into
    * @param newNode new node to insert
-   * @param state result of the insertion:
-   *     state.found true if the key already existed in the tree
-   *     state.value the old value if the key existed 
+   * @param state result of the insertion: state.found true if the key already
+   *          existed in the tree state.value the old value if the key existed
    * @return the new subtree root
    */
   private Node<K, V> insert(Node<K, V> tree, Node<K, V> newNode, State<V> state) {
diff --git a/user/super/com/google/gwt/emul/java/util/TreeSet.java b/user/super/com/google/gwt/emul/java/util/TreeSet.java
index b4bce7f..496e87d 100644
--- a/user/super/com/google/gwt/emul/java/util/TreeSet.java
+++ b/user/super/com/google/gwt/emul/java/util/TreeSet.java
@@ -15,6 +15,8 @@
  */
 package java.util;
 
+import java.io.Serializable;
+
 /**
  * Implements a set using a TreeMap. <a
  * href="http://java.sun.com/j2se/1.5.0/docs/api/java/util/TreeSet.html">[Sun
@@ -22,15 +24,15 @@
  * 
  * @param <E> element type.
  */
-public class TreeSet<E> extends AbstractSet<E> implements SortedSet<E> {
+public class TreeSet<E> extends AbstractSet<E> implements SortedSet<E>, Serializable {
 
   /**
-   * TreeSet is stored as a TreeMap of the requested type to null Objects.
+   * TreeSet is stored as a TreeMap of the requested type to a constant integer.
    */
-  private SortedMap<E, Object> map;
+  SortedMap<E, Boolean> map;
 
   public TreeSet() {
-    map = new TreeMap<E, Object>();
+    map = new TreeMap<E, Boolean>();
   }
 
   public TreeSet(Collection<? extends E> c) {
@@ -40,9 +42,9 @@
 
   public TreeSet(Comparator<? super E> c) {
     if (c == null) {
-      map = new TreeMap<E, Object>();
+      map = new TreeMap<E, Boolean>();
     } else {
-      map = new TreeMap<E, Object>(c);
+      map = new TreeMap<E, Boolean>(c);
     }
   }
 
@@ -57,14 +59,14 @@
    * 
    * @param map map to use for backing store
    */
-  private TreeSet(SortedMap<E, Object> map) {
+  private TreeSet(SortedMap<E, Boolean> map) {
     this.map = map;
   }
 
   @Override
   public boolean add(E o) {
     // Use "this" as a convenient non-null value to store in the map
-    return map.put(o, this) == null;
+    return map.put(o, Boolean.FALSE) == null;
   }
 
   @Override
diff --git a/user/test/com/google/gwt/dev/jjs/test/CompilerTest.java b/user/test/com/google/gwt/dev/jjs/test/CompilerTest.java
index 4c744e7..e6e3657 100644
--- a/user/test/com/google/gwt/dev/jjs/test/CompilerTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/CompilerTest.java
@@ -26,9 +26,29 @@
 @SuppressWarnings("unused")
 public class CompilerTest extends GWTTestCase {
 
+  private abstract static class AbstractSuper {
+    public static String foo() {
+      if (FALSE) {
+        // prevent inlining
+        return foo();
+      }
+      return "AbstractSuper";
+    }
+  }
+
   private abstract static class Apple implements Fruit {
   }
 
+  private static class ConcreteSub extends AbstractSuper {
+    public static String foo() {
+      if (FALSE) {
+        // prevent inlining
+        return foo();
+      }
+      return "ConcreteSub";
+    }
+  }
+
   private static interface Fruit {
   }
 
@@ -174,6 +194,8 @@
     return @com.google.gwt.dev.jjs.test.CompilerTest$SideEffectCauser5::causeClinitSideEffectOnRead;
   }-*/;
 
+  private Integer boxedInteger = 0;
+
   @Override
   public String getModuleName() {
     return "com.google.gwt.dev.jjs.CompilerSuite";
@@ -557,6 +579,16 @@
     assertEquals("null true", test);
   }
 
+  /**
+   * Issue 2886: inlining should cope with local variables that do not have an
+   * explicit declaration node.
+   */
+  public void testInliningBoxedIncrement() {
+    // should not actually inline, because it has a temp variable
+    incrementBoxedInteger();
+    assertEquals((Integer) 1, boxedInteger);
+  }
+
   public void testJavaScriptReservedWords() {
     boolean delete = TRUE;
     for (int in = 0; in < 10; ++in) {
@@ -777,6 +809,11 @@
     assertEquals(new Foo(2).i, 2);
   }
 
+  public void testStaticMethodResolution() {
+    // Issue 2922
+    assertEquals("AbstractSuper", AbstractSuper.foo());
+  }
+
   public void testStringOptimizations() {
     assertEquals("Herro, AJAX", "Hello, AJAX".replace('l', 'r'));
     assertEquals('J', "Hello, AJAX".charAt(8));
@@ -988,6 +1025,11 @@
     }
   }
 
+  private void incrementBoxedInteger() {
+    // the following will need a temporary variable created
+    boxedInteger++;
+  }
+
   private boolean returnFalse() {
     return false;
   }
diff --git a/user/test/com/google/gwt/emultest/java/sql/SqlTimeTest.java b/user/test/com/google/gwt/emultest/java/sql/SqlTimeTest.java
index e3129f4..0646e3c 100644
--- a/user/test/com/google/gwt/emultest/java/sql/SqlTimeTest.java
+++ b/user/test/com/google/gwt/emultest/java/sql/SqlTimeTest.java
@@ -102,13 +102,20 @@
     }
 
     Time t = Time.valueOf("13:01:30");
-    // Months are 0-based, days are 1-based
     assertEquals(13, t.getHours());
     assertEquals(1, t.getMinutes());
     assertEquals(30, t.getSeconds());
 
     Time d2 = Time.valueOf(t.toString());
     assertEquals(t, d2);
+
+    // tests to see if the various parts are indeed decoded in base-10 (till
+    // r3728 the base was first inferred)
+    Time t2 = Time.valueOf("08:09:01");
+    assertEquals(8, t2.getHours());
+    assertEquals(9, t2.getMinutes());
+    assertEquals(1, t2.getSeconds());
+    assertEquals(t2, Time.valueOf(t2.toString()));
   }
 
   public void testToString() {
diff --git a/user/test/com/google/gwt/i18n/client/CommonInterfaceAnnotations.java b/user/test/com/google/gwt/i18n/client/CommonInterfaceAnnotations.java
new file mode 100644
index 0000000..c33704d
--- /dev/null
+++ b/user/test/com/google/gwt/i18n/client/CommonInterfaceAnnotations.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.i18n.client;
+
+import com.google.gwt.i18n.client.LocalizableResource.GenerateKeys;
+
+/**
+ * Base interface to test annotation inheritance.
+ * 
+ * <p>This works by setting the key generator to MD5 on this interface,
+ * then verifying that keys in the subinterface are looked up with
+ * MD5 hashes rather than method names.
+ */
+@GenerateKeys("com.google.gwt.i18n.rebind.keygen.MD5KeyGenerator")
+public interface CommonInterfaceAnnotations extends Messages {
+
+  @DefaultMessage("foo")
+  String foo();
+}
diff --git a/user/test/com/google/gwt/i18n/client/I18N2Test.java b/user/test/com/google/gwt/i18n/client/I18N2Test.java
index 3cc843b..89568ab 100644
--- a/user/test/com/google/gwt/i18n/client/I18N2Test.java
+++ b/user/test/com/google/gwt/i18n/client/I18N2Test.java
@@ -16,6 +16,7 @@
 package com.google.gwt.i18n.client;
 
 import com.google.gwt.core.client.GWT;
+import com.google.gwt.i18n.client.TestAnnotatedMessages.Nested;
 import com.google.gwt.i18n.client.gen.Colors;
 import com.google.gwt.i18n.client.gen.TestBadKeys;
 import com.google.gwt.junit.client.GWTTestCase;
@@ -32,6 +33,7 @@
     return "com.google.gwt.i18n.I18N2Test";
   }
 
+  @SuppressWarnings("deprecation")
   public void testAnnotatedMessages() {
     TestAnnotatedMessages m = GWT.create(TestAnnotatedMessages.class);
     assertEquals("Test me", m.basicText());
@@ -103,6 +105,10 @@
     assertEquals("a_b_c", test.a_b_c());
     assertEquals("a_b_c", test.getString("a_b_c"));
     assertEquals("__s_dup_dup", test.__s_dup_dup());
+    assertEquals("e in b_C_d", test.getString("__dup_dup"));
+    assertEquals("e in b_C_d", test.__dup_dup());
+    assertEquals("andStar", test.getString("__"));
+    assertEquals("andStar", test.__());
   }
 
   public void testBinding() {
@@ -125,6 +131,18 @@
     assertEquals("a circle", s.circle());
   }
 
+  /**
+   * Verify that nested annotations are looked up with both A$B names
+   * and A_B names.  Note that $ takes precedence and only one file for a
+   * given level in the inheritance tree will be used, so A$B_locale will
+   * be used and A_B_locale ignored.
+   */
+  public void testNestedAnnotations() {
+    Nested m = GWT.create(Nested.class);
+    assertEquals("nested dollar b_C", m.nestedDollar());
+    assertEquals("nested underscore b", m.nestedUnderscore());
+  }
+
   public void testWalkUpColorTree() {
     Colors colors = (Colors) GWT.create(Colors.class);
     assertEquals("red_b_C_d", colors.red());
diff --git a/user/test/com/google/gwt/i18n/client/I18NTest.java b/user/test/com/google/gwt/i18n/client/I18NTest.java
index 1e5e2a7..5889825 100644
--- a/user/test/com/google/gwt/i18n/client/I18NTest.java
+++ b/user/test/com/google/gwt/i18n/client/I18NTest.java
@@ -16,6 +16,7 @@
 package com.google.gwt.i18n.client;
 
 import com.google.gwt.core.client.GWT;
+import com.google.gwt.i18n.client.TestAnnotatedMessages.Nested;
 import com.google.gwt.i18n.client.gen.Colors;
 import com.google.gwt.i18n.client.gen.Shapes;
 import com.google.gwt.i18n.client.gen.TestMessages;
@@ -51,6 +52,7 @@
     return "com.google.gwt.i18n.I18NTest";
   }
 
+  @SuppressWarnings("unchecked") // intentional test of raw map
   public void testAnnotatedConstants() {
     TestAnnotatedConstants c = GWT.create(TestAnnotatedConstants.class);
     assertEquals(14, c.fourteen());
@@ -115,13 +117,22 @@
     assertEquals("PL: Total is US$11,305.01", m.currencyFormat(11305.01));
     assertEquals("PL: Default number format is 1,017.1",
         m.defaultNumberFormat(1017.1));
+    @SuppressWarnings("deprecation")
+    Date date = new Date(107, 11, 1, 12, 1, 2);
     assertEquals("PL: It is 12:01 PM on Saturday, December 1, 2007",
-        m.getTimeDate(new Date(107, 11, 1, 12, 1, 2)));
+        m.getTimeDate(date));
     assertEquals("PL: 13 widgets", m.pluralWidgetsOther(13));
     assertEquals("Too many widgets to count (150) in pig-latin",
         m.pluralWidgetsOther(150));
   }
 
+  public void testAnnotationInheritance() {
+    TestAnnotationGrandchild m = GWT.create(TestAnnotationGrandchild.class);
+    assertEquals("foo", m.foo());
+    assertEquals("bar_piglatin", m.bar());
+    assertEquals("baz_piglatin", m.baz());
+  }
+
   public void testBindings() {
     TestBinding b = (TestBinding) GWT.create(TestBinding.class);
     assertEquals("default", b.a());
@@ -519,6 +530,13 @@
     assertEquals("Extend Protected Inner", extendProtectedInner);
   }
 
+  public void testNestedAnnotations() {
+    Nested m = GWT.create(Nested.class);
+    // no translation exists in piglatin for nested dollar
+    assertEquals("nested dollar", m.nestedDollar());
+    assertEquals("estednay underscoray", m.nestedUnderscore());
+  }
+
   public void testShapesFamily() {
     Shapes shapes = (Shapes) GWT.create(Shapes.class);
     // test overload
diff --git a/user/test/com/google/gwt/i18n/client/TestAnnotatedMessages$Nested_b_C.properties b/user/test/com/google/gwt/i18n/client/TestAnnotatedMessages$Nested_b_C.properties
new file mode 100644
index 0000000..57a9bd3
--- /dev/null
+++ b/user/test/com/google/gwt/i18n/client/TestAnnotatedMessages$Nested_b_C.properties
@@ -0,0 +1 @@
+nestedDollar = nested dollar b_C
diff --git a/user/test/com/google/gwt/i18n/client/TestAnnotatedMessages.java b/user/test/com/google/gwt/i18n/client/TestAnnotatedMessages.java
index 437a559..7c0d56c 100644
--- a/user/test/com/google/gwt/i18n/client/TestAnnotatedMessages.java
+++ b/user/test/com/google/gwt/i18n/client/TestAnnotatedMessages.java
@@ -26,47 +26,66 @@
  * Test of Messages generation using annotations.
  */
 @DefaultLocale("en-US")
-//@GenerateKeys("com.google.gwt.i18n.rebind.keygen.MD5KeyGenerator")
-@GenerateKeys("com.google.gwt.i18n.rebind.keygen.MethodNameKeyGenerator") // default
+// @GenerateKeys("com.google.gwt.i18n.rebind.keygen.MD5KeyGenerator")
+@GenerateKeys("com.google.gwt.i18n.rebind.keygen.MethodNameKeyGenerator")
+// default
 @Generate(format = "com.google.gwt.i18n.rebind.format.PropertiesFormat")
 public interface TestAnnotatedMessages extends Messages {
 
+  /**
+   * Test of property file lookup on nested classes.
+   * 
+   * nestedDollar() is redefined in a property file with a $ in it.
+   * nestedUnderscore() is redefined in a property file with a _ in it.
+   */
+  public interface Nested extends Messages {
+
+    @DefaultMessage("nested dollar")
+    String nestedDollar();
+
+    @DefaultMessage("nested underscore")
+    String nestedUnderscore();
+  }
+
   @DefaultMessage("Test me")
   String basicText();
-  
+
   @DefaultMessage("Once more, with meaning")
   @Meaning("Mangled quote")
   String withMeaning();
-  
+
   @DefaultMessage("One argument: {0}")
   String oneArgument(String value);
-  
+
   @DefaultMessage("One argument, which is optional")
-  String optionalArgument(@Optional String value);
-  
+  String optionalArgument(@Optional
+  String value);
+
   @DefaultMessage("Two arguments, {1} and {0}, inverted")
   String invertedArguments(String one, String two);
-  
+
   @DefaultMessage("Don''t tell me I can''t '{'quote things in braces'}'")
   String quotedText();
-  
+
   @DefaultMessage("This '{0}' would be an argument if not quoted")
   String quotedArg();
-  
+
   @DefaultMessage("Total is {0,number,currency}")
   String currencyFormat(double value);
-  
+
   @DefaultMessage("Default number format is {0,number}")
   String defaultNumberFormat(double value);
-  
+
   @DefaultMessage("It is {0,time,short} on {0,date,full}")
   String getTimeDate(Date value);
-  
+
   @DefaultMessage("{0} widgets")
-  @PluralText({"one", "A widget"})
-  String pluralWidgetsOther(@PluralCount int count);
+  @PluralText( {"one", "A widget"})
+  String pluralWidgetsOther(@PluralCount
+  int count);
 
   @DefaultMessage("{1} {0}")
-  @PluralText({"one", "A {0}"})
-  String twoParamPlural(String name, @PluralCount int count);
+  @PluralText( {"one", "A {0}"})
+  String twoParamPlural(String name, @PluralCount
+  int count);
 }
diff --git a/user/test/com/google/gwt/i18n/client/TestAnnotatedMessages_Nested_b.properties b/user/test/com/google/gwt/i18n/client/TestAnnotatedMessages_Nested_b.properties
new file mode 100644
index 0000000..50b477c
--- /dev/null
+++ b/user/test/com/google/gwt/i18n/client/TestAnnotatedMessages_Nested_b.properties
@@ -0,0 +1 @@
+nestedUnderscore = nested underscore b
diff --git a/user/test/com/google/gwt/i18n/client/TestAnnotatedMessages_Nested_piglatin.properties b/user/test/com/google/gwt/i18n/client/TestAnnotatedMessages_Nested_piglatin.properties
new file mode 100644
index 0000000..4732950
--- /dev/null
+++ b/user/test/com/google/gwt/i18n/client/TestAnnotatedMessages_Nested_piglatin.properties
@@ -0,0 +1 @@
+nestedUnderscore = estednay underscoray
diff --git a/user/test/com/google/gwt/i18n/client/TestAnnotationGrandchild.java b/user/test/com/google/gwt/i18n/client/TestAnnotationGrandchild.java
new file mode 100644
index 0000000..c69b16d
--- /dev/null
+++ b/user/test/com/google/gwt/i18n/client/TestAnnotationGrandchild.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.i18n.client;
+
+/**
+ * Verifies that class-level annotations on grandparent interface are still honored,
+ * to make sure multiple levels of inheritance are handled.
+ */
+public interface TestAnnotationGrandchild extends TestAnnotationInheritance {
+
+  @DefaultMessage("baz")
+  String baz();
+}
diff --git a/user/test/com/google/gwt/i18n/client/TestAnnotationGrandchild_piglatin.properties b/user/test/com/google/gwt/i18n/client/TestAnnotationGrandchild_piglatin.properties
new file mode 100644
index 0000000..6dda386
--- /dev/null
+++ b/user/test/com/google/gwt/i18n/client/TestAnnotationGrandchild_piglatin.properties
@@ -0,0 +1,4 @@
+# MD5 hash of "bar"
+37B51D194A7513E45B56F6524F2D51F2 = bar_piglatin
+# MD5 hash of "baz"
+73FEFFA4B7F6BB68E44CF984C85F6E88 = baz_piglatin
diff --git a/user/test/com/google/gwt/i18n/client/TestAnnotationInheritance.java b/user/test/com/google/gwt/i18n/client/TestAnnotationInheritance.java
new file mode 100644
index 0000000..92ddcdc
--- /dev/null
+++ b/user/test/com/google/gwt/i18n/client/TestAnnotationInheritance.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.i18n.client;
+
+/**
+ * Verifies that class-level annotations on superinterface are still used
+ * by this interface.  In this case, we verify that this interface's keys
+ * are based on the MD5 hash of the default value.
+ */
+public interface TestAnnotationInheritance extends CommonInterfaceAnnotations {
+
+  @DefaultMessage("bar")
+  String bar();
+}
diff --git a/user/test/com/google/gwt/i18n/client/gen/Colors.java b/user/test/com/google/gwt/i18n/client/gen/Colors.java
index 2da193e..560836d 100644
--- a/user/test/com/google/gwt/i18n/client/gen/Colors.java
+++ b/user/test/com/google/gwt/i18n/client/gen/Colors.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 Google Inc.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -17,71 +17,79 @@
 
 /**
  * Interface to represent the constants contained in resource bundle:
- * com/google/gwt/i18n/client/gen/Colors.properties.
+ * 'com/google/gwt/i18n/client/gen/Colors.properties'.
  */
 public interface Colors extends com.google.gwt.i18n.client.Constants {
 
   /**
-   * Translated "ĝrééñ".
-   * 
-   * @return translated "ĝrééñ"
-   * @gwt.key green
-   */
-  String green();
-
-  /**
-   * Translated "réd ".
-   * 
-   * @return translated "réd "
-   * @gwt.key red
-   */
-  String red();
-
-  /**
-   * Translated "ŵĥîţé".
-   * 
-   * @return translated "ŵĥîţé"
-   * @gwt.key white
-   */
-  String white();
-
-  /**
-   * Translated "ĝréý".
-   * 
-   * @return translated "ĝréý"
-   * @gwt.key grey
-   */
-  String grey();
-
-  /**
-   * Translated "bļûç".
-   * 
-   * @return translated "bļûç"
-   * @gwt.key blue
-   */
-  String blue();
-
-  /**
-   * Translated "ýéļļöŵ".
-   * 
-   * @return translated "ýéļļöŵ"
-   * @gwt.key yellow
-   */
-  String yellow();
-
-  /**
    * Translated "bļåçķ".
    * 
    * @return translated "bļåçķ"
-   * @gwt.key black
    */
+  @DefaultStringValue("bļåçķ")
+  @Key("black")
   String black();
 
   /**
+   * Translated "bļûç".
+   * 
+   * @return translated "bļûç"
+   */
+  @DefaultStringValue("bļûç")
+  @Key("blue")
+  String blue();
+
+  /**
+   * Translated "ĝrééñ".
+   * 
+   * @return translated "ĝrééñ"
+   */
+  @DefaultStringValue("ĝrééñ")
+  @Key("green")
+  String green();
+
+  /**
+   * Translated "ĝréý".
+   * 
+   * @return translated "ĝréý"
+   */
+  @DefaultStringValue("ĝréý")
+  @Key("grey")
+  String grey();
+
+  /**
+   * Translated "réd ".
+   * 
+   * @return translated "réd "
+   */
+  @DefaultStringValue("réd ")
+  @Key("red")
+  String red();
+
+  /**
    * Translated "any primary color".
    * 
    * @return translated "any primary color"
-   * @gwt.key shapeColor
    */
+  @DefaultStringValue("any primary color")
+  @Key("shapeColor")
   String shapeColor();
+
+  /**
+   * Translated "ŵĥîţé".
+   * 
+   * @return translated "ŵĥîţé"
+   */
+  @DefaultStringValue("ŵĥîţé")
+  @Key("white")
+  String white();
+
+  /**
+   * Translated "ýéļļöŵ".
+   * 
+   * @return translated "ýéļļöŵ"
+   */
+  @DefaultStringValue("ýéļļöŵ")
+  @Key("yellow")
+  String yellow();
 }
diff --git a/user/test/com/google/gwt/i18n/client/gen/Shapes.java b/user/test/com/google/gwt/i18n/client/gen/Shapes.java
index 089cd74..1f2be04 100644
--- a/user/test/com/google/gwt/i18n/client/gen/Shapes.java
+++ b/user/test/com/google/gwt/i18n/client/gen/Shapes.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 Google Inc.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -17,39 +17,43 @@
 
 /**
  * Interface to represent the constants contained in resource bundle:
- * com/google/gwt/i18n/client/gen/Shapes.properties.
+ * 'com/google/gwt/i18n/client/gen/Shapes.properties'.
  */
 public interface Shapes extends com.google.gwt.i18n.client.Constants {
 
   /**
-   * Translated "a triangle".
-   * 
-   * @return translated "a triangle"
-   * @gwt.key triangle
-   */
-  String triangle();
-
-  /**
-   * Translated "a square ".
-   * 
-   * @return translated "a square "
-   * @gwt.key square
-   */
-  String square();
-
-  /**
    * Translated "a circle".
    * 
    * @return translated "a circle"
-   * @gwt.key circle
    */
+  @DefaultStringValue("a circle")
+  @Key("circle")
   String circle();
 
   /**
    * Translated "a color wheel".
    * 
    * @return translated "a color wheel"
-   * @gwt.key shapeColor
    */
+  @DefaultStringValue("a color wheel")
+  @Key("shapeColor")
   String shapeColor();
+
+  /**
+   * Translated "a square\u0009".
+   * 
+   * @return translated "a square\u0009"
+   */
+  @DefaultStringValue("a square\u0009")
+  @Key("square")
+  String square();
+
+  /**
+   * Translated "a triangle".
+   * 
+   * @return translated "a triangle"
+   */
+  @DefaultStringValue("a triangle")
+  @Key("triangle")
+  String triangle();
 }
diff --git a/user/test/com/google/gwt/i18n/client/gen/SingleConstant.java b/user/test/com/google/gwt/i18n/client/gen/SingleConstant.java
index 57648d9..a3cfec7 100644
--- a/user/test/com/google/gwt/i18n/client/gen/SingleConstant.java
+++ b/user/test/com/google/gwt/i18n/client/gen/SingleConstant.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 Google Inc.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -17,7 +17,7 @@
 
 /**
  * Interface to represent the constants contained in resource bundle:
- * com/google/gwt/i18n/client/gen/SingleConstant.properties.
+ * 'com/google/gwt/i18n/client/gen/SingleConstant.properties'.
  */
 public interface SingleConstant extends com.google.gwt.i18n.client.Constants {
 
@@ -25,7 +25,8 @@
    * Translated "me".
    * 
    * @return translated "me"
-   * @gwt.key justOne
    */
+  @DefaultStringValue("me")
+  @Key("justOne")
   String justOne();
 }
diff --git a/user/test/com/google/gwt/i18n/client/gen/SingleMessages.java b/user/test/com/google/gwt/i18n/client/gen/SingleMessages.java
index a97c277..d14e857 100644
--- a/user/test/com/google/gwt/i18n/client/gen/SingleMessages.java
+++ b/user/test/com/google/gwt/i18n/client/gen/SingleMessages.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 Google Inc.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -17,7 +17,7 @@
 
 /**
  * Interface to represent the messages contained in resource bundle:
- * com/google/gwt/i18n/client/gen/SingleMessages.properties.
+ * 'com/google/gwt/i18n/client/gen/SingleMessages.properties'.
  */
 public interface SingleMessages extends com.google.gwt.i18n.client.Messages {
 
@@ -25,7 +25,8 @@
    * Translated "me".
    * 
    * @return translated "me"
-   * @gwt.key justOne
    */
+  @DefaultMessage("me")
+  @Key("justOne")
   String justOne();
 }
diff --git a/user/test/com/google/gwt/i18n/client/gen/TestBadKeys.java b/user/test/com/google/gwt/i18n/client/gen/TestBadKeys.java
index 08ca867..d10bb2e 100644
--- a/user/test/com/google/gwt/i18n/client/gen/TestBadKeys.java
+++ b/user/test/com/google/gwt/i18n/client/gen/TestBadKeys.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 Google Inc.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -17,17 +17,27 @@
 
 /**
  * Interface to represent the constants contained in resource bundle:
- * com/google/gwt/i18n/client/gen/TestBadKeys.properties.
+ * 'com/google/gwt/i18n/client/gen/TestBadKeys.properties'.
  */
 public interface TestBadKeys extends
     com.google.gwt.i18n.client.ConstantsWithLookup {
 
   /**
+   * Translated "andStar".
+   * 
+   * @return translated "andStar"
+   */
+  @DefaultStringValue("andStar")
+  @Key("&*")
+  String __();
+
+  /**
    * Translated "_".
    * 
    * @return translated "_"
-   * @gwt.key -
    */
+  @DefaultStringValue("_")
+  @Key("-")
   String _();
 
   /**
@@ -36,159 +46,188 @@
    * 
    * @return translated
    *         "________________________________________________________________"
-   * @gwt.key ----------------------------------------------------------------
    */
+  @DefaultStringValue("________________________________________________________________")
+  @Key("----------------------------------------------------------------")
   String ________________________________________________________________();
 
   /**
-   * Translated "__dup".
-   * 
-   * @return translated "__dup"
-   * @gwt.key .
-   */
-  String __dup();
-
-  /**
    * Translated "__s".
    * 
    * @return translated "__s"
-   * @gwt.key --s
    */
+  @DefaultStringValue("__s")
+  @Key("--s")
   String __s();
 
   /**
    * Translated "__s_dup".
    * 
    * @return translated "__s_dup"
-   * @gwt.key -.s
    */
+  @DefaultStringValue("__s_dup")
+  @Key("-.s")
   String __s_dup();
 
   /**
-   * Translated "__s_dup_dup".
-   * 
-   * @return translated "__s_dup_dup"
-   * @gwt.key ..s
-   */
-  String __s_dup_dup();
-
-  /**
-   * Translated "_1_2_3_4".
-   * 
-   * @return translated "_1_2_3_4"
-   * @gwt.key _1.2.3.4
-   */
-  String _1_2_3_4();
-
-  /**
    * Translated "_c_____".
    * 
    * @return translated "_c_____"
-   * @gwt.key -c..-.-
    */
+  @DefaultStringValue("_c_____")
+  @Key("-c..-.-")
   String _c_____();
 
   /**
+   * Translated "__dup".
+   * 
+   * @return translated "__dup"
+   */
+  @DefaultStringValue("__dup")
+  @Key(".")
+  String __dup();
+
+  /**
+   * Translated "__s_dup_dup".
+   * 
+   * @return translated "__s_dup_dup"
+   */
+  @DefaultStringValue("__s_dup_dup")
+  @Key("..s")
+  String __s_dup_dup();
+
+  /**
    * Translated "_level".
    * 
    * @return translated "_level"
-   * @gwt.key .level
    */
+  @DefaultStringValue("_level")
+  @Key(".level")
   String _level();
 
   /**
-   * Translated "a__b".
-   * 
-   * @return translated "a__b"
-   * @gwt.key a-.b
-   */
-  String a__b();
-
-  /**
-   * Translated "a_b_c".
-   * 
-   * @return translated "a_b_c"
-   * @gwt.key a-b-c
-   */
-  String a_b_c();
-
-  /**
    * Translated "AWT_end".
    * 
    * @return translated "AWT_end"
-   * @gwt.key AWT.end
    */
+  @DefaultStringValue("AWT_end")
+  @Key("AWT.end")
   String AWT_end();
 
   /**
    * Translated "AWT_f5".
    * 
    * @return translated "AWT_f5"
-   * @gwt.key AWT.f5
    */
+  @DefaultStringValue("AWT_f5")
+  @Key("AWT.f5")
   String AWT_f5();
 
   /**
-   * Translated "cell_2_5".
-   * 
-   * @return translated "cell_2_5"
-   * @gwt.key cell.2.5
-   */
-  String cell_2_5();
-
-  /**
    * Translated "Cursor_MoveDrop_32x32_File".
    * 
    * @return translated "Cursor_MoveDrop_32x32_File"
-   * @gwt.key Cursor.MoveDrop.32x32.File
    */
+  @DefaultStringValue("Cursor_MoveDrop_32x32_File")
+  @Key("Cursor.MoveDrop.32x32.File")
   String Cursor_MoveDrop_32x32_File();
 
   /**
+   * Translated "_1_2_3_4".
+   * 
+   * @return translated "_1_2_3_4"
+   */
+  @DefaultStringValue("_1_2_3_4")
+  @Key("_1.2.3.4")
+  String _1_2_3_4();
+
+  /**
+   * Translated "a__b".
+   * 
+   * @return translated "a__b"
+   */
+  @DefaultStringValue("a__b")
+  @Key("a-.b")
+  String a__b();
+
+  /**
+   * Translated "a_b_c".
+   * 
+   * @return translated "a_b_c"
+   */
+  @DefaultStringValue("a_b_c")
+  @Key("a-b-c")
+  String a_b_c();
+
+  /**
+   * Translated "cell_2_5".
+   * 
+   * @return translated "cell_2_5"
+   */
+  @DefaultStringValue("cell_2_5")
+  @Key("cell.2.5")
+  String cell_2_5();
+
+  /**
    * Translated "entity_160".
    * 
    * @return translated "entity_160"
-   * @gwt.key entity.160
    */
+  @DefaultStringValue("entity_160")
+  @Key("entity.160")
   String entity_160();
 
   /**
    * Translated "logger_org_hibernate_jdbc".
    * 
    * @return translated "logger_org_hibernate_jdbc"
-   * @gwt.key logger.org.hibernate.jdbc
    */
+  @DefaultStringValue("logger_org_hibernate_jdbc")
+  @Key("logger.org.hibernate.jdbc")
   String logger_org_hibernate_jdbc();
 
   /**
    * Translated "maven_checkstyle_properties".
    * 
    * @return translated "maven_checkstyle_properties"
-   * @gwt.key maven.checkstyle.properties
    */
+  @DefaultStringValue("maven_checkstyle_properties")
+  @Key("maven.checkstyle.properties")
   String maven_checkstyle_properties();
 
   /**
    * Translated "maven_jdiff_old_tag".
    * 
    * @return translated "maven_jdiff_old_tag"
-   * @gwt.key maven.jdiff.old.tag
    */
+  @DefaultStringValue("maven_jdiff_old_tag")
+  @Key("maven.jdiff.old.tag")
   String maven_jdiff_old_tag();
 
   /**
    * Translated "permissions_755".
    * 
    * @return translated "permissions_755"
-   * @gwt.key permissions.755
    */
+  @DefaultStringValue("permissions_755")
+  @Key("permissions.755")
   String permissions_755();
 
   /**
    * Translated "zh_spacer".
    * 
    * @return translated "zh_spacer"
-   * @gwt.key zh.spacer
    */
+  @DefaultStringValue("zh_spacer")
+  @Key("zh.spacer")
   String zh_spacer();
+
+  /**
+   * Translated "e".
+   * 
+   * @return translated "e"
+   */
+  @DefaultStringValue("e")
+  @Key("�")
+  String __dup_dup();
 }
diff --git a/user/test/com/google/gwt/i18n/client/gen/TestBadKeys.properties b/user/test/com/google/gwt/i18n/client/gen/TestBadKeys.properties
index 46ab4d5..4761eff 100644
--- a/user/test/com/google/gwt/i18n/client/gen/TestBadKeys.properties
+++ b/user/test/com/google/gwt/i18n/client/gen/TestBadKeys.properties
@@ -19,4 +19,5 @@
 -c..-.- = _c_____
 ---------------------------------------------------------------- = ________________________________________________________________
 _1.2.3.4 = _1_2_3_4
-
+&* = andStar
+é = e
diff --git a/user/test/com/google/gwt/i18n/client/gen/TestBadKeys_b_C_d.properties b/user/test/com/google/gwt/i18n/client/gen/TestBadKeys_b_C_d.properties
new file mode 100644
index 0000000..a036225
--- /dev/null
+++ b/user/test/com/google/gwt/i18n/client/gen/TestBadKeys_b_C_d.properties
@@ -0,0 +1 @@
+é = e in b_C_d
diff --git a/user/test/com/google/gwt/i18n/client/gen/TestConstantsQuoting.java b/user/test/com/google/gwt/i18n/client/gen/TestConstantsQuoting.java
new file mode 100644
index 0000000..a2a50af
--- /dev/null
+++ b/user/test/com/google/gwt/i18n/client/gen/TestConstantsQuoting.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.i18n.client.gen;
+
+/**
+ * Interface to represent the constants contained in resource bundle:
+ * 'com/google/gwt/i18n/client/gen/TestConstantsQuoting.properties'.
+ */
+public interface TestConstantsQuoting extends
+    com.google.gwt.i18n.client.Constants {
+
+  /**
+   * Translated "Doesn''t work this way here".
+   * 
+   * @return translated "Doesn''t work this way here"
+   */
+  @DefaultStringValue("Doesn''t work this way here")
+  @Key("doubledQuote")
+  String doubledQuote();
+
+  /**
+   * Translated "Embedded\r\ncr-nl.".
+   * 
+   * @return translated "Embedded\r\ncr-nl."
+   */
+  @DefaultStringValue("Embedded\r\ncr-nl.")
+  @Key("embeddedCRNL")
+  String embeddedCRNL();
+
+  /**
+   * Translated "This line has an\nembedded newline".
+   * 
+   * @return translated "This line has an\nembedded newline"
+   */
+  @DefaultStringValue("This line has an\nembedded newline")
+  @Key("embeddedNL")
+  String embeddedNL();
+
+  /**
+   * Translated "\"Don't worry, be happy\" he said.".
+   * 
+   * @return translated "\"Don't worry, be happy\" he said."
+   */
+  @DefaultStringValue("\"Don't worry, be happy\" he said.")
+  @Key("embeddedQuote")
+  String embeddedQuote();
+}
diff --git a/user/test/com/google/gwt/i18n/client/gen/TestConstantsQuoting.properties b/user/test/com/google/gwt/i18n/client/gen/TestConstantsQuoting.properties
new file mode 100644
index 0000000..97baf73
--- /dev/null
+++ b/user/test/com/google/gwt/i18n/client/gen/TestConstantsQuoting.properties
@@ -0,0 +1,4 @@
+embeddedNL=This line has an\nembedded newline
+embeddedCRNL=Embedded\r\ncr-nl.
+embeddedQuote="Don't worry, be happy" he said.
+doubledQuote=Doesn''t work this way here
diff --git a/user/test/com/google/gwt/i18n/client/gen/TestMessages.java b/user/test/com/google/gwt/i18n/client/gen/TestMessages.java
index 136c8cd..022ebeb 100644
--- a/user/test/com/google/gwt/i18n/client/gen/TestMessages.java
+++ b/user/test/com/google/gwt/i18n/client/gen/TestMessages.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 Google Inc.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -16,107 +16,120 @@
 package com.google.gwt.i18n.client.gen;
 
 /**
- * Interface to represent the messages contained in resource bundle
- * com/google/gwt/i18n/client/gen/TestMessages.properties.
+ * Interface to represent the messages contained in resource bundle:
+ * 'com/google/gwt/i18n/client/gen/TestMessages.properties'.
  */
 public interface TestMessages extends com.google.gwt.i18n.client.Messages {
 
   /**
-   * Translated "{0},{1}, "a","b", "{0}", "{1}", ''a'', 'b', '{0}', ''{1}''".
-   * 
-   * @return translated "{0},{1}, "a","b", "{0}", "{1}", ''a'', 'b', '{0}',
-   *         ''{1}''"
-   * @gwt.key argsWithQuotes
-   */
-  String argsWithQuotes(String arg0, String arg1);
-
-  /**
-   * Translated "{1} is the second arg, {0} is the first".
-   * 
-   * @return translated "{1} is the second arg, {0} is the first"
-   * @gwt.key args2
-   */
-  String args2(String arg0, String arg1);
-
-  /**
    * Translated "no args".
    * 
    * @return translated "no args"
-   * @gwt.key args0
    */
+  @DefaultMessage("no args")
+  @Key("args0")
   String args0();
 
   /**
-   * Translated "{0}".
-   * 
-   * @return translated "{0}"
-   * @gwt.key simpleMessageTest
-   */
-  String simpleMessageTest(String arg0);
-
-  /**
-   * Translated ""~" ~~ "~~~~ """.
-   * 
-   * @return translated ""~" ~~ "~~~~ """
-   * @gwt.key testWithXs
-   */
-  String testWithXs();
-
-  /**
-   * Translated "arg0arg1 arg0,arg1 {0}arg4".
-   * 
-   * @return translated "arg0arg1 arg0,arg1 {0}arg4"
-   * @gwt.key argsTest
-   */
-  String argsTest(String arg0);
-
-  /**
-   * Translated "repeatedArgs: {0}, {1}, {0}, {1}, {0}, {1}, {0}, {1}".
-   * 
-   * @return translated "repeatedArgs: {0}, {1}, {0}, {1}, {0}, {1}, {0}, {1}"
-   * @gwt.key testLotsOfUsageOfArgs
-   */
-  String testLotsOfUsageOfArgs(String arg0, String arg1);
-
-  /**
-   * Translated "{0},{1},{2},{3},{4},{5},{6},{7},{8},{9}".
-   * 
-   * @return translated "{0},{1},{2},{3},{4},{5},{6},{7},{8},{9}"
-   * @gwt.key args10
-   */
-  String args10(String arg0, String arg1, String arg2, String arg3,
-      String arg4, String arg5, String arg6, String arg7, String arg8,
-      String arg9);
-
-  /**
-   * Translated "お{0}你{1}好".
-   * 
-   * @return translated "お{0}你{1}好"
-   * @gwt.key unicode
-   */
-  String unicode(String arg0, String arg1);
-
-  /**
    * Translated "{0} is a arg".
    * 
    * @return translated "{0} is a arg"
-   * @gwt.key args1
    */
+  @DefaultMessage("{0} is a arg")
+  @Key("args1")
   String args1(String arg0);
 
   /**
-   * Translated "{quoted}".
+   * Translated "{0},{1},{2},{3},{4},{5},{6},{7},{8},{9}".
    * 
-   * @return translated "{quoted}"
-   * @gwt.key quotedBraces
+   * @return translated "{0},{1},{2},{3},{4},{5},{6},{7},{8},{9}"
    */
-  String quotedBraces();
+  @DefaultMessage("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9}")
+  @Key("args10")
+  String args10(String arg0, String arg1, String arg2, String arg3,
+      String arg4, String arg5, String arg6, String arg7, String arg8,
+      String arg9);
+
+  /**
+   * Translated "{1} is the second arg, {0} is the first".
+   * 
+   * @return translated "{1} is the second arg, {0} is the first"
+   */
+  @DefaultMessage("{1} is the second arg, {0} is the first")
+  @Key("args2")
+  String args2(String arg0, String arg1);
+
+  /**
+   * Translated "arg0arg1 arg0,arg1 {0}arg4".
+   * 
+   * @return translated "arg0arg1 arg0,arg1 {0}arg4"
+   */
+  @DefaultMessage("arg0arg1 arg0,arg1 {0}arg4")
+  @Key("argsTest")
+  String argsTest(String arg0);
+
+  /**
+   * Translated "{0},{1}, \"a\",\"b\", \"{0}\", \"{1}\", ''a'', 'b', '{0}',
+   * ''{1}''".
+   * 
+   * @return translated "{0},{1}, \"a\",\"b\", \"{0}\", \"{1}\", ''a'', 'b',
+   *         '{0}', ''{1}''"
+   */
+  @DefaultMessage("{0},{1}, \"a\",\"b\", \"{0}\", \"{1}\", ''a'', 'b', '{0}', ''{1}''")
+  @Key("argsWithQuotes")
+  String argsWithQuotes(String arg0, String arg1);
 
   /**
    * Translated "".
    * 
    * @return translated ""
-   * @gwt.key empty
    */
+  @DefaultMessage("")
+  @Key("empty")
   String empty();
+
+  /**
+   * Translated "'{'quoted'}'".
+   * 
+   * @return translated "'{'quoted'}'"
+   */
+  @DefaultMessage("'{'quoted'}'")
+  @Key("quotedBraces")
+  String quotedBraces();
+
+  /**
+   * Translated "{0}".
+   * 
+   * @return translated "{0}"
+   */
+  @DefaultMessage("{0}")
+  @Key("simpleMessageTest")
+  String simpleMessageTest(String arg0);
+
+  /**
+   * Translated "repeatedArgs: {0}, {1}, {0}, {1}, {0}, {1}, {0}, {1}".
+   * 
+   * @return translated "repeatedArgs: {0}, {1}, {0}, {1}, {0}, {1}, {0}, {1}"
+   */
+  @DefaultMessage("repeatedArgs: {0}, {1}, {0}, {1}, {0}, {1}, {0}, {1}")
+  @Key("testLotsOfUsageOfArgs")
+  String testLotsOfUsageOfArgs(String arg0, String arg1);
+
+  /**
+   * Translated "\"~\" ~~ \"~~~~ \"\"".
+   * 
+   * @return translated "\"~\" ~~ \"~~~~ \"\""
+   */
+  @DefaultMessage("\"~\" ~~ \"~~~~ \"\"")
+  @Key("testWithXs")
+  String testWithXs();
+
+  /**
+   * Translated "お{0}你{1}好".
+   * 
+   * @return translated "お{0}你{1}好"
+   */
+  @DefaultMessage("お{0}你{1}好")
+  @Key("unicode")
+  String unicode(String arg0, String arg1);
 }
diff --git a/user/test/com/google/gwt/i18n/client/gen/TestMessagesQuoting.java b/user/test/com/google/gwt/i18n/client/gen/TestMessagesQuoting.java
new file mode 100644
index 0000000..b20efdc
--- /dev/null
+++ b/user/test/com/google/gwt/i18n/client/gen/TestMessagesQuoting.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.i18n.client.gen;
+
+/**
+ * Interface to represent the messages contained in resource bundle:
+ * 'com/google/gwt/i18n/client/gen/TestMessagesQuoting.properties'.
+ */
+public interface TestMessagesQuoting extends
+    com.google.gwt.i18n.client.Messages {
+
+  /**
+   * Translated "Embedded\r\ncr-nl.".
+   * 
+   * @return translated "Embedded\r\ncr-nl."
+   */
+  @DefaultMessage("Embedded\r\ncr-nl.")
+  @Key("embeddedCRNL")
+  String embeddedCRNL();
+
+  /**
+   * Translated "This line has an\nembedded newline".
+   * 
+   * @return translated "This line has an\nembedded newline"
+   */
+  @DefaultMessage("This line has an\nembedded newline")
+  @Key("embeddedNL")
+  String embeddedNL();
+
+  /**
+   * Translated "\"Don''t worry, be happy\" he said.".
+   * 
+   * @return translated "\"Don''t worry, be happy\" he said."
+   */
+  @DefaultMessage("\"Don''t worry, be happy\" he said.")
+  @Key("embeddedQuote")
+  String embeddedQuote();
+}
diff --git a/user/test/com/google/gwt/i18n/client/gen/TestMessagesQuoting.properties b/user/test/com/google/gwt/i18n/client/gen/TestMessagesQuoting.properties
new file mode 100644
index 0000000..35263b8
--- /dev/null
+++ b/user/test/com/google/gwt/i18n/client/gen/TestMessagesQuoting.properties
@@ -0,0 +1,3 @@
+embeddedNL=This line has an\nembedded newline
+embeddedCRNL=Embedded\r\ncr-nl.
+embeddedQuote="Don''t worry, be happy" he said.
diff --git a/user/test/com/google/gwt/i18n/tools/I18NSyncTest_.java b/user/test/com/google/gwt/i18n/tools/I18NSyncTest_.java
index 30d0024..389359d 100644
--- a/user/test/com/google/gwt/i18n/tools/I18NSyncTest_.java
+++ b/user/test/com/google/gwt/i18n/tools/I18NSyncTest_.java
@@ -50,7 +50,12 @@
       // Should be caught
     }
   }
-
+  
+  public void testConstantsQuoting() throws IOException  {
+    String className = CLIENT_SOURCE_PACKAGE + "TestConstantsQuoting";
+    I18NSync.createConstantsInterfaceFromClassName(className, CLIENT_SOURCE_DIR);
+  }
+  
   public void testFileIsDirCase() {
     try {
       I18NSync.createMessagesInterfaceFromClassName(CLIENT_SOURCE_PACKAGE, null);
@@ -77,15 +82,14 @@
     I18NSync.createMessagesInterfaceFromClassName(className, CLIENT_SOURCE_DIR);
   }
 
+  public void testMessagesQuoting() throws IOException  {
+    String className = CLIENT_SOURCE_PACKAGE + "TestMessagesQuoting";
+    I18NSync.createMessagesInterfaceFromClassName(className, CLIENT_SOURCE_DIR);
+  }
+
   public void testMethodRenaming() throws IOException {
     String className = CLIENT_SOURCE_PACKAGE + "TestBadKeys";
     I18NSync.createConstantsWithLookupInterfaceFromClassName(className,
         CLIENT_SOURCE_DIR);
   }
-
-  public void testWarning() throws IOException {
-    String className = CLIENT_SOURCE_PACKAGE + "TestReallyBadKeys";
-    I18NSync.createConstantsWithLookupInterfaceFromClassName(className);
-  }
-
 }
diff --git a/user/test/com/google/gwt/user/client/rpc/CollectionsTest.java b/user/test/com/google/gwt/user/client/rpc/CollectionsTest.java
index 78eac3f..db7c871 100644
--- a/user/test/com/google/gwt/user/client/rpc/CollectionsTest.java
+++ b/user/test/com/google/gwt/user/client/rpc/CollectionsTest.java
@@ -22,6 +22,8 @@
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeHashMap;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeHashSet;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeLinkedHashMap;
+import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeTreeMap;
+import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeTreeSet;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeVector;
 
 import java.sql.Time;
@@ -32,6 +34,8 @@
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.TreeMap;
+import java.util.TreeSet;
 import java.util.Vector;
 
 /**
@@ -591,6 +595,48 @@
     });
   }
 
+  public void testTreeMap() {
+    delayTestFinish(TEST_DELAY);
+
+    CollectionsTestServiceAsync service = getServiceAsync();
+    for (boolean option : new boolean[] {true, false}) {
+      final TreeMap<String, MarkerTypeTreeMap> expected = TestSetFactory.createTreeMap(option);
+      service.echo(expected, option,
+          new AsyncCallback<TreeMap<String, MarkerTypeTreeMap>>() {
+            public void onFailure(Throwable caught) {
+              TestSetValidator.rethrowException(caught);
+            }
+
+            public void onSuccess(TreeMap<String, MarkerTypeTreeMap> result) {
+              assertNotNull(result);
+              assertTrue(TestSetValidator.isValid(expected, result));
+              finishTest();
+            }
+          });
+    }
+  }
+
+  public void testTreeSet() {
+    delayTestFinish(TEST_DELAY);
+    
+    CollectionsTestServiceAsync service = getServiceAsync();
+    for (boolean option : new boolean[] {true, false}) {
+      final TreeSet<MarkerTypeTreeSet> expected = TestSetFactory.createTreeSet(option);
+      service.echo(expected, option,
+          new AsyncCallback<TreeSet<MarkerTypeTreeSet>>() {
+            public void onFailure(Throwable caught) {
+              TestSetValidator.rethrowException(caught);
+            }
+
+            public void onSuccess(TreeSet<MarkerTypeTreeSet> result) {
+              assertNotNull(result);
+              assertTrue(TestSetValidator.isValid(expected, result));
+              finishTest();
+            }
+          });
+    }
+  }
+
   public void testVector() {
     delayTestFinish(TEST_DELAY);
 
diff --git a/user/test/com/google/gwt/user/client/rpc/CollectionsTestService.java b/user/test/com/google/gwt/user/client/rpc/CollectionsTestService.java
index 38590f9..6560d31 100644
--- a/user/test/com/google/gwt/user/client/rpc/CollectionsTestService.java
+++ b/user/test/com/google/gwt/user/client/rpc/CollectionsTestService.java
@@ -21,6 +21,8 @@
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeHashSet;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeLinkedHashMap;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeLinkedHashSet;
+import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeTreeMap;
+import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeTreeSet;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeVector;
 
 import java.sql.Time;
@@ -32,6 +34,8 @@
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.TreeMap;
+import java.util.TreeSet;
 import java.util.Vector;
 
 /**
@@ -50,6 +54,7 @@
       super(msg);
     }
   }
+
   ArrayList<MarkerTypeArrayList> echo(ArrayList<MarkerTypeArrayList> value)
       throws CollectionsTestServiceException;
 
@@ -113,6 +118,13 @@
 
   Timestamp[] echo(Timestamp[] value) throws CollectionsTestServiceException;
 
+  TreeMap<String, MarkerTypeTreeMap> echo(
+      TreeMap<String, MarkerTypeTreeMap> value, boolean option)
+      throws CollectionsTestServiceException;
+
+  TreeSet<MarkerTypeTreeSet> echo(TreeSet<MarkerTypeTreeSet> value,
+      boolean option) throws CollectionsTestServiceException;
+
   Vector<MarkerTypeVector> echo(Vector<MarkerTypeVector> value)
       throws CollectionsTestServiceException;
 
diff --git a/user/test/com/google/gwt/user/client/rpc/CollectionsTestServiceAsync.java b/user/test/com/google/gwt/user/client/rpc/CollectionsTestServiceAsync.java
index 5423060..66fe565 100644
--- a/user/test/com/google/gwt/user/client/rpc/CollectionsTestServiceAsync.java
+++ b/user/test/com/google/gwt/user/client/rpc/CollectionsTestServiceAsync.java
@@ -21,6 +21,8 @@
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeHashSet;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeLinkedHashMap;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeLinkedHashSet;
+import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeTreeMap;
+import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeTreeSet;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeVector;
 
 import java.sql.Time;
@@ -32,6 +34,8 @@
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.TreeMap;
+import java.util.TreeSet;
 import java.util.Vector;
 
 /**
@@ -94,7 +98,13 @@
   void echo(String[][] value, AsyncCallback<String[][]> callback);
 
   void echo(Time[] value, AsyncCallback<Time[]> callback);
+  
+  void echo(TreeMap<String, MarkerTypeTreeMap> value, boolean option,
+      AsyncCallback<TreeMap<String, MarkerTypeTreeMap>> callback);
 
+  void echo(TreeSet<MarkerTypeTreeSet> value, boolean option,
+      AsyncCallback<TreeSet<MarkerTypeTreeSet>> callback);
+  
   void echo(Timestamp[] value, AsyncCallback<Timestamp[]> callback);
 
   void echo(Vector<MarkerTypeVector> value,
diff --git a/user/test/com/google/gwt/user/client/rpc/TestSetFactory.java b/user/test/com/google/gwt/user/client/rpc/TestSetFactory.java
index 8bb6dd9..a9fdeff 100644
--- a/user/test/com/google/gwt/user/client/rpc/TestSetFactory.java
+++ b/user/test/com/google/gwt/user/client/rpc/TestSetFactory.java
@@ -15,16 +15,20 @@
  */
 package com.google.gwt.user.client.rpc;
 
+import java.io.Serializable;
 import java.sql.Time;
 import java.sql.Timestamp;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Comparator;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.TreeMap;
+import java.util.TreeSet;
 import java.util.Vector;
 
 /**
@@ -37,7 +41,7 @@
    * exposure in various collections.
    */
   public static class MarkerBase implements IsSerializable {
-    public String value;
+    private String value;
 
     public MarkerBase(String value) {
       this.value = value;
@@ -53,6 +57,10 @@
       return false;
     }
 
+    public String getValue() {
+      return value;
+    }
+
     @Override
     public int hashCode() {
       return value.hashCode();
@@ -157,6 +165,43 @@
    * A single-use marker type to independently check type parameter exposure in
    * various collections.
    */
+  public static final class MarkerTypeTreeMap extends MarkerBase {
+
+    public MarkerTypeTreeMap(String value) {
+      super(value);
+    }
+
+    MarkerTypeTreeMap() {
+      super(null);
+    }
+  }
+
+  /**
+   * A single-use marker type to independently check type parameter exposure in
+   * various collections.
+   */
+  public static final class MarkerTypeTreeSet extends MarkerBase implements
+      Comparable<MarkerTypeTreeSet> {
+
+    public MarkerTypeTreeSet(String value) {
+      super(value);
+    }
+
+    MarkerTypeTreeSet() {
+      super(null);
+    }
+
+    // if getValue() returns null, a null-pointer expection will be thrown,
+    // which is the intended effect
+    public int compareTo(MarkerTypeTreeSet arg0) {
+      return getValue().compareTo(arg0.getValue());
+    }
+  }
+
+  /**
+   * A single-use marker type to independently check type parameter exposure in
+   * various collections.
+   */
   public static final class MarkerTypeVector extends MarkerBase {
 
     public MarkerTypeVector(String value) {
@@ -236,6 +281,35 @@
   public static class UnserializableNode {
   }
 
+  static class ReverseSorter<T extends Comparable<T>> implements Comparator<T>,
+      Serializable {
+
+    // for gwt-serialization
+    ReverseSorter() {
+    }
+
+    public int compare(T a, T b) {
+      // Explicit null check to match JRE specs
+      if (a == null || b == null) {
+        throw new NullPointerException();
+      }
+      return b.compareTo(a);
+    }
+
+    @Override
+    public int hashCode() {
+      return 0;
+    }
+
+    @Override
+    public boolean equals(Object ob) {
+      if (!(ob instanceof ReverseSorter)) {
+        return false;
+      }
+      return true;
+    }
+  }
+
   public static ArrayList<MarkerTypeArrayList> createArrayList() {
     ArrayList<MarkerTypeArrayList> list = new ArrayList<MarkerTypeArrayList>();
     list.add(new MarkerTypeArrayList("foo"));
@@ -425,6 +499,38 @@
         "valueOf", "constructor", "__proto__"};
   }
 
+  public static TreeMap<String, MarkerTypeTreeMap> createTreeMap(
+      boolean defaultComparator) {
+    TreeMap<String, MarkerTypeTreeMap> map;
+    if (defaultComparator) {
+      map = new TreeMap<String, MarkerTypeTreeMap>();
+    } else {
+      map = new TreeMap<String, MarkerTypeTreeMap>(new ReverseSorter<String>());
+    }
+    map.put("foo", new MarkerTypeTreeMap("foo"));
+    map.put("bar", new MarkerTypeTreeMap("bar"));
+    map.put("baz", new MarkerTypeTreeMap("baz"));
+    map.put("bal", new MarkerTypeTreeMap("bal"));
+    map.put("w00t", new MarkerTypeTreeMap("w00t"));
+    return map;
+  }
+
+  public static TreeSet<MarkerTypeTreeSet> createTreeSet(
+      boolean defaultComparator) {
+    TreeSet<MarkerTypeTreeSet> set;
+    if (defaultComparator) {
+      set = new TreeSet<MarkerTypeTreeSet>();
+    } else {
+      set = new TreeSet<MarkerTypeTreeSet>(new ReverseSorter<MarkerTypeTreeSet>());
+    }
+    set.add(new MarkerTypeTreeSet("foo"));
+    set.add(new MarkerTypeTreeSet("bar"));
+    set.add(new MarkerTypeTreeSet("baz"));
+    set.add(new MarkerTypeTreeSet("bal"));
+    set.add(new MarkerTypeTreeSet("w00t"));
+    return set;
+  }
+
   public static Vector<MarkerTypeVector> createVector() {
     Vector<MarkerTypeVector> vector = new Vector<MarkerTypeVector>();
     vector.add(new MarkerTypeVector("foo"));
diff --git a/user/test/com/google/gwt/user/client/rpc/TestSetValidator.java b/user/test/com/google/gwt/user/client/rpc/TestSetValidator.java
index da95a6d..a0b21f8 100644
--- a/user/test/com/google/gwt/user/client/rpc/TestSetValidator.java
+++ b/user/test/com/google/gwt/user/client/rpc/TestSetValidator.java
@@ -15,6 +15,8 @@
  */
 package com.google.gwt.user.client.rpc;
 
+import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeTreeMap;
+import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeTreeSet;
 import com.google.gwt.user.client.rpc.TestSetFactory.SerializableDoublyLinkedNode;
 import com.google.gwt.user.client.rpc.TestSetFactory.SerializablePrivateNoArg;
 import com.google.gwt.user.client.rpc.TestSetFactory.SerializableWithTwoArrays;
@@ -26,7 +28,10 @@
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
 import java.util.Vector;
 import java.util.Map.Entry;
 
@@ -318,6 +323,60 @@
     return node.one == node.two;
   }
 
+  // also checks whether the sorting of entries is maintained or not.
+  public static boolean isValid(TreeMap<String, MarkerTypeTreeMap> expected,
+      TreeMap<String, MarkerTypeTreeMap> map) {
+    if (map == null) {
+      return false;
+    }
+    if (!equalsWithNullCheck(map.comparator(), expected.comparator())) {
+      return false;
+    }
+    int size = 0;
+    if ((size = expected.size()) != map.size()) {
+      return false;
+    }
+    // entrySet returns entries in the sorted order
+    List<Map.Entry<String, MarkerTypeTreeMap>> actualList = new ArrayList<Map.Entry<String, MarkerTypeTreeMap>>(
+        map.entrySet());
+    List<Map.Entry<String, MarkerTypeTreeMap>> expectedList = new ArrayList<Map.Entry<String, MarkerTypeTreeMap>>(
+        expected.entrySet());
+    for (int index = 0; index < size; index++) {
+      Entry<String, MarkerTypeTreeMap> expectedEntry = expectedList.get(index);
+      Entry<String, MarkerTypeTreeMap> actualEntry = actualList.get(index);
+      if (!equalsWithNullCheck(expectedEntry.getKey(), actualEntry.getKey())
+          || !equalsWithNullCheck(expectedEntry.getValue(),
+              actualEntry.getValue())) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  // also checks whether the sorting of entries is maintained or not.
+  public static boolean isValid(TreeSet<MarkerTypeTreeSet> expected,
+      TreeSet<MarkerTypeTreeSet> set) {
+    if (set == null) {
+      return false;
+    }
+    if (!equalsWithNullCheck(set.comparator(), expected.comparator())) {
+      return false;
+    }
+    int size = 0;
+    if ((size = expected.size()) != set.size()) {
+      return false;
+    }
+    // entrySet returns entries in the sorted order
+    List<MarkerTypeTreeSet> actualList = new ArrayList<MarkerTypeTreeSet>(set);
+    List<MarkerTypeTreeSet> expectedList = new ArrayList<MarkerTypeTreeSet>(expected);
+    for (int index = 0; index < size; index++) {
+      if (!equalsWithNullCheck(expectedList.get(index), actualList.get(index))) {
+        return false;
+      }
+    }
+    return true;
+  }
+  
   public static boolean isValid(Vector expected, Vector actual) {
     if (actual == null) {
       return false;
@@ -446,6 +505,12 @@
     return true;
   }
 
+  /**
+   * Wrap an exception in RuntimeException if necessary so it doesn't have to be listed in
+   * throws clauses.
+   * 
+   * @param caught exception to wrap
+   */
   public static void rethrowException(Throwable caught) {
     if (caught instanceof RuntimeException) {
       throw (RuntimeException) caught;
@@ -453,5 +518,8 @@
       throw new RuntimeException(caught);
     }
   }
+  private static boolean equalsWithNullCheck(Object a, Object b) {
+    return a == b || (a != null && a.equals(b));
+  }
 
 }
diff --git a/user/test/com/google/gwt/user/client/rpc/UnicodeEscapingService.java b/user/test/com/google/gwt/user/client/rpc/UnicodeEscapingService.java
index 7edbbfa..d31416f 100644
--- a/user/test/com/google/gwt/user/client/rpc/UnicodeEscapingService.java
+++ b/user/test/com/google/gwt/user/client/rpc/UnicodeEscapingService.java
@@ -19,12 +19,69 @@
  * Service used to test unicode escaping.
  */
 public interface UnicodeEscapingService extends RemoteService {
+  
+  /**
+   * Exception for escaping errors.
+   */
+  public static class InvalidCharacterException extends Exception {
+
+    private static String toHex(int val) {
+      String hex = Integer.toHexString(val);
+      return "00000".substring(hex.length()) + hex;
+    }
+
+    private int index;
+    private int expected;
+    private int actual;
+
+    protected InvalidCharacterException() { }
+
+    public InvalidCharacterException(int index, int expected, int actual) {
+      super(index < 0 ? "String length mismatch: expected = " + expected + ", actual = " + actual
+          : "At index " + index + ", expected = U+" + toHex(expected) + ", actual = U+"
+          + toHex(actual));
+      this.index = index;
+      this.expected = expected;
+      this.actual = actual;
+    }
+
+    public int getActual() {
+      return actual;
+    }
+
+    public int getExpected() {
+      return expected;
+    }
+
+    public int getIndex() {
+      return index;
+    }
+  }
+
   /**
    * Returns a string containing the characters from start to end.
    * 
-   * @param start start character value, inclusive
-   * @param end end character value, exclusive
+   * Used to verify server->client escaping.
+   * 
+   * @param start start character value, inclusive -- note if greater
+   *     than {@link Character#MIN_SUPPLEMENTARY_CODE_POINT} it will
+   *     be included as surrogate pairs in the returned string.
+   * @param end end character value, exclusive (see above comment)
    * @return a string containing the characters from start to end
    */
   String getStringContainingCharacterRange(int start, int end);
+
+  /**
+   * Verifies that the string contains the specified characters.
+   * 
+   * Used to verify client->server escaping.
+   *
+   * @param start start code point value included
+   * @param end first code point not included
+   * @param str string to verify
+   * @throws InvalidCharacterException if the string does not contain the specified characters
+   * @return true if the verification succeeded
+   */
+  boolean verifyStringContainingCharacterRange(int start, int end, String str)
+      throws InvalidCharacterException;
 }
diff --git a/user/test/com/google/gwt/user/client/rpc/UnicodeEscapingServiceAsync.java b/user/test/com/google/gwt/user/client/rpc/UnicodeEscapingServiceAsync.java
index f563218..6ae9fd4 100644
--- a/user/test/com/google/gwt/user/client/rpc/UnicodeEscapingServiceAsync.java
+++ b/user/test/com/google/gwt/user/client/rpc/UnicodeEscapingServiceAsync.java
@@ -15,10 +15,14 @@
  */
 package com.google.gwt.user.client.rpc;
 
+import com.google.gwt.user.client.rpc.UnicodeEscapingService.InvalidCharacterException;
+
 /**
  * Async version of the {@link UnicodeEscapingService} interface.
  */
 public interface UnicodeEscapingServiceAsync {
   void getStringContainingCharacterRange(int start, int end,
-      AsyncCallback callback);
+      AsyncCallback<String> callback);
+  void verifyStringContainingCharacterRange(int start, int end, String str,
+      AsyncCallback<Boolean> callback) throws InvalidCharacterException;
 }
diff --git a/user/test/com/google/gwt/user/client/rpc/UnicodeEscapingTest.java b/user/test/com/google/gwt/user/client/rpc/UnicodeEscapingTest.java
index 1c5f989..f45777e 100644
--- a/user/test/com/google/gwt/user/client/rpc/UnicodeEscapingTest.java
+++ b/user/test/com/google/gwt/user/client/rpc/UnicodeEscapingTest.java
@@ -17,68 +17,256 @@
 
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.user.client.rpc.UnicodeEscapingService.InvalidCharacterException;
 
 /**
- * Test which verifies that we properly escape JSON strings sent back from the
- * server.
+ * Test that any valid string can be sent via RPC in both directions.
+ * 
+ * TODO(jat): make unpaired surrogates work properly if it is possible to do
+ * so on all browsers, then add them to this test.
  */
 public class UnicodeEscapingTest extends GWTTestCase {
 
-  private static final int DEFAULT_TEST_FINISH_DELAY_MS = 5000;
-  private static final int CHARACTER_RANGE_SIZE = 1024;
-  private static final int LAST_CHARACTER = 0x10000;
+  /** the size of a block of characters to test */
+  private static final int CHARACTER_BLOCK_SIZE = 64;
 
-  private int start = 0;
+  /**
+   * When doing the non-BMP test, we don't test every block of characters
+   * because it takes too long - this is the increment to use.  It is not a
+   * power of two so we alter the alignment of the block of characters we skip.
+   */
+  private static final int NON_BMP_TEST_INCREMENT = 8192 + 64;
 
+  /** the time to wait for the test of a block of characters */
+  private static final int TEST_FINISH_DELAY_MS = 500000;
+
+  /**
+   * Generates a string containing a sequence of code points.
+   * 
+   * @param start first code point to include in the string
+   * @param end one past the last code point to include in the string
+   * @return a string containing all the requested code points
+   */
+  public static String getStringContainingCharacterRange(int start, int end) {
+    StringBuffer buf = new StringBuffer();
+    for (int codePoint = start; codePoint < end; ++codePoint) {
+      if (Character.isSupplementaryCodePoint(codePoint)) {
+        buf.append(Character.toChars(codePoint));
+      } else {
+        buf.append((char) codePoint);
+      }
+    }
+
+    return buf.toString();
+  }
+
+  /*
+   * Copied from HistoryTest.
+   */
+  private static native boolean isSafari2() /*-{
+    var exp = / AppleWebKit\/([\d]+)/;
+    var result = exp.exec(navigator.userAgent);
+    if (result) {
+      // The standard history implementation works fine on WebKit >= 522
+      // (Safari 3 beta).
+      if (parseInt(result[1]) >= 522) {
+        return false;
+      }
+    }
+  
+    // The standard history implementation works just fine on the iPhone, which
+    // unfortunately reports itself as WebKit/420+.
+    if (navigator.userAgent.indexOf('iPhone') != -1) {
+      return false;
+    }
+  
+    return true;
+  }-*/;
+  /**
+   * Verifies that the supplied string includes the requested code points.
+   * 
+   * @param start first code point to include in the string
+   * @param end one past the last code point to include in the string
+   * @param str the string to test
+   * @throws InvalidCharacterException if a character doesn't match
+   * @throws RuntimeException if the string is too long
+   */
+  public static void verifyStringContainingCharacterRange(int start, int end,
+      String str) throws InvalidCharacterException {
+    if (str == null) {
+      throw new NullPointerException("String is null");
+    }
+    int expectedLen = end - start;
+    int strLen = str.codePointCount(0, str.length());
+    for (int i = 0, codePoint = start; i < strLen;
+        i = Character.offsetByCodePoints(str, i, 1)) {
+      int strCodePoint = str.codePointAt(i);
+      if (strCodePoint != codePoint) {
+        throw new InvalidCharacterException(i, codePoint, strCodePoint);
+      }
+      ++codePoint;
+    }
+    if (strLen < expectedLen) {
+      throw new InvalidCharacterException(strLen, start + strLen, -1);
+    } else if (expectedLen != strLen) {
+      throw new RuntimeException("Too many characters returned on block from U+"
+          + Integer.toHexString(start) + " to U+" + Integer.toHexString(end)
+          + ": expected=" + expectedLen + ", actual=" + strLen);
+    }
+  }
   private static UnicodeEscapingServiceAsync getService() {
-    UnicodeEscapingServiceAsync service = (UnicodeEscapingServiceAsync) GWT.create(UnicodeEscapingService.class);
+    UnicodeEscapingServiceAsync service = GWT.create(
+        UnicodeEscapingService.class);
     ServiceDefTarget target = (ServiceDefTarget) service;
     target.setServiceEntryPoint(GWT.getModuleBaseURL() + "unicodeEscape");
     return service;
   }
 
+  /** start of current block being tested */
+  private int current;
+
+  @Override
   public String getModuleName() {
     return "com.google.gwt.user.RPCSuite";
   }
 
   /**
+   * Generate strings containing ranges of characters and sends them to the
+   * server for verification. This ensures that client->server string escaping
+   * properly handles all BMP characters.
+   * 
+   * Unpaired or improperly paired surrogates are not tested here, as some
+   * browsers refuse to accept them.  Properly paired surrogates are tested
+   * in the non-BMP test.
+   *  
+   * Note that this does not test all possible combinations, which might be an
+   * issue, particularly with combining marks, though they should be logically
+   * equivalent in that case.
+   * 
+   * @throws InvalidCharacterException
+   */
+  public void testClientToServerBMP() throws InvalidCharacterException {
+    delayTestFinish(TEST_FINISH_DELAY_MS);
+    if (isSafari2()) {
+      // Safari2 can't be fixed for many characters, including null
+      // We only guarantee that basic ISO-Latin characters are unmolested.
+      clientToServerVerifyRange(0x0001, 0x0300, CHARACTER_BLOCK_SIZE,
+          CHARACTER_BLOCK_SIZE);
+    } else {
+      clientToServerVerifyRange(Character.MIN_CODE_POINT,
+          Character.MIN_SURROGATE, CHARACTER_BLOCK_SIZE,
+          CHARACTER_BLOCK_SIZE);
+      clientToServerVerifyRange(Character.MAX_SURROGATE + 1,
+          Character.MIN_SUPPLEMENTARY_CODE_POINT, CHARACTER_BLOCK_SIZE,
+          CHARACTER_BLOCK_SIZE);
+    }
+  }
+
+  /**
+   * Generate strings containing ranges of characters and sends them to the
+   * server for verification. This ensures that client->server string escaping
+   * properly handles all non-BMP characters.
+   * 
+   * Note that this does not test all possible combinations, which might be an
+   * issue, particularly with combining marks, though they should be logically
+   * equivalent in that case.
+   * 
+   * @throws InvalidCharacterException
+   */
+  public void testClientToServerNonBMP() throws InvalidCharacterException {
+    delayTestFinish(TEST_FINISH_DELAY_MS);
+    clientToServerVerifyRange(Character.MIN_SUPPLEMENTARY_CODE_POINT,
+        Character.MAX_CODE_POINT + 1, CHARACTER_BLOCK_SIZE,
+        NON_BMP_TEST_INCREMENT);
+  }
+
+  /**
+   * Requests strings of CHARACTER_RANGE_SIZE from the server and validates
+   * that the returned string length matches CHARACTER_RANGE_SIZE and that all
+   * of the characters remain intact.
+   * 
+   * Note that this does not test all possible combinations, which might be an
+   * issue, particularly with combining marks, though they should be logically
+   * equivalent in that case.
+   */
+  public void testServerToClientBMP() {
+    delayTestFinish(TEST_FINISH_DELAY_MS);
+    serverToClientVerify(Character.MIN_CODE_POINT,
+        Character.MIN_SUPPLEMENTARY_CODE_POINT, CHARACTER_BLOCK_SIZE,
+        CHARACTER_BLOCK_SIZE);
+  }
+
+  /**
    * Requests strings of CHARACTER_RANGE_SIZE from the server and validates that
    * the returned string length matches CHARACTER_RANGE_SIZE and that all of the
-   * characters remain intact.
+   * characters remain intact.  Note that this test verifies non-BMP characters
+   * (ie, those which are represented as pairs of surrogates).
+   * 
+   * Note that this does not test all possible combinations, which might be an
+   * issue, particularly with combining marks, though they should be logically
+   * equivalent in that case.
    */
-  public void testUnicodeEscaping() {
-    delayTestFinish(DEFAULT_TEST_FINISH_DELAY_MS);
+  public void testServerToClientNonBMP() {
+    delayTestFinish(TEST_FINISH_DELAY_MS);
+    serverToClientVerify(Character.MIN_SUPPLEMENTARY_CODE_POINT,
+        Character.MAX_CODE_POINT + 1, CHARACTER_BLOCK_SIZE,
+        NON_BMP_TEST_INCREMENT);
+  }
 
-    getService().getStringContainingCharacterRange(0, CHARACTER_RANGE_SIZE,
-        new AsyncCallback() {
-          public void onFailure(Throwable caught) {
-            TestSetValidator.rethrowException(caught);
+  private void clientToServerVerifyRange(final int start, final int end,
+      final int size, final int step) throws InvalidCharacterException {
+    current = start;
+    int blockEnd = Math.min(end, current + size);
+    getService().verifyStringContainingCharacterRange(current, blockEnd,
+        getStringContainingCharacterRange(start, blockEnd),
+        new AsyncCallback<Boolean>() {
+      public void onFailure(Throwable caught) {
+        TestSetValidator.rethrowException(caught);
+      }
+
+      public void onSuccess(Boolean ignored) {
+        current += step;
+        if (current < end) {
+          delayTestFinish(TEST_FINISH_DELAY_MS);
+          int blockEnd = Math.min(end, current + size);
+          try {
+            getService().verifyStringContainingCharacterRange(current, blockEnd,
+                getStringContainingCharacterRange(current, blockEnd), this);
+          } catch (InvalidCharacterException e) {
+            TestSetValidator.rethrowException(e);
           }
+        } else {
+          finishTest();
+        }
+      }
+    });
+  }
 
-          public void onSuccess(Object result) {
-            String str = (String) result;
+  private void serverToClientVerify(final int start, final int end,
+      final int size, final int step) {
+    current = start;
+    getService().getStringContainingCharacterRange(start, Math.min(end,
+        current + size), new AsyncCallback<String>() {
+      public void onFailure(Throwable caught) {
+        TestSetValidator.rethrowException(caught);
+      }
 
-            assertTrue("expected: " + Integer.toString(CHARACTER_RANGE_SIZE)
-                + " actual: " + str.length() + " for character range ["
-                + Integer.toString(start) + ", "
-                + Integer.toString(start + CHARACTER_RANGE_SIZE) + ")",
-                CHARACTER_RANGE_SIZE == str.length());
-
-            char[] chars = str.toCharArray();
-            for (int i = 0; i < CHARACTER_RANGE_SIZE; ++i) {
-              assertEquals(i + start, chars[i]);
-            }
-
-            start += CHARACTER_RANGE_SIZE;
-            if (start < LAST_CHARACTER) {
-              delayTestFinish(DEFAULT_TEST_FINISH_DELAY_MS);
-
-              getService().getStringContainingCharacterRange(start,
-                  start + CHARACTER_RANGE_SIZE, this);
-            } else {
-              finishTest();
-            }
-          }
-        });
+      public void onSuccess(String str) {
+        try {
+          verifyStringContainingCharacterRange(current, Math.min(end,
+              current + size), str);
+        } catch (InvalidCharacterException e) {
+          TestSetValidator.rethrowException(e);
+        }
+        current += step;
+        if (current < end) {
+          delayTestFinish(TEST_FINISH_DELAY_MS);
+          getService().getStringContainingCharacterRange(current,
+              Math.min(end, current + size), this);
+        } else {
+          finishTest();
+        }
+      }
+    });
   }
 }
diff --git a/user/test/com/google/gwt/user/server/rpc/CollectionsTestServiceImpl.java b/user/test/com/google/gwt/user/server/rpc/CollectionsTestServiceImpl.java
index 12bd35b..d02c6e1 100644
--- a/user/test/com/google/gwt/user/server/rpc/CollectionsTestServiceImpl.java
+++ b/user/test/com/google/gwt/user/server/rpc/CollectionsTestServiceImpl.java
@@ -24,6 +24,8 @@
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeHashSet;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeLinkedHashMap;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeLinkedHashSet;
+import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeTreeMap;
+import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeTreeSet;
 import com.google.gwt.user.client.rpc.TestSetFactory.MarkerTypeVector;
 
 import java.sql.Time;
@@ -36,6 +38,8 @@
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.TreeMap;
+import java.util.TreeSet;
 import java.util.Vector;
 
 /**
@@ -328,6 +332,29 @@
     return actual;
   }
 
+  public TreeMap<String, MarkerTypeTreeMap> echo(
+      TreeMap<String, MarkerTypeTreeMap> actual, boolean option)
+      throws CollectionsTestServiceException {
+    TreeMap<String, MarkerTypeTreeMap> expected = TestSetFactory.createTreeMap(option);
+    if (!TestSetValidator.isValid(expected, actual)) {
+      throw new CollectionsTestServiceException("expected: "
+          + expected.toString() + " actual: " + actual.toString());
+    }
+
+    return actual;
+  }
+
+  public TreeSet<MarkerTypeTreeSet> echo(TreeSet<MarkerTypeTreeSet> actual,
+      boolean option) throws CollectionsTestServiceException {
+    TreeSet<MarkerTypeTreeSet> expected = TestSetFactory.createTreeSet(option);
+    if (!TestSetValidator.isValid(expected, actual)) {
+      throw new CollectionsTestServiceException("expected: "
+          + expected.toString() + " actual: " + actual.toString());
+    }
+
+    return actual;
+  }
+
   public Vector<MarkerTypeVector> echo(Vector<MarkerTypeVector> actual)
       throws CollectionsTestServiceException {
     Vector<MarkerTypeVector> expected = TestSetFactory.createVector();
@@ -348,4 +375,5 @@
 
     return value;
   }
+
 }
diff --git a/user/test/com/google/gwt/user/server/rpc/RPCTest.java b/user/test/com/google/gwt/user/server/rpc/RPCTest.java
index 8eb1bb3..20dd1b9 100644
--- a/user/test/com/google/gwt/user/server/rpc/RPCTest.java
+++ b/user/test/com/google/gwt/user/server/rpc/RPCTest.java
@@ -15,10 +15,14 @@
  */
 package com.google.gwt.user.server.rpc;
 
+import static com.google.gwt.user.client.rpc.impl.AbstractSerializationStream.RPC_SEPARATOR_CHAR;
+
 import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
 import com.google.gwt.user.client.rpc.RemoteService;
 import com.google.gwt.user.client.rpc.SerializableException;
 import com.google.gwt.user.client.rpc.SerializationException;
+import com.google.gwt.user.client.rpc.impl.AbstractSerializationStream;
+import com.google.gwt.user.server.rpc.impl.ServerSerializationStreamReader;
 
 import junit.framework.TestCase;
 
@@ -27,6 +31,7 @@
 /**
  * Tests for the {@link com.google.gwt.user.server.rpc.RPC RPC} class.
  */
+@SuppressWarnings("deprecation")
 public class RPCTest extends TestCase {
 
   private static interface A extends RemoteService {
@@ -41,50 +46,74 @@
     void method1();
   }
 
-  private static final String VALID_ENCODED_REQUEST = "4\uffff" + // version
-      "0\uffff" + // flags
-      "4\uffff" + // string table entry count
-      A.class.getName() + "\uffff" + // string table entry #0
-      "method2" + "\uffff" + // string table entry #1
-      "moduleBaseURL" + "\uffff" + // string table entry #2
-      "whitelistHashcode" + "\uffff" + // string table entry #4
-      "3\uffff" + // module base URL
-      "4\uffff" + // whitelist hashcode
-      "1\uffff" + // interface name
-      "2\uffff" + // method name
-      "0\uffff"; // param count
+  private static final String VALID_ENCODED_REQUEST = "" +
+      AbstractSerializationStream.SERIALIZATION_STREAM_VERSION +
+      RPC_SEPARATOR_CHAR + // version
+      "0" + RPC_SEPARATOR_CHAR + // flags
+      "4" + RPC_SEPARATOR_CHAR + // string table entry count
+      A.class.getName() + RPC_SEPARATOR_CHAR + // string table entry #1
+      "method2" + RPC_SEPARATOR_CHAR + // string table entry #2
+      "moduleBaseURL" + RPC_SEPARATOR_CHAR + // string table entry #3
+      "whitelistHashcode" + RPC_SEPARATOR_CHAR + // string table entry #4
+      "3" + RPC_SEPARATOR_CHAR + // module base URL
+      "4" + RPC_SEPARATOR_CHAR + // whitelist hashcode
+      "1" + RPC_SEPARATOR_CHAR + // interface name
+      "2" + RPC_SEPARATOR_CHAR + // method name
+      "0" + RPC_SEPARATOR_CHAR; // param count
 
-  private static final String INVALID_METHOD_REQUEST = "4\uffff" + // version
-      "0\uffff" + // flags
-      "4\uffff" + // string table entry count
-      A.class.getName() + "\uffff" + // string table entry #0
-      "method3" + "\uffff" + // string table entry #1
-      "moduleBaseURL" + "\uffff" + // string table entry #2
-      "whitelistHashcode" + "\uffff" + // string table entry #4
-      "3\uffff" + // module base URL
-      "4\uffff" + // whitelist hashcode
-      "1\uffff" + // interface name
-      "2\uffff" + // method name
-      "0\uffff"; // param count
+  private static final String INVALID_METHOD_REQUEST = "" +
+      AbstractSerializationStream.SERIALIZATION_STREAM_VERSION +
+      RPC_SEPARATOR_CHAR + // version
+      "0" + RPC_SEPARATOR_CHAR + // flags
+      "4" + RPC_SEPARATOR_CHAR + // string table entry count
+      A.class.getName() + RPC_SEPARATOR_CHAR + // string table entry #1
+      "method3" + RPC_SEPARATOR_CHAR + // string table entry #2
+      "moduleBaseURL" + RPC_SEPARATOR_CHAR + // string table entry #3
+      "whitelistHashcode" + RPC_SEPARATOR_CHAR + // string table entry #4
+      "3" + RPC_SEPARATOR_CHAR + // module base URL
+      "4" + RPC_SEPARATOR_CHAR + // whitelist hashcode
+      "1" + RPC_SEPARATOR_CHAR + // interface name
+      "2" + RPC_SEPARATOR_CHAR + // method name
+      "0" + RPC_SEPARATOR_CHAR; // param count
 
-  private static final String INVALID_INTERFACE_REQUEST = "4\uffff" + // version
-      "0\uffff" + // flags
-      "4\uffff" + // string table entry count
-      B.class.getName() + "\uffff" + // string table entry #0
-      "method1" + "\uffff" + // string table entry #1
-      "moduleBaseURL" + "\uffff" + // string table entry #2
-      "whitelistHashcode" + "\uffff" + // string table entry #4
-      "3\uffff" + // module base URL
-      "4\uffff" + // whitelist hashcode
-      "1\uffff" + // interface name
-      "2\uffff" + // method name
-      "0\uffff"; // param count
+  private static final String INVALID_INTERFACE_REQUEST = "" +
+      AbstractSerializationStream.SERIALIZATION_STREAM_VERSION +
+      RPC_SEPARATOR_CHAR + // version
+      "0" + RPC_SEPARATOR_CHAR + // flags
+      "4" + RPC_SEPARATOR_CHAR + // string table entry count
+      B.class.getName() + RPC_SEPARATOR_CHAR + // string table entry #1
+      "method1" + RPC_SEPARATOR_CHAR + // string table entry #2
+      "moduleBaseURL" + RPC_SEPARATOR_CHAR + // string table entry #3
+      "whitelistHashcode" + RPC_SEPARATOR_CHAR + // string table entry #4
+      "3" + RPC_SEPARATOR_CHAR + // module base URL
+      "4" + RPC_SEPARATOR_CHAR + // whitelist hashcode
+      "1" + RPC_SEPARATOR_CHAR + // interface name
+      "2" + RPC_SEPARATOR_CHAR + // method name
+      "0" + RPC_SEPARATOR_CHAR; // param count
+
+  private static final String STRING_QUOTE_REQUEST = "" +
+      AbstractSerializationStream.SERIALIZATION_STREAM_VERSION +
+      RPC_SEPARATOR_CHAR + // version
+      "0" + RPC_SEPARATOR_CHAR + // flags
+      "7" + RPC_SEPARATOR_CHAR + // string table entry count
+      A.class.getName() + RPC_SEPARATOR_CHAR + // string table entry #1
+      "method2" + RPC_SEPARATOR_CHAR + // string table entry #2
+      "moduleBaseURL" + RPC_SEPARATOR_CHAR + // string table entry #3
+      "whitelistHashcode" + RPC_SEPARATOR_CHAR + // string table entry #4
+      "Raw backslash \\\\" + RPC_SEPARATOR_CHAR + // string table entry #5
+      "Quoted separator \\!" + RPC_SEPARATOR_CHAR + // string table entry #6
+      "\\uffff\\\\!\\\\0\\0" + RPC_SEPARATOR_CHAR + // string table entry #7
+      "3" + RPC_SEPARATOR_CHAR + // module base URL
+      "4" + RPC_SEPARATOR_CHAR + // whitelist hashcode
+      "5" + RPC_SEPARATOR_CHAR + // begin test data
+      "6" + RPC_SEPARATOR_CHAR +
+      "7" + RPC_SEPARATOR_CHAR;
 
   private static final String VALID_V2_ENCODED_REQUEST = "2\uffff" + // version
       "0\uffff" + // flags
       "2\uffff" + // string table entry count
-      A.class.getName() + "\uffff" + // string table entry #0
-      "method2" + "\uffff" + // string table entry #1
+      A.class.getName() + "\uffff" + // string table entry #1
+      "method2\uffff" + // string table entry #2
       "1\uffff" + // interface name
       "2\uffff" + // method name
       "0\uffff"; // param count
@@ -92,9 +121,22 @@
   private static final String VALID_V3_ENCODED_REQUEST = "3\uffff" + // version
       "0\uffff" + // flags
       "4\uffff" + // string table entry count
-      A.class.getName() + "\uffff" + // string table entry #0
-      "method2" + "\uffff" + // string table entry #1
-      "moduleBaseURL" + "\uffff" + // string table entry #2
+      A.class.getName() + "\uffff" + // string table entry #1
+      "method2\uffff" + // string table entry #2
+      "moduleBaseURL\uffff" + // string table entry #3
+      "whitelistHashcode\uffff" + // string table entry #4
+      "3\uffff" + // module base URL
+      "4\uffff" + // whitelist hashcode
+      "1\uffff" + // interface name
+      "2\uffff" + // method name
+      "0\uffff"; // param count
+
+  private static final String VALID_V4_ENCODED_REQUEST = "4\uffff" + // version
+      "0\uffff" + // flags
+      "4\uffff" + // string table entry count
+      A.class.getName() + "\uffff" + // string table entry #1
+      "method2" + "\uffff" + // string table entry #2
+      "moduleBaseURL" + "\uffff" + // string table entry #3
       "whitelistHashcode" + "\uffff" + // string table entry #4
       "3\uffff" + // module base URL
       "4\uffff" + // whitelist hashcode
@@ -120,6 +162,13 @@
     } catch (IncompatibleRemoteServiceException e) {
       // Expected
     }
+
+    try {
+      RPC.decodeRequest(VALID_V4_ENCODED_REQUEST, A.class, null);
+      fail("Should have thrown an IncompatibleRemoteServiceException");
+    } catch (IncompatibleRemoteServiceException e) {
+      // Expected
+    }
   }
 
   /**
@@ -402,4 +451,12 @@
       }
     }, A_method1, null);
   }
+  
+  public void testSerializationStreamDequote() throws SerializationException {
+    ServerSerializationStreamReader reader = new ServerSerializationStreamReader(null, null);
+    reader.prepareToRead(STRING_QUOTE_REQUEST);
+    assertEquals("Raw backslash \\", reader.readString());
+    assertEquals("Quoted separator " + RPC_SEPARATOR_CHAR, reader.readString());
+    assertEquals("\uffff\\!\\0\u0000", reader.readString());
+  }
 }
diff --git a/user/test/com/google/gwt/user/server/rpc/UnicodeEscapingServiceImpl.java b/user/test/com/google/gwt/user/server/rpc/UnicodeEscapingServiceImpl.java
index 76bd2e7..7d4e99f 100644
--- a/user/test/com/google/gwt/user/server/rpc/UnicodeEscapingServiceImpl.java
+++ b/user/test/com/google/gwt/user/server/rpc/UnicodeEscapingServiceImpl.java
@@ -16,6 +16,7 @@
 package com.google.gwt.user.server.rpc;
 
 import com.google.gwt.user.client.rpc.UnicodeEscapingService;
+import com.google.gwt.user.client.rpc.UnicodeEscapingTest;
 
 /**
  * Implementation of the {@link UnicodeEscapingService} interface. 
@@ -24,18 +25,18 @@
     UnicodeEscapingService {
 
   /**
-   * @see com.google.gwt.user.client.rpc.UnicodeEscapingService#getStringContainingCharacterRange(int,
-   *      int)
+   * @see UnicodeEscapingService#getStringContainingCharacterRange(int, int)
    */
   public String getStringContainingCharacterRange(int start, int end) {
-    int nChars = end - start;
-      
-    char[] chars = new char[nChars];
-    for (int i = 0; i < nChars; ++i) {
-      char ch = (char) (start + i);
-      chars[i] = ch;
-    }
+    return UnicodeEscapingTest.getStringContainingCharacterRange(start, end);
+  }
 
-    return new String(chars);
+  /**
+   * @see UnicodeEscapingService#verifyStringContainingCharacterRange(int, int, String)
+   */
+  public boolean verifyStringContainingCharacterRange(int start, int end,
+      String str) throws InvalidCharacterException {
+    UnicodeEscapingTest.verifyStringContainingCharacterRange(start, end, str);
+    return true;
   }
 }
diff --git a/user/test/com/google/gwt/xml/client/XMLTest.java b/user/test/com/google/gwt/xml/client/XMLTest.java
index a34f366..180bad7 100644
--- a/user/test/com/google/gwt/xml/client/XMLTest.java
+++ b/user/test/com/google/gwt/xml/client/XMLTest.java
@@ -312,7 +312,13 @@
     assertEquals(top.getChildNodes().getLength(), 1);
   }
 
-  public void testParse() {
+  /**
+   * This test is failing on one Safari configuration in web mode in the 1.5
+   * release branch, but it passes in all other configurations and in the trunk.
+   * The files in the xml package are identical between the trunk and the 1.5
+   * branch.
+   */
+  public void disabledTestParse() {
     Document docA = XMLParser.parse("<!--hello-->   <a spam=\"ham\">\n  <?pi hello ?>dfgdfg  <b/>\t</a>");
 
     Document docB = XMLParser.createDocument();