Revert GWTShell and GWTCompiler change due to internal breakage.

Review by: andrewwarner@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@11409 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/build.xml b/dev/build.xml
index 08677ef..0ea5528 100755
--- a/dev/build.xml
+++ b/dev/build.xml
@@ -34,6 +34,7 @@
       excludes="**/super/**">
       <classpath>
         <pathelement location="${javac.out}" />
+        <pathelement location="${gwt.tools.lib}/tomcat/servlet-api-2.5.jar" />
         <pathelement location="${gwt.tools.lib}/junit/junit-4.8.2.jar" />
         <pathelement location="${gwt.tools.lib}/jfreechart/jfreechart-1.0.3.jar" />
         <pathelement location="${gwt.tools.lib}/selenium/selenium-java-client-driver.jar" />
@@ -62,13 +63,33 @@
           <include name="jetty/jetty-6.1.11.jar" />
           <include name="icu4j/4.4.2/icu4j.jar" />
           <include name="protobuf/protobuf-2.2.0/protobuf-java-rebased-2.2.0.jar" />
-          <!-- dependencies needed for JSP support in DevMode: BEGIN -->
+          <include name="tomcat/ant-launcher-1.6.5.jar" />
+          <include name="tomcat/catalina-1.0.jar" />
+          <include name="tomcat/catalina-optional-1.0.jar" />
+          <include name="tomcat/commons-beanutils-1.6.jar" />
+          <include name="tomcat/commons-collections-3.1.jar" />
+          <include name="tomcat/commons-digester-1.5.jar" />
           <include name="tomcat/commons-el-1.0.jar" />
+          <include name="tomcat/commons-logging-1.0.jar" />
+          <include name="tomcat/commons-modeler-1.1.jar" />
+          <include name="tomcat/jakarta-regexp-1.3.jar" />
           <include name="tomcat/jasper-compiler-1.0.jar" />
           <include name="tomcat/jasper-runtime-1.0.jar" />
           <include name="tomcat/jsp-api-2.0.jar" />
-          <!-- dependencies needed for JSP support in DevMode: END -->
-          <include name="apache/commons/commons-collections-3.2.1.jar" />
+          <include name="tomcat/mx4j-jmx-1.1.jar" />
+          <include name="tomcat/naming-common-1.0.jar" />
+          <include name="tomcat/naming-factory-1.0.jar" />
+          <include name="tomcat/naming-java-1.0.jar" />
+          <include name="tomcat/naming-resources-1.0.jar" />
+          <include name="tomcat/servlet-api-2.5.jar" />
+          <include name="tomcat/servlet-api-2.4.jar" />
+          <include name="tomcat/servlets-common-1.0.jar" />
+          <include name="tomcat/servlets-default-1.0.jar" />
+          <include name="tomcat/servlets-invoker-1.0.jar" />
+          <include name="tomcat/tomcat-coyote-1.0.jar" />
+          <include name="tomcat/tomcat-http11-1.0.jar" />
+          <include name="tomcat/tomcat-jk2-2.1.jar" />
+          <include name="tomcat/tomcat-util-5.1.jar" />
           <!-- htmlunit dependencies not already included: BEGIN -->
           <include name="apache/http/httpclient-4.1.2.jar" />
           <include name="apache/http/httpcore-4.1.2.jar" />
@@ -110,13 +131,33 @@
           <zipfileset src="${gwt.tools.lib}/jetty/jetty-6.1.11.jar" />
           <zipfileset src="${gwt.tools.lib}/icu4j/4.4.2/icu4j.jar" />
           <zipfileset src="${gwt.tools.lib}/protobuf/protobuf-2.2.0/protobuf-java-rebased-2.2.0.jar" />
-          <!-- dependencies needed for JSP support in DevMode: BEGIN -->
+          <zipfileset src="${gwt.tools.lib}/tomcat/ant-launcher-1.6.5.jar" />
+          <zipfileset src="${gwt.tools.lib}/tomcat/catalina-1.0.jar" />
+          <zipfileset src="${gwt.tools.lib}/tomcat/catalina-optional-1.0.jar" />
+          <zipfileset src="${gwt.tools.lib}/tomcat/commons-beanutils-1.6.jar" />
+          <zipfileset src="${gwt.tools.lib}/tomcat/commons-collections-3.1.jar" />
+          <zipfileset src="${gwt.tools.lib}/tomcat/commons-digester-1.5.jar" />
           <zipfileset src="${gwt.tools.lib}/tomcat/commons-el-1.0.jar" />
+          <zipfileset src="${gwt.tools.lib}/tomcat/commons-logging-1.0.jar" />
+          <zipfileset src="${gwt.tools.lib}/tomcat/commons-modeler-1.1.jar" />
+          <zipfileset src="${gwt.tools.lib}/tomcat/jakarta-regexp-1.3.jar" />
           <zipfileset src="${gwt.tools.lib}/tomcat/jasper-compiler-1.0.jar" />
           <zipfileset src="${gwt.tools.lib}/tomcat/jasper-runtime-1.0.jar" />
           <zipfileset src="${gwt.tools.lib}/tomcat/jsp-api-2.0.jar" />
-          <!-- dependencies needed for JSP support in DevMode: END -->
-          <zipfileset src="${gwt.tools.lib}/apache/commons/commons-collections-3.2.1.jar" />
+          <zipfileset src="${gwt.tools.lib}/tomcat/mx4j-jmx-1.1.jar" />
+          <zipfileset src="${gwt.tools.lib}/tomcat/naming-common-1.0.jar" />
+          <zipfileset src="${gwt.tools.lib}/tomcat/naming-factory-1.0.jar" />
+          <zipfileset src="${gwt.tools.lib}/tomcat/naming-java-1.0.jar" />
+          <zipfileset src="${gwt.tools.lib}/tomcat/naming-resources-1.0.jar" />
+          <zipfileset src="${gwt.tools.lib}/tomcat/servlet-api-2.5.jar" />
+          <zipfileset src="${gwt.tools.lib}/tomcat/servlet-api-2.4.jar" />
+          <zipfileset src="${gwt.tools.lib}/tomcat/servlets-common-1.0.jar" />
+          <zipfileset src="${gwt.tools.lib}/tomcat/servlets-default-1.0.jar" />
+          <zipfileset src="${gwt.tools.lib}/tomcat/servlets-invoker-1.0.jar" />
+          <zipfileset src="${gwt.tools.lib}/tomcat/tomcat-coyote-1.0.jar" />
+          <zipfileset src="${gwt.tools.lib}/tomcat/tomcat-http11-1.0.jar" />
+          <zipfileset src="${gwt.tools.lib}/tomcat/tomcat-jk2-2.1.jar" />
+          <zipfileset src="${gwt.tools.lib}/tomcat/tomcat-util-5.1.jar" />
           <!-- htmlunit dependencies not already included: BEGIN -->
           <zipfileset src="${gwt.tools.lib}/apache/http/httpclient-4.1.2.jar" />
           <zipfileset src="${gwt.tools.lib}/apache/http/httpcore-4.1.2.jar" />
@@ -179,7 +220,7 @@
       <classpath>
           <pathelement location="${gwt.tools.lib}/apache/ant-1.6.5.jar" />
           <pathelement location="${gwt.tools.lib}/eclipse/jdt-3.4.2_r894.jar" />
-          <pathelement location="${gwt.tools.lib}/apache/commons/commons-collections-3.2.1.jar" />
+          <pathelement location="${gwt.tools.lib}/tomcat/commons-collections-3.1.jar" />
           <pathelement location="${gwt.tools.lib}/guava/guava-10.0.1/guava-10.0.1-rebased.jar" />
           <pathelement location="${gwt.tools.lib}/jscomp/r1649/compiler-rebased.jar" />
       </classpath>
diff --git a/dev/core/src/com/google/gwt/dev/CompileTaskRunner.java b/dev/core/src/com/google/gwt/dev/CompileTaskRunner.java
index 914713d..7c65f6e 100644
--- a/dev/core/src/com/google/gwt/dev/CompileTaskRunner.java
+++ b/dev/core/src/com/google/gwt/dev/CompileTaskRunner.java
@@ -63,7 +63,7 @@
         }
       });
 
-      compilerThread.setName("GWT Compiler Thread");
+      compilerThread.setName("GWTCompiler Thread");
       compilerThread.start();
       // TODO(jat): create an app frame for loggerWindow
       
