Merge changes Ibbbec9ca,I25cff8e2

* changes:
  Adding a sceleton KotlinStubWriter.
  Add an option --kotlin-stubs that will gate kotlin stub generation
diff --git a/src/main/java/com/android/tools/metalava/Options.kt b/src/main/java/com/android/tools/metalava/Options.kt
index 09f863e..88626fc 100644
--- a/src/main/java/com/android/tools/metalava/Options.kt
+++ b/src/main/java/com/android/tools/metalava/Options.kt
@@ -74,6 +74,7 @@
 const val ARG_EXACT_API = "--exact-api"
 const val ARG_STUBS = "--stubs"
 const val ARG_DOC_STUBS = "--doc-stubs"
+const val ARG_KOTLIN_STUBS = "--kotlin-stubs"
 const val ARG_STUBS_SOURCE_LIST = "--write-stubs-source-list"
 const val ARG_DOC_STUBS_SOURCE_LIST = "--write-doc-stubs-source-list"
 const val ARG_PROGUARD = "--proguard"
@@ -391,6 +392,9 @@
      * other tools like javac/javadoc using the special @-syntax. */
     var docStubsSourceList: File? = null
 
+    /** Whether code compiled from Kotlin should be emitted as .kt stubs instead of .java stubs */
+    var kotlinStubs = false
+
     /** Proguard Keep list file to write */
     var proguard: File? = null
 
@@ -786,6 +790,7 @@
 
                 ARG_STUBS, "-stubs" -> stubsDir = stringToNewDir(getValue(args, ++index))
                 ARG_DOC_STUBS -> docStubsDir = stringToNewDir(getValue(args, ++index))
+                ARG_KOTLIN_STUBS -> kotlinStubs = true
                 ARG_STUBS_SOURCE_LIST -> stubsSourceList = stringToNewFile(getValue(args, ++index))
                 ARG_DOC_STUBS_SOURCE_LIST -> docStubsSourceList = stringToNewFile(getValue(args, ++index))
 
@@ -2082,6 +2087,8 @@
                 "indicate that an element is recently marked as non null, whereas in the documentation stubs we'll " +
                 "just list this as @NonNull. Another difference is that @doconly elements are included in " +
                 "documentation stubs, but not regular stubs, etc.",
+            ARG_KOTLIN_STUBS, "[CURRENTLY EXPERIMENTAL] If specified, stubs generated from Kotlin source code will " +
+                "be written in Kotlin rather than the Java programming language.",
             ARG_INCLUDE_ANNOTATIONS, "Include annotations such as @Nullable in the stub files.",
             ARG_EXCLUDE_ANNOTATIONS, "Exclude annotations such as @Nullable from the stub files; the default.",
             "$ARG_PASS_THROUGH_ANNOTATION <annotation classes>", "A comma separated list of fully qualified names of" +
diff --git a/src/main/java/com/android/tools/metalava/stub/KotlinStubWriter.kt b/src/main/java/com/android/tools/metalava/stub/KotlinStubWriter.kt
new file mode 100644
index 0000000..c2dc245
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/stub/KotlinStubWriter.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 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 com.android.tools.metalava.stub
+
+import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.MemberItem
+import com.android.tools.metalava.model.PackageItem
+import com.android.tools.metalava.model.psi.EXPAND_DOCUMENTATION
+import com.android.tools.metalava.model.psi.trimDocIndent
+import com.android.tools.metalava.model.visitors.ItemVisitor
+import com.android.tools.metalava.options
+import java.io.PrintWriter
+import java.util.function.Predicate
+
+class KotlinStubWriter(
+    private val writer: PrintWriter,
+    private val filterEmit: Predicate<Item>,
+    private val filterReference: Predicate<Item>,
+    private val generateAnnotations: Boolean = false,
+    private val preFiltered: Boolean = true,
+    private val docStubs: Boolean
+) : ItemVisitor() {
+    override fun visitClass(cls: ClassItem) {
+        if (cls.isTopLevelClass()) {
+            val qualifiedName = cls.containingPackage().qualifiedName()
+            if (qualifiedName.isNotBlank()) {
+                writer.println("package $qualifiedName")
+                writer.println()
+            }
+            @Suppress("ConstantConditionIf")
+            if (EXPAND_DOCUMENTATION) {
+                val compilationUnit = cls.getCompilationUnit()
+                compilationUnit?.getImportStatements(filterReference)?.let {
+                    for (item in it) {
+                        when (item) {
+                            is PackageItem ->
+                                writer.println("import ${item.qualifiedName()}.*")
+                            is ClassItem ->
+                                writer.println("import ${item.qualifiedName()}")
+                            is MemberItem ->
+                                writer.println("import static ${item.containingClass().qualifiedName()}.${item.name()}")
+                        }
+                    }
+                    writer.println()
+                }
+            }
+        }
+        appendDocumentation(cls, writer)
+
+        writer.println("@file:Suppress(\"ALL\")")
+
+        when {
+            cls.isAnnotationType() -> writer.print("annotation class")
+            cls.isInterface() -> writer.print("interface")
+            cls.isEnum() -> writer.print("enum class")
+            else -> writer.print("class")
+        }
+
+        writer.print(" ")
+        writer.print(cls.simpleName())
+
+        writer.print(" {\n")
+    }
+
+    private fun appendDocumentation(item: Item, writer: PrintWriter) {
+        if (options.includeDocumentationInStubs || docStubs) {
+            val documentation = if (docStubs && EXPAND_DOCUMENTATION) {
+                item.fullyQualifiedDocumentation()
+            } else {
+                item.documentation
+            }
+            if (documentation.isNotBlank()) {
+                val trimmed = trimDocIndent(documentation)
+                writer.println(trimmed)
+                writer.println()
+            }
+        }
+    }
+
+    override fun afterVisitClass(cls: ClassItem) {
+        writer.println("}\n\n")
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/stub/StubWriter.kt b/src/main/java/com/android/tools/metalava/stub/StubWriter.kt
index ae7d7bf..c2a9bf1 100644
--- a/src/main/java/com/android/tools/metalava/stub/StubWriter.kt
+++ b/src/main/java/com/android/tools/metalava/stub/StubWriter.kt
@@ -194,7 +194,11 @@
 
             startFile(sourceFile)
 
-            stubWriter = JavaStubWriter(textWriter, filterEmit, filterReference, generateAnnotations, preFiltered, docStubs)
+            stubWriter = if (options.kotlinStubs && cls.isKotlin()) {
+                KotlinStubWriter(textWriter, filterEmit, filterReference, generateAnnotations, preFiltered, docStubs)
+            } else {
+                JavaStubWriter(textWriter, filterEmit, filterReference, generateAnnotations, preFiltered, docStubs)
+            }
 
             // Copyright statements from the original file?
             val compilationUnit = cls.getCompilationUnit()
diff --git a/src/test/java/com/android/tools/metalava/OptionsTest.kt b/src/test/java/com/android/tools/metalava/OptionsTest.kt
index 8b585eb..2942245 100644
--- a/src/test/java/com/android/tools/metalava/OptionsTest.kt
+++ b/src/test/java/com/android/tools/metalava/OptionsTest.kt
@@ -206,6 +206,9 @@
                                              recently marked as non null, whereas in the documentation stubs we'll just
                                              list this as @NonNull. Another difference is that @doconly elements are
                                              included in documentation stubs, but not regular stubs, etc.
+--kotlin-stubs                               
+                                             [CURRENTLY EXPERIMENTAL] If specified, stubs generated from Kotlin source
+                                             code will be written in Kotlin rather than the Java programming language.
 --include-annotations                        
                                              Include annotations such as @Nullable in the stub files.
 --exclude-annotations                        
diff --git a/src/test/java/com/android/tools/metalava/stub/StubsTest.kt b/src/test/java/com/android/tools/metalava/stub/StubsTest.kt
index 1d07013..f652d9d 100644
--- a/src/test/java/com/android/tools/metalava/stub/StubsTest.kt
+++ b/src/test/java/com/android/tools/metalava/stub/StubsTest.kt
@@ -23,6 +23,7 @@
 import com.android.tools.metalava.ARG_EXCLUDE_ANNOTATIONS
 import com.android.tools.metalava.ARG_EXCLUDE_DOCUMENTATION_FROM_STUBS
 import com.android.tools.metalava.ARG_HIDE_PACKAGE
+import com.android.tools.metalava.ARG_KOTLIN_STUBS
 import com.android.tools.metalava.ARG_PASS_THROUGH_ANNOTATION
 import com.android.tools.metalava.ARG_UPDATE_API
 import com.android.tools.metalava.DriverTest
@@ -4212,6 +4213,61 @@
         )
     }
 
+    @Test
+    fun `Basic Kotlin stubs`() {
+        check(
+            checkCompilation = true,
+            extraArguments = arrayOf(
+                ARG_KOTLIN_STUBS
+            ),
+            sourceFiles = arrayOf(
+                kotlin(
+                    """
+                    /* My file header */
+                    // Another comment
+                    @file:JvmName("Driver")
+                    package test.pkg
+                    /** My class doc */
+                    class Kotlin(val property1: String = "Default Value", arg2: Int) : Parent() {
+                        override fun method() = "Hello World"
+                        /** My method doc */
+                        fun otherMethod(ok: Boolean, times: Int) {
+                        }
+
+                        /** property doc */
+                        var property2: String? = null
+
+                        /** @hide */
+                        var hiddenProperty: String? = "hidden"
+
+                        private var someField = 42
+                        @JvmField
+                        var someField2 = 42
+                    }
+
+                    /** Parent class doc */
+                    open class Parent {
+                        open fun method(): String? = null
+                        open fun method2(value1: Boolean, value2: Boolean?): String? = null
+                        open fun method3(value1: Int?, value2: Int): Int = null
+                    }
+                    """
+                )
+            ),
+            stubs = arrayOf(
+                """
+                    /* My file header */
+                    // Another comment
+                    package test.pkg
+                    /** My class doc */
+                    @file:Suppress("ALL")
+                    class Kotlin {
+                    }
+                """
+            )
+        )
+    }
+
     // TODO: Test what happens when a class extends a hidden extends a public in separate packages,
     // and the hidden has a @hide constructor so the stub in the leaf class doesn't compile -- I should
     // check for this and fail build.