Add an isPrivate flag to EmittedArtifact.
Private resources are emitted into a module's auxiliary directory.
Track the Generator that created a GeneratedResource.
Update i18n Generator to use private artifacts.
Removes no-deploy/ partial path treatments from generated resources.

Patch by: bobv, jat (i18n)
Review by: jat, bruce


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2276 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/GeneratorContext.java b/dev/core/src/com/google/gwt/core/ext/GeneratorContext.java
index 33de516..336fa55 100644
--- a/dev/core/src/com/google/gwt/core/ext/GeneratorContext.java
+++ b/dev/core/src/com/google/gwt/core/ext/GeneratorContext.java
@@ -15,6 +15,8 @@
  */
 package com.google.gwt.core.ext;
 
+import com.google.gwt.core.ext.linker.Artifact;
+import com.google.gwt.core.ext.linker.GeneratedResource;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 
 import java.io.OutputStream;
@@ -32,14 +34,33 @@
   void commit(TreeLogger logger, PrintWriter pw);
 
   /**
+   * Add an Artifact to the {@link com.google.gwt.core.ext.linker.ArtifactSet}
+   * that will be presented to the {@link Linker} chain at the end of the
+   * compilation cycle. Custom sub-classes of Artifact can be used to write
+   * cooperating Generator and Linker combinations. This method is semantically
+   * equivalent to calling
+   * {@link com.google.gwt.core.ext.linker.ArtifactSet#replace(Artifact)} if an
+   * equivalent Artifact had previously been committed.
+   * 
+   * @param logger a logger; normally the logger passed into
+   *          {@link Generator#generate(TreeLogger, GeneratorContext, String)}
+   *          or a branch thereof
+   * @param artifact the Artifact to provide to the Linker chain.
+   */
+  void commitArtifact(TreeLogger logger, Artifact<?> artifact)
+      throws UnableToCompleteException;
+
+  /**
    * Commits resource generation begun with
    * {@link #tryCreateResource(TreeLogger, String)}.
    * 
+   * @return the GeneratedResource that was created as a result of committing
+   *         the OutputStream.
    * @throws UnableToCompleteException if the resource cannot be written to
    *           disk, if the specified stream is unknown, or if the stream has
    *           already been committed
    */