diff --git a/dev/core/src/com/google/gwt/dev/GWTCompiler.java b/dev/core/src/com/google/gwt/dev/GWTCompiler.java
new file mode 100644
index 0000000..ef2e4ea
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/GWTCompiler.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.linker.ArtifactSet;
+import com.google.gwt.dev.CompileTaskRunner.CompileTask;
+import com.google.gwt.dev.cfg.ModuleDef;
+import com.google.gwt.dev.cfg.ModuleDefLoader;
+import com.google.gwt.dev.jjs.JJSOptions;
+import com.google.gwt.dev.jjs.PermutationResult;
+import com.google.gwt.dev.shell.CheckForUpdates;
+import com.google.gwt.dev.shell.CheckForUpdates.UpdateResult;
+import com.google.gwt.dev.util.FileBackedObject;
+import com.google.gwt.dev.util.Util;
+import com.google.gwt.dev.util.arg.ArgHandlerLocalWorkers;
+import com.google.gwt.dev.util.arg.ArgHandlerOutDir;
+import com.google.gwt.dev.util.arg.ArgHandlerWorkDirOptional;
+import com.google.gwt.dev.util.log.speedtracer.CompilerEventType;
+import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
+import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
+import com.google.gwt.util.tools.ToolBase;
+import com.google.gwt.util.tools.Utility;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.FutureTask;
+
+/**
+ * The main executable entry point for the GWT Java to JavaScript compiler.
+ * 
+ * @deprecated Use {@link Compiler} instead
+ */
+@Deprecated
+public class GWTCompiler {
+
+  static final class ArgProcessor extends PrecompileTaskArgProcessor {
+    public ArgProcessor(LegacyCompilerOptions options) {
+      super(options);
+
+      registerHandler(new ArgHandlerOutDir(options));
+
+      // Override the ArgHandlerWorkDirRequired in the super class.
+      registerHandler(new ArgHandlerWorkDirOptional(options));
+
+      registerHandler(new ArgHandlerLocalWorkers(options));
+    }
+
+    @Override
+    protected String getName() {
+      return GWTCompiler.class.getName();
+    }
+  }
+
+  /**
+   * Simple implementation of {@link LegacyCompilerOptions}.
+   */
+  public static class GWTCompilerOptionsImpl extends PrecompileTaskOptionsImpl
+      implements LegacyCompilerOptions {
+
+    private int localWorkers;
+    private File outDir;
+
+    public GWTCompilerOptionsImpl() {
+    }
+
+    public GWTCompilerOptionsImpl(LegacyCompilerOptions other) {
+      copyFrom(other);
+    }
+
+    public void copyFrom(LegacyCompilerOptions other) {
+      super.copyFrom(other);
+      setLocalWorkers(other.getLocalWorkers());
+      setOutDir(other.getOutDir());
+    }
+
+    @Override
+    public int getLocalWorkers() {
+      return localWorkers;
+    }
+
+    @Override
+    public File getOutDir() {
+      return outDir;
+    }
+
+    @Override
+    public void setLocalWorkers(int localWorkers) {
+      this.localWorkers = localWorkers;
+    }
+
+    @Override
+    public void setOutDir(File outDir) {
+      this.outDir = outDir;
+    }
+  }
+
+  public static void main(String[] args) {
+    ToolBase.legacyWarn(GWTCompiler.class, Compiler.class);
+    SpeedTracerLogger.init();
+    Event compileEvent = SpeedTracerLogger.start(CompilerEventType.COMPILE);
+
+    /*
+     * 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 LegacyCompilerOptions options = new GWTCompilerOptionsImpl();
+    boolean success = false;
+    if (new ArgProcessor(options).processArgs(args)) {
+      CompileTask task = new CompileTask() {
+        @Override
+        public boolean run(TreeLogger logger) throws UnableToCompleteException {
+          FutureTask<UpdateResult> updater = null;
+          if (!options.isUpdateCheckDisabled()) {
+            updater = CheckForUpdates.checkForUpdatesInBackgroundThread(logger,
+                CheckForUpdates.ONE_DAY);
+          }
+          boolean success = new GWTCompiler(options).run(logger);
+          if (success) {
+            CheckForUpdates.logUpdateAvailable(logger, updater);
+          }
+          return success;
+        }
+      };
+      if (CompileTaskRunner.runWithAppropriateLogger(options, task)) {
+        // Exit w/ success code.
+        success = true;
+      }
+    }
+
+    compileEvent.end();
+    // Exit w/ non-success code.
+    System.exit(success ? 0 : 1);
+  }
+
+  private final GWTCompilerOptionsImpl options;
+
+  public GWTCompiler(LegacyCompilerOptions options) {
+    this.options = new GWTCompilerOptionsImpl(options);
+  }
+
+  /**
+   * Compiles the set of modules specified in the options.
+   */
+  public boolean run(TreeLogger logger) throws UnableToCompleteException {
+    ModuleDef[] modules = new ModuleDef[options.getModuleNames().size()];
+    int i = 0;
+    for (String moduleName : options.getModuleNames()) {
+      modules[i++] = ModuleDefLoader.loadFromClassPath(logger, moduleName, true);
+    }
+    return run(logger, modules);
+  }
+
+  /**
+   * Compiles a specific set of modules.
+   */
+  public boolean run(TreeLogger logger, ModuleDef... modules)
+      throws UnableToCompleteException {
+    Event compileEvent = SpeedTracerLogger.start(CompilerEventType.COMPILE);
+    boolean tempWorkDir = false;
+    try {
+      if (options.getWorkDir() == null) {
+        options.setWorkDir(Utility.makeTemporaryDirectory(null, "gwtc"));
+        tempWorkDir = true;
+      }
+
+      for (ModuleDef module : modules) {
+        String moduleName = module.getName();
+
+        if (options.isValidateOnly()) {
+          if (!Precompile.validate(logger, options, module, options.getGenDir())) {
+            return false;
+          }
+        } else {
+          long compileStart = System.currentTimeMillis();
+          logger = logger.branch(TreeLogger.INFO, "Compiling module "
+              + moduleName);
+
+          // Optimize early since permutation compiles will run in process.
+          options.setOptimizePrecompile(true);
+          Precompilation precompilation = Precompile.precompile(logger,
+              options, module, options.getGenDir());
+
+          if (precompilation == null) {
+            return false;
+          }
+          Permutation[] allPerms = precompilation.getPermutations();
+          List<FileBackedObject<PermutationResult>> resultFiles = CompilePerms.makeResultFiles(
+              options.getCompilerWorkDir(moduleName), allPerms);
+          CompilePerms.compile(logger, precompilation, allPerms,
+              options.getLocalWorkers(), resultFiles);
+
+          ArtifactSet generatedArtifacts = precompilation.getGeneratedArtifacts();
+          JJSOptions precompileOptions = precompilation.getUnifiedAst().getOptions();
+
+          precompilation = null; // No longer needed, so save the memory
+
+          Link.legacyLink(logger.branch(TreeLogger.TRACE, "Linking into "
+              + options.getOutDir().getPath()), module, generatedArtifacts,
+              allPerms, resultFiles, options.getOutDir(), precompileOptions);
+
+          long compileDone = System.currentTimeMillis();
+          long delta = compileDone - compileStart;
+          if (logger.isLoggable(TreeLogger.INFO)) {
+            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 {
+      compileEvent.end();
+      if (tempWorkDir) {
+        Util.recursiveDelete(options.getWorkDir(), false);
+      }
+    }
+    return true;
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/GWTShell.java b/dev/core/src/com/google/gwt/dev/GWTShell.java
new file mode 100644
index 0000000..77339ff
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/GWTShell.java
@@ -0,0 +1,252 @@
+/*
+ * 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.Visibility;
+import com.google.gwt.core.ext.linker.impl.StandardLinkerContext;
+import com.google.gwt.dev.cfg.ModuleDef;
+import com.google.gwt.dev.shell.WorkDirs;
+import com.google.gwt.dev.shell.tomcat.EmbeddedTomcatServer;
+import com.google.gwt.dev.util.OutputFileSetOnDirectory;
+import com.google.gwt.dev.util.arg.ArgHandlerDisableUpdateCheck;
+import com.google.gwt.dev.util.arg.ArgHandlerOutDir;
+import com.google.gwt.util.tools.ArgHandlerExtra;
+
+import java.io.File;
+import java.util.Locale;
+import java.util.Set;
+
+/**
+ * The main executable class for the hosted mode shell.
+ */
+@Deprecated
+public class GWTShell extends DevModeBase {
+
+  /**
+   * Handles the list of startup urls that can be passed at the end of the
+   * command line.
+   */
+  protected static class ArgHandlerStartupURLsExtra extends ArgHandlerExtra {
+
+    private final OptionStartupURLs options;
+
+    public ArgHandlerStartupURLsExtra(OptionStartupURLs options) {
+      this.options = options;
+    }
+
+    @Override
+    public boolean addExtraArg(String arg) {
+      options.addStartupURL(arg);
+      return true;
+    }
+
+    @Override
+    public String getPurpose() {
+      return "Automatically launches the specified URL";
+    }
+
+    @Override
+    public String[] getTagArgs() {
+      return new String[] {"url"};
+    }
+  }
+
+  /**
+   * The GWTShell argument processor.
+   */
+  protected static class ArgProcessor extends DevModeBase.ArgProcessor {
+    public ArgProcessor(ShellOptionsImpl options, boolean forceServer,
+        boolean noURLs) {
+      super(options, forceServer);
+      if (!noURLs) {
+        registerHandler(new ArgHandlerStartupURLsExtra(options));
+      }
+      registerHandler(new ArgHandlerOutDir(options));
+      registerHandler(new ArgHandlerDisableUpdateCheck(options));
+    }
+
+    @Override
+    protected String getName() {
+      return GWTShell.class.getName();
+    }
+  }
+
+  /**
+   * Concrete class to implement all shell options.
+   */
+  protected static class ShellOptionsImpl extends HostedModeBaseOptionsImpl
+      implements WorkDirs, LegacyCompilerOptions {
+    private int localWorkers;
+    private File outDir;
+
+    public File getCompilerOutputDir(ModuleDef moduleDef) {
+      return new File(getOutDir(), moduleDef.getName());
+    }
+
+    public int getLocalWorkers() {
+      return localWorkers;
+    }
+
+    public File getOutDir() {
+      return outDir;
+    }
+
+    public File getShellPublicGenDir(ModuleDef moduleDef) {
+      File moduleWorkDir = new File(getWorkDir(), moduleDef.getName());
+      return new File(moduleWorkDir, "public");
+    }
+
+    @Override
+    public File getWorkDir() {
+      return new File(getOutDir(), ".gwt-tmp");
+    }
+
+    public void setLocalWorkers(int localWorkers) {
+      this.localWorkers = localWorkers;
+    }
+
+    public void setOutDir(File outDir) {
+      this.outDir = outDir;
+    }
+  }
+
+  public static String checkHost(String hostUnderConsideration,
+      Set<String> hosts) {
+    hostUnderConsideration = hostUnderConsideration.toLowerCase(Locale.ENGLISH);
+    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();
+  }
+
+  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.
+     */
+    GWTShell gwtShell = new GWTShell();
+    ArgProcessor argProcessor = new ArgProcessor(gwtShell.options, false, false);
+    if (argProcessor.processArgs(args)) {
+      gwtShell.run();
+      // Exit w/ success code.
+      System.exit(0);
+    }
+    // Exit w/ non-success code.
+    System.exit(-1);
+  }
+
+  /**
+   * Hiding super field because it's actually the same object, just with a
+   * stronger type.
+   */
+  @SuppressWarnings("hiding")
+  protected final ShellOptionsImpl options = (ShellOptionsImpl) super.options;
+
+  protected File outDir;
+
+  @SuppressWarnings("unused")
+  public void restartServer(TreeLogger logger) throws UnableToCompleteException {
+    // Unimplemented.
+  }
+
+  @Override
+  protected HostedModeBaseOptions createOptions() {
+    return new ShellOptionsImpl();
+  }
+
+  @Override
+  protected void doShutDownServer() {
+    // Stop the HTTP server.
+    //
+    EmbeddedTomcatServer.stop();
+  }
+
+  @Override
+  protected boolean doStartup() {
+    File persistentCacheDir = new File(options.getWorkDir(), "gwt-unitCache");
+    return super.doStartup(persistentCacheDir);
+  }
+
+  @Override
+  protected int doStartUpServer() {
+    // TODO(jat): find a safe way to get an icon for Tomcat
+    TreeLogger logger = ui.getWebServerLogger("Tomcat", null);
+    // TODO(bruce): make tomcat work in terms of the modular launcher
+    String whyFailed = EmbeddedTomcatServer.start(isHeadless() ? getTopLogger()
+        : logger, getPort(), options, shouldAutoGenerateResources());
+
+    if (whyFailed != null) {
+      getTopLogger().log(TreeLogger.ERROR, "Starting Tomcat: " + whyFailed);
+      return -1;
+    }
+    return EmbeddedTomcatServer.getPort();
+  }
+
+  @Override
+  protected synchronized void produceOutput(TreeLogger logger,
+      StandardLinkerContext linkerStack, ArtifactSet artifacts,
+      ModuleDef module, boolean isRelink) throws UnableToCompleteException {
+    /*
+     * Legacy: in GWTShell we only copy generated artifacts into the public gen
+     * folder. Public files and "autogen" files have special handling (that
+     * needs to die).
+     */
+    if (isRelink) {
+      File outputDir = options.getShellPublicGenDir(module);
+      outputDir.mkdirs();
+      OutputFileSetOnDirectory outFileSet = new OutputFileSetOnDirectory(
+          outputDir, "");
+      linkerStack.produceOutput(logger, artifacts, Visibility.Public,
+          outFileSet);
+      outFileSet.close();
+    }
+  }
+
+  protected boolean shouldAutoGenerateResources() {
+    return true;
+  }
+
+  @Override
+  protected void warnAboutNoStartupUrls() {
+    getTopLogger().log(TreeLogger.WARN,
+        "No startup URLs were supplied -- add them to the end of the GWTShell"
+        + " command line");
+  }
+}
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 378ab1a..8d972e9 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java
@@ -466,9 +466,9 @@
   }
 
   /**
-   * For convenience in unit tests, servlets can be automatically loaded and
-   * mapped in the embedded web server. If a servlet is already mapped to the
-   * specified path, it is replaced.
+   * For convenience in hosted mode, servlets can be automatically loaded and
+   * delegated to via {@link com.google.gwt.dev.shell.GWTShellServlet}. If a
+   * servlet is already mapped to the specified path, it is replaced.
    * 
    * @param path the url path at which the servlet resides
    * @param servletClassName the name of the servlet to publish
diff --git a/dev/core/src/com/google/gwt/dev/etc/tomcat/conf/web.xml b/dev/core/src/com/google/gwt/dev/etc/tomcat/conf/web.xml
new file mode 100644
index 0000000..fee85f3
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/etc/tomcat/conf/web.xml
@@ -0,0 +1,566 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!-- A tweaked version of the default Tomcat web.xml to remove everything except the stuff we want to use -->
+<web-app version="2.4">
+
+    <session-config>
+        <session-timeout>30</session-timeout>
+    </session-config>
+
+    <mime-mapping>
+        <extension>abs</extension>
+        <mime-type>audio/x-mpeg</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>ai</extension>
+        <mime-type>application/postscript</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>aif</extension>
+        <mime-type>audio/x-aiff</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>aifc</extension>
+        <mime-type>audio/x-aiff</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>aiff</extension>
+        <mime-type>audio/x-aiff</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>aim</extension>
+        <mime-type>application/x-aim</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>art</extension>
+        <mime-type>image/x-jg</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>asf</extension>
+        <mime-type>video/x-ms-asf</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>asx</extension>
+        <mime-type>video/x-ms-asf</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>au</extension>
+        <mime-type>audio/basic</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>avi</extension>
+        <mime-type>video/x-msvideo</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>avx</extension>
+        <mime-type>video/x-rad-screenplay</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>bcpio</extension>
+        <mime-type>application/x-bcpio</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>bin</extension>
+        <mime-type>application/octet-stream</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>bmp</extension>
+        <mime-type>image/bmp</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>body</extension>
+        <mime-type>text/html</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>cdf</extension>
+        <mime-type>application/x-cdf</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>cer</extension>
+        <mime-type>application/x-x509-ca-cert</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>class</extension>
+        <mime-type>application/java</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>cpio</extension>
+        <mime-type>application/x-cpio</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>csh</extension>
+        <mime-type>application/x-csh</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>css</extension>
+        <mime-type>text/css</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>dib</extension>
+        <mime-type>image/bmp</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>doc</extension>
+        <mime-type>application/msword</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>dtd</extension>
+        <mime-type>text/plain</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>dv</extension>
+        <mime-type>video/x-dv</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>dvi</extension>
+        <mime-type>application/x-dvi</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>eps</extension>
+        <mime-type>application/postscript</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>etx</extension>
+        <mime-type>text/x-setext</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>exe</extension>
+        <mime-type>application/octet-stream</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>gif</extension>
+        <mime-type>image/gif</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>gtar</extension>
+        <mime-type>application/x-gtar</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>gz</extension>
+        <mime-type>application/x-gzip</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>hdf</extension>
+        <mime-type>application/x-hdf</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>hqx</extension>
+        <mime-type>application/mac-binhex40</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>htc</extension>
+        <mime-type>text/x-component</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>htm</extension>
+        <mime-type>text/html</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>html</extension>
+        <mime-type>text/html</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>hqx</extension>
+        <mime-type>application/mac-binhex40</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>ief</extension>
+        <mime-type>image/ief</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>jad</extension>
+        <mime-type>text/vnd.sun.j2me.app-descriptor</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>jar</extension>
+        <mime-type>application/java-archive</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>java</extension>
+        <mime-type>text/plain</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>jnlp</extension>
+        <mime-type>application/x-java-jnlp-file</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>jpe</extension>
+        <mime-type>image/jpeg</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>jpeg</extension>
+        <mime-type>image/jpeg</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>jpg</extension>
+        <mime-type>image/jpeg</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>js</extension>
+        <mime-type>text/javascript</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>jsf</extension>
+        <mime-type>text/plain</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>jspf</extension>
+        <mime-type>text/plain</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>kar</extension>
+        <mime-type>audio/x-midi</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>latex</extension>
+        <mime-type>application/x-latex</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>m3u</extension>
+        <mime-type>audio/x-mpegurl</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>mac</extension>
+        <mime-type>image/x-macpaint</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>man</extension>
+        <mime-type>application/x-troff-man</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>me</extension>
+        <mime-type>application/x-troff-me</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>mid</extension>
+        <mime-type>audio/x-midi</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>midi</extension>
+        <mime-type>audio/x-midi</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>mif</extension>
+        <mime-type>application/x-mif</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>mov</extension>
+        <mime-type>video/quicktime</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>movie</extension>
+        <mime-type>video/x-sgi-movie</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>mp1</extension>
+        <mime-type>audio/x-mpeg</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>mp2</extension>
+        <mime-type>audio/x-mpeg</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>mp3</extension>
+        <mime-type>audio/x-mpeg</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>mpa</extension>
+        <mime-type>audio/x-mpeg</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>mpe</extension>
+        <mime-type>video/mpeg</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>mpeg</extension>
+        <mime-type>video/mpeg</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>mpega</extension>
+        <mime-type>audio/x-mpeg</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>mpg</extension>
+        <mime-type>video/mpeg</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>mpv2</extension>
+        <mime-type>video/mpeg2</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>ms</extension>
+        <mime-type>application/x-wais-source</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>nc</extension>
+        <mime-type>application/x-netcdf</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>oda</extension>
+        <mime-type>application/oda</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>pbm</extension>
+        <mime-type>image/x-portable-bitmap</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>pct</extension>
+        <mime-type>image/pict</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>pdf</extension>
+        <mime-type>application/pdf</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>pgm</extension>
+        <mime-type>image/x-portable-graymap</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>pic</extension>
+        <mime-type>image/pict</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>pict</extension>
+        <mime-type>image/pict</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>pls</extension>
+        <mime-type>audio/x-scpls</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>png</extension>
+        <mime-type>image/png</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>pnm</extension>
+        <mime-type>image/x-portable-anymap</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>pnt</extension>
+        <mime-type>image/x-macpaint</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>ppm</extension>
+        <mime-type>image/x-portable-pixmap</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>ppt</extension>
+        <mime-type>application/powerpoint</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>ps</extension>
+        <mime-type>application/postscript</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>psd</extension>
+        <mime-type>image/x-photoshop</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>qt</extension>
+        <mime-type>video/quicktime</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>qti</extension>
+        <mime-type>image/x-quicktime</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>qtif</extension>
+        <mime-type>image/x-quicktime</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>ras</extension>
+        <mime-type>image/x-cmu-raster</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>rgb</extension>
+        <mime-type>image/x-rgb</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>rm</extension>
+        <mime-type>application/vnd.rn-realmedia</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>roff</extension>
+        <mime-type>application/x-troff</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>rtf</extension>
+        <mime-type>application/rtf</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>rtx</extension>
+        <mime-type>text/richtext</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>sh</extension>
+        <mime-type>application/x-sh</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>shar</extension>
+        <mime-type>application/x-shar</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>smf</extension>
+        <mime-type>audio/x-midi</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>sit</extension>
+        <mime-type>application/x-stuffit</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>snd</extension>
+        <mime-type>audio/basic</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>src</extension>
+        <mime-type>application/x-wais-source</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>sv4cpio</extension>
+        <mime-type>application/x-sv4cpio</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>sv4crc</extension>
+        <mime-type>application/x-sv4crc</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>swf</extension>
+        <mime-type>application/x-shockwave-flash</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>t</extension>
+        <mime-type>application/x-troff</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>tar</extension>
+        <mime-type>application/x-tar</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>tcl</extension>
+        <mime-type>application/x-tcl</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>tex</extension>
+        <mime-type>application/x-tex</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>texi</extension>
+        <mime-type>application/x-texinfo</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>texinfo</extension>
+        <mime-type>application/x-texinfo</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>tif</extension>
+        <mime-type>image/tiff</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>tiff</extension>
+        <mime-type>image/tiff</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>tr</extension>
+        <mime-type>application/x-troff</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>tsv</extension>
+        <mime-type>text/tab-separated-values</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>txt</extension>
+        <mime-type>text/plain</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>ulw</extension>
+        <mime-type>audio/basic</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>ustar</extension>
+        <mime-type>application/x-ustar</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>xbm</extension>
+        <mime-type>image/x-xbitmap</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>xht</extension>
+        <mime-type>application/xhtml+xml</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>xhtml</extension>
+        <mime-type>application/xhtml+xml</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>xml</extension>
+        <mime-type>text/xml</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>xpm</extension>
+        <mime-type>image/x-xpixmap</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>xsl</extension>
+        <mime-type>text/xml</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>xwd</extension>
+        <mime-type>image/x-xwindowdump</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>wav</extension>
+        <mime-type>audio/x-wav</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>svg</extension>
+        <mime-type>image/svg+xml</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>svgz</extension>
+        <mime-type>image/svg+xml</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>vsd</extension>
+        <mime-type>application/x-visio</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <!-- Wireless Bitmap -->
+        <extension>wbmp</extension>
+        <mime-type>image/vnd.wap.wbmp</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <!-- WML Source -->
+        <extension>wml</extension>
+        <mime-type>text/vnd.wap.wml</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <!-- Compiled WML -->
+        <extension>wmlc</extension>
+        <mime-type>application/vnd.wap.wmlc</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <!-- WML Script Source -->
+        <extension>wmls</extension>
+        <mime-type>text/vnd.wap.wmlscript</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <!-- Compiled WML Script -->
+        <extension>wmlscriptc</extension>
+        <mime-type>application/vnd.wap.wmlscriptc</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>wrl</extension>
+        <mime-type>x-world/x-vrml</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>Z</extension>
+        <mime-type>application/x-compress</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>z</extension>
+        <mime-type>application/x-compress</mime-type>
+    </mime-mapping>
+    <mime-mapping>
+        <extension>zip</extension>
+        <mime-type>application/zip</mime-type>
+    </mime-mapping>
+</web-app>
diff --git a/dev/core/src/com/google/gwt/dev/etc/tomcat/webapps/ROOT/WEB-INF/web.xml b/dev/core/src/com/google/gwt/dev/etc/tomcat/webapps/ROOT/WEB-INF/web.xml
new file mode 100644
index 0000000..e600b2f
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/etc/tomcat/webapps/ROOT/WEB-INF/web.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app>
+
+	<servlet>
+		<servlet-name>shell</servlet-name>
+		<servlet-class>com.google.gwt.dev.shell.GWTShellServlet</servlet-class>
+	</servlet>
+	
+	<servlet-mapping>
+		<servlet-name>shell</servlet-name>
+		<url-pattern>/*</url-pattern>
+	</servlet-mapping>
+
+</web-app>
diff --git a/dev/core/src/com/google/gwt/dev/shell/GWTShellServlet.java b/dev/core/src/com/google/gwt/dev/shell/GWTShellServlet.java
new file mode 100644
index 0000000..9e7ead7
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/shell/GWTShellServlet.java
@@ -0,0 +1,991 @@
+/*
+ * 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.HostedModeLinker;
+import com.google.gwt.core.ext.linker.impl.StandardLinkerContext;
+import com.google.gwt.dev.cfg.ModuleDef;
+import com.google.gwt.dev.cfg.ModuleDefLoader;
+import com.google.gwt.dev.jjs.JJSOptionsImpl;
+import com.google.gwt.dev.resource.Resource;
+import com.google.gwt.dev.shell.log.ServletContextTreeLogger;
+import com.google.gwt.dev.util.HttpHeaders;
+import com.google.gwt.dev.util.Util;
+import com.google.gwt.thirdparty.guava.common.collect.MapMaker;
+import com.google.gwt.util.tools.Utility;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Built-in servlet for convenient access to the public path of a specified
+ * module.
+ */
+public class GWTShellServlet extends HttpServlet {
+
+  private static class RequestParts {
+    public final String moduleName;
+
+    public final String partialPath;
+
+    public RequestParts(HttpServletRequest request)
+        throws UnableToCompleteException {
+      String pathInfo = request.getPathInfo();
+      if (pathInfo != null) {
+        int slash = pathInfo.indexOf('/', 1);
+        if (slash != -1) {
+          moduleName = pathInfo.substring(1, slash);
+          partialPath = pathInfo.substring(slash + 1);
+          return;
+        } else {
+          moduleName = pathInfo.substring(1);
+          partialPath = null;
+          return;
+        }
+      }
+      throw new UnableToCompleteException();
+    }
+  }
+
+  /**
+   * This the default cache time in seconds for files that aren't either
+   * *.cache.*, *.nocache.*.
+   */
+  private static final int DEFAULT_CACHE_SECONDS = 5;
+
+  private static final String XHTML_MIME_TYPE = "application/xhtml+xml";
+
+  /**
+   * Must keep only weak references to ModuleDefs else we permanently pin them.
+   */
+  private final Map<String, ModuleDef> loadedModulesByName = new MapMaker().weakValues().makeMap();
+
+  /**
+   * The lifetime of the module pins the lifetime of the associated servlet;
+   * this is because the loaded servlet has a weak backRef to its live module
+   * through its context. When the module dies, the servlet needs to die also.
+   */
+  private final Map<ModuleDef, Map<String, HttpServlet>> loadedServletsByModuleAndClassName =
+      new MapMaker().weakKeys().makeMap();
+
+  private final Map<String, String> mimeTypes = new HashMap<String, String>();
+
+  /**
+   * Only for backwards compatibility. Shouldn't we remove this now?
+   */
+  private final Map<String, ModuleDef> modulesByServletPath = new MapMaker().weakValues().makeMap();
+
+  private int nextRequestId;
+
+  private final Object requestIdLock = new Object();
+
+  private TreeLogger topLogger;
+
+  private WorkDirs workDirs;
+
+  public GWTShellServlet() {
+    initMimeTypes();
+  }
+
+  @Override
+  protected void doGet(HttpServletRequest request, HttpServletResponse response)
+      throws ServletException, IOException {
+    processFileRequest(request, response);
+  }
+
+  @Override
+  protected void doPost(HttpServletRequest request, HttpServletResponse response)
+      throws ServletException, IOException {
+    processFileRequest(request, response);
+  }
+
+  protected void processFileRequest(HttpServletRequest request,
+      HttpServletResponse response) throws IOException {
+
+    String pathInfo = request.getPathInfo();
+    if (pathInfo.length() == 0 || pathInfo.equals("/")) {
+      response.setContentType("text/html");
+      PrintWriter writer = response.getWriter();
+      writer.println("<html><body><basefont face='arial'>");
+      writer.println("To launch an application, specify a URL of the form <code>/<i>module</i>/<i>file.html</i></code>");
+      writer.println("</body></html>");
+      return;
+    }
+
+    TreeLogger logger = getLogger();
+
+    // Parse the request assuming it is module/resource.
+    //
+    RequestParts parts;
+    try {
+      parts = new RequestParts(request);
+    } catch (UnableToCompleteException e) {
+      sendErrorResponse(response, HttpServletResponse.SC_NOT_FOUND,
+          "Don't know what to do with this URL: '" + pathInfo + "'");
+      return;
+    }
+
+    String partialPath = parts.partialPath;
+    String moduleName = parts.moduleName;
+
+    // If the module is renamed, substitute the renamed module name
+    ModuleDef moduleDef = loadedModulesByName.get(moduleName);
+    if (moduleDef != null) {
+      moduleName = moduleDef.getName();
+    }
+
+    if (partialPath == null) {
+      // Redir back to the same URL but ending with a slash.
+      //
+      response.sendRedirect(moduleName + "/");
+      return;
+    } else if (partialPath.length() > 0) {
+      // Both the module name and a resource.
+      //
+      doGetPublicFile(request, response, logger, partialPath, moduleName);
+      return;
+    } else {
+      // Was just the module name, ending with a slash.
+      //
+      doGetModule(request, response, logger, parts);
+      return;
+    }
+  }
+
+  @Override
+  protected void service(HttpServletRequest request,
+      HttpServletResponse response) throws ServletException, IOException {
+
+    TreeLogger logger = getLogger();
+    int id = allocateRequestId();
+    if (logger.isLoggable(TreeLogger.TRACE)) {
+      StringBuffer url = request.getRequestURL();
+
+      // Branch the logger in case we decide to log more below.
+      logger = logger.branch(TreeLogger.TRACE, "Request " + id + ": " + url,
+          null);
+    }
+
+    String servletClassName = null;
+    ModuleDef moduleDef = null;
+
+    try {
+      // Attempt to split the URL into module/path, which we'll use to see
+      // if we can map the request to a module's servlet.
+      RequestParts parts = new RequestParts(request);
+
+      if ("favicon.ico".equalsIgnoreCase(parts.moduleName)) {
+        sendErrorResponse(response, HttpServletResponse.SC_NOT_FOUND,
+            "Icon not available");
+        return;
+      }
+
+      // See if the request references a module we know.
+      moduleDef = getModuleDef(logger, parts.moduleName);
+      if (moduleDef != null) {
+        // Okay, we know this module. Do we know this servlet path?
+        // It is right to prepend the slash because (1) ModuleDefSchema requires
+        // every servlet path to begin with a slash and (2) RequestParts always
+        // rips off the leading slash.
+        String servletPath = "/" + parts.partialPath;
+        servletClassName = moduleDef.findServletForPath(servletPath);
+
+        // Fall-through below, where we check servletClassName.
+      } else {
+        // Fall-through below, where we check servletClassName.
+      }
+    } catch (UnableToCompleteException e) {
+      // Do nothing, since it was speculative anyway.
+    }
+
+    // BEGIN BACKWARD COMPATIBILITY
+    if (servletClassName == null) {
+      // Try to map a bare path that isn't preceded by the module name.
+      // This is no longer the recommended practice, so we warn.
+      String path = request.getPathInfo();
+      moduleDef = modulesByServletPath.get(path);
+      if (moduleDef != null) {
+        // See if there is a servlet we can delegate to for the given url.
+        servletClassName = moduleDef.findServletForPath(path);
+
+        if (servletClassName != null) {
+          TreeLogger branch = logger.branch(TreeLogger.WARN,
+              "Use of deprecated hosted mode servlet path mapping", null);
+          branch.log(
+              TreeLogger.WARN,
+              "The client code is invoking the servlet with a URL that is not module-relative: "
+                  + path, null);
+          branch.log(
+              TreeLogger.WARN,
+              "Prepend GWT.getModuleBaseURL() to the URL in client code to create a module-relative URL: /"
+                  + moduleDef.getName() + path, null);
+          branch.log(
+              TreeLogger.WARN,
+              "Using module-relative URLs ensures correct URL-independent behavior in external servlet containers",
+              null);
+        }
+
+        // Fall-through below, where we check servletClassName.
+      } else {
+        // Fall-through below, where we check servletClassName.
+      }
+    }
+    // END BACKWARD COMPATIBILITY
+
+    // Load/get the servlet if we found one.
+    if (servletClassName != null) {
+      HttpServlet delegatee = tryGetOrLoadServlet(logger, moduleDef,
+          servletClassName);
+      if (delegatee == null) {
+        logger.log(TreeLogger.ERROR, "Unable to dispatch request", null);
+        sendErrorResponse(response,
+            HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+            "Unable to find/load mapped servlet class '" + servletClassName
+                + "'");
+        return;
+      }
+
+      // Delegate everything to the downstream servlet and we're done.
+      delegatee.service(request, response);
+    } else {
+      // Use normal default processing on this request, since we couldn't
+      // recognize it as anything special.
+      super.service(request, response);
+    }
+  }
+
+  private int allocateRequestId() {
+    synchronized (requestIdLock) {
+      return nextRequestId++;
+    }
+  }
+
+  /**
+   * Handle auto-generated resources.
+   * 
+   * @return <code>true</code> if a resource was generated
+   */
+  private boolean autoGenerateResources(HttpServletRequest request,
+      HttpServletResponse response, TreeLogger logger, String partialPath,
+      String moduleName) throws IOException {
+
+    if (partialPath.equals(moduleName + ".nocache.js")) {
+      if (request.getParameter("compiled") == null) {
+        // Generate the .js file.
+        try {
+          String js = genSelectionScript(logger, moduleName);
+          setResponseCacheHeaders(response, 0); // do not cache selection script
+          response.setStatus(HttpServletResponse.SC_OK);
+          response.setContentType("text/javascript");
+          response.getWriter().println(js);
+          return true;
+        } 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.
+        }
+      }
+    } else if (partialPath.equals("hosted.html")) {
+      String html = HostedModeLinker.getHostedHtml();
+      setResponseCacheHeaders(response, DEFAULT_CACHE_SECONDS);
+      response.setStatus(HttpServletResponse.SC_OK);
+      response.setContentType("text/html");
+      response.getWriter().println(html);
+      return true;
+    }
+
+    return false;
+  }
+
+  private void doGetModule(HttpServletRequest request,
+      HttpServletResponse response, TreeLogger logger, RequestParts parts)
+      throws IOException {
+
+    // Generate a generic empty host page.
+    //
+    String msg = "The development shell servlet received a request to generate a host page for module '"
+        + parts.moduleName + "' ";
+
+    logger = logger.branch(TreeLogger.TRACE, msg, null);
+
+    try {
+      // Try to load the module just to make sure it'll work.
+      getModuleDef(logger, parts.moduleName);
+    } catch (UnableToCompleteException e) {
+      sendErrorResponse(response, HttpServletResponse.SC_NOT_FOUND,
+          "Unable to find/load module '" + Util.escapeXml(parts.moduleName)
+              + "' (see server log for details)");
+      return;
+    }
+
+    response.setContentType("text/html");
+    PrintWriter writer = response.getWriter();
+    writer.println("<html><head>");
+    writer.print("<script language='javascript' src='");
+    writer.print(parts.moduleName);
+    writer.println(".nocache.js'></script>");
+
+    // Create a property for each query param.
+    Map<String, String[]> params = getParameterMap(request);
+    for (Map.Entry<String, String[]> entry : params.entrySet()) {
+      String[] values = entry.getValue();
+      if (values.length > 0) {
+        writer.print("<meta name='gwt:property' content='");
+        writer.print(entry.getKey());
+        writer.print("=");
+        writer.print(values[values.length - 1]);
+        writer.println("'>");
+      }
+    }
+
+    writer.println("</head><body>");
+    writer.println("<iframe src=\"javascript:''\" id='__gwt_historyFrame' "
+        + "style='position:absolute;width:0;height:0;border:0'></iframe>");
+    writer.println("<noscript>");
+    writer.println("  <div style=\"width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color: white; border: 1px solid red; padding: 4px; font-family: sans-serif\">");
+    writer.println("    Your web browser must have JavaScript enabled");
+    writer.println("    in order for this application to display correctly.");
+    writer.println("  </div>");
+    writer.println("</noscript>");
+    writer.println("</body></html>");
+
+    // Done.
+  }
+
+  /**
+   * Fetch a file and return it as the HTTP response, setting the cache-related
+   * headers according to the name of the file (see
+   * {@link #getCacheTime(String)}). This function honors If-Modified-Since to
+   * minimize the impact of limiting caching of files for development.
+   * 
+   * @param request the HTTP request
+   * @param response the HTTP response
+   * @param logger a TreeLogger to use for debug output
+   * @param partialPath the path within the module
+   * @param moduleName the name of the module
+   * @throws IOException
+   */
+  @SuppressWarnings("deprecation")
+  private void doGetPublicFile(HttpServletRequest request,
+      HttpServletResponse response, TreeLogger logger, String partialPath,
+      String moduleName) throws IOException {
+
+    // Create a logger branch for this request.
+    logger = logger.branch(TreeLogger.TRACE,
+        "The development shell servlet received a request for '"
+        + partialPath + "' in module '" + moduleName + ".gwt.xml' ", null);
+
+    // Handle auto-generation of resources.
+    if (shouldAutoGenerateResources()) {
+      if (autoGenerateResources(request, response, logger, partialPath,
+          moduleName)) {
+        return;
+      }
+    }
+
+    URL foundResource = null;
+    try {
+      // Look for the requested file on the public path.
+      //
+      ModuleDef moduleDef = getModuleDef(logger, moduleName);
+      if (shouldAutoGenerateResources()) {
+        Resource publicResource = moduleDef.findPublicFile(partialPath);
+        if (publicResource != null) {
+          foundResource = publicResource.getURL();
+        }
+
+        if (foundResource == null) {
+          // Look for public generated files
+          File shellDir = getShellWorkDirs().getShellPublicGenDir(moduleDef);
+          File requestedFile = new File(shellDir, partialPath);
+          if (requestedFile.exists()) {
+            try {
+              foundResource = requestedFile.toURI().toURL();
+            } catch (MalformedURLException e) {
+              // ignore since it was speculative anyway
+            }
+          }
+        }
+      }
+
+      /*
+       * If the user is coming from compiled web-mode, check the linker output
+       * directory for the real bootstrap file.
+       */
+      if (foundResource == null) {
+        File moduleDir = getShellWorkDirs().getCompilerOutputDir(moduleDef);
+        File requestedFile = new File(moduleDir, partialPath);
+        if (requestedFile.exists()) {
+          try {
+            foundResource = requestedFile.toURI().toURL();
+          } catch (MalformedURLException e) {
+            // ignore since it was speculative anyway
+          }
+        }
+      }
+
+      if (foundResource == null) {
+        String msg;
+        if ("gwt.js".equals(partialPath)) {
+          msg = "Loading the old 'gwt.js' bootstrap script is no longer supported; please load '"
+              + moduleName + ".nocache.js' directly";
+        } else {
+          msg = "Resource not found: " + partialPath + "; "
+              + "(could a file be missing from the public path or a <servlet> "
+              + "tag misconfigured in module " + moduleName + ".gwt.xml ?)";
+        }
+        logger.log(TreeLogger.WARN, msg, null);
+        throw new UnableToCompleteException();
+      }
+    } catch (UnableToCompleteException e) {
+      sendErrorResponse(response, HttpServletResponse.SC_NOT_FOUND,
+          "Cannot find resource '" + partialPath
+              + "' in the public path of module '" + moduleName + "'");
+      return;
+    }
+
+    // Get the MIME type.
+    String path = foundResource.toExternalForm();
+    String mimeType = null;
+    try {
+      mimeType = getServletContext().getMimeType(path);
+    } catch (UnsupportedOperationException e) {
+      // Certain minimalist servlet containers throw this.
+      // Fall through to guess the type.
+    }
+
+    if (mimeType == null) {
+      mimeType = guessMimeType(path);
+      if (logger.isLoggable(TreeLogger.TRACE)) {
+        logger.log(TreeLogger.TRACE, "Guessed MIME type '" + mimeType + "'", null);
+      }
+    }
+
+    maybeIssueXhtmlWarning(logger, mimeType, partialPath);
+
+    long cacheSeconds = getCacheTime(path);
+
+    InputStream is = null;
+    try {
+      // Check for up-to-datedness.
+      URLConnection conn = foundResource.openConnection();
+      long lastModified = conn.getLastModified();
+      if (isNotModified(request, lastModified)) {
+        response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+        setResponseCacheHeaders(response, cacheSeconds);
+        return;
+      }
+
+      // Set up headers to really send it.
+      response.setStatus(HttpServletResponse.SC_OK);
+      long now = new Date().getTime();
+      response.setHeader(HttpHeaders.DATE,
+          HttpHeaders.toInternetDateFormat(now));
+      response.setContentType(mimeType);
+      String lastModifiedStr = HttpHeaders.toInternetDateFormat(lastModified);
+      response.setHeader(HttpHeaders.LAST_MODIFIED, lastModifiedStr);
+
+      // Expiration header. Either immediately stale (requiring an
+      // "If-Modified-Since") or infinitely cacheable (not requiring even a
+      // freshness check).
+      setResponseCacheHeaders(response, cacheSeconds);
+
+      // Content length.
+      int contentLength = conn.getContentLength();
+      if (contentLength >= 0) {
+        response.setHeader(HttpHeaders.CONTENT_LENGTH,
+            Integer.toString(contentLength));
+      }
+
+      // Send the bytes.
+      is = conn.getInputStream();
+      streamOut(is, response.getOutputStream(), 1024 * 8);
+    } finally {
+      Utility.close(is);
+    }
+  }
+
+  /**
+   * Generates a module.js file on the fly. Note that the nocache file that is
+   * generated that can only be used for hosted mode. It cannot produce a web
+   * mode version, since this servlet doesn't know strong names, since by
+   * definition of "hosted mode" JavaScript hasn't been compiled at this point.
+   */
+  private String genSelectionScript(TreeLogger logger, String moduleName)
+      throws UnableToCompleteException {
+    if (logger.isLoggable(TreeLogger.TRACE)) {
+      logger.log(TreeLogger.TRACE,
+          "Generating a script selection script for module " + moduleName);
+    }
+    ModuleDef module = getModuleDef(logger, moduleName);
+    StandardLinkerContext context = new StandardLinkerContext(logger, module,
+        new JJSOptionsImpl());
+    ArtifactSet artifacts = context.getArtifactsForPublicResources(logger,
+        module);
+    HostedModeLinker linker = new HostedModeLinker();
+    return linker.generateSelectionScript(logger, context, artifacts);
+  }
+
+  /**
+   * Get the length of time a given file should be cacheable. If the path
+   * contains *.nocache.*, it is never cacheable; if it contains *.cache.*, it
+   * is infinitely cacheable; anything else gets a default time.
+   * 
+   * @return cache time in seconds, or 0 if the file is not cacheable at all
+   */
+  private long getCacheTime(String path) {
+    int lastDot = path.lastIndexOf('.');
+    if (lastDot >= 0) {
+      String prefix = path.substring(0, lastDot);
+      if (prefix.endsWith(".cache")) {
+        // RFC2616 says to never give a cache time of more than a year
+        // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.21
+        return HttpHeaders.SEC_YR;
+      } else if (prefix.endsWith(".nocache")) {
+        return 0;
+      }
+    }
+    return DEFAULT_CACHE_SECONDS;
+  }
+
+  private synchronized TreeLogger getLogger() {
+    if (topLogger == null) {
+      ServletContext servletContext = getServletContext();
+      final String attr = "com.google.gwt.dev.shell.logger";
+      topLogger = (TreeLogger) servletContext.getAttribute(attr);
+      if (topLogger == null) {
+        // No shell available, so wrap the regular servlet context logger.
+        //
+        topLogger = new ServletContextTreeLogger(servletContext);
+      }
+    }
+    return topLogger;
+  }
+
+  /**
+   * We don't actually log this on purpose since the client does anyway.
+   */
+  private ModuleDef getModuleDef(TreeLogger logger, String moduleName)
+      throws UnableToCompleteException {
+    synchronized (loadedModulesByName) {
+      ModuleDef moduleDef = loadedModulesByName.get(moduleName);
+      if (moduleDef == null) {
+        moduleDef = ModuleDefLoader.loadFromClassPath(logger, moduleName, false);
+        loadedModulesByName.put(moduleName, moduleDef);
+        loadedModulesByName.put(moduleDef.getName(), moduleDef);
+
+        // BEGIN BACKWARD COMPATIBILITY
+        // The following map of servlet path to module is included only
+        // for backward-compatibility. We are going to remove this functionality
+        // when we go out of beta. The new behavior is that the client should
+        // specify the module name as part of the URL and construct it using
+        // getModuleBaseURL().
+        String[] servletPaths = moduleDef.getServletPaths();
+        for (int i = 0; i < servletPaths.length; i++) {
+          modulesByServletPath.put(servletPaths[i], moduleDef);
+        }
+        // END BACKWARD COMPATIBILITY
+      }
+      return moduleDef;
+    }
+  }
+
+  @SuppressWarnings("unchecked")
+  private Map<String, String[]> getParameterMap(HttpServletRequest request) {
+    return request.getParameterMap();
+  }
+
+  private synchronized WorkDirs getShellWorkDirs() {
+    if (workDirs == null) {
+      ServletContext servletContext = getServletContext();
+      final String attr = "com.google.gwt.dev.shell.workdirs";
+      workDirs = (WorkDirs) servletContext.getAttribute(attr);
+      assert (workDirs != null);
+    }
+    return workDirs;
+  }
+
+  private String guessMimeType(String fullPath) {
+    int dot = fullPath.lastIndexOf('.');
+    if (dot != -1) {
+      String ext = fullPath.substring(dot + 1);
+      String mimeType = mimeTypes.get(ext);
+      if (mimeType != null) {
+        return mimeType;
+      }
+
+      // Otherwise, fall through.
+      //
+    }
+
+    // Last resort.
+    //
+    return "application/octet-stream";
+  }
+
+  private void initMimeTypes() {
+    mimeTypes.put("abs", "audio/x-mpeg");
+    mimeTypes.put("ai", "application/postscript");
+    mimeTypes.put("aif", "audio/x-aiff");
+    mimeTypes.put("aifc", "audio/x-aiff");
+    mimeTypes.put("aiff", "audio/x-aiff");
+    mimeTypes.put("aim", "application/x-aim");
+    mimeTypes.put("art", "image/x-jg");
+    mimeTypes.put("asf", "video/x-ms-asf");
+    mimeTypes.put("asx", "video/x-ms-asf");
+    mimeTypes.put("au", "audio/basic");
+    mimeTypes.put("avi", "video/x-msvideo");
+    mimeTypes.put("avx", "video/x-rad-screenplay");
+    mimeTypes.put("bcpio", "application/x-bcpio");
+    mimeTypes.put("bin", "application/octet-stream");
+    mimeTypes.put("bmp", "image/bmp");
+    mimeTypes.put("body", "text/html");
+    mimeTypes.put("cdf", "application/x-cdf");
+    mimeTypes.put("cer", "application/x-x509-ca-cert");
+    mimeTypes.put("class", "application/java");
+    mimeTypes.put("cpio", "application/x-cpio");
+    mimeTypes.put("csh", "application/x-csh");
+    mimeTypes.put("css", "text/css");
+    mimeTypes.put("dib", "image/bmp");
+    mimeTypes.put("doc", "application/msword");
+    mimeTypes.put("dtd", "text/plain");
+    mimeTypes.put("dv", "video/x-dv");
+    mimeTypes.put("dvi", "application/x-dvi");
+    mimeTypes.put("eps", "application/postscript");
+    mimeTypes.put("etx", "text/x-setext");
+    mimeTypes.put("exe", "application/octet-stream");
+    mimeTypes.put("gif", "image/gif");
+    mimeTypes.put("gtar", "application/x-gtar");
+    mimeTypes.put("gz", "application/x-gzip");
+    mimeTypes.put("hdf", "application/x-hdf");
+    mimeTypes.put("hqx", "application/mac-binhex40");
+    mimeTypes.put("htc", "text/x-component");
+    mimeTypes.put("htm", "text/html");
+    mimeTypes.put("html", "text/html");
+    mimeTypes.put("hqx", "application/mac-binhex40");
+    mimeTypes.put("ief", "image/ief");
+    mimeTypes.put("jad", "text/vnd.sun.j2me.app-descriptor");
+    mimeTypes.put("jar", "application/java-archive");
+    mimeTypes.put("java", "text/plain");
+    mimeTypes.put("jnlp", "application/x-java-jnlp-file");
+    mimeTypes.put("jpe", "image/jpeg");
+    mimeTypes.put("jpeg", "image/jpeg");
+    mimeTypes.put("jpg", "image/jpeg");
+    mimeTypes.put("js", "text/javascript");
+    mimeTypes.put("jsf", "text/plain");
+    mimeTypes.put("jspf", "text/plain");
+    mimeTypes.put("kar", "audio/x-midi");
+    mimeTypes.put("latex", "application/x-latex");
+    mimeTypes.put("m3u", "audio/x-mpegurl");
+    mimeTypes.put("mac", "image/x-macpaint");
+    mimeTypes.put("man", "application/x-troff-man");
+    mimeTypes.put("me", "application/x-troff-me");
+    mimeTypes.put("mid", "audio/x-midi");
+    mimeTypes.put("midi", "audio/x-midi");
+    mimeTypes.put("mif", "application/x-mif");
+    mimeTypes.put("mov", "video/quicktime");
+    mimeTypes.put("movie", "video/x-sgi-movie");
+    mimeTypes.put("mp1", "audio/x-mpeg");
+    mimeTypes.put("mp2", "audio/x-mpeg");
+    mimeTypes.put("mp3", "audio/x-mpeg");
+    mimeTypes.put("mpa", "audio/x-mpeg");
+    mimeTypes.put("mpe", "video/mpeg");
+    mimeTypes.put("mpeg", "video/mpeg");
+    mimeTypes.put("mpega", "audio/x-mpeg");
+    mimeTypes.put("mpg", "video/mpeg");
+    mimeTypes.put("mpv2", "video/mpeg2");
+    mimeTypes.put("ms", "application/x-wais-source");
+    mimeTypes.put("nc", "application/x-netcdf");
+    mimeTypes.put("oda", "application/oda");
+    mimeTypes.put("pbm", "image/x-portable-bitmap");
+    mimeTypes.put("pct", "image/pict");
+    mimeTypes.put("pdf", "application/pdf");
+    mimeTypes.put("pgm", "image/x-portable-graymap");
+    mimeTypes.put("pic", "image/pict");
+    mimeTypes.put("pict", "image/pict");
+    mimeTypes.put("pls", "audio/x-scpls");
+    mimeTypes.put("png", "image/png");
+    mimeTypes.put("pnm", "image/x-portable-anymap");
+    mimeTypes.put("pnt", "image/x-macpaint");
+    mimeTypes.put("ppm", "image/x-portable-pixmap");
+    mimeTypes.put("ppt", "application/powerpoint");
+    mimeTypes.put("ps", "application/postscript");
+    mimeTypes.put("psd", "image/x-photoshop");
+    mimeTypes.put("qt", "video/quicktime");
+    mimeTypes.put("qti", "image/x-quicktime");
+    mimeTypes.put("qtif", "image/x-quicktime");
+    mimeTypes.put("ras", "image/x-cmu-raster");
+    mimeTypes.put("rgb", "image/x-rgb");
+    mimeTypes.put("rm", "application/vnd.rn-realmedia");
+    mimeTypes.put("roff", "application/x-troff");
+    mimeTypes.put("rtf", "application/rtf");
+    mimeTypes.put("rtx", "text/richtext");
+    mimeTypes.put("sh", "application/x-sh");
+    mimeTypes.put("shar", "application/x-shar");
+    mimeTypes.put("smf", "audio/x-midi");
+    mimeTypes.put("sit", "application/x-stuffit");
+    mimeTypes.put("snd", "audio/basic");
+    mimeTypes.put("src", "application/x-wais-source");
+    mimeTypes.put("sv4cpio", "application/x-sv4cpio");
+    mimeTypes.put("sv4crc", "application/x-sv4crc");
+    mimeTypes.put("swf", "application/x-shockwave-flash");
+    mimeTypes.put("t", "application/x-troff");
+    mimeTypes.put("tar", "application/x-tar");
+    mimeTypes.put("tcl", "application/x-tcl");
+    mimeTypes.put("tex", "application/x-tex");
+    mimeTypes.put("texi", "application/x-texinfo");
+    mimeTypes.put("texinfo", "application/x-texinfo");
+    mimeTypes.put("tif", "image/tiff");
+    mimeTypes.put("tiff", "image/tiff");
+    mimeTypes.put("tr", "application/x-troff");
+    mimeTypes.put("tsv", "text/tab-separated-values");
+    mimeTypes.put("txt", "text/plain");
+    mimeTypes.put("ulw", "audio/basic");
+    mimeTypes.put("ustar", "application/x-ustar");
+    mimeTypes.put("xbm", "image/x-xbitmap");
+    mimeTypes.put("xht", "application/xhtml+xml");
+    mimeTypes.put("xhtml", "application/xhtml+xml");
+    mimeTypes.put("xml", "text/xml");
+    mimeTypes.put("xpm", "image/x-xpixmap");
+    mimeTypes.put("xsl", "text/xml");
+    mimeTypes.put("xwd", "image/x-xwindowdump");
+    mimeTypes.put("wav", "audio/x-wav");
+    mimeTypes.put("svg", "image/svg+xml");
+    mimeTypes.put("svgz", "image/svg+xml");
+    mimeTypes.put("vsd", "application/x-visio");
+    mimeTypes.put("wbmp", "image/vnd.wap.wbmp");
+    mimeTypes.put("wml", "text/vnd.wap.wml");
+    mimeTypes.put("wmlc", "application/vnd.wap.wmlc");
+    mimeTypes.put("wmls", "text/vnd.wap.wmlscript");
+    mimeTypes.put("wmlscriptc", "application/vnd.wap.wmlscriptc");
+    mimeTypes.put("wrl", "x-world/x-vrml");
+    mimeTypes.put("Z", "application/x-compress");
+    mimeTypes.put("z", "application/x-compress");
+    mimeTypes.put("zip", "application/zip");
+  }
+
+  /**
+   * Checks to see whether or not a client's file is out of date relative to the
+   * original.
+   */
+  private boolean isNotModified(HttpServletRequest request, long ageOfServerCopy) {
+    // The age of the server copy *must* have the milliseconds truncated.
+    // Since milliseconds isn't part of the GMT format, failure to truncate
+    // will leave the file in a state where it appears constantly out of date
+    // and yet it can never get in sync because the Last-Modified date keeps
+    // truncating off the milliseconds part on its way out.
+    // 
+    ageOfServerCopy -= (ageOfServerCopy % 1000);
+
+    long ageOfClientCopy = 0;
+    String ifModifiedSince = request.getHeader("If-Modified-Since");
+    if (ifModifiedSince != null) {
+      // Rip off any additional stuff at the end, such as "; length="
+      // (IE does add this).
+      //
+      int lastSemi = ifModifiedSince.lastIndexOf(';');
+      if (lastSemi != -1) {
+        ifModifiedSince = ifModifiedSince.substring(0, lastSemi);
+      }
+      ageOfClientCopy = HttpHeaders.fromInternetDateFormat(ifModifiedSince);
+    }
+
+    if (ageOfClientCopy >= ageOfServerCopy) {
+      // The client already has a good copy.
+      //
+      return true;
+    } else {
+      // The client needs a fresh copy of the requested file.
+      //
+      return false;
+    }
+  }
+
+  private void maybeIssueXhtmlWarning(TreeLogger logger, String mimeType,
+      String path) {
+    if (!XHTML_MIME_TYPE.equals(mimeType)) {
+      return;
+    }
+
+    String msg = "File was returned with content-type of \"" + mimeType
+        + "\". GWT requires browser features that are not available to "
+        + "documents with this content-type.";
+
+    int ix = path.lastIndexOf('.');
+    if (ix >= 0 && ix < path.length()) {
+      String base = path.substring(0, ix);
+      msg += " Consider renaming \"" + path + "\" to \"" + base + ".html\".";
+    }
+
+    logger.log(TreeLogger.WARN, msg, null);
+  }
+
+  private void sendErrorResponse(HttpServletResponse response, int statusCode,
+      String msg) throws IOException {
+    response.setContentType("text/html");
+    response.getWriter().println(msg);
+    response.setStatus(statusCode);
+  }
+
+  /**
+   * Sets the Cache-control and Expires headers in the response based on the
+   * supplied cache time.
+   * 
+   * Expires is used in addition to Cache-control for older clients or proxies
+   * which may not properly understand Cache-control.
+   * 
+   * @param response the HttpServletResponse to update
+   * @param cacheTime non-negative number of seconds to cache the response; 0
+   *          means specifically do not allow caching at all.
+   * @throws IllegalArgumentException if cacheTime is negative
+   */
+  private void setResponseCacheHeaders(HttpServletResponse response,
+      long cacheTime) {
+    long expires;
+    if (cacheTime < 0) {
+      throw new IllegalArgumentException("cacheTime of " + cacheTime
+          + " is negative");
+    }
+    if (cacheTime > 0) {
+      // Expire the specified seconds in the future.
+      expires = new Date().getTime() + cacheTime * HttpHeaders.MS_SEC;
+    } else {
+      // Prevent caching by using a time in the past for cache expiration.
+      // Use January 2, 1970 00:00:00, to account for timezone changes
+      // in case a browser tries to convert to a local timezone first
+      // 0=Jan 1, so add 1 day's worth of milliseconds to get Jan 2
+      expires = HttpHeaders.SEC_DAY * HttpHeaders.MS_SEC;
+    }
+    response.setHeader(HttpHeaders.CACHE_CONTROL,
+        HttpHeaders.CACHE_CONTROL_MAXAGE + cacheTime);
+    String expiresString = HttpHeaders.toInternetDateFormat(expires);
+    response.setHeader(HttpHeaders.EXPIRES, expiresString);
+  }
+
+  private boolean shouldAutoGenerateResources() {
+    ServletContext servletContext = getServletContext();
+    final String attr = "com.google.gwt.dev.shell.shouldAutoGenerateResources";
+    Boolean attrValue = (Boolean) servletContext.getAttribute(attr);
+    if (attrValue == null) {
+      return true;
+    }
+    return attrValue;
+  }
+
+  private void streamOut(InputStream in, OutputStream out, int bufferSize)
+      throws IOException {
+    assert (bufferSize >= 0);
+
+    byte[] buffer = new byte[bufferSize];
+    int bytesRead = 0;
+    while (true) {
+      bytesRead = in.read(buffer);
+      if (bytesRead >= 0) {
+        // Copy the bytes out.
+        out.write(buffer, 0, bytesRead);
+      } else {
+        // End of input stream.
+        out.flush();
+        return;
+      }
+    }
+  }
+
+  private HttpServlet tryGetOrLoadServlet(TreeLogger logger,
+      ModuleDef moduleDef, String className) {
+
+    // Maps className to live servlet for this module.
+    Map<String, HttpServlet> moduleServlets;
+    synchronized (loadedServletsByModuleAndClassName) {
+      moduleServlets = loadedServletsByModuleAndClassName.get(moduleDef);
+      if (moduleServlets == null) {
+        moduleServlets = new HashMap<String, HttpServlet>();
+        loadedServletsByModuleAndClassName.put(moduleDef, moduleServlets);
+      }
+    }
+
+    synchronized (moduleServlets) {
+      HttpServlet servlet = moduleServlets.get(className);
+      if (servlet != null) {
+        // Found it.
+        //
+        return servlet;
+      }
+
+      // Try to load and instantiate it.
+      //
+      Throwable caught = null;
+      try {
+        Class<?> servletClass = Class.forName(className);
+        Object newInstance = servletClass.newInstance();
+        if (!(newInstance instanceof HttpServlet)) {
+          logger.log(TreeLogger.ERROR,
+              "Not compatible with HttpServlet: " + className
+                  + " (does your service extend RemoteServiceServlet?)", null);
+          return null;
+        }
+
+        // Success. Hang onto the instance so we can reuse it.
+        //
+        servlet = (HttpServlet) newInstance;
+
+        // We create proxies for ServletContext and ServletConfig to enable
+        // RemoteServiceServlets to load public and generated resources via
+        // ServletContext.getResourceAsStream()
+        //
+        ServletContext context = new HostedModeServletContextProxy(
+            getServletContext(), moduleDef, getShellWorkDirs());
+        ServletConfig config = new HostedModeServletConfigProxy(
+            getServletConfig(), context);
+
+        servlet.init(config);
+
+        moduleServlets.put(className, servlet);
+        return servlet;
+      } catch (ClassNotFoundException e) {
+        caught = e;
+      } catch (InstantiationException e) {
+        caught = e;
+      } catch (IllegalAccessException e) {
+        caught = e;
+      } catch (ServletException e) {
+        caught = e;
+      }
+      String msg = "Unable to instantiate '" + className + "'";
+      logger.log(TreeLogger.ERROR, msg, caught);
+      return null;
+    }
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/shell/HostedModeServletContextProxy.java b/dev/core/src/com/google/gwt/dev/shell/HostedModeServletContextProxy.java
new file mode 100644
index 0000000..d6b321e
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/shell/HostedModeServletContextProxy.java
@@ -0,0 +1,338 @@
+/*
+ * 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.dev.shell;
+
+import com.google.gwt.dev.cfg.ModuleDef;
+import com.google.gwt.dev.resource.Resource;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.ref.WeakReference;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.Set;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+
+/**
+ * ServletContext proxy that implements the getResource and getResourceAsStream
+ * members so that they can work with the {@link GWTShellServlet}.
+ */
+class HostedModeServletContextProxy implements ServletContext {
+  private final ServletContext context;
+  /**
+   * Avoid pinning my moduleDef.
+   */
+  private final WeakReference<ModuleDef> moduleDefRef;
+  private final WorkDirs workDirs;
+
+  HostedModeServletContextProxy(ServletContext context, ModuleDef moduleDef,
+      WorkDirs workDirs) {
+    this.context = context;
+    this.moduleDefRef = new WeakReference<ModuleDef>(moduleDef);
+    this.workDirs = workDirs;
+  }
+
+  /**
+   * @param arg0
+   * @return
+   * @see javax.servlet.ServletContext#getAttribute(java.lang.String)
+   */
+  public Object getAttribute(String arg0) {
+    return context.getAttribute(arg0);
+  }
+
+  /**
+   * @return
+   * @see javax.servlet.ServletContext#getAttributeNames()
+   */
+  @SuppressWarnings("unchecked")
+  public Enumeration<String> getAttributeNames() {
+    return context.getAttributeNames();
+  }
+
+  /**
+   * @param arg0
+   * @return
+   * @see javax.servlet.ServletContext#getContext(java.lang.String)
+   */
+  public ServletContext getContext(String arg0) {
+    return context.getContext(arg0);
+  }
+
+  public String getContextPath() {
+    return context.getContextPath();
+  }
+
+  /**
+   * @param arg0
+   * @return
+   * @see javax.servlet.ServletContext#getInitParameter(java.lang.String)
+   */
+  public String getInitParameter(String arg0) {
+    return context.getInitParameter(arg0);
+  }
+
+  /**
+   * @return
+   * @see javax.servlet.ServletContext#getInitParameterNames()
+   */
+  public Enumeration<?> getInitParameterNames() {
+    return context.getInitParameterNames();
+  }
+
+  /**
+   * @return
+   * @see javax.servlet.ServletContext#getMajorVersion()
+   */
+  public int getMajorVersion() {
+    return context.getMajorVersion();
+  }
+
+  /**
+   * @param arg0
+   * @return
+   * @see javax.servlet.ServletContext#getMimeType(java.lang.String)
+   */
+  public String getMimeType(String arg0) {
+    return context.getMimeType(arg0);
+  }
+
+  /**
+   * @return
+   * @see javax.servlet.ServletContext#getMinorVersion()
+   */
+  public int getMinorVersion() {
+    return context.getMinorVersion();
+  }
+
+  /**
+   * @param arg0
+   * @return
+   * @see javax.servlet.ServletContext#getNamedDispatcher(java.lang.String)
+   */
+  public RequestDispatcher getNamedDispatcher(String arg0) {
+    return context.getNamedDispatcher(arg0);
+  }
+
+  /**
+   * @param arg0
+   * @return
+   * @see javax.servlet.ServletContext#getRealPath(java.lang.String)
+   */
+  public String getRealPath(String arg0) {
+    return context.getRealPath(arg0);
+  }
+
+  /**
+   * @param arg0
+   * @return
+   * @see javax.servlet.ServletContext#getRequestDispatcher(java.lang.String)
+   */
+  public RequestDispatcher getRequestDispatcher(String arg0) {
+    return context.getRequestDispatcher(arg0);
+  }
+
+  /**
+   * @param arg0
+   * @return
+   * @throws MalformedURLException
+   * @see javax.servlet.ServletContext#getResource(java.lang.String)
+   */
+  @SuppressWarnings("deprecation")
+  public URL getResource(String path) throws MalformedURLException {
+    ModuleDef moduleDef = moduleDefRef.get();
+    assert (moduleDef != null) : "GWTShellServlet should have guaranteed that a"
+        + " live servlet will never process a request for a dead module; if you"
+        + " are using this servlet outside the context of processing a call,"
+        + " then don't do that";
+
+    String moduleContext = "/" + moduleDef.getName() + "/";
+    if (!path.startsWith(moduleContext)) {
+      // Check for a renamed module
+      moduleContext = "/" + moduleDef.getCanonicalName() + "/";
+      if (!path.startsWith(moduleContext)) {
+        // This path is in a different context; just return null
+        return null;
+      }
+    }
+
+    String partialPath = path.substring(moduleContext.length());
+
+    // Try to get the resource from the application's public path
+    Resource publicResource = moduleDef.findPublicFile(partialPath);
+    if (publicResource != null) {
+      return publicResource.getURL();
+    }
+
+    // Otherwise try the path in the shell's public generated directory
+    File shellDir = workDirs.getShellPublicGenDir(moduleDef);
+    File requestedFile = new File(shellDir, partialPath);
+    if (requestedFile.exists()) {
+      return requestedFile.toURI().toURL();
+    }
+
+    /*
+     * If the user is coming from compiled web-mode, check the linker output
+     * directory for the file. We'll default to using the output directory of
+     * the first linker defined in the <set-linker> tab.
+     */
+    File linkDir = workDirs.getCompilerOutputDir(moduleDef);
+    requestedFile = new File(linkDir, partialPath);
+    if (requestedFile.exists()) {
+      try {
+        return requestedFile.toURI().toURL();
+      } catch (MalformedURLException e) {
+        // ignore since it was speculative anyway
+      }
+    }
+
+    return null;
+  }
+
+  /**
+   * @param arg0
+   * @return
+   * @see javax.servlet.ServletContext#getResourceAsStream(java.lang.String)
+   */
+  public InputStream getResourceAsStream(String arg0) {
+    URL url;
+    try {
+      url = getResource(arg0);
+      if (url != null) {
+        return url.openStream();
+      }
+    } catch (MalformedURLException e) {
+      // Ignore the exception; return null
+    } catch (IOException e) {
+      // Ignore the exception; return null
+    }
+
+    return null;
+  }
+
+  /**
+   * 
+   * @param path
+   * @return
+   * @see javax.servlet.ServletContext#getResourcePaths(java.lang.String)
+   */
+  @SuppressWarnings("unchecked")
+  public Set<String> getResourcePaths(String path) {
+    return context.getResourcePaths(path);
+  }
+
+  /**
+   * @return
+   * @see javax.servlet.ServletContext#getServerInfo()
+   */
+  public String getServerInfo() {
+    return context.getServerInfo();
+  }
+
+  /**
+   * @param arg0
+   * @return
+   * @throws ServletException
+   * @deprecated
+   * @see javax.servlet.ServletContext#getServlet(java.lang.String)
+   */
+  @Deprecated
+  public Servlet getServlet(String arg0) throws ServletException {
+    return context.getServlet(arg0);
+  }
+
+  /**
+   * @return
+   * @see javax.servlet.ServletContext#getServletContextName()
+   */
+  public String getServletContextName() {
+    return context.getServletContextName();
+  }
+
+  /**
+   * @return
+   * @deprecated
+   * @see javax.servlet.ServletContext#getServletNames()
+   */
+  @Deprecated
+  @SuppressWarnings("unchecked")
+  public Enumeration<String> getServletNames() {
+    return context.getServletNames();
+  }
+
+  /**
+   * @return
+   * @deprecated
+   * @see javax.servlet.ServletContext#getServlets()
+   */
+  @Deprecated
+  @SuppressWarnings("unchecked")
+  public Enumeration<Servlet> getServlets() {
+    return context.getServlets();
+  }
+
+  /**
+   * @param arg0
+   * @param arg1
+   * @deprecated
+   * @see javax.servlet.ServletContext#log(java.lang.Exception,
+   *      java.lang.String)
+   */
+  @Deprecated
+  public void log(Exception arg0, String arg1) {
+    context.log(arg0, arg1);
+  }
+
+  /**
+   * @param arg0
+   * @see javax.servlet.ServletContext#log(java.lang.String)
+   */
+  public void log(String arg0) {
+    context.log(arg0);
+  }
+
+  /**
+   * @param arg0
+   * @param arg1
+   * @see javax.servlet.ServletContext#log(java.lang.String,java.lang.Throwable)
+   */
+  public void log(String arg0, Throwable arg1) {
+    context.log(arg0, arg1);
+  }
+
+  /**
+   * @param arg0
+   * @see javax.servlet.ServletContext#removeAttribute(java.lang.String)
+   */
+  public void removeAttribute(String arg0) {
+    context.removeAttribute(arg0);
+  }
+
+  /**
+   * @param arg0
+   * @param arg1
+   * @see javax.servlet.ServletContext#setAttribute(java.lang.String,java.lang.Object)
+   */
+  public void setAttribute(String arg0, Object arg1) {
+    context.setAttribute(arg0, arg1);
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/shell/tomcat/CatalinaLoggerAdapter.java b/dev/core/src/com/google/gwt/dev/shell/tomcat/CatalinaLoggerAdapter.java
new file mode 100644
index 0000000..9cf2d95
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/shell/tomcat/CatalinaLoggerAdapter.java
@@ -0,0 +1,75 @@
+/*
+ * 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.shell.tomcat;
+
+import com.google.gwt.core.ext.TreeLogger;
+
+import org.apache.catalina.logger.LoggerBase;
+
+class CatalinaLoggerAdapter extends LoggerBase {
+
+  private final TreeLogger logger;
+
+  public CatalinaLoggerAdapter(TreeLogger logger) {
+    this.logger = logger;
+  }
+
+  @Override
+  public void log(Exception exception, String msg) {
+    logger.log(TreeLogger.WARN, msg, exception);
+  }
+
+  @Override
+  public void log(String msg) {
+    logger.log(TreeLogger.INFO, msg, null);
+  }
+
+  @Override
+  public void log(String message, int verbosity) {
+    TreeLogger.Type type = mapVerbosityToLogType(verbosity);
+    logger.log(type, message, null);
+  }
+
+  @Override
+  public void log(String msg, Throwable throwable) {
+    logger.log(TreeLogger.WARN, msg, throwable);
+  }
+
+  @Override
+  public void log(String message, Throwable throwable, int verbosity) {
+    TreeLogger.Type type = mapVerbosityToLogType(verbosity);
+    logger.log(type, message, throwable);
+  }
+
+  private TreeLogger.Type mapVerbosityToLogType(int verbosity) {
+    switch (verbosity) {
+      case LoggerBase.FATAL:
+      case LoggerBase.ERROR:
+      case LoggerBase.WARNING:
+        return TreeLogger.WARN;
+
+      case LoggerBase.INFORMATION:
+        return TreeLogger.DEBUG;
+      case LoggerBase.DEBUG:
+        return TreeLogger.SPAM;
+
+      default:
+        // really, this was an unexpected type
+        return TreeLogger.WARN;
+    }
+  }
+
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/dev/shell/tomcat/CommonsLoggerAdapter.java b/dev/core/src/com/google/gwt/dev/shell/tomcat/CommonsLoggerAdapter.java
new file mode 100644
index 0000000..d071d2a
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/shell/tomcat/CommonsLoggerAdapter.java
@@ -0,0 +1,119 @@
+/*
+ * 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.shell.tomcat;
+
+import com.google.gwt.core.ext.TreeLogger;
+
+/**
+ * Maps Tomcat's commons logger onto the GWT shell's tree logger.
+ */
+public class CommonsLoggerAdapter implements org.apache.commons.logging.Log {
+
+  private TreeLogger log;
+
+  /**
+   * @param name unused
+   */
+  public CommonsLoggerAdapter(String name) {
+    // NOTE: this is ugly, but I don't know of any other way to get a
+    // non-static log to which we can delegate.
+    //
+    log = EmbeddedTomcatServer.sTomcat.getLogger();
+  }
+
+  public void debug(Object message) {
+    doLog(TreeLogger.SPAM, message, null);
+  }
+
+  public void debug(Object message, Throwable t) {
+    doLog(TreeLogger.SPAM, message, t);
+  }
+
+  public void error(Object message) {
+    doLog(TreeLogger.WARN, message, null);
+  }
+
+  public void error(Object message, Throwable t) {
+    doLog(TreeLogger.WARN, message, t);
+  }
+
+  public void fatal(Object message) {
+    doLog(TreeLogger.WARN, message, null);
+  }
+
+  public void fatal(Object message, Throwable t) {
+    doLog(TreeLogger.WARN, message, t);
+  }
+
+  public void info(Object message) {
+    // Intentionally low-level to us.
+    doLog(TreeLogger.TRACE, message, null);
+  }
+
+  public void info(Object message, Throwable t) {
+    // Intentionally low-level to us.
+    doLog(TreeLogger.TRACE, message, t);
+  }
+
+  public boolean isDebugEnabled() {
+    return log.isLoggable(TreeLogger.SPAM);
+  }
+
+  public boolean isErrorEnabled() {
+    return log.isLoggable(TreeLogger.WARN);
+  }
+
+  public boolean isFatalEnabled() {
+    return log.isLoggable(TreeLogger.WARN);
+  }
+
+  public boolean isInfoEnabled() {
+    // Intentionally low-level to us.
+    return log.isLoggable(TreeLogger.TRACE);
+  }
+
+  public boolean isTraceEnabled() {
+    // Intentionally low-level to us.
+    return log.isLoggable(TreeLogger.SPAM);
+  }
+
+  public boolean isWarnEnabled() {
+    return log.isLoggable(TreeLogger.WARN);
+  }
+
+  public void trace(Object message) {
+    // Intentionally low-level to us.
+    doLog(TreeLogger.DEBUG, message, null);
+  }
+
+  public void trace(Object message, Throwable t) {
+    // Intentionally low-level to us.
+    doLog(TreeLogger.DEBUG, message, t);
+  }
+
+  public void warn(Object message) {
+    doLog(TreeLogger.WARN, message, null);
+  }
+
+  public void warn(Object message, Throwable t) {
+    doLog(TreeLogger.WARN, message, t);
+  }
+
+  private void doLog(TreeLogger.Type type, Object message, Throwable t) {
+    String msg = message.toString();
+    log.log(type, msg, t);
+  }
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/dev/shell/tomcat/EmbeddedTomcatServer.java b/dev/core/src/com/google/gwt/dev/shell/tomcat/EmbeddedTomcatServer.java
new file mode 100644
index 0000000..e7d27ac
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/shell/tomcat/EmbeddedTomcatServer.java
@@ -0,0 +1,436 @@
+/*
+ * 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.shell.tomcat;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.dev.resource.Resource;
+import com.google.gwt.dev.resource.impl.ClassPathEntry;
+import com.google.gwt.dev.resource.impl.PathPrefix;
+import com.google.gwt.dev.resource.impl.PathPrefixSet;
+import com.google.gwt.dev.resource.impl.ResourceOracleImpl;
+import com.google.gwt.dev.shell.WorkDirs;
+import com.google.gwt.dev.util.Util;
+
+import org.apache.catalina.Connector;
+import org.apache.catalina.ContainerEvent;
+import org.apache.catalina.ContainerListener;
+import org.apache.catalina.Engine;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.Logger;
+import org.apache.catalina.core.StandardContext;
+import org.apache.catalina.core.StandardHost;
+import org.apache.catalina.startup.Embedded;
+import org.apache.catalina.startup.HostConfig;
+import org.apache.coyote.tomcat5.CoyoteConnector;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Wraps an instance of the Tomcat web server used in hosted mode.
+ */
+public class EmbeddedTomcatServer {
+
+  static EmbeddedTomcatServer sTomcat;
+
+  public static int getPort() {
+    return sTomcat.port;
+  }
+
+  public static String start(TreeLogger topLogger, int port, WorkDirs workDirs) {
+    return start(topLogger, port, workDirs, true);
+  }
+
+  public static synchronized String start(TreeLogger topLogger, int port,
+      WorkDirs workDirs, boolean shouldAutoGenerateResources) {
+    if (sTomcat != null) {
+      throw new IllegalStateException("Embedded Tomcat is already running");
+    }
+
+    try {
+      new EmbeddedTomcatServer(topLogger, port, workDirs,
+          shouldAutoGenerateResources);
+      return null;
+    } catch (LifecycleException e) {
+      String msg = e.getMessage();
+      if (msg != null && msg.indexOf("already in use") != -1) {
+        msg = "Port "
+            + port
+            + " is already is use; you probably still have another session active";
+      } else {
+        msg = "Unable to start the embedded Tomcat server; double-check that your configuration is valid";
+      }
+      return msg;
+    }
+  }
+
+  // Stop the embedded Tomcat server.
+  //
+  public static synchronized void stop() {
+    if (sTomcat != null) {
+      try {
+        sTomcat.catEmbedded.stop();
+      } catch (LifecycleException e) {
+        // There's nothing we can really do about this and the logger is
+        // gone in many scenarios, so we just ignore it.
+        //
+      } finally {
+        sTomcat = null;
+      }
+    }
+  }
+
+  /**
+   * Returns what local port the Tomcat connector is running on.
+   * 
+   * When starting Tomcat with port 0 (i.e. choose an open port), there is just
+   * no way to figure out what port it actually chose. So we're using pure
+   * hackery to steal the port via reflection. The only works because we bundle
+   * Tomcat with GWT and know exactly what version it is.
+   */
+  private static int computeLocalPort(Connector connector) {
+    Throwable caught = null;
+    try {
+      Field phField = CoyoteConnector.class.getDeclaredField("protocolHandler");
+      phField.setAccessible(true);
+      Object protocolHandler = phField.get(connector);
+
+      Field epField = protocolHandler.getClass().getDeclaredField("ep");
+      epField.setAccessible(true);
+      Object endPoint = epField.get(protocolHandler);
+
+      Field ssField = endPoint.getClass().getDeclaredField("serverSocket");
+      ssField.setAccessible(true);
+      ServerSocket serverSocket = (ServerSocket) ssField.get(endPoint);
+
+      return serverSocket.getLocalPort();
+    } catch (SecurityException e) {
+      caught = e;
+    } catch (NoSuchFieldException e) {
+      caught = e;
+    } catch (IllegalArgumentException e) {
+      caught = e;
+    } catch (IllegalAccessException e) {
+      caught = e;
+    }
+    throw new RuntimeException(
+        "Failed to retrieve the startup port from Embedded Tomcat", caught);
+  }
+
+  private Embedded catEmbedded;
+
+  private Engine catEngine;
+
+  private StandardHost catHost = null;
+
+  private int port;
+
+  private final TreeLogger startupBranchLogger;
+
+  private EmbeddedTomcatServer(final TreeLogger topLogger, int listeningPort,
+      final WorkDirs workDirs, final boolean shouldAutoGenerateResources)
+      throws LifecycleException {
+    if (topLogger == null) {
+      throw new NullPointerException("No logger specified");
+    }
+
+    final TreeLogger logger = topLogger.branch(TreeLogger.INFO,
+        "Starting HTTP on port " + listeningPort, null);
+
+    startupBranchLogger = logger;
+
+    // Make myself the one static instance.
+    // NOTE: there is only one small implementation reason that this has
+    // to be a singleton, which is that the commons logger LogFactory insists
+    // on creating your logger class which must have a constructor with
+    // exactly one String argument, and since we want LoggerAdapter to delegate
+    // to the logger instance available through instance host, there is no
+    // way I can think of to delegate without accessing a static field.
+    // An inner class is almost right, except there's no outer instance.
+    //
+    sTomcat = this;
+
+    // Assume the working directory is simply the user's current directory.
+    //
+    File topWorkDir = new File(System.getProperty("user.dir"));
+
+    // Tell Tomcat its base directory so that it won't complain.
+    //
+    String catBase = System.getProperty("catalina.base");
+    if (catBase == null) {
+      // we (briefly) supported catalina.base.create, so let's not cut support
+      // until the deprecated sunset
+      catBase = System.getProperty("catalina.base.create");
+      if (catBase != null) {
+        logger.log(TreeLogger.WARN, "catalina.base.create is deprecated.  " +
+            "Use catalina.base, and it will be created if necessary.");
+        topWorkDir = new File(catBase);
+      }
+      catBase = generateDefaultCatalinaBase(logger, topWorkDir);
+      System.setProperty("catalina.base", catBase);
+    }
+
+    // Some debug messages for ourselves.
+    //
+    if (logger.isLoggable(TreeLogger.DEBUG)) {
+      logger.log(TreeLogger.DEBUG, "catalina.base = " + catBase, null);
+    }
+
+    // Set up the logger that will be returned by the Commons logging factory.
+    //
+    String adapterClassName = CommonsLoggerAdapter.class.getName();
+    System.setProperty("org.apache.commons.logging.Log", adapterClassName);
+
+    // And set up an adapter that will work with the Catalina logger family.
+    //
+    Logger catalinaLogger = new CatalinaLoggerAdapter(topLogger);
+
+    // Create an embedded server.
+    //
+    catEmbedded = new Embedded();
+    catEmbedded.setDebug(0);
+    catEmbedded.setLogger(catalinaLogger);
+
+    // The embedded engine is called "gwt".
+    //
+    catEngine = catEmbedded.createEngine();
+    catEngine.setName("gwt");
+    catEngine.setDefaultHost("localhost");
+    catEngine.setParentClassLoader(this.getClass().getClassLoader());
+
+    // It answers localhost requests.
+    //
+    // String appBase = fCatalinaBaseDir.getAbsolutePath();
+    String appBase = catBase + "/webapps";
+    catHost = (StandardHost) catEmbedded.createHost("localhost", appBase);
+
+    // Hook up a host config to search for and pull in webapps.
+    //
+    HostConfig hostConfig = new HostConfig();
+    catHost.addLifecycleListener(hostConfig);
+
+    // Hook pre-install events so that we can add attributes to allow loaded
+    // instances to find their development instance host.
+    //
+    catHost.addContainerListener(new ContainerListener() {
+      public void containerEvent(ContainerEvent event) {
+        if (StandardHost.PRE_INSTALL_EVENT.equals(event.getType())) {
+          StandardContext webapp = (StandardContext) event.getData();
+          publishShellLoggerAttribute(logger, topLogger, webapp);
+          publishShellWorkDirsAttribute(logger, workDirs, webapp);
+          publishShouldAutoGenerateResourcesAttribute(logger,
+              shouldAutoGenerateResources, webapp);
+        }
+      }
+    });
+
+    // Tell the engine about the host.
+    //
+    catEngine.addChild(catHost);
+    catEngine.setDefaultHost(catHost.getName());
+
+    // Tell the embedded manager about the engine.
+    //
+    catEmbedded.addEngine(catEngine);
+    InetAddress nullAddr = null;
+    Connector connector = catEmbedded.createConnector(nullAddr, listeningPort,
+        false);
+    catEmbedded.addConnector(connector);
+
+    // start up!
+    catEmbedded.start();
+    port = computeLocalPort(connector);
+
+    if (port != listeningPort) {
+      if (logger.isLoggable(TreeLogger.INFO)) {
+        logger.log(TreeLogger.INFO, "HTTP listening on port " + port, null);
+      }
+    }
+  }
+
+  public TreeLogger getLogger() {
+    return startupBranchLogger;
+  }
+
+  /*
+   * Assumes that the leaf is a file (not a directory).
+   */
+  private void copyFileNoOverwrite(TreeLogger logger, String srcResName,
+      Resource srcRes, File catBase) {
+
+    File dest = new File(catBase, srcResName);
+    try {
+      // Only copy if src is newer than desc.
+      long srcLastModified = srcRes.getLastModified();
+      long dstLastModified = dest.lastModified();
+
+      if (srcLastModified < dstLastModified) {
+        // Don't copy over it.
+        if (logger.isLoggable(TreeLogger.SPAM)) {
+          logger.log(TreeLogger.SPAM, "Source is older than existing: "
+              + dest.getAbsolutePath(), null);
+        }
+        return;
+      } else if (srcLastModified == dstLastModified) {
+        // Exact same time; quietly don't overwrite.
+        return;
+      } else if (dest.exists()) {
+        // Warn about the overwrite
+        logger.log(TreeLogger.WARN, "Overwriting existing file '"
+            + dest.getAbsolutePath() + "' with '" + srcRes.getLocation()
+            + "', which has a newer timestamp");
+      }
+
+      // Make dest directories as required.
+      File destParent = dest.getParentFile();
+      if (destParent != null) {
+        // No need to check mkdirs result because IOException later anyway.
+        destParent.mkdirs();
+      }
+
+      Util.copy(srcRes.openContents(), new FileOutputStream(dest));
+      dest.setLastModified(srcLastModified);
+
+      if (logger.isLoggable(TreeLogger.TRACE)) {
+        logger.log(TreeLogger.TRACE, "Wrote: " + dest.getAbsolutePath(), null);
+      }
+    } catch (IOException e) {
+      logger.log(TreeLogger.WARN, "Failed to write: " + dest.getAbsolutePath(),
+          e);
+    } 
+  }
+
+  /**
+   * Extracts a valid catalina base instance from the classpath. Does not
+   * overwrite any existing files.
+   */
+  private String generateDefaultCatalinaBase(TreeLogger logger, File workDir) {
+    logger = logger.branch(
+        TreeLogger.TRACE,
+        "Property 'catalina.base' not specified; checking for a standard catalina base image instead",
+        null);
+
+    // Recursively copies out files and directories
+    String tomcatEtcDir = "com/google/gwt/dev/etc/tomcat/";
+    Map<String, Resource> resourceMap = null;
+    Throwable caught = null;
+    try {
+      resourceMap = getResourcesFor(logger, tomcatEtcDir);
+    } catch (URISyntaxException e) {
+      caught = e;
+    } catch (IOException e) {
+      caught = e;
+    }
+
+    File catBase = new File(workDir, "tomcat");
+    if (resourceMap == null || resourceMap.isEmpty()) {
+      logger.log(TreeLogger.WARN, "Could not find " + tomcatEtcDir, caught);
+    } else {
+      for (Entry<String, Resource> entry : resourceMap.entrySet()) {
+        copyFileNoOverwrite(logger, entry.getKey(), entry.getValue(), catBase);
+      }
+    }
+
+    return catBase.getAbsolutePath();
+  }
+
+  /**
+   * Hacky, but fast.
+   */
+  private Map<String, Resource> getResourcesFor(TreeLogger logger,
+      String tomcatEtcDir) throws URISyntaxException, IOException {
+    ClassLoader contextClassLoader = this.getClass().getClassLoader();
+    URL url = contextClassLoader.getResource(tomcatEtcDir);
+    if (url == null) {
+      return null;
+    }
+    String prefix = "";
+    String urlString = url.toString();
+    if (urlString.startsWith("jar:")) {
+      assert urlString.toLowerCase(Locale.ENGLISH).contains(".jar!/"
+          + tomcatEtcDir);
+      urlString = urlString.substring(4, urlString.indexOf('!'));
+      url = new URL(urlString);
+      prefix = tomcatEtcDir;
+    } else if (urlString.startsWith("zip:")) {
+      assert urlString.toLowerCase(Locale.ENGLISH).contains(".zip!/"
+          + tomcatEtcDir);
+      urlString = urlString.substring(4, urlString.indexOf('!'));
+      url = new URL(urlString);
+      prefix = tomcatEtcDir;
+    }
+    ClassPathEntry entry = ResourceOracleImpl.createEntryForUrl(logger, url);
+    assert (entry != null);
+    ResourceOracleImpl resourceOracle = new ResourceOracleImpl(
+        Collections.singletonList(entry));
+    PathPrefixSet pathPrefixSet = new PathPrefixSet();
+    PathPrefix pathPrefix = new PathPrefix(prefix, null, true);
+    pathPrefixSet.add(pathPrefix);
+    resourceOracle.setPathPrefixes(pathPrefixSet);
+    ResourceOracleImpl.refresh(logger, resourceOracle);
+    Map<String, Resource> resourceMap = resourceOracle.getResourceMap();
+    return resourceMap;
+  }
+
+  private void publishAttributeToWebApp(TreeLogger logger,
+      StandardContext webapp, String attrName, Object attrValue) {
+    if (logger.isLoggable(TreeLogger.TRACE)) {
+      logger.log(TreeLogger.TRACE, "Adding attribute  '" + attrName
+          + "' to web app '" + webapp.getName() + "'", null);
+    }
+    webapp.getServletContext().setAttribute(attrName, attrValue);
+  }
+
+  /**
+   * Publish the shell's tree logger as an attribute. This attribute is used to
+   * find the logger out of the thin air within the shell servlet.
+   */
+  private void publishShellLoggerAttribute(TreeLogger logger,
+      TreeLogger loggerToPublish, StandardContext webapp) {
+    final String attr = "com.google.gwt.dev.shell.logger";
+    publishAttributeToWebApp(logger, webapp, attr, loggerToPublish);
+  }
+
+  /**
+   * Publish the shell's work dir as an attribute. This attribute is used to
+   * find it out of the thin air within the shell servlet.
+   */
+  private void publishShellWorkDirsAttribute(TreeLogger logger,
+      WorkDirs workDirs, StandardContext webapp) {
+    final String attr = "com.google.gwt.dev.shell.workdirs";
+    publishAttributeToWebApp(logger, webapp, attr, workDirs);
+  }
+
+  /**
+   * Publish to the web app whether it should automatically generate resources.
+   */
+  private void publishShouldAutoGenerateResourcesAttribute(TreeLogger logger,
+      boolean shouldAutoGenerateResources, StandardContext webapp) {
+    publishAttributeToWebApp(logger, webapp,
+        "com.google.gwt.dev.shell.shouldAutoGenerateResources",
+        shouldAutoGenerateResources);
+  }
+}
diff --git a/dev/core/src/org/apache/COPYING b/dev/core/src/org/apache/COPYING
new file mode 100644
index 0000000..d9a10c0
--- /dev/null
+++ b/dev/core/src/org/apache/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/dev/core/src/org/apache/catalina/loader/WebappClassLoader.java b/dev/core/src/org/apache/catalina/loader/WebappClassLoader.java
new file mode 100644
index 0000000..f941fad
--- /dev/null
+++ b/dev/core/src/org/apache/catalina/loader/WebappClassLoader.java
@@ -0,0 +1,2169 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * 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.
+ */
+// Modified by Google.
+
+package org.apache.catalina.loader;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FilePermission;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.AccessControlException;
+import java.security.AccessController;
+import java.security.CodeSource;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.security.Policy;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Vector;
+import java.util.jar.Attributes;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.jar.Attributes.Name;
+
+import javax.naming.NameClassPair;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.DirContext;
+
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.util.StringManager;
+import org.apache.naming.JndiPermission;
+import org.apache.naming.resources.Resource;
+import org.apache.naming.resources.ResourceAttributes;
+import org.apache.tomcat.util.compat.JdkCompat;
+
+/**
+ * Specialized web application class loader.
+ * <p>
+ * This class loader is a full reimplementation of the 
+ * <code>URLClassLoader</code> from the JDK. It is desinged to be fully
+ * compatible with a normal <code>URLClassLoader</code>, although its internal
+ * behavior may be completely different.
+ * <p>
+ * <strong>IMPLEMENTATION NOTE</strong> - This class loader faithfully follows 
+ * the delegation model recommended in the specification. The system class 
+ * loader will be queried first, then the local repositories, and only then 
+ * delegation to the parent class loader will occur. This allows the web 
+ * application to override any shared class except the classes from J2SE.
+ * Special handling is provided from the JAXP XML parser interfaces, the JNDI
+ * interfaces, and the classes from the servlet API, which are never loaded 
+ * from the webapp repository.
+ * <p>
+ * <strong>IMPLEMENTATION NOTE</strong> - Due to limitations in Jasper 
+ * compilation technology, any repository which contains classes from 
+ * the servlet API will be ignored by the class loader.
+ * <p>
+ * <strong>IMPLEMENTATION NOTE</strong> - The class loader generates source
+ * URLs which include the full JAR URL when a class is loaded from a JAR file,
+ * which allows setting security permission at the class level, even when a
+ * class is contained inside a JAR.
+ * <p>
+ * <strong>IMPLEMENTATION NOTE</strong> - Local repositories are searched in
+ * the order they are added via the initial constructor and/or any subsequent
+ * calls to <code>addRepository()</code> or <code>addJar()</code>.
+ * <p>
+ * <strong>IMPLEMENTATION NOTE</strong> - No check for sealing violations or
+ * security is made unless a security manager is present.
+ *
+ * @author Remy Maucherat
+ * @author Craig R. McClanahan
+ * @version $Revision: 1.34 $ $Date: 2004/05/26 15:47:40 $
+ */
+public class WebappClassLoader
+    extends URLClassLoader
+    implements Reloader, Lifecycle
+ {
+
+    private static org.apache.commons.logging.Log log=
+        org.apache.commons.logging.LogFactory.getLog( WebappClassLoader.class );
+
+    protected class PrivilegedFindResource
+        implements PrivilegedAction {
+
+        private File file;
+        private String path;
+
+        PrivilegedFindResource(File file, String path) {
+            this.file = file;
+            this.path = path;
+        }
+
+        public Object run() {
+            return findResourceInternal(file, path);
+        }
+
+    }
+
+
+    // ------------------------------------------------------- Static Variables
+
+
+    /**
+     * The set of trigger classes that will cause a proposed repository not
+     * to be added if this class is visible to the class loader that loaded
+     * this factory class.  Typically, trigger classes will be listed for
+     * components that have been integrated into the JDK for later versions,
+     * but where the corresponding JAR files are required to run on
+     * earlier versions.
+     */
+    private static final String[] triggers = {
+        "javax.servlet.Servlet"                     // Servlet API
+    };
+
+    /** 
+     * Jdk Compatibility Support.
+     */
+    private static JdkCompat jdkCompat = JdkCompat.getJdkCompat();
+
+    /**
+     * Set of package names which are not allowed to be loaded from a webapp
+     * class loader without delegating first.
+     */
+    private static final String[] packageTriggers = {
+        "javax",                                     // Java extensions
+        "org.xml.sax",                               // SAX 1 & 2
+        "org.w3c.dom",                               // DOM 1 & 2
+        "org.apache.xerces",                         // Xerces 1 & 2
+        "org.apache.xalan"                           // Xalan
+    };
+
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a new ClassLoader with no defined repositories and no
+     * parent ClassLoader.
+     */
+    public WebappClassLoader() {
+
+        super(new URL[0]);
+        this.parent = getParent();
+        system = getSystemClassLoader();
+        securityManager = System.getSecurityManager();
+
+        if (securityManager != null) {
+            refreshPolicy();
+        }
+
+    }
+
+
+    /**
+     * Construct a new ClassLoader with no defined repositories and no
+     * parent ClassLoader.
+     */
+    public WebappClassLoader(ClassLoader parent) {
+
+        super(new URL[0], parent);
+                
+        this.parent = getParent();
+        
+        system = getSystemClassLoader();
+        securityManager = System.getSecurityManager();
+
+        if (securityManager != null) {
+            refreshPolicy();
+        }
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Associated directory context giving access to the resources in this
+     * webapp.
+     */
+    protected DirContext resources = null;
+
+
+    /**
+     * The cache of ResourceEntry for classes and resources we have loaded,
+     * keyed by resource name.
+     */
+    protected HashMap resourceEntries = new HashMap();
+
+
+    /**
+     * The list of not found resources.
+     */
+    protected HashMap notFoundResources = new HashMap();
+
+
+    /**
+     * The debugging detail level of this component.
+     */
+    protected int debug = 0;
+
+
+    /**
+     * Should this class loader delegate to the parent class loader
+     * <strong>before</strong> searching its own repositories (i.e. the
+     * usual Java2 delegation model)?  If set to <code>false</code>,
+     * this class loader will search its own repositories first, and
+     * delegate to the parent only if the class or resource is not
+     * found locally.
+     */
+    protected boolean delegate = false;
+
+
+    /**
+     * Last time a JAR was accessed.
+     */
+    protected long lastJarAccessed = 0L;
+
+
+    /**
+     * The list of local repositories, in the order they should be searched
+     * for locally loaded classes or resources.
+     */
+    protected String[] repositories = new String[0];
+
+
+     /**
+      * Repositories URLs, used to cache the result of getURLs.
+      */
+     protected URL[] repositoryURLs = null;
+
+
+    /**
+     * Repositories translated as path in the work directory (for Jasper
+     * originally), but which is used to generate fake URLs should getURLs be
+     * called.
+     */
+    protected File[] files = new File[0];
+
+
+    /**
+     * The list of JARs, in the order they should be searched
+     * for locally loaded classes or resources.
+     */
+    protected JarFile[] jarFiles = new JarFile[0];
+
+
+    /**
+     * The list of JARs, in the order they should be searched
+     * for locally loaded classes or resources.
+     */
+    protected File[] jarRealFiles = new File[0];
+
+
+    /**
+     * The path which will be monitored for added Jar files.
+     */
+    protected String jarPath = null;
+
+
+    /**
+     * The list of JARs, in the order they should be searched
+     * for locally loaded classes or resources.
+     */
+    protected String[] jarNames = new String[0];
+
+
+    /**
+     * The list of JARs last modified dates, in the order they should be
+     * searched for locally loaded classes or resources.
+     */
+    protected long[] lastModifiedDates = new long[0];
+
+
+    /**
+     * The list of resources which should be checked when checking for
+     * modifications.
+     */
+    protected String[] paths = new String[0];
+
+
+    /**
+     * A list of read File and Jndi Permission's required if this loader
+     * is for a web application context.
+     */
+    private ArrayList permissionList = new ArrayList();
+
+
+    /**
+     * Path where resources loaded from JARs will be extracted.
+     */
+    private File loaderDir = null;
+
+
+    /**
+     * The PermissionCollection for each CodeSource for a web
+     * application context.
+     */
+    private HashMap loaderPC = new HashMap();
+
+
+    /**
+     * Instance of the SecurityManager installed.
+     */
+    private SecurityManager securityManager = null;
+
+
+    /**
+     * The parent class loader.
+     */
+    private ClassLoader parent = null;
+
+
+    /**
+     * The system class loader.
+     */
+    private ClassLoader system = null;
+
+
+    /**
+     * Has this component been started?
+     */
+    protected boolean started = false;
+
+
+    /**
+     * Has external repositories.
+     */
+    protected boolean hasExternalRepositories = false;
+
+
+    /**
+     * All permission.
+     */
+    private Permission allPermission = new java.security.AllPermission();
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Get associated resources.
+     */
+    public DirContext getResources() {
+
+        return this.resources;
+
+    }
+
+
+    /**
+     * Set associated resources.
+     */
+    public void setResources(DirContext resources) {
+
+        this.resources = resources;
+
+    }
+
+
+    /**
+     * Return the debugging detail level for this component.
+     */
+    public int getDebug() {
+
+        return (this.debug);
+
+    }
+
+
+    /**
+     * Set the debugging detail level for this component.
+     *
+     * @param debug The new debugging detail level
+     */
+    public void setDebug(int debug) {
+
+        this.debug = debug;
+
+    }
+
+
+    /**
+     * Return the "delegate first" flag for this class loader.
+     */
+    public boolean getDelegate() {
+
+        return (this.delegate);
+
+    }
+
+
+    /**
+     * Set the "delegate first" flag for this class loader.
+     *
+     * @param delegate The new "delegate first" flag
+     */
+    public void setDelegate(boolean delegate) {
+
+        this.delegate = delegate;
+
+    }
+
+
+    /**
+     * If there is a Java SecurityManager create a read FilePermission
+     * or JndiPermission for the file directory path.
+     *
+     * @param path file directory path
+     */
+    public void addPermission(String path) {
+        if (path == null) {
+            return;
+        }
+
+        if (securityManager != null) {
+            Permission permission = null;
+            if( path.startsWith("jndi:") || path.startsWith("jar:jndi:") ) {
+                if (!path.endsWith("/")) {
+                    path = path + "/";
+                }
+                permission = new JndiPermission(path + "*");
+                addPermission(permission);
+            } else {
+                if (!path.endsWith(File.separator)) {
+                    permission = new FilePermission(path, "read");
+                    addPermission(permission);
+                    path = path + File.separator;
+                }
+                permission = new FilePermission(path + "-", "read");
+                addPermission(permission);
+            }
+        }
+    }
+
+
+    /**
+     * If there is a Java SecurityManager create a read FilePermission
+     * or JndiPermission for URL.
+     *
+     * @param url URL for a file or directory on local system
+     */
+    public void addPermission(URL url) {
+        if (url != null) {
+            addPermission(url.toString());
+        }
+    }
+
+
+    /**
+     * If there is a Java SecurityManager create a Permission.
+     *
+     * @param url URL for a file or directory on local system
+     */
+    public void addPermission(Permission permission) {
+        if ((securityManager != null) && (permission != null)) {
+            permissionList.add(permission);
+        }
+    }
+
+
+    /**
+     * Return the JAR path.
+     */
+    public String getJarPath() {
+
+        return this.jarPath;
+
+    }
+
+
+    /**
+     * Change the Jar path.
+     */
+    public void setJarPath(String jarPath) {
+
+        this.jarPath = jarPath;
+
+    }
+
+
+    /**
+     * Change the work directory.
+     */
+    public void setWorkDir(File workDir) {
+        this.loaderDir = new File(workDir, "loader");
+    }
+
+
+    // ------------------------------------------------------- Reloader Methods
+
+
+    /**
+     * Add a new repository to the set of places this ClassLoader can look for
+     * classes to be loaded.
+     *
+     * @param repository Name of a source of classes to be loaded, such as a
+     *  directory pathname, a JAR file pathname, or a ZIP file pathname
+     *
+     * @exception IllegalArgumentException if the specified repository is
+     *  invalid or does not exist
+     */
+    public void addRepository(String repository) {
+
+        // Ignore any of the standard repositories, as they are set up using
+        // either addJar or addRepository
+        if (repository.startsWith("/WEB-INF/lib")
+            || repository.startsWith("/WEB-INF/classes"))
+            return;
+
+        // Add this repository to our underlying class loader
+        try {
+            URL url = new URL(repository);
+            super.addURL(url);
+            hasExternalRepositories = true;
+            repositoryURLs = null;
+        } catch (MalformedURLException e) {
+            IllegalArgumentException iae = new IllegalArgumentException
+                ("Invalid repository: " + repository); 
+            jdkCompat.chainException(iae, e);
+            throw iae;
+        }
+
+    }
+
+
+    /**
+     * Add a new repository to the set of places this ClassLoader can look for
+     * classes to be loaded.
+     *
+     * @param repository Name of a source of classes to be loaded, such as a
+     *  directory pathname, a JAR file pathname, or a ZIP file pathname
+     *
+     * @exception IllegalArgumentException if the specified repository is
+     *  invalid or does not exist
+     */
+    synchronized void addRepository(String repository, File file) {
+
+        // Note : There should be only one (of course), but I think we should
+        // keep this a bit generic
+
+        if (repository == null)
+            return;
+
+        if (log.isDebugEnabled())
+            log.debug("addRepository(" + repository + ")");
+
+        int i;
+
+        // Add this repository to our internal list
+        String[] result = new String[repositories.length + 1];
+        for (i = 0; i < repositories.length; i++) {
+            result[i] = repositories[i];
+        }
+        result[repositories.length] = repository;
+        repositories = result;
+
+        // Add the file to the list
+        File[] result2 = new File[files.length + 1];
+        for (i = 0; i < files.length; i++) {
+            result2[i] = files[i];
+        }
+        result2[files.length] = file;
+        files = result2;
+
+    }
+
+
+    synchronized void addJar(String jar, JarFile jarFile, File file)
+        throws IOException {
+
+        if (jar == null)
+            return;
+        if (jarFile == null)
+            return;
+        if (file == null)
+            return;
+
+        if (log.isDebugEnabled())
+            log.debug("addJar(" + jar + ")");
+
+        int i;
+
+        if ((jarPath != null) && (jar.startsWith(jarPath))) {
+
+            String jarName = jar.substring(jarPath.length());
+            while (jarName.startsWith("/"))
+                jarName = jarName.substring(1);
+
+            String[] result = new String[jarNames.length + 1];
+            for (i = 0; i < jarNames.length; i++) {
+                result[i] = jarNames[i];
+            }
+            result[jarNames.length] = jarName;
+            jarNames = result;
+
+        }
+
+        try {
+
+            // Register the JAR for tracking
+
+            long lastModified =
+                ((ResourceAttributes) resources.getAttributes(jar))
+                .getLastModified();
+
+            String[] result = new String[paths.length + 1];
+            for (i = 0; i < paths.length; i++) {
+                result[i] = paths[i];
+            }
+            result[paths.length] = jar;
+            paths = result;
+
+            long[] result3 = new long[lastModifiedDates.length + 1];
+            for (i = 0; i < lastModifiedDates.length; i++) {
+                result3[i] = lastModifiedDates[i];
+            }
+            result3[lastModifiedDates.length] = lastModified;
+            lastModifiedDates = result3;
+
+        } catch (NamingException e) {
+            // Ignore
+        }
+
+        // If the JAR currently contains invalid classes, don't actually use it
+        // for classloading
+        if (!validateJarFile(file))
+            return;
+
+        JarFile[] result2 = new JarFile[jarFiles.length + 1];
+        for (i = 0; i < jarFiles.length; i++) {
+            result2[i] = jarFiles[i];
+        }
+        result2[jarFiles.length] = jarFile;
+        jarFiles = result2;
+
+        // Add the file to the list
+        File[] result4 = new File[jarRealFiles.length + 1];
+        for (i = 0; i < jarRealFiles.length; i++) {
+            result4[i] = jarRealFiles[i];
+        }
+        result4[jarRealFiles.length] = file;
+        jarRealFiles = result4;
+    }
+
+
+    /**
+     * Return a String array of the current repositories for this class
+     * loader.  If there are no repositories, a zero-length array is
+     * returned.For security reason, returns a clone of the Array (since 
+     * String are immutable).
+     */
+    public String[] findRepositories() {
+
+        return ((String[])repositories.clone());
+
+    }
+
+
+    /**
+     * Have one or more classes or resources been modified so that a reload
+     * is appropriate?
+     */
+    public boolean modified() {
+
+        if (log.isDebugEnabled())
+            log.debug("modified()");
+
+        // Checking for modified loaded resources
+        int length = paths.length;
+
+        // A rare race condition can occur in the updates of the two arrays
+        // It's totally ok if the latest class added is not checked (it will
+        // be checked the next time
+        int length2 = lastModifiedDates.length;
+        if (length > length2)
+            length = length2;
+
+        for (int i = 0; i < length; i++) {
+            try {
+                long lastModified =
+                    ((ResourceAttributes) resources.getAttributes(paths[i]))
+                    .getLastModified();
+                if (lastModified != lastModifiedDates[i]) {
+                    if( log.isDebugEnabled() ) 
+                        log.debug("  Resource '" + paths[i]
+                                  + "' was modified; Date is now: "
+                                  + new java.util.Date(lastModified) + " Was: "
+                                  + new java.util.Date(lastModifiedDates[i]));
+                    return (true);
+                }
+            } catch (NamingException e) {
+                log.error("    Resource '" + paths[i] + "' is missing");
+                return (true);
+            }
+        }
+
+        length = jarNames.length;
+
+        // Check if JARs have been added or removed
+        if (getJarPath() != null) {
+
+            try {
+                NamingEnumeration enum_ = resources.listBindings(getJarPath());
+                int i = 0;
+                while (enum_.hasMoreElements() && (i < length)) {
+                    NameClassPair ncPair = (NameClassPair) enum_.nextElement();
+                    String name = ncPair.getName();
+                    // Ignore non JARs present in the lib folder
+                    if (!name.endsWith(".jar"))
+                        continue;
+                    if (!name.equals(jarNames[i])) {
+                        // Missing JAR
+                        log.info("    Additional JARs have been added : '" 
+                                 + name + "'");
+                        return (true);
+                    }
+                    i++;
+                }
+                if (enum_.hasMoreElements()) {
+                    while (enum_.hasMoreElements()) {
+                        NameClassPair ncPair = 
+                            (NameClassPair) enum_.nextElement();
+                        String name = ncPair.getName();
+                        // Additional non-JAR files are allowed
+                        if (name.endsWith(".jar")) {
+                            // There was more JARs
+                            log.info("    Additional JARs have been added");
+                            return (true);
+                        }
+                    }
+                } else if (i < jarNames.length) {
+                    // There was less JARs
+                    log.info("    Additional JARs have been added");
+                    return (true);
+                }
+            } catch (NamingException e) {
+                if (log.isDebugEnabled())
+                    log.debug("    Failed tracking modifications of '"
+                        + getJarPath() + "'");
+            } catch (ClassCastException e) {
+                log.error("    Failed tracking modifications of '"
+                          + getJarPath() + "' : " + e.getMessage());
+            }
+
+        }
+
+        // No classes have been modified
+        return (false);
+
+    }
+
+
+    /**
+     * Render a String representation of this object.
+     */
+    public String toString() {
+
+        StringBuffer sb = new StringBuffer("WebappClassLoader\r\n");
+        sb.append("  delegate: ");
+        sb.append(delegate);
+        sb.append("\r\n");
+        sb.append("  repositories:\r\n");
+        if (repositories != null) {
+            for (int i = 0; i < repositories.length; i++) {
+                sb.append("    ");
+                sb.append(repositories[i]);
+                sb.append("\r\n");
+            }
+        }
+        if (this.parent != null) {
+            sb.append("----------> Parent Classloader:\r\n");
+            sb.append(this.parent.toString());
+            sb.append("\r\n");
+        }
+        return (sb.toString());
+
+    }
+
+
+    // ---------------------------------------------------- ClassLoader Methods
+
+
+     /**
+      * Add the specified URL to the classloader.
+      */
+     protected void addURL(URL url) {
+         super.addURL(url);
+         hasExternalRepositories = true;
+         repositoryURLs = null;
+     }
+
+
+    /**
+     * Find the specified class in our local repositories, if possible.  If
+     * not found, throw <code>ClassNotFoundException</code>.
+     *
+     * @param name Name of the class to be loaded
+     *
+     * @exception ClassNotFoundException if the class was not found
+     */
+    public Class findClass(String name) throws ClassNotFoundException {
+
+        if (log.isDebugEnabled())
+            log.debug("    findClass(" + name + ")");
+
+        // (1) Permission to define this class when using a SecurityManager
+        if (securityManager != null) {
+            int i = name.lastIndexOf('.');
+            if (i >= 0) {
+                try {
+                    if (log.isTraceEnabled())
+                        log.trace("      securityManager.checkPackageDefinition");
+                    securityManager.checkPackageDefinition(name.substring(0,i));
+                } catch (Exception se) {
+                    if (log.isTraceEnabled())
+                        log.trace("      -->Exception-->ClassNotFoundException", se);
+                    throw new ClassNotFoundException(name, se);
+                }
+            }
+        }
+
+        // Ask our superclass to locate this class, if possible
+        // (throws ClassNotFoundException if it is not found)
+        Class clazz = null;
+        try {
+            if (log.isTraceEnabled())
+                log.trace("      findClassInternal(" + name + ")");
+            try {
+                clazz = findClassInternal(name);
+            } catch(ClassNotFoundException cnfe) {
+                if (!hasExternalRepositories) {
+                    throw cnfe;
+                }
+            } catch(AccessControlException ace) {
+                throw new ClassNotFoundException(name, ace);
+            } catch (RuntimeException e) {
+                if (log.isTraceEnabled())
+                    log.trace("      -->RuntimeException Rethrown", e);
+                throw e;
+            }
+            if ((clazz == null) && hasExternalRepositories) {
+                try {
+                    clazz = super.findClass(name);
+                } catch(AccessControlException ace) {
+                    throw new ClassNotFoundException(name, ace);
+                } catch (RuntimeException e) {
+                    if (log.isTraceEnabled())
+                        log.trace("      -->RuntimeException Rethrown", e);
+                    throw e;
+                }
+            }
+            if (clazz == null) {
+                if (log.isDebugEnabled())
+                    log.debug("    --> Returning ClassNotFoundException");
+                throw new ClassNotFoundException(name);
+            }
+        } catch (ClassNotFoundException e) {
+            if (log.isTraceEnabled())
+                log.trace("    --> Passing on ClassNotFoundException");
+            throw e;
+        }
+
+        // Return the class we have located
+        if (log.isTraceEnabled())
+            log.debug("      Returning class " + clazz);
+        if ((log.isTraceEnabled()) && (clazz != null))
+            log.debug("      Loaded by " + clazz.getClassLoader());
+        return (clazz);
+
+    }
+
+
+    /**
+     * Find the specified resource in our local repository, and return a
+     * <code>URL</code> refering to it, or <code>null</code> if this resource
+     * cannot be found.
+     *
+     * @param name Name of the resource to be found
+     */
+    public URL findResource(final String name) {
+
+        if (log.isDebugEnabled())
+            log.debug("    findResource(" + name + ")");
+
+        URL url = null;
+
+        ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
+        if (entry == null) {
+            entry = findResourceInternal(name, name);
+        }
+        if (entry != null) {
+            url = entry.source;
+        }
+
+        if ((url == null) && hasExternalRepositories)
+            url = super.findResource(name);
+
+        if (log.isDebugEnabled()) {
+            if (url != null)
+                log.debug("    --> Returning '" + url.toString() + "'");
+            else
+                log.debug("    --> Resource not found, returning null");
+        }
+        return (url);
+
+    }
+
+
+    /**
+     * Return an enumeration of <code>URLs</code> representing all of the
+     * resources with the given name.  If no resources with this name are
+     * found, return an empty enumeration.
+     *
+     * @param name Name of the resources to be found
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    public Enumeration findResources(String name) throws IOException {
+
+        if (log.isDebugEnabled())
+            log.debug("    findResources(" + name + ")");
+
+        Vector result = new Vector();
+
+        int jarFilesLength = jarFiles.length;
+        int repositoriesLength = repositories.length;
+
+        int i;
+
+        // Looking at the repositories
+        for (i = 0; i < repositoriesLength; i++) {
+            try {
+                String fullPath = repositories[i] + name;
+                resources.lookup(fullPath);
+                // Note : Not getting an exception here means the resource was
+                // found
+                try {
+                    result.addElement(getURI(new File(files[i], name)));
+                } catch (MalformedURLException e) {
+                    // Ignore
+                }
+            } catch (NamingException e) {
+            }
+        }
+
+        // Looking at the JAR files
+        synchronized (jarFiles) {
+            openJARs();
+            for (i = 0; i < jarFilesLength; i++) {
+                JarEntry jarEntry = jarFiles[i].getJarEntry(name);
+                if (jarEntry != null) {
+                    try {
+                        String jarFakeUrl = getURI(jarRealFiles[i]).toString();
+                        jarFakeUrl = "jar:" + jarFakeUrl + "!/" + name;
+                        result.addElement(new URL(jarFakeUrl));
+                    } catch (MalformedURLException e) {
+                        // Ignore
+                    }
+                }
+            }
+        }
+
+        // Adding the results of a call to the superclass
+        if (hasExternalRepositories) {
+
+            Enumeration otherResourcePaths = super.findResources(name);
+
+            while (otherResourcePaths.hasMoreElements()) {
+                result.addElement(otherResourcePaths.nextElement());
+            }
+
+        }
+
+        return result.elements();
+
+    }
+
+
+    /**
+     * Find the resource with the given name.  A resource is some data
+     * (images, audio, text, etc.) that can be accessed by class code in a
+     * way that is independent of the location of the code.  The name of a
+     * resource is a "/"-separated path name that identifies the resource.
+     * If the resource cannot be found, return <code>null</code>.
+     * <p>
+     * This method searches according to the following algorithm, returning
+     * as soon as it finds the appropriate URL.  If the resource cannot be
+     * found, returns <code>null</code>.
+     * <ul>
+     * <li>If the <code>delegate</code> property is set to <code>true</code>,
+     *     call the <code>getResource()</code> method of the parent class
+     *     loader, if any.</li>
+     * <li>Call <code>findResource()</code> to find this resource in our
+     *     locally defined repositories.</li>
+     * <li>Call the <code>getResource()</code> method of the parent class
+     *     loader, if any.</li>
+     * </ul>
+     *
+     * @param name Name of the resource to return a URL for
+     */
+    public URL getResource(String name) {
+
+        if (log.isDebugEnabled())
+            log.debug("getResource(" + name + ")");
+        URL url = null;
+
+        // (1) Delegate to parent if requested
+        if (delegate) {
+            if (log.isDebugEnabled())
+                log.debug("  Delegating to parent classloader " + parent);
+            ClassLoader loader = parent;
+            if (loader == null)
+                loader = system;
+            url = loader.getResource(name);
+            if (url != null) {
+                if (log.isDebugEnabled())
+                    log.debug("  --> Returning '" + url.toString() + "'");
+                return (url);
+            }
+        }
+
+        // (2) Search local repositories
+        url = findResource(name);
+        if (url != null) {
+            // Locating the repository for special handling in the case 
+            // of a JAR
+            ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
+            try {
+                String repository = entry.codeBase.toString();
+                if ((repository.endsWith(".jar")) 
+                    && (!(name.endsWith(".class")))) {
+                    // Copy binary content to the work directory if not present
+                    File resourceFile = new File(loaderDir, name);
+                    url = resourceFile.toURL();
+                }
+            } catch (Exception e) {
+                // Ignore
+            }
+            if (log.isDebugEnabled())
+                log.debug("  --> Returning '" + url.toString() + "'");
+            return (url);
+        }
+
+        // (3) Delegate to parent unconditionally if not already attempted
+        if( !delegate ) {
+            ClassLoader loader = parent;
+            if (loader == null)
+                loader = system;
+            url = loader.getResource(name);
+            if (url != null) {
+                if (log.isDebugEnabled())
+                    log.debug("  --> Returning '" + url.toString() + "'");
+                return (url);
+            }
+        }
+
+        // (4) Resource was not found
+        if (log.isDebugEnabled())
+            log.debug("  --> Resource not found, returning null");
+        return (null);
+
+    }
+
+
+    /**
+     * Find the resource with the given name, and return an input stream
+     * that can be used for reading it.  The search order is as described
+     * for <code>getResource()</code>, after checking to see if the resource
+     * data has been previously cached.  If the resource cannot be found,
+     * return <code>null</code>.
+     *
+     * @param name Name of the resource to return an input stream for
+     */
+    public InputStream getResourceAsStream(String name) {
+
+        if (log.isDebugEnabled())
+            log.debug("getResourceAsStream(" + name + ")");
+        InputStream stream = null;
+
+        // (0) Check for a cached copy of this resource
+        stream = findLoadedResource(name);
+        if (stream != null) {
+            if (log.isDebugEnabled())
+                log.debug("  --> Returning stream from cache");
+            return (stream);
+        }
+
+        // (1) Delegate to parent if requested
+        if (delegate) {
+            if (log.isDebugEnabled())
+                log.debug("  Delegating to parent classloader " + parent);
+            ClassLoader loader = parent;
+            if (loader == null)
+                loader = system;
+            stream = loader.getResourceAsStream(name);
+            if (stream != null) {
+                // FIXME - cache???
+                if (log.isDebugEnabled())
+                    log.debug("  --> Returning stream from parent");
+                return (stream);
+            }
+        }
+
+        // (2) Search local repositories
+        if (log.isDebugEnabled())
+            log.debug("  Searching local repositories");
+        URL url = findResource(name);
+        if (url != null) {
+            // FIXME - cache???
+            if (log.isDebugEnabled())
+                log.debug("  --> Returning stream from local");
+            stream = findLoadedResource(name);
+            try {
+                if (hasExternalRepositories && (stream == null))
+                    stream = url.openStream();
+            } catch (IOException e) {
+                ; // Ignore
+            }
+            if (stream != null)
+                return (stream);
+        }
+
+        // (3) Delegate to parent unconditionally
+        if (!delegate) {
+            if (log.isDebugEnabled())
+                log.debug("  Delegating to parent classloader unconditionally " + parent);
+            ClassLoader loader = parent;
+            if (loader == null)
+                loader = system;
+            stream = loader.getResourceAsStream(name);
+            if (stream != null) {
+                // FIXME - cache???
+                if (log.isDebugEnabled())
+                    log.debug("  --> Returning stream from parent");
+                return (stream);
+            }
+        }
+
+        // (4) Resource was not found
+        if (log.isDebugEnabled())
+            log.debug("  --> Resource not found, returning null");
+        return (null);
+
+    }
+
+
+    /**
+     * Load the class with the specified name.  This method searches for
+     * classes in the same manner as <code>loadClass(String, boolean)</code>
+     * with <code>false</code> as the second argument.
+     *
+     * @param name Name of the class to be loaded
+     *
+     * @exception ClassNotFoundException if the class was not found
+     */
+    public Class loadClass(String name) throws ClassNotFoundException {
+
+        return (loadClass(name, false));
+
+    }
+
+
+    /**
+     * Load the class with the specified name, searching using the following
+     * algorithm until it finds and returns the class.  If the class cannot
+     * be found, returns <code>ClassNotFoundException</code>.
+     * <ul>
+     * <li>Call <code>findLoadedClass(String)</code> to check if the
+     *     class has already been loaded.  If it has, the same
+     *     <code>Class</code> object is returned.</li>
+     * <li>If the <code>delegate</code> property is set to <code>true</code>,
+     *     call the <code>loadClass()</code> method of the parent class
+     *     loader, if any.</li>
+     * <li>Call <code>findClass()</code> to find this class in our locally
+     *     defined repositories.</li>
+     * <li>Call the <code>loadClass()</code> method of our parent
+     *     class loader, if any.</li>
+     * </ul>
+     * If the class was found using the above steps, and the
+     * <code>resolve</code> flag is <code>true</code>, this method will then
+     * call <code>resolveClass(Class)</code> on the resulting Class object.
+     *
+     * @param name Name of the class to be loaded
+     * @param resolve If <code>true</code> then resolve the class
+     *
+     * @exception ClassNotFoundException if the class was not found
+     */
+    public Class loadClass(String name, boolean resolve)
+        throws ClassNotFoundException {
+
+        if (log.isDebugEnabled())
+            log.debug("loadClass(" + name + ", " + resolve + ")");
+        Class clazz = null;
+
+        // Don't load classes if class loader is stopped
+        if (!started) {
+            log.info(sm.getString("webappClassLoader.stopped"));
+            throw new ThreadDeath();
+        }
+
+        // (0) Check our previously loaded local class cache
+        clazz = findLoadedClass0(name);
+        if (clazz != null) {
+            if (log.isDebugEnabled())
+                log.debug("  Returning class from cache");
+            if (resolve)
+                resolveClass(clazz);
+            return (clazz);
+        }
+
+        // (0.1) Check our previously loaded class cache
+        clazz = findLoadedClass(name);
+        if (clazz != null) {
+            if (log.isDebugEnabled())
+                log.debug("  Returning class from cache");
+            if (resolve)
+                resolveClass(clazz);
+            return (clazz);
+        }
+
+        // (0.2) Try loading the class with the system class loader, to prevent
+        //       the webapp from overriding J2SE classes
+        // GOOGLE: use the bootstrap loader, not the system loader; it breaks
+        //       embedding.
+        try {
+            // clazz = system.loadClass(name);
+             clazz = Class.forName(name, false, null);
+             if (clazz != null) {
+                 if (resolve)
+                     resolveClass(clazz);
+                 return (clazz);
+             }
+        } catch (ClassNotFoundException e) {
+            // Ignore
+        }
+
+        // (0.5) Permission to access this class when using a SecurityManager
+        if (securityManager != null) {
+            int i = name.lastIndexOf('.');
+            if (i >= 0) {
+                try {
+                    securityManager.checkPackageAccess(name.substring(0,i));
+                } catch (SecurityException se) {
+                    String error = "Security Violation, attempt to use " +
+                        "Restricted Class: " + name;
+                    log.info(error, se);
+                    throw new ClassNotFoundException(error, se);
+                }
+            }
+        }
+
+        boolean delegateLoad = delegate || filter(name);
+
+        // (1) Delegate to our parent if requested
+        if (delegateLoad) {
+            if (log.isDebugEnabled())
+                log.debug("  Delegating to parent classloader1 " + parent);
+            ClassLoader loader = parent;
+            if (loader == null)
+                loader = system;
+            try {
+                clazz = loader.loadClass(name);
+                if (clazz != null) {
+                    if (log.isDebugEnabled())
+                        log.debug("  Loading class from parent");
+                    if (resolve)
+                        resolveClass(clazz);
+                    return (clazz);
+                }
+            } catch (ClassNotFoundException e) {
+                ;
+            }
+        }
+
+        // (2) Search local repositories
+        if (log.isDebugEnabled())
+            log.debug("  Searching local repositories");
+        try {
+            clazz = findClass(name);
+            if (clazz != null) {
+                if (log.isDebugEnabled())
+                    log.debug("  Loading class from local repository");
+                if (resolve)
+                    resolveClass(clazz);
+                return (clazz);
+            }
+        } catch (ClassNotFoundException e) {
+            ;
+        }
+
+        // (3) Delegate to parent unconditionally
+        if (!delegateLoad) {
+            if (log.isDebugEnabled())
+                log.debug("  Delegating to parent classloader at end: " + parent);
+            ClassLoader loader = parent;
+            if (loader == null)
+                loader = system;
+            try {
+                clazz = loader.loadClass(name);
+                if (clazz != null) {
+                    if (log.isDebugEnabled())
+                        log.debug("  Loading class from parent");
+                    if (resolve)
+                        resolveClass(clazz);
+                    return (clazz);
+                }
+            } catch (ClassNotFoundException e) {
+                ;
+            }
+        }
+
+        throw new ClassNotFoundException(name);
+    }
+
+
+    /**
+     * Get the Permissions for a CodeSource.  If this instance
+     * of WebappClassLoader is for a web application context,
+     * add read FilePermission or JndiPermissions for the base
+     * directory (if unpacked),
+     * the context URL, and jar file resources.
+     *
+     * @param codeSource where the code was loaded from
+     * @return PermissionCollection for CodeSource
+     */
+    protected PermissionCollection getPermissions(CodeSource codeSource) {
+
+        String codeUrl = codeSource.getLocation().toString();
+        PermissionCollection pc;
+        if ((pc = (PermissionCollection)loaderPC.get(codeUrl)) == null) {
+            pc = super.getPermissions(codeSource);
+            if (pc != null) {
+                Iterator perms = permissionList.iterator();
+                while (perms.hasNext()) {
+                    Permission p = (Permission)perms.next();
+                    pc.add(p);
+                }
+                loaderPC.put(codeUrl,pc);
+            }
+        }
+        return (pc);
+
+    }
+
+
+    /**
+     * Returns the search path of URLs for loading classes and resources.
+     * This includes the original list of URLs specified to the constructor,
+     * along with any URLs subsequently appended by the addURL() method.
+     * @return the search path of URLs for loading classes and resources.
+     */
+    public URL[] getURLs() {
+
+        if (repositoryURLs != null) {
+            return repositoryURLs;
+        }
+
+        URL[] external = super.getURLs();
+
+        int filesLength = files.length;
+        int jarFilesLength = jarRealFiles.length;
+        int length = filesLength + jarFilesLength + external.length;
+        int i;
+
+        try {
+
+            URL[] urls = new URL[length];
+            for (i = 0; i < length; i++) {
+                if (i < filesLength) {
+                    urls[i] = getURL(files[i]);
+                } else if (i < filesLength + jarFilesLength) {
+                    urls[i] = getURL(jarRealFiles[i - filesLength]);
+                } else {
+                    urls[i] = external[i - filesLength - jarFilesLength];
+                }
+            }
+
+            repositoryURLs = urls;
+
+        } catch (MalformedURLException e) {
+            repositoryURLs = new URL[0];
+        }
+
+        return repositoryURLs;
+
+    }
+
+
+    // ------------------------------------------------------ Lifecycle Methods
+
+
+    /**
+     * Add a lifecycle event listener to this component.
+     *
+     * @param listener The listener to add
+     */
+    public void addLifecycleListener(LifecycleListener listener) {
+    }
+
+
+    /**
+     * Get the lifecycle listeners associated with this lifecycle. If this 
+     * Lifecycle has no listeners registered, a zero-length array is returned.
+     */
+    public LifecycleListener[] findLifecycleListeners() {
+        return new LifecycleListener[0];
+    }
+
+
+    /**
+     * Remove a lifecycle event listener from this component.
+     *
+     * @param listener The listener to remove
+     */
+    public void removeLifecycleListener(LifecycleListener listener) {
+    }
+
+
+    /**
+     * Start the class loader.
+     *
+     * @exception LifecycleException if a lifecycle error occurs
+     */
+    public void start() throws LifecycleException {
+
+        started = true;
+
+    }
+
+
+    /**
+     * Stop the class loader.
+     *
+     * @exception LifecycleException if a lifecycle error occurs
+     */
+    public void stop() throws LifecycleException {
+
+        started = false;
+
+        int length = files.length;
+        for (int i = 0; i < length; i++) {
+            files[i] = null;
+        }
+
+        length = jarFiles.length;
+        for (int i = 0; i < length; i++) {
+            try {
+                if (jarFiles[i] != null) {
+                    jarFiles[i].close();
+                }
+            } catch (IOException e) {
+                // Ignore
+            }
+            jarFiles[i] = null;
+        }
+
+        notFoundResources.clear();
+        resourceEntries.clear();
+        resources = null;
+        repositories = null;
+        repositoryURLs = null;
+        files = null;
+        jarFiles = null;
+        jarRealFiles = null;
+        jarPath = null;
+        jarNames = null;
+        lastModifiedDates = null;
+        paths = null;
+        hasExternalRepositories = false;
+        parent = null;
+
+        permissionList.clear();
+        loaderPC.clear();
+
+        if (loaderDir != null) {
+            deleteDir(loaderDir);
+        }
+
+        org.apache.commons.logging.LogFactory.release(this);
+
+    }
+
+
+    /**
+     * Used to periodically signal to the classloader to release 
+     * JAR resources.
+     */
+    public void closeJARs(boolean force) {
+        if (jarFiles.length > 0) {
+            try {
+                synchronized (jarFiles) {
+                    if (force || (System.currentTimeMillis() 
+                                  > (lastJarAccessed + 90000))) {
+                        for (int i = 0; i < jarFiles.length; i++) {
+                            if (jarFiles[i] != null) {
+                                jarFiles[i].close();
+                                jarFiles[i] = null;
+                            }
+                        }
+                    }
+                }
+            } catch (IOException e) {
+                log("Failed to close JAR", e);
+            }
+        }
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Used to periodically signal to the classloader to release JAR resources.
+     */
+    protected void openJARs() {
+        if (started && (jarFiles.length > 0)) {
+            lastJarAccessed = System.currentTimeMillis();
+            if (jarFiles[0] == null) {
+                try {
+                    for (int i = 0; i < jarFiles.length; i++) {
+                        jarFiles[i] = new JarFile(jarRealFiles[i]);
+                    }
+                } catch (IOException e) {
+                    log("Failed to open JAR", e);
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Find specified class in local repositories.
+     *
+     * @return the loaded class, or null if the class isn't found
+     */
+    protected Class findClassInternal(String name)
+        throws ClassNotFoundException {
+
+        if (!validate(name))
+            throw new ClassNotFoundException(name);
+
+        String tempPath = name.replace('.', '/');
+        String classPath = tempPath + ".class";
+
+        ResourceEntry entry = null;
+
+        entry = findResourceInternal(name, classPath);
+
+        if ((entry == null) || (entry.binaryContent == null))
+            throw new ClassNotFoundException(name);
+
+        Class clazz = entry.loadedClass;
+        if (clazz != null)
+            return clazz;
+
+        // Looking up the package
+        String packageName = null;
+        int pos = name.lastIndexOf('.');
+        if (pos != -1)
+            packageName = name.substring(0, pos);
+
+        Package pkg = null;
+
+        if (packageName != null) {
+
+            pkg = getPackage(packageName);
+
+            // Define the package (if null)
+            if (pkg == null) {
+                if (entry.manifest == null) {
+                    definePackage(packageName, null, null, null, null, null,
+                                  null, null);
+                } else {
+                    definePackage(packageName, entry.manifest, entry.codeBase);
+                }
+            }
+
+        }
+
+        // Create the code source object
+        CodeSource codeSource =
+            new CodeSource(entry.codeBase, entry.certificates);
+
+        if (securityManager != null) {
+
+            // Checking sealing
+            if (pkg != null) {
+                boolean sealCheck = true;
+                if (pkg.isSealed()) {
+                    sealCheck = pkg.isSealed(entry.codeBase);
+                } else {
+                    sealCheck = (entry.manifest == null)
+                        || !isPackageSealed(packageName, entry.manifest);
+                }
+                if (!sealCheck)
+                    throw new SecurityException
+                        ("Sealing violation loading " + name + " : Package "
+                         + packageName + " is sealed.");
+            }
+
+        }
+
+        if (entry.loadedClass == null) {
+            synchronized (this) {
+                if (entry.loadedClass == null) {
+                    clazz = defineClass(name, entry.binaryContent, 0,
+                                        entry.binaryContent.length, 
+                                        codeSource);
+                    entry.loadedClass = clazz;
+                    entry.binaryContent = null;
+                    entry.source = null;
+                    entry.codeBase = null;
+                    entry.manifest = null;
+                    entry.certificates = null;
+                } else {
+                    clazz = entry.loadedClass;
+                }
+            }
+        } else {
+            clazz = entry.loadedClass;
+        }
+
+        return clazz;
+
+    }
+
+    /**
+     * Find specified resource in local repositories. This block
+     * will execute under an AccessControl.doPrivilege block.
+     *
+     * @return the loaded resource, or null if the resource isn't found
+     */
+    private ResourceEntry findResourceInternal(File file, String path){
+        ResourceEntry entry = new ResourceEntry();
+        try {
+            entry.source = getURI(new File(file, path));
+            entry.codeBase = getURL(new File(file, path));
+        } catch (MalformedURLException e) {
+            return null;
+        }   
+        return entry;
+    }
+    
+
+    /**
+     * Find specified resource in local repositories.
+     *
+     * @return the loaded resource, or null if the resource isn't found
+     */
+    protected ResourceEntry findResourceInternal(String name, String path) {
+
+        if (!started) {
+            log.info(sm.getString("webappClassLoader.stopped"));
+            return null;
+        }
+
+        if ((name == null) || (path == null))
+            return null;
+
+        ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
+        if (entry != null)
+            return entry;
+
+        int contentLength = -1;
+        InputStream binaryStream = null;
+
+        int jarFilesLength = jarFiles.length;
+        int repositoriesLength = repositories.length;
+
+        int i;
+
+        Resource resource = null;
+
+        for (i = 0; (entry == null) && (i < repositoriesLength); i++) {
+            try {
+
+                String fullPath = repositories[i] + path;
+
+                Object lookupResult = resources.lookup(fullPath);
+                if (lookupResult instanceof Resource) {
+                    resource = (Resource) lookupResult;
+                }
+
+                // Note : Not getting an exception here means the resource was
+                // found
+                 if (securityManager != null) {
+                    PrivilegedAction dp =
+                        new PrivilegedFindResource(files[i], path);
+                    entry = (ResourceEntry)AccessController.doPrivileged(dp);
+                 } else {
+                    entry = findResourceInternal(files[i], path);
+                 }
+
+                ResourceAttributes attributes =
+                    (ResourceAttributes) resources.getAttributes(fullPath);
+                contentLength = (int) attributes.getContentLength();
+                entry.lastModified = attributes.getLastModified();
+
+                if (resource != null) {
+
+                    try {
+                        binaryStream = resource.streamContent();
+                    } catch (IOException e) {
+                        return null;
+                    }
+
+                    // Register the full path for modification checking
+                    // Note: Only syncing on a 'constant' object is needed
+                    synchronized (allPermission) {
+
+                        int j;
+
+                        long[] result2 = 
+                            new long[lastModifiedDates.length + 1];
+                        for (j = 0; j < lastModifiedDates.length; j++) {
+                            result2[j] = lastModifiedDates[j];
+                        }
+                        result2[lastModifiedDates.length] = entry.lastModified;
+                        lastModifiedDates = result2;
+
+                        String[] result = new String[paths.length + 1];
+                        for (j = 0; j < paths.length; j++) {
+                            result[j] = paths[j];
+                        }
+                        result[paths.length] = fullPath;
+                        paths = result;
+
+                    }
+
+                }
+
+            } catch (NamingException e) {
+            }
+        }
+
+        if ((entry == null) && (notFoundResources.containsKey(name)))
+            return null;
+
+        JarEntry jarEntry = null;
+
+        synchronized (jarFiles) {
+
+            openJARs();
+            for (i = 0; (entry == null) && (i < jarFilesLength); i++) {
+
+                jarEntry = jarFiles[i].getJarEntry(path);
+
+                if (jarEntry != null) {
+
+                    entry = new ResourceEntry();
+                    try {
+                        entry.codeBase = getURL(jarRealFiles[i]);
+                        String jarFakeUrl = getURI(jarRealFiles[i]).toString();
+                        jarFakeUrl = "jar:" + jarFakeUrl + "!/" + path;
+                        entry.source = new URL(jarFakeUrl);
+                        entry.lastModified = jarRealFiles[i].lastModified();
+                    } catch (MalformedURLException e) {
+                        return null;
+                    }
+                    contentLength = (int) jarEntry.getSize();
+                    try {
+                        entry.manifest = jarFiles[i].getManifest();
+                        binaryStream = jarFiles[i].getInputStream(jarEntry);
+                    } catch (IOException e) {
+                        return null;
+                    }
+
+                    // Extract resources contained in JAR to the workdir
+                    if (!(path.endsWith(".class"))) {
+                        byte[] buf = new byte[1024];
+                        File resourceFile = new File
+                            (loaderDir, jarEntry.getName());
+                        if (!resourceFile.exists()) {
+                            Enumeration entries = jarFiles[i].entries();
+                            while (entries.hasMoreElements()) {
+                                JarEntry jarEntry2 = 
+                                    (JarEntry) entries.nextElement();
+                                if (!(jarEntry2.isDirectory()) 
+                                    && (!jarEntry2.getName().endsWith
+                                        (".class"))) {
+                                    resourceFile = new File
+                                        (loaderDir, jarEntry2.getName());
+                                    // No need to check mkdirs result because an
+                                    // IOException will occur anyway
+                                    resourceFile.getParentFile().mkdirs();
+                                    FileOutputStream os = null;
+                                    InputStream is = null;
+                                    try {
+                                        is = jarFiles[i].getInputStream
+                                            (jarEntry2);
+                                        os = new FileOutputStream
+                                            (resourceFile);
+                                        while (true) {
+                                            int n = is.read(buf);
+                                            if (n <= 0) {
+                                                break;
+                                            }
+                                            os.write(buf, 0, n);
+                                        }
+                                    } catch (IOException e) {
+                                        // Ignore
+                                    } finally {
+                                        try {
+                                            if (is != null) {
+                                                is.close();
+                                            }
+                                        } catch (IOException e) {
+                                        }
+                                        try {
+                                            if (os != null) {
+                                                os.close();
+                                            }
+                                        } catch (IOException e) {
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+
+                }
+
+            }
+
+            if (entry == null) {
+                synchronized (notFoundResources) {
+                    notFoundResources.put(name, name);
+                }
+                return null;
+            }
+
+            if (binaryStream != null) {
+
+                byte[] binaryContent = new byte[contentLength];
+
+                try {
+                    int pos = 0;
+
+                    while (true) {
+                        int n = binaryStream.read(binaryContent, pos,
+                                                  binaryContent.length - pos);
+                        if (n <= 0)
+                            break;
+                        pos += n;
+                    }
+                    binaryStream.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                    return null;
+                } catch (Exception e) {
+                    e.printStackTrace();
+                    return null;
+                }
+
+                entry.binaryContent = binaryContent;
+
+                // The certificates are only available after the JarEntry 
+                // associated input stream has been fully read
+                if (jarEntry != null) {
+                    entry.certificates = jarEntry.getCertificates();
+                }
+
+            }
+
+        }
+
+        // Add the entry in the local resource repository
+        synchronized (resourceEntries) {
+            // Ensures that all the threads which may be in a race to load
+            // a particular class all end up with the same ResourceEntry
+            // instance
+            ResourceEntry entry2 = (ResourceEntry) resourceEntries.get(name);
+            if (entry2 == null) {
+                resourceEntries.put(name, entry);
+            } else {
+                entry = entry2;
+            }
+        }
+
+        return entry;
+
+    }
+
+
+    /**
+     * Returns true if the specified package name is sealed according to the
+     * given manifest.
+     */
+    protected boolean isPackageSealed(String name, Manifest man) {
+
+        String path = name + "/";
+        Attributes attr = man.getAttributes(path);
+        String sealed = null;
+        if (attr != null) {
+            sealed = attr.getValue(Name.SEALED);
+        }
+        if (sealed == null) {
+            if ((attr = man.getMainAttributes()) != null) {
+                sealed = attr.getValue(Name.SEALED);
+            }
+        }
+        return "true".equalsIgnoreCase(sealed);
+
+    }
+
+
+    /**
+     * Finds the resource with the given name if it has previously been
+     * loaded and cached by this class loader, and return an input stream
+     * to the resource data.  If this resource has not been cached, return
+     * <code>null</code>.
+     *
+     * @param name Name of the resource to return
+     */
+    protected InputStream findLoadedResource(String name) {
+
+        ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
+        if (entry != null) {
+            if (entry.binaryContent != null)
+                return new ByteArrayInputStream(entry.binaryContent);
+        }
+        return (null);
+
+    }
+
+
+    /**
+     * Finds the class with the given name if it has previously been
+     * loaded and cached by this class loader, and return the Class object.
+     * If this class has not been cached, return <code>null</code>.
+     *
+     * @param name Name of the resource to return
+     */
+    protected Class findLoadedClass0(String name) {
+
+        ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
+        if (entry != null) {
+            return entry.loadedClass;
+        }
+        return (null);  // FIXME - findLoadedResource()
+
+    }
+
+
+    /**
+     * Refresh the system policy file, to pick up eventual changes.
+     */
+    protected void refreshPolicy() {
+
+        try {
+            // The policy file may have been modified to adjust 
+            // permissions, so we're reloading it when loading or 
+            // reloading a Context
+            Policy policy = Policy.getPolicy();
+            policy.refresh();
+        } catch (AccessControlException e) {
+            // Some policy files may restrict this, even for the core,
+            // so this exception is ignored
+        }
+
+    }
+
+
+    /**
+     * Filter classes.
+     * 
+     * @param name class name
+     * @return true if the class should be filtered
+     */
+    protected boolean filter(String name) {
+
+        if (name == null)
+            return false;
+
+        // Looking up the package
+        String packageName = null;
+        int pos = name.lastIndexOf('.');
+        if (pos != -1)
+            packageName = name.substring(0, pos);
+        else
+            return false;
+
+        for (int i = 0; i < packageTriggers.length; i++) {
+            if (packageName.startsWith(packageTriggers[i]))
+                return true;
+        }
+
+        return false;
+
+    }
+
+
+    /**
+     * Validate a classname. As per SRV.9.7.2, we must restict loading of 
+     * classes from J2SE (java.*) and classes of the servlet API 
+     * (javax.servlet.*). That should enhance robustness and prevent a number
+     * of user error (where an older version of servlet.jar would be present
+     * in /WEB-INF/lib).
+     * 
+     * @param name class name
+     * @return true if the name is valid
+     */
+    protected boolean validate(String name) {
+
+        if (name == null)
+            return false;
+        if (name.startsWith("java."))
+            return false;
+
+        return true;
+
+    }
+
+
+    /**
+     * Check the specified JAR file, and return <code>true</code> if it does
+     * not contain any of the trigger classes.
+     *
+     * @param jarfile The JAR file to be checked
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    private boolean validateJarFile(File jarfile)
+        throws IOException {
+
+        if (triggers == null)
+            return (true);
+        JarFile jarFile = new JarFile(jarfile);
+        for (int i = 0; i < triggers.length; i++) {
+            Class clazz = null;
+            try {
+                if (parent != null) {
+                    clazz = parent.loadClass(triggers[i]);
+                } else {
+                    clazz = Class.forName(triggers[i]);
+                }
+            } catch (Throwable t) {
+                clazz = null;
+            }
+            if (clazz == null)
+                continue;
+            String name = triggers[i].replace('.', '/') + ".class";
+            if (log.isDebugEnabled())
+                log.debug(" Checking for " + name);
+            JarEntry jarEntry = jarFile.getJarEntry(name);
+            if (jarEntry != null) {
+                log.info("validateJarFile(" + jarfile + 
+                    ") - jar not loaded. See Servlet Spec 2.3, "
+                    + "section 9.7.2. Offending class: " + name);
+                jarFile.close();
+                return (false);
+            }
+        }
+        jarFile.close();
+        return (true);
+
+    }
+
+
+    /**
+     * Get URL.
+     */
+    protected URL getURL(File file)
+        throws MalformedURLException {
+
+        File realFile = file;
+        try {
+            realFile = realFile.getCanonicalFile();
+        } catch (IOException e) {
+            // Ignore
+        }
+        return realFile.toURL();
+
+    }
+
+
+    /**
+     * Get URL.
+     */
+    protected URL getURI(File file)
+        throws MalformedURLException {
+
+        return jdkCompat.getURI(file);
+
+    }
+
+
+    /**
+     * Delete the specified directory, including all of its contents and
+     * subdirectories recursively.
+     *
+     * @param dir File object representing the directory to be deleted
+     */
+    protected static void deleteDir(File dir) {
+
+        String files[] = dir.list();
+        if (files == null) {
+            files = new String[0];
+        }
+        for (int i = 0; i < files.length; i++) {
+            File file = new File(dir, files[i]);
+            if (file.isDirectory()) {
+                deleteDir(file);
+            } else {
+                file.delete();
+            }
+        }
+        dir.delete();
+
+    }
+
+
+    /**
+     * Log a debugging output message.
+     *
+     * @param message Message to be logged
+     */
+    private void log(String message) {
+
+        System.out.println("WebappClassLoader: " + message);
+
+    }
+
+
+    /**
+     * Log a debugging output message with an exception.
+     *
+     * @param message Message to be logged
+     * @param throwable Exception to be logged
+     */
+    private void log(String message, Throwable throwable) {
+
+        System.out.println("WebappClassLoader: " + message);
+        throwable.printStackTrace(System.out);
+
+    }
+
+}
diff --git a/dev/core/test/com/google/gwt/dev/GWTCompilerTest.java b/dev/core/test/com/google/gwt/dev/GWTCompilerTest.java
new file mode 100644
index 0000000..8955714
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/GWTCompilerTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2009 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.dev.GWTCompiler.GWTCompilerOptionsImpl;
+import com.google.gwt.dev.jjs.JsOutputOption;
+
+import java.io.File;
+
+/**
+ * Test for deprecated {@link GWTShell}.
+ */
+@SuppressWarnings("deprecation")
+public class GWTCompilerTest extends ArgProcessorTestBase {
+
+  private final GWTCompiler.ArgProcessor argProcessor;
+  private final GWTCompilerOptionsImpl options = new GWTCompilerOptionsImpl();
+
+  public GWTCompilerTest() {
+    argProcessor = new GWTCompiler.ArgProcessor(options);
+  }
+
+  public void testAllValidArgs() {
+    assertProcessSuccess(argProcessor, "-logLevel", "DEBUG", "-style",
+        "PRETTY", "-ea", "-XdisableAggressiveOptimization", "-out", "myWww",
+        "-gen", "myGen", "c.g.g.h.H", "my.Module");
+
+    assertEquals(new File("myGen").getAbsoluteFile(),
+        options.getGenDir().getAbsoluteFile());
+    assertEquals(new File("myWww"), options.getOutDir());
+
+    assertEquals(TreeLogger.DEBUG, options.getLogLevel());
+    assertEquals(JsOutputOption.PRETTY, options.getOutput());
+    assertTrue(options.isEnableAssertions());
+    assertFalse(options.isAggressivelyOptimize());
+
+    assertEquals(2, options.getModuleNames().size());
+    assertEquals("c.g.g.h.H", options.getModuleNames().get(0));
+    assertEquals("my.Module", options.getModuleNames().get(1));
+  }
+
+  public void testDefaultArgs() {
+    assertProcessSuccess(argProcessor, "c.g.g.h.H");
+
+    assertEquals(null, options.getGenDir());
+    assertEquals(new File("").getAbsoluteFile(),
+        options.getOutDir().getAbsoluteFile());
+
+    assertEquals(TreeLogger.INFO, options.getLogLevel());
+    assertEquals(JsOutputOption.OBFUSCATED, options.getOutput());
+    assertFalse(options.isEnableAssertions());
+    assertTrue(options.isAggressivelyOptimize());
+
+    assertEquals(1, options.getModuleNames().size());
+    assertEquals("c.g.g.h.H", options.getModuleNames().get(0));
+  }
+
+  public void testForbiddenArgs() {
+    assertProcessFailure(argProcessor, "-localWorkers", "2");
+    assertProcessFailure(argProcessor, "-extra", "extra");
+    assertProcessFailure(argProcessor, "-war", "war");
+    assertProcessFailure(argProcessor, "-work", "work");
+  }
+}
diff --git a/dev/core/test/com/google/gwt/dev/GWTShellTest.java b/dev/core/test/com/google/gwt/dev/GWTShellTest.java
new file mode 100644
index 0000000..557189e
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/GWTShellTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2009 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.dev.GWTShell.ShellOptionsImpl;
+import com.google.gwt.dev.HostedModeTest.MySCL;
+import com.google.gwt.dev.jjs.JsOutputOption;
+import com.google.gwt.dev.shell.BrowserWidgetHostChecker;
+
+import java.io.File;
+
+/**
+ * Test for deprecated {@link GWTShell}.
+ */
+@SuppressWarnings("deprecation")
+public class GWTShellTest extends ArgProcessorTestBase {
+
+  private final GWTShell.ArgProcessor argProcessor;
+  private final ShellOptionsImpl options = new ShellOptionsImpl();
+
+  public GWTShellTest() {
+    argProcessor = new GWTShell.ArgProcessor(options, false, false);
+  }
+
+  public void testAllValidArgs() {
+    assertProcessSuccess(argProcessor, "-port", "8080", "-whitelist", "white",
+        "-blacklist", "black", "-logLevel", "DEBUG", "-noserver", "-out",
+        "myWww", "-gen", "myGen", "http://www.google.com/", "foo");
+
+    assertNotNull(BrowserWidgetHostChecker.matchWhitelisted("white"));
+    assertNotNull(BrowserWidgetHostChecker.matchBlacklisted("black"));
+    assertFalse(BrowserWidgetHostChecker.isAlwaysWhitelisted("http://127.0.0.1.40"));
+    assertFalse(BrowserWidgetHostChecker.isAlwaysWhitelisted("http://127.0.0.1.40:88"));
+    assertFalse(BrowserWidgetHostChecker.isAlwaysWhitelisted("http://127.0.0.1.40:88/"));
+    assertFalse(BrowserWidgetHostChecker.isAlwaysWhitelisted("http://127.0.0.1.40:88/foo"));
+    assertFalse(BrowserWidgetHostChecker.isAlwaysWhitelisted("http://localhost.evildomain.org"));
+    assertFalse(BrowserWidgetHostChecker.isAlwaysWhitelisted("http://localhost.evildomain.org:88"));
+    assertFalse(BrowserWidgetHostChecker.isAlwaysWhitelisted("http://localhost.evildomain.org:88/"));
+    assertFalse(BrowserWidgetHostChecker.isAlwaysWhitelisted("http://localhost.evildomain.org:88/foo"));
+    assertFalse(BrowserWidgetHostChecker.isAlwaysWhitelisted("http://localhost.evildomain.org/"));
+    assertFalse(BrowserWidgetHostChecker.isAlwaysWhitelisted("http://localhost.evildomain.org/foo"));
+    assertFalse(BrowserWidgetHostChecker.isAlwaysWhitelisted("http://www.evildomain.org/foo?http://localhost"));
+    assertTrue(BrowserWidgetHostChecker.isAlwaysWhitelisted("http://127.0.0.1"));
+    assertTrue(BrowserWidgetHostChecker.isAlwaysWhitelisted("http://127.0.0.1:88"));
+    assertTrue(BrowserWidgetHostChecker.isAlwaysWhitelisted("http://127.0.0.1:88/"));
+    assertTrue(BrowserWidgetHostChecker.isAlwaysWhitelisted("http://127.0.0.1:88/foo"));
+
+    assertEquals(new File("myGen").getAbsoluteFile(),
+        options.getGenDir().getAbsoluteFile());
+    assertEquals(new File("myWww"), options.getOutDir());
+
+    assertEquals(TreeLogger.DEBUG, options.getLogLevel());
+
+    assertEquals(8080, options.getPort());
+    assertTrue(options.isNoServer());
+    assertEquals(2, options.getStartupURLs().size());
+    assertEquals("http://www.google.com/", options.getStartupURLs().get(0));
+    assertEquals("foo", options.getStartupURLs().get(1));
+  }
+
+  public void testDefaultArgs() {
+    assertProcessSuccess(argProcessor);
+
+    assertEquals(null, options.getGenDir());
+    assertEquals(new File("").getAbsoluteFile(),
+        options.getOutDir().getAbsoluteFile());
+
+    assertEquals(TreeLogger.INFO, options.getLogLevel());
+    assertEquals(JsOutputOption.OBFUSCATED, options.getOutput());
+    assertFalse(options.isEnableAssertions());
+    assertTrue(options.isAggressivelyOptimize());
+
+    assertEquals(8888, options.getPort());
+    assertFalse(options.isNoServer());
+    assertEquals(0, options.getStartupURLs().size());
+  }
+
+  public void testForbiddenArgs() {
+    assertProcessFailure(argProcessor, "-localWorkers", "2");
+    assertProcessFailure(argProcessor, "-extra", "extra");
+    assertProcessFailure(argProcessor, "-war", "war");
+    assertProcessFailure(argProcessor, "-work", "work");
+    assertProcessFailure(argProcessor, "-server", MySCL.class.getName());
+  }
+}
diff --git a/eclipse/dev/.classpath b/eclipse/dev/.classpath
index 4b1f5b4..fa17a0d 100644
--- a/eclipse/dev/.classpath
+++ b/eclipse/dev/.classpath
@@ -12,16 +12,35 @@
 	<classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/ant-launcher-1.6.5.jar"/>
 	<classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/catalina-1.0.jar"/>
 	<classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/catalina-optional-1.0.jar"/>
+	<classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/commons-beanutils-1.6.jar"/>
+	<classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/commons-collections-3.1.jar" sourcepath="/GWT_TOOLS/lib/tomcat/commons-collections-3.1-src.zip"/>
+	<classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/commons-digester-1.5.jar"/>
 	<classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/commons-el-1.0.jar"/>
+	<classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/commons-logging-1.0.jar"/>
+	<classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/commons-modeler-1.1.jar"/>
+	<classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/jakarta-regexp-1.3.jar"/>
 	<classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/jasper-compiler-1.0.jar"/>
 	<classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/jasper-runtime-1.0.jar"/>
 	<classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/jsp-api-2.0.jar"/>
+	<classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/mx4j-jmx-1.1.jar"/>
+	<classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/naming-common-1.0.jar"/>
+	<classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/naming-factory-1.0.jar"/>
+	<classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/naming-java-1.0.jar"/>
+	<classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/naming-resources-1.0.jar"/>
+	<classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/servlet-api-2.5.jar"/>
+	<classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/servlet-api-2.4.jar"/>
+	<classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/servlets-common-1.0.jar"/>
+	<classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/servlets-default-1.0.jar"/>
+	<classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/servlets-invoker-1.0.jar"/>
+	<classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/tomcat-coyote-1.0.jar"/>
+	<classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/tomcat-http11-1.0.jar" sourcepath="/GWT_TOOLS/lib/tomcat/tomcat-http11-1.0.jar"/>
+	<classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/tomcat-jk2-2.1.jar"/>
+	<classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/tomcat-util-5.1.jar"/>
 	<classpathentry kind="var" path="GWT_TOOLS/lib/htmlunit/htmlunit-2.9/htmlunit-core-js-2.9.jar" sourcepath="/GWT_TOOLS/lib/htmlunit/htmlunit-2.9/htmlunit-core-js-2.9-sources.jar"/>
 	<classpathentry kind="var" path="GWT_TOOLS/lib/htmlunit/htmlunit-2.9/htmlunit-2.9.jar" sourcepath="/GWT_TOOLS/lib/htmlunit/htmlunit-2.9/htmlunit-2.9-sources.jar"/>
 	<classpathentry kind="var" path="GWT_TOOLS/lib/protobuf/protobuf-2.2.0/protobuf-java-rebased-2.2.0.jar"/>
 	<classpathentry kind="var" path="GWT_TOOLS/lib/guava/guava-10.0.1/guava-10.0.1-rebased.jar"/>
 	<classpathentry kind="var" path="GWT_TOOLS/lib/jscomp/sourcemap-rebased.jar"/>
 	<classpathentry kind="var" path="GWT_TOOLS/lib/jscomp/r1649/compiler-rebased.jar"/>
-	<classpathentry kind="var" path="GWT_TOOLS/lib/apache/commons/commons-collections-3.2.1.jar"/>
 	<classpathentry kind="output" path="bin"/>
 </classpath>