Fix handling of generated resource outputs

which should be included in the source or class output locations, respectively.

RELNOTES: N/A

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=272496172
diff --git a/java/com/google/turbine/binder/Binder.java b/java/com/google/turbine/binder/Binder.java
index 47efe98..2468a3f 100644
--- a/java/com/google/turbine/binder/Binder.java
+++ b/java/com/google/turbine/binder/Binder.java
@@ -66,6 +66,7 @@
 import java.time.Duration;
 import java.util.List;
 import java.util.Optional;
+import javax.annotation.processing.Processor;
 
 /** The entry point for analysis. */
 public class Binder {
@@ -530,6 +531,11 @@
           units, modules, classPathEnv, tli, generatedSources, generatedClasses, statistics);
     }
 
+    public BindingResult withGeneratedSources(ImmutableList<SourceFile> generatedSources) {
+      return new BindingResult(
+          units, modules, classPathEnv, tli, generatedSources, generatedClasses, statistics);
+    }
+
     public BindingResult withStatistics(Statistics statistics) {
       return new BindingResult(
           units, modules, classPathEnv, tli, generatedSources, generatedClasses, statistics);
diff --git a/java/com/google/turbine/binder/Processing.java b/java/com/google/turbine/binder/Processing.java
index 1391336..809276e 100644
--- a/java/com/google/turbine/binder/Processing.java
+++ b/java/com/google/turbine/binder/Processing.java
@@ -255,6 +255,9 @@
       // TODO(cushon): consider handling generated classes after each round
       result = result.withGeneratedClasses(filer.generatedClasses());
     }
+    if (!filer.generatedSources().isEmpty()) {
+      result = result.withGeneratedSources(filer.generatedSources());
+    }
 
     result =
         result.withStatistics(Statistics.create(timers.build(), ImmutableMap.copyOf(statistics)));
diff --git a/java/com/google/turbine/processing/TurbineFiler.java b/java/com/google/turbine/processing/TurbineFiler.java
index 86a5897..6fe2de9 100644
--- a/java/com/google/turbine/processing/TurbineFiler.java
+++ b/java/com/google/turbine/processing/TurbineFiler.java
@@ -79,7 +79,6 @@
 
   private final Map<String, SourceFile> generatedSources = new LinkedHashMap<>();
   private final Map<String, byte[]> generatedClasses = new LinkedHashMap<>();
-  private final Map<String, byte[]> generatedResources = new LinkedHashMap<>();
 
   /** Generated source file objects from all rounds. */
   public ImmutableList<SourceFile> generatedSources() {
@@ -91,11 +90,6 @@
     return ImmutableMap.copyOf(generatedClasses);
   }
 
-  /** Generated resource file objects from all rounds. */
-  public ImmutableMap<String, byte[]> generatedResources() {
-    return ImmutableMap.copyOf(generatedResources);
-  }
-
   public TurbineFiler(
       Set<String> seen, Function<String, Supplier<byte[]>> classPath, ClassLoader loader) {
     this.seen = seen;
@@ -119,7 +113,16 @@
           generatedClasses.put(path, e.bytes());
           break;
         case OTHER:
-          generatedResources.put(path, e.bytes());
+          switch (e.location()) {
+            case CLASS_OUTPUT:
+              generatedClasses.put(path, e.bytes());
+              break;
+            case SOURCE_OUTPUT:
+              this.generatedSources.put(path, new SourceFile(path, e.contents()));
+              break;
+            default:
+              throw new AssertionError(e.location());
+          }
           break;
         case HTML:
           throw new UnsupportedOperationException(String.valueOf(e.getKind()));
@@ -135,7 +138,7 @@
       throws IOException {
     String name = n.toString();
     checkArgument(!name.contains("/"), "invalid type name: %s", name);
-    return create(Kind.SOURCE, name.replace('.', '/') + ".java");
+    return create(StandardLocation.SOURCE_OUTPUT, Kind.SOURCE, name.replace('.', '/') + ".java");
   }
 
   @Override
@@ -143,25 +146,28 @@
       throws IOException {
     String name = n.toString();
     checkArgument(!name.contains("/"), "invalid type name: %s", name);
-    return create(Kind.CLASS, name.replace('.', '/') + ".class");
+    return create(StandardLocation.CLASS_OUTPUT, Kind.CLASS, name.replace('.', '/') + ".class");
   }
 
   @Override
   public FileObject createResource(
       Location location, CharSequence p, CharSequence r, Element... originatingElements)
       throws IOException {
+    checkArgument(location instanceof StandardLocation, "%s", location);
     String pkg = p.toString();
     String relativeName = r.toString();
     checkArgument(!pkg.contains("/"), "invalid package: %s", pkg);
     String path = packageRelativePath(pkg, relativeName);
-    return create(Kind.OTHER, path);
+    return create((StandardLocation) location, Kind.OTHER, path);
   }
 
-  private JavaFileObject create(Kind kind, String path) throws FilerException {
+  private JavaFileObject create(StandardLocation location, Kind kind, String path)
+      throws FilerException {
+    checkArgument(location.isOutputLocation());
     if (!seen.add(path)) {
       throw new FilerException("already created " + path);
     }
-    TurbineJavaFileObject result = new TurbineJavaFileObject(kind, path);
+    TurbineJavaFileObject result = new TurbineJavaFileObject(location, kind, path);
     files.add(result);
     return result;
   }
@@ -270,11 +276,13 @@
 
   private static class TurbineJavaFileObject extends WriteOnlyFileObject implements JavaFileObject {
 
+    private final StandardLocation location;
     private final Kind kind;
     private final CharSequence name;
     private final ByteArrayOutputStream baos = new ByteArrayOutputStream();
 
-    public TurbineJavaFileObject(Kind kind, CharSequence name) {
+    public TurbineJavaFileObject(StandardLocation location, Kind kind, CharSequence name) {
+      this.location = location;
       this.kind = kind;
       this.name = name;
     }
@@ -336,6 +344,10 @@
     public String contents() {
       return new String(baos.toByteArray(), UTF_8);
     }
+
+    public StandardLocation location() {
+      return location;
+    }
   }
 
   private static class ResourceFileObject extends ReadOnlyFileObject {
diff --git a/javatests/com/google/turbine/processing/ProcessingIntegrationTest.java b/javatests/com/google/turbine/processing/ProcessingIntegrationTest.java
index 95a4b42..57c3e0f 100644
--- a/javatests/com/google/turbine/processing/ProcessingIntegrationTest.java
+++ b/javatests/com/google/turbine/processing/ProcessingIntegrationTest.java
@@ -18,14 +18,18 @@
 
 import static com.google.common.collect.ImmutableList.toImmutableList;
 import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.fail;
 
 import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.MoreCollectors;
 import com.google.turbine.binder.Binder;
+import com.google.turbine.binder.Binder.BindingResult;
 import com.google.turbine.binder.ClassPathBinder;
 import com.google.turbine.binder.Processing;
+import com.google.turbine.binder.Processing.ProcessorInfo;
 import com.google.turbine.diag.SourceFile;
 import com.google.turbine.diag.TurbineError;
 import com.google.turbine.lower.IntegrationTestSupport;
@@ -44,6 +48,7 @@
 import javax.lang.model.element.TypeElement;
 import javax.tools.Diagnostic;
 import javax.tools.JavaFileObject;
+import javax.tools.StandardLocation;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -167,4 +172,86 @@
       assertThat(diags.get(1)).contains("proc error");
     }
   }
+
+  @SupportedAnnotationTypes("*")
+  public static class ResourceProcessor extends AbstractProcessor {
+
+    @Override
+    public SourceVersion getSupportedSourceVersion() {
+      return SourceVersion.latestSupported();
+    }
+
+    private boolean first = true;
+
+    @Override
+    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+      if (first) {
+        try {
+          try (Writer writer = processingEnv.getFiler().createSourceFile("Gen").openWriter()) {
+            writer.write("class Gen {}");
+          }
+          try (Writer writer =
+              processingEnv
+                  .getFiler()
+                  .createResource(StandardLocation.SOURCE_OUTPUT, "", "source.txt")
+                  .openWriter()) {
+            writer.write("hello source output");
+          }
+          try (Writer writer =
+              processingEnv
+                  .getFiler()
+                  .createResource(StandardLocation.CLASS_OUTPUT, "", "class.txt")
+                  .openWriter()) {
+            writer.write("hello class output");
+          }
+        } catch (IOException e) {
+          throw new UncheckedIOException(e);
+        }
+        first = false;
+      }
+      return false;
+    }
+  }
+
+  @Test
+  public void resources() throws IOException {
+    ImmutableList<Tree.CompUnit> units =
+        IntegrationTestSupport.TestInput.parse(
+                Joiner.on('\n')
+                    .join(
+                        "=== Test.java ===", //
+                        "@Deprecated",
+                        "class Test {",
+                        "}"))
+            .sources
+            .entrySet()
+            .stream()
+            .map(e -> new SourceFile(e.getKey(), e.getValue()))
+            .map(Parser::parse)
+            .collect(toImmutableList());
+    BindingResult bound =
+        Binder.bind(
+            units,
+            ClassPathBinder.bindClasspath(ImmutableList.of()),
+            ProcessorInfo.create(
+                ImmutableList.of(new ResourceProcessor()),
+                getClass().getClassLoader(),
+                ImmutableMap.of(),
+                SourceVersion.latestSupported()),
+            TestClassPaths.TURBINE_BOOTCLASSPATH,
+            Optional.empty());
+
+    assertThat(bound.generatedSources().stream().map(s -> s.path()).collect(toImmutableList()))
+        .containsExactly("Gen.java", "source.txt");
+    assertThat(bound.generatedClasses().keySet()).containsExactly("class.txt");
+
+    assertThat(
+            bound.generatedSources().stream()
+                .filter(s -> s.path().equals("source.txt"))
+                .collect(MoreCollectors.onlyElement())
+                .source())
+        .isEqualTo("hello source output");
+    assertThat(new String(bound.generatedClasses().get("class.txt"), UTF_8))
+        .isEqualTo("hello class output");
+  }
 }