-  void commitResource(TreeLogger logger, OutputStream os)
+  GeneratedResource commitResource(TreeLogger logger, OutputStream os)
       throws UnableToCompleteException;
 
   /**
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/AbstractLinker.java b/dev/core/src/com/google/gwt/core/ext/linker/AbstractLinker.java
index 0d086c3..f2b3cd3 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/AbstractLinker.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/AbstractLinker.java
@@ -20,7 +20,6 @@
 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.InputStream;
 
@@ -29,26 +28,6 @@
  */
 public abstract class AbstractLinker extends Linker {
   /**
-   * Internal type to wrap a byte array.
-   */
-  private static class SyntheticArtifact extends EmittedArtifact {
-    private final byte[] data;
-
-    public SyntheticArtifact(Class<? extends Linker> linkerType,
-        String partialPath, byte[] data) {
-      super(linkerType, partialPath);
-      assert data != null;
-      this.data = data;
-    }
-
-    @Override
-    public InputStream getContents(TreeLogger logger)
-        throws UnableToCompleteException {
-      return new ByteArrayInputStream(data);
-    }
-  }
-
-  /**
    * A helper method to create an artifact from an array of bytes.
    * 
    * @param logger a TreeLogger
@@ -57,8 +36,7 @@
    * @return an artifact that contains the given data
    * @throws UnableToCompleteException
    */
-  @SuppressWarnings("unused")
-  protected final EmittedArtifact emitBytes(TreeLogger logger, byte[] what,
+  protected final SyntheticArtifact emitBytes(TreeLogger logger, byte[] what,
       String partialPath) throws UnableToCompleteException {
     return new SyntheticArtifact(getClass(), partialPath, what);
   }
@@ -72,7 +50,7 @@
    * @param partialPath the partial path of the emitted resource
    * @return an artifact that contains the contents of the InputStream
    */
-  protected final EmittedArtifact emitInputStream(TreeLogger logger,
+  protected final SyntheticArtifact emitInputStream(TreeLogger logger,
       InputStream what, String partialPath) throws UnableToCompleteException {
     ByteArrayOutputStream out = new ByteArrayOutputStream();
     Util.copy(logger, what, out);
@@ -87,8 +65,8 @@
    * @param partialPath the partial path of the emitted resource
    * @return an artifact that contains the contents of the given String
    */
-  protected final EmittedArtifact emitString(TreeLogger logger,
-      String what, String partialPath) throws UnableToCompleteException {
+  protected final SyntheticArtifact emitString(TreeLogger logger, String what,
+      String partialPath) throws UnableToCompleteException {
     return emitBytes(logger, Util.getBytes(what), partialPath);
   }
 
@@ -104,7 +82,7 @@
    *          Artifact's partial path
    * @return an artifact that contains the given data
    */
-  protected final EmittedArtifact emitWithStrongName(TreeLogger logger,
+  protected final SyntheticArtifact emitWithStrongName(TreeLogger logger,
       byte[] what, String prefix, String suffix)
       throws UnableToCompleteException {
     String strongName = prefix + Util.computeStrongName(what) + suffix;
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/EmittedArtifact.java b/dev/core/src/com/google/gwt/core/ext/linker/EmittedArtifact.java
index 9c4aba1..85c4ed2 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/EmittedArtifact.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/EmittedArtifact.java
@@ -32,15 +32,27 @@
 
   private final String partialPath;
 
+  /**
+   * This is mutable because it has no effect on identity.
+   */
+  private boolean isPrivate;
+
   protected EmittedArtifact(Class<? extends Linker> linker, String partialPath) {
     super(linker);
     assert partialPath != null;
     this.partialPath = partialPath;
   }
 
+  /**
+   * Provides access to the contents of the EmittedResource.
+   */
   public abstract InputStream getContents(TreeLogger logger)
       throws UnableToCompleteException;
 
+  /**
+   * Returns the partial path within the output directory of the
+   * EmittedArtifact.
+   */
   public final String getPartialPath() {
     return partialPath;
   }
@@ -50,6 +62,31 @@
     return getPartialPath().hashCode();
   }
 
+  /**
+   * Returns whether or not the data contained in the EmittedArtifact should be
+   * written into the module output directory or into an auxiliary directory.
+   * <p>
+   * EmittedArtifacts that return <code>true</code> for this method will not
+   * be emitted into the normal module output location, but will instead be
+   * written into a directory that is a sibling to the module output directory.
+   * The partial path of the EmittedArtifact will be prepended with the
+   * short-name of the Linker type that created the EmittedArtifact.
+   * <p>
+   * Private EmittedArtifacts are intended for resources that generally should
+   * not be deployed to the server in the same location as the module
+   * compilation artifacts.
+   */
+  public boolean isPrivate() {
+    return isPrivate;
+  }
+
+  /**
+   * Sets the private attribute of the EmittedResource.
+   */
+  public void setPrivate(boolean isPrivate) {
+    this.isPrivate = isPrivate;
+  }
+
   @Override
   public String toString() {
     return getPartialPath();
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/GeneratedResource.java b/dev/core/src/com/google/gwt/core/ext/linker/GeneratedResource.java
index 99b1ac6..28c9400 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/GeneratedResource.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/GeneratedResource.java
@@ -15,14 +15,27 @@
  */
 package com.google.gwt.core.ext.linker;
 
+import com.google.gwt.core.ext.Generator;
 import com.google.gwt.core.ext.Linker;
 
 /**
- * A resource generated during the compilation process by a Generator.
+ * A resource created by a {@link Generator} invoking
+ * {@link com.google.gwt.core.ext.GeneratorContext#tryCreateResource(com.google.gwt.core.ext.TreeLogger, String)}
+ * during the compilation process.
  */
 public abstract class GeneratedResource extends EmittedArtifact {
+  private final Class<? extends Generator> generatorType;
+
   protected GeneratedResource(Class<? extends Linker> linkerType,
-      String partialPath) {
+      Class<? extends Generator> generatorType, String partialPath) {
     super(linkerType, partialPath);
+    this.generatorType = generatorType;
+  }
+
+  /**
+   * The type of Generator that created the resource.
+   */
+  public final Class<? extends Generator> getGenerator() {
+    return generatorType;
   }
 }
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/SyntheticArtifact.java b/dev/core/src/com/google/gwt/core/ext/linker/SyntheticArtifact.java
new file mode 100644
index 0000000..5e93e34
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/linker/SyntheticArtifact.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.core.ext.linker;
+
+import com.google.gwt.core.ext.Linker;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+/**
+ * Artifacts created by {@link AbstractLinker}.
+ */
+public class SyntheticArtifact extends EmittedArtifact {
+  private final byte[] data;
+
+  SyntheticArtifact(Class<? extends Linker> linkerType, String partialPath,
+      byte[] data) {
+    super(linkerType, partialPath);
+    assert data != null;
+    this.data = data;
+  }
+
+  @Override
+  public InputStream getContents(TreeLogger logger)
+      throws UnableToCompleteException {
+    return new ByteArrayInputStream(data);
+  }
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardGeneratedResource.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardGeneratedResource.java
index 3bd14f3..d520f33 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardGeneratedResource.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardGeneratedResource.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.core.ext.linker.impl;
 
+import com.google.gwt.core.ext.Generator;
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.linker.GeneratedResource;
@@ -29,8 +30,9 @@
 public class StandardGeneratedResource extends GeneratedResource {
   private final URL url;
 
-  public StandardGeneratedResource(String partialPath, URL url) {
-    super(StandardLinkerContext.class, partialPath);
+  public StandardGeneratedResource(Class<? extends Generator> generatorType,
+      String partialPath, URL url) {
+    super(StandardLinkerContext.class, generatorType, partialPath);
     this.url = url;
   }
 
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
index 2cf9cd1..a480c17 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
@@ -21,7 +21,6 @@
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.linker.ArtifactSet;
 import com.google.gwt.core.ext.linker.EmittedArtifact;
-import com.google.gwt.core.ext.linker.GeneratedResource;
 import com.google.gwt.core.ext.linker.LinkerOrder;
 import com.google.gwt.core.ext.linker.PublicResource;
 import com.google.gwt.core.ext.linker.SelectionProperty;
@@ -55,7 +54,6 @@
 import java.io.IOException;
 import java.io.Reader;
 import java.io.StringReader;
-import java.net.MalformedURLException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
@@ -102,6 +100,12 @@
   private final File compilationsDir;
   private final JJSOptions jjsOptions;
   private final List<Class<? extends Linker>> linkerClasses;
+  private final Map<Class<? extends Linker>, String> linkerShortNames = new HashMap<Class<? extends Linker>, String>();
+
+  /**
+   * This is the output directory for private files.
+   */
+  private final File moduleAuxDir;
   private final String moduleFunctionName;
   private final String moduleName;
 
@@ -136,10 +140,29 @@
       compilationsDir.mkdirs();
       logger.log(TreeLogger.SPAM, "compilationsDir: "
           + compilationsDir.getPath(), null);
+
+      this.moduleAuxDir = new File(moduleOutDir.getParentFile(), moduleName
+          + "-aux");
+      if (moduleAuxDir.exists()) {
+        Util.recursiveDelete(moduleAuxDir, false);
+      }
+      logger.log(TreeLogger.SPAM, "mouduleAuxDir: " + moduleAuxDir.getPath(),
+          null);
     } else {
       compilationsDir = null;
+      moduleAuxDir = null;
     }
 
+    for (Map.Entry<String, Class<? extends Linker>> entry : module.getLinkers().entrySet()) {
+      linkerShortNames.put(entry.getValue(), entry.getKey());
+    }
+
+    /*
+     * This will make all private PublicResources and GeneratedResources appear
+     * in the root of the module auxiliary directory.
+     */
+    linkerShortNames.put(this.getClass(), "");
+
     // Always return the properties in the same order as a convenience
     SortedSet<SelectionProperty> mutableProperties = new TreeSet<SelectionProperty>(
         SELECTION_PROPERTY_COMPARATOR);
@@ -164,22 +187,7 @@
       logger.log(TreeLogger.SPAM, "Added style " + style, null);
     }
 
-    if (generatorDir != null) {
-      for (String path : Util.recursiveListPartialPaths(generatorDir, false)) {
-        String partialPath = path.replace(File.separatorChar, '/');
-        try {
-          GeneratedResource resource = new StandardGeneratedResource(
-              partialPath, (new File(generatorDir, path)).toURL());
-          artifacts.add(resource);
-          logger.log(TreeLogger.SPAM, "Added generated resource " + resource,
-              null);
-        } catch (MalformedURLException e) {
-          // This won't happen unless there's a bad partial path from Util
-          logger.log(TreeLogger.ERROR,
-              "Unable to convert generated resource to URL", e);
-        }
-      }
-    }
+    // Generated files should be passed in via addArtifacts()
 
     for (String path : module.getAllPublicFiles()) {
       String partialPath = path.replace(File.separatorChar, '/');
@@ -190,10 +198,25 @@
     }
   }
 
+  /**
+   * Adds or replaces Artifacts in the ArtifactSet that will be passed into the
+   * Linkers invoked.
+   */
+  public void addOrReplaceArtifacts(ArtifactSet artifacts) {
+    this.artifacts.removeAll(artifacts);
+    this.artifacts.addAll(artifacts);
+  }
+
+  /**
+   * Returns the ArtifactSet that will passed into the invoke Linkers.
+   */
   public ArtifactSet getArtifacts() {
     return artifacts;
   }
 
+  /**
+   * Gets or creates a CompilationResult for the given JavaScript program.
+   */
   public StandardCompilationResult getCompilation(TreeLogger logger, String js)
       throws UnableToCompleteException {
 
@@ -245,7 +268,14 @@
       TreeLogger artifactLogger = logger.branch(TreeLogger.DEBUG,
           "Emitting resource " + artifact.getPartialPath(), null);
 
-      File outFile = new File(moduleOutDir, artifact.getPartialPath());
+      File outFile;
+      if (artifact.isPrivate()) {
+        outFile = new File(getLinkerAuxDir(artifact.getLinker()),
+            artifact.getPartialPath());
+      } else {
+        outFile = new File(moduleOutDir, artifact.getPartialPath());
+      }
+
       assert !outFile.exists() : "Attempted to overwrite " + outFile.getPath();
       Util.copy(logger, artifact.getContents(artifactLogger), outFile);
     }
@@ -310,6 +340,24 @@
   }
 
   /**
+   * Creates a linker-specific subdirectory in the module's auxiliary output
+   * directory.
+   */
+  private File getLinkerAuxDir(Class<? extends Linker> linkerType) {
+    // The auxiliary directory is create lazily
+    if (!moduleAuxDir.exists()) {
+      moduleAuxDir.mkdirs();
+    }
+    assert linkerShortNames.containsKey(linkerType) : linkerType.getName()
+        + " unknown";
+    File toReturn = new File(moduleAuxDir, linkerShortNames.get(linkerType));
+    if (!toReturn.exists()) {
+      toReturn.mkdirs();
+    }
+    return toReturn;
+  }
+
+  /**
    * Run the linker stack.
    */
   private ArtifactSet invokeLinkerStack(TreeLogger logger)
diff --git a/dev/core/src/com/google/gwt/core/linker/NoDeployResourcesLinker.java b/dev/core/src/com/google/gwt/core/linker/NoDeployResourcesLinker.java
deleted file mode 100644
index 89836fe..0000000
--- a/dev/core/src/com/google/gwt/core/linker/NoDeployResourcesLinker.java
+++ /dev/null
@@ -1,54 +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.core.linker;
-
-import com.google.gwt.core.ext.Linker;
-import com.google.gwt.core.ext.LinkerContext;
-import com.google.gwt.core.ext.TreeLogger;
-import com.google.gwt.core.ext.UnableToCompleteException;
-import com.google.gwt.core.ext.linker.ArtifactSet;
-import com.google.gwt.core.ext.linker.GeneratedResource;
-import com.google.gwt.core.ext.linker.LinkerOrder;
-import com.google.gwt.core.ext.linker.LinkerOrder.Order;
-
-/**
- * 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);
-
-    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/GWTCompiler.java b/dev/core/src/com/google/gwt/dev/GWTCompiler.java
index e431b83..a83801e 100644
--- a/dev/core/src/com/google/gwt/dev/GWTCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/GWTCompiler.java
@@ -18,6 +18,7 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.TreeLogger.Type;
+import com.google.gwt.core.ext.linker.ArtifactSet;
 import com.google.gwt.core.ext.linker.SelectionProperty;
 import com.google.gwt.core.ext.linker.impl.StandardCompilationResult;
 import com.google.gwt.core.ext.linker.impl.StandardLinkerContext;
@@ -120,9 +121,9 @@
 
     private final Map<String, String> cache = new HashMap<String, String>();
 
-    public CompilationRebindOracle() {
+    public CompilationRebindOracle(ArtifactSet generatorArtifacts) {
       super(typeOracle, propOracle, module, rules, genDir,
-          generatorResourcesDir, cacheManager);
+          generatorResourcesDir, cacheManager, generatorArtifacts);
     }
 
     /**
@@ -175,21 +176,25 @@
   private class DistillerRebindPermutationOracle implements
       RebindPermutationOracle {
 
-    private final StandardRebindOracle rebindOracle = new StandardRebindOracle(
-        typeOracle, propOracle, module, rules, genDir, generatorResourcesDir,
-        cacheManager) {
+    private final StandardRebindOracle rebindOracle;
 
-      /**
-       * Record generated types.
-       */
-      @Override
-      protected void onGeneratedTypes(String result, JClassType[] genTypes) {
-        List<JClassType> list = new ArrayList<JClassType>();
-        Util.addAll(list, genTypes);
-        Object existing = generatedTypesByResultTypeName.put(result, list);
-        assert (existing == null) : "Internal error: redundant notification of generated types";
-      }
-    };
+    public DistillerRebindPermutationOracle(ArtifactSet generatorArtifacts) {
+      rebindOracle = new StandardRebindOracle(typeOracle, propOracle, module,
+          rules, genDir, generatorResourcesDir, cacheManager,
+          generatorArtifacts) {
+
+        /**
+         * Record generated types.
+         */
+        @Override
+        protected void onGeneratedTypes(String result, JClassType[] genTypes) {
+          List<JClassType> list = new ArrayList<JClassType>();
+          Util.addAll(list, genTypes);
+          Object existing = generatedTypesByResultTypeName.put(result, list);
+          assert (existing == null) : "Internal error: redundant notification of generated types";
+        }
+      };
+    }
 
     public String[] getAllPossibleRebindAnswers(TreeLogger logger,
         String requestTypeName) throws UnableToCompleteException {
@@ -382,7 +387,9 @@
       // Use the real entry points.
       declEntryPts = module.getEntryPointTypeNames();
     }
-    rebindPermOracle = new DistillerRebindPermutationOracle();
+
+    ArtifactSet generatorArtifacts = new ArtifactSet();
+    rebindPermOracle = new DistillerRebindPermutationOracle(generatorArtifacts);
     properties = module.getProperties();
     perms = new PropertyPermutations(properties);
     WebModeCompilerFrontEnd frontEnd = new WebModeCompilerFrontEnd(
@@ -392,7 +399,7 @@
 
     StandardLinkerContext linkerContext = new StandardLinkerContext(logger,
         module, outDir, generatorResourcesDir, jjsOptions);
-    compilePermutations(logger, linkerContext);
+    compilePermutations(logger, linkerContext, generatorArtifacts);
 
     if (jjsOptions.isValidateOnly()) {
       logger.log(TreeLogger.INFO, "Validation succeeded", null);
@@ -400,7 +407,7 @@
     }
 
     logger.log(TreeLogger.INFO, "Compilation succeeded", null);
-
+    linkerContext.addOrReplaceArtifacts(generatorArtifacts);
     linkerContext.link(logger, linkerContext, null);
   }
 
@@ -468,7 +475,8 @@
   }
 
   private void compilePermutations(TreeLogger logger,
-      StandardLinkerContext linkerContext) throws UnableToCompleteException {
+      StandardLinkerContext linkerContext, ArtifactSet generatedArtifacts)
+      throws UnableToCompleteException {
     logger = logger.branch(TreeLogger.DEBUG, "Compiling permutations", null);
     Property[] orderedProps = perms.getOrderedProperties();
     int permNumber = 1;
@@ -476,7 +484,7 @@
 
       String[] orderedPropValues = iter.next();
       String js = realizePermutation(logger, orderedProps, orderedPropValues,
-          permNumber);
+          permNumber, generatedArtifacts);
 
       // This is the case in validateOnly mode, which doesn't produce output
       if (js == null) {
@@ -523,7 +531,8 @@
    * </ul>
    */
   private String realizePermutation(TreeLogger logger, Property[] currentProps,
-      String[] currentValues, int permNumber) throws UnableToCompleteException {
+      String[] currentValues, int permNumber, ArtifactSet generatorArtifacts)
+      throws UnableToCompleteException {
     String msg = "Analyzing permutation #" + permNumber;
     logger = logger.branch(TreeLogger.TRACE, msg, null);
 
@@ -532,7 +541,8 @@
     // Create a rebind oracle that will record decisions so that we can cache
     // them and avoid future computations.
     //
-    CompilationRebindOracle rebindOracle = new CompilationRebindOracle();
+    CompilationRebindOracle rebindOracle = new CompilationRebindOracle(
+        generatorArtifacts);
 
     // Tell the property provider above about the current property values.
     // Note that the rebindOracle is actually sensitive to these values because
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 ce9b8a1..e716af6 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java
@@ -42,6 +42,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
@@ -102,7 +103,7 @@
 
   private TypeOracle lazyTypeOracle;
 
-  private final Map<String, Class<? extends Linker>> linkersTypesByName = new HashMap<String, Class<? extends Linker>>();
+  private final Map<String, Class<? extends Linker>> linkerTypesByName = new LinkedHashMap<String, Class<? extends Linker>>();
 
   private final long moduleDefCreationTime = System.currentTimeMillis();
 
@@ -219,7 +220,7 @@
   }
 
   public void defineLinker(String name, Class<? extends Linker> linker) {
-    linkersTypesByName.put(name, linker);
+    linkerTypesByName.put(name, linker);
   }
 
   public synchronized URL findPublicFile(String partialPath) {
@@ -285,7 +286,11 @@
   }
 
   public Class<? extends Linker> getLinker(String name) {
-    return linkersTypesByName.get(name);
+    return linkerTypesByName.get(name);
+  }
+
+  public Map<String, Class<? extends Linker>> getLinkers() {
+    return linkerTypesByName;
   }
 
   public synchronized String getName() {
diff --git a/dev/core/src/com/google/gwt/dev/cfg/RuleGenerateWith.java b/dev/core/src/com/google/gwt/dev/cfg/RuleGenerateWith.java
index 83481f0..a221bcb 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/RuleGenerateWith.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/RuleGenerateWith.java
@@ -19,6 +19,7 @@
 import com.google.gwt.core.ext.GeneratorContext;
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.shell.StandardGeneratorContext;
 
 /**
  * A rule to replace the type being rebound with a class whose name is
@@ -39,6 +40,10 @@
     String msg = "Invoking " + toString();
     logger = logger.branch(TreeLogger.DEBUG, msg, null);
 
+    if (context instanceof StandardGeneratorContext) {
+      ((StandardGeneratorContext) context).setCurrentGenerator(generator.getClass());
+    }
+
     long before = System.currentTimeMillis();
     String className = generator.generate(logger, context, typeName);
     long after = System.currentTimeMillis();
diff --git a/dev/core/src/com/google/gwt/dev/shell/ShellModuleSpaceHost.java b/dev/core/src/com/google/gwt/dev/shell/ShellModuleSpaceHost.java
index 518223e..109d96a 100644
--- a/dev/core/src/com/google/gwt/dev/shell/ShellModuleSpaceHost.java
+++ b/dev/core/src/com/google/gwt/dev/shell/ShellModuleSpaceHost.java
@@ -113,7 +113,7 @@
     //
     Rules rules = module.getRules();
     rebindOracle = new StandardRebindOracle(typeOracle, propOracle, module,
-        rules, genDir, shellDir, module.getCacheManager());
+        rules, genDir, shellDir, module.getCacheManager(), null);
 
     // Create a completely isolated class loader which owns all classes
     // associated with a particular module. This effectively builds a
diff --git a/dev/core/src/com/google/gwt/dev/shell/StandardGeneratorContext.java b/dev/core/src/com/google/gwt/dev/shell/StandardGeneratorContext.java
index f31780a..f8fb4cd 100644
--- a/dev/core/src/com/google/gwt/dev/shell/StandardGeneratorContext.java
+++ b/dev/core/src/com/google/gwt/dev/shell/StandardGeneratorContext.java
@@ -15,10 +15,15 @@
  */
 package com.google.gwt.dev.shell;
 
+import com.google.gwt.core.ext.Generator;
 import com.google.gwt.core.ext.GeneratorContext;
 import com.google.gwt.core.ext.PropertyOracle;
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.linker.Artifact;
+import com.google.gwt.core.ext.linker.ArtifactSet;
+import com.google.gwt.core.ext.linker.GeneratedResource;
+import com.google.gwt.core.ext.linker.impl.StandardGeneratedResource;
 import com.google.gwt.core.ext.typeinfo.CompilationUnitProvider;
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.NotFoundException;
@@ -154,10 +159,14 @@
     }
   }
 
+  private final ArtifactSet artifactSet;
+
   private final CacheManager cacheManager;
 
   private final Set<GeneratedCompilationUnitProvider> committedGeneratedCups = new HashSet<GeneratedCompilationUnitProvider>();
 
+  private Class<? extends Generator> currentGenerator;
+
   private final File genDir;
 
   private final Set<String> generatedTypeNames = new HashSet<String>();
@@ -180,13 +189,14 @@
    */
   public StandardGeneratorContext(TypeOracle typeOracle,
       PropertyOracle propOracle, PublicOracle publicOracle, File genDir,
-      File outDir, CacheManager cacheManager) {
+      File outDir, CacheManager cacheManager, ArtifactSet artifactSet) {
     this.typeOracle = typeOracle;
     this.propOracle = propOracle;
     this.publicOracle = publicOracle;
     this.genDir = genDir;
     this.outDir = outDir;
     this.cacheManager = cacheManager;
+    this.artifactSet = artifactSet;
   }
 
   /**
@@ -204,7 +214,19 @@
     }
   }
 
-  public void commitResource(TreeLogger logger, OutputStream os)
+  /**
+   * Adds an Artifact to the ArtifactSet if one has been provided to the
+   * context.
+   */
+  public void commitArtifact(TreeLogger logger, Artifact<?> artifact)
+      throws UnableToCompleteException {
+    // The artifactSet will be null in hosted mode, since we never run Linkers
+    if (artifactSet != null) {
+      artifactSet.replace(artifact);
+    }
+  }
+
+  public GeneratedResource commitResource(TreeLogger logger, OutputStream os)
       throws UnableToCompleteException {
 
     // Find the pending resource using its output stream as a key.
@@ -214,11 +236,27 @@
       pendingResource.commit(logger);
       cacheManager.addGeneratedResource(pendingResource.getPartialPath());
 
-      // The resource is now no longer pending, so remove it from the map.
-      // If the commit above throws an exception, it's okay to leave the entry
-      // in the map because it will be reported later as not having been
-      // committed, which is accurate.
-      pendingResourcesByOutputStream.remove(os);
+      // Add the GeneratedResource to the ArtifactSet
+      GeneratedResource toReturn;
+      try {
+        toReturn = new StandardGeneratedResource(currentGenerator,
+            pendingResource.getPartialPath(), pendingResource.getFile().toURL());
+        commitArtifact(logger, toReturn);
+
+        /*
+         * The resource is now no longer pending, so remove it from the map. If
+         * the commit above throws an exception, it's okay to leave the entry in
+         * the map because it will be reported later as not having been
+         * committed, which is accurate.
+         */
+        pendingResourcesByOutputStream.remove(os);
+
+        return toReturn;
+      } catch (MalformedURLException e) {
+        // This is very unlikely, since the file already exists
+        logger.log(TreeLogger.ERROR, "Unable to commit artifact", e);
+        throw new UnableToCompleteException();
+      }
     } else {
       logger.log(TreeLogger.WARN,
           "Generator attempted to commit an unknown OutputStream", null);
@@ -320,6 +358,10 @@
     return typeOracle;
   }
 
+  public void setCurrentGenerator(Class<? extends Generator> currentGenerator) {
+    this.currentGenerator = currentGenerator;
+  }
+
   public final PrintWriter tryCreate(TreeLogger logger, String packageName,
       String simpleTypeName) {
     String typeName = packageName + "." + simpleTypeName;
diff --git a/dev/core/src/com/google/gwt/dev/shell/StandardRebindOracle.java b/dev/core/src/com/google/gwt/dev/shell/StandardRebindOracle.java
index fbb237b..554a351 100644
--- a/dev/core/src/com/google/gwt/dev/shell/StandardRebindOracle.java
+++ b/dev/core/src/com/google/gwt/dev/shell/StandardRebindOracle.java
@@ -18,12 +18,12 @@
 import com.google.gwt.core.ext.PropertyOracle;
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.linker.ArtifactSet;
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.dev.cfg.PublicOracle;
 import com.google.gwt.dev.cfg.Rule;
 import com.google.gwt.dev.cfg.Rules;
-import com.google.gwt.dev.cfg.StaticPropertyOracle;
 import com.google.gwt.dev.jdt.CacheManager;
 import com.google.gwt.dev.jdt.RebindOracle;
 import com.google.gwt.dev.util.Util;
@@ -54,7 +54,7 @@
     public Rebinder(TypeOracle typeOracle, PropertyOracle propOracle,
         PublicOracle publicOracle) {
       genCtx = new StandardGeneratorContext(typeOracle, propOracle,
-          publicOracle, genDir, outDir, cacheManager);
+          publicOracle, genDir, outDir, cacheManager, artifactSet);
     }
 
     public String rebind(TreeLogger logger, String typeName)
@@ -133,6 +133,8 @@
     }
   }
 
+  private final ArtifactSet artifactSet;
+
   private final CacheManager cacheManager;
 
   private final File genDir;
@@ -149,7 +151,7 @@
 
   public StandardRebindOracle(TypeOracle typeOracle, PropertyOracle propOracle,
       PublicOracle publicOracle, Rules rules, File genDir, File moduleOutDir,
-      CacheManager cacheManager) {
+      CacheManager cacheManager, ArtifactSet artifactSet) {
     this.typeOracle = typeOracle;
     this.propOracle = propOracle;
     this.publicOracle = publicOracle;
@@ -161,14 +163,7 @@
     } else {
       this.cacheManager = new CacheManager(typeOracle);
     }
-  }
-
-  public StandardRebindOracle(TypeOracle typeOracle,
-      StaticPropertyOracle propOracle, PublicOracle publicOracle, Rules rules,
-      File genDir, File moduleOutDir) {
-    // This is a path used for non-hosted mode execution; therefore no caching.
-    this(typeOracle, propOracle, publicOracle, rules, genDir, moduleOutDir,
-        null);
+    this.artifactSet = artifactSet;
   }
 
   public String rebind(TreeLogger logger, String typeName)
diff --git a/dev/core/test/com/google/gwt/dev/shell/StandardGeneratorContextTest.java b/dev/core/test/com/google/gwt/dev/shell/StandardGeneratorContextTest.java
index bbfa58b..f88a339 100644
--- a/dev/core/test/com/google/gwt/dev/shell/StandardGeneratorContextTest.java
+++ b/dev/core/test/com/google/gwt/dev/shell/StandardGeneratorContextTest.java
@@ -16,9 +16,15 @@
 package com.google.gwt.dev.shell;
 
 import com.google.gwt.core.ext.BadPropertyValueException;
+import com.google.gwt.core.ext.Generator;
+import com.google.gwt.core.ext.GeneratorContext;
+import com.google.gwt.core.ext.Linker;
 import com.google.gwt.core.ext.PropertyOracle;
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.linker.Artifact;
+import com.google.gwt.core.ext.linker.ArtifactSet;
+import com.google.gwt.core.ext.linker.GeneratedResource;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.dev.cfg.PublicOracle;
 import com.google.gwt.dev.jdt.CacheManager;
@@ -42,6 +48,28 @@
  */
 public class StandardGeneratorContextTest extends TestCase {
 
+  private static class MockArtifact extends Artifact<MockArtifact> {
+
+    public MockArtifact() {
+      super(Linker.class);
+    }
+
+    @Override
+    protected int compareToComparableArtifact(MockArtifact o) {
+      return 0;
+    }
+
+    @Override
+    protected Class<MockArtifact> getComparableArtifactType() {
+      return MockArtifact.class;
+    }
+
+    @Override
+    public int hashCode() {
+      return 0;
+    }
+  }
+
   private static class MockCacheManager extends CacheManager {
   }
 
@@ -57,6 +85,14 @@
     }
   }
 
+  private static class MockGenerator extends Generator {
+    @Override
+    public String generate(TreeLogger logger, GeneratorContext context,
+        String typeName) throws UnableToCompleteException {
+      return typeName;
+    }
+  }
+
   private static class MockPublicOracle implements PublicOracle {
 
     public URL findPublicFile(String partialPath) {
@@ -93,6 +129,8 @@
   private static class MockTypeOracle extends TypeOracle {
   }
 
+  private final ArtifactSet artifactSet = new ArtifactSet();
+
   /**
    * Stores the File objects to delete in the order they were created. Delete
    * them in reverse order.
@@ -112,7 +150,7 @@
     tempGenDir = createTempDir("gwt-gen-");
     tempOutDir = createTempDir("gwt-out-");
     genCtx = new StandardGeneratorContext(mockTypeOracle, mockPropOracle,
-        mockPublicOracle, tempGenDir, tempOutDir, mockCacheManager);
+        mockPublicOracle, tempGenDir, tempOutDir, mockCacheManager, artifactSet);
   }
 
   public void testTryCreateResource_badFileName() {
@@ -165,9 +203,16 @@
     String path = createTempOutFilename();
     OutputStream os = genCtx.tryCreateResource(mockLogger, path);
     os.write("going to call commit twice after this...".getBytes(Util.DEFAULT_ENCODING));
-    genCtx.commitResource(mockLogger, os);
+    genCtx.setCurrentGenerator(MockGenerator.class);
+    GeneratedResource res = genCtx.commitResource(mockLogger, os);
+    assertEquals(path, res.getPartialPath());
+    assertEquals(MockGenerator.class, res.getGenerator());
     File createdFile = new File(tempOutDir, path);
     assertTrue(createdFile.exists());
+    assertEquals(1, artifactSet.size());
+    GeneratedResource generatedResource = (GeneratedResource) artifactSet.iterator().next();
+    assertEquals(path, generatedResource.getPartialPath());
+    assertEquals(MockGenerator.class, generatedResource.getGenerator());
     rememberToDelete(createdFile);
     try {
       genCtx.commitResource(mockLogger, os);
@@ -175,6 +220,8 @@
     } catch (UnableToCompleteException e) {
       // Success
     }
+    // Didn't change the artifactSet
+    assertEquals(1, artifactSet.size());
   }
 
   public void testTryCreateResource_commitNotCalled()
@@ -189,6 +236,7 @@
 
     File wouldBeCreatedFile = new File(tempOutDir, path);
     assertFalse(wouldBeCreatedFile.exists());
+    assertEquals(0, artifactSet.size());
   }
 
   public void testTryCreateResource_commitWithBadStream() {
@@ -198,6 +246,7 @@
     } catch (UnableToCompleteException e) {
       // Success
     }
+    assertEquals(0, artifactSet.size());
 
     try {
       OutputStream os = new ByteArrayOutputStream();
@@ -206,6 +255,7 @@
     } catch (UnableToCompleteException e) {
       // Success
     }
+    assertEquals(0, artifactSet.size());
   }
 
   /**
@@ -287,11 +337,13 @@
     assertNull(
         "tryCreateResource() should return null when the target file is already on the public path",
         os);
+    assertEquals(0, artifactSet.size());
   }
 
   @Override
   protected void setUp() throws Exception {
     mockCacheManager.invalidateVolatileFiles();
+    artifactSet.clear();
   }
 
   @Override
diff --git a/user/src/com/google/gwt/i18n/client/LocalizableResource.java b/user/src/com/google/gwt/i18n/client/LocalizableResource.java
index 3f111b1..b1791a4 100644
--- a/user/src/com/google/gwt/i18n/client/LocalizableResource.java
+++ b/user/src/com/google/gwt/i18n/client/LocalizableResource.java
@@ -105,12 +105,14 @@
      * A platform-specific filename for output. If not present, the file will be
      * named based on the fully-qualified name of the annotated interface. File
      * names without a slash are given a relative name based on the
-     * fully-qualified package name of the annotated interface. Relative pathnames
-     * are generated in the directory specified by the "-out" flag to the
-     * compiler, or the current directory if not present.  Unless exactly one locale
-     * is specified for locales (not just only one locale happened to be compiled for),
-     * the locale will be appended to the name (such as _default [for the default
-     * locale], _en_US, etc) as well as the proper extension for the specified format.
+     * fully-qualified package name of the annotated interface. Relative
+     * pathnames are generated in the auxiliary module directory (moduleName-aux
+     * in the output directory, which is specified by the "-out" flag to the
+     * compiler, or the current directory if not present) -- absolute path names
+     * are not allowed. Unless exactly one locale is specified for locales (not
+     * just only one locale happened to be compiled for), the locale will be
+     * appended to the name (such as _default [for the default locale], _en_US,
+     * etc) as well as the proper extension for the specified format.
      * 
      * Note that if multiple generators are used, they will have the same base
      * filename so the extensions must be different.
diff --git a/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableImplCreator.java b/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableImplCreator.java
index 1af5e11..3b1f1b8 100644
--- a/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableImplCreator.java
+++ b/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableImplCreator.java
@@ -134,7 +134,7 @@
     Generate generate = targetClass.getAnnotation(Generate.class);
     if (generate != null) {
       try {
-        String path = "no-deploy" + File.separatorChar + generate.fileName();
+        String path = generate.fileName();
         if (Generate.DEFAULT.equals(path)) {
           path = targetClass.getPackage().getName() + "."
           + targetClass.getName().replace('.', '_');
@@ -175,7 +175,7 @@
                   new OutputStreamWriter(outStr, "UTF-8")), false);
               msgWriter.write(logger, resource, out, targetClass);
               out.flush();
-              context.commitResource(logger, outStr);
+              context.commitResource(logger, outStr).setPrivate(true);
             }
           }
         }
diff --git a/user/src/com/google/gwt/user/User.gwt.xml b/user/src/com/google/gwt/user/User.gwt.xml
index 47fa350..d79a3da 100644
--- a/user/src/com/google/gwt/user/User.gwt.xml
+++ b/user/src/com/google/gwt/user/User.gwt.xml
@@ -39,7 +39,4 @@
    <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/module/client/NoDeployTest.java b/user/test/com/google/gwt/module/client/NoDeployTest.java
index 31a8b96..e67dd78 100644
--- a/user/test/com/google/gwt/module/client/NoDeployTest.java
+++ b/user/test/com/google/gwt/module/client/NoDeployTest.java
@@ -49,7 +49,7 @@
 
     // Try fetching a file that should exist
     RequestBuilder builder = new RequestBuilder(RequestBuilder.GET,
-        GWT.getHostPageBaseURL() + "deploy/exists.txt");
+        GWT.getHostPageBaseURL() + "publicFile.txt");
     delayTestFinish(500);
     builder.sendRequest("", new RequestCallback() {
 
@@ -74,7 +74,7 @@
 
     // Try fetching a file that shouldn't exist
     RequestBuilder builder = new RequestBuilder(RequestBuilder.GET,
-        GWT.getHostPageBaseURL() + "no-deploy/inGenerated.txt");
+        GWT.getHostPageBaseURL() + "privateFile.txt");
     delayTestFinish(500);
     builder.sendRequest("", new RequestCallback() {
 
diff --git a/user/test/com/google/gwt/module/rebind/NoDeployGenerator.java b/user/test/com/google/gwt/module/rebind/NoDeployGenerator.java
index 0c1bceb..d5b86f1 100644
--- a/user/test/com/google/gwt/module/rebind/NoDeployGenerator.java
+++ b/user/test/com/google/gwt/module/rebind/NoDeployGenerator.java
@@ -19,7 +19,6 @@
 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.core.linker.NoDeployResourcesLinker;
 import com.google.gwt.dev.util.Util;
 import com.google.gwt.module.client.NoDeployTest;
 
@@ -36,9 +35,8 @@
       String typeName) throws UnableToCompleteException {
 
     try {
-      createFile(logger, context, "deploy/exists.txt");
-      createFile(logger, context, NoDeployResourcesLinker.PREFIX
-          + "inGenerated.txt");
+      createFile(logger, context, "publicFile.txt", false);
+      createFile(logger, context, "privateFile.txt", true);
     } catch (IOException e) {
       logger.log(TreeLogger.ERROR, "Unable to create test file", e);
       throw new UnableToCompleteException();
@@ -48,7 +46,8 @@
   }
 
   private void createFile(TreeLogger logger, GeneratorContext context,
-      String path) throws UnableToCompleteException, IOException {
+      String path, boolean isPrivate) throws UnableToCompleteException,
+      IOException {
 
     OutputStream out = context.tryCreateResource(logger, path);
     if (out == null) {
@@ -56,6 +55,6 @@
     }
 
     out.write(Util.getBytes(NoDeployTest.TEST_TEXT));
-    context.commitResource(logger, out);
+    context.commitResource(logger, out).setPrivate(isPrivate);
   }
 }