First round of changes to implement WAR design.
http://code.google.com/p/google-web-toolkit/wiki/WAR_Design_1_6

Review by: bobv


git-svn-id: https://google-web-toolkit.googlecode.com/svn/releases/1.6@4262 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/CompilePerms.java b/dev/core/src/com/google/gwt/dev/CompilePerms.java
index 94ef4c4..810a9b9 100644
--- a/dev/core/src/com/google/gwt/dev/CompilePerms.java
+++ b/dev/core/src/com/google/gwt/dev/CompilePerms.java
@@ -193,15 +193,14 @@
   /**
    * Compile multiple permutations.
    */
-  public static boolean compile(TreeLogger logger,
-      Precompilation precompilation, Permutation[] perms, int localWorkers,
-      File[] resultFiles) throws UnableToCompleteException {
+  public static void compile(TreeLogger logger, Precompilation precompilation,
+      Permutation[] perms, int localWorkers, File[] resultFiles)
+      throws UnableToCompleteException {
     final TreeLogger branch = logger.branch(TreeLogger.INFO, "Compiling "
         + perms.length + " permutations");
     PermutationWorkerFactory.compilePermutations(logger, precompilation, perms,
         localWorkers, resultFiles);
     branch.log(TreeLogger.INFO, "Permutation compile succeeded");
-    return true;
   }
 
   public static void main(String[] args) {
@@ -250,49 +249,53 @@
   }
 
   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();
-    Permutation[] subPerms;
-    int[] permsToRun = options.getPermsToCompile();
-    if (permsToRun.length == 0) {
-      subPerms = perms;
-    } else {
-      int i = 0;
-      subPerms = new Permutation[permsToRun.length];
-      // 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;
-        }
-        subPerms[i++] = perms[permToRun];
+    for (String moduleName : options.getModuleNames()) {
+      File compilerWorkDir = options.getCompilerWorkDir(moduleName);
+      File precompilationFile = new File(compilerWorkDir,
+          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;
+      }
 
-    File[] resultFiles = makeResultFiles(options.getCompilerWorkDir(), perms);
-    return compile(logger, precompilation, subPerms, options.getLocalWorkers(),
-        resultFiles);
+      Permutation[] perms = precompilation.getPermutations();
+      Permutation[] subPerms;
+      int[] permsToRun = options.getPermsToCompile();
+      if (permsToRun.length == 0) {
+        subPerms = perms;
+      } else {
+        int i = 0;
+        subPerms = new Permutation[permsToRun.length];
+        // 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;
+          }
+          subPerms[i++] = perms[permToRun];
+        }
+      }
+
+      File[] resultFiles = makeResultFiles(compilerWorkDir, perms);
+      compile(logger, precompilation, subPerms, options.getLocalWorkers(),
+          resultFiles);
+    }
+    return true;
   }
 }
diff --git a/dev/core/src/com/google/gwt/dev/CompileTaskOptionsImpl.java b/dev/core/src/com/google/gwt/dev/CompileTaskOptionsImpl.java
index e92d1c8..cabbc4f 100644
--- a/dev/core/src/com/google/gwt/dev/CompileTaskOptionsImpl.java
+++ b/dev/core/src/com/google/gwt/dev/CompileTaskOptionsImpl.java
@@ -18,6 +18,8 @@
 import com.google.gwt.core.ext.TreeLogger.Type;
 
 import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Concrete class to implement compiler task options.
@@ -25,7 +27,7 @@
 class CompileTaskOptionsImpl implements CompileTaskOptions {
 
   private Type logLevel;
-  private String moduleName;
+  private final List<String> moduleNames = new ArrayList<String>();
   private boolean useGuiLogger;
   private File workDir;
 
@@ -36,23 +38,27 @@
     copyFrom(other);
   }
 
+  public void addModuleName(String moduleName) {
+    moduleNames.add(moduleName);
+  }
+
   public void copyFrom(CompileTaskOptions other) {
     setLogLevel(other.getLogLevel());
-    setModuleName(other.getModuleName());
+    setModuleNames(other.getModuleNames());
     setUseGuiLogger(other.isUseGuiLogger());
     setWorkDir(other.getWorkDir());
   }
 
-  public File getCompilerWorkDir() {
-    return new File(new File(getWorkDir(), getModuleName()), "compiler");
+  public File getCompilerWorkDir(String moduleName) {
+    return new File(new File(getWorkDir(), moduleName), "compiler");
   }
 
   public Type getLogLevel() {
     return logLevel;
   }
 
-  public String getModuleName() {
-    return moduleName;
+  public List<String> getModuleNames() {
+    return new ArrayList<String>(moduleNames);
   }
 
   public File getWorkDir() {
@@ -67,8 +73,9 @@
     this.logLevel = logLevel;
   }
 
-  public void setModuleName(String moduleName) {
-    this.moduleName = moduleName;
+  public void setModuleNames(List<String> moduleNames) {
+    this.moduleNames.clear();
+    this.moduleNames.addAll(moduleNames);
   }
 
   public void setUseGuiLogger(boolean useGuiLogger) {
diff --git a/dev/core/src/com/google/gwt/dev/CompileTaskRunner.java b/dev/core/src/com/google/gwt/dev/CompileTaskRunner.java
index a5efcfb..ce1ef6a 100644
--- a/dev/core/src/com/google/gwt/dev/CompileTaskRunner.java
+++ b/dev/core/src/com/google/gwt/dev/CompileTaskRunner.java
@@ -46,7 +46,7 @@
     if (options.isUseGuiLogger()) {
       // Initialize a tree logger window.
       DetachedTreeLoggerWindow loggerWindow = DetachedTreeLoggerWindow.getInstance(
-          "Build Output for " + options.getModuleName(), 800, 600, true);
+          "Build Output for " + options.getModuleNames(), 800, 600, true);
 
       // Eager AWT initialization for OS X to ensure safe coexistence with SWT.
       BootStrapPlatform.maybeInitializeAWT();
diff --git a/dev/core/src/com/google/gwt/dev/Compiler.java b/dev/core/src/com/google/gwt/dev/Compiler.java
new file mode 100644
index 0000000..3ecb3d2
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/Compiler.java
@@ -0,0 +1,192 @@
+/*
+ * 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.Link.LinkOptionsImpl;
+import com.google.gwt.dev.Precompile.PrecompileOptionsImpl;
+import com.google.gwt.dev.cfg.ModuleDef;
+import com.google.gwt.dev.cfg.ModuleDefLoader;
+import com.google.gwt.dev.util.PerfLogger;
+import com.google.gwt.dev.util.Util;
+import com.google.gwt.dev.util.arg.ArgHandlerExtraDir;
+import com.google.gwt.dev.util.arg.ArgHandlerLocalWorkers;
+import com.google.gwt.dev.util.arg.ArgHandlerWarDir;
+import com.google.gwt.dev.util.arg.ArgHandlerWorkDirOptional;
+import com.google.gwt.util.tools.Utility;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * The main executable entry point for the GWT Java to JavaScript compiler.
+ */
+public class Compiler {
+
+  static class ArgProcessor extends Precompile.ArgProcessor {
+    public ArgProcessor(CompilerOptions options) {
+      super(options);
+
+      registerHandler(new ArgHandlerLocalWorkers(options));
+
+      // Override the ArgHandlerWorkDirRequired in the super class.
+      registerHandler(new ArgHandlerWorkDirOptional(options));
+
+      registerHandler(new ArgHandlerWarDir(options));
+      registerHandler(new ArgHandlerExtraDir(options));
+    }
+
+    @Override
+    protected String getName() {
+      return GWTCompiler.class.getName();
+    }
+  }
+
+  static class CompilerOptionsImpl extends PrecompileOptionsImpl implements
+      CompilerOptions {
+
+    private LinkOptionsImpl linkOptions = new LinkOptionsImpl();
+    private int localWorkers;
+
+    public CompilerOptionsImpl() {
+    }
+
+    public CompilerOptionsImpl(CompilerOptions other) {
+      copyFrom(other);
+    }
+
+    public void copyFrom(CompilerOptions other) {
+      super.copyFrom(other);
+      linkOptions.copyFrom(other);
+      localWorkers = other.getLocalWorkers();
+    }
+
+    public File getExtraDir() {
+      return linkOptions.getExtraDir();
+    }
+
+    public int getLocalWorkers() {
+      return localWorkers;
+    }
+
+    public File getWarDir() {
+      return linkOptions.getWarDir();
+    }
+
+    public void setExtraDir(File extraDir) {
+      linkOptions.setExtraDir(extraDir);
+    }
+
+    public void setLocalWorkers(int localWorkers) {
+      this.localWorkers = localWorkers;
+    }
+
+    public void setWarDir(File outDir) {
+      linkOptions.setWarDir(outDir);
+    }
+  }
+
+  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 Compiler(options).run(logger);
+        }
+      };
+      if (CompileTaskRunner.runWithAppropriateLogger(options, task)) {
+        // Exit w/ success code.
+        System.exit(0);
+      }
+    }
+    // Exit w/ non-success code.
+    System.exit(1);
+  }
+
+  private final CompilerOptionsImpl options;
+
+  public Compiler(CompilerOptions options) {
+    this.options = new CompilerOptionsImpl(options);
+  }
+
+  public boolean run(TreeLogger logger) throws UnableToCompleteException {
+    PerfLogger.start("compile");
+    boolean tempWorkDir = false;
+    try {
+      if (options.getWorkDir() == null) {
+        options.setWorkDir(Utility.makeTemporaryDirectory(null, "gwtc"));
+        tempWorkDir = true;
+      }
+
+      for (String moduleName : options.getModuleNames()) {
+        ModuleDef module = ModuleDefLoader.loadFromClassPath(logger, moduleName);
+        File compilerWorkDir = options.getCompilerWorkDir(moduleName);
+
+        if (options.isValidateOnly()) {
+          if (!Precompile.validate(logger, options, module,
+              options.getGenDir(), compilerWorkDir)) {
+            return false;
+          }
+        } else {
+          long compileStart = System.currentTimeMillis();
+          logger = logger.branch(TreeLogger.INFO, "Compiling module "
+              + moduleName);
+
+          Precompilation precompilation = Precompile.precompile(logger,
+              options, module, options.getGenDir(), compilerWorkDir);
+
+          if (precompilation == null) {
+            return false;
+          }
+
+          Permutation[] allPerms = precompilation.getPermutations();
+          File[] resultFiles = CompilePerms.makeResultFiles(compilerWorkDir,
+              allPerms);
+          CompilePerms.compile(logger, precompilation, allPerms,
+              options.getLocalWorkers(), resultFiles);
+
+          Link.link(logger.branch(TreeLogger.INFO, "Linking into "
+              + options.getWarDir().getPath()), module, precompilation,
+              resultFiles, options.getWarDir(), options.getExtraDir());
+
+          long compileDone = System.currentTimeMillis();
+          long delta = compileDone - compileStart;
+          logger.log(TreeLogger.INFO, "Compilation succeeded -- "
+              + String.format("%.3f", delta / 1000d) + "s");
+        }
+      }
+
+    } catch (IOException e) {
+      logger.log(TreeLogger.ERROR, "Unable to create compiler work directory",
+          e);
+      return false;
+    } finally {
+      PerfLogger.end();
+      if (tempWorkDir) {
+        Util.recursiveDelete(options.getWorkDir(), false);
+      }
+    }
+    return true;
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/GWTCompiler.java b/dev/core/src/com/google/gwt/dev/GWTCompiler.java
index 3e81a49..93d16ab 100644
--- a/dev/core/src/com/google/gwt/dev/GWTCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/GWTCompiler.java
@@ -18,13 +18,11 @@
 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.Link.LinkOptionsImpl;
 import com.google.gwt.dev.Precompile.PrecompileOptionsImpl;
 import com.google.gwt.dev.cfg.ModuleDef;
 import com.google.gwt.dev.cfg.ModuleDefLoader;
 import com.google.gwt.dev.util.PerfLogger;
 import com.google.gwt.dev.util.Util;
-import com.google.gwt.dev.util.arg.ArgHandlerExtraDir;
 import com.google.gwt.dev.util.arg.ArgHandlerLocalWorkers;
 import com.google.gwt.dev.util.arg.ArgHandlerOutDir;
 import com.google.gwt.dev.util.arg.ArgHandlerWorkDirOptional;
@@ -39,15 +37,15 @@
 public class GWTCompiler {
 
   static final class ArgProcessor extends Precompile.ArgProcessor {
-    public ArgProcessor(CompilerOptions options) {
+    public ArgProcessor(LegacyCompilerOptions options) {
       super(options);
 
+      registerHandler(new ArgHandlerOutDir(options));
+
       // Override the ArgHandlerWorkDirRequired in the super class.
       registerHandler(new ArgHandlerWorkDirOptional(options));
 
-      registerHandler(new ArgHandlerExtraDir(options));
       registerHandler(new ArgHandlerLocalWorkers(options));
-      registerHandler(new ArgHandlerOutDir(options));
     }
 
     @Override
@@ -57,26 +55,22 @@
   }
 
   static class GWTCompilerOptionsImpl extends PrecompileOptionsImpl implements
-      CompilerOptions {
+      LegacyCompilerOptions {
 
-    private LinkOptionsImpl linkOptions = new LinkOptionsImpl();
     private int localWorkers;
+    private File outDir;
 
     public GWTCompilerOptionsImpl() {
     }
 
-    public GWTCompilerOptionsImpl(CompilerOptions other) {
+    public GWTCompilerOptionsImpl(LegacyCompilerOptions other) {
       copyFrom(other);
     }
 
-    public void copyFrom(CompilerOptions other) {
+    public void copyFrom(LegacyCompilerOptions other) {
       super.copyFrom(other);
-      linkOptions.copyFrom(other);
-      localWorkers = other.getLocalWorkers();
-    }
-
-    public File getExtraDir() {
-      return linkOptions.getExtraDir();
+      setLocalWorkers(other.getLocalWorkers());
+      setOutDir(other.getOutDir());
     }
 
     public int getLocalWorkers() {
@@ -84,11 +78,7 @@
     }
 
     public File getOutDir() {
-      return linkOptions.getOutDir();
-    }
-
-    public void setExtraDir(File extraDir) {
-      linkOptions.setExtraDir(extraDir);
+      return outDir;
     }
 
     public void setLocalWorkers(int localWorkers) {
@@ -96,7 +86,7 @@
     }
 
     public void setOutDir(File outDir) {
-      linkOptions.setOutDir(outDir);
+      this.outDir = outDir;
     }
   }
 
@@ -107,7 +97,7 @@
      * shutdown AWT related threads, since the contract for their termination is
      * still implementation-dependent.
      */
-    final CompilerOptions options = new GWTCompilerOptionsImpl();
+    final LegacyCompilerOptions options = new GWTCompilerOptionsImpl();
     if (new ArgProcessor(options).processArgs(args)) {
       CompileTask task = new CompileTask() {
         public boolean run(TreeLogger logger) throws UnableToCompleteException {
@@ -125,62 +115,67 @@
 
   private final GWTCompilerOptionsImpl options;
 
-  public GWTCompiler(CompilerOptions options) {
+  public GWTCompiler(LegacyCompilerOptions options) {
     this.options = new GWTCompilerOptionsImpl(options);
   }
 
   public boolean run(TreeLogger logger) throws UnableToCompleteException {
-    ModuleDef module = ModuleDefLoader.loadFromClassPath(logger,
-        options.getModuleName());
+    PerfLogger.start("compile");
+    boolean tempWorkDir = false;
+    try {
+      if (options.getWorkDir() == null) {
+        options.setWorkDir(Utility.makeTemporaryDirectory(null, "gwtc"));
+        tempWorkDir = true;
+      }
 
-    if (options.isValidateOnly()) {
-      return Precompile.validate(logger, options, module, options.getGenDir(),
-          options.getCompilerWorkDir());
-    } else {
-      PerfLogger.start("compile");
-      long compileStart = System.currentTimeMillis();
-      logger = logger.branch(TreeLogger.INFO, "Compiling module "
-          + options.getModuleName());
+      for (String moduleName : options.getModuleNames()) {
+        ModuleDef module = ModuleDefLoader.loadFromClassPath(logger, moduleName);
+        File compilerWorkDir = options.getCompilerWorkDir(moduleName);
 
-      boolean tempWorkDir = false;
-      try {
-        if (options.getWorkDir() == null) {
-          options.setWorkDir(Utility.makeTemporaryDirectory(null, "gwtc"));
-          tempWorkDir = true;
-        }
+        if (options.isValidateOnly()) {
+          if (!Precompile.validate(logger, options, module,
+              options.getGenDir(), compilerWorkDir)) {
+            return false;
+          }
+        } else {
+          long compileStart = System.currentTimeMillis();
+          logger = logger.branch(TreeLogger.INFO, "Compiling module "
+              + moduleName);
 
-        Precompilation precompilation = Precompile.precompile(logger, options,
-            module, options.getGenDir(), options.getCompilerWorkDir());
+          Precompilation precompilation = Precompile.precompile(logger,
+              options, module, options.getGenDir(), compilerWorkDir);
 
-        if (precompilation == null) {
-          return false;
-        }
+          if (precompilation == null) {
+            return false;
+          }
 
-        Permutation[] allPerms = precompilation.getPermutations();
-        File[] resultFiles = CompilePerms.makeResultFiles(
-            options.getCompilerWorkDir(), allPerms);
-        CompilePerms.compile(logger, precompilation, allPerms,
-            options.getLocalWorkers(), resultFiles);
+          Permutation[] allPerms = precompilation.getPermutations();
+          File[] resultFiles = CompilePerms.makeResultFiles(compilerWorkDir,
+              allPerms);
+          CompilePerms.compile(logger, precompilation, allPerms,
+              options.getLocalWorkers(), resultFiles);
 
-        Link.link(logger.branch(TreeLogger.INFO, "Linking into "
-            + options.getOutDir().getPath()), module, precompilation,
-            resultFiles, options.getOutDir(), options.getExtraDir());
+          Link.legacyLink(logger.branch(TreeLogger.INFO, "Linking into "
+              + options.getOutDir().getPath()), module, precompilation,
+              resultFiles, options.getOutDir());
 
-        long compileDone = System.currentTimeMillis();
-        long delta = compileDone - compileStart;
-        logger.log(TreeLogger.INFO, "Compilation succeeded -- "
-            + String.format("%.3f", delta / 1000d) + "s");
-        return true;
-      } catch (IOException e) {
-        logger.log(TreeLogger.ERROR,
-            "Unable to create compiler work directory", e);
-      } finally {
-        PerfLogger.end();
-        if (tempWorkDir) {
-          Util.recursiveDelete(options.getWorkDir(), false);
+          long compileDone = System.currentTimeMillis();
+          long delta = compileDone - compileStart;
+          logger.log(TreeLogger.INFO, "Compilation succeeded -- "
+              + String.format("%.3f", delta / 1000d) + "s");
         }
       }
+
+    } catch (IOException e) {
+      logger.log(TreeLogger.ERROR, "Unable to create compiler work directory",
+          e);
       return false;
+    } finally {
+      PerfLogger.end();
+      if (tempWorkDir) {
+        Util.recursiveDelete(options.getWorkDir(), false);
+      }
     }
+    return true;
   }
 }
diff --git a/dev/core/src/com/google/gwt/dev/GWTHosted.java b/dev/core/src/com/google/gwt/dev/GWTHosted.java
deleted file mode 100644
index 87bd0d4..0000000
--- a/dev/core/src/com/google/gwt/dev/GWTHosted.java
+++ /dev/null
@@ -1,257 +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;
-
-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.cfg.ModuleDef;
-import com.google.gwt.dev.cfg.ModuleDefLoader;
-import com.google.gwt.dev.shell.ArtifactAcceptor;
-import com.google.gwt.dev.shell.GWTShellServletFilter;
-import com.google.gwt.dev.shell.ServletContainer;
-import com.google.gwt.dev.shell.ServletContainerLauncher;
-import com.google.gwt.dev.shell.jetty.JettyLauncher;
-import com.google.gwt.dev.util.PerfLogger;
-import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
-import com.google.gwt.util.tools.ArgHandlerExtra;
-import com.google.gwt.util.tools.ArgHandlerString;
-
-import java.io.PrintWriter;
-import java.net.BindException;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * The main executable class for the hosted mode shell.
- */
-public class GWTHosted extends GWTShell {
-
-  /**
-   * Handles the set of modules that can be passed at the end of the command
-   * line.
-   */
-  protected class ArgHandlerModulesExtra extends ArgHandlerExtra {
-
-    @Override
-    public boolean addExtraArg(String arg) {
-      return addModule(console, arg);
-    }
-
-    @Override
-    public String getPurpose() {
-      return "Specifies the set of modules to host";
-    }
-
-    @Override
-    public String[] getTagArgs() {
-      return new String[] {"module"};
-    }
-  }
-  /**
-   * Handles the -server command line flag.
-   */
-  protected class ArgHandlerServer extends ArgHandlerString {
-    @Override
-    public String getPurpose() {
-      return "Prevents the embedded Tomcat server from running, even if a port is specified";
-    }
-
-    @Override
-    public String getTag() {
-      return "-server";
-    }
-
-    @Override
-    public String[] getTagArgs() {
-      return new String[] {"serverLauncherClass"};
-    }
-
-    @Override
-    public boolean setString(String arg) {
-      // Supercedes -noserver.
-      setRunTomcat(true);
-      return setServer(console, arg);
-    }
-  }
-
-  /**
-   * Handles a startup url that can be passed on the command line.
-   */
-  protected class ArgHandlerStartupURLs extends ArgHandlerString {
-
-    @Override
-    public String getPurpose() {
-      return "Automatically launches the specified URL";
-    }
-
-    @Override
-    public String getTag() {
-      return "-startupUrl";
-    }
-
-    @Override
-    public String[] getTagArgs() {
-      return new String[] {"url"};
-    }
-
-    @Override
-    public boolean setString(String arg) {
-      addStartupURL(arg);
-      return true;
-    }
-  }
-
-  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.
-     */
-    GWTHosted shellMain = new GWTHosted();
-    if (shellMain.processArgs(args)) {
-      shellMain.run();
-    }
-    System.exit(0);
-  }
-
-  protected final PrintWriterTreeLogger console = new PrintWriterTreeLogger(
-      new PrintWriter(System.err, true));
-
-  /**
-   * The servlet launcher to use (defaults to embedded Jetty).
-   */
-  private ServletContainerLauncher launcher = new JettyLauncher();
-
-  /**
-   * The set of modules this hosted mode instance can run.
-   */
-  private Set<ModuleDef> modules = new HashSet<ModuleDef>();
-
-  /**
-   * The server that was started.
-   */
-  private ServletContainer server;
-
-  /**
-   * Our servlet filter, embedded into the server, which autogenerates GWT
-   * modules when the selection script is requested.
-   */
-  private GWTShellServletFilter servletFilter;
-
-  {
-    console.setMaxDetail(TreeLogger.WARN);
-  }
-
-  public GWTHosted() {
-    super(false, true);
-    registerHandler(new ArgHandlerServer());
-    registerHandler(new ArgHandlerStartupURLs());
-    registerHandler(new ArgHandlerModulesExtra());
-  }
-
-  public boolean addModule(TreeLogger logger, String moduleName) {
-    try {
-      ModuleDef moduleDef = ModuleDefLoader.loadFromClassPath(logger,
-          moduleName);
-      modules.add(moduleDef);
-      return true;
-    } catch (UnableToCompleteException e) {
-      logger.log(TreeLogger.ERROR, "Unable to load module '" + moduleName + "'");
-      return false;
-    }
-  }
-
-  public boolean setServer(TreeLogger logger, String serverClassName) {
-    Throwable t;
-    try {
-      Class<?> clazz = Class.forName(serverClassName, true,
-          Thread.currentThread().getContextClassLoader());
-      Class<? extends ServletContainerLauncher> sclClass = clazz.asSubclass(ServletContainerLauncher.class);
-      launcher = sclClass.newInstance();
-      return true;
-    } catch (ClassCastException e) {
-      t = e;
-    } catch (ClassNotFoundException e) {
-      t = e;
-    } catch (InstantiationException e) {
-      t = e;
-    } catch (IllegalAccessException e) {
-      t = e;
-    }
-    logger.log(TreeLogger.ERROR, "Unable to load server class '"
-        + serverClassName + "'", t);
-    return false;
-  }
-
-  @Override
-  protected ArtifactAcceptor doCreateArtifactAcceptor(final ModuleDef module) {
-    return new ArtifactAcceptor() {
-      public void accept(TreeLogger logger, ArtifactSet newlyGeneratedArtifacts)
-          throws UnableToCompleteException {
-        servletFilter.relink(logger, module, newlyGeneratedArtifacts);
-      }
-    };
-  }
-
-  @Override
-  protected void shutDown() {
-    if (server != null) {
-      try {
-        server.stop();
-      } catch (UnableToCompleteException e) {
-        // Already logged.
-      }
-      server = null;
-    }
-  }
-
-  @Override
-  protected int startUpServer() {
-    PerfLogger.start("GWTHosted.startUpServer");
-    try {
-      TreeLogger serverLogger = getTopLogger().branch(TreeLogger.INFO,
-          "Starting HTTP on port " + getPort(), null);
-      ModuleDef[] moduleArray = modules.toArray(new ModuleDef[modules.size()]);
-      for (ModuleDef moduleDef : moduleArray) {
-        String[] servletPaths = moduleDef.getServletPaths();
-        if (servletPaths.length > 0) {
-          serverLogger.log(TreeLogger.WARN,
-              "Ignoring legacy <servlet> tag(s) in module '"
-                  + moduleDef.getName()
-                  + "'; add servlet tags to your web.xml instead");
-        }
-      }
-      servletFilter = new GWTShellServletFilter(serverLogger, options,
-          moduleArray);
-      server = launcher.start(serverLogger, getPort(), options.getOutDir(),
-          servletFilter);
-      assert (server != null);
-      return server.getPort();
-    } catch (BindException e) {
-      System.err.println("Port "
-          + getPort()
-          + " is already is use; you probably still have another session active");
-    } catch (Exception e) {
-      System.err.println("Unable to start embedded HTTP server");
-      e.printStackTrace();
-    } finally {
-      PerfLogger.end();
-    }
-    return -1;
-  }
-}
diff --git a/dev/core/src/com/google/gwt/dev/GWTShell.java b/dev/core/src/com/google/gwt/dev/GWTShell.java
index 48a5d17..a83f85d 100644
--- a/dev/core/src/com/google/gwt/dev/GWTShell.java
+++ b/dev/core/src/com/google/gwt/dev/GWTShell.java
@@ -17,153 +17,23 @@
 
 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.EmittedArtifact;
-import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.dev.GWTCompiler.GWTCompilerOptionsImpl;
 import com.google.gwt.dev.cfg.ModuleDef;
-import com.google.gwt.dev.cfg.ModuleDefLoader;
 import com.google.gwt.dev.shell.ArtifactAcceptor;
-import com.google.gwt.dev.shell.BrowserWidget;
-import com.google.gwt.dev.shell.BrowserWidgetHost;
-import com.google.gwt.dev.shell.BrowserWidgetHostChecker;
-import com.google.gwt.dev.shell.LowLevel;
-import com.google.gwt.dev.shell.ModuleSpaceHost;
-import com.google.gwt.dev.shell.PlatformSpecific;
-import com.google.gwt.dev.shell.ShellMainWindow;
-import com.google.gwt.dev.shell.ShellModuleSpaceHost;
 import com.google.gwt.dev.shell.WorkDirs;
 import com.google.gwt.dev.shell.tomcat.EmbeddedTomcatServer;
 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.ArgHandlerExtraDir;
-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.arg.ArgHandlerWorkDirOptional;
-import com.google.gwt.dev.util.log.AbstractTreeLogger;
 import com.google.gwt.util.tools.ArgHandlerExtra;
-import com.google.gwt.util.tools.ArgHandlerFlag;
-import com.google.gwt.util.tools.ArgHandlerString;
-import com.google.gwt.util.tools.ToolBase;
-
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.DisposeEvent;
-import org.eclipse.swt.events.DisposeListener;
-import org.eclipse.swt.graphics.Cursor;
-import org.eclipse.swt.graphics.Image;
-import org.eclipse.swt.graphics.Rectangle;
-import org.eclipse.swt.internal.Library;
-import org.eclipse.swt.layout.FillLayout;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Shell;
 
 import java.io.File;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
 
 /**
  * The main executable class for the hosted mode shell.
  */
-public class GWTShell extends ToolBase {
-
-  /**
-   * Handles the -blacklist command line argument.
-   */
-  protected class ArgHandlerBlacklist extends ArgHandlerString {
-
-    @Override
-    public String[] getDefaultArgs() {
-      return new String[] {"-blacklist", ""};
-    }
-
-    @Override
-    public String getPurpose() {
-      return "Prevents the user browsing URLs that match the specified regexes (comma or space separated)";
-    }
-
-    @Override
-    public String getTag() {
-      return "-blacklist";
-    }
-
-    @Override
-    public String[] getTagArgs() {
-      return new String[] {"blacklist-string"};
-    }
-
-    @Override
-    public boolean setString(String blacklistStr) {
-      return BrowserWidgetHostChecker.blacklistRegexes(blacklistStr);
-    }
-  }
-
-  /**
-   * Handles the -noserver command line flag.
-   */
-  protected class ArgHandlerNoServerFlag extends ArgHandlerFlag {
-    @Override
-    public String getPurpose() {
-      return "Prevents the embedded Tomcat server from running, even if a port is specified";
-    }
-
-    @Override
-    public String getTag() {
-      return "-noserver";
-    }
-
-    @Override
-    public boolean setFlag() {
-      runTomcat = false;
-      return true;
-    }
-  }
-
-  /**
-   * Handles the -port command line flag.
-   */
-  protected class ArgHandlerPort extends ArgHandlerString {
-
-    @Override
-    public String[] getDefaultArgs() {
-      return new String[] {"-port", "8888"};
-    }
-
-    @Override
-    public String getPurpose() {
-      return "Runs an embedded Tomcat instance on the specified port (defaults to 8888)";
-    }
-
-    @Override
-    public String getTag() {
-      return "-port";
-    }
-
-    @Override
-    public String[] getTagArgs() {
-      return new String[] {"port-number | \"auto\""};
-    }
-
-    @Override
-    public boolean setString(String value) {
-      if (value.equals("auto")) {
-        port = 0;
-      } else {
-        try {
-          port = Integer.parseInt(value);
-        } catch (NumberFormatException e) {
-          System.err.println("A port must be an integer or \"auto\"");
-          return false;
-        }
-      }
-      return true;
-    }
-  }
+public class GWTShell extends HostedModeBase {
 
   /**
    * Handles the list of startup urls that can be passed at the end of the
@@ -189,33 +59,23 @@
   }
 
   /**
-   * Handles the -whitelist command line flag.
+   * The GWTShell argument processor.
    */
-  protected class ArgHandlerWhitelist extends ArgHandlerString {
+  protected class ArgProcessor extends HostedModeBase.ArgProcessor {
+    public ArgProcessor(boolean forceServer, boolean noURLs) {
+      if (!forceServer) {
+        registerHandler(new ArgHandlerNoServerFlag());
+      }
 
-    @Override
-    public String[] getDefaultArgs() {
-      return new String[] {"-whitelist", ""};
+      if (!noURLs) {
+        registerHandler(new ArgHandlerStartupURLsExtra());
+      }
+      registerHandler(new ArgHandlerOutDir(options));
     }
 
     @Override
-    public String getPurpose() {
-      return "Allows the user to browse URLs that match the specified regexes (comma or space separated)";
-    }
-
-    @Override
-    public String getTag() {
-      return "-whitelist";
-    }
-
-    @Override
-    public String[] getTagArgs() {
-      return new String[] {"whitelist-string"};
-    }
-
-    @Override
-    public boolean setString(String whitelistStr) {
-      return BrowserWidgetHostChecker.whitelistRegexes(whitelistStr);
+    protected String getName() {
+      return GWTShell.class.getName();
     }
   }
 
@@ -223,153 +83,23 @@
    * Concrete class to implement all compiler options.
    */
   static class ShellOptionsImpl extends GWTCompilerOptionsImpl implements
-      ShellOptions, WorkDirs {
+      HostedModeBaseOptions, WorkDirs {
     public File getCompilerOutputDir(ModuleDef moduleDef) {
-      return new File(getOutDir(), moduleDef.getDeployTo());
+      return new File(getOutDir(), moduleDef.getName());
+    }
+
+    public File getShellBaseWorkDir(ModuleDef moduleDef) {
+      return new File(new File(getWorkDir(), moduleDef.getName()), "shell");
     }
 
     public File getShellPublicGenDir(ModuleDef moduleDef) {
       return new File(getShellBaseWorkDir(moduleDef), "public");
     }
 
-    /**
-     * The base shell work directory.
-     */
-    protected File getShellBaseWorkDir(ModuleDef moduleDef) {
-      return new File(new File(getWorkDir(), moduleDef.getName()), "shell");
+    @Override
+    public File getWorkDir() {
+      return new File(getOutDir(), ".gwt-tmp");
     }
-
-    /**
-     * Where generated files go by default until we are sure they are public;
-     * then they are copied into {@link #getShellPublicGenDir(ModuleDef)}.
-     */
-    protected File getShellPrivateGenDir(ModuleDef moduleDef) {
-      return new File(getShellBaseWorkDir(moduleDef), "gen");
-    }
-  }
-
-  private class BrowserWidgetHostImpl implements BrowserWidgetHost {
-    public BrowserWidgetHostImpl() {
-    }
-
-    public void compile(ModuleDef moduleDef) throws UnableToCompleteException {
-      GWTShell.this.compile(getLogger(), moduleDef);
-    }
-
-    public void compile(String[] moduleNames) throws UnableToCompleteException {
-      for (int i = 0; i < moduleNames.length; i++) {
-        String moduleName = moduleNames[i];
-        ModuleDef moduleDef = loadModule(moduleName, getLogger());
-        compile(moduleDef);
-      }
-    }
-
-    public ModuleSpaceHost createModuleSpaceHost(BrowserWidget widget,
-        final String moduleName) throws UnableToCompleteException {
-      TreeLogger logger = getLogger();
-
-      // Switch to a wait cursor.
-      //
-      Shell widgetShell = widget.getShell();
-      try {
-        Cursor waitCursor = display.getSystemCursor(SWT.CURSOR_WAIT);
-        widgetShell.setCursor(waitCursor);
-
-        // Try to find an existing loaded version of the module def.
-        //
-        ModuleDef moduleDef = loadModule(moduleName, logger);
-        assert (moduleDef != null);
-
-        TypeOracle typeOracle = moduleDef.getTypeOracle(logger);
-        ShellModuleSpaceHost host = doCreateShellModuleSpaceHost(logger,
-            typeOracle, moduleDef);
-        return host;
-      } finally {
-        Cursor normalCursor = display.getSystemCursor(SWT.CURSOR_ARROW);
-        widgetShell.setCursor(normalCursor);
-      }
-    }
-
-    public TreeLogger getLogger() {
-      return getTopLogger();
-    }
-
-    public String normalizeURL(String whatTheUserTyped) {
-      return GWTShell.this.normalizeURL(whatTheUserTyped);
-    }
-
-    public BrowserWidget openNewBrowserWindow()
-        throws UnableToCompleteException {
-      return GWTShell.this.openNewBrowserWindow();
-    }
-
-    /**
-     * Load a module.
-     * 
-     * @param moduleName name of the module to load
-     * @param logger TreeLogger to use
-     * @return the loaded module
-     * @throws UnableToCompleteException
-     */
-    private ModuleDef loadModule(String moduleName, TreeLogger logger)
-        throws UnableToCompleteException {
-      boolean assumeFresh = !alreadySeenModules.contains(moduleName);
-      ModuleDef moduleDef = ModuleDefLoader.loadFromClassPath(logger,
-          moduleName, !assumeFresh);
-      alreadySeenModules.add(moduleName);
-      assert (moduleDef != null) : "Required module state is absent";
-      return moduleDef;
-    }
-  }
-
-  private static Image[] icons;
-
-  static {
-    // Correct menu on Mac OS X
-    Display.setAppName("GWT");
-  }
-
-  public static String checkHost(String hostUnderConsideration,
-      Set<String> hosts) {
-    hostUnderConsideration = hostUnderConsideration.toLowerCase();
-    for (String rule : hosts) {
-      // match on lowercased regex
-      if (hostUnderConsideration.matches(".*" + rule + ".*")) {
-        return rule;
-      }
-    }
-    return null;
-  }
-
-  public static String computeHostRegex(String url) {
-    // the entire URL up to the first slash not prefixed by a slash or colon.
-    String raw = url.split("(?<![:/])/")[0];
-    // escape the dots and put a begin line specifier on the result
-    return "^" + raw.replaceAll("[.]", "[.]");
-  }
-
-  public static String formatRules(Set<String> invalidHttpHosts) {
-    StringBuffer out = new StringBuffer();
-    for (String rule : invalidHttpHosts) {
-      out.append(rule);
-      out.append(" ");
-    }
-    return out.toString();
-  }
-
-  /**
-   * Well-known place to get the GWT icons.
-   */
-  public static Image[] getIcons() {
-    // Make sure icon images are loaded.
-    //
-    if (icons == null) {
-      icons = new Image[] {
-          LowLevel.loadImage("icon16.png"), LowLevel.loadImage("icon24.png"),
-          LowLevel.loadImage("icon32.png"), LowLevel.loadImage("icon48.png"),
-          LowLevel.loadImage("icon128.png")};
-    }
-    return icons;
   }
 
   public static void main(String[] args) {
@@ -380,225 +110,30 @@
      * still implementation-dependent.
      */
     GWTShell shellMain = new GWTShell();
-    if (shellMain.processArgs(args)) {
+    if (shellMain.new ArgProcessor(false, false).processArgs(args)) {
       shellMain.run();
+      // Exit w/ success code.
+      System.exit(0);
     }
-    System.exit(0);
+    // Exit w/ non-success code.
+    System.exit(-1);
   }
 
   /**
-   * Use the default display; constructing a new one would make instantiating
-   * multiple GWTShells fail with a mysterious exception.
+   * Hiding super field because it's actually the same object, just with a
+   * stronger type.
    */
-  protected final Display display = Display.getDefault();
+  @SuppressWarnings("hiding")
+  protected final ShellOptionsImpl options = (ShellOptionsImpl) super.options;
 
-  protected final ShellOptionsImpl options = new ShellOptionsImpl();
-
-  /**
-   * Cheat on the first load's refresh by assuming the module loaded by
-   * {@link com.google.gwt.dev.shell.GWTShellServlet} is still fresh. This
-   * prevents a double-refresh on startup. Subsequent refreshes will trigger a
-   * real refresh.
-   */
-  private Set<String> alreadySeenModules = new HashSet<String>();
-
-  private BrowserWidgetHostImpl browserHost = new BrowserWidgetHostImpl();
-
-  private final List<Shell> browserShells = new ArrayList<Shell>();
-
-  private boolean headlessMode = false;
-
-  private ShellMainWindow mainWnd;
-
-  private int port;
-
-  private boolean runTomcat = true;
-
-  private boolean started;
-
-  private final List<String> startupUrls = new ArrayList<String>();
-
-  public GWTShell() {
-    this(false, false);
-  }
-
-  protected GWTShell(boolean forceServer, boolean noURLs) {
-    // Set any platform specific system properties.
-    BootStrapPlatform.init();
-    BootStrapPlatform.applyPlatformHacks();
-
-    registerHandler(getArgHandlerPort());
-
-    if (!forceServer) {
-      registerHandler(new ArgHandlerNoServerFlag());
-    }
-
-    registerHandler(new ArgHandlerWhitelist());
-    registerHandler(new ArgHandlerBlacklist());
-
-    registerHandler(new ArgHandlerLogLevel(options));
-
-    registerHandler(new ArgHandlerGenDir(options));
-    registerHandler(new ArgHandlerWorkDirOptional(options));
-
-    if (!noURLs) {
-      registerHandler(new ArgHandlerStartupURLsExtra());
-    }
-
-    registerHandler(new ArgHandlerExtraDir(options));
-    registerHandler(new ArgHandlerOutDir(options));
-
-    registerHandler(new ArgHandlerScriptStyle(options));
-    registerHandler(new ArgHandlerEnableAssertions(options));
-    registerHandler(new ArgHandlerDisableAggressiveOptimization(options));
-  }
-
-  public void addStartupURL(String url) {
-    startupUrls.add(url);
-  }
-
-  public void closeAllBrowserWindows() {
-    while (!browserShells.isEmpty()) {
-      browserShells.get(0).dispose();
-    }
-  }
-
-  public CompilerOptions getCompilerOptions() {
+  public LegacyCompilerOptions getCompilerOptions() {
     return new GWTCompilerOptionsImpl(options);
   }
 
-  public int getPort() {
-    return port;
-  }
-
-  public TreeLogger getTopLogger() {
-    return mainWnd.getLogger();
-  }
-
-  public boolean hasBrowserWindowsOpen() {
-    if (browserShells.isEmpty()) {
-      return false;
-    } else {
-      return true;
-    }
-  }
-
-  /**
-   * Launch the arguments as Urls in separate windows.
-   */
-  public void launchStartupUrls(final TreeLogger logger) {
-    if (startupUrls != null) {
-      // Launch a browser window for each startup url.
-      //
-      String startupURL = "";
-      try {
-        for (String prenormalized : startupUrls) {
-          startupURL = normalizeURL(prenormalized);
-          logger.log(TreeLogger.TRACE, "Starting URL: " + startupURL, null);
-          BrowserWidget bw = openNewBrowserWindow();
-          bw.go(startupURL);
-        }
-      } catch (UnableToCompleteException e) {
-        logger.log(TreeLogger.ERROR,
-            "Unable to open new window for startup URL: " + startupURL, null);
-      }
-    }
-  }
-
-  public String normalizeURL(String unknownUrlText) {
-    if (unknownUrlText.indexOf(":") != -1) {
-      // Assume it's a full url.
-      return unknownUrlText;
-    }
-
-    // Assume it's a trailing url path.
-    //
-    if (unknownUrlText.length() > 0 && unknownUrlText.charAt(0) == '/') {
-      unknownUrlText = unknownUrlText.substring(1);
-    }
-
-    int prt = getPort();
-    if (prt != 80 && prt != 0) {
-      // CHECKSTYLE_OFF: Not really an assembled error message, so no space
-      // after ':'.
-      return "http://localhost:" + prt + "/" + unknownUrlText;
-      // CHECKSTYLE_ON
-    } else {
-      return "http://localhost/" + unknownUrlText;
-    }
-  }
-
-  /**
-   * Called directly by ShellMainWindow and indirectly via BrowserWidgetHost.
-   */
-  public BrowserWidget openNewBrowserWindow() throws UnableToCompleteException {
-    boolean succeeded = false;
-    Shell s = createTrackedBrowserShell();
-    try {
-      BrowserWidget bw = PlatformSpecific.createBrowserWidget(getTopLogger(),
-          s, browserHost);
-
-      if (mainWnd != null) {
-        Rectangle r = mainWnd.getShell().getBounds();
-        int n = browserShells.size() + 1;
-        s.setBounds(r.x + n * 50, r.y + n * 50, 800, 600);
-      } else {
-        s.setSize(800, 600);
-      }
-
-      if (!isHeadless()) {
-        s.open();
-      }
-
-      bw.onFirstShown();
-      succeeded = true;
-      return bw;
-    } finally {
-      if (!succeeded) {
-        s.dispose();
-      }
-    }
-  }
-
-  /**
-   * Sets up all the major aspects of running the shell graphically, including
-   * creating the main window and optionally starting the embedded Tomcat
-   * server.
-   */
-  public void run() {
-    try {
-      if (!startUp()) {
-        // Failed to initalize.
-        return;
-      }
-
-      // Eager AWT initialization for OS X to ensure safe coexistence with SWT.
-      BootStrapPlatform.maybeInitializeAWT();
-
-      // Tomcat's running now, so launch browsers for startup urls now.
-      launchStartupUrls(getTopLogger());
-
-      pumpEventLoop();
-
-      shutDown();
-
-    } catch (Exception e) {
-      e.printStackTrace();
-    }
-  }
-
   public void setCompilerOptions(CompilerOptions options) {
     this.options.copyFrom(options);
   }
 
-  public void setPort(int port) {
-    this.port = port;
-  }
-
-  public void setRunTomcat(boolean run) {
-    runTomcat = run;
-  }
-
   /**
    * Compiles a logical module def. The caller can modify the specified module
    * def programmatically in some cases (this is needed for JUnit support, for
@@ -606,11 +141,16 @@
    */
   protected void compile(TreeLogger logger, ModuleDef moduleDef)
       throws UnableToCompleteException {
-    CompilerOptions newOptions = new GWTCompilerOptionsImpl(options);
-    newOptions.setModuleName(moduleDef.getName());
+    LegacyCompilerOptions newOptions = new GWTCompilerOptionsImpl(options);
+    newOptions.addModuleName(moduleDef.getName());
     new GWTCompiler(newOptions).run(logger);
   }
 
+  @Override
+  protected HostedModeBaseOptions createOptions() {
+    return new ShellOptionsImpl();
+  }
+
   protected ArtifactAcceptor doCreateArtifactAcceptor(final ModuleDef module) {
     return new ArtifactAcceptor() {
       public void accept(TreeLogger logger, ArtifactSet artifacts)
@@ -631,147 +171,13 @@
     };
   }
 
-  /**
-   * Creates an instance of ShellModuleSpaceHost (or a derived class) using the
-   * specified constituent parts. This method is made to be overridden for
-   * subclasses that need to change the behavior of ShellModuleSpaceHost.
-   * 
-   * @param logger TreeLogger to use
-   * @param typeOracle
-   * @param moduleDef
-   * @param genDir
-   * @return ShellModuleSpaceHost instance
-   */
-  protected ShellModuleSpaceHost doCreateShellModuleSpaceHost(
-      TreeLogger logger, TypeOracle typeOracle, ModuleDef moduleDef) {
-    // Clear out the shell temp directory.
-    Util.recursiveDelete(options.getShellBaseWorkDir(moduleDef), true);
-    return new ShellModuleSpaceHost(logger, typeOracle, moduleDef,
-        options.getGenDir(), options.getShellPrivateGenDir(moduleDef),
-        doCreateArtifactAcceptor(moduleDef));
-  }
-
-  /**
-   * Can be override to change the default log level in subclasses. JUnit does
-   * this for example.
-   */
-  protected Type doGetDefaultLogLevel() {
-    return Type.INFO;
-  }
-
-  /**
-   * Derived classes can override to prevent automatic update checking.
-   */
-  protected boolean doShouldCheckForUpdates() {
-    return true;
-  }
-
-  /**
-   * Derived classes can override to set a default port.
-   */
-  protected ArgHandlerPort getArgHandlerPort() {
-    return new ArgHandlerPort();
-  }
-
-  protected BrowserWidgetHost getBrowserHost() {
-    return browserHost;
-  }
-
-  protected void initializeLogger() {
-    final AbstractTreeLogger logger = mainWnd.getLogger();
-    logger.setMaxDetail(options.getLogLevel());
-  }
-
-  /**
-   * By default we will open the application window.
-   * 
-   * @return true if we are running in headless mode
-   */
-  protected boolean isHeadless() {
-    return headlessMode;
-  }
-
-  protected boolean notDone() {
-    if (!mainWnd.isDisposed()) {
-      return true;
-    }
-    if (!browserShells.isEmpty()) {
-      return true;
-    }
-    return false;
-  }
-
-  /**
-   * 
-   */
-  protected void pumpEventLoop() {
-    TreeLogger logger = getTopLogger();
-
-    // Run the event loop. When there are no open shells, quit.
-    //
-    while (notDone()) {
-      try {
-        if (!display.readAndDispatch()) {
-          sleep();
-        }
-      } catch (Throwable e) {
-        String msg = e.getMessage();
-        msg = (msg != null ? msg : e.getClass().getName());
-        logger.log(TreeLogger.ERROR, msg, e);
-      }
-    }
-  }
-
-  protected void setHeadless(boolean headlessMode) {
-    this.headlessMode = headlessMode;
-  }
-
-  /**
-   * 
-   */
-  protected void shutDown() {
-    if (!runTomcat) {
-      return;
-    }
-
+  @Override
+  protected void shutDownServer() {
     // Stop the HTTP server.
     //
     EmbeddedTomcatServer.stop();
   }
 
-  protected void sleep() {
-    display.sleep();
-  }
-
-  protected boolean startUp() {
-    if (started) {
-      throw new IllegalStateException("Startup code has already been run");
-    }
-
-    started = true;
-
-    loadRequiredNativeLibs();
-
-    // Create the main app window.
-    // When it is up and running, it will start the Tomcat server if desired.
-    //
-    openAppWindow();
-
-    // Initialize the logger.
-    //
-    initializeLogger();
-
-    if (runTomcat) {
-      int resultPort = startUpServer();
-      if (resultPort < 0) {
-        return false;
-      }
-      port = resultPort;
-    }
-
-    return true;
-  }
-
   protected int startUpServer() {
     // TODO(bruce): make tomcat work in terms of the modular launcher
     String whyFailed = EmbeddedTomcatServer.start(getTopLogger(), getPort(),
@@ -785,59 +191,4 @@
     }
     return EmbeddedTomcatServer.getPort();
   }
-
-  private Shell createTrackedBrowserShell() {
-    final Shell shell = new Shell(display);
-    FillLayout fillLayout = new FillLayout();
-    fillLayout.marginWidth = 0;
-    fillLayout.marginHeight = 0;
-    shell.setLayout(fillLayout);
-    browserShells.add(shell);
-    shell.addDisposeListener(new DisposeListener() {
-      public void widgetDisposed(DisposeEvent e) {
-        if (e.widget == shell) {
-          browserShells.remove(shell);
-        }
-      }
-    });
-
-    shell.setImages(getIcons());
-
-    return shell;
-  }
-
-  private void loadRequiredNativeLibs() {
-    String libName = null;
-    try {
-      libName = "swt";
-      Library.loadLibrary(libName);
-    } catch (UnsatisfiedLinkError e) {
-      StringBuffer sb = new StringBuffer();
-      sb.append("Unable to load required native library '" + libName + "'");
-      sb.append("\n\tPlease specify the JVM startup argument ");
-      sb.append("\"-Djava.library.path\"");
-      throw new RuntimeException(sb.toString(), e);
-    }
-  }
-
-  private void openAppWindow() {
-    final Shell shell = new Shell(display);
-
-    FillLayout fillLayout = new FillLayout();
-    fillLayout.marginWidth = 0;
-    fillLayout.marginHeight = 0;
-    shell.setLayout(fillLayout);
-
-    shell.setImages(getIcons());
-
-    boolean checkForUpdates = doShouldCheckForUpdates();
-
-    mainWnd = new ShellMainWindow(this, shell, runTomcat ? getPort() : 0,
-        checkForUpdates);
-
-    shell.setSize(700, 600);
-    if (!isHeadless()) {
-      shell.open();
-    }
-  }
 }
diff --git a/dev/core/src/com/google/gwt/dev/HostedMode.java b/dev/core/src/com/google/gwt/dev/HostedMode.java
new file mode 100644
index 0000000..f19bf27
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/HostedMode.java
@@ -0,0 +1,320 @@
+/*
+ * 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.EmittedArtifact;
+import com.google.gwt.core.ext.linker.impl.StandardLinkerContext;
+import com.google.gwt.dev.Compiler.CompilerOptionsImpl;
+import com.google.gwt.dev.cfg.ModuleDef;
+import com.google.gwt.dev.cfg.ModuleDefLoader;
+import com.google.gwt.dev.shell.ArtifactAcceptor;
+import com.google.gwt.dev.shell.ServletContainer;
+import com.google.gwt.dev.shell.ServletContainerLauncher;
+import com.google.gwt.dev.shell.jetty.JettyLauncher;
+import com.google.gwt.dev.util.Util;
+import com.google.gwt.dev.util.arg.ArgHandlerExtraDir;
+import com.google.gwt.dev.util.arg.ArgHandlerModuleName;
+import com.google.gwt.dev.util.arg.ArgHandlerWarDir;
+import com.google.gwt.dev.util.arg.ArgHandlerWorkDirOptional;
+import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
+import com.google.gwt.util.tools.ArgHandlerString;
+import com.google.gwt.util.tools.Utility;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.BindException;
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+/**
+ * The main executable class for the hosted mode shell.
+ */
+public class HostedMode extends HostedModeBase {
+
+  /**
+   * Handles the -server command line flag.
+   */
+  protected class ArgHandlerServer extends ArgHandlerString {
+    @Override
+    public String getPurpose() {
+      return "Prevents the embedded Tomcat server from running, even if a port is specified";
+    }
+
+    @Override
+    public String getTag() {
+      return "-server";
+    }
+
+    @Override
+    public String[] getTagArgs() {
+      return new String[] {"serverLauncherClass"};
+    }
+
+    @Override
+    public boolean setString(String arg) {
+      // Supercedes -noserver.
+      setRunTomcat(true);
+      return setServer(console, arg);
+    }
+  }
+
+  /**
+   * Handles a startup url that can be passed on the command line.
+   */
+  protected class ArgHandlerStartupURLs extends ArgHandlerString {
+
+    @Override
+    public String getPurpose() {
+      return "Automatically launches the specified URL";
+    }
+
+    @Override
+    public String getTag() {
+      return "-startupUrl";
+    }
+
+    @Override
+    public String[] getTagArgs() {
+      return new String[] {"url"};
+    }
+
+    @Override
+    public boolean setString(String arg) {
+      addStartupURL(arg);
+      return true;
+    }
+  }
+
+  class ArgProcessor extends HostedModeBase.ArgProcessor {
+    public ArgProcessor() {
+      registerHandler(new ArgHandlerServer());
+      registerHandler(new ArgHandlerNoServerFlag());
+      registerHandler(new ArgHandlerStartupURLs());
+      registerHandler(new ArgHandlerWarDir(options));
+      registerHandler(new ArgHandlerExtraDir(options));
+      registerHandler(new ArgHandlerWorkDirOptional(options) {
+        @Override
+        public String[] getDefaultArgs() {
+          return new String[] {"-workDir", "work"};
+        }
+      });
+      registerHandler(new ArgHandlerModuleName(options));
+    }
+
+    @Override
+    protected String getName() {
+      return GWTShell.class.getName();
+    }
+  }
+
+  /**
+   * Concrete class to implement all compiler options.
+   */
+  static class HostedModeOptionsImpl extends CompilerOptionsImpl implements
+      HostedModeBaseOptions {
+    public File getShellBaseWorkDir(ModuleDef moduleDef) {
+      return new File(new File(getWorkDir(), moduleDef.getName()), "shell");
+    }
+  }
+
+  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.
+     */
+    HostedMode shellMain = new HostedMode();
+    if (shellMain.new ArgProcessor().processArgs(args)) {
+      // Exit w/ success code.
+      System.exit(0);
+    }
+    // Exit w/ non-success code.
+    System.exit(-1);
+  }
+
+  protected final PrintWriterTreeLogger console = new PrintWriterTreeLogger(
+      new PrintWriter(System.err, true));
+
+  /**
+   * Hiding super field because it's actually the same object, just with a
+   * stronger type.
+   */
+  @SuppressWarnings("hiding")
+  protected final HostedModeOptionsImpl options = (HostedModeOptionsImpl) super.options;
+
+  /**
+   * The servlet launcher to use (defaults to embedded Jetty).
+   */
+  private ServletContainerLauncher launcher = new JettyLauncher();
+
+  private final Map<ModuleDef, StandardLinkerContext> linkerMap = new IdentityHashMap<ModuleDef, StandardLinkerContext>();
+
+  /**
+   * The server that was started.
+   */
+  private ServletContainer server;
+
+  /**
+   * Tracks whether we created a temp workdir that we need to destroy.
+   */
+  private boolean tempWorkDir = false;
+
+  {
+    console.setMaxDetail(TreeLogger.WARN);
+  }
+
+  public boolean setServer(TreeLogger logger, String serverClassName) {
+    Throwable t;
+    try {
+      Class<?> clazz = Class.forName(serverClassName, true,
+          Thread.currentThread().getContextClassLoader());
+      Class<? extends ServletContainerLauncher> sclClass = clazz.asSubclass(ServletContainerLauncher.class);
+      launcher = sclClass.newInstance();
+      return true;
+    } catch (ClassCastException e) {
+      t = e;
+    } catch (ClassNotFoundException e) {
+      t = e;
+    } catch (InstantiationException e) {
+      t = e;
+    } catch (IllegalAccessException e) {
+      t = e;
+    }
+    logger.log(TreeLogger.ERROR, "Unable to load server class '"
+        + serverClassName + "'", t);
+    return false;
+  }
+
+  protected void compile(TreeLogger logger, ModuleDef moduleDef)
+      throws UnableToCompleteException {
+    CompilerOptions newOptions = new CompilerOptionsImpl(options);
+    newOptions.addModuleName(moduleDef.getName());
+    new Compiler(newOptions).run(logger);
+  }
+
+  @Override
+  protected HostedModeBaseOptions createOptions() {
+    return new HostedModeOptionsImpl();
+  }
+
+  @Override
+  protected ArtifactAcceptor doCreateArtifactAcceptor(final ModuleDef module) {
+    final File moduleOutDir = new File(options.getWarDir(), module.getName());
+    return new ArtifactAcceptor() {
+      public void accept(TreeLogger logger, ArtifactSet newlyGeneratedArtifacts)
+          throws UnableToCompleteException {
+        StandardLinkerContext linkerStack = linkerMap.get(module);
+        ArtifactSet artifacts = linkerStack.invokeRelink(logger,
+            newlyGeneratedArtifacts);
+        // TODO: extras
+        linkerStack.produceOutputDirectory(logger, artifacts, moduleOutDir,
+            null);
+      }
+    };
+  }
+
+  @Override
+  protected void shutDownServer() {
+    if (server != null) {
+      try {
+        server.stop();
+      } catch (UnableToCompleteException e) {
+        // Already logged.
+      }
+      server = null;
+    }
+
+    if (tempWorkDir) {
+      Util.recursiveDelete(options.getWorkDir(), false);
+    }
+  }
+
+  @Override
+  protected int startUpServer() {
+    tempWorkDir = options.getWorkDir() == null;
+    if (tempWorkDir) {
+      try {
+        options.setWorkDir(Utility.makeTemporaryDirectory(null, "gwtc"));
+      } catch (IOException e) {
+        System.err.println("Unable to create hosted mode work directory");
+        e.printStackTrace();
+        return -1;
+      }
+    }
+
+    for (String moduleName : options.getModuleNames()) {
+      TreeLogger linkLogger = getTopLogger().branch(TreeLogger.DEBUG,
+          "Prelinking module " + moduleName);
+      try {
+        ModuleDef module = ModuleDefLoader.loadFromClassPath(linkLogger,
+            moduleName);
+
+        // TODO: Validate servlet tags.
+        String[] servletPaths = module.getServletPaths();
+        if (servletPaths.length > 0) {
+          linkLogger.log(TreeLogger.WARN,
+              "Ignoring legacy <servlet> tag(s) in module '" + moduleName
+                  + "'; add servlet tags to your web.xml instead");
+        }
+
+        File moduleOutDir = new File(options.getWarDir(), moduleName);
+        StandardLinkerContext linkerStack = new StandardLinkerContext(
+            linkLogger, module, options);
+        linkerMap.put(module, linkerStack);
+
+        // TODO: remove all public files initially, only conditionally emit.
+        ArtifactSet artifacts = linkerStack.invokeLink(linkLogger);
+        for (EmittedArtifact artifact : artifacts.find(EmittedArtifact.class)) {
+          TreeLogger artifactLogger = linkLogger.branch(TreeLogger.DEBUG,
+              "Emitting resource " + artifact.getPartialPath(), null);
+
+          if (!artifact.isPrivate()) {
+            File outFile = new File(moduleOutDir, artifact.getPartialPath());
+            // if (!outFile.exists()) {
+            Util.copy(artifactLogger, artifact.getContents(artifactLogger),
+                outFile);
+            outFile.setLastModified(artifact.getLastModified());
+            // }
+          }
+        }
+      } catch (UnableToCompleteException e) {
+        // Already logged.
+        return -1;
+      }
+    }
+
+    try {
+      TreeLogger serverLogger = getTopLogger().branch(TreeLogger.INFO,
+          "Starting HTTP on port " + getPort(), null);
+      server = launcher.start(serverLogger, getPort(), options.getWarDir());
+      assert (server != null);
+      return server.getPort();
+    } catch (BindException e) {
+      System.err.println("Port "
+          + getPort()
+          + " is already is use; you probably still have another session active");
+    } catch (Exception e) {
+      System.err.println("Unable to start embedded HTTP server");
+      e.printStackTrace();
+    }
+    return -1;
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/HostedModeBase.java b/dev/core/src/com/google/gwt/dev/HostedModeBase.java
new file mode 100644
index 0000000..f351ff8
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/HostedModeBase.java
@@ -0,0 +1,685 @@
+/*
+ * 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.TreeLogger.Type;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+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.ArtifactAcceptor;
+import com.google.gwt.dev.shell.BrowserWidget;
+import com.google.gwt.dev.shell.BrowserWidgetHost;
+import com.google.gwt.dev.shell.BrowserWidgetHostChecker;
+import com.google.gwt.dev.shell.BrowserWindowController;
+import com.google.gwt.dev.shell.ModuleSpaceHost;
+import com.google.gwt.dev.shell.PlatformSpecific;
+import com.google.gwt.dev.shell.ShellMainWindow;
+import com.google.gwt.dev.shell.ShellModuleSpaceHost;
+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.ArgHandlerLogLevel;
+import com.google.gwt.dev.util.arg.ArgHandlerScriptStyle;
+import com.google.gwt.dev.util.arg.OptionGenDir;
+import com.google.gwt.dev.util.arg.OptionLogLevel;
+import com.google.gwt.dev.util.log.AbstractTreeLogger;
+import com.google.gwt.util.tools.ArgHandlerFlag;
+import com.google.gwt.util.tools.ArgHandlerString;
+import com.google.gwt.util.tools.ToolBase;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.graphics.Cursor;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.internal.Library;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * The main executable class for the hosted mode shell.
+ */
+abstract class HostedModeBase implements BrowserWindowController {
+
+  /**
+   * Handles the -blacklist command line argument.
+   */
+  protected class ArgHandlerBlacklist extends ArgHandlerString {
+
+    @Override
+    public String[] getDefaultArgs() {
+      return new String[] {"-blacklist", ""};
+    }
+
+    @Override
+    public String getPurpose() {
+      return "Prevents the user browsing URLs that match the specified regexes (comma or space separated)";
+    }
+
+    @Override
+    public String getTag() {
+      return "-blacklist";
+    }
+
+    @Override
+    public String[] getTagArgs() {
+      return new String[] {"blacklist-string"};
+    }
+
+    @Override
+    public boolean setString(String blacklistStr) {
+      return BrowserWidgetHostChecker.blacklistRegexes(blacklistStr);
+    }
+  }
+
+  /**
+   * Handles the -noserver command line flag.
+   */
+  protected class ArgHandlerNoServerFlag extends ArgHandlerFlag {
+    @Override
+    public String getPurpose() {
+      return "Prevents the embedded Tomcat server from running, even if a port is specified";
+    }
+
+    @Override
+    public String getTag() {
+      return "-noserver";
+    }
+
+    @Override
+    public boolean setFlag() {
+      runTomcat = false;
+      return true;
+    }
+  }
+
+  /**
+   * Handles the -port command line flag.
+   */
+  protected class ArgHandlerPort extends ArgHandlerString {
+
+    @Override
+    public String[] getDefaultArgs() {
+      return new String[] {"-port", "8888"};
+    }
+
+    @Override
+    public String getPurpose() {
+      return "Runs an embedded Tomcat instance on the specified port (defaults to 8888)";
+    }
+
+    @Override
+    public String getTag() {
+      return "-port";
+    }
+
+    @Override
+    public String[] getTagArgs() {
+      return new String[] {"port-number | \"auto\""};
+    }
+
+    @Override
+    public boolean setString(String value) {
+      if (value.equals("auto")) {
+        port = 0;
+      } else {
+        try {
+          port = Integer.parseInt(value);
+        } catch (NumberFormatException e) {
+          System.err.println("A port must be an integer or \"auto\"");
+          return false;
+        }
+      }
+      return true;
+    }
+  }
+
+  /**
+   * Handles the -whitelist command line flag.
+   */
+  protected class ArgHandlerWhitelist extends ArgHandlerString {
+
+    @Override
+    public String[] getDefaultArgs() {
+      return new String[] {"-whitelist", ""};
+    }
+
+    @Override
+    public String getPurpose() {
+      return "Allows the user to browse URLs that match the specified regexes (comma or space separated)";
+    }
+
+    @Override
+    public String getTag() {
+      return "-whitelist";
+    }
+
+    @Override
+    public String[] getTagArgs() {
+      return new String[] {"whitelist-string"};
+    }
+
+    @Override
+    public boolean setString(String whitelistStr) {
+      return BrowserWidgetHostChecker.whitelistRegexes(whitelistStr);
+    }
+  }
+
+  abstract class ArgProcessor extends ToolBase {
+    public ArgProcessor() {
+      registerHandler(getArgHandlerPort());
+      registerHandler(new ArgHandlerWhitelist());
+      registerHandler(new ArgHandlerBlacklist());
+      registerHandler(new ArgHandlerLogLevel(options));
+      registerHandler(new ArgHandlerGenDir(options));
+      registerHandler(new ArgHandlerScriptStyle(options));
+      registerHandler(new ArgHandlerEnableAssertions(options));
+      registerHandler(new ArgHandlerDisableAggressiveOptimization(options));
+    }
+
+    /*
+     * Overridden to make public.
+     */
+    @Override
+    public final boolean processArgs(String[] args) {
+      return super.processArgs(args);
+    }
+
+    /*
+     * Overridden to make abstract.
+     */
+    @Override
+    protected abstract String getName();
+  }
+
+  interface HostedModeBaseOptions extends JJSOptions, OptionLogLevel,
+      OptionGenDir {
+
+    /**
+     * The base shell work directory.
+     */
+    File getShellBaseWorkDir(ModuleDef moduleDef);
+  }
+
+  private class BrowserWidgetHostImpl implements BrowserWidgetHost {
+    public BrowserWidgetHostImpl() {
+    }
+
+    public void compile(ModuleDef moduleDef) throws UnableToCompleteException {
+      HostedModeBase.this.compile(getLogger(), moduleDef);
+    }
+
+    public void compile(String[] moduleNames) throws UnableToCompleteException {
+      for (int i = 0; i < moduleNames.length; i++) {
+        String moduleName = moduleNames[i];
+        ModuleDef moduleDef = loadModule(moduleName, getLogger());
+        compile(moduleDef);
+      }
+    }
+
+    public ModuleSpaceHost createModuleSpaceHost(BrowserWidget widget,
+        final String moduleName) throws UnableToCompleteException {
+      TreeLogger logger = getLogger();
+
+      // Switch to a wait cursor.
+      //
+      Shell widgetShell = widget.getShell();
+      try {
+        Cursor waitCursor = display.getSystemCursor(SWT.CURSOR_WAIT);
+        widgetShell.setCursor(waitCursor);
+
+        // Try to find an existing loaded version of the module def.
+        //
+        ModuleDef moduleDef = loadModule(moduleName, logger);
+        assert (moduleDef != null);
+
+        TypeOracle typeOracle = moduleDef.getTypeOracle(logger);
+        ShellModuleSpaceHost host = doCreateShellModuleSpaceHost(logger,
+            typeOracle, moduleDef);
+        return host;
+      } finally {
+        Cursor normalCursor = display.getSystemCursor(SWT.CURSOR_ARROW);
+        widgetShell.setCursor(normalCursor);
+      }
+    }
+
+    public TreeLogger getLogger() {
+      return getTopLogger();
+    }
+
+    public String normalizeURL(String whatTheUserTyped) {
+      return HostedModeBase.this.normalizeURL(whatTheUserTyped);
+    }
+
+    public BrowserWidget openNewBrowserWindow()
+        throws UnableToCompleteException {
+      return HostedModeBase.this.openNewBrowserWindow();
+    }
+
+    /**
+     * Load a module.
+     * 
+     * @param moduleName name of the module to load
+     * @param logger TreeLogger to use
+     * @return the loaded module
+     * @throws UnableToCompleteException
+     */
+    private ModuleDef loadModule(String moduleName, TreeLogger logger)
+        throws UnableToCompleteException {
+      boolean assumeFresh = !alreadySeenModules.contains(moduleName);
+      ModuleDef moduleDef = ModuleDefLoader.loadFromClassPath(logger,
+          moduleName, !assumeFresh);
+      alreadySeenModules.add(moduleName);
+      assert (moduleDef != null) : "Required module state is absent";
+      return moduleDef;
+    }
+  }
+
+  static {
+    // Force ToolBase to clinit, which causes SWT stuff to happen.
+    new ToolBase() {
+    };
+    // Correct menu on Mac OS X
+    Display.setAppName("GWT");
+  }
+
+  protected final HostedModeBaseOptions options;
+
+  /**
+   * Cheat on the first load's refresh by assuming the module loaded by
+   * {@link com.google.gwt.dev.shell.GWTShellServlet} is still fresh. This
+   * prevents a double-refresh on startup. Subsequent refreshes will trigger a
+   * real refresh.
+   */
+  private Set<String> alreadySeenModules = new HashSet<String>();
+
+  private BrowserWidgetHostImpl browserHost = new BrowserWidgetHostImpl();
+
+  private final List<Shell> browserShells = new ArrayList<Shell>();
+
+  /**
+   * Use the default display; constructing a new one would make instantiating
+   * multiple GWTShells fail with a mysterious exception.
+   */
+  private final Display display = Display.getDefault();
+
+  private boolean headlessMode = false;
+
+  private ShellMainWindow mainWnd;
+
+  private int port;
+
+  private boolean runTomcat = true;
+
+  private boolean started;
+
+  private final List<String> startupUrls = new ArrayList<String>();
+
+  public HostedModeBase() {
+    // Set any platform specific system properties.
+    BootStrapPlatform.init();
+    BootStrapPlatform.applyPlatformHacks();
+    options = createOptions();
+  }
+
+  public final void addStartupURL(String url) {
+    startupUrls.add(url);
+  }
+
+  public final void closeAllBrowserWindows() {
+    while (!browserShells.isEmpty()) {
+      browserShells.get(0).dispose();
+    }
+  }
+
+  public final int getPort() {
+    return port;
+  }
+
+  public TreeLogger getTopLogger() {
+    return mainWnd.getLogger();
+  }
+
+  public final boolean hasBrowserWindowsOpen() {
+    if (browserShells.isEmpty()) {
+      return false;
+    } else {
+      return true;
+    }
+  }
+
+  /**
+   * Launch the arguments as Urls in separate windows.
+   */
+  public final void launchStartupUrls(final TreeLogger logger) {
+    if (startupUrls != null) {
+      // Launch a browser window for each startup url.
+      //
+      String startupURL = "";
+      try {
+        for (String prenormalized : startupUrls) {
+          startupURL = normalizeURL(prenormalized);
+          logger.log(TreeLogger.TRACE, "Starting URL: " + startupURL, null);
+          BrowserWidget bw = openNewBrowserWindow();
+          bw.go(startupURL);
+        }
+      } catch (UnableToCompleteException e) {
+        logger.log(TreeLogger.ERROR,
+            "Unable to open new window for startup URL: " + startupURL, null);
+      }
+    }
+  }
+
+  public final String normalizeURL(String unknownUrlText) {
+    if (unknownUrlText.indexOf(":") != -1) {
+      // Assume it's a full url.
+      return unknownUrlText;
+    }
+
+    // Assume it's a trailing url path.
+    //
+    if (unknownUrlText.length() > 0 && unknownUrlText.charAt(0) == '/') {
+      unknownUrlText = unknownUrlText.substring(1);
+    }
+
+    int prt = getPort();
+    if (prt != 80 && prt != 0) {
+      // CHECKSTYLE_OFF: Not really an assembled error message, so no space
+      // after ':'.
+      return "http://localhost:" + prt + "/" + unknownUrlText;
+      // CHECKSTYLE_ON
+    } else {
+      return "http://localhost/" + unknownUrlText;
+    }
+  }
+
+  /**
+   * Called directly by ShellMainWindow and indirectly via BrowserWidgetHost.
+   */
+  public final BrowserWidget openNewBrowserWindow()
+      throws UnableToCompleteException {
+    boolean succeeded = false;
+    Shell s = createTrackedBrowserShell();
+    try {
+      BrowserWidget bw = PlatformSpecific.createBrowserWidget(getTopLogger(),
+          s, browserHost);
+
+      if (mainWnd != null) {
+        Rectangle r = mainWnd.getShell().getBounds();
+        int n = browserShells.size() + 1;
+        s.setBounds(r.x + n * 50, r.y + n * 50, 800, 600);
+      } else {
+        s.setSize(800, 600);
+      }
+
+      if (!isHeadless()) {
+        s.open();
+      }
+
+      bw.onFirstShown();
+      succeeded = true;
+      return bw;
+    } finally {
+      if (!succeeded) {
+        s.dispose();
+      }
+    }
+  }
+
+  /**
+   * Sets up all the major aspects of running the shell graphically, including
+   * creating the main window and optionally starting the embedded Tomcat
+   * server.
+   */
+  public final void run() {
+    try {
+      if (!startUp()) {
+        // Failed to initalize.
+        return;
+      }
+
+      // Eager AWT initialization for OS X to ensure safe coexistence with SWT.
+      BootStrapPlatform.maybeInitializeAWT();
+
+      // Tomcat's running now, so launch browsers for startup urls now.
+      launchStartupUrls(getTopLogger());
+
+      pumpEventLoop();
+    } catch (Exception e) {
+      e.printStackTrace();
+    } finally {
+      shutDown();
+    }
+  }
+
+  public final void setPort(int port) {
+    this.port = port;
+  }
+
+  public final void setRunTomcat(boolean run) {
+    runTomcat = run;
+  }
+
+  /**
+   * Compiles a module.
+   */
+  protected abstract void compile(TreeLogger logger, ModuleDef moduleDef)
+      throws UnableToCompleteException;
+
+  protected abstract HostedModeBaseOptions createOptions();
+
+  protected abstract ArtifactAcceptor doCreateArtifactAcceptor(ModuleDef module);
+
+  /**
+   * Creates an instance of ShellModuleSpaceHost (or a derived class) using the
+   * specified constituent parts. This method is made to be overridden for
+   * subclasses that need to change the behavior of ShellModuleSpaceHost.
+   * 
+   * @param logger TreeLogger to use
+   * @param typeOracle
+   * @param moduleDef
+   * @param genDir
+   * @return ShellModuleSpaceHost instance
+   */
+  protected final ShellModuleSpaceHost doCreateShellModuleSpaceHost(
+      TreeLogger logger, TypeOracle typeOracle, ModuleDef moduleDef) {
+    // Clear out the shell temp directory.
+    Util.recursiveDelete(options.getShellBaseWorkDir(moduleDef), true);
+    return new ShellModuleSpaceHost(logger, typeOracle, moduleDef,
+        options.getGenDir(), new File(options.getShellBaseWorkDir(moduleDef),
+            "gen"), doCreateArtifactAcceptor(moduleDef));
+  }
+
+  /**
+   * Can be override to change the default log level in subclasses. JUnit does
+   * this for example.
+   */
+  protected Type doGetDefaultLogLevel() {
+    return Type.INFO;
+  }
+
+  /**
+   * Derived classes can override to prevent automatic update checking.
+   */
+  protected boolean doShouldCheckForUpdates() {
+    return true;
+  }
+
+  /**
+   * Derived classes can override to set a default port.
+   */
+  protected ArgHandlerPort getArgHandlerPort() {
+    return new ArgHandlerPort();
+  }
+
+  protected final BrowserWidgetHost getBrowserHost() {
+    return browserHost;
+  }
+
+  protected void initializeLogger() {
+    final AbstractTreeLogger logger = mainWnd.getLogger();
+    logger.setMaxDetail(options.getLogLevel());
+  }
+
+  /**
+   * By default we will open the application window.
+   * 
+   * @return true if we are running in headless mode
+   */
+  protected final boolean isHeadless() {
+    return headlessMode;
+  }
+
+  protected boolean notDone() {
+    if (!mainWnd.isDisposed()) {
+      return true;
+    }
+    if (!browserShells.isEmpty()) {
+      return true;
+    }
+    return false;
+  }
+
+  protected final void pumpEventLoop() {
+    TreeLogger logger = getTopLogger();
+
+    // Run the event loop. When there are no open shells, quit.
+    //
+    while (notDone()) {
+      try {
+        if (!display.readAndDispatch()) {
+          sleep();
+        }
+      } catch (Throwable e) {
+        String msg = e.getMessage();
+        msg = (msg != null ? msg : e.getClass().getName());
+        logger.log(TreeLogger.ERROR, msg, e);
+      }
+    }
+  }
+
+  protected final void setHeadless(boolean headlessMode) {
+    this.headlessMode = headlessMode;
+  }
+
+  protected final void shutDown() {
+    if (!runTomcat) {
+      return;
+    }
+    shutDownServer();
+  }
+
+  protected abstract void shutDownServer();
+
+  protected void sleep() {
+    display.sleep();
+  }
+
+  protected final boolean startUp() {
+    if (started) {
+      throw new IllegalStateException("Startup code has already been run");
+    }
+
+    started = true;
+
+    loadRequiredNativeLibs();
+
+    // Create the main app window.
+    openAppWindow();
+
+    // Initialize the logger.
+    //
+    initializeLogger();
+
+    if (runTomcat) {
+      int resultPort = startUpServer();
+      if (resultPort < 0) {
+        return false;
+      }
+      port = resultPort;
+    }
+
+    return true;
+  }
+
+  protected abstract int startUpServer();
+
+  private Shell createTrackedBrowserShell() {
+    final Shell shell = new Shell(display);
+    FillLayout fillLayout = new FillLayout();
+    fillLayout.marginWidth = 0;
+    fillLayout.marginHeight = 0;
+    shell.setLayout(fillLayout);
+    browserShells.add(shell);
+    shell.addDisposeListener(new DisposeListener() {
+      public void widgetDisposed(DisposeEvent e) {
+        if (e.widget == shell) {
+          browserShells.remove(shell);
+        }
+      }
+    });
+
+    shell.setImages(ShellMainWindow.getIcons());
+
+    return shell;
+  }
+
+  private void loadRequiredNativeLibs() {
+    String libName = null;
+    try {
+      libName = "swt";
+      Library.loadLibrary(libName);
+    } catch (UnsatisfiedLinkError e) {
+      StringBuffer sb = new StringBuffer();
+      sb.append("Unable to load required native library '" + libName + "'");
+      sb.append("\n\tPlease specify the JVM startup argument ");
+      sb.append("\"-Djava.library.path\"");
+      throw new RuntimeException(sb.toString(), e);
+    }
+  }
+
+  private void openAppWindow() {
+    final Shell shell = new Shell(display);
+
+    FillLayout fillLayout = new FillLayout();
+    fillLayout.marginWidth = 0;
+    fillLayout.marginHeight = 0;
+    shell.setLayout(fillLayout);
+
+    shell.setImages(ShellMainWindow.getIcons());
+
+    boolean checkForUpdates = doShouldCheckForUpdates();
+
+    mainWnd = new ShellMainWindow(this, shell, runTomcat ? getPort() : 0,
+        checkForUpdates);
+
+    shell.setSize(700, 600);
+    if (!isHeadless()) {
+      shell.open();
+    }
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/ShellOptions.java b/dev/core/src/com/google/gwt/dev/HostedModeOptions.java
similarity index 83%
rename from dev/core/src/com/google/gwt/dev/ShellOptions.java
rename to dev/core/src/com/google/gwt/dev/HostedModeOptions.java
index 71ea4e8..53ed608 100644
--- a/dev/core/src/com/google/gwt/dev/ShellOptions.java
+++ b/dev/core/src/com/google/gwt/dev/HostedModeOptions.java
@@ -19,11 +19,11 @@
 import com.google.gwt.dev.util.arg.OptionExtraDir;
 import com.google.gwt.dev.util.arg.OptionGenDir;
 import com.google.gwt.dev.util.arg.OptionLogLevel;
-import com.google.gwt.dev.util.arg.OptionOutDir;
+import com.google.gwt.dev.util.arg.OptionWarDir;
 
 /**
  * The complete set of options for the GWT compiler.
  */
-public interface ShellOptions extends JJSOptions, OptionLogLevel, OptionOutDir,
-    OptionGenDir, OptionExtraDir {
+public interface HostedModeOptions extends JJSOptions, OptionLogLevel,
+    OptionExtraDir, OptionWarDir, OptionGenDir {
 }
diff --git a/dev/core/src/com/google/gwt/dev/ShellOptions.java b/dev/core/src/com/google/gwt/dev/LegacyCompilerOptions.java
similarity index 65%
copy from dev/core/src/com/google/gwt/dev/ShellOptions.java
copy to dev/core/src/com/google/gwt/dev/LegacyCompilerOptions.java
index 71ea4e8..c72bcc1 100644
--- a/dev/core/src/com/google/gwt/dev/ShellOptions.java
+++ b/dev/core/src/com/google/gwt/dev/LegacyCompilerOptions.java
@@ -15,15 +15,13 @@
  */
 package com.google.gwt.dev;
 
-import com.google.gwt.dev.jjs.JJSOptions;
-import com.google.gwt.dev.util.arg.OptionExtraDir;
-import com.google.gwt.dev.util.arg.OptionGenDir;
-import com.google.gwt.dev.util.arg.OptionLogLevel;
-import com.google.gwt.dev.util.arg.OptionOutDir;
+import com.google.gwt.dev.Link.LegacyLinkOptions;
+import com.google.gwt.dev.Precompile.PrecompileOptions;
 
 /**
  * The complete set of options for the GWT compiler.
  */
-public interface ShellOptions extends JJSOptions, OptionLogLevel, OptionOutDir,
-    OptionGenDir, OptionExtraDir {
+public interface LegacyCompilerOptions extends PrecompileOptions, LegacyLinkOptions,
+    OptionLocalWorkers {
 }
+ 
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/dev/Link.java b/dev/core/src/com/google/gwt/dev/Link.java
index f10d910..b16262f 100644
--- a/dev/core/src/com/google/gwt/dev/Link.java
+++ b/dev/core/src/com/google/gwt/dev/Link.java
@@ -28,9 +28,10 @@
 import com.google.gwt.dev.cfg.StaticPropertyOracle;
 import com.google.gwt.dev.util.Util;
 import com.google.gwt.dev.util.arg.ArgHandlerExtraDir;
-import com.google.gwt.dev.util.arg.ArgHandlerOutDir;
+import com.google.gwt.dev.util.arg.ArgHandlerWarDir;
 import com.google.gwt.dev.util.arg.OptionExtraDir;
 import com.google.gwt.dev.util.arg.OptionOutDir;
+import com.google.gwt.dev.util.arg.OptionWarDir;
 
 import java.io.File;
 import java.util.HashMap;
@@ -43,15 +44,21 @@
   /**
    * Options for Link.
    */
+  public interface LegacyLinkOptions extends CompileTaskOptions, OptionOutDir {
+  }
+
+  /**
+   * Options for Link.
+   */
   public interface LinkOptions extends CompileTaskOptions, OptionExtraDir,
-      OptionOutDir {
+      OptionWarDir {
   }
 
   static class ArgProcessor extends CompileArgProcessor {
     public ArgProcessor(LinkOptions options) {
       super(options);
       registerHandler(new ArgHandlerExtraDir(options));
-      registerHandler(new ArgHandlerOutDir(options));
+      registerHandler(new ArgHandlerWarDir(options));
     }
 
     @Override
@@ -67,7 +74,7 @@
       LinkOptions {
 
     private File extraDir;
-    private File outDir;
+    private File warDir;
 
     public LinkOptionsImpl() {
     }
@@ -79,26 +86,36 @@
     public void copyFrom(LinkOptions other) {
       super.copyFrom(other);
       setExtraDir(other.getExtraDir());
-      setOutDir(other.getOutDir());
+      setWarDir(other.getWarDir());
     }
 
     public File getExtraDir() {
       return extraDir;
     }
 
-    public File getOutDir() {
-      return outDir;
+    public File getWarDir() {
+      return warDir;
     }
 
     public void setExtraDir(File extraDir) {
       this.extraDir = extraDir;
     }
 
-    public void setOutDir(File outDir) {
-      this.outDir = outDir;
+    public void setWarDir(File warDir) {
+      this.warDir = warDir;
     }
   }
 
+  public static void legacyLink(TreeLogger logger, ModuleDef module,
+      Precompilation precompilation, File[] resultFiles, File outDir)
+      throws UnableToCompleteException {
+    StandardLinkerContext linkerContext = new StandardLinkerContext(logger,
+        module, precompilation.getUnifiedAst().getOptions());
+    ArtifactSet artifacts = doLink(logger, linkerContext, precompilation,
+        resultFiles);
+    doProduceLegacyOutput(logger, artifacts, linkerContext, module, outDir);
+  }
+
   public static ArtifactSet link(TreeLogger logger, ModuleDef module,
       Precompilation precompilation, File[] resultFiles)
       throws UnableToCompleteException {
@@ -157,45 +174,30 @@
     return linkerContext.invokeLink(logger);
   }
 
-  private static void doProduceOutput(TreeLogger logger, ArtifactSet artifacts,
-      StandardLinkerContext linkerContext, ModuleDef module, File outDir,
-      File extraDir) throws UnableToCompleteException {
-    boolean warnOnExtra = false;
-    File moduleExtraDir;
-    if (extraDir == null) {
-      /*
-       * Legacy behavior for backwards compatibility; if the extra directory is
-       * not specified, make it a sibling to the deploy directory, with -aux.
-       */
-      String deployDir = module.getDeployTo();
-      deployDir = deployDir.substring(0, deployDir.length() - 1) + "-aux";
-      moduleExtraDir = new File(outDir, deployDir);
-
-      /*
-       * Only warn when we create a new legacy extra dir.
-       */
-      warnOnExtra = !moduleExtraDir.exists();
-    } else {
-      moduleExtraDir = new File(extraDir, module.getDeployTo());
-    }
-
-    File moduleOutDir = new File(outDir, module.getDeployTo());
+  private static void doProduceLegacyOutput(TreeLogger logger,
+      ArtifactSet artifacts, StandardLinkerContext linkerContext,
+      ModuleDef module, File outDir) throws UnableToCompleteException {
+    File moduleOutDir = new File(outDir, module.getName());
+    File moduleExtraDir = new File(outDir, module.getName() + "-aux");
     Util.recursiveDelete(moduleOutDir, true);
     Util.recursiveDelete(moduleExtraDir, true);
     linkerContext.produceOutputDirectory(logger, artifacts, moduleOutDir,
         moduleExtraDir);
+    logger.log(TreeLogger.INFO, "Link succeeded");
+  }
 
-    /*
-     * Warn on legacy extra directory, but only if: 1) It didn't exist before.
-     * 2) We just created it.
-     */
-    if (warnOnExtra && moduleExtraDir.exists()) {
-      logger.log(
-          TreeLogger.WARN,
-          "Non-public artificats were produced in '"
-              + moduleExtraDir.getAbsolutePath()
-              + "' within the public output folder; use -extra to specify an alternate location");
+  private static void doProduceOutput(TreeLogger logger, ArtifactSet artifacts,
+      StandardLinkerContext linkerContext, ModuleDef module, File outDir,
+      File extraDir) throws UnableToCompleteException {
+    File moduleOutDir = new File(outDir, module.getName());
+    File moduleExtraDir = (extraDir == null) ? null : new File(extraDir,
+        module.getName());
+    Util.recursiveDelete(moduleOutDir, true);
+    if (moduleExtraDir != null) {
+      Util.recursiveDelete(moduleExtraDir, true);
     }
+    linkerContext.produceOutputDirectory(logger, artifacts, moduleOutDir,
+        moduleExtraDir);
     logger.log(TreeLogger.INFO, "Link succeeded");
   }
 
@@ -224,8 +226,6 @@
     }
   }
 
-  private ModuleDef module;
-
   private final LinkOptionsImpl options;
 
   public Link(LinkOptions options) {
@@ -233,48 +233,50 @@
   }
 
   public boolean run(TreeLogger logger) throws UnableToCompleteException {
-    module = ModuleDefLoader.loadFromClassPath(logger, options.getModuleName());
+    for (String moduleName : options.getModuleNames()) {
+      File compilerWorkDir = options.getCompilerWorkDir(moduleName);
+      ModuleDef module = ModuleDefLoader.loadFromClassPath(logger, moduleName);
 
-    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[] resultFiles = new File[perms.length];
-    for (int i = 0; i < perms.length; ++i) {
-      resultFiles[i] = CompilePerms.makePermFilename(
-          options.getCompilerWorkDir(), i);
-      if (!resultFiles[i].exists()) {
+      File precompilationFile = new File(compilerWorkDir,
+          Precompile.PRECOMPILATION_FILENAME);
+      if (!precompilationFile.exists()) {
         logger.log(TreeLogger.ERROR, "File not found '"
             + precompilationFile.getAbsolutePath()
-            + "'; please compile all permutations");
+            + "'; 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[] resultFiles = new File[perms.length];
+      for (int i = 0; i < perms.length; ++i) {
+        resultFiles[i] = CompilePerms.makePermFilename(compilerWorkDir, i);
+        if (!resultFiles[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,
+          resultFiles);
+
+      doProduceOutput(branch, artifacts, linkerContext, module,
+          options.getWarDir(), options.getExtraDir());
     }
-
-    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,
-        resultFiles);
-
-    doProduceOutput(branch, artifacts, linkerContext, module,
-        options.getOutDir(), options.getExtraDir());
     return true;
   }
 }
diff --git a/dev/core/src/com/google/gwt/dev/Precompile.java b/dev/core/src/com/google/gwt/dev/Precompile.java
index bb96be3..b8f6997 100644
--- a/dev/core/src/com/google/gwt/dev/Precompile.java
+++ b/dev/core/src/com/google/gwt/dev/Precompile.java
@@ -351,50 +351,44 @@
   }
 
   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(),
-          options.getCompilerWorkDir())) {
+    for (String moduleName : options.getModuleNames()) {
+      File compilerWorkDir = options.getCompilerWorkDir(moduleName);
+      Util.recursiveDelete(compilerWorkDir, true);
+      compilerWorkDir.mkdirs();
+
+      this.module = ModuleDefLoader.loadFromClassPath(logger, moduleName);
+
+      // TODO: All JDT checks now before even building TypeOracle?
+      module.getCompilationState(logger);
+
+      if (options.isValidateOnly()) {
+        TreeLogger branch = logger.branch(TreeLogger.INFO,
+            "Validating compilation " + module.getName());
+        if (!validate(branch, options, module, options.getGenDir(),
+            compilerWorkDir)) {
+          branch.log(TreeLogger.ERROR, "Validation failed");
+          return false;
+        }
         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(), options.getCompilerWorkDir());
-      if (precompilation != null) {
-        Util.writeObjectAsFile(branch, new File(options.getCompilerWorkDir(),
+        TreeLogger branch = logger.branch(TreeLogger.INFO,
+            "Precompiling module " + module.getName());
+        Precompilation precompilation = precompile(branch, options, module,
+            options.getGenDir(), compilerWorkDir);
+        if (precompilation == null) {
+          branch.log(TreeLogger.ERROR, "Precompilation failed");
+          return false;
+        }
+        Util.writeObjectAsFile(branch, new File(compilerWorkDir,
             PRECOMPILATION_FILENAME), precompilation);
-        Util.writeStringAsFile(branch, new File(options.getCompilerWorkDir(),
+        Util.writeStringAsFile(branch, new File(compilerWorkDir,
             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());
-
-    // TODO: All JDT checks now before even building TypeOracle?
-    module.getCompilationState(logger);
+    return true;
   }
 }
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 cbc3e39..8f66eaf 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java
@@ -85,8 +85,6 @@
 
   private Class<? extends Linker> activePrimaryLinker;
 
-  private String deployTo;
-
   private final List<String> entryPointTypeNames = new ArrayList<String>();
 
   private final Set<File> gwtXmlFiles = new HashSet<File>();
@@ -269,17 +267,6 @@
     return lazyCompilationState;
   }
 
-  /**
-   * Returns the desired deployment path within the output directory. The
-   * returned value will start and end with a <code>'/'</code> character.
-   */
-  public String getDeployTo() {
-    String result = (deployTo == null) ? ('/' + getName() + '/') : deployTo;
-    assert result.startsWith("/");
-    assert result.endsWith("/");
-    return result;
-  }
-
   public synchronized String[] getEntryPointTypeNames() {
     final int n = entryPointTypeNames.size();
     return entryPointTypeNames.toArray(new String[n]);
@@ -407,26 +394,6 @@
   }
 
   /**
-   * Set the deployment path for this module. Setting this value to
-   * <code>null</code> or the empty string will default to the fully-qualified
-   * module name.
-   */
-  public void setDeployTo(String deployTo) {
-    if (deployTo != null && deployTo.length() == 0) {
-      deployTo = null;
-    } else {
-      assert deployTo.startsWith("/");
-      // Ensure ends with trailing slash.
-      if (!deployTo.endsWith("/")) {
-        deployTo += '/';
-      }
-      assert deployTo.endsWith("/");
-    }
-
-    this.deployTo = deployTo;
-  }
-
-  /**
    * Override the module's apparent name. Setting this value to
    * <code>null<code> will disable the name override.
    */
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 bd394eb..a0c2e94 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java
@@ -921,7 +921,6 @@
   }
 
   protected final String __module_1_rename_to = "";
-  protected final String __module_2_deploy_to = "";
 
   private final PropertyAttrCvt bindingPropAttrCvt = new PropertyAttrCvt(
       BindingProperty.class);
@@ -968,21 +967,11 @@
     registerAttributeConverter(Class.class, classAttrCvt);
   }
 
-  protected Schema __module_begin(NullableName renameTo, String deployTo)
-      throws UnableToCompleteException {
-
-    if (deployTo != null && deployTo.length() > 0) {
-      // Only absolute paths, although it is okay to have multiple slashes.
-      if (!deployTo.startsWith("/")) {
-        logger.log(TreeLogger.ERROR, "deploy-to '" + deployTo
-            + "' must begin with forward slash (e.g. '/foo')");
-        throw new UnableToCompleteException();
-      }
-    }
+  protected Schema __module_begin(NullableName renameTo) {
     return bodySchema;
   }
 
-  protected void __module_end(NullableName renameTo, String deployTo) {
+  protected void __module_end(NullableName renameTo) {
     // Maybe infer source and public.
     //
     if (!foundExplicitSourceOrSuperSource) {
@@ -997,7 +986,6 @@
 
     // We do this in __module_end so this value is never inherited
     moduleDef.setNameOverride(renameTo.token);
-    moduleDef.setDeployTo(deployTo);
   }
 
   /**
diff --git a/dev/core/src/com/google/gwt/dev/shell/BrowserWindowController.java b/dev/core/src/com/google/gwt/dev/shell/BrowserWindowController.java
new file mode 100644
index 0000000..e2bb884
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/shell/BrowserWindowController.java
@@ -0,0 +1,31 @@
+/*
+ * 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.shell;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+
+/**
+ * Interface to the browser window controller.
+ */
+public interface BrowserWindowController {
+  void closeAllBrowserWindows();
+
+  boolean hasBrowserWindowsOpen();
+
+  String normalizeURL(String string);
+
+  BrowserWidget openNewBrowserWindow() throws UnableToCompleteException;
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/dev/shell/DialogBase.java b/dev/core/src/com/google/gwt/dev/shell/DialogBase.java
index 8642486..358abdc 100644
--- a/dev/core/src/com/google/gwt/dev/shell/DialogBase.java
+++ b/dev/core/src/com/google/gwt/dev/shell/DialogBase.java
@@ -15,8 +15,6 @@
  */
 package com.google.gwt.dev.shell;
 
-import com.google.gwt.dev.GWTShell;
-
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.DisposeEvent;
 import org.eclipse.swt.events.DisposeListener;
@@ -136,7 +134,7 @@
     Shell parent = getParent();
     shell = new Shell(parent, SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL
         | SWT.RESIZE);
-    shell.setImages(GWTShell.getIcons());
+    shell.setImages(ShellMainWindow.getIcons());
     shell.setText(getText());
     shell.setLayout(new FillLayout());
 
diff --git a/dev/core/src/com/google/gwt/dev/shell/GWTShellServletFilter.java b/dev/core/src/com/google/gwt/dev/shell/GWTShellServletFilter.java
deleted file mode 100644
index 4b5c6a6..0000000
--- a/dev/core/src/com/google/gwt/dev/shell/GWTShellServletFilter.java
+++ /dev/null
@@ -1,155 +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.shell;
-
-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.impl.StandardLinkerContext;
-import com.google.gwt.dev.ShellOptions;
-import com.google.gwt.dev.cfg.ModuleDef;
-
-import org.apache.commons.collections.map.AbstractReferenceMap;
-import org.apache.commons.collections.map.ReferenceIdentityMap;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.servlet.Filter;
-import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-
-/**
- * Built-in servlet for convenient access to the public path of a specified
- * module.
- */
-public class GWTShellServletFilter implements Filter {
-
-  private final Map<String, ModuleDef> autogenScripts = new HashMap<String, ModuleDef>();
-  /**
-   * Maintains a persistent map of linker contexts for each module, for
-   * incremental re-link with new generated artifacts.
-   */
-  @SuppressWarnings("unchecked")
-  private final Map<ModuleDef, StandardLinkerContext> linkerContextsByModule = new ReferenceIdentityMap(
-      AbstractReferenceMap.WEAK, AbstractReferenceMap.HARD, true);
-
-  private TreeLogger logger;
-
-  private final ShellOptions options;
-
-  public GWTShellServletFilter(TreeLogger logger, ShellOptions options,
-      ModuleDef[] moduleDefs) {
-    this.logger = logger;
-    this.options = options;
-    for (ModuleDef moduleDef : moduleDefs) {
-      String scriptName = moduleDef.getDeployTo() + moduleDef.getName()
-          + ".nocache.js";
-      autogenScripts.put(scriptName, moduleDef);
-    }
-  }
-
-  public void destroy() {
-  }
-
-  public void doFilter(ServletRequest req, ServletResponse resp,
-      FilterChain chain) throws IOException, ServletException {
-
-    if (req instanceof HttpServletRequest) {
-      HttpServletRequest request = (HttpServletRequest) req;
-      String pathInfo = request.getRequestURI();
-      logger.log(TreeLogger.TRACE, "Request for: " + pathInfo);
-      ModuleDef moduleDef = autogenScripts.get(pathInfo);
-      if (moduleDef != null) {
-        /*
-         * If the '?compiled' request property is specified, don't
-         * auto-generate.
-         * 
-         * TODO(scottb): does this even do anything anymore?
-         * 
-         * TODO(scottb): how do we avoid clobbering a compiled selection script?
-         */
-        if (req.getParameter("compiled") == null) {
-          try {
-            // Run the linkers for hosted mode.
-            hostedModeLink(logger.branch(TreeLogger.TRACE, "Request for '"
-                + pathInfo + "' maps to script generator for module '"
-                + moduleDef.getName() + "'"), moduleDef);
-          } catch (UnableToCompleteException e) {
-            /*
-             * The error will have already been logged. Continue, since this
-             * could actually be a request for a static file that happens to
-             * have an unfortunately confusing name.
-             */
-          }
-        }
-      }
-    }
-
-    // Do normal handling, knowing that the linkers may have run earlier to
-    // produce files we are just about to serve.
-    chain.doFilter(req, resp);
-  }
-
-  public void init(FilterConfig filterConfig) throws ServletException {
-  }
-
-  /**
-   * Called when new generated artifacts are produced.
-   */
-  public void relink(TreeLogger logger, ModuleDef moduleDef,
-      ArtifactSet newArtifacts) throws UnableToCompleteException {
-    StandardLinkerContext context = linkerContextsByModule.get(moduleDef);
-    assert context != null;
-
-    ArtifactSet artifacts = context.invokeRelink(logger, newArtifacts);
-    dumpArtifacts(logger, moduleDef, context, artifacts);
-  }
-
-  private void dumpArtifacts(TreeLogger logger, ModuleDef moduleDef,
-      StandardLinkerContext context, ArtifactSet artifacts)
-      throws UnableToCompleteException {
-    File outputPath = new File(options.getOutDir(), moduleDef.getDeployTo());
-    File extraPath = null;
-    if (options.getExtraDir() != null) {
-      extraPath = new File(options.getExtraDir(), moduleDef.getDeployTo());
-    }
-    context.produceOutputDirectory(logger, artifacts, outputPath, extraPath);
-  }
-
-  private void hostedModeLink(TreeLogger logger, ModuleDef moduleDef)
-      throws UnableToCompleteException {
-    String moduleName = moduleDef.getName();
-    logger.log(TreeLogger.TRACE, "Running linkers for module " + moduleName);
-
-    // TODO: blow away artifacts from a previous link.
-
-    // Perform the initial link.
-    StandardLinkerContext context = new StandardLinkerContext(logger,
-        moduleDef, options);
-    ArtifactSet artifacts = context.invokeLink(logger);
-    dumpArtifacts(logger, moduleDef, context, artifacts);
-
-    // Save off a new active link state (which may overwrite an old one).
-    linkerContextsByModule.put(moduleDef, context);
-  }
-}
diff --git a/dev/core/src/com/google/gwt/dev/shell/ServletContainerLauncher.java b/dev/core/src/com/google/gwt/dev/shell/ServletContainerLauncher.java
index d9ef731..ffec980 100644
--- a/dev/core/src/com/google/gwt/dev/shell/ServletContainerLauncher.java
+++ b/dev/core/src/com/google/gwt/dev/shell/ServletContainerLauncher.java
@@ -20,8 +20,6 @@
 import java.io.File;
 import java.net.BindException;
 
-import javax.servlet.Filter;
-
 /**
  * Defines the service provider interface for launching servlet containers that
  * can be used by the shell.
@@ -34,12 +32,10 @@
    * @param logger the server logger
    * @param port the TCP port to serve on
    * @param appRootDir the base WAR directory
-   * @param filter a servlet filter that must be installed on the root path to
-   *          serve generated files
    * @return the launch servlet contained
    * @throws BindException if the requested port is already in use
    * @throws Exception if the server fails to start for any other reason
    */
-  ServletContainer start(TreeLogger logger, int port, File appRootDir,
-      Filter filter) throws BindException, Exception;
+  ServletContainer start(TreeLogger logger, int port, File appRootDir)
+      throws BindException, Exception;
 }
diff --git a/dev/core/src/com/google/gwt/dev/shell/ShellMainWindow.java b/dev/core/src/com/google/gwt/dev/shell/ShellMainWindow.java
index 1e46069..031cc44 100644
--- a/dev/core/src/com/google/gwt/dev/shell/ShellMainWindow.java
+++ b/dev/core/src/com/google/gwt/dev/shell/ShellMainWindow.java
@@ -17,7 +17,6 @@
 
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
-import com.google.gwt.dev.GWTShell;
 import com.google.gwt.dev.util.Util;
 import com.google.gwt.dev.util.log.AbstractTreeLogger;
 import com.google.gwt.dev.util.log.TreeLoggerWidget;
@@ -30,6 +29,7 @@
 import org.eclipse.swt.events.ShellEvent;
 import org.eclipse.swt.events.ShellListener;
 import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Image;
 import org.eclipse.swt.layout.FillLayout;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.layout.GridLayout;
@@ -46,7 +46,6 @@
   private class Toolbar extends HeaderBarBase {
 
     private ToolItem about;
-
     private ToolItem clearLog;
     private ToolItem collapseAll;
     private ToolItem expandAll;
@@ -60,9 +59,9 @@
       newWindow.addSelectionListener(new SelectionAdapter() {
         @Override
         public void widgetSelected(SelectionEvent event) {
-          String startupUrl = serverWindow.normalizeURL("/");
+          String startupUrl = browserWindowController.normalizeURL("/");
           try {
-            BrowserWidget bw = serverWindow.openNewBrowserWindow();
+            BrowserWidget bw = browserWindowController.openNewBrowserWindow();
             bw.go(startupUrl);
           } catch (UnableToCompleteException e) {
             getLogger().log(TreeLogger.ERROR,
@@ -137,6 +136,23 @@
     }
   }
 
+  private static Image[] icons;
+
+  /**
+   * Well-known place to get the GWT icons.
+   */
+  public static Image[] getIcons() {
+    // Make sure icon images are loaded.
+    //
+    if (icons == null) {
+      icons = new Image[] {
+          LowLevel.loadImage("icon16.png"), LowLevel.loadImage("icon24.png"),
+          LowLevel.loadImage("icon32.png"), LowLevel.loadImage("icon48.png"),
+          LowLevel.loadImage("icon128.png")};
+    }
+    return icons;
+  }
+
   private static String verify(String hash) {
     char[] in = hash.toCharArray();
     char[] ou = new char[in.length];
@@ -156,19 +172,19 @@
     return String.valueOf(ou);
   }
 
+  private BrowserWindowController browserWindowController;
+
   private Color colorWhite;
 
   private TreeLoggerWidget logPane;
 
-  private GWTShell serverWindow;
-
   private Toolbar toolbar;
 
-  public ShellMainWindow(GWTShell serverWindow, final Shell parent,
-      int serverPort, boolean checkForUpdates) {
+  public ShellMainWindow(BrowserWindowController browserWindowController,
+      final Shell parent, int serverPort, boolean checkForUpdates) {
     super(parent, SWT.NONE);
 
-    this.serverWindow = serverWindow;
+    this.browserWindowController = browserWindowController;
 
     colorWhite = new Color(null, 255, 255, 255);
 
@@ -256,7 +272,7 @@
   }
 
   public void shellClosed(ShellEvent e) {
-    if (serverWindow.hasBrowserWindowsOpen()) {
+    if (browserWindowController.hasBrowserWindowsOpen()) {
       boolean closeWindows = true;
       if (System.getProperty("gwt.shell.endquick") == null) {
         closeWindows = DialogBase.confirmAction((Shell) e.widget,
@@ -265,7 +281,7 @@
       }
 
       if (closeWindows) {
-        serverWindow.closeAllBrowserWindows();
+        browserWindowController.closeAllBrowserWindows();
         e.doit = true;
       } else {
         e.doit = false;
diff --git a/dev/core/src/com/google/gwt/dev/shell/jetty/JettyLauncher.java b/dev/core/src/com/google/gwt/dev/shell/jetty/JettyLauncher.java
index 832311d..cf1dcdb 100644
--- a/dev/core/src/com/google/gwt/dev/shell/jetty/JettyLauncher.java
+++ b/dev/core/src/com/google/gwt/dev/shell/jetty/JettyLauncher.java
@@ -21,18 +21,14 @@
 import com.google.gwt.dev.shell.ServletContainer;
 import com.google.gwt.dev.shell.ServletContainerLauncher;
 
-import org.mortbay.jetty.Handler;
 import org.mortbay.jetty.Server;
 import org.mortbay.jetty.nio.SelectChannelConnector;
-import org.mortbay.jetty.servlet.FilterHolder;
 import org.mortbay.jetty.webapp.WebAppContext;
 import org.mortbay.log.Log;
 import org.mortbay.log.Logger;
 
 import java.io.File;
 
-import javax.servlet.Filter;
-
 /**
  * A launcher for an embedded Jetty server.
  */
@@ -190,20 +186,23 @@
   }
 
   @SuppressWarnings("unchecked")
-  public ServletContainer start(TreeLogger logger, int port, File appRootDir,
-      Filter shellServletFilter) throws Exception {
+  public ServletContainer start(TreeLogger logger, int port, File appRootDir)
+      throws Exception {
     checkStartParams(logger, port, appRootDir);
 
-    // The dance we do with Jetty's logging system.
-    System.setProperty("VERBOSE", "true");
-    JettyTreeLogger.setDefaultConstruction(logger, TreeLogger.INFO);
-    System.setProperty("org.mortbay.log.class", JettyTreeLogger.class.getName());
-    // Force initialization.
-    Log.isDebugEnabled();
-    if (JettyTreeLogger.isDefaultConstructionReady()) {
-      // The log system was already initialized and did not use our
-      // newly-constructed logger, set it explicitly now.
-      Log.setLog(new JettyTreeLogger());
+    // The dance we do with Jetty's logging system -- disabled, log to console.
+    if (false) {
+      System.setProperty("VERBOSE", "true");
+      JettyTreeLogger.setDefaultConstruction(logger, TreeLogger.INFO);
+      System.setProperty("org.mortbay.log.class",
+          JettyTreeLogger.class.getName());
+      // Force initialization.
+      Log.isDebugEnabled();
+      if (JettyTreeLogger.isDefaultConstructionReady()) {
+        // The log system was already initialized and did not use our
+        // newly-constructed logger, set it explicitly now.
+        Log.setLog(new JettyTreeLogger());
+      }
     }
 
     Server server = new Server();
@@ -222,12 +221,6 @@
     wac.getInitParams().put(
         "org.mortbay.jetty.servlet.Default.useFileMappedBuffer", "false");
 
-    // Setup the shell servlet filter to generate nocache.js files (and run
-    // the hosted mode linker stack.
-    FilterHolder filterHolder = new FilterHolder();
-    filterHolder.setFilter(shellServletFilter);
-    wac.addFilter(filterHolder, "/*", Handler.ALL);
-
     server.setHandler(wac);
     server.setStopAtShutdown(true);
     server.start();
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
index 3dab092..4b010ed 100644
--- a/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerModuleName.java
+++ b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerModuleName.java
@@ -30,7 +30,7 @@
 
   @Override
   public boolean addExtraArg(String arg) {
-    option.setModuleName(arg);
+    option.addModuleName(arg);
     return true;
   }
 
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerWarDir.java b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerWarDir.java
new file mode 100644
index 0000000..5f74707
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerWarDir.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 ArgHandlerWarDir extends ArgHandlerDir {
+
+  private final OptionWarDir option;
+
+  public ArgHandlerWarDir(OptionWarDir option) {
+    this.option = option;
+  }
+
+  public String[] getDefaultArgs() {
+    return new String[] {"-war", "war"};
+  }
+
+  public String getPurpose() {
+    return "The war directory to write output files into (defaults to war)";
+  }
+
+  public String getTag() {
+    return "-war";
+  }
+
+  @Override
+  public void setDir(File dir) {
+    option.setWarDir(dir);
+  }
+
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerWorkDirOptional.java b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerWorkDirOptional.java
index fc923fd..80c8f32 100644
--- a/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerWorkDirOptional.java
+++ b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerWorkDirOptional.java
@@ -22,7 +22,7 @@
 /**
  * Argument handler for processing an optional working directory.
  */
-public final class ArgHandlerWorkDirOptional extends ArgHandlerDir {
+public class ArgHandlerWorkDirOptional extends ArgHandlerDir {
 
   private final OptionWorkDir option;
 
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerWorkDirRequired.java b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerWorkDirRequired.java
index 6413f35..0f86683 100644
--- a/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerWorkDirRequired.java
+++ b/dev/core/src/com/google/gwt/dev/util/arg/ArgHandlerWorkDirRequired.java
@@ -15,37 +15,21 @@
  */
 package com.google.gwt.dev.util.arg;
 
-import com.google.gwt.util.tools.ArgHandlerDir;
-
-import java.io.File;
-
 /**
  * Argument handler for processing a required work directory.
  */
-public final class ArgHandlerWorkDirRequired extends ArgHandlerDir {
-
-  private final OptionWorkDir option;
+public class ArgHandlerWorkDirRequired extends ArgHandlerWorkDirOptional {
 
   public ArgHandlerWorkDirRequired(OptionWorkDir option) {
-    this.option = option;
+    super(option);
   }
 
   public String getPurpose() {
     return "The compiler work directory (must be writeable)";
   }
 
-  public String getTag() {
-    return "-workDir";
-  }
-
   @Override
   public boolean isRequired() {
     return true;
   }
-
-  @Override
-  public void setDir(File dir) {
-    option.setWorkDir(dir);
-  }
-
 }
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
index 688406e..749b563 100644
--- a/dev/core/src/com/google/gwt/dev/util/arg/OptionModuleName.java
+++ b/dev/core/src/com/google/gwt/dev/util/arg/OptionModuleName.java
@@ -15,18 +15,25 @@
  */
 package com.google.gwt.dev.util.arg;
 
+import java.util.List;
+
 /**
  * Option to set the module name.
  */
 public interface OptionModuleName {
 
   /**
-   * Returns the name of the module.
+   * Returns the list of module names.
    */
-  String getModuleName();
+  List<String> getModuleNames();
 
   /**
    * Sets the name of the module.
    */
-  void setModuleName(String moduleName);
+  void addModuleName(String moduleName);
+
+  /**
+   * Sets the list of module names.
+   */
+  void setModuleNames(List<String> moduleNames);
 }
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/OptionWarDir.java b/dev/core/src/com/google/gwt/dev/util/arg/OptionWarDir.java
new file mode 100644
index 0000000..0839e95
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/arg/OptionWarDir.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 OptionWarDir {
+
+  /**
+   * Returns the output directory.
+   */
+  File getWarDir();
+
+  /**
+   * Sets the output directory.
+   */
+  void setWarDir(File dir);
+}
diff --git a/eclipse/samples/DynaTable2/.classpath b/eclipse/samples/DynaTable2/.classpath
index 447a770..85b7671 100644
--- a/eclipse/samples/DynaTable2/.classpath
+++ b/eclipse/samples/DynaTable2/.classpath
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
 	<classpathentry kind="src" path="core/src"/>
+	<classpathentry kind="src" output="war" path="core/src.war"/>
 	<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"/>
diff --git a/eclipse/samples/DynaTable2/.project b/eclipse/samples/DynaTable2/.project
index 5ece582..e8b7c7e 100644
--- a/eclipse/samples/DynaTable2/.project
+++ b/eclipse/samples/DynaTable2/.project
@@ -24,7 +24,7 @@
 		<link>
 			<name>core</name>
 			<type>2</type>
-			<locationURI>GWT_ROOT/samples/dynatable</locationURI>
+			<locationURI>GWT_ROOT/samples/dynatable2</locationURI>
 		</link>
 	</linkedResources>
 </projectDescription>
diff --git a/eclipse/samples/DynaTable2/DynaTable2 compile.launch b/eclipse/samples/DynaTable2/DynaTable2 compile.launch
index ff78aaf..c171104 100644
--- a/eclipse/samples/DynaTable2/DynaTable2 compile.launch
+++ b/eclipse/samples/DynaTable2/DynaTable2 compile.launch
@@ -16,8 +16,8 @@
 <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="-out war&#13;&#10;-style PRETTY&#13;&#10;-logLevel INFO&#13;&#10;-extra extra&#13;&#10;com.google.gwt.sample.dynatable.DynaTable2"/>
+<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.Compiler"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-style PRETTY&#13;&#10;-logLevel INFO&#13;&#10;com.google.gwt.sample.dynatable2.DynaTable2"/>
 <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"/>
+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-ea&#13;&#10;-Xmx256M&#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 hosted.launch b/eclipse/samples/DynaTable2/DynaTable2 hosted.launch
index e25b728..562af59 100644
--- a/eclipse/samples/DynaTable2/DynaTable2 hosted.launch
+++ b/eclipse/samples/DynaTable2/DynaTable2 hosted.launch
@@ -1,10 +1,10 @@
 <?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"/>
+<listEntry value="/gwt-dev-windows/core/src/com/google/gwt/dev/HostedMode.java"/>
 </listAttribute>
 <listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
-<listEntry value="4"/>
+<listEntry value="1"/>
 </listAttribute>
 <booleanAttribute key="org.eclipse.debug.core.appendEnvironmentVariables" value="true"/>
 <listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
@@ -16,8 +16,8 @@
 <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.GWTHosted"/>
-<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-out war&#13;&#10;-style PRETTY&#13;&#10;-logLevel INFO&#13;&#10;-extra extra&#13;&#10;-startupUrl DynaTable2.html&#13;&#10;com.google.gwt.sample.dynatable.DynaTable2"/>
+<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.HostedMode"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-style PRETTY&#13;&#10;-logLevel INFO&#13;&#10;-extra extra&#13;&#10;-startupUrl DynaTable2.html&#13;&#10;com.google.gwt.sample.dynatable2.DynaTable2"/>
 <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"/>
+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-ea&#13;&#10;-Xmx256M&#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/DynaTable2Legacy compile.launch b/eclipse/samples/DynaTable2/DynaTable2Legacy compile.launch
new file mode 100644
index 0000000..868f116
--- /dev/null
+++ b/eclipse/samples/DynaTable2/DynaTable2Legacy 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="/gwt-dev-windows/core/src/com/google/gwt/dev/GWTCompiler.java"/>

+</listAttribute>

+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">

+<listEntry value="1"/>

+</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="-out www&#13;&#10;-style PRETTY&#13;&#10;-logLevel INFO&#13;&#10;com.google.gwt.sample.dynatable2.DynaTable2Legacy"/>

+<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="DynaTable2"/>

+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-ea&#13;&#10;-Xmx256M&#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/DynaTable2Legacy shell.launch b/eclipse/samples/DynaTable2/DynaTable2Legacy shell.launch
new file mode 100644
index 0000000..a8a2fdc
--- /dev/null
+++ b/eclipse/samples/DynaTable2/DynaTable2Legacy 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="/gwt-dev-windows/core/src/com/google/gwt/dev/GWTShell.java"/>

+</listAttribute>

+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">

+<listEntry value="1"/>

+</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="-out www&#13;&#10;-style PRETTY&#13;&#10;-logLevel INFO&#13;&#10;com.google.gwt.sample.dynatable2.DynaTable2Legacy/DynaTable2Legacy.html"/>

+<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="DynaTable2"/>

+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-ea&#13;&#10;-Xmx256M&#13;&#10;-Dgwt.devjar=C:\gwt\releases\1.6\build\staging\gwt-windows-0.0.0\gwt-dev-windows.jar"/>

+</launchConfiguration>

diff --git a/samples/dynatable2/build.xml b/samples/dynatable2/build.xml
new file mode 100644
index 0000000..00c96d6
--- /dev/null
+++ b/samples/dynatable2/build.xml
@@ -0,0 +1,5 @@
+<project name="dynatable" default="build" basedir=".">
+  <property name="sample.root" value="dynatable" />
+  <property name="sample.module" value="DynaTable" />
+  <import file="../common.ant.xml" />
+</project>
diff --git a/samples/dynatable2/src.war/DynaTable2.css b/samples/dynatable2/src.war/DynaTable2.css
new file mode 100644
index 0000000..ab81abf
--- /dev/null
+++ b/samples/dynatable2/src.war/DynaTable2.css
@@ -0,0 +1,85 @@
+
+body {
+  background-color: white;
+  color: black;
+  font-family: Arial, sans-serif;
+  font-size: small;
+  margin: 8px;
+  margin-top: 3px;
+}
+
+.DynaTable-DynaTableWidget {
+	width: 100%;
+	border: 1px solid #ACA899;
+}
+
+
+.DynaTable-DynaTableWidget .navbar {
+	width: 100%;
+	background-color: #ECE9D8;
+	vertical-align: middle;
+	border-bottom: 1px solid #ACA899;
+}
+
+.DynaTable-DynaTableWidget .navbar button {
+	width: 3em;
+	text-align: center;
+	vertical-align: middle;
+}
+
+.DynaTable-DynaTableWidget .navbar .status {
+	vertical-align: middle;
+	padding-right: 10px;
+}
+
+.DynaTable-DynaTableWidget .table {
+	margin: 10px;
+}
+
+.DynaTable-DynaTableWidget .table td.header {
+	text-align: left;
+	font-weight: bold;
+	text-decoration: underline;
+}
+
+.DynaTable-DynaTableWidget .table td.name {
+	width: 10em;
+}
+
+.DynaTable-DynaTableWidget .table td.desc {
+	width: 20em;
+}
+
+.DynaTable-DynaTableWidget .table td.sched {
+	width: 20em;
+}
+
+.DynaTable-DynaTableWidget .table td {
+	vertical-align: top;
+}
+
+.DynaTable-DayFilterWidget {
+	margin: 3em 1em 1em 0;
+	width: 10em;
+	padding: 0px 8px 0px 8px;
+	border: 1px solid #ACA899;
+}
+
+.DynaTable-DayFilterWidget button {
+	width: 4em;
+	margin: 8px 4px 8px 4px;
+}
+
+.DynaTable-ErrorDialog {
+  border: 2px outset;
+  background-color: white;
+  width: 50%;
+}
+
+.DynaTable-ErrorDialog .Caption {
+  background-color: #C3D9FF;
+  padding: 3px;
+  margin: 2px;
+  font-weight: bold;
+  cursor: default;
+}
diff --git a/samples/dynatable2/src.war/DynaTable2.html b/samples/dynatable2/src.war/DynaTable2.html
new file mode 100644
index 0000000..d4b8b2e
--- /dev/null
+++ b/samples/dynatable2/src.war/DynaTable2.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<!--                                                                        -->
+<!-- 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   -->
+<!-- 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. License for the specific language governing permissions and   -->
+<!-- limitations under the License.                                         -->
+ 
+<html>
+  <head>
+    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+    <link type="text/css" rel="stylesheet" href="DynaTable2.css">
+    <title></title>
+  </head>
+  <body>
+  <iframe src="javascript:''" id='__gwt_historyFrame' tabIndex='-1' style='width:0;height:0;border:0'></iframe>
+	<script type="text/javascript" language='javascript' src='dynatable2/dynatable2.nocache.js'></script>
+    <h1>School Schedule for Professors and Students</h1>
+    <table width="100%" border="0" summary="School Schedule for Professors and Students">
+      <tr valign="top">
+        <td id="calendar" align="center" width="90%">
+        </td>
+        <td id="days" align="center" width="10%">
+        </td>
+      </tr>
+    </table>
+  </body>
+</html>
diff --git a/samples/dynatable2/src.war/WEB-INF/classes/marker b/samples/dynatable2/src.war/WEB-INF/classes/marker
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/samples/dynatable2/src.war/WEB-INF/classes/marker
diff --git a/samples/dynatable2/src.war/WEB-INF/web.xml b/samples/dynatable2/src.war/WEB-INF/web.xml
new file mode 100644
index 0000000..01c3949
--- /dev/null
+++ b/samples/dynatable2/src.war/WEB-INF/web.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app>
+
+	<servlet>
+		<servlet-name>calendar</servlet-name>
+		<servlet-class>
+			com.google.gwt.sample.dynatable2.server.SchoolCalendarServiceImpl
+		</servlet-class>
+	</servlet>
+	<servlet-mapping>
+		<servlet-name>calendar</servlet-name>
+		<url-pattern>/com.google.gwt.sample.dynatable2.DynaTable2/calendar</url-pattern>
+	</servlet-mapping>
+
+</web-app>
diff --git a/samples/dynatable2/src/com/google/gwt/sample/dynatable2/COPYING b/samples/dynatable2/src/com/google/gwt/sample/dynatable2/COPYING
new file mode 100644
index 0000000..d9a10c0
--- /dev/null
+++ b/samples/dynatable2/src/com/google/gwt/sample/dynatable2/COPYING
@@ -0,0 +1,176 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
diff --git a/samples/dynatable2/src/com/google/gwt/sample/dynatable2/DynaTable2.gwt.xml b/samples/dynatable2/src/com/google/gwt/sample/dynatable2/DynaTable2.gwt.xml
new file mode 100644
index 0000000..7781947
--- /dev/null
+++ b/samples/dynatable2/src/com/google/gwt/sample/dynatable2/DynaTable2.gwt.xml
@@ -0,0 +1,19 @@
+<!--                                                                        -->
+<!-- Copyright 2007 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   -->
+<!-- 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. License for the specific language governing permissions and   -->
+<!-- limitations under the License.                                         -->
+
+<module rename-to="dynatable2">
+	<inherits name='com.google.gwt.user.User'/>
+	<entry-point class='com.google.gwt.sample.dynatable2.client.DynaTable'/>
+	<servlet path='/calendar' class='com.google.gwt.sample.dynatable2.server.SchoolCalendarServiceImpl'/>
+</module>
diff --git a/samples/dynatable2/src/com/google/gwt/sample/dynatable2/DynaTable2Legacy.gwt.xml b/samples/dynatable2/src/com/google/gwt/sample/dynatable2/DynaTable2Legacy.gwt.xml
new file mode 100644
index 0000000..85f806e
--- /dev/null
+++ b/samples/dynatable2/src/com/google/gwt/sample/dynatable2/DynaTable2Legacy.gwt.xml
@@ -0,0 +1,20 @@
+<!--                                                                        -->
+<!-- Copyright 2007 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   -->
+<!-- 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. License for the specific language governing permissions and   -->
+<!-- limitations under the License.                                         -->
+
+<module>
+	<inherits name='com.google.gwt.user.User'/>
+	<entry-point class='com.google.gwt.sample.dynatable2.client.DynaTable'/>
+	<servlet path='/calendar' class='com.google.gwt.sample.dynatable2.server.SchoolCalendarServiceImpl'/>
+	<public path='legacyPublic'/>
+</module>
diff --git a/samples/dynatable2/src/com/google/gwt/sample/dynatable2/client/DayFilterWidget.java b/samples/dynatable2/src/com/google/gwt/sample/dynatable2/client/DayFilterWidget.java
new file mode 100644
index 0000000..7be4cd8
--- /dev/null
+++ b/samples/dynatable2/src/com/google/gwt/sample/dynatable2/client/DayFilterWidget.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2007 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.sample.dynatable2.client;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HasAlignment;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * A UI Widget that allows a user to filter the days being displayed in the
+ * dynamic table.
+ */
+public class DayFilterWidget extends Composite {
+
+  private class DayCheckBox extends CheckBox {
+    public final int day;
+
+    public DayCheckBox(String caption, int day) {
+      super(caption);
+
+      // Remember custom data for this widget.
+      this.day = day;
+
+      // Use a shared handler to save memory.
+      addClickHandler(dayCheckBoxHandler);
+
+      // Initialize based on the calendar's current value.
+      setChecked(calendar.getDayIncluded(day));
+    }
+  }
+
+  private class DayCheckBoxHandler implements ClickHandler {
+    public void onClick(ClickEvent event) {
+      onClick((DayCheckBox) event.getSource());
+    }
+    
+    public void onClick(DayCheckBox dayCheckBox) {
+      calendar.setDayIncluded(dayCheckBox.day, dayCheckBox.isChecked());
+    }
+  }
+
+  private final SchoolCalendarWidget calendar;
+
+  private final VerticalPanel outer = new VerticalPanel();
+
+  private final DayCheckBoxHandler dayCheckBoxHandler = new DayCheckBoxHandler();
+
+  public DayFilterWidget(SchoolCalendarWidget calendar) {
+    this.calendar = calendar;
+    initWidget(outer);
+    setStyleName("DynaTable-DayFilterWidget");
+    outer.add(new DayCheckBox("Sunday", 0));
+    outer.add(new DayCheckBox("Monday", 1));
+    outer.add(new DayCheckBox("Tuesday", 2));
+    outer.add(new DayCheckBox("Wednesday", 3));
+    outer.add(new DayCheckBox("Thursday", 4));
+    outer.add(new DayCheckBox("Friday", 5));
+    outer.add(new DayCheckBox("Saturday", 6));
+
+    Button buttonAll = new Button("All", new ClickHandler() {
+      public void onClick(ClickEvent event) {
+        setAllCheckBoxes(true);
+      }
+    });
+
+    Button buttonNone = new Button("None", new ClickHandler() {
+      public void onClick(ClickEvent event) {
+        setAllCheckBoxes(false);
+      }
+    });
+
+    HorizontalPanel hp = new HorizontalPanel();
+    hp.setHorizontalAlignment(HasAlignment.ALIGN_CENTER);
+    hp.add(buttonAll);
+    hp.add(buttonNone);
+
+    outer.add(hp);
+    outer.setCellVerticalAlignment(hp, HasAlignment.ALIGN_BOTTOM);
+    outer.setCellHorizontalAlignment(hp, HasAlignment.ALIGN_CENTER);
+  }
+
+  private void setAllCheckBoxes(boolean checked) {
+    for (int i = 0, n = outer.getWidgetCount(); i < n; ++i) {
+      Widget w = outer.getWidget(i);
+      if (w instanceof DayCheckBox) {
+        ((DayCheckBox) w).setChecked(checked);
+        dayCheckBoxHandler.onClick((DayCheckBox) w);
+      }
+    }
+  }
+}
diff --git a/samples/dynatable2/src/com/google/gwt/sample/dynatable2/client/DynaTable.java b/samples/dynatable2/src/com/google/gwt/sample/dynatable2/client/DynaTable.java
new file mode 100644
index 0000000..41e208b
--- /dev/null
+++ b/samples/dynatable2/src/com/google/gwt/sample/dynatable2/client/DynaTable.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2007 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.sample.dynatable2.client;
+
+import com.google.gwt.core.client.EntryPoint;
+import com.google.gwt.user.client.ui.RootPanel;
+
+/**
+ * The entry point class which performs the initial loading of the DynaTable
+ * application.
+ */
+public class DynaTable implements EntryPoint {
+
+  public void onModuleLoad() {
+    // Find the slot for the calendar widget.
+    //
+    RootPanel slot = RootPanel.get("calendar");
+    if (slot != null) {
+      SchoolCalendarWidget calendar = new SchoolCalendarWidget(15);
+      slot.add(calendar);
+
+      // Find the slot for the days filter widget.
+      //
+      slot = RootPanel.get("days");
+      if (slot != null) {
+        DayFilterWidget filter = new DayFilterWidget(calendar);
+        slot.add(filter);
+      }
+    }
+  }
+}
diff --git a/samples/dynatable2/src/com/google/gwt/sample/dynatable2/client/DynaTableDataProvider.java b/samples/dynatable2/src/com/google/gwt/sample/dynatable2/client/DynaTableDataProvider.java
new file mode 100644
index 0000000..6875d63
--- /dev/null
+++ b/samples/dynatable2/src/com/google/gwt/sample/dynatable2/client/DynaTableDataProvider.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2007 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.sample.dynatable2.client;
+
+/**
+ * An interface for providing row-level updates of data, intended here to used
+ * to update a DynaTableWidget.
+ */
+public interface DynaTableDataProvider {
+
+  /**
+   * An interface allow a widget to accept or report failure when a row data
+   * is issued for update.
+   */
+  interface RowDataAcceptor {
+    void accept(int startRow, String[][] rows);
+    void failed(Throwable caught);
+  }
+
+  void updateRowData(int startRow, int maxRows, RowDataAcceptor acceptor);
+}
diff --git a/samples/dynatable2/src/com/google/gwt/sample/dynatable2/client/DynaTableWidget.java b/samples/dynatable2/src/com/google/gwt/sample/dynatable2/client/DynaTableWidget.java
new file mode 100644
index 0000000..fb96245
--- /dev/null
+++ b/samples/dynatable2/src/com/google/gwt/sample/dynatable2/client/DynaTableWidget.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright 2007 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.sample.dynatable2.client;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.sample.dynatable2.client.DynaTableDataProvider.RowDataAcceptor;
+import com.google.gwt.user.client.rpc.InvocationException;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.DialogBox;
+import com.google.gwt.user.client.ui.DockPanel;
+import com.google.gwt.user.client.ui.Grid;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.HasAlignment;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.VerticalPanel;
+
+/**
+ * A composite Widget that implements the main interface for the dynamic table,
+ * including the data table, status indicators, and paging buttons.
+ */
+public class DynaTableWidget extends Composite {
+
+  /**
+   * A dialog box for displaying an error.
+   */
+  private static class ErrorDialog extends DialogBox implements ClickHandler {
+    private HTML body = new HTML("");
+
+    public ErrorDialog() {
+      setStylePrimaryName("DynaTable-ErrorDialog");
+      Button closeButton = new Button("Close", this);
+      VerticalPanel panel = new VerticalPanel();
+      panel.setSpacing(4);
+      panel.add(body);
+      panel.add(closeButton);
+      panel.setCellHorizontalAlignment(closeButton, VerticalPanel.ALIGN_RIGHT);
+      setWidget(panel);
+    }
+
+    public String getBody() {
+      return body.getHTML();
+    }
+
+    public void onClick(ClickEvent event) {
+      hide();
+    }
+
+    public void setBody(String html) {
+      body.setHTML(html);
+    }
+  }
+
+  private class NavBar extends Composite implements ClickHandler {
+
+    public final DockPanel bar = new DockPanel();
+    public final Button gotoFirst = new Button("&lt;&lt;", this);
+    public final Button gotoNext = new Button("&gt;", this);
+    public final Button gotoPrev = new Button("&lt;", this);
+    public final HTML status = new HTML();
+
+    public NavBar() {
+      initWidget(bar);
+      bar.setStyleName("navbar");
+      status.setStyleName("status");
+
+      HorizontalPanel buttons = new HorizontalPanel();
+      buttons.add(gotoFirst);
+      buttons.add(gotoPrev);
+      buttons.add(gotoNext);
+      bar.add(buttons, DockPanel.EAST);
+      bar.setCellHorizontalAlignment(buttons, DockPanel.ALIGN_RIGHT);
+      bar.add(status, DockPanel.CENTER);
+      bar.setVerticalAlignment(DockPanel.ALIGN_MIDDLE);
+      bar.setCellHorizontalAlignment(status, HasAlignment.ALIGN_RIGHT);
+      bar.setCellVerticalAlignment(status, HasAlignment.ALIGN_MIDDLE);
+      bar.setCellWidth(status, "100%");
+
+      // Initialize prev & first button to disabled.
+      //
+      gotoPrev.setEnabled(false);
+      gotoFirst.setEnabled(false);
+    }
+
+    public void onClick(ClickEvent event) {
+      Object source = event.getSource();
+      if (source == gotoNext) {
+        startRow += getDataRowCount();
+        refresh();
+      } else if (source == gotoPrev) {
+        startRow -= getDataRowCount();
+        if (startRow < 0) {
+          startRow = 0;
+        }
+        refresh();
+      } else if (source == gotoFirst) {
+        startRow = 0;
+        refresh();
+      }
+    }
+  }
+
+  private class RowDataAcceptorImpl implements RowDataAcceptor {
+    public void accept(int startRow, String[][] data) {
+
+      int destRowCount = getDataRowCount();
+      int destColCount = grid.getCellCount(0);
+      assert (data.length <= destRowCount) : "Too many rows";
+
+      int srcRowIndex = 0;
+      int srcRowCount = data.length;
+      int destRowIndex = 1; // skip navbar row
+      for (; srcRowIndex < srcRowCount; ++srcRowIndex, ++destRowIndex) {
+        String[] srcRowData = data[srcRowIndex];
+        assert (srcRowData.length == destColCount) : " Column count mismatch";
+        for (int srcColIndex = 0; srcColIndex < destColCount; ++srcColIndex) {
+          String cellHTML = srcRowData[srcColIndex];
+          grid.setText(destRowIndex, srcColIndex, cellHTML);
+        }
+      }
+
+      // Clear remaining table rows.
+      //
+      boolean isLastPage = false;
+      for (; destRowIndex < destRowCount + 1; ++destRowIndex) {
+        isLastPage = true;
+        for (int destColIndex = 0; destColIndex < destColCount; ++destColIndex) {
+          grid.clearCell(destRowIndex, destColIndex);
+        }
+      }
+
+      // Synchronize the nav buttons.
+      navbar.gotoNext.setEnabled(!isLastPage);
+      navbar.gotoFirst.setEnabled(startRow > 0);
+      navbar.gotoPrev.setEnabled(startRow > 0);
+
+      // Update the status message.
+      //
+      setStatusText((startRow + 1) + " - " + (startRow + srcRowCount));
+    }
+
+    public void failed(Throwable caught) {
+      setStatusText("Error");
+      if (errorDialog == null) {
+        errorDialog = new ErrorDialog();
+      }
+      if (caught instanceof InvocationException) {
+        errorDialog.setText("An RPC server could not be reached");
+        errorDialog.setBody(NO_CONNECTION_MESSAGE);
+      } else {
+        errorDialog.setText("Unexcepted Error processing remote call");
+        errorDialog.setBody(caught.getMessage());
+      }
+      errorDialog.center();
+    }
+  }
+
+  private static final String NO_CONNECTION_MESSAGE = "<p>The DynaTable example uses a <a href=\"http://code.google.com/"
+      + "webtoolkit/documentation/com.google.gwt.doc.DeveloperGuide."
+      + "RemoteProcedureCalls.html\" target=\"_blank\">Remote Procedure Call</a> "
+      + "(RPC) to request data from the server.  In order for the RPC to "
+      + "successfully return data, the server component must be available.</p>"
+      + "<p>If you are running this demo from compiled code, the server "
+      + "component may not be available to respond to the RPC requests from "
+      + "DynaTable.  Try running DynaTable in hosted mode to see the demo "
+      + "in action.</p> "
+      + "<p>Click on the Remote Procedure Call link above for more information "
+      + "on GWT's RPC infrastructure.";
+
+  private final RowDataAcceptor acceptor = new RowDataAcceptorImpl();
+
+  private final Grid grid = new Grid();
+
+  private final NavBar navbar = new NavBar();
+
+  private ErrorDialog errorDialog = null;
+
+  private final DockPanel outer = new DockPanel();
+
+  private final DynaTableDataProvider provider;
+
+  private int startRow = 0;
+
+  public DynaTableWidget(DynaTableDataProvider provider, String[] columns,
+      String[] columnStyles, int rowCount) {
+
+    if (columns.length == 0) {
+      throw new IllegalArgumentException(
+          "expecting a positive number of columns");
+    }
+
+    if (columnStyles != null && columns.length != columnStyles.length) {
+      throw new IllegalArgumentException("expecting as many styles as columns");
+    }
+
+    this.provider = provider;
+    initWidget(outer);
+    grid.setStyleName("table");
+    outer.add(navbar, DockPanel.NORTH);
+    outer.add(grid, DockPanel.CENTER);
+    initTable(columns, columnStyles, rowCount);
+    setStyleName("DynaTable-DynaTableWidget");
+  }
+
+  public void clearStatusText() {
+    navbar.status.setHTML("&nbsp;");
+  }
+
+  public void refresh() {
+    // Disable buttons temporarily to stop the user from running off the end.
+    //
+    navbar.gotoFirst.setEnabled(false);
+    navbar.gotoPrev.setEnabled(false);
+    navbar.gotoNext.setEnabled(false);
+
+    setStatusText("Please wait...");
+    provider.updateRowData(startRow, grid.getRowCount() - 1, acceptor);
+  }
+
+  public void setRowCount(int rows) {
+    grid.resizeRows(rows);
+  }
+
+  public void setStatusText(String text) {
+    navbar.status.setText(text);
+  }
+
+  private int getDataRowCount() {
+    return grid.getRowCount() - 1;
+  }
+
+  private void initTable(String[] columns, String[] columnStyles, int rowCount) {
+    // Set up the header row. It's one greater than the number of visible rows.
+    //
+    grid.resize(rowCount + 1, columns.length);
+    for (int i = 0, n = columns.length; i < n; i++) {
+      grid.setText(0, i, columns[i]);
+      if (columnStyles != null) {
+        grid.getCellFormatter().setStyleName(0, i, columnStyles[i] + " header");
+      }
+    }
+  }
+}
diff --git a/samples/dynatable2/src/com/google/gwt/sample/dynatable2/client/Person.java b/samples/dynatable2/src/com/google/gwt/sample/dynatable2/client/Person.java
new file mode 100644
index 0000000..d73702f
--- /dev/null
+++ b/samples/dynatable2/src/com/google/gwt/sample/dynatable2/client/Person.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2007 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.sample.dynatable2.client;
+
+import com.google.gwt.user.client.rpc.IsSerializable;
+
+/**
+ * Hold relevant data for Person. This class is meant to be serialized in RPC
+ * calls.
+ */
+public abstract class Person implements IsSerializable {
+
+  private String description = "DESC";
+
+  private String name;
+
+  public Person() {
+  }
+
+  public String getDescription() {
+    return description;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public abstract String getSchedule(boolean[] daysFilter);
+
+  public void setDescription(String description) {
+    this.description = description;
+  }
+
+  public void setName(String name) {
+    this.name = name;
+  }
+}
diff --git a/samples/dynatable2/src/com/google/gwt/sample/dynatable2/client/Professor.java b/samples/dynatable2/src/com/google/gwt/sample/dynatable2/client/Professor.java
new file mode 100644
index 0000000..932d188
--- /dev/null
+++ b/samples/dynatable2/src/com/google/gwt/sample/dynatable2/client/Professor.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2007 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.sample.dynatable2.client;
+
+/**
+ * Holds relevant data for a Professor type Person. This class is intended to be
+ * serialized in RPC calls.
+ */
+public class Professor extends Person {
+
+  private Schedule teachingSchedule = new Schedule();
+
+  public Professor() {
+  }
+
+  @Override
+  public String getSchedule(boolean[] daysFilter) {
+    return teachingSchedule.getDescription(daysFilter);
+  }
+
+  public Schedule getTeachingSchedule() {
+    return teachingSchedule;
+  }
+}
diff --git a/samples/dynatable2/src/com/google/gwt/sample/dynatable2/client/Schedule.java b/samples/dynatable2/src/com/google/gwt/sample/dynatable2/client/Schedule.java
new file mode 100644
index 0000000..ac0b40e
--- /dev/null
+++ b/samples/dynatable2/src/com/google/gwt/sample/dynatable2/client/Schedule.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.sample.dynatable2.client;
+
+import com.google.gwt.user.client.rpc.IsSerializable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Hold the relevant data for a Schedule. This class is meant to be serialized
+ * in RPC calls.
+ */
+public class Schedule implements IsSerializable {
+
+  private List<TimeSlot> timeSlots = new ArrayList<TimeSlot>();
+
+  public Schedule() {
+  }
+
+  public void addTimeSlot(TimeSlot timeSlot) {
+    timeSlots.add(timeSlot);
+  }
+
+  public String getDescription(boolean[] daysFilter) {
+    String s = null;
+    for (TimeSlot timeSlot : timeSlots) {
+      if (daysFilter[timeSlot.getDayOfWeek()]) {
+        if (s == null) {
+          s = timeSlot.getDescription();
+        } else {
+          s += ", " + timeSlot.getDescription();
+        }
+      }
+    }
+
+    if (s != null) {
+      return s;
+    } else {
+      return "";
+    }
+  }
+
+  @Override
+  public String toString() {
+    return getDescription(null);
+  }
+
+}
diff --git a/samples/dynatable2/src/com/google/gwt/sample/dynatable2/client/SchoolCalendarService.java b/samples/dynatable2/src/com/google/gwt/sample/dynatable2/client/SchoolCalendarService.java
new file mode 100644
index 0000000..fee68f6
--- /dev/null
+++ b/samples/dynatable2/src/com/google/gwt/sample/dynatable2/client/SchoolCalendarService.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2007 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.sample.dynatable2.client;
+
+import com.google.gwt.user.client.rpc.RemoteService;
+
+/**
+ * The interface for the RPC server endpoint to get school calendar
+ * information.
+ */
+public interface SchoolCalendarService extends RemoteService {
+  
+  Person[] getPeople(int startIndex, int maxCount);
+  
+}
diff --git a/samples/dynatable2/src/com/google/gwt/sample/dynatable2/client/SchoolCalendarServiceAsync.java b/samples/dynatable2/src/com/google/gwt/sample/dynatable2/client/SchoolCalendarServiceAsync.java
new file mode 100644
index 0000000..3aa3757
--- /dev/null
+++ b/samples/dynatable2/src/com/google/gwt/sample/dynatable2/client/SchoolCalendarServiceAsync.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2007 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.sample.dynatable2.client;
+
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+/**
+ * The interface for the RPC server endpoint that provides school calendar
+ * information for clients that will be calling asynchronously. 
+ */
+public interface SchoolCalendarServiceAsync {
+
+  void getPeople(int startIndex, int maxCount, AsyncCallback<Person[]> callback);
+
+}
diff --git a/samples/dynatable2/src/com/google/gwt/sample/dynatable2/client/SchoolCalendarWidget.java b/samples/dynatable2/src/com/google/gwt/sample/dynatable2/client/SchoolCalendarWidget.java
new file mode 100644
index 0000000..65e4fd2
--- /dev/null
+++ b/samples/dynatable2/src/com/google/gwt/sample/dynatable2/client/SchoolCalendarWidget.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2007 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.sample.dynatable2.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DeferredCommand;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwt.user.client.rpc.ServiceDefTarget;
+import com.google.gwt.user.client.ui.Composite;
+
+/**
+ * A Composite widget that abstracts a DynaTableWidget and a data provider tied
+ * to the <@link SchoolCalendarService> RPC endpoint.
+ */
+public class SchoolCalendarWidget extends Composite {
+
+  /**
+   * A data provider that bridges the provides row level updates from the data
+   * available through a <@link SchoolCalendarService>.
+   */
+  public class CalendarProvider implements DynaTableDataProvider {
+
+    private final SchoolCalendarServiceAsync calService;
+
+    private int lastMaxRows = -1;
+
+    private Person[] lastPeople;
+
+    private int lastStartRow = -1;
+
+    public CalendarProvider() {
+      // Initialize the service.
+      //
+      calService = (SchoolCalendarServiceAsync) GWT.create(SchoolCalendarService.class);
+
+      // By default, we assume we'll make RPCs to a servlet, but see
+      // updateRowData(). There is special support for canned RPC responses.
+      // (Which is a totally demo hack, by the way :-)
+      // 
+      ServiceDefTarget target = (ServiceDefTarget) calService;
+
+      // Use a module-relative URLs to ensure that this client code can find
+      // its way home, even when the URL changes (as might happen when you
+      // deploy this as a webapp under an external servlet container).
+      String moduleRelativeURL = GWT.getModuleBaseURL() + "calendar";
+      target.setServiceEntryPoint(moduleRelativeURL);
+    }
+
+    public void updateRowData(final int startRow, final int maxRows,
+        final RowDataAcceptor acceptor) {
+      // Check the simple cache first.
+      //
+      if (startRow == lastStartRow) {
+        if (maxRows == lastMaxRows) {
+          // Use the cached batch.
+          //
+          pushResults(acceptor, startRow, lastPeople);
+          return;
+        }
+      }
+
+      // Fetch the data remotely.
+      //
+      calService.getPeople(startRow, maxRows, new AsyncCallback<Person[]>() {
+        public void onFailure(Throwable caught) {
+          acceptor.failed(caught);
+        }
+
+        public void onSuccess(Person[] result) {
+          lastStartRow = startRow;
+          lastMaxRows = maxRows;
+          lastPeople = result;
+          pushResults(acceptor, startRow, result);
+        }
+
+      });
+    }
+
+    private void pushResults(RowDataAcceptor acceptor, int startRow,
+        Person[] people) {
+      String[][] rows = new String[people.length][];
+      for (int i = 0, n = rows.length; i < n; i++) {
+        Person person = people[i];
+        rows[i] = new String[3];
+        rows[i][0] = person.getName();
+        rows[i][1] = person.getDescription();
+        rows[i][2] = person.getSchedule(daysFilter);
+      }
+      acceptor.accept(startRow, rows);
+    }
+  }
+
+  private final CalendarProvider calProvider = new CalendarProvider();
+
+  private final boolean[] daysFilter = new boolean[] {
+      true, true, true, true, true, true, true};
+
+  private final DynaTableWidget dynaTable;
+
+  private Command pendingRefresh;
+
+  public SchoolCalendarWidget(int visibleRows) {
+    String[] columns = new String[] {"Name", "Description", "Schedule"};
+    String[] styles = new String[] {"name", "desc", "sched"};
+    dynaTable = new DynaTableWidget(calProvider, columns, styles, visibleRows);
+    initWidget(dynaTable);
+  }
+
+  protected boolean getDayIncluded(int day) {
+    return daysFilter[day];
+  }
+
+  @Override
+  protected void onLoad() {
+    dynaTable.refresh();
+  }
+
+  protected void setDayIncluded(int day, boolean included) {
+    if (daysFilter[day] == included) {
+      // No change.
+      //
+      return;
+    }
+
+    daysFilter[day] = included;
+    if (pendingRefresh == null) {
+      pendingRefresh = new Command() {
+        public void execute() {
+          pendingRefresh = null;
+          dynaTable.refresh();
+        }
+      };
+      DeferredCommand.addCommand(pendingRefresh);
+    }
+  }
+}
diff --git a/samples/dynatable2/src/com/google/gwt/sample/dynatable2/client/Student.java b/samples/dynatable2/src/com/google/gwt/sample/dynatable2/client/Student.java
new file mode 100644
index 0000000..ff8d068
--- /dev/null
+++ b/samples/dynatable2/src/com/google/gwt/sample/dynatable2/client/Student.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2007 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.sample.dynatable2.client;
+
+/**
+ * Holds relevant data for a Student type Person. This class is intended to be
+ * serialized in RPC calls.
+ */
+public class Student extends Person {
+
+  private Schedule classSchedule = new Schedule();
+
+  public Schedule getClassSchedule() {
+    return classSchedule;
+  }
+
+  @Override
+  public String getSchedule(boolean[] daysFilter) {
+    return classSchedule.getDescription(daysFilter);
+  }
+}
diff --git a/samples/dynatable2/src/com/google/gwt/sample/dynatable2/client/TimeSlot.java b/samples/dynatable2/src/com/google/gwt/sample/dynatable2/client/TimeSlot.java
new file mode 100644
index 0000000..1a0f130
--- /dev/null
+++ b/samples/dynatable2/src/com/google/gwt/sample/dynatable2/client/TimeSlot.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2007 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.sample.dynatable2.client;
+
+import com.google.gwt.user.client.rpc.IsSerializable;
+
+/**
+ * Hold relevant data for a time slot. This class is intended to be serialized
+ * as part of RPC calls.
+ */
+public class TimeSlot implements IsSerializable, Comparable<TimeSlot> {
+
+  private static final transient String[] DAYS = new String[] {
+      "Sun", "Mon", "Tues", "Wed", "Thurs", "Fri", "Sat"};
+
+  private int endMinutes;
+
+  private int startMinutes;
+
+  private int zeroBasedDayOfWeek;
+
+  public TimeSlot() {
+  }
+
+  public TimeSlot(int zeroBasedDayOfWeek, int startMinutes, int endMinutes) {
+    this.zeroBasedDayOfWeek = zeroBasedDayOfWeek;
+    this.startMinutes = startMinutes;
+    this.endMinutes = endMinutes;
+  }
+
+  public int compareTo(TimeSlot o) {
+    if (zeroBasedDayOfWeek < o.zeroBasedDayOfWeek) {
+      return -1;
+    } else if (zeroBasedDayOfWeek > o.zeroBasedDayOfWeek) {
+      return 1;
+    } else {
+      if (startMinutes < o.startMinutes) {
+        return -1;
+      } else if (startMinutes > o.startMinutes) {
+        return 1;
+      }
+    }
+
+    return 0;
+  }
+
+  public int getDayOfWeek() {
+    return zeroBasedDayOfWeek;
+  }
+
+  public String getDescription() {
+    return DAYS[zeroBasedDayOfWeek] + " " + getHrsMins(startMinutes) + "-"
+        + getHrsMins(endMinutes);
+  }
+
+  public int getEndMinutes() {
+    return endMinutes;
+  }
+
+  public int getStartMinutes() {
+    return startMinutes;
+  }
+
+  public void setDayOfWeek(int zeroBasedDayOfWeek) {
+    if (0 <= zeroBasedDayOfWeek && zeroBasedDayOfWeek < 7) {
+      this.zeroBasedDayOfWeek = zeroBasedDayOfWeek;
+    } else {
+      throw new IllegalArgumentException("day must be in the range 0-6");
+    }
+  }
+
+  public void setEndMinutes(int endMinutes) {
+    this.endMinutes = endMinutes;
+  }
+
+  public void setStartMinutes(int startMinutes) {
+    this.startMinutes = startMinutes;
+  }
+
+  private String getHrsMins(int mins) {
+    int hrs = mins / 60;
+    if (hrs > 12) {
+      hrs -= 12;
+    }
+
+    int remainder = mins % 60;
+
+    return hrs + ":"
+        + (remainder < 10 ? "0" + remainder : String.valueOf(remainder));
+  }
+}
diff --git a/samples/dynatable2/src/com/google/gwt/sample/dynatable2/legacyPublic/DynaTable2Legacy.css b/samples/dynatable2/src/com/google/gwt/sample/dynatable2/legacyPublic/DynaTable2Legacy.css
new file mode 100644
index 0000000..ab81abf
--- /dev/null
+++ b/samples/dynatable2/src/com/google/gwt/sample/dynatable2/legacyPublic/DynaTable2Legacy.css
@@ -0,0 +1,85 @@
+
+body {
+  background-color: white;
+  color: black;
+  font-family: Arial, sans-serif;
+  font-size: small;
+  margin: 8px;
+  margin-top: 3px;
+}
+
+.DynaTable-DynaTableWidget {
+	width: 100%;
+	border: 1px solid #ACA899;
+}
+
+
+.DynaTable-DynaTableWidget .navbar {
+	width: 100%;
+	background-color: #ECE9D8;
+	vertical-align: middle;
+	border-bottom: 1px solid #ACA899;
+}
+
+.DynaTable-DynaTableWidget .navbar button {
+	width: 3em;
+	text-align: center;
+	vertical-align: middle;
+}
+
+.DynaTable-DynaTableWidget .navbar .status {
+	vertical-align: middle;
+	padding-right: 10px;
+}
+
+.DynaTable-DynaTableWidget .table {
+	margin: 10px;
+}
+
+.DynaTable-DynaTableWidget .table td.header {
+	text-align: left;
+	font-weight: bold;
+	text-decoration: underline;
+}
+
+.DynaTable-DynaTableWidget .table td.name {
+	width: 10em;
+}
+
+.DynaTable-DynaTableWidget .table td.desc {
+	width: 20em;
+}
+
+.DynaTable-DynaTableWidget .table td.sched {
+	width: 20em;
+}
+
+.DynaTable-DynaTableWidget .table td {
+	vertical-align: top;
+}
+
+.DynaTable-DayFilterWidget {
+	margin: 3em 1em 1em 0;
+	width: 10em;
+	padding: 0px 8px 0px 8px;
+	border: 1px solid #ACA899;
+}
+
+.DynaTable-DayFilterWidget button {
+	width: 4em;
+	margin: 8px 4px 8px 4px;
+}
+
+.DynaTable-ErrorDialog {
+  border: 2px outset;
+  background-color: white;
+  width: 50%;
+}
+
+.DynaTable-ErrorDialog .Caption {
+  background-color: #C3D9FF;
+  padding: 3px;
+  margin: 2px;
+  font-weight: bold;
+  cursor: default;
+}
diff --git a/samples/dynatable2/src/com/google/gwt/sample/dynatable2/legacyPublic/DynaTable2Legacy.html b/samples/dynatable2/src/com/google/gwt/sample/dynatable2/legacyPublic/DynaTable2Legacy.html
new file mode 100644
index 0000000..3c5887a
--- /dev/null
+++ b/samples/dynatable2/src/com/google/gwt/sample/dynatable2/legacyPublic/DynaTable2Legacy.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<!--                                                                        -->
+<!-- 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   -->
+<!-- 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. License for the specific language governing permissions and   -->
+<!-- limitations under the License.                                         -->
+ 
+<html>
+  <head>
+    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+    <link type="text/css" rel="stylesheet" href="DynaTable2Legacy.css">
+    <title></title>
+  </head>
+  <body>
+  <iframe src="javascript:''" id='__gwt_historyFrame' tabIndex='-1' style='width:0;height:0;border:0'></iframe>
+	<script type="text/javascript" language='javascript' src='com.google.gwt.sample.dynatable2.DynaTable2Legacy.nocache.js'></script>
+    <h1>School Schedule for Professors and Students</h1>
+    <table width="100%" border="0" summary="School Schedule for Professors and Students">
+      <tr valign="top">
+        <td id="calendar" align="center" width="90%">
+        </td>
+        <td id="days" align="center" width="10%">
+        </td>
+      </tr>
+    </table>
+  </body>
+</html>
diff --git a/samples/dynatable2/src/com/google/gwt/sample/dynatable2/server/SchoolCalendarServiceImpl.java b/samples/dynatable2/src/com/google/gwt/sample/dynatable2/server/SchoolCalendarServiceImpl.java
new file mode 100644
index 0000000..2705edb
--- /dev/null
+++ b/samples/dynatable2/src/com/google/gwt/sample/dynatable2/server/SchoolCalendarServiceImpl.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2007 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.sample.dynatable2.server;
+
+import com.google.gwt.sample.dynatable2.client.Person;
+import com.google.gwt.sample.dynatable2.client.Professor;
+import com.google.gwt.sample.dynatable2.client.Schedule;
+import com.google.gwt.sample.dynatable2.client.SchoolCalendarService;
+import com.google.gwt.sample.dynatable2.client.Student;
+import com.google.gwt.sample.dynatable2.client.TimeSlot;
+import com.google.gwt.user.server.rpc.RemoteServiceServlet;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * The implementation of the RPC service which runs on the server.
+ */
+public class SchoolCalendarServiceImpl extends RemoteServiceServlet implements
+    SchoolCalendarService {
+
+  private static final String[] FIRST_NAMES = new String[] {
+      "Inman", "Sally", "Omar", "Teddy", "Jimmy", "Cathy", "Barney", "Fred",
+      "Eddie", "Carlos"};
+
+  private static final String[] LAST_NAMES = new String[] {
+      "Smith", "Jones", "Epps", "Gibbs", "Webber", "Blum", "Mendez",
+      "Crutcher", "Needler", "Wilson", "Chase", "Edelstein"};
+
+  private static final String[] SUBJECTS = new String[] {
+      "Chemistry", "Phrenology", "Geometry", "Underwater Basket Weaving",
+      "Basketball", "Computer Science", "Statistics", "Materials Engineering",
+      "English Literature", "Geology"};
+
+  private static final Person[] NO_PEOPLE = new Person[0];
+
+  private static final int CLASS_LENGTH_MINS = 50;
+
+  private static final int MAX_SCHED_ENTRIES = 5;
+
+  private static final int MIN_SCHED_ENTRIES = 1;
+
+  private static final int MAX_PEOPLE = 100;
+
+  private static final int STUDENTS_PER_PROF = 5;
+
+  private final List<Person> people = new ArrayList<Person>();
+
+  private final Random rnd = new Random(3);
+
+  public SchoolCalendarServiceImpl() {
+    generateRandomPeople();
+  }
+
+  public Person[] getPeople(int startIndex, int maxCount) {
+    int peopleCount = people.size();
+
+    int start = startIndex;
+    if (start >= peopleCount) {
+      return NO_PEOPLE;
+    }
+
+    int end = Math.min(startIndex + maxCount, peopleCount);
+    if (start == end) {
+      return NO_PEOPLE;
+    }
+
+    int resultCount = end - start;
+    Person[] results = new Person[resultCount];
+    for (int from = start, to = 0; to < resultCount; ++from, ++to) {
+      results[to] = people.get(from);
+    }
+
+    return results;
+  }
+
+  /**
+   * Write the serialized response out to stdout. This is a very unusual thing
+   * to do, but it allows us to create a static file version of the response
+   * without deploying a servlet.
+   */
+  @Override
+  protected void onAfterResponseSerialized(String serializedResponse) {
+    System.out.println(serializedResponse);
+  }
+
+  private void generateRandomPeople() {
+    for (int i = 0; i < MAX_PEOPLE; ++i) {
+      Person person = generateRandomPerson();
+      people.add(person);
+    }
+  }
+
+  private Person generateRandomPerson() {
+    // 1 out of every so many people is a prof.
+    //
+    if (rnd.nextInt(STUDENTS_PER_PROF) == 1) {
+      return generateRandomProfessor();
+    } else {
+      return generateRandomStudent();
+    }
+  }
+
+  private Person generateRandomProfessor() {
+    Professor prof = new Professor();
+
+    String firstName = pickRandomString(FIRST_NAMES);
+    String lastName = pickRandomString(LAST_NAMES);
+    prof.setName("Dr. " + firstName + " " + lastName);
+
+    String subject = pickRandomString(SUBJECTS);
+    prof.setDescription("Professor of " + subject);
+
+    generateRandomSchedule(prof.getTeachingSchedule());
+
+    return prof;
+  }
+
+  private void generateRandomSchedule(Schedule sched) {
+    int range = MAX_SCHED_ENTRIES - MIN_SCHED_ENTRIES + 1;
+    int howMany = MIN_SCHED_ENTRIES + rnd.nextInt(range);
+
+    TimeSlot[] timeSlots = new TimeSlot[howMany];
+
+    for (int i = 0; i < howMany; ++i) {
+      int startHrs = 8 + rnd.nextInt(9); // 8 am - 5 pm
+      int startMins = 15 * rnd.nextInt(4); // on the hour or some quarter
+      int dayOfWeek = 1 + rnd.nextInt(5); // Mon - Fri
+
+      int absStartMins = 60 * startHrs + startMins; // convert to minutes
+      int absStopMins = absStartMins + CLASS_LENGTH_MINS;
+
+      timeSlots[i] = new TimeSlot(dayOfWeek, absStartMins, absStopMins);
+    }
+
+    Arrays.sort(timeSlots);
+
+    for (int i = 0; i < howMany; ++i) {
+      sched.addTimeSlot(timeSlots[i]);
+    }
+  }
+
+  private Person generateRandomStudent() {
+    Student student = new Student();
+
+    String firstName = pickRandomString(FIRST_NAMES);
+    String lastName = pickRandomString(LAST_NAMES);
+    student.setName(firstName + " " + lastName);
+
+    String subject = pickRandomString(SUBJECTS);
+    student.setDescription("Majoring in " + subject);
+
+    generateRandomSchedule(student.getClassSchedule());
+
+    return student;
+  }
+
+  private String pickRandomString(String[] a) {
+    int i = rnd.nextInt(a.length);
+    return a[i];
+  }
+}
diff --git a/user/build.xml b/user/build.xml
index 6bc6113..0539253 100755
--- a/user/build.xml
+++ b/user/build.xml
@@ -78,7 +78,7 @@
 
   <target name="remoteweb-test" description="Run a remoteweb test at the given host and path" if="gwt.remote.browsers">
     <echo message="Performing remote browser testing at ${gwt.remote.browsers}" />
-    <gwt.junit test.args="${test.args} -out www -workDir ${junit.out}/remoteweb/workdir -remoteweb ${gwt.remote.browsers}" test.out="${junit.out}/remoteweb" test.cases="default.web.tests" >
+    <gwt.junit test.args="${test.args} -out www -remoteweb ${gwt.remote.browsers}" test.out="${junit.out}/remoteweb" test.cases="default.web.tests" >
       <extraclasspaths>
         <pathelement location="${gwt.build}/out/dev/core/bin-test" />
       </extraclasspaths>
@@ -87,7 +87,7 @@
 
   <target name="selenium-test" description="Run a remote test using Selenium RC test at the given host and path" if="gwt.selenium.hosts">
     <echo message="Performing remote browser testing using Selenium RC at ${gwt.selenium.hosts}" />
-    <gwt.junit test.args="${test.args} -out www -workDir ${junit.out}/selenium/workdir -selenium ${gwt.selenium.hosts}" test.out="${junit.out}/selenium" test.cases="default.web.tests" >
+    <gwt.junit test.args="${test.args} -out www -selenium ${gwt.selenium.hosts}" test.out="${junit.out}/selenium" test.cases="default.web.tests" >
       <extraclasspaths>
         <pathelement location="${gwt.build}/out/dev/core/bin-test" />
       </extraclasspaths>
@@ -95,7 +95,7 @@
   </target>
 
   <target name="test.hosted" depends="compile, compile.tests" description="Run only hosted-mode tests for this project.">
-    <gwt.junit test.args="${test.args} -workDir ${junit.out}/${build.host.platform}-hosted-mode/workdir" test.out="${junit.out}/${build.host.platform}-hosted-mode" test.cases="default.hosted.tests" >
+    <gwt.junit test.args="${test.args}" test.out="${junit.out}/${build.host.platform}-hosted-mode" test.cases="default.hosted.tests" >
       <extraclasspaths>
         <pathelement location="${gwt.build}/out/dev/core/bin-test" />
       </extraclasspaths>
@@ -103,7 +103,7 @@
   </target>
 
   <target name="test.web" depends="compile, compile.tests" description="Run only web-mode tests for this project.">
-    <gwt.junit test.args="${test.args} -out www -workDir ${junit.out}/${build.host.platform}-web-mode/workdir -web" test.out="${junit.out}/${build.host.platform}-web-mode" test.cases="default.web.tests" >
+    <gwt.junit test.args="${test.args} -out www -web" test.out="${junit.out}/${build.host.platform}-web-mode" test.cases="default.web.tests" >
       <extraclasspaths>
         <pathelement location="${gwt.build}/out/dev/core/bin-test" />
       </extraclasspaths>
diff --git a/user/src/com/google/gwt/junit/JUnitShell.java b/user/src/com/google/gwt/junit/JUnitShell.java
index cd21132..a5141a3 100644
--- a/user/src/com/google/gwt/junit/JUnitShell.java
+++ b/user/src/com/google/gwt/junit/JUnitShell.java
@@ -90,6 +90,189 @@
     void processResult(TestCase testCase, JUnitResult result);
   }
 
+  class ArgProcessor extends GWTShell.ArgProcessor {
+
+    public ArgProcessor() {
+      super(true, true);
+      registerHandler(new ArgHandlerFlag() {
+
+        @Override
+        public String getPurpose() {
+          return "Causes your test to run in web (compiled) mode (defaults to hosted mode)";
+        }
+
+        @Override
+        public String getTag() {
+          return "-web";
+        }
+
+        @Override
+        public boolean setFlag() {
+          runStyle = new RunStyleLocalWeb(JUnitShell.this);
+          numClients = 1;
+          return true;
+        }
+
+      });
+
+      registerHandler(new ArgHandlerString() {
+
+        @Override
+        public String getPurpose() {
+          return "Runs web mode via RMI to a set of BrowserManagerServers; "
+              + "e.g. rmi://localhost/ie6,rmi://localhost/firefox";
+        }
+
+        @Override
+        public String getTag() {
+          return "-remoteweb";
+        }
+
+        @Override
+        public String[] getTagArgs() {
+          return new String[] {"rmiUrl"};
+        }
+
+        @Override
+        public boolean isUndocumented() {
+          return true;
+        }
+
+        @Override
+        public boolean setString(String str) {
+          String[] urls = str.split(",");
+          runStyle = RunStyleRemoteWeb.create(JUnitShell.this, urls);
+          numClients = urls.length;
+          return runStyle != null;
+        }
+      });
+
+      registerHandler(new ArgHandlerString() {
+
+        @Override
+        public String getPurpose() {
+          return "Runs web mode via HTTP to a set of Selenium servers; "
+              + "e.g. localhost:4444/*firefox,remotehost:4444/*iexplore";
+        }
+
+        @Override
+        public String getTag() {
+          return "-selenium";
+        }
+
+        @Override
+        public String[] getTagArgs() {
+          return new String[] {"seleniumHost"};
+        }
+
+        @Override
+        public boolean setString(String str) {
+          String[] targets = str.split(",");
+          numClients = targets.length;
+          runStyle = RunStyleSelenium.create(JUnitShell.this, targets);
+          return runStyle != null;
+        }
+      });
+
+      registerHandler(new ArgHandlerString() {
+
+        @Override
+        public String getPurpose() {
+          return "Run external browsers in web mode (pass a comma separated list of executables.)";
+        }
+
+        @Override
+        public String getTag() {
+          return "-externalbrowser";
+        }
+
+        @Override
+        public String[] getTagArgs() {
+          return new String[] {"browserPaths"};
+        }
+
+        @Override
+        public boolean isUndocumented() {
+          return true;
+        }
+
+        @Override
+        public boolean setString(String str) {
+          String[] paths = str.split(",");
+          runStyle = new RunStyleExternalBrowser(JUnitShell.this, paths);
+          numClients = paths.length;
+          return runStyle != null;
+        }
+      });
+
+      registerHandler(new ArgHandler() {
+
+        @Override
+        public String[] getDefaultArgs() {
+          return null;
+        }
+
+        @Override
+        public String getPurpose() {
+          return "Causes the system to wait for a remote browser to connect";
+        }
+
+        @Override
+        public String getTag() {
+          return "-manual";
+        }
+
+        @Override
+        public String[] getTagArgs() {
+          return new String[] {"[numClients]"};
+        }
+
+        @Override
+        public int handle(String[] args, int tagIndex) {
+          int value = 1;
+          if (tagIndex + 1 < args.length) {
+            try {
+              // See if the next item is an integer.
+              value = Integer.parseInt(args[tagIndex + 1]);
+              if (value >= 1) {
+                setInt(value);
+                return 1;
+              }
+            } catch (NumberFormatException e) {
+              // fall-through
+            }
+          }
+          setInt(1);
+          return 0;
+        }
+
+        public void setInt(int value) {
+          runStyle = new RunStyleManual(JUnitShell.this, value);
+          numClients = value;
+        }
+
+      });
+
+      registerHandler(new ArgHandlerFlag() {
+        @Override
+        public String getPurpose() {
+          return "Causes the log window and browser windows to be displayed; useful for debugging";
+        }
+
+        @Override
+        public String getTag() {
+          return "-notHeadless";
+        }
+
+        @Override
+        public boolean setFlag() {
+          setHeadless(false);
+          return true;
+        }
+      });
+    }
+  }
+
   private static class JUnitStrategy implements Strategy {
     public String getModuleInherit() {
       return "com.google.gwt.junit.JUnit";
@@ -204,8 +387,8 @@
       unitTestShell = new JUnitShell();
       unitTestShell.lastLaunchFailed = true;
       String[] args = unitTestShell.synthesizeArgs();
-      if (!unitTestShell.processArgs(args)) {
-
+      ArgProcessor argProcessor = unitTestShell.new ArgProcessor();
+      if (!argProcessor.processArgs(args)) {
         throw new JUnitFatalLaunchException("Error processing shell arguments");
       }
 
@@ -215,6 +398,11 @@
       if (!unitTestShell.startUp()) {
         throw new JUnitFatalLaunchException("Shell failed to start");
       }
+      Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+        public void run() {
+          unitTestShell.shutDown();
+        }
+      }));
       unitTestShell.lastLaunchFailed = false;
     }
 
@@ -242,6 +430,16 @@
   private boolean lastLaunchFailed;
 
   /**
+   * We need to keep a hard reference to the last module that was launched until
+   * all client browsers have successfully transitioned to the current module.
+   * Failure to do so allows the last module to be GC'd, which transitively
+   * kills the {@link com.google.gwt.junit.server.JUnitHostImpl} servlet. If the
+   * servlet dies, the client browsers will be unable to transition.
+   */
+  @SuppressWarnings("unused")
+  private ModuleDef lastModule;
+
+  /**
    * Portal to interact with the servlet.
    */
   private JUnitMessageQueue messageQueue;
@@ -270,200 +468,10 @@
   private long testBeginTimeout;
 
   /**
-   * We need to keep a hard reference to the last module that was launched until
-   * all client browsers have successfully transitioned to the current module.
-   * Failure to do so allows the last module to be GC'd, which transitively
-   * kills the {@link com.google.gwt.junit.server.JUnitHostImpl} servlet. If the
-   * servlet dies, the client browsers will be unable to transition.
-   */
-  @SuppressWarnings("unused")
-  private ModuleDef lastModule;
-
-  /**
    * Enforce the singleton pattern. The call to {@link GWTShell}'s ctor forces
    * server mode and disables processing extra arguments as URLs to be shown.
    */
   private JUnitShell() {
-    super(true, true);
-
-    registerHandler(new ArgHandlerFlag() {
-
-      @Override
-      public String getPurpose() {
-        return "Causes your test to run in web (compiled) mode (defaults to hosted mode)";
-      }
-
-      @Override
-      public String getTag() {
-        return "-web";
-      }
-
-      @Override
-      public boolean setFlag() {
-        runStyle = new RunStyleLocalWeb(JUnitShell.this);
-        numClients = 1;
-        return true;
-      }
-
-    });
-
-    registerHandler(new ArgHandlerString() {
-
-      @Override
-      public String getPurpose() {
-        return "Runs web mode via RMI to a set of BrowserManagerServers; "
-            + "e.g. rmi://localhost/ie6,rmi://localhost/firefox";
-      }
-
-      @Override
-      public String getTag() {
-        return "-remoteweb";
-      }
-
-      @Override
-      public String[] getTagArgs() {
-        return new String[] {"rmiUrl"};
-      }
-
-      @Override
-      public boolean isUndocumented() {
-        return true;
-      }
-
-      @Override
-      public boolean setString(String str) {
-        String[] urls = str.split(",");
-        runStyle = RunStyleRemoteWeb.create(JUnitShell.this, urls);
-        numClients = urls.length;
-        return runStyle != null;
-      }
-    });
-
-    registerHandler(new ArgHandlerString() {
-
-      @Override
-      public String getPurpose() {
-        return "Runs web mode via HTTP to a set of Selenium servers; "
-            + "e.g. localhost:4444/*firefox,remotehost:4444/*iexplore";
-      }
-
-      @Override
-      public String getTag() {
-        return "-selenium";
-      }
-
-      @Override
-      public String[] getTagArgs() {
-        return new String[] {"seleniumHost"};
-      }
-
-      @Override
-      public boolean setString(String str) {
-        String[] targets = str.split(",");
-        numClients = targets.length;
-        runStyle = RunStyleSelenium.create(JUnitShell.this, targets);
-        return runStyle != null;
-      }
-    });
-
-    registerHandler(new ArgHandlerString() {
-
-      @Override
-      public String getPurpose() {
-        return "Run external browsers in web mode (pass a comma separated list of executables.)";
-      }
-
-      @Override
-      public String getTag() {
-        return "-externalbrowser";
-      }
-
-      @Override
-      public String[] getTagArgs() {
-        return new String[] {"browserPaths"};
-      }
-
-      @Override
-      public boolean isUndocumented() {
-        return true;
-      }
-
-      @Override
-      public boolean setString(String str) {
-        String[] paths = str.split(",");
-        runStyle = new RunStyleExternalBrowser(JUnitShell.this, paths);
-        numClients = paths.length;
-        return runStyle != null;
-      }
-    });
-
-    registerHandler(new ArgHandler() {
-
-      @Override
-      public String[] getDefaultArgs() {
-        return null;
-      }
-
-      @Override
-      public String getPurpose() {
-        return "Causes the system to wait for a remote browser to connect";
-      }
-
-      @Override
-      public String getTag() {
-        return "-manual";
-      }
-
-      @Override
-      public String[] getTagArgs() {
-        return new String[] {"[numClients]"};
-      }
-
-      @Override
-      public int handle(String[] args, int tagIndex) {
-        int value = 1;
-        if (tagIndex + 1 < args.length) {
-          try {
-            // See if the next item is an integer.
-            value = Integer.parseInt(args[tagIndex + 1]);
-            if (value >= 1) {
-              setInt(value);
-              return 1;
-            }
-          } catch (NumberFormatException e) {
-            // fall-through
-          }
-        }
-        setInt(1);
-        return 0;
-      }
-
-      public void setInt(int value) {
-        runStyle = new RunStyleManual(JUnitShell.this, value);
-        numClients = value;
-      }
-
-    });
-
-    registerHandler(new ArgHandlerFlag() {
-
-      @Override
-      public String getPurpose() {
-        return "Causes the log window and browser windows to be displayed; useful for debugging";
-      }
-
-      @Override
-      public String getTag() {
-        return "-notHeadless";
-      }
-
-      @Override
-      public boolean setFlag() {
-        setHeadless(false);
-        return true;
-      }
-    });
-
     setRunTomcat(true);
     setHeadless(true);