Introduce XFiler

The initial abstraction for XProcessing ignored the Filer
API since it is an interface. This CL changes it to have
an abstraction in XProcessing instead to avoid re-implementing
full Filer API in KSP.

Also added a test for code generation. Unfortunately, KSP
does not yet support access to generated code from java sources.
https://github.com/google/ksp/issues/119

Fortunately, it won't affect Room as we don't require access
to generated code.

Bug: 160322705
Test: XProcessingEnvTest.generateCode
Change-Id: I953a17cd857b7360d12269ccda1fe3c6b0489906
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/XFiler.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/XFiler.kt
new file mode 100644
index 0000000..dce852c
--- /dev/null
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/XFiler.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * 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 androidx.room.compiler.processing
+
+import com.squareup.javapoet.JavaFile
+
+/**
+ * Code generation interface for XProcessing.
+ */
+interface XFiler {
+    fun write(javaFile: JavaFile)
+}
+
+fun JavaFile.writeTo(generator: XFiler) = generator.write(this)
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/XProcessingEnv.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/XProcessingEnv.kt
index 11858cd..908cc4b 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/XProcessingEnv.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/XProcessingEnv.kt
@@ -18,7 +18,6 @@
 
 import androidx.room.compiler.processing.javac.JavacProcessingEnv
 import com.squareup.javapoet.TypeName
-import javax.annotation.processing.Filer
 import javax.annotation.processing.ProcessingEnvironment
 import kotlin.reflect.KClass
 
@@ -40,7 +39,7 @@
     /**
      * The API to generate files
      */
-    val filer: Filer
+    val filer: XFiler
 
     /**
      * Looks for the [XTypeElement] with the given qualified name and returns `null` if it does not
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacFiler.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacFiler.kt
new file mode 100644
index 0000000..354236a
--- /dev/null
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacFiler.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * 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 androidx.room.compiler.processing.javac
+
+import androidx.room.compiler.processing.XFiler
+import com.squareup.javapoet.JavaFile
+import javax.annotation.processing.Filer
+
+internal class JavacFiler(val filer: Filer) : XFiler {
+    override fun write(javaFile: JavaFile) {
+        javaFile.writeTo(filer)
+    }
+}
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacProcessingEnv.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacProcessingEnv.kt
index 753ebbe..746a4be 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacProcessingEnv.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacProcessingEnv.kt
@@ -50,7 +50,7 @@
         JavacProcessingEnvMessager(delegate)
     }
 
-    override val filer = delegate.filer
+    override val filer = JavacFiler(delegate.filer)
 
     override val options: Map<String, String>
         get() = delegate.options
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspFiler.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspFiler.kt
new file mode 100644
index 0000000..e75da68
--- /dev/null
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspFiler.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * 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 androidx.room.compiler.processing.ksp
+
+import androidx.room.compiler.processing.XFiler
+import com.squareup.javapoet.JavaFile
+import org.jetbrains.kotlin.ksp.processing.CodeGenerator
+
+internal class KspFiler(
+    private val delegate: CodeGenerator
+) : XFiler {
+    override fun write(javaFile: JavaFile) {
+        delegate.createNewFile(
+            packageName = javaFile.packageName,
+            fileName = javaFile.typeSpec.name,
+            extensionName = "java"
+        ).use { outputStream ->
+            outputStream.bufferedWriter(Charsets.UTF_8).use {
+                javaFile.writeTo(it)
+            }
+        }
+    }
+}
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt
index 079c176..9137b71 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt
@@ -17,6 +17,7 @@
 package androidx.room.compiler.processing.ksp
 
 import androidx.room.compiler.processing.XArrayType
+import androidx.room.compiler.processing.XFiler
 import androidx.room.compiler.processing.XDeclaredType
 import androidx.room.compiler.processing.XMessager
 import androidx.room.compiler.processing.XProcessingEnv
@@ -30,14 +31,14 @@
 import org.jetbrains.kotlin.ksp.symbol.KSType
 import org.jetbrains.kotlin.ksp.symbol.KSTypeReference
 import org.jetbrains.kotlin.ksp.symbol.Variance
-import javax.annotation.processing.Filer
 
 internal class KspProcessingEnv(
     override val options: Map<String, String>,
-    private val codeGenerator: CodeGenerator,
+    codeGenerator: CodeGenerator,
     private val logger: KSPLogger,
     val resolver: Resolver
 ) : XProcessingEnv {
+
     private val typeElementStore =
         XTypeElementStore { qName ->
             resolver.getClassDeclarationByName(
@@ -53,8 +54,7 @@
     override val messager: XMessager
         get() = TODO("Not yet implemented")
 
-    override val filer: Filer
-        get() = TODO("Not yet implemented")
+    override val filer: XFiler = KspFiler(codeGenerator)
 
     val commonTypes = CommonTypes(resolver)
 
diff --git a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XProcessingEnvTest.kt b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XProcessingEnvTest.kt
index 1da78f89c..6c8efc0 100644
--- a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XProcessingEnvTest.kt
+++ b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XProcessingEnvTest.kt
@@ -17,14 +17,18 @@
 package androidx.room.compiler.processing
 
 import androidx.room.compiler.processing.util.Source
+import androidx.room.compiler.processing.util.TestInvocation
 import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.compiler.processing.util.runProcessorTestIncludingKsp
 import com.google.common.truth.Truth.assertThat
 import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.JavaFile
 import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.TypeSpec
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
+import javax.lang.model.element.Modifier
 
 @RunWith(JUnit4::class)
 class XProcessingEnvTest {
@@ -166,6 +170,50 @@
         }
     }
 
+    @Test
+    fun generateCode() {
+        val javaSrc = Source.java(
+            "foo.bar.AccessGenerated",
+            """
+            package foo.bar;
+            public class AccessGenerated {
+                ToBeGenerated x;
+            }
+            """.trimIndent()
+        )
+        val kotlinSrc = Source.kotlin(
+            "AccessGenerated.kt",
+            """
+            package foo.bar;
+            public class AccessGenerated(x: ToBeGenerated)
+            """.trimIndent()
+        )
+        listOf(javaSrc, kotlinSrc).forEach { src ->
+            fun runTest(block: (TestInvocation) -> Unit) {
+                // KSP does not support generated code access in java sources yet
+                // TODO remove this check once the bug is fixed.
+                //  https://github.com/google/ksp/issues/119
+                if (src === javaSrc) {
+                    runProcessorTest(sources = listOf(src), block)
+                } else {
+                    runProcessorTestIncludingKsp(sources = listOf(src), block)
+                }
+            }
+            runTest { invocation ->
+                val className = ClassName.get("foo.bar", "ToBeGenerated")
+                if (invocation.processingEnv.findTypeElement(className) == null) {
+                    // generate only if it doesn't exist to handle multi-round
+                    val spec = TypeSpec.classBuilder(className)
+                        .addModifiers(Modifier.PUBLIC)
+                        .build()
+                    JavaFile.builder(className.packageName(), spec)
+                        .build()
+                        .writeTo(invocation.processingEnv.filer)
+                }
+            }
+        }
+    }
+
     companion object {
         val PRIMITIVE_TYPES = mapOf(
             TypeName.BOOLEAN.toString() to TypeName.BOOLEAN,
diff --git a/room/compiler/src/main/kotlin/androidx/room/writer/ClassWriter.kt b/room/compiler/src/main/kotlin/androidx/room/writer/ClassWriter.kt
index 77cbf82..23b04c1 100644
--- a/room/compiler/src/main/kotlin/androidx/room/writer/ClassWriter.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/writer/ClassWriter.kt
@@ -20,6 +20,7 @@
 import androidx.room.ext.S
 import androidx.room.ext.typeName
 import androidx.room.compiler.processing.XProcessingEnv
+import androidx.room.compiler.processing.writeTo
 import androidx.room.solver.CodeGenScope.Companion.CLASS_PROPERTY_PREFIX
 import com.squareup.javapoet.AnnotationSpec
 import com.squareup.javapoet.ClassName
diff --git a/room/compiler/src/test/kotlin/androidx/room/solver/BasicColumnTypeAdaptersTest.kt b/room/compiler/src/test/kotlin/androidx/room/solver/BasicColumnTypeAdaptersTest.kt
index 425a078..4096f31 100644
--- a/room/compiler/src/test/kotlin/androidx/room/solver/BasicColumnTypeAdaptersTest.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/solver/BasicColumnTypeAdaptersTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.room.compiler.processing.XProcessingEnv
 import androidx.room.compiler.processing.XType
+import androidx.room.compiler.processing.writeTo
 import androidx.room.processor.Context
 import androidx.room.testing.TestInvocation
 import com.squareup.javapoet.ClassName
@@ -152,7 +153,9 @@
                     .build()
             )
             .build()
-        JavaFile.builder("foo.bar", spec).build().writeTo(invocation.processingEnv.filer)
+        JavaFile.builder("foo.bar", spec).build().writeTo(
+            invocation.processingEnv.filer
+        )
     }
 
     @Test