Merging the new^4 linker change branch into trunk!!!  BOOOYA!!!

Suggested by: bruce
Patch by: bobv
Review by: scottb
Inspired by: a hot Reuben sandwich
Not Inspired by: javax.servlet
Snide remarks by: knorton


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2192 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/linker/IFrameLinker.java b/dev/core/src/com/google/gwt/core/linker/IFrameLinker.java
similarity index 81%
rename from dev/core/src/com/google/gwt/dev/linker/IFrameLinker.java
rename to dev/core/src/com/google/gwt/core/linker/IFrameLinker.java
index 06aca6c..50c9b9d 100644
--- a/dev/core/src/com/google/gwt/dev/linker/IFrameLinker.java
+++ b/dev/core/src/com/google/gwt/core/linker/IFrameLinker.java
@@ -13,11 +13,16 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.dev.linker;
+package com.google.gwt.core.linker;
 
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.dev.About;
+import com.google.gwt.dev.linker.ArtifactSet;
+import com.google.gwt.dev.linker.LinkerContext;
+import com.google.gwt.dev.linker.LinkerOrder;
+import com.google.gwt.dev.linker.LinkerOrder.Order;
+import com.google.gwt.dev.linker.impl.SelectionScriptLinker;
 import com.google.gwt.dev.util.DefaultTextOutput;
 import com.google.gwt.dev.util.Util;
 import com.google.gwt.util.tools.Utility;
@@ -28,31 +33,35 @@
  * Implements the canonical GWT bootstrap sequence that loads the GWT module in
  * a separate iframe.
  */
+@LinkerOrder(Order.PRIMARY)
 public class IFrameLinker extends SelectionScriptLinker {
 
-  @Override
-  protected String getCompilationExtension(TreeLogger logger,
-      LinkerContext context) {
-    return ".cache.html";
-  }
-
   public String getDescription() {
     return "Standard";
   }
 
   @Override
-  protected void doEmitArtifacts(TreeLogger logger, LinkerContext context)
-      throws UnableToCompleteException {
-    super.doEmitArtifacts(logger, context);
+  public ArtifactSet link(TreeLogger logger, LinkerContext context,
+      ArtifactSet artifacts) throws UnableToCompleteException {
+    ArtifactSet toReturn = super.link(logger, context, artifacts);
 
     try {
       // Add hosted mode iframe contents
-      String hostedHtml = Utility.getFileFromClassPath("com/google/gwt/dev/linker/hosted.html");
-      doEmit(logger, context, Util.getBytes(hostedHtml), "hosted.html");
+      // TODO move hosted.html into gwt-user if HostedModeLinker goes away
+      String hostedHtml = Utility.getFileFromClassPath("com/google/gwt/dev/linker/impl/hosted.html");
+      toReturn.add(emitBytes(logger, Util.getBytes(hostedHtml), "hosted.html"));
     } catch (IOException e) {
       logger.log(TreeLogger.ERROR, "Unable to copy support resource", e);
       throw new UnableToCompleteException();
     }
+
+    return toReturn;
+  }
+
+  @Override
+  protected String getCompilationExtension(TreeLogger logger,
+      LinkerContext context) {
+    return ".cache.html";
   }
 
   @Override
@@ -110,7 +119,7 @@
   @Override
   protected String getSelectionScriptTemplate(TreeLogger logger,
       LinkerContext context) {
-    return "com/google/gwt/dev/linker/IFrameTemplate.js";
+    return "com/google/gwt/core/linker/IFrameTemplate.js";
   }
 
 }
diff --git a/dev/core/src/com/google/gwt/dev/linker/IFrameTemplate.js b/dev/core/src/com/google/gwt/core/linker/IFrameTemplate.js
similarity index 100%
rename from dev/core/src/com/google/gwt/dev/linker/IFrameTemplate.js
rename to dev/core/src/com/google/gwt/core/linker/IFrameTemplate.js
diff --git a/dev/core/src/com/google/gwt/core/linker/NoDeployResourcesLinker.java b/dev/core/src/com/google/gwt/core/linker/NoDeployResourcesLinker.java
new file mode 100644
index 0000000..f87d301
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/linker/NoDeployResourcesLinker.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.core.linker;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.linker.ArtifactSet;
+import com.google.gwt.dev.linker.EmittedArtifact;
+import com.google.gwt.dev.linker.GeneratedResource;
+import com.google.gwt.dev.linker.Linker;
+import com.google.gwt.dev.linker.LinkerContext;
+import com.google.gwt.dev.linker.LinkerOrder;
+import com.google.gwt.dev.linker.PublicResource;
+import com.google.gwt.dev.linker.LinkerOrder.Order;
+
+import java.util.SortedSet;
+
+/**
+ * This class prevents generated resources whose partial path begins with
+ * {@value #PREFIX} from being emitted into the output.
+ */
+@LinkerOrder(Order.PRE)
+public class NoDeployResourcesLinker extends Linker {
+  public static final String PREFIX = "no-deploy/";
+
+  @Override
+  public String getDescription() {
+    return "Filter generated resources in the " + PREFIX + " path";
+  }
+
+  @Override
+  public ArtifactSet link(TreeLogger logger, LinkerContext context,
+      ArtifactSet artifacts) throws UnableToCompleteException {
+
+    ArtifactSet toReturn = new ArtifactSet(artifacts);
+
+    SortedSet<EmittedArtifact> search = toReturn.find(PublicResource.class);
+    
+    for (GeneratedResource artifact : toReturn.find(GeneratedResource.class)) {
+      if (artifact.getPartialPath().startsWith(PREFIX)) {
+        toReturn.remove(artifact);
+      }
+    }
+
+    return toReturn;
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/linker/SingleScriptLinker.java b/dev/core/src/com/google/gwt/core/linker/SingleScriptLinker.java
similarity index 72%
rename from dev/core/src/com/google/gwt/dev/linker/SingleScriptLinker.java
rename to dev/core/src/com/google/gwt/core/linker/SingleScriptLinker.java
index 07671be..99ea7a3 100644
--- a/dev/core/src/com/google/gwt/dev/linker/SingleScriptLinker.java
+++ b/dev/core/src/com/google/gwt/core/linker/SingleScriptLinker.java
@@ -13,11 +13,18 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.dev.linker;
+package com.google.gwt.core.linker;
 
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.dev.About;
+import com.google.gwt.dev.linker.ArtifactSet;
+import com.google.gwt.dev.linker.CompilationResult;
+import com.google.gwt.dev.linker.EmittedArtifact;
+import com.google.gwt.dev.linker.LinkerContext;
+import com.google.gwt.dev.linker.LinkerOrder;
+import com.google.gwt.dev.linker.LinkerOrder.Order;
+import com.google.gwt.dev.linker.impl.SelectionScriptLinker;
 import com.google.gwt.dev.util.DefaultTextOutput;
 import com.google.gwt.dev.util.Util;
 
@@ -26,44 +33,15 @@
  * this Linker requires that the module has exactly one distinct compilation
  * result.
  */
+@LinkerOrder(Order.PRIMARY)
 public class SingleScriptLinker extends SelectionScriptLinker {
   public String getDescription() {
     return "Single Script";
   }
 
-  /**
-   * Guard against more than one CompilationResult and delegate to super-class.
-   */
   @Override
-  protected void doEmitArtifacts(TreeLogger logger, LinkerContext context)
-      throws UnableToCompleteException {
-    if (context.getCompilations().size() != 1) {
-      logger = logger.branch(TreeLogger.ERROR,
-          "The module must have exactly one distinct"
-              + " permutation when using the " + getDescription() + " Linker.",
-          null);
-
-      int count = 0;
-      for (CompilationResult result : context.getCompilations()) {
-        logger.log(TreeLogger.INFO, "Permutation " + ++count + ": "
-            + result.toString(), null);
-      }
-
-      throw new UnableToCompleteException();
-    }
-    super.doEmitArtifacts(logger, context);
-  }
-
-  /**
-   * Emits the single compilation wrapped in an anonymous function block.
-   */
-  @Override
-  protected void doEmitCompilation(TreeLogger logger, LinkerContext context,
-      CompilationResult result) throws UnableToCompleteException {
-  }
-
-  @Override
-  protected void emitSelectionScript(TreeLogger logger, LinkerContext context)
+  protected EmittedArtifact emitSelectionScript(TreeLogger logger,
+      LinkerContext context, ArtifactSet artifacts)
       throws UnableToCompleteException {
 
     DefaultTextOutput out = new DefaultTextOutput(true);
@@ -71,7 +49,7 @@
     // Emit the selection script in a function closure.
     out.print("(function () {");
     out.newlineOpt();
-    String bootstrap = generateSelectionScript(logger, context);
+    String bootstrap = generateSelectionScript(logger, context, artifacts);
     bootstrap = context.optimizeJavaScript(logger, bootstrap);
     out.print(bootstrap);
     out.print("})();");
@@ -89,7 +67,19 @@
     out.print("var $moduleName, $moduleBase;");
     out.newlineOpt();
 
-    CompilationResult result = context.getCompilations().first();
+    CompilationResult result = null;
+    for (CompilationResult artifact : artifacts.find(CompilationResult.class)) {
+      if (result == null) {
+        result = artifact;
+      } else {
+        logger = logger.branch(TreeLogger.ERROR,
+            "The module must have exactly one distinct"
+                + " permutation when using the " + getDescription()
+                + " Linker.", null);
+        throw new UnableToCompleteException();
+      }
+    }
+
     out.print(result.getJavaScript());
 
     // Add a callback to the selection script
@@ -107,7 +97,7 @@
     out.newlineOpt();
 
     byte[] selectionScriptBytes = Util.getBytes(out.toString());
-    doEmit(logger, context, selectionScriptBytes, context.getModuleName()
+    return emitBytes(logger, selectionScriptBytes, context.getModuleName()
         + ".nocache.js");
   }
 
@@ -144,6 +134,6 @@
   @Override
   protected String getSelectionScriptTemplate(TreeLogger logger,
       LinkerContext context) throws UnableToCompleteException {
-    return "com/google/gwt/dev/linker/SSOTemplate.js";
+    return "com/google/gwt/core/linker/SingleScriptTemplate.js";
   }
 }
diff --git a/dev/core/src/com/google/gwt/dev/linker/SSOTemplate.js b/dev/core/src/com/google/gwt/core/linker/SingleScriptTemplate.js
similarity index 100%
rename from dev/core/src/com/google/gwt/dev/linker/SSOTemplate.js
rename to dev/core/src/com/google/gwt/core/linker/SingleScriptTemplate.js
diff --git a/dev/core/src/com/google/gwt/dev/linker/XSLinker.java b/dev/core/src/com/google/gwt/core/linker/XSLinker.java
similarity index 90%
rename from dev/core/src/com/google/gwt/dev/linker/XSLinker.java
rename to dev/core/src/com/google/gwt/core/linker/XSLinker.java
index c06d230..1dc08c7 100644
--- a/dev/core/src/com/google/gwt/dev/linker/XSLinker.java
+++ b/dev/core/src/com/google/gwt/core/linker/XSLinker.java
@@ -13,16 +13,21 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.dev.linker;
+package com.google.gwt.core.linker;
 
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.dev.About;
+import com.google.gwt.dev.linker.LinkerContext;
+import com.google.gwt.dev.linker.LinkerOrder;
+import com.google.gwt.dev.linker.LinkerOrder.Order;
+import com.google.gwt.dev.linker.impl.SelectionScriptLinker;
 import com.google.gwt.dev.util.DefaultTextOutput;
 
 /**
  * Generates a cross-site compatible bootstrap sequence.
  */
+@LinkerOrder(Order.PRIMARY)
 public class XSLinker extends SelectionScriptLinker {
 
   public String getDescription() {
@@ -90,6 +95,6 @@
   @Override
   protected String getSelectionScriptTemplate(TreeLogger logger,
       LinkerContext context) throws UnableToCompleteException {
-    return "com/google/gwt/dev/linker/XSTemplate.js";
+    return "com/google/gwt/core/linker/XSTemplate.js";
   }
 }
diff --git a/dev/core/src/com/google/gwt/dev/linker/XSTemplate.js b/dev/core/src/com/google/gwt/core/linker/XSTemplate.js
similarity index 100%
rename from dev/core/src/com/google/gwt/dev/linker/XSTemplate.js
rename to dev/core/src/com/google/gwt/core/linker/XSTemplate.js
diff --git a/dev/core/src/com/google/gwt/dev/GWTCompiler.java b/dev/core/src/com/google/gwt/dev/GWTCompiler.java
index 2de4086..f038c89 100644
--- a/dev/core/src/com/google/gwt/dev/GWTCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/GWTCompiler.java
@@ -35,7 +35,6 @@
 import com.google.gwt.dev.jjs.JJSOptions;
 import com.google.gwt.dev.jjs.JavaToJavaScriptCompiler;
 import com.google.gwt.dev.jjs.JsOutputOption;
-import com.google.gwt.dev.linker.Linker;
 import com.google.gwt.dev.linker.SelectionProperty;
 import com.google.gwt.dev.linker.impl.StandardCompilationResult;
 import com.google.gwt.dev.linker.impl.StandardLinkerContext;
@@ -161,11 +160,15 @@
       return out;
     }
 
+    @SuppressWarnings("unused")
     protected boolean recordDecision(String in, String out) {
+      // TODO(bobv): consider caching compilations again?
       return false;
     }
 
+    @SuppressWarnings("unused")
     protected void recordGeneratedTypeHash(String typeName, String sourceHash) {
+      // TODO(bobv): consider caching compilations again?
     }
   }
 
@@ -217,8 +220,8 @@
     }
   }
 
-  private static final String GENERATOR_DIR = ".gwt-compiler"
-      + File.separatorChar + "generated";
+  public static final String GWT_COMPILER_DIR = ".gwt-tmp" + File.separatorChar
+      + "compiler";
 
   public static void main(String[] args) {
     /*
@@ -283,8 +286,6 @@
 
   private StandardSourceOracle sourceOracle;
 
-  private String[] targets;
-
   private TypeOracle typeOracle;
 
   private boolean useGuiLogger;
@@ -349,11 +350,17 @@
     // Set up all the initial state.
     checkModule(logger);
 
+    // Place generated resources inside the out dir as a sibling to the module
+    generatorResourcesDir = new File(outDir, GWT_COMPILER_DIR + File.separator
+        + moduleDef.getName() + File.separator + "generated");
+
     // Tweak the output directory so that output lives under the module name.
     outDir = new File(outDir, module.getName());
 
-    // Clean out the generated resources directory and/or create it
-    generatorResourcesDir = new File(outDir, GENERATOR_DIR);
+    // Clean the outDir.
+    Util.recursiveDelete(outDir, true);
+
+    // Clean out the generated resources directory and/or create it.
     Util.recursiveDelete(generatorResourcesDir, true);
     generatorResourcesDir.mkdirs();
 
@@ -383,31 +390,6 @@
     jjs = new JavaToJavaScriptCompiler(logger, frontEnd, declEntryPts,
         jjsOptions);
 
-    if (!jjsOptions.isValidateOnly()) {
-      if (targets != null) {
-        // Make sure all targets specified on the command-line exist
-        boolean badTarget = false;
-        for (String target : targets) {
-          if (module.getLinker(target) == null) {
-            logger.log(TreeLogger.ERROR, "Unknown target " + target, null);
-            badTarget = true;
-          }
-        }
-        if (badTarget) {
-          throw new UnableToCompleteException();
-        }
-      } else {
-        // Otherwise, make sure at least one linker is set in the ModuleDef
-        targets = module.getActiveLinkerNames();
-        if (targets == null || targets.length == 0) {
-          logger.log(TreeLogger.ERROR,
-              "At least one Linker must be specified in the "
-                  + "module or on the command line", null);
-          throw new UnableToCompleteException();
-        }
-      }
-    }
-
     StandardLinkerContext linkerContext = new StandardLinkerContext(logger,
         module, outDir, generatorResourcesDir, jjsOptions);
     compilePermutations(logger, linkerContext);
@@ -419,11 +401,7 @@
 
     logger.log(TreeLogger.INFO, "Compilation succeeded", null);
 
-    // Use the LinkerContext to invoke the Linkers
-    for (String linkerName : targets) {
-      Linker l = module.getLinker(linkerName);
-      linkerContext.invokeLinker(logger, linkerName, l);
-    }
+    linkerContext.link(logger, linkerContext, null);
   }
 
   public File getGenDir() {
@@ -478,10 +456,6 @@
     jjsOptions.setOutput(JsOutputOption.PRETTY);
   }
 
-  public void setTargets(String[] targets) {
-    this.targets = targets;
-  }
-
   /**
    * Ensure the module has at least one entry point (except in validation mode).
    */
diff --git a/dev/core/src/com/google/gwt/dev/GWTShell.java b/dev/core/src/com/google/gwt/dev/GWTShell.java
index ac742c1..c75dc4c 100644
--- a/dev/core/src/com/google/gwt/dev/GWTShell.java
+++ b/dev/core/src/com/google/gwt/dev/GWTShell.java
@@ -267,8 +267,8 @@
 
         // Create a sandbox for the module.
         //
-        File moduleDir = new File(outDir, moduleName);
-        File shellDir = new File(moduleDir, GWT_SHELL_PATH);
+        File shellDir = new File(outDir, GWT_SHELL_PATH + File.separator
+            + moduleName);
 
         TypeOracle typeOracle = moduleDef.getTypeOracle(logger);
         ShellModuleSpaceHost host = doCreateShellModuleSpaceHost(logger,
@@ -309,7 +309,8 @@
     }
   }
 
-  public static final String GWT_SHELL_PATH = ".gwt-shell";
+  public static final String GWT_SHELL_PATH = ".gwt-tmp" + File.separator
+      + "shell";
 
   private static Image[] icons;
 
diff --git a/dev/core/src/com/google/gwt/dev/cfg/Messages.java b/dev/core/src/com/google/gwt/dev/cfg/Messages.java
index a232c18..0c33373 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/Messages.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/Messages.java
@@ -31,6 +31,9 @@
   public static final Message1String LINKER_NAME_INVALID = new Message1String(
       TreeLogger.ERROR, "Invalid linker name '$0'");
 
+  public static final Message1String NAME_INVALID = new Message1String(
+      TreeLogger.ERROR, "Invalid name '$0'");
+
   public static final Message1String PROPERTY_NAME_INVALID = new Message1String(
       TreeLogger.ERROR, "Invalid property name '$0'");
 
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 2ab0fd6..2b2a88d 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java
@@ -23,7 +23,8 @@
 import com.google.gwt.dev.jdt.TypeOracleBuilder;
 import com.google.gwt.dev.jdt.URLCompilationUnitProvider;
 import com.google.gwt.dev.linker.Linker;
-import com.google.gwt.dev.linker.LinkerContextShim;
+import com.google.gwt.dev.linker.LinkerOrder;
+import com.google.gwt.dev.linker.LinkerOrder.Order;
 import com.google.gwt.dev.util.Empty;
 import com.google.gwt.dev.util.FileOracle;
 import com.google.gwt.dev.util.FileOracleFactory;
@@ -41,6 +42,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -77,7 +79,9 @@
     return true;
   }
 
-  private String[] activeLinkerNames = new String[0];
+  private final Set<Class<? extends Linker>> activeLinkers = new LinkedHashSet<Class<? extends Linker>>();
+
+  private Class<? extends Linker> activePrimaryLinker;
 
   private final ArrayList<URLCompilationUnitProvider> allCups = new ArrayList<URLCompilationUnitProvider>();
 
@@ -98,14 +102,18 @@
 
   private TypeOracle lazyTypeOracle;
 
-  private final List<Class<? extends LinkerContextShim>> linkerContextShimTypes = new ArrayList<Class<? extends LinkerContextShim>>();
-
-  private final Map<String, Linker> linkersByName = new HashMap<String, Linker>();
+  private final Map<String, Class<? extends Linker>> linkersTypesByName = new HashMap<String, Class<? extends Linker>>();
 
   private final long moduleDefCreationTime = System.currentTimeMillis();
 
   private final String name;
 
+  /**
+   * Must use a separate field to track override, because setNameOverride() will
+   * get called every time a module is inherited, but only the last one matters.
+   */
+  private String nameOverride;
+
   private final Properties properties = new Properties();
 
   private final FileOracleFactory publicPathEntries = new FileOracleFactory();
@@ -132,18 +140,15 @@
     gwtXmlFiles.add(xmlFile);
   }
 
-  public void addLinker(String name, Linker linker) {
-    linkersByName.put(name, linker);
-  }
+  public void addLinker(String name) {
+    Class<? extends Linker> clazz = getLinker(name);
+    assert clazz != null;
 
-  public void addLinkerContextShim(Class<? extends LinkerContextShim> clazz) {
-    /*
-     * It's possible a shim may be registered more than once, so this check is
-     * used to de-duplicate the final list, which will reflect the order of the
-     * first appearance of any LinkerContextShim type.
-     */
-    if (!linkerContextShimTypes.contains(clazz)) {
-      linkerContextShimTypes.add(clazz);
+    LinkerOrder order = clazz.getAnnotation(LinkerOrder.class);
+    if (order.value() == Order.PRIMARY) {
+      activePrimaryLinker = clazz;
+    } else {
+      activeLinkers.add(clazz);
     }
   }
 
@@ -213,6 +218,10 @@
     entryPointTypeNames.clear();
   }
 
+  public void defineLinker(String name, Class<? extends Linker> linker) {
+    linkersTypesByName.put(name, linker);
+  }
+
   public synchronized URL findPublicFile(String partialPath) {
     return lazyPublicOracle.find(partialPath);
   }
@@ -238,8 +247,12 @@
     return null;
   }
 
-  public String[] getActiveLinkerNames() {
-    return activeLinkerNames;
+  public Set<Class<? extends Linker>> getActiveLinkers() {
+    return activeLinkers;
+  }
+
+  public Class<? extends Linker> getActivePrimaryLinker() {
+    return activePrimaryLinker;
   }
 
   public String[] getAllPublicFiles() {
@@ -260,19 +273,15 @@
   }
 
   public synchronized String getFunctionName() {
-    return name.replace('.', '_');
+    return getName().replace('.', '_');
   }
 
-  public Linker getLinker(String name) {
-    return linkersByName.get(name);
-  }
-
-  public List<Class<? extends LinkerContextShim>> getLinkerContextShims() {
-    return linkerContextShimTypes;
+  public Class<? extends Linker> getLinker(String name) {
+    return linkersTypesByName.get(name);
   }
 
   public synchronized String getName() {
-    return name;
+    return nameOverride != null ? nameOverride : name;
   }
 
   /**
@@ -349,8 +358,12 @@
     PerfLogger.end();
   }
 
-  public void setActiveLinkerNames(String... names) {
-    activeLinkerNames = names;
+  /**
+   * Override the module's apparent name. Setting this value to
+   * <code>null<code> will disable the name override.
+   */
+  public void setNameOverride(String nameOverride) {
+    this.nameOverride = nameOverride;
   }
 
   /**
@@ -373,8 +386,7 @@
    * 
    * @param logger Logs the activity.
    */
-  synchronized void normalize(TreeLogger logger)
-      throws UnableToCompleteException {
+  synchronized void normalize(TreeLogger logger) {
     PerfLogger.start("ModuleDef.normalize");
 
     // Normalize property providers.
@@ -435,24 +447,6 @@
     branch = Messages.PUBLIC_PATH_LOCATIONS.branch(logger, null);
     lazyPublicOracle = publicPathEntries.create(branch);
 
-    boolean fail = false;
-    for (String linkerName : activeLinkerNames) {
-      if (!linkersByName.containsKey(linkerName)) {
-        logger.log(TreeLogger.ERROR, "Unknown linker name " + linkerName, null);
-        fail = true;
-      }
-    }
-
-    if (linkersByName.size() == 0 || activeLinkerNames.length == 0) {
-      logger.log(TreeLogger.ERROR, "At least one Linker must be defind and "
-          + "at least one Linker must be active.", null);
-      fail = true;
-    }
-
-    if (fail) {
-      throw new UnableToCompleteException();
-    }
-
     PerfLogger.end();
   }
 
@@ -462,7 +456,7 @@
     TypeOracle newTypeOracle = null;
 
     try {
-      String msg = "Analyzing source in module '" + name + "'";
+      String msg = "Analyzing source in module '" + getName() + "'";
       TreeLogger branch = logger.branch(TreeLogger.TRACE, msg, null);
       long before = System.currentTimeMillis();
       TypeOracleBuilder builder = new TypeOracleBuilder(getCacheManager());
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java b/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java
index 09f2561..878ba02 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java
@@ -26,7 +26,7 @@
 import com.google.gwt.dev.js.ast.JsProgram;
 import com.google.gwt.dev.js.ast.JsStatement;
 import com.google.gwt.dev.linker.Linker;
-import com.google.gwt.dev.linker.LinkerContextShim;
+import com.google.gwt.dev.linker.LinkerOrder;
 import com.google.gwt.dev.util.Empty;
 import com.google.gwt.dev.util.Util;
 import com.google.gwt.dev.util.xml.AttributeConverter;
@@ -35,7 +35,6 @@
 import java.io.IOException;
 import java.io.StringReader;
 import java.net.URL;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -49,6 +48,8 @@
 public class ModuleDefSchema extends Schema {
   private final class BodySchema extends Schema {
 
+    protected final String __add_linker_1_name = null;
+
     protected final String __define_linker_1_name = null;
 
     protected final String __define_linker_2_class = null;
@@ -59,8 +60,6 @@
 
     protected final String __entry_point_1_class = null;
 
-    protected final String __extend_linker_context_1_class = null;
-
     protected final String __extend_property_1_name = null;
 
     protected final String __extend_property_2_values = null;
@@ -89,8 +88,6 @@
 
     protected final String __servlet_2_class = null;
 
-    protected final String __set_linker_1_name = null;
-
     protected final String __set_property_1_name = null;
 
     protected final String __set_property_2_value = null;
@@ -119,9 +116,29 @@
 
     private Schema fChild;
 
-    protected Schema __define_linker_begin(LinkerName name, Linker linker)
+    protected Schema __add_linker_begin(LinkerName name)
         throws UnableToCompleteException {
-      moduleDef.addLinker(name.name, linker);
+      if (moduleDef.getLinker(name.name) == null) {
+        Messages.LINKER_NAME_INVALID.log(logger, name.name, null);
+        throw new UnableToCompleteException();
+      }
+      moduleDef.addLinker(name.name);
+      return null;
+    }
+
+    protected Schema __define_linker_begin(LinkerName name,
+        Class<? extends Linker> linker) throws UnableToCompleteException {
+      if (!Linker.class.isAssignableFrom(linker)) {
+        logger.log(TreeLogger.ERROR, "A linker must extend "
+            + Linker.class.getName(), null);
+        throw new UnableToCompleteException();
+      }
+      if (linker.getAnnotation(LinkerOrder.class) == null) {
+        logger.log(TreeLogger.ERROR, "Linkers must be annotated with the "
+            + LinkerOrder.class.getName() + " annotation", null);
+        throw new UnableToCompleteException();
+      }
+      moduleDef.defineLinker(name.name, linker);
       return null;
     }
 
@@ -148,18 +165,6 @@
       return null;
     }
 
-    protected Schema __extend_linker_context_begin(Class<?> clazz)
-        throws UnableToCompleteException {
-      try {
-        moduleDef.addLinkerContextShim(clazz.asSubclass(LinkerContextShim.class));
-        return null;
-      } catch (ClassCastException e) {
-        Messages.INVALID_CLASS_DERIVATION.log(logger, clazz,
-            LinkerContextShim.class, null);
-        throw new UnableToCompleteException();
-      }
-    }
-
     protected Schema __extend_property_begin(Property property,
         PropertyValue[] values) {
       for (int i = 0; i < values.length; i++) {
@@ -290,15 +295,6 @@
       return null;
     }
 
-    protected Schema __set_linker_begin(LinkerName[] names) {
-      String[] asString = new String[names.length];
-      for (int i = 0; i < names.length; i++) {
-        asString[i] = names[i].name;
-      }
-      moduleDef.setActiveLinkerNames(asString);
-      return null;
-    }
-
     protected Schema __set_property_begin(Property prop, PropertyValue value) {
       prop.setActiveValue(value.token);
 
@@ -586,31 +582,6 @@
   /**
    * Converts a string into a linker name, validating it in the process.
    */
-  private final class LinkerNameArrayAttrCvt extends AttributeConverter {
-
-    public Object convertToArg(Schema schema, int line, String elem,
-        String attr, String value) throws UnableToCompleteException {
-      String[] tokens = value.split(",");
-      List<LinkerName> toReturn = new ArrayList<LinkerName>(tokens.length);
-
-      for (String token : tokens) {
-        token = token.trim();
-        if (moduleDef.getLinker(token) == null) {
-          Messages.LINKER_NAME_INVALID.log(logger, token, null);
-          throw new UnableToCompleteException();
-        }
-
-        toReturn.add(new LinkerName(token));
-      }
-
-      // It is a valid list of names.
-      return toReturn.toArray(new LinkerName[tokens.length]);
-    }
-  }
-
-  /**
-   * Converts a string into a linker name, validating it in the process.
-   */
   private final class LinkerNameAttrCvt extends AttributeConverter {
 
     public Object convertToArg(Schema schema, int line, String elem,
@@ -627,6 +598,46 @@
   }
 
   /**
+   * A dotted Java identifier or null. Zero-length names are represented as
+   * null.
+   */
+  private static class NullableName {
+    public final String token;
+
+    public NullableName(String token) {
+      this.token = token;
+    }
+  }
+
+  /**
+   * Converts a string into a nullable name, validating it in the process.
+   */
+  private final class NullableNameAttrCvt extends AttributeConverter {
+
+    public Object convertToArg(Schema schema, int line, String elem,
+        String attr, String value) throws UnableToCompleteException {
+      if (value == null || value.length() == 0) {
+        return new NullableName(null);
+      }
+
+      // Ensure each part of the name is valid.
+      //
+      String[] tokens = (value + ". ").split("\\.");
+      for (int i = 0; i < tokens.length - 1; i++) {
+        String token = tokens[i];
+        if (!Util.isValidJavaIdent(token)) {
+          Messages.NAME_INVALID.log(logger, value, null);
+          throw new UnableToCompleteException();
+        }
+      }
+
+      // It is a valid name.
+      //
+      return new NullableName(value);
+    }
+  }
+
+  /**
    * Creates singleton instances of objects based on an attribute containing a
    * class name.
    */
@@ -844,26 +855,25 @@
     return "yes".equalsIgnoreCase(s) || "true".equalsIgnoreCase(s);
   }
 
+  protected final String __module_1_renameto = "";
+
   private final BodySchema bodySchema;
 
   private final ClassAttrCvt classAttrCvt = new ClassAttrCvt();
 
   private boolean foundAnyPublic;
-
   private boolean foundExplicitSourceOrSuperSource;
   private final ObjAttrCvt<Generator> genAttrCvt = new ObjAttrCvt<Generator>(
       Generator.class);
   private final JsParser jsParser = new JsParser();
   private final JsProgram jsPgm = new JsProgram();
-  private final ObjAttrCvt<Linker> linkerAttrCvt = new ObjAttrCvt<Linker>(
-      Linker.class);
-  private final LinkerNameArrayAttrCvt linkerNameArrayAttrCvt = new LinkerNameArrayAttrCvt();
   private final LinkerNameAttrCvt linkerNameAttrCvt = new LinkerNameAttrCvt();
   private final ModuleDefLoader loader;
   private final TreeLogger logger;
   private final ModuleDef moduleDef;
   private final String modulePackageAsPath;
   private final URL moduleURL;
+  private final NullableNameAttrCvt nullableNameAttrCvt = new NullableNameAttrCvt();
   private final PropertyAttrCvt propAttrCvt = new PropertyAttrCvt();
   private final PropertyNameAttrCvt propNameAttrCvt = new PropertyNameAttrCvt();
   private final PropertyValueArrayAttrCvt propValueArrayAttrCvt = new PropertyValueArrayAttrCvt();
@@ -884,17 +894,16 @@
     registerAttributeConverter(PropertyValue.class, propValueAttrCvt);
     registerAttributeConverter(PropertyValue[].class, propValueArrayAttrCvt);
     registerAttributeConverter(Generator.class, genAttrCvt);
-    registerAttributeConverter(Linker.class, linkerAttrCvt);
     registerAttributeConverter(LinkerName.class, linkerNameAttrCvt);
-    registerAttributeConverter(LinkerName[].class, linkerNameArrayAttrCvt);
+    registerAttributeConverter(NullableName.class, nullableNameAttrCvt);
     registerAttributeConverter(Class.class, classAttrCvt);
   }
 
-  protected Schema __module_begin() {
+  protected Schema __module_begin(NullableName renameTo) {
     return bodySchema;
   }
 
-  protected void __module_end() {
+  protected void __module_end(NullableName renameTo) {
     // Maybe infer source and public.
     //
     if (!foundExplicitSourceOrSuperSource) {
@@ -906,6 +915,9 @@
       bodySchema.addPublicPackage(modulePackageAsPath, "public", Empty.STRINGS,
           Empty.STRINGS, true, true);
     }
+
+    // We do this in __module_end so this value is never inherited
+    moduleDef.setNameOverride(renameTo.token);
   }
 
   /**
diff --git a/dev/core/src/com/google/gwt/dev/linker/AbstractLinker.java b/dev/core/src/com/google/gwt/dev/linker/AbstractLinker.java
index bb33a16..deda779 100644
--- a/dev/core/src/com/google/gwt/dev/linker/AbstractLinker.java
+++ b/dev/core/src/com/google/gwt/dev/linker/AbstractLinker.java
@@ -19,126 +19,80 @@
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.dev.util.Util;
 
+import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
-import java.io.IOException;
 import java.io.InputStream;
-import java.io.OutputStream;
 
 /**
  * Provides basic functions common to all Linker implementations.
  */
 public abstract class AbstractLinker extends Linker {
-
   /**
-   * Delegates to {@link #doEmitArtifacts(TreeLogger, LinkerContext)}.
+   * Internal type to wrap a byte array.
    */
-  public final void link(TreeLogger logger, LinkerContext context)
-      throws UnableToCompleteException {
-    doEmitArtifacts(logger, context);
+  private static class SyntheticArtifact extends EmittedArtifact {
+    private final byte[] data;
+
+    public SyntheticArtifact(Class<? extends Linker> linkerType,
+        String partialPath, byte[] data) {
+      super(linkerType, partialPath);
+      this.data = data;
+    }
+
+    @Override
+    public InputStream getContents(TreeLogger logger)
+        throws UnableToCompleteException {
+      return new ByteArrayInputStream(data);
+    }
   }
 
   /**
-   * Emit a byte array into the output. Linkers that require knowledge of all
-   * resources emitted may override this function to spy on the output.
+   * A helper method to create an artifact from an array of bytes.
    * 
-   * @param logger a logger
-   * @param context the LinkerContext
-   * @param what the bytes to emit
-   * @param where the partial path within the output directory
+   * @param logger a TreeLogger
+   * @param what the data to emit
+   * @param partialPath the partial path of the resource
+   * @return an artifact that contains the given data
    * @throws UnableToCompleteException
    */
-  protected void doEmit(TreeLogger logger, LinkerContext context, byte[] what,
-      String where) throws UnableToCompleteException {
-    OutputStream out = context.tryCreateArtifact(logger, where);
-    if (out != null) {
-      try {
-        out.write(what);
-        context.commit(logger, out);
-      } catch (IOException e) {
-        logger.log(TreeLogger.ERROR, "Unable to emit artifact", e);
-        throw new UnableToCompleteException();
-      }
-    }
+  @SuppressWarnings("unused")
+  protected final EmittedArtifact emitBytes(TreeLogger logger, byte[] what,
+      String partialPath) throws UnableToCompleteException {
+    return new SyntheticArtifact(getClass(), partialPath, what);
   }
 
   /**
-   * The default implementation will emit all compilations, public resources,
-   * and generated resources by calling the relevant functions defined in the
-   * linker interface.
-   */
-  protected void doEmitArtifacts(TreeLogger logger, LinkerContext context)
-      throws UnableToCompleteException {
-    for (CompilationResult result : context.getCompilations()) {
-      doEmitCompilation(logger, context, result);
-    }
-
-    // Copy the public resources
-    for (PublicResource resource : context.getPublicResources()) {
-      doEmitPublicResource(logger, context, resource);
-    }
-
-    // Copy the generated resources
-    for (GeneratedResource resource : context.getGeneratedResources()) {
-      doEmitGeneratedResource(logger, context, resource);
-    }
-  }
-
-  /**
-   * Linkers must implement this function to emit compilations.
+   * A helper method to create an artifact to emit the contents of an
+   * InputStream.
    * 
-   * @param logger
-   * @param context
-   * @param result
-   * @throws UnableToCompleteException
+   * @param logger a TreeLogger
+   * @param what the source InputStream
+   * @param partialPath the partial path of the emitted resource
+   * @return an artifact that contains the contents of the InputStream
    */
-  protected abstract void doEmitCompilation(TreeLogger logger,
-      LinkerContext context, CompilationResult result)
-      throws UnableToCompleteException;
-
-  protected void doEmitGeneratedResource(TreeLogger logger,
-      LinkerContext context, GeneratedResource resource)
-      throws UnableToCompleteException {
-    emitInputStream(logger, context, resource.tryGetResourceAsStream(logger),
-        resource.getPartialPath());
-  }
-
-  protected void doEmitPublicResource(TreeLogger logger, LinkerContext context,
-      PublicResource resource) throws UnableToCompleteException {
-    emitInputStream(logger, context, resource.tryGetResourceAsStream(logger),
-        resource.getPartialPath());
-  }
-
-  /**
-   * Helper method that emits the contents of an InputStream into the output.
-   * This delegates to
-   * {@link #doEmit(TreeLogger, LinkerContext, byte[], String)}.
-   * 
-   * @param logger a logger
-   * @param context the LinkerContext
-   * @param what the stream to emit
-   * @param where the partial path within the output directory
-   * @throws UnableToCompleteException
-   */
-  protected final void emitInputStream(TreeLogger logger,
-      LinkerContext context, InputStream what, String where)
-      throws UnableToCompleteException {
+  protected final EmittedArtifact emitInputStream(TreeLogger logger,
+      InputStream what, String partialPath) throws UnableToCompleteException {
     ByteArrayOutputStream out = new ByteArrayOutputStream();
     Util.copy(logger, what, out);
-    doEmit(logger, context, out.toByteArray(), where);
+    return new SyntheticArtifact(getClass(), partialPath, out.toByteArray());
   }
 
   /**
-   * A helper method to emit a byte array into the output, using an
-   * automatically-computed strong path. This function delegates to
-   * {@link #doEmit(TreeLogger, LinkerContext, byte[], String)}.
+   * A helper method to create an artifact from an array of bytes with a strong
+   * name.
    * 
-   * @return the partial path of the emitted resource.
+   * @param logger a TreeLogger
+   * @param what the data to emit
+   * @param prefix a non-null string to prepend to the hash to determine the
+   *          Artifact's partial path
+   * @param suffix a non-null string to append to the hash to determine the
+   *          Artifact's partial path
+   * @return an artifact that contains the given data
    */
-  protected final String emitWithStrongName(TreeLogger logger,
-      LinkerContext context, byte[] what, String prefix, String suffix)
+  protected final EmittedArtifact emitWithStrongName(TreeLogger logger,
+      byte[] what, String prefix, String suffix)
       throws UnableToCompleteException {
     String strongName = prefix + Util.computeStrongName(what) + suffix;
-    doEmit(logger, context, what, strongName);
-    return strongName;
+    return emitBytes(logger, what, strongName);
   }
 }
diff --git a/dev/core/src/com/google/gwt/dev/linker/Artifact.java b/dev/core/src/com/google/gwt/dev/linker/Artifact.java
new file mode 100644
index 0000000..4c25187
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/linker/Artifact.java
@@ -0,0 +1,82 @@
+/*
+ * 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.linker;
+
+/**
+ * A base type for all artifacts relating to the link process. In order to
+ * ensure stable output between runs of the compiler, Artifact types must
+ * implement a stable comparison between instances of a relevant base type (the
+ * exact comparison order is irrelevant).
+ * 
+ * @param <C> The type of Artifact interface that the Artifact can be compared
+ *          to.
+ */
+public abstract class Artifact<C extends Artifact<C>> implements
+    Comparable<Artifact<?>> {
+  private final Class<? extends Linker> linker;
+
+  protected Artifact(Class<? extends Linker> linker) {
+    this.linker = linker;
+  }
+
+  public final int compareTo(Artifact<?> o) {
+    if (getComparableArtifactType().equals(o.getComparableArtifactType())) {
+      return compareToComparableArtifact(getComparableArtifactType().cast(o));
+    } else {
+      return getComparableArtifactType().getName().compareTo(
+          o.getComparableArtifactType().getName());
+    }
+  }
+
+  @Override
+  public final boolean equals(Object obj) {
+    if (obj instanceof Artifact) {
+      return compareTo((Artifact<?>) obj) == 0;
+    } else {
+      return false;
+    }
+  }
+
+  public final Class<? extends Linker> getLinker() {
+    return linker;
+  }
+
+  /**
+   * The class which is returned from {@link #getComparableArtifactType()} must
+   * declare a final implementation which returns the same hash code for objects
+   * for which {@link #compareToComparableArtifact(Artifact)} returns 0.
+   */
+  @Override
+  public abstract int hashCode();
+
+  @Override
+  public String toString() {
+    return getClass().getName() + " created by " + getLinker().getName();
+  }
+
+  /**
+   * Performs comparison with an artifact of a compatible base type. Objects
+   * which compare to 0 are assumed equal, and must return the same
+   * {@link #hashCode()}.
+   */
+  protected abstract int compareToComparableArtifact(C o);
+
+  /**
+   * Returns the base type to use for comparisons between Artifacts. All
+   * concrete implementations of this methods must be final.
+   */
+  protected abstract Class<C> getComparableArtifactType();
+}
diff --git a/dev/core/src/com/google/gwt/dev/linker/ArtifactSet.java b/dev/core/src/com/google/gwt/dev/linker/ArtifactSet.java
new file mode 100644
index 0000000..3a894ad
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/linker/ArtifactSet.java
@@ -0,0 +1,179 @@
+/*
+ * 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.linker;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * Provides stable ordering and de-duplication of artifacts.
+ */
+public final class ArtifactSet implements SortedSet<Artifact<?>> {
+
+  private SortedSet<Artifact<?>> treeSet = new TreeSet<Artifact<?>>();
+
+  public ArtifactSet() {
+  }
+
+  public ArtifactSet(Collection<? extends Artifact<?>> copyFrom) {
+    addAll(copyFrom);
+  }
+
+  public boolean add(Artifact<?> o) {
+    return treeSet.add(o);
+  }
+
+  public boolean addAll(Collection<? extends Artifact<?>> c) {
+    return treeSet.addAll(c);
+  }
+
+  public void clear() {
+    treeSet.clear();
+  }
+
+  public Comparator<? super Artifact<?>> comparator() {
+    return treeSet.comparator();
+  }
+
+  public boolean contains(Object o) {
+    return treeSet.contains(o);
+  }
+
+  public boolean containsAll(Collection<?> c) {
+    return treeSet.containsAll(c);
+  }
+
+  public boolean equals(Object o) {
+    return treeSet.equals(o);
+  }
+
+  /**
+   * Find all Artifacts assignable to some base type. The returned value will be
+   * a snapshot of the values in the ArtifactSet. The following two examples
+   * result in an equivalent set:
+   * 
+   * <pre>
+   * SortedSet&lt;EmittedArtifact&gt; search = artifactSet.find(PublicResource.class);
+   * search.addAll(artifactSet.find(GeneratedResource.class);
+   * </pre>
+   * 
+   * or
+   * 
+   * <pre>
+   * SortedSet&lt;EmittedArtifact&gt; search = artifactSet.find(EmittedArtifact.class);
+   * </pre>
+   * 
+   * @param <A> a type bound possibly wider than the desired type of artifact
+   * @param <T> the desired type of Artifact
+   * @param artifactType the desired type of Artifact
+   * @return all Artifacts in the ArtifactSet assignable to the desired type
+   */
+  public <A extends Artifact<?>, T extends A> SortedSet<A> find(
+      Class<T> artifactType) {
+    // TODO make this sub-linear
+    SortedSet<A> toReturn = new TreeSet<A>();
+    for (Artifact<?> artifact : this) {
+      if (artifactType.isInstance(artifact)) {
+        toReturn.add(artifactType.cast(artifact));
+      }
+    }
+    return toReturn;
+  }
+
+  public Artifact<?> first() {
+    return treeSet.first();
+  }
+
+  /**
+   * Prevent further modification of the the ArtifactSet. Any attempts to alter
+   * the ArtifactSet after invoking this method will result in an
+   * UnsupportedOperationException.
+   */
+  public void freeze() {
+    if (treeSet instanceof TreeSet) {
+      treeSet = Collections.unmodifiableSortedSet(treeSet);
+    }
+  }
+
+  public int hashCode() {
+    return treeSet.hashCode();
+  }
+
+  public SortedSet<Artifact<?>> headSet(Artifact<?> toElement) {
+    return treeSet.headSet(toElement);
+  }
+
+  public boolean isEmpty() {
+    return treeSet.isEmpty();
+  }
+
+  public Iterator<Artifact<?>> iterator() {
+    return treeSet.iterator();
+  }
+
+  public Artifact<?> last() {
+    return treeSet.last();
+  }
+
+  public boolean remove(Object o) {
+    return treeSet.remove(o);
+  }
+
+  public boolean removeAll(Collection<?> c) {
+    return treeSet.removeAll(c);
+  }
+
+  /**
+   * Possibly replace an existing Artifact.
+   * 
+   * @param artifact the replacement Artifact
+   * @return <code>true</code> if an equivalent Artifact was already present.
+   */
+  public boolean replace(Artifact<?> artifact) {
+    boolean toReturn = treeSet.remove(artifact);
+    treeSet.add(artifact);
+    return toReturn;
+  }
+
+  public boolean retainAll(Collection<?> c) {
+    return treeSet.retainAll(c);
+  }
+
+  public int size() {
+    return treeSet.size();
+  }
+
+  public SortedSet<Artifact<?>> subSet(Artifact<?> fromElement,
+      Artifact<?> toElement) {
+    return treeSet.subSet(fromElement, toElement);
+  }
+
+  public SortedSet<Artifact<?>> tailSet(Artifact<?> fromElement) {
+    return treeSet.tailSet(fromElement);
+  }
+
+  public Object[] toArray() {
+    return treeSet.toArray();
+  }
+
+  public <T> T[] toArray(T[] a) {
+    return treeSet.toArray(a);
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/linker/CompilationResult.java b/dev/core/src/com/google/gwt/dev/linker/CompilationResult.java
index a5e916f..de375a7 100644
--- a/dev/core/src/com/google/gwt/dev/linker/CompilationResult.java
+++ b/dev/core/src/com/google/gwt/dev/linker/CompilationResult.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.dev.linker;
 
+import java.util.Map;
 import java.util.SortedMap;
 import java.util.SortedSet;
 
@@ -22,12 +23,17 @@
  * Represents a unique compilation of the module. Multiple permutations may
  * result in identical JavaScript.
  */
-public interface CompilationResult {
+public abstract class CompilationResult extends Artifact<CompilationResult> {
+
+  protected CompilationResult(Class<? extends Linker> linkerType) {
+    super(linkerType);
+  }
+
   /**
    * Returns the JavaScript compilation. The exact form and function of the
    * JavaScript should be considered opaque.
    */
-  String getJavaScript();
+  public abstract String getJavaScript();
 
   /**
    * Provides values for {@link SelectionProperty} instances that are not
@@ -35,5 +41,37 @@
    * multiple mappings, one for each permutation that resulted in the
    * compilation.
    */
-  SortedSet<SortedMap<SelectionProperty, String>> getPropertyMap();
+  public abstract SortedSet<SortedMap<SelectionProperty, String>> getPropertyMap();
+
+  @Override
+  public final int hashCode() {
+    return getJavaScript().hashCode();
+  }
+
+  @Override
+  public String toString() {
+    StringBuffer b = new StringBuffer();
+    b.append("{");
+    for (SortedMap<SelectionProperty, String> map : getPropertyMap()) {
+      b.append(" {");
+      for (Map.Entry<SelectionProperty, String> entry : map.entrySet()) {
+        b.append(" ").append(entry.getKey().getName()).append(":").append(
+            entry.getValue());
+      }
+      b.append(" }");
+    }
+    b.append(" }");
+
+    return b.toString();
+  }
+
+  @Override
+  protected final int compareToComparableArtifact(CompilationResult o) {
+    return getJavaScript().compareTo(o.getJavaScript());
+  }
+
+  @Override
+  protected final Class<CompilationResult> getComparableArtifactType() {
+    return CompilationResult.class;
+  }
 }
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/dev/linker/EmittedArtifact.java b/dev/core/src/com/google/gwt/dev/linker/EmittedArtifact.java
new file mode 100644
index 0000000..6653a33
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/linker/EmittedArtifact.java
@@ -0,0 +1,61 @@
+/*
+ * 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.linker;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+
+import java.io.InputStream;
+
+/**
+ * An artifact that intended to be emitted into the output.
+ */
+public abstract class EmittedArtifact extends Artifact<EmittedArtifact> {
+
+  private final String partialPath;
+
+  protected EmittedArtifact(Class<? extends Linker> linker, String partialPath) {
+    super(linker);
+    this.partialPath = partialPath;
+  }
+
+  public abstract InputStream getContents(TreeLogger logger)
+      throws UnableToCompleteException;
+
+  public final String getPartialPath() {
+    return partialPath;
+  }
+
+  @Override
+  public final int hashCode() {
+    return getPartialPath().hashCode();
+  }
+
+  @Override
+  public String toString() {
+    return getPartialPath();
+  }
+
+  @Override
+  protected final int compareToComparableArtifact(EmittedArtifact o) {
+    return getPartialPath().compareTo(o.getPartialPath());
+  }
+
+  @Override
+  protected final Class<EmittedArtifact> getComparableArtifactType() {
+    return EmittedArtifact.class;
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/linker/GeneratedResource.java b/dev/core/src/com/google/gwt/dev/linker/GeneratedResource.java
index 6f80ef3..5c8f2ee 100644
--- a/dev/core/src/com/google/gwt/dev/linker/GeneratedResource.java
+++ b/dev/core/src/com/google/gwt/dev/linker/GeneratedResource.java
@@ -18,6 +18,9 @@
 /**
  * A resource generated during the compilation process by a Generator.
  */
-public interface GeneratedResource extends ModuleResource {
-  String getPartialPath();
+public abstract class GeneratedResource extends EmittedArtifact {
+  protected GeneratedResource(Class<? extends Linker> linkerType,
+      String partialPath) {
+    super(linkerType, partialPath);
+  }
 }
diff --git a/dev/core/src/com/google/gwt/dev/linker/Linker.java b/dev/core/src/com/google/gwt/dev/linker/Linker.java
index 1ec9abd..b19f4f2 100644
--- a/dev/core/src/com/google/gwt/dev/linker/Linker.java
+++ b/dev/core/src/com/google/gwt/dev/linker/Linker.java
@@ -19,9 +19,10 @@
 import com.google.gwt.core.ext.UnableToCompleteException;
 
 /**
- * Defines a linker for the GWT compiler. One or more Linkers will be invoked
- * after the Java to JavaScript compilation process and are responsible for
- * assembly of the final output from the compiler.
+ * Defines a linker for the GWT compiler. Each Linker must be annotated with a
+ * {@link LinkerOrder} annotation to determine the relative ordering of the
+ * Linkers. Exact order of Linker execution will be determined by the order of
+ * <code>add-linker</code> tags in the module configuration.
  */
 public abstract class Linker {
   /**
@@ -30,14 +31,15 @@
   public abstract String getDescription();
 
   /**
-   * Invoke the Linker. The implementation of this method should rely only on
-   * the provided LinkerContext in order to manipulate the environment.
+   * Invoke the Linker.
    * 
    * @param logger the TreeLogger to record to
    * @param context provides access to the Linker's environment
+   * @param artifacts an unmodifiable view of the artifacts to link
+   * @return the artifacts that should be propagated through the linker chain
    * @throws UnableToCompleteException if compilation violates assumptions made
    *           by the Linker or for errors encountered by the Linker
    */
-  public abstract void link(TreeLogger logger, LinkerContext context)
-      throws UnableToCompleteException;
+  public abstract ArtifactSet link(TreeLogger logger, LinkerContext context,
+      ArtifactSet artifacts) throws UnableToCompleteException;
 }
diff --git a/dev/core/src/com/google/gwt/dev/linker/LinkerContext.java b/dev/core/src/com/google/gwt/dev/linker/LinkerContext.java
index 7767d25..b799b05 100644
--- a/dev/core/src/com/google/gwt/dev/linker/LinkerContext.java
+++ b/dev/core/src/com/google/gwt/dev/linker/LinkerContext.java
@@ -18,92 +18,16 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 
-import java.io.OutputStream;
-import java.util.Comparator;
 import java.util.SortedSet;
 
 /**
- * Provides access to all information and runtime services required by a
- * {@link Linker}. Methods that return a {@link SortedSet} are guaranteed to
- * have stable iteration order between runs of the compiler over identical
- * input. Unless otherwise specified, the exact iteration order is left as an
- * implementation detail.
+ * Provides access to data about the linking process. Methods that return a
+ * {@link SortedSet} are guaranteed to have stable iteration order between runs
+ * of the compiler over identical input. Unless otherwise specified, the exact
+ * iteration order is left as an implementation detail.
  */
 public interface LinkerContext {
   /**
-   * Orders CompilationResults by string comparison of their JavaScript.
-   */
-  Comparator<CompilationResult> COMPILATION_RESULT_COMPARATOR = new Comparator<CompilationResult>() {
-    public int compare(CompilationResult o1, CompilationResult o2) {
-      return o1.getJavaScript().compareTo(o2.getJavaScript());
-    }
-  };
-
-  /**
-   * Orders GeneratedResources by string comparison of their partial paths.
-   */
-  Comparator<GeneratedResource> GENERATED_RESOURCE_COMPARATOR = new Comparator<GeneratedResource>() {
-    public int compare(GeneratedResource o1, GeneratedResource o2) {
-      return o1.getPartialPath().compareTo(o2.getPartialPath());
-    }
-  };
-
-  /**
-   * Orders PublicResources by string comparison of their partial paths.
-   */
-  Comparator<PublicResource> PUBLIC_RESOURCE_COMPARATOR = new Comparator<PublicResource>() {
-    public int compare(PublicResource o1, PublicResource o2) {
-      return o1.getPartialPath().compareTo(o2.getPartialPath());
-    }
-  };
-
-  /**
-   * Orders ModuleScriptResources by string comparison of their src attributes.
-   */
-  Comparator<ModuleScriptResource> SCRIPT_RESOURCE_COMPARATOR = new Comparator<ModuleScriptResource>() {
-    public int compare(ModuleScriptResource o1, ModuleScriptResource o2) {
-      return o1.getSrc().compareTo(o2.getSrc());
-    }
-  };
-
-  /**
-   * Orders SelectionProperties by string comparison of their names.
-   */
-  Comparator<SelectionProperty> SELECTION_PROPERTY_COMPARATOR = new Comparator<SelectionProperty>() {
-    public int compare(SelectionProperty o1, SelectionProperty o2) {
-      return o1.getName().compareTo(o2.getName());
-    }
-  };
-
-  /**
-   * Orders ModuleStyleResources by string comparison of their src attributes.
-   */
-  Comparator<ModuleStylesheetResource> STYLE_RESOURCE_COMPARATOR = new Comparator<ModuleStylesheetResource>() {
-    public int compare(ModuleStylesheetResource o1, ModuleStylesheetResource o2) {
-      return o1.getSrc().compareTo(o2.getSrc());
-    }
-  };
-
-  /**
-   * Finalizes the OutputStream for a given artifact. This method must be called
-   * in order to actually place the artifact into the output directory. If the
-   * OutptStream has not already been closed, this method will close the
-   * OutputStream.
-   */
-  void commit(TreeLogger logger, OutputStream out)
-      throws UnableToCompleteException;
-
-  /**
-   * Returns all unique compilations of the module.
-   */
-  SortedSet<CompilationResult> getCompilations();
-
-  /**
-   * Returns all resources emitted through a GeneratorContext.
-   */
-  SortedSet<GeneratedResource> getGeneratedResources();
-
-  /**
    * Returns the name of the module's bootstrap function.
    */
   String getModuleFunctionName();
@@ -114,18 +38,6 @@
   String getModuleName();
 
   /**
-   * Provides access to all <code>script</code> tags referenced by the module
-   * definition.
-   */
-  SortedSet<ModuleScriptResource> getModuleScripts();
-
-  /**
-   * Provides access to all <code>stylesheet</code> tags referenced by the
-   * module definition.
-   */
-  SortedSet<ModuleStylesheetResource> getModuleStylesheets();
-
-  /**
    * Returns all deferred binding properties defined in the module. The
    * SelectionProperties will be sorted by the standard string comparison
    * function on the name of the property.
@@ -133,11 +45,6 @@
   SortedSet<SelectionProperty> getProperties();
 
   /**
-   * Returns all files in the module's public path.
-   */
-  SortedSet<PublicResource> getPublicResources();
-
-  /**
    * Applies optimizations to a JavaScript program. This method is intended to
    * be applied to bootstrap scripts in order to apply context-specific
    * transformations to the program, based on the compiler's configuration. The
@@ -150,41 +57,4 @@
    */
   String optimizeJavaScript(TreeLogger logger, String jsProgram)
       throws UnableToCompleteException;
-
-  /**
-   * Attempt to create an artifact within the linker's output directory. If a
-   * similarly-named resource has already been created, this method will return
-   * <code>null</code>. The data written to the OutputStream will not be
-   * written into the output directory unless
-   * {@link #commit(TreeLogger, OutputStream)} is called.
-   * 
-   * @param logger
-   * @param partialPath the partial path of the artifact
-   * @return An OutputStream through which the artifact may be written, or
-   *         <code>null</code> if the artifact had previously been created.
-   */
-  OutputStream tryCreateArtifact(TreeLogger logger, String partialPath);
-
-  /**
-   * Provides named access to generated resources.
-   * 
-   * @param logger a logging destination
-   * @param partialPath a partial path, generally obtained from
-   *          {@link GeneratedResource#getPartialPath()}
-   * @return The requested resource, or <code>null</code> if no such resource
-   *         exists
-   */
-  GeneratedResource tryGetGeneratedResource(TreeLogger logger,
-      String partialPath);
-
-  /**
-   * Provides name access to resources in the module's public path.
-   * 
-   * @param logger a logging destination
-   * @param partialPath a partial path, generally obtained from
-   *          {@link PublicResource#getPartialPath()}
-   * @return The requested resource or <code>null</code> if no such resource
-   *         exists
-   */
-  PublicResource tryGetPublicResource(TreeLogger logger, String partialPath);
 }
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/dev/linker/LinkerContextShim.java b/dev/core/src/com/google/gwt/dev/linker/LinkerContextShim.java
deleted file mode 100644
index 59b25c5..0000000
--- a/dev/core/src/com/google/gwt/dev/linker/LinkerContextShim.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright 2008 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.dev.linker;
-
-import com.google.gwt.core.ext.TreeLogger;
-import com.google.gwt.core.ext.UnableToCompleteException;
-
-import java.io.OutputStream;
-import java.util.SortedSet;
-
-/**
- * This base class allows behaviors to be injected into the
- * {@link LinkerContext} that is observed by the {@link Linker} types operating
- * on the output from the compiler. Instances of LinkerContextShim are mapped
- * into the compilation process by including {@code <extend-linker-context>}
- * tags in the GWT module definition. Subclasses of LinkerContextShim must
- * define a two-argument constructor that accepts an instance of TreeLogger and
- * LinkerContext.
- * <p>
- * The default behavior of all methods in this class is to delegate to the
- * LinkerContext returned from {@link #getParent()}. Separate shim instances
- * are guaranteed to be used for each Linker instance.
- * <p>
- * No guarantees are made on the order in which or number of times any method on
- * the LinkerContextShim will be invoked. Implementations are encouraged to
- * precompute all return values for each method in their constructors and return
- * an unmodifiable wrapper around the collection using
- * {@link java.util.Collections#unmodifiableSortedSet(SortedSet)}.
- */
-public abstract class LinkerContextShim implements LinkerContext {
-  private final LinkerContext parent;
-
-  protected LinkerContextShim(TreeLogger logger, LinkerContext parent)
-      throws UnableToCompleteException {
-    this.parent = parent;
-  }
-
-  /**
-   * Finalize all actions performed by the LinkerContextShim. This method will
-   * be called in reverse order; it will not be called on a parent until all of
-   * its children have been committed.
-   */
-  public void commit(TreeLogger logger) throws UnableToCompleteException {
-  }
-
-  public void commit(TreeLogger logger, OutputStream out)
-      throws UnableToCompleteException {
-    getParent().commit(logger, out);
-  }
-
-  public SortedSet<CompilationResult> getCompilations() {
-    return getParent().getCompilations();
-  }
-
-  public SortedSet<GeneratedResource> getGeneratedResources() {
-    return getParent().getGeneratedResources();
-  }
-
-  public String getModuleFunctionName() {
-    return getParent().getModuleFunctionName();
-  }
-
-  public String getModuleName() {
-    return getParent().getModuleName();
-  }
-
-  public SortedSet<ModuleScriptResource> getModuleScripts() {
-    return getParent().getModuleScripts();
-  }
-
-  public SortedSet<ModuleStylesheetResource> getModuleStylesheets() {
-    return getParent().getModuleStylesheets();
-  }
-
-  /**
-   * Obtain a reference to the parent LinkerContext. This method is guaranteed
-   * to return a useful value before any of the other LinkerContext-derived
-   * methods are invoked.
-   */
-  // NB This is final because StandardLinkerContext depends on it to unwind
-  public final LinkerContext getParent() {
-    return parent;
-  }
-
-  public SortedSet<SelectionProperty> getProperties() {
-    return getParent().getProperties();
-  }
-
-  public SortedSet<PublicResource> getPublicResources() {
-    return getParent().getPublicResources();
-  }
-
-  public String optimizeJavaScript(TreeLogger logger, String jsProgram)
-      throws UnableToCompleteException {
-    return getParent().optimizeJavaScript(logger, jsProgram);
-  }
-
-  public OutputStream tryCreateArtifact(TreeLogger logger, String partialPath) {
-    return getParent().tryCreateArtifact(logger, partialPath);
-  }
-
-  public GeneratedResource tryGetGeneratedResource(TreeLogger logger,
-      String partialPath) {
-    return getParent().tryGetGeneratedResource(logger, partialPath);
-  }
-
-  public PublicResource tryGetPublicResource(TreeLogger logger,
-      String partialPath) {
-    return getParent().tryGetPublicResource(logger, partialPath);
-  }
-}
diff --git a/dev/core/src/com/google/gwt/dev/linker/LinkerOrder.java b/dev/core/src/com/google/gwt/dev/linker/LinkerOrder.java
new file mode 100644
index 0000000..f26a78d
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/linker/LinkerOrder.java
@@ -0,0 +1,55 @@
+/*
+ * 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.linker;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Defines the relative order in which a Linker will be run.
+ */
+@Documented
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface LinkerOrder {
+  /**
+   * Allowable values for the LinkerOrder.
+   */
+  public enum Order {
+    /**
+     * Runs after the primary linker.
+     */
+    POST,
+
+    /**
+     * Runs before the primary linker.
+     */
+    PRE,
+
+    /**
+     * Defines the primary linker. Adding a primary linker to the Linker stack
+     * will override the previously-set primary linker.
+     */
+    PRIMARY;
+  }
+
+  Order value();
+}
diff --git a/dev/core/src/com/google/gwt/dev/linker/ModuleResource.java b/dev/core/src/com/google/gwt/dev/linker/ModuleResource.java
deleted file mode 100644
index 4f65a9f..0000000
--- a/dev/core/src/com/google/gwt/dev/linker/ModuleResource.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2008 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.dev.linker;
-
-import com.google.gwt.core.ext.TreeLogger;
-import com.google.gwt.core.ext.UnableToCompleteException;
-
-import java.io.InputStream;
-
-/**
- * Provides access to resources used by the module.
- */
-public interface ModuleResource {
-  /**
-   * Provides access to the contents of the resource if it is statically
-   * available.
-   * 
-   * @return An InputStream accessing the contents of the resource or
-   *         <code>null</code> if the resource is not available at link time.
-   */
-  InputStream tryGetResourceAsStream(TreeLogger logger)
-      throws UnableToCompleteException;
-}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/dev/linker/ModuleScriptResource.java b/dev/core/src/com/google/gwt/dev/linker/ModuleScriptResource.java
deleted file mode 100644
index a4a737d..0000000
--- a/dev/core/src/com/google/gwt/dev/linker/ModuleScriptResource.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2008 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.dev.linker;
-
-/**
- * An external script file referenced in the module manifest.
- */
-public interface ModuleScriptResource extends ModuleResource {
-  /**
-   * The <code>src</code> attribute of the resource. This string is returned
-   * raw and may be a partial path or a URL.
-   */
-  String getSrc();
-}
diff --git a/dev/core/src/com/google/gwt/dev/linker/ModuleStylesheetResource.java b/dev/core/src/com/google/gwt/dev/linker/ModuleStylesheetResource.java
deleted file mode 100644
index 4d57310..0000000
--- a/dev/core/src/com/google/gwt/dev/linker/ModuleStylesheetResource.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2008 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.dev.linker;
-
-/**
- * An external stylesheet referenced in the module manifest.
- */
-public interface ModuleStylesheetResource extends ModuleResource {
-  /**
-   * The <code>src</code> attribute of the resource. This string is returned
-   * raw and may be a partial path or a URL.
-   */
-  String getSrc();
-}
diff --git a/dev/core/src/com/google/gwt/dev/linker/NoDeployResourcesShim.java b/dev/core/src/com/google/gwt/dev/linker/NoDeployResourcesShim.java
deleted file mode 100644
index fd49ccc..0000000
--- a/dev/core/src/com/google/gwt/dev/linker/NoDeployResourcesShim.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright 2008 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.dev.linker;
-
-import com.google.gwt.core.ext.TreeLogger;
-import com.google.gwt.core.ext.UnableToCompleteException;
-
-import java.util.Collections;
-import java.util.SortedSet;
-import java.util.TreeSet;
-
-/**
- * This class prevents generated resources whose partial path begins with
- * {@value #PREFIX} from being visible.
- */
-public class NoDeployResourcesShim extends LinkerContextShim {
-  public static final String PREFIX = "no-deploy/";
-  private final SortedSet<GeneratedResource> generatedResources;
-
-  public NoDeployResourcesShim(TreeLogger logger, LinkerContext parent)
-      throws UnableToCompleteException {
-    super(logger, parent);
-
-    SortedSet<GeneratedResource> mutableSet = new TreeSet<GeneratedResource>(
-        GENERATED_RESOURCE_COMPARATOR);
-
-    SortedSet<GeneratedResource> view = super.getGeneratedResources();
-    for (GeneratedResource res : view) {
-      if (!res.getPartialPath().toLowerCase().startsWith(PREFIX)) {
-        mutableSet.add(res);
-      } else {
-        logger.log(TreeLogger.SPAM, "Excluding generated resource "
-            + res.getPartialPath(), null);
-      }
-    }
-
-    assert mutableSet.size() <= view.size();
-
-    if (mutableSet.size() == view.size()) {
-      // Reuse the existing view
-      generatedResources = view;
-
-    } else {
-      // Ensure that the new view is immutable
-      generatedResources = Collections.unmodifiableSortedSet(mutableSet);
-    }
-  }
-
-  @Override
-  public SortedSet<GeneratedResource> getGeneratedResources() {
-    return generatedResources;
-  }
-}
diff --git a/dev/core/src/com/google/gwt/dev/linker/PublicResource.java b/dev/core/src/com/google/gwt/dev/linker/PublicResource.java
index 07397da..c81a9b6 100644
--- a/dev/core/src/com/google/gwt/dev/linker/PublicResource.java
+++ b/dev/core/src/com/google/gwt/dev/linker/PublicResource.java
@@ -18,6 +18,9 @@
 /**
  * A resource in the module's public path.
  */
-public interface PublicResource extends ModuleResource {
-  String getPartialPath();
+public abstract class PublicResource extends EmittedArtifact {
+  protected PublicResource(Class<? extends Linker> linkerType,
+      String partialPath) {
+    super(linkerType, partialPath);
+  }
 }
diff --git a/dev/core/src/com/google/gwt/dev/linker/ScriptReference.java b/dev/core/src/com/google/gwt/dev/linker/ScriptReference.java
new file mode 100644
index 0000000..82e91ba
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/linker/ScriptReference.java
@@ -0,0 +1,56 @@
+/*
+ * 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.linker;
+
+/**
+ * An external script file referenced in the module manifest.
+ */
+public abstract class ScriptReference extends Artifact<ScriptReference> {
+  private final String src;
+
+  protected ScriptReference(Class<? extends Linker> linkerType, String src) {
+    super(linkerType);
+    this.src = src;
+  }
+
+  /**
+   * The <code>src</code> attribute of the resource. This string is returned
+   * raw and may be a partial path or a URL.
+   */
+  public final String getSrc() {
+    return src;
+  }
+
+  @Override
+  public final int hashCode() {
+    return getSrc().hashCode();
+  }
+
+  @Override
+  public String toString() {
+    return "<script src='" + getSrc() + "'>";
+  }
+
+  @Override
+  protected final int compareToComparableArtifact(ScriptReference o) {
+    return getSrc().compareTo(o.getSrc());
+  }
+
+  @Override
+  protected final Class<ScriptReference> getComparableArtifactType() {
+    return ScriptReference.class;
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/linker/StylesheetReference.java b/dev/core/src/com/google/gwt/dev/linker/StylesheetReference.java
new file mode 100644
index 0000000..2900f13
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/linker/StylesheetReference.java
@@ -0,0 +1,56 @@
+/*
+ * 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.linker;
+
+/**
+ * An external stylesheet referenced in the module manifest.
+ */
+public abstract class StylesheetReference extends Artifact<StylesheetReference> {
+  private final String src;
+
+  protected StylesheetReference(Class<? extends Linker> linkerType, String src) {
+    super(linkerType);
+    this.src = src;
+  }
+
+  /**
+   * The <code>src</code> attribute of the resource. This string is returned
+   * raw and may be a partial path or a URL.
+   */
+  public final String getSrc() {
+    return src;
+  }
+
+  @Override
+  public final int hashCode() {
+    return getSrc().hashCode();
+  }
+
+  @Override
+  public String toString() {
+    return "<style src='" + getSrc() + "'>";
+  }
+
+  @Override
+  protected final int compareToComparableArtifact(StylesheetReference o) {
+    return getSrc().compareTo(o.getSrc());
+  }
+
+  @Override
+  protected Class<StylesheetReference> getComparableArtifactType() {
+    return StylesheetReference.class;
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/linker/HostedModeLinker.java b/dev/core/src/com/google/gwt/dev/linker/impl/HostedModeLinker.java
similarity index 76%
rename from dev/core/src/com/google/gwt/dev/linker/HostedModeLinker.java
rename to dev/core/src/com/google/gwt/dev/linker/impl/HostedModeLinker.java
index 710a440..3aad6f2 100644
--- a/dev/core/src/com/google/gwt/dev/linker/HostedModeLinker.java
+++ b/dev/core/src/com/google/gwt/dev/linker/impl/HostedModeLinker.java
@@ -13,10 +13,12 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.dev.linker;
+package com.google.gwt.dev.linker.impl;
 
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.linker.ArtifactSet;
+import com.google.gwt.dev.linker.LinkerContext;
 import com.google.gwt.dev.util.Util;
 import com.google.gwt.util.tools.Utility;
 
@@ -26,25 +28,14 @@
  * This is a partial implementation of the Linker interface to support hosted
  * mode.
  */
+// TODO When this class is removed, move SelectionScriptLinker to gwt-user
 public final class HostedModeLinker extends SelectionScriptLinker {
 
   @Override
-  public void doEmitArtifacts(TreeLogger logger, LinkerContext context)
+  public String generateSelectionScript(TreeLogger logger,
+      LinkerContext context, ArtifactSet artifacts)
       throws UnableToCompleteException {
-    try {
-      // Add hosted mode iframe contents
-      String hostedHtml = Utility.getFileFromClassPath("com/google/gwt/dev/linker/hosted.html");
-      doEmit(logger, context, Util.getBytes(hostedHtml), "hosted.html");
-    } catch (IOException e) {
-      logger.log(TreeLogger.ERROR, "Unable to copy support resource", e);
-      throw new UnableToCompleteException();
-    }
-  }
-
-  @Override
-  public String generateSelectionScript(TreeLogger logger, LinkerContext context)
-      throws UnableToCompleteException {
-    return super.generateSelectionScript(logger, context);
+    return super.generateSelectionScript(logger, context, artifacts);
   }
 
   public String getDescription() {
@@ -52,6 +43,23 @@
   }
 
   @Override
+  public ArtifactSet link(TreeLogger logger, LinkerContext context,
+      ArtifactSet artifacts) throws UnableToCompleteException {
+    ArtifactSet toReturn = new ArtifactSet(artifacts);
+
+    try {
+      // Add hosted mode iframe contents
+      String hostedHtml = Utility.getFileFromClassPath("com/google/gwt/dev/linker/impl/hosted.html");
+      toReturn.add(emitBytes(logger, Util.getBytes(hostedHtml), "hosted.html"));
+    } catch (IOException e) {
+      logger.log(TreeLogger.ERROR, "Unable to copy support resource", e);
+      throw new UnableToCompleteException();
+    }
+
+    return toReturn;
+  }
+
+  @Override
   protected String getCompilationExtension(TreeLogger logger,
       LinkerContext context) throws UnableToCompleteException {
     return unsupported(logger);
diff --git a/dev/core/src/com/google/gwt/dev/linker/SelectionScriptLinker.java b/dev/core/src/com/google/gwt/dev/linker/impl/SelectionScriptLinker.java
similarity index 81%
rename from dev/core/src/com/google/gwt/dev/linker/SelectionScriptLinker.java
rename to dev/core/src/com/google/gwt/dev/linker/impl/SelectionScriptLinker.java
index b2bd677..a7d4924 100644
--- a/dev/core/src/com/google/gwt/dev/linker/SelectionScriptLinker.java
+++ b/dev/core/src/com/google/gwt/dev/linker/impl/SelectionScriptLinker.java
@@ -13,10 +13,18 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.dev.linker;
+package com.google.gwt.dev.linker.impl;
 
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.linker.AbstractLinker;
+import com.google.gwt.dev.linker.ArtifactSet;
+import com.google.gwt.dev.linker.CompilationResult;
+import com.google.gwt.dev.linker.EmittedArtifact;
+import com.google.gwt.dev.linker.LinkerContext;
+import com.google.gwt.dev.linker.ScriptReference;
+import com.google.gwt.dev.linker.SelectionProperty;
+import com.google.gwt.dev.linker.StylesheetReference;
 import com.google.gwt.dev.util.Util;
 import com.google.gwt.util.tools.Utility;
 
@@ -26,13 +34,19 @@
 import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.SortedSet;
 
 /**
  * A base class for Linkers that use an external script to boostrap the GWT
  * module. This implementation injects JavaScript snippits into a JS program
  * defined in an external file.
  */
-abstract class SelectionScriptLinker extends AbstractLinker {
+public abstract class SelectionScriptLinker extends AbstractLinker {
+  /**
+   * TODO(bobv): Move this class into c.g.g.core.linker when HostedModeLinker
+   * goes away?
+   */
+
   /**
    * Determines whether or not the URL is relative.
    * 
@@ -73,32 +87,39 @@
   private final Map<CompilationResult, String> compilationPartialPaths = new IdentityHashMap<CompilationResult, String>();
 
   @Override
-  protected void doEmitArtifacts(TreeLogger logger, LinkerContext context)
-      throws UnableToCompleteException {
-    super.doEmitArtifacts(logger, context);
+  public ArtifactSet link(TreeLogger logger, LinkerContext context,
+      ArtifactSet artifacts) throws UnableToCompleteException {
+    ArtifactSet toReturn = new ArtifactSet(artifacts);
 
-    emitSelectionScript(logger, context);
+    for (CompilationResult compilation : toReturn.find(CompilationResult.class)) {
+      toReturn.add(doEmitCompilation(logger, context, compilation));
+    }
+
+    toReturn.add(emitSelectionScript(logger, context, artifacts));
+    return toReturn;
   }
 
-  @Override
-  protected void doEmitCompilation(TreeLogger logger, LinkerContext context,
-      CompilationResult result) throws UnableToCompleteException {
+  protected EmittedArtifact doEmitCompilation(TreeLogger logger,
+      LinkerContext context, CompilationResult result)
+      throws UnableToCompleteException {
     StringBuffer b = new StringBuffer();
     b.append(getModulePrefix(logger, context));
     b.append(result.getJavaScript());
     b.append(getModuleSuffix(logger, context));
-    String partialPath = emitWithStrongName(logger, context,
+    EmittedArtifact toReturn = emitWithStrongName(logger,
         Util.getBytes(b.toString()), "", getCompilationExtension(logger,
             context));
-    compilationPartialPaths.put(result, partialPath);
+    compilationPartialPaths.put(result, toReturn.getPartialPath());
+    return toReturn;
   }
 
-  protected void emitSelectionScript(TreeLogger logger, LinkerContext context)
+  protected EmittedArtifact emitSelectionScript(TreeLogger logger,
+      LinkerContext context, ArtifactSet artifacts)
       throws UnableToCompleteException {
-    String selectionScript = generateSelectionScript(logger, context);
+    String selectionScript = generateSelectionScript(logger, context, artifacts);
     byte[] selectionScriptBytes = Util.getBytes(context.optimizeJavaScript(
         logger, selectionScript));
-    doEmit(logger, context, selectionScriptBytes, context.getModuleName()
+    return emitBytes(logger, selectionScriptBytes, context.getModuleName()
         + ".nocache.js");
   }
 
@@ -147,7 +168,9 @@
   }
 
   protected String generateSelectionScript(TreeLogger logger,
-      LinkerContext context) throws UnableToCompleteException {
+      LinkerContext context, ArtifactSet artifacts)
+      throws UnableToCompleteException {
+
     StringBuffer selectionScript;
     try {
       selectionScript = new StringBuffer(
@@ -168,13 +191,13 @@
     // Add external dependencies
     startPos = selectionScript.indexOf("// __MODULE_DEPS_END__");
     if (startPos != -1) {
-      for (ModuleStylesheetResource resource : context.getModuleStylesheets()) {
+      for (StylesheetReference resource : artifacts.find(StylesheetReference.class)) {
         String text = generateStylesheetInjector(resource.getSrc());
         selectionScript.insert(startPos, text);
         startPos += text.length();
       }
 
-      for (ModuleScriptResource resource : context.getModuleScripts()) {
+      for (ScriptReference resource : artifacts.find(ScriptReference.class)) {
         String text = generateScriptInjector(resource.getSrc());
         selectionScript.insert(startPos, text);
         startPos += text.length();
@@ -192,23 +215,24 @@
     }
 
     // Possibly add permutations
+    SortedSet<CompilationResult> compilations = artifacts.find(CompilationResult.class);
     startPos = selectionScript.indexOf("// __PERMUTATIONS_END__");
     if (startPos != -1) {
       StringBuffer text = new StringBuffer();
-      if (context.getCompilations().size() == 0) {
+      if (compilations.size() == 0) {
         // We'll see this when running in Hosted Mode. The way the selection
         // templates are structured is such that this line won't be executed
         text.append("strongName = null;");
 
-      } else if (context.getCompilations().size() == 1) {
+      } else if (compilations.size() == 1) {
         // Just one distinct compilation; no need to evaluate properties
-        Iterator<CompilationResult> iter = context.getCompilations().iterator();
+        Iterator<CompilationResult> iter = compilations.iterator();
         CompilationResult result = iter.next();
         text.append("strongName = '" + compilationPartialPaths.get(result)
             + "';");
 
       } else {
-        for (CompilationResult r : context.getCompilations()) {
+        for (CompilationResult r : compilations) {
           for (Map<SelectionProperty, String> propertyMap : r.getPropertyMap()) {
             // unflatten([v1, v2, v3], 'strongName');
             text.append("unflattenKeylistIntoAnswers([");
diff --git a/dev/core/src/com/google/gwt/dev/linker/impl/StandardCompilationResult.java b/dev/core/src/com/google/gwt/dev/linker/impl/StandardCompilationResult.java
index b751a45..893a822 100644
--- a/dev/core/src/com/google/gwt/dev/linker/impl/StandardCompilationResult.java
+++ b/dev/core/src/com/google/gwt/dev/linker/impl/StandardCompilationResult.java
@@ -35,7 +35,7 @@
 /**
  * The standard implementation of {@link CompilationResult}.
  */
-public class StandardCompilationResult implements CompilationResult {
+public class StandardCompilationResult extends CompilationResult {
 
   /**
    * Smaller maps come before larger maps, then we compare the concatenation of
@@ -74,6 +74,7 @@
 
   public StandardCompilationResult(TreeLogger logger, String js, File cacheDir)
       throws UnableToCompleteException {
+    super(StandardLinkerContext.class);
     this.js = new SoftReference<String>(js);
 
     byte[] bytes = Util.getBytes(js);
@@ -110,21 +111,4 @@
   public String getStrongName() {
     return strongName;
   }
-
-  @Override
-  public String toString() {
-    StringBuffer b = new StringBuffer();
-    b.append("{");
-    for (SortedMap<SelectionProperty, String> map : propertyValues) {
-      b.append(" {");
-      for (Map.Entry<SelectionProperty, String> entry : map.entrySet()) {
-        b.append(" ").append(entry.getKey().getName()).append(":").append(
-            entry.getValue());
-      }
-      b.append(" }");
-    }
-    b.append(" }");
-
-    return b.toString();
-  }
 }
diff --git a/dev/core/src/com/google/gwt/dev/linker/impl/StandardGeneratedResource.java b/dev/core/src/com/google/gwt/dev/linker/impl/StandardGeneratedResource.java
index 5f4bd81..ba2ce8d 100644
--- a/dev/core/src/com/google/gwt/dev/linker/impl/StandardGeneratedResource.java
+++ b/dev/core/src/com/google/gwt/dev/linker/impl/StandardGeneratedResource.java
@@ -15,21 +15,33 @@
  */
 package com.google.gwt.dev.linker.impl;
 
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.dev.linker.GeneratedResource;
 
+import java.io.IOException;
+import java.io.InputStream;
 import java.net.URL;
 
 /**
  * The standard implementation of {@link GeneratedResource}.
  */
-public class StandardGeneratedResource extends StandardModuleResource implements
-    GeneratedResource {
+public class StandardGeneratedResource extends GeneratedResource {
+  private final URL url;
 
   public StandardGeneratedResource(String partialPath, URL url) {
-    super(partialPath, url);
+    super(StandardLinkerContext.class, partialPath);
+    this.url = url;
   }
 
-  public String getPartialPath() {
-    return getId();
+  @Override
+  public InputStream getContents(TreeLogger logger)
+      throws UnableToCompleteException {
+    try {
+      return url.openStream();
+    } catch (IOException e) {
+      logger.log(TreeLogger.ERROR, "Unable to open file", e);
+      throw new UnableToCompleteException();
+    }
   }
 }
diff --git a/dev/core/src/com/google/gwt/dev/linker/impl/StandardLinkerContext.java b/dev/core/src/com/google/gwt/dev/linker/impl/StandardLinkerContext.java
index 6471d2b..d949572 100644
--- a/dev/core/src/com/google/gwt/dev/linker/impl/StandardLinkerContext.java
+++ b/dev/core/src/com/google/gwt/dev/linker/impl/StandardLinkerContext.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.GWTCompiler;
 import com.google.gwt.dev.cfg.ModuleDef;
 import com.google.gwt.dev.cfg.Property;
 import com.google.gwt.dev.cfg.Script;
@@ -38,43 +39,39 @@
 import com.google.gwt.dev.js.ast.JsName;
 import com.google.gwt.dev.js.ast.JsProgram;
 import com.google.gwt.dev.js.ast.JsScope;
-import com.google.gwt.dev.linker.CompilationResult;
+import com.google.gwt.dev.linker.ArtifactSet;
+import com.google.gwt.dev.linker.EmittedArtifact;
 import com.google.gwt.dev.linker.GeneratedResource;
 import com.google.gwt.dev.linker.Linker;
 import com.google.gwt.dev.linker.LinkerContext;
-import com.google.gwt.dev.linker.LinkerContextShim;
-import com.google.gwt.dev.linker.ModuleScriptResource;
-import com.google.gwt.dev.linker.ModuleStylesheetResource;
+import com.google.gwt.dev.linker.LinkerOrder;
 import com.google.gwt.dev.linker.PublicResource;
 import com.google.gwt.dev.linker.SelectionProperty;
+import com.google.gwt.dev.linker.LinkerOrder.Order;
 import com.google.gwt.dev.util.DefaultTextOutput;
 import com.google.gwt.dev.util.Util;
 
-import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
-import java.io.OutputStream;
 import java.io.Reader;
 import java.io.StringReader;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
 import java.net.MalformedURLException;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Comparator;
+import java.util.EnumSet;
 import java.util.HashMap;
-import java.util.HashSet;
-import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.SortedSet;
+import java.util.Stack;
 import java.util.TreeSet;
 
 /**
  * An implementation of {@link LinkerContext} that is initialized from a
  * {@link ModuleDef}.
  */
-public class StandardLinkerContext implements LinkerContext {
+public class StandardLinkerContext extends Linker implements LinkerContext {
 
   /**
    * Applies the {@link JsStringInterner} optimization to each top-level
@@ -95,18 +92,16 @@
     }
   }
 
-  private final File compilationsDir;
-  private final SortedSet<GeneratedResource> generatedResources;
-  private final Map<String, GeneratedResource> generatedResourcesByName = new HashMap<String, GeneratedResource>();
-  private final JJSOptions jjsOptions;
+  static final Comparator<SelectionProperty> SELECTION_PROPERTY_COMPARATOR = new Comparator<SelectionProperty>() {
+    public int compare(SelectionProperty o1, SelectionProperty o2) {
+      return o1.getName().compareTo(o2.getName());
+    }
+  };
 
-  /**
-   * This determines where a call to {@link #commit(TreeLogger, OutputStream)}
-   * will write to. It's intended to be updated by
-   * {@link #invokeLinker(TreeLogger, String, Linker)} so that each Linker will
-   * write into a different output directory.
-   */
-  private File linkerOutDir;
+  private final ArtifactSet artifacts = new ArtifactSet();
+  private final File compilationsDir;
+  private final JJSOptions jjsOptions;
+  private final List<Class<? extends Linker>> linkerClasses;
   private final String moduleFunctionName;
   private final String moduleName;
 
@@ -115,32 +110,28 @@
    * compilation.
    */
   private final File moduleOutDir;
-  private final Set<String> openPaths = new HashSet<String>();
-  private final Map<ByteArrayOutputStream, File> outs = new IdentityHashMap<ByteArrayOutputStream, File>();
   private final SortedSet<SelectionProperty> properties;
   private final Map<String, StandardSelectionProperty> propertiesByName = new HashMap<String, StandardSelectionProperty>();
-  private final SortedSet<PublicResource> publicResources;
-  private final Map<String, PublicResource> publicResourcesByName = new HashMap<String, PublicResource>();
   private final Map<String, StandardCompilationResult> resultsByStrongName = new HashMap<String, StandardCompilationResult>();
-  private final SortedSet<ModuleScriptResource> scriptResources;
-  private final List<Class<? extends LinkerContextShim>> shimClasses;
-  private final SortedSet<ModuleStylesheetResource> stylesheetResources;
 
   public StandardLinkerContext(TreeLogger logger, ModuleDef module,
-      File outDir, File generatorDir, JJSOptions jjsOptions)
-      throws UnableToCompleteException {
+      File moduleOutDir, File generatorDir, JJSOptions jjsOptions) {
     logger = logger.branch(TreeLogger.DEBUG,
         "Constructing StandardLinkerContext", null);
 
     this.jjsOptions = jjsOptions;
     this.moduleFunctionName = module.getFunctionName();
     this.moduleName = module.getName();
-    this.moduleOutDir = outDir;
-    this.shimClasses = new ArrayList<Class<? extends LinkerContextShim>>(
-        module.getLinkerContextShims());
+    this.moduleOutDir = moduleOutDir;
+    this.linkerClasses = new ArrayList<Class<? extends Linker>>(
+        module.getActiveLinkers());
+    linkerClasses.add(module.getActivePrimaryLinker());
 
     if (moduleOutDir != null) {
-      compilationsDir = new File(moduleOutDir, ".gwt-compiler/compilations");
+      compilationsDir = new File(moduleOutDir.getParentFile(),
+          GWTCompiler.GWT_COMPILER_DIR + File.separator + moduleName
+              + File.separator + "compilations");
+
       Util.recursiveDelete(compilationsDir, true);
       compilationsDir.mkdirs();
       logger.log(TreeLogger.SPAM, "compilationsDir: "
@@ -161,34 +152,25 @@
     }
     properties = Collections.unmodifiableSortedSet(mutableProperties);
 
-    SortedSet<ModuleScriptResource> scripts = new TreeSet<ModuleScriptResource>(
-        SCRIPT_RESOURCE_COMPARATOR);
     for (Script script : module.getScripts()) {
-      scripts.add(new StandardScriptResource(script.getSrc(),
+      artifacts.add(new StandardScriptReference(script.getSrc(),
           module.findPublicFile(script.getSrc())));
       logger.log(TreeLogger.SPAM, "Added script " + script.getSrc(), null);
     }
-    scriptResources = Collections.unmodifiableSortedSet(scripts);
 
-    SortedSet<ModuleStylesheetResource> styles = new TreeSet<ModuleStylesheetResource>(
-        STYLE_RESOURCE_COMPARATOR);
     for (String style : module.getStyles()) {
-      styles.add(new StandardStylesheetResource(style,
+      artifacts.add(new StandardStylesheetReference(style,
           module.findPublicFile(style)));
       logger.log(TreeLogger.SPAM, "Added style " + style, null);
     }
-    stylesheetResources = Collections.unmodifiableSortedSet(styles);
 
-    SortedSet<GeneratedResource> genResources = new TreeSet<GeneratedResource>(
-        GENERATED_RESOURCE_COMPARATOR);
     if (generatorDir != null) {
       for (String path : Util.recursiveListPartialPaths(generatorDir, false)) {
+        String partialPath = path.replace(File.separatorChar, '/');
         try {
-          String partialPath = path.replace(File.separatorChar, '/');
           GeneratedResource resource = new StandardGeneratedResource(
               partialPath, (new File(generatorDir, path)).toURL());
-          generatedResourcesByName.put(partialPath, resource);
-          genResources.add(resource);
+          artifacts.add(resource);
           logger.log(TreeLogger.SPAM, "Added generated resource " + resource,
               null);
         } catch (MalformedURLException e) {
@@ -198,53 +180,18 @@
         }
       }
     }
-    generatedResources = Collections.unmodifiableSortedSet(genResources);
 
-    SortedSet<PublicResource> pubResources = new TreeSet<PublicResource>(
-        PUBLIC_RESOURCE_COMPARATOR);
     for (String path : module.getAllPublicFiles()) {
-      PublicResource resource = new StandardPublicResource(path,
+      String partialPath = path.replace(File.separatorChar, '/');
+      PublicResource resource = new StandardPublicResource(partialPath,
           module.findPublicFile(path));
-      publicResourcesByName.put(path, resource);
-      pubResources.add(resource);
+      artifacts.add(resource);
       logger.log(TreeLogger.SPAM, "Added public resource " + resource, null);
     }
-    publicResources = Collections.unmodifiableSortedSet(pubResources);
   }
 
-  public void commit(TreeLogger logger, OutputStream toCommit)
-      throws UnableToCompleteException {
-    logger = logger.branch(TreeLogger.DEBUG,
-        "Attempting to commit OutputStream", null);
-
-    if (!outs.containsKey(toCommit)) {
-      logger.log(TreeLogger.ERROR,
-          "OutputStream was foreign to this LinkerContext", null);
-      throw new UnableToCompleteException();
-    }
-
-    File f = outs.get(toCommit);
-    if (f == null) {
-      logger.log(TreeLogger.ERROR,
-          "The OutputStream has already been committed", null);
-      throw new UnableToCompleteException();
-    }
-
-    /*
-     * Record that we will no longer accept this OutputStream as opposed to
-     * removing it, which would erroneously indicate that it was a foreign
-     * OutputStream.
-     */
-    ByteArrayOutputStream original = (ByteArrayOutputStream) toCommit;
-    outs.put(original, null);
-
-    try {
-      Util.writeBytesToFile(logger, f, original.toByteArray());
-    } finally {
-      // Dump the byte buffer;
-      original.reset();
-    }
-    logger.log(TreeLogger.DEBUG, "Successfully committed " + f.getPath(), null);
+  public ArtifactSet getArtifacts() {
+    return artifacts;
   }
 
   public StandardCompilationResult getCompilation(TreeLogger logger, String js)
@@ -255,19 +202,14 @@
     if (result == null) {
       result = new StandardCompilationResult(logger, js, compilationsDir);
       resultsByStrongName.put(result.getStrongName(), result);
+      artifacts.add(result);
     }
     return result;
   }
 
-  public SortedSet<CompilationResult> getCompilations() {
-    SortedSet<CompilationResult> toReturn = new TreeSet<CompilationResult>(
-        COMPILATION_RESULT_COMPARATOR);
-    toReturn.addAll(resultsByStrongName.values());
-    return Collections.unmodifiableSortedSet(toReturn);
-  }
-
-  public SortedSet<GeneratedResource> getGeneratedResources() {
-    return generatedResources;
+  @Override
+  public String getDescription() {
+    return "Root Linker";
   }
 
   public String getModuleFunctionName() {
@@ -278,14 +220,6 @@
     return moduleName;
   }
 
-  public SortedSet<ModuleScriptResource> getModuleScripts() {
-    return scriptResources;
-  }
-
-  public SortedSet<ModuleStylesheetResource> getModuleStylesheets() {
-    return stylesheetResources;
-  }
-
   public SortedSet<SelectionProperty> getProperties() {
     return properties;
   }
@@ -294,82 +228,25 @@
     return propertiesByName.get(name);
   }
 
-  public SortedSet<PublicResource> getPublicResources() {
-    return publicResources;
-  }
+  @Override
+  public ArtifactSet link(TreeLogger logger, LinkerContext context,
+      ArtifactSet artifacts) throws UnableToCompleteException {
 
-  /**
-   * Run a linker in an isolated out directory.
-   */
-  public void invokeLinker(TreeLogger logger, String target, Linker linker)
-      throws UnableToCompleteException {
-    try {
-      // Assign the directory the Linker will work in.
-      linkerOutDir = new File(moduleOutDir, target);
-      if (!moduleOutDir.equals(linkerOutDir.getParentFile())) {
-        // This should never actually happen, since the target must be
-        // a valid Java identifier
-        logger.log(TreeLogger.ERROR,
-            "Trying to create linker dir in wrong place", null);
-        throw new UnableToCompleteException();
-      }
+    logger = logger.branch(TreeLogger.INFO, "Linking compilation into "
+        + moduleOutDir.getPath(), null);
 
-      // We nuke the contents of the directory
-      Util.recursiveDelete(linkerOutDir, true);
-      linkerOutDir.mkdirs();
+    artifacts = invokeLinkerStack(logger);
 
-      if (!linkerOutDir.canWrite()) {
-        logger.log(TreeLogger.ERROR, "Unable create linker dir"
-            + linkerOutDir.getPath(), null);
-        throw new UnableToCompleteException();
-      }
+    for (EmittedArtifact artifact : artifacts.find(EmittedArtifact.class)) {
+      TreeLogger artifactLogger = logger.branch(TreeLogger.SPAM,
+          "Emitting resource " + artifact.getPartialPath(), null);
 
-      logger = logger.branch(TreeLogger.INFO, "Linking compilation with "
-          + linker.getDescription() + " Linker into " + linkerOutDir.getPath(),
-          null);
-
-      // Instantiate per-Linker instances of the LinkerContextShims
-      LinkerContext shimParent = this;
-      for (Class<? extends LinkerContextShim> clazz : shimClasses) {
-        TreeLogger shimLogger = logger.branch(TreeLogger.DEBUG,
-            "Constructing LinkerContextShim " + clazz.getName(), null);
-        try {
-          Constructor<? extends LinkerContextShim> constructor = clazz.getConstructor(
-              TreeLogger.class, LinkerContext.class);
-          shimParent = constructor.newInstance(shimLogger, shimParent);
-        } catch (InstantiationException e) {
-          shimLogger.log(TreeLogger.ERROR,
-              "Unable to create LinkerContextShim", e);
-          throw new UnableToCompleteException();
-        } catch (InvocationTargetException e) {
-          shimLogger.log(TreeLogger.ERROR,
-              "Unable to create LinkerContextShim", e);
-          throw new UnableToCompleteException();
-        } catch (NoSuchMethodException e) {
-          shimLogger.log(TreeLogger.ERROR,
-              "LinkerContextShim subtypes must implement a two-argument "
-                  + "constructor accepting a TreeLogger and a LinkerContext", e);
-          throw new UnableToCompleteException();
-        } catch (IllegalAccessException e) {
-          shimLogger.log(TreeLogger.ERROR,
-              "Unable to create LinkerContextShim", e);
-          throw new UnableToCompleteException();
-        }
-      }
-
-      linker.link(logger, shimParent);
-
-      // Unwind the LinkerContextShim stack
-      while (shimParent != this) {
-        LinkerContextShim shim = (LinkerContextShim) shimParent;
-        shim.commit(logger.branch(TreeLogger.DEBUG,
-            "Committing LinkerContextShim " + shim.getClass().getName(), null));
-        shimParent = shim.getParent();
-      }
-
-    } finally {
-      reset();
+      File outFile = new File(moduleOutDir, artifact.getPartialPath());
+      assert !outFile.exists() : "Attempted to overwrite " + outFile.getPath();
+      Util.copy(logger, artifact.getContents(artifactLogger), outFile);
     }
+
+    return artifacts;
   }
 
   public String optimizeJavaScript(TreeLogger logger, String program)
@@ -428,36 +305,69 @@
     return out.toString();
   }
 
-  public OutputStream tryCreateArtifact(TreeLogger logger, String partialPath) {
-    File f = new File(linkerOutDir, partialPath);
-    if (f.exists() || openPaths.contains(partialPath)) {
-      logger.branch(TreeLogger.DEBUG, "Refusing to create artifact "
-          + partialPath + " because it already exists or is already open.",
-          null);
-      return null;
+  /**
+   * Run the linker stack.
+   */
+  private ArtifactSet invokeLinkerStack(TreeLogger logger)
+      throws UnableToCompleteException {
+    ArtifactSet workingArtifacts = new ArtifactSet(artifacts);
+    Stack<Linker> linkerStack = new Stack<Linker>();
+
+    EnumSet<Order> phasePre = EnumSet.of(Order.PRE, Order.PRIMARY);
+    EnumSet<Order> phasePost = EnumSet.of(Order.POST);
+
+    // Instantiate instances of the Linkers
+    for (Class<? extends Linker> clazz : linkerClasses) {
+      Linker linker;
+
+      // Create an instance of the Linker
+      try {
+        linker = clazz.newInstance();
+        linkerStack.push(linker);
+      } catch (InstantiationException e) {
+        logger.log(TreeLogger.ERROR, "Unable to create LinkerContextShim", e);
+        throw new UnableToCompleteException();
+      } catch (IllegalAccessException e) {
+        logger.log(TreeLogger.ERROR, "Unable to create LinkerContextShim", e);
+        throw new UnableToCompleteException();
+      }
+
+      // Detemine if we need to invoke the Linker in the current link phase
+      Order order = clazz.getAnnotation(LinkerOrder.class).value();
+      if (!phasePre.contains(order)) {
+        continue;
+      }
+
+      // The primary Linker is guaranteed to be last in the order
+      if (order == Order.PRIMARY) {
+        assert linkerClasses.get(linkerClasses.size() - 1).equals(clazz);
+      }
+
+      TreeLogger linkerLogger = logger.branch(TreeLogger.INFO,
+          "Invoking Linker " + linker.getDescription(), null);
+
+      workingArtifacts.freeze();
+      workingArtifacts = linker.link(linkerLogger, this, workingArtifacts);
     }
 
-    ByteArrayOutputStream out = new ByteArrayOutputStream();
-    outs.put(out, f);
-    openPaths.add(partialPath);
-    return out;
-  }
+    // Pop the primary linker off of the stack
+    linkerStack.pop();
 
-  public GeneratedResource tryGetGeneratedResource(TreeLogger logger,
-      String name) {
-    return generatedResourcesByName.get(name);
-  }
+    // Unwind the stack
+    while (!linkerStack.isEmpty()) {
+      Linker linker = linkerStack.pop();
+      Class<? extends Linker> linkerType = linker.getClass();
 
-  public PublicResource tryGetPublicResource(TreeLogger logger, String name) {
-    return publicResourcesByName.get(name);
-  }
+      // See if the Linker should be run in the current phase
+      Order order = linkerType.getAnnotation(LinkerOrder.class).value();
+      if (phasePost.contains(order)) {
+        workingArtifacts.freeze();
+        workingArtifacts = linker.link(logger.branch(TreeLogger.INFO,
+            "Invoking Linker " + linker.getDescription(), null), this,
+            workingArtifacts);
+      }
+    }
 
-  /**
-   * Reset the context.
-   */
-  private void reset() {
-    linkerOutDir = null;
-    openPaths.clear();
-    outs.clear();
+    return workingArtifacts;
   }
 }
diff --git a/dev/core/src/com/google/gwt/dev/linker/impl/StandardModuleResource.java b/dev/core/src/com/google/gwt/dev/linker/impl/StandardModuleResource.java
deleted file mode 100644
index 5cbc300..0000000
--- a/dev/core/src/com/google/gwt/dev/linker/impl/StandardModuleResource.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2008 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.dev.linker.impl;
-
-import com.google.gwt.core.ext.TreeLogger;
-import com.google.gwt.core.ext.UnableToCompleteException;
-import com.google.gwt.dev.linker.ModuleResource;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-
-/**
- * The standard implementation of {@link ModuleResource}.
- */
-public abstract class StandardModuleResource implements ModuleResource {
-  private final String id;
-  private final URL url;
-
-  protected StandardModuleResource(String id, URL url) {
-    this.id = id;
-    this.url = url;
-  }
-
-  public String getId() {
-    return id;
-  }
-
-  public URL getURL() {
-    return url;
-  }
-
-  public InputStream tryGetResourceAsStream(TreeLogger logger)
-      throws UnableToCompleteException {
-    if (url == null) {
-      logger.branch(TreeLogger.DEBUG, "No contents for resource", null);
-      return null;
-    }
-
-    logger = logger.branch(TreeLogger.DEBUG, "Attempting to get stream for "
-        + url.toExternalForm(), null);
-
-    try {
-      return url.openStream();
-    } catch (IOException e) {
-      logger.log(TreeLogger.ERROR, "Unable to open stream", e);
-      throw new UnableToCompleteException();
-    }
-  }
-}
diff --git a/dev/core/src/com/google/gwt/dev/linker/impl/StandardPublicResource.java b/dev/core/src/com/google/gwt/dev/linker/impl/StandardPublicResource.java
index 269dbf4..a58ecdd 100644
--- a/dev/core/src/com/google/gwt/dev/linker/impl/StandardPublicResource.java
+++ b/dev/core/src/com/google/gwt/dev/linker/impl/StandardPublicResource.java
@@ -15,21 +15,33 @@
  */
 package com.google.gwt.dev.linker.impl;
 
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.dev.linker.PublicResource;
 
+import java.io.IOException;
+import java.io.InputStream;
 import java.net.URL;
 
 /**
  * The standard implementation of {@link PublicResource}.
  */
-public class StandardPublicResource extends StandardModuleResource implements
-    PublicResource {
+public class StandardPublicResource extends PublicResource {
+  private final URL url;
 
   public StandardPublicResource(String partialPath, URL url) {
-    super(partialPath, url);
+    super(StandardLinkerContext.class, partialPath);
+    this.url = url;
   }
 
-  public String getPartialPath() {
-    return getId();
+  @Override
+  public InputStream getContents(TreeLogger logger)
+      throws UnableToCompleteException {
+    try {
+      return url.openStream();
+    } catch (IOException e) {
+      logger.log(TreeLogger.ERROR, "Unable to open file", e);
+      throw new UnableToCompleteException();
+    }
   }
 }
diff --git a/dev/core/src/com/google/gwt/dev/linker/impl/StandardScriptResource.java b/dev/core/src/com/google/gwt/dev/linker/impl/StandardScriptReference.java
similarity index 61%
copy from dev/core/src/com/google/gwt/dev/linker/impl/StandardScriptResource.java
copy to dev/core/src/com/google/gwt/dev/linker/impl/StandardScriptReference.java
index 62303ea..1844609 100644
--- a/dev/core/src/com/google/gwt/dev/linker/impl/StandardScriptResource.java
+++ b/dev/core/src/com/google/gwt/dev/linker/impl/StandardScriptReference.java
@@ -15,25 +15,20 @@
  */
 package com.google.gwt.dev.linker.impl;
 
-import com.google.gwt.dev.linker.ModuleScriptResource;
+import com.google.gwt.dev.linker.ScriptReference;
 
 import java.net.URL;
 
 /**
- * The standard implementation of {@link ModuleScriptResource}.
+ * The standard implementation of {@link ScriptReference}.
  */
-public class StandardScriptResource extends StandardModuleResource implements
-    ModuleScriptResource {
+public class StandardScriptReference extends ScriptReference {
 
-  public StandardScriptResource(String src) {
-    this(src, null);
-  }
-
-  public StandardScriptResource(String src, URL url) {
-    super(src, url);
-  }
-
-  public String getSrc() {
-    return getId();
+  /**
+   * Might use <code>url</code>someday.
+   */
+  @SuppressWarnings("unused")
+  public StandardScriptReference(String src, URL url) {
+    super(StandardLinkerContext.class, src);
   }
 }
diff --git a/dev/core/src/com/google/gwt/dev/linker/impl/StandardScriptResource.java b/dev/core/src/com/google/gwt/dev/linker/impl/StandardStylesheetReference.java
similarity index 61%
rename from dev/core/src/com/google/gwt/dev/linker/impl/StandardScriptResource.java
rename to dev/core/src/com/google/gwt/dev/linker/impl/StandardStylesheetReference.java
index 62303ea..5aff3ea 100644
--- a/dev/core/src/com/google/gwt/dev/linker/impl/StandardScriptResource.java
+++ b/dev/core/src/com/google/gwt/dev/linker/impl/StandardStylesheetReference.java
@@ -15,25 +15,20 @@
  */
 package com.google.gwt.dev.linker.impl;
 
-import com.google.gwt.dev.linker.ModuleScriptResource;
+import com.google.gwt.dev.linker.StylesheetReference;
 
 import java.net.URL;
 
 /**
- * The standard implementation of {@link ModuleScriptResource}.
+ * The standard implementation of {@link StylesheetReference}.
  */
-public class StandardScriptResource extends StandardModuleResource implements
-    ModuleScriptResource {
+public class StandardStylesheetReference extends StylesheetReference {
 
-  public StandardScriptResource(String src) {
-    this(src, null);
-  }
-
-  public StandardScriptResource(String src, URL url) {
-    super(src, url);
-  }
-
-  public String getSrc() {
-    return getId();
+  /**
+   * Might use <code>url</code>someday.
+   */
+  @SuppressWarnings("unused")
+  public StandardStylesheetReference(String src, URL url) {
+    super(StandardLinkerContext.class, src);
   }
 }
diff --git a/dev/core/src/com/google/gwt/dev/linker/impl/StandardStylesheetResource.java b/dev/core/src/com/google/gwt/dev/linker/impl/StandardStylesheetResource.java
deleted file mode 100644
index 306ab97..0000000
--- a/dev/core/src/com/google/gwt/dev/linker/impl/StandardStylesheetResource.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2008 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.dev.linker.impl;
-
-import com.google.gwt.dev.linker.ModuleStylesheetResource;
-
-import java.net.URL;
-
-/**
- * The standard implementation of {@link ModuleStylesheetResource}.
- */
-public class StandardStylesheetResource extends StandardModuleResource
-    implements ModuleStylesheetResource {
-
-  public StandardStylesheetResource(String src) {
-    this(src, null);
-  }
-
-  public StandardStylesheetResource(String src, URL url) {
-    super(src, url);
-  }
-
-  public String getSrc() {
-    return getId();
-  }
-}
diff --git a/dev/core/src/com/google/gwt/dev/linker/hosted.html b/dev/core/src/com/google/gwt/dev/linker/impl/hosted.html
similarity index 100%
rename from dev/core/src/com/google/gwt/dev/linker/hosted.html
rename to dev/core/src/com/google/gwt/dev/linker/impl/hosted.html
diff --git a/dev/core/src/com/google/gwt/dev/shell/GWTShellServlet.java b/dev/core/src/com/google/gwt/dev/shell/GWTShellServlet.java
index d647c3c..3593d94 100644
--- a/dev/core/src/com/google/gwt/dev/shell/GWTShellServlet.java
+++ b/dev/core/src/com/google/gwt/dev/shell/GWTShellServlet.java
@@ -21,7 +21,9 @@
 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.linker.HostedModeLinker;
+import com.google.gwt.dev.linker.ArtifactSet;
+import com.google.gwt.dev.linker.EmittedArtifact;
+import com.google.gwt.dev.linker.impl.HostedModeLinker;
 import com.google.gwt.dev.linker.impl.StandardLinkerContext;
 import com.google.gwt.dev.util.HttpHeaders;
 import com.google.gwt.dev.util.Util;
@@ -390,10 +392,10 @@
       ModuleDef moduleDef = getModuleDef(logger, moduleName);
       foundResource = moduleDef.findPublicFile(partialPath);
 
-      File moduleDir = new File(getOutputDir(), moduleName);
       if (foundResource == null) {
         // Look for generated files
-        File shellDir = new File(moduleDir, GWTShell.GWT_SHELL_PATH);
+        File shellDir = new File(getOutputDir(), GWTShell.GWT_SHELL_PATH
+            + File.separator + moduleName);
         File requestedFile = new File(shellDir, partialPath);
         if (requestedFile.exists()) {
           try {
@@ -410,9 +412,8 @@
        * output directory of the first linker defined in the <set-linker> tab.
        */
       if (foundResource == null) {
-        File linkerDir = new File(moduleDir,
-            moduleDef.getActiveLinkerNames()[0]);
-        File requestedFile = new File(linkerDir, partialPath);
+        File moduleDir = new File(getOutputDir(), moduleName);
+        File requestedFile = new File(moduleDir, partialPath);
         if (requestedFile.exists()) {
           try {
             foundResource = requestedFile.toURI().toURL();
@@ -521,13 +522,21 @@
     logger.log(TreeLogger.TRACE, msg, null);
 
     ModuleDef moduleDef = getModuleDef(logger, moduleName);
-    File moduleDir = new File(getOutputDir(), moduleDef.getName());
+    File linkerDir = new File(getOutputDir(), GWTShell.GWT_SHELL_PATH
+        + File.separator + moduleName);
 
     StandardLinkerContext context = new StandardLinkerContext(logger,
-        moduleDef, moduleDir, null, new JJSOptions());
+        moduleDef, null, null, new JJSOptions());
     HostedModeLinker linker = new HostedModeLinker();
-    context.invokeLinker(logger, GWTShell.GWT_SHELL_PATH, linker);
-    return linker.generateSelectionScript(logger, context);
+
+    ArtifactSet artifacts = linker.link(logger, context, new ArtifactSet());
+    for (EmittedArtifact artifact : artifacts.find(EmittedArtifact.class)) {
+      File out = new File(linkerDir, artifact.getPartialPath());
+      Util.copy(logger, artifact.getContents(logger), out);
+    }
+
+    return linker.generateSelectionScript(logger, context,
+        context.getArtifacts());
   }
 
   /**
@@ -921,10 +930,8 @@
         // RemoteServiceServlets to load public and generated resources via
         // ServeletContext.getResourceAsStream()
         //
-        File moduleDir = new File(getOutputDir(), moduleDef.getName());
-
         ServletContext context = new HostedModeServletContextProxy(
-            getServletContext(), moduleDef, moduleDir);
+            getServletContext(), moduleDef, getOutputDir());
         ServletConfig config = new HostedModeServletConfigProxy(
             getServletConfig(), context);
 
diff --git a/dev/core/src/com/google/gwt/dev/shell/HostedModeServletContextProxy.java b/dev/core/src/com/google/gwt/dev/shell/HostedModeServletContextProxy.java
index a016880..97f6058 100644
--- a/dev/core/src/com/google/gwt/dev/shell/HostedModeServletContextProxy.java
+++ b/dev/core/src/com/google/gwt/dev/shell/HostedModeServletContextProxy.java
@@ -38,13 +38,13 @@
 class HostedModeServletContextProxy implements ServletContext {
   private final ServletContext context;
   private final ModuleDef moduleDef;
-  private final File moduleDir;
+  private final File outDir;
 
   HostedModeServletContextProxy(ServletContext context, ModuleDef moduleDef,
-      File moduleDir) {
+      File outDir) {
     this.context = context;
     this.moduleDef = moduleDef;
-    this.moduleDir = moduleDir;
+    this.outDir = outDir;
   }
 
   /**
@@ -161,7 +161,8 @@
     URL url = moduleDef.findPublicFile(partialPath);
     if (url == null) {
       // Otherwise try the path but rooted in the shell's output directory
-      File shellDir = new File(moduleDir, GWTShell.GWT_SHELL_PATH);
+      File shellDir = new File(outDir, GWTShell.GWT_SHELL_PATH + File.separator
+          + moduleDef.getName());
       File requestedFile = new File(shellDir, partialPath);
       if (requestedFile.exists()) {
         url = requestedFile.toURI().toURL();
@@ -174,8 +175,8 @@
      * the first linker defined in the <set-linker> tab.
      */
     if (url == null) {
-      File linkerDir = new File(moduleDir, moduleDef.getActiveLinkerNames()[0]);
-      File requestedFile = new File(linkerDir, partialPath);
+      File requestedFile = new File(new File(outDir, moduleDef.getName()),
+          partialPath);
       if (requestedFile.exists()) {
         try {
           url = requestedFile.toURI().toURL();
diff --git a/dev/core/src/com/google/gwt/dev/util/Util.java b/dev/core/src/com/google/gwt/dev/util/Util.java
index 77ffafe..42d2baa 100644
--- a/dev/core/src/com/google/gwt/dev/util/Util.java
+++ b/dev/core/src/com/google/gwt/dev/util/Util.java
@@ -31,6 +31,7 @@
 import java.io.BufferedReader;
 import java.io.BufferedWriter;
 import java.io.File;
+import java.io.FileFilter;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
@@ -94,6 +95,7 @@
     return t;
   }
 
+  @SuppressWarnings("unchecked")
   public static <T> T[] append(T[] xs, T x) {
     int n = xs.length;
     T[] t = (T[]) Array.newInstance(xs.getClass().getComponentType(), n + 1);
@@ -102,6 +104,7 @@
     return t;
   }
 
+  @SuppressWarnings("unchecked")
   public static <T> T[] append(T[] appendToThis, T[] these) {
     if (appendToThis == null) {
       throw new NullPointerException("attempt to append to a null array");
@@ -671,20 +674,39 @@
    *          directory
    */
   public static void recursiveDelete(File file, boolean childrenOnly) {
+    recursiveDelete(file, childrenOnly, null);
+  }
+
+  /**
+   * Selectively deletes a file or recursively deletes a directory.
+   * 
+   * @param file the file to delete, or if this is a directory, the directory
+   *          that serves as the root of a recursive deletion
+   * @param childrenOnly if <code>true</code>, only the children of a
+   *          directory are recursively deleted but the specified directory
+   *          itself is spared; if <code>false</code>, the specified
+   *          directory is also deleted; ignored if <code>file</code> is not a
+   *          directory
+   * @param filter only files matching this filter will be deleted
+   */
+  public static void recursiveDelete(File file, boolean childrenOnly,
+      FileFilter filter) {
     if (file.isDirectory()) {
       File[] children = file.listFiles();
       if (children != null) {
         for (int i = 0; i < children.length; i++) {
-          recursiveDelete(children[i], false);
+          recursiveDelete(children[i], false, filter);
         }
       }
       if (childrenOnly) {
         // Do not delete the specified directory itself.
-        //
         return;
       }
     }
-    file.delete();
+
+    if (filter == null || filter.accept(file)) {
+      file.delete();
+    }
   }
 
   /**
@@ -728,6 +750,7 @@
     return new File(file.getParentFile(), name);
   }
 
+  @SuppressWarnings("unchecked")
   public static <T> T[] removeNulls(T[] a) {
     int n = a.length;
     for (int i = 0; i < a.length; i++) {
@@ -767,6 +790,7 @@
    * Class<? super T> is used to allow creation of generic types, such as
    * Map.Entry<K,V> since we can only pass in Map.Entry.class.
    */
+  @SuppressWarnings("unchecked")
   public static <T> T[] toArray(Class<? super T> componentType,
       Collection<? extends T> coll) {
     int n = coll.size();
@@ -778,6 +802,7 @@
    * Like {@link #toArray(Class, Collection)}, but the option of having the
    * array reversed.
    */
+  @SuppressWarnings("unchecked")
   public static <T> T[] toArrayReversed(Class<? super T> componentType,
       Collection<? extends T> coll) {
     int n = coll.size();
@@ -1002,7 +1027,7 @@
    * 
    * @param byteLength number of bytes to read
    * @return byte array containing the bytes read or <code>null</code> if
-   *         there is an {@link IOException} or if the requested number of bytes 
+   *         there is an {@link IOException} or if the requested number of bytes
    *         cannot be read from the {@link InputStream}
    */
   private static byte[] readBytesFromInputStream(InputStream input,
diff --git a/user/src/com/google/gwt/core/Core.gwt.xml b/user/src/com/google/gwt/core/Core.gwt.xml
index 91aee75..fa52b58 100644
--- a/user/src/com/google/gwt/core/Core.gwt.xml
+++ b/user/src/com/google/gwt/core/Core.gwt.xml
@@ -18,13 +18,11 @@
 <!-- Every module should directly or indirectly inherit this module.        -->
 <!--                                                                        -->
 <module>
-   <inherits name="com.google.gwt.dev.jjs.intrinsic.Intrinsic"/>
-   <inherits name="com.google.gwt.emul.Emulation"/>
-   <define-linker name="std" class="com.google.gwt.dev.linker.IFrameLinker" />
-   <define-linker name="sso" class="com.google.gwt.dev.linker.SingleScriptLinker" />
-   <define-linker name="xs" class="com.google.gwt.dev.linker.XSLinker" />
-   <set-linker name="std" />
-   
-   <!-- Filters generated resources in the no-deploy/ directory -->
-   <extend-linker-context class="com.google.gwt.dev.linker.NoDeployResourcesShim" />
+  <inherits name="com.google.gwt.dev.jjs.intrinsic.Intrinsic" />
+  <inherits name="com.google.gwt.emul.Emulation" />
+  <define-linker name="std" class="com.google.gwt.core.linker.IFrameLinker" />
+  <define-linker name="xs" class="com.google.gwt.core.linker.XSLinker" />
+  <define-linker name="sso" class="com.google.gwt.core.linker.SingleScriptLinker" />
+  
+  <add-linker name="std" />
 </module>
diff --git a/user/src/com/google/gwt/user/User.gwt.xml b/user/src/com/google/gwt/user/User.gwt.xml
index d79a3da..47fa350 100644
--- a/user/src/com/google/gwt/user/User.gwt.xml
+++ b/user/src/com/google/gwt/user/User.gwt.xml
@@ -39,4 +39,7 @@
    <inherits name="com.google.gwt.user.TitledPanel" />
    <inherits name="com.google.gwt.user.Window" />
    <inherits name="com.google.gwt.user.Accessibility"/>
+
+   <define-linker name="noDeploy" class="com.google.gwt.core.linker.NoDeployResourcesLinker" />
+   <add-linker name="noDeploy" />
 </module>
diff --git a/user/test/com/google/gwt/dev/cfg/PublicTagTest.java b/user/test/com/google/gwt/dev/cfg/PublicTagTest.java
index 66c49ba..1af2ad6 100644
--- a/user/test/com/google/gwt/dev/cfg/PublicTagTest.java
+++ b/user/test/com/google/gwt/dev/cfg/PublicTagTest.java
@@ -68,7 +68,7 @@
     String moduleName = PublicTagTest.class.getName();
 
     // Find our module output directory and delete it
-    File moduleDir = new File(curDir, "www/" + moduleName + "/std");
+    File moduleDir = new File(curDir, "www/" + moduleName);
     if (moduleDir.exists()) {
       Util.recursiveDelete(moduleDir, false);
     }
diff --git a/user/test/com/google/gwt/module/client/NoDeployTest.java b/user/test/com/google/gwt/module/client/NoDeployTest.java
index eb4038b..31a8b96 100644
--- a/user/test/com/google/gwt/module/client/NoDeployTest.java
+++ b/user/test/com/google/gwt/module/client/NoDeployTest.java
@@ -66,7 +66,7 @@
 
   public void testNoDeploy() throws RequestException {
     if (!GWT.isScript()) {
-      // LinkerContextShims aren't used in hosted-mode
+      // Linkers aren't used in hosted-mode
       return;
     }
 
diff --git a/user/test/com/google/gwt/module/rebind/NoDeployGenerator.java b/user/test/com/google/gwt/module/rebind/NoDeployGenerator.java
index 46b1d94..0c1bceb 100644
--- a/user/test/com/google/gwt/module/rebind/NoDeployGenerator.java
+++ b/user/test/com/google/gwt/module/rebind/NoDeployGenerator.java
@@ -19,7 +19,7 @@
 import com.google.gwt.core.ext.GeneratorContext;
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
-import com.google.gwt.dev.linker.NoDeployResourcesShim;
+import com.google.gwt.core.linker.NoDeployResourcesLinker;
 import com.google.gwt.dev.util.Util;
 import com.google.gwt.module.client.NoDeployTest;
 
@@ -37,7 +37,8 @@
 
     try {
       createFile(logger, context, "deploy/exists.txt");
-      createFile(logger, context, NoDeployResourcesShim.PREFIX + "inGenerated.txt");
+      createFile(logger, context, NoDeployResourcesLinker.PREFIX
+          + "inGenerated.txt");
     } catch (IOException e) {
       logger.log(TreeLogger.ERROR, "Unable to create test file", e);
       throw new UnableToCompleteException();