1 /*
<lambda>null2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tools.metalava.stub
18 
19 import com.android.tools.metalava.model.BaseItemVisitor
20 import com.android.tools.metalava.model.ClassItem
21 import com.android.tools.metalava.model.ExceptionTypeItem
22 import com.android.tools.metalava.model.Item
23 import com.android.tools.metalava.model.MethodItem
24 import com.android.tools.metalava.model.ModifierListWriter
25 import com.android.tools.metalava.model.TypeItem
26 import com.android.tools.metalava.model.TypeParameterList
27 import com.android.tools.metalava.model.psi.PsiClassItem
28 import java.io.PrintWriter
29 import java.util.function.Predicate
30 
31 internal class KotlinStubWriter(
32     private val writer: PrintWriter,
33     private val modifierListWriter: ModifierListWriter,
34     private val filterReference: Predicate<Item>,
35     private val preFiltered: Boolean = true,
36     private val config: StubWriterConfig,
37 ) : BaseItemVisitor() {
38 
39     override fun visitClass(cls: ClassItem) {
40         if (cls.isTopLevelClass()) {
41             val qualifiedName = cls.containingPackage().qualifiedName()
42             if (qualifiedName.isNotBlank()) {
43                 writer.println("package $qualifiedName")
44                 writer.println()
45             }
46             cls.getSourceFile()?.getImports(filterReference)?.let {
47                 for (item in it) {
48                     writer.println("import ${item.pattern}")
49                 }
50                 writer.println()
51             }
52         }
53         appendDocumentation(cls, writer, config)
54 
55         writer.println("@file:Suppress(\"ALL\")")
56 
57         appendModifiers(cls)
58 
59         when {
60             cls.isAnnotationType() -> writer.print("annotation class")
61             cls.isInterface() -> writer.print("interface")
62             cls.isEnum() -> writer.print("enum class")
63             else -> writer.print("class")
64         }
65 
66         writer.print(" ")
67         writer.print(cls.simpleName())
68 
69         generateTypeParameterList(typeList = cls.typeParameterList, addSpace = false)
70         val printedSuperClass = generateSuperClassDeclaration(cls)
71         generateInterfaceList(cls, printedSuperClass)
72         writer.print(" {\n")
73     }
74 
75     private fun generateTypeParameterList(typeList: TypeParameterList, addSpace: Boolean) {
76         val typeListString = typeList.toString()
77         if (typeListString.isNotEmpty()) {
78             writer.print(typeListString)
79 
80             if (addSpace) {
81                 writer.print(' ')
82             }
83         }
84     }
85 
86     private fun appendModifiers(item: Item) = modifierListWriter.write(item)
87 
88     private fun generateSuperClassDeclaration(cls: ClassItem): Boolean {
89         if (cls.isEnum() || cls.isAnnotationType()) {
90             // No extends statement for enums and annotations; it's implied by the "enum" and
91             // "@interface" keywords
92             return false
93         }
94 
95         val superClass =
96             if (preFiltered) cls.superClassType() else cls.filteredSuperClassType(filterReference)
97 
98         if (superClass != null && !superClass.isJavaLangObject()) {
99             val qualifiedName =
100                 superClass.toTypeString() // TODO start passing language = Language.KOTLIN
101             writer.print(" : ")
102 
103             if (qualifiedName.contains("<")) {
104                 // TODO: push this into the model at filter-time such that clients don't need
105                 // to remember to do this!!
106                 val s = superClass.asClass()
107                 if (s != null) {
108                     val replaced = superClass.convertType(cls, s)
109                     writer.print(replaced.toTypeString())
110                     return true
111                 }
112             }
113             (cls as PsiClassItem).psiClass.superClassType
114             writer.print(qualifiedName)
115             // TODO: print out arguments to the parent constructor
116             writer.print("()")
117             return true
118         }
119         return false
120     }
121 
122     private fun generateInterfaceList(cls: ClassItem, printedSuperClass: Boolean) {
123         if (cls.isAnnotationType()) {
124             // No extends statement for annotations; it's implied by the "@interface" keyword
125             return
126         }
127 
128         val interfaces =
129             if (preFiltered) cls.interfaceTypes().asSequence()
130             else cls.filteredInterfaceTypes(filterReference).asSequence()
131 
132         if (interfaces.any()) {
133             if (printedSuperClass) {
134                 writer.print(",")
135             } else {
136                 writer.print(" :")
137             }
138             interfaces.forEachIndexed { index, type ->
139                 if (index > 0) {
140                     writer.print(",")
141                 }
142                 writer.print(" ")
143                 writer.print(type.toTypeString()) // TODO start passing language = Language.KOTLIN
144             }
145         }
146     }
147 
148     private fun writeType(type: TypeItem?) {
149         type ?: return
150 
151         val typeString =
152             type.toTypeString(
153                 annotations = false,
154                 kotlinStyleNulls = true,
155                 filter = filterReference
156                 // TODO pass in language = Language.KOTLIN
157             )
158 
159         writer.print(typeString)
160     }
161 
162     override fun visitMethod(method: MethodItem) {
163         if (method.isKotlinProperty()) return // will be handled by visitProperty
164 
165         writer.println()
166         appendDocumentation(method, writer, config)
167 
168         // TODO: Should be an annotation
169         generateThrowsList(method)
170 
171         appendModifiers(method)
172         generateTypeParameterList(typeList = method.typeParameterList, addSpace = true)
173 
174         writer.print("fun ")
175         writer.print(method.name())
176         generateParameterList(method)
177 
178         writer.print(": ")
179         val returnType = method.returnType()
180         writeType(returnType)
181 
182         if (method.containingClass().isAnnotationType()) {
183             val default = method.defaultValue()
184             if (default.isNotEmpty()) {
185                 writer.print(" default ")
186                 writer.print(default)
187             }
188         }
189 
190         if (ModifierListWriter.requiresMethodBodyInStubs(method)) {
191             writer.print(" = ")
192             writeThrowStub()
193         }
194         writer.println()
195     }
196 
197     override fun afterVisitClass(cls: ClassItem) {
198         writer.println("}\n\n")
199     }
200 
201     private fun writeThrowStub() {
202         writer.write("error(\"Stub!\")")
203     }
204 
205     private fun generateParameterList(method: MethodItem) {
206         writer.print("(")
207         method.parameters().asSequence().forEachIndexed { i, parameter ->
208             if (i > 0) {
209                 writer.print(", ")
210             }
211             appendModifiers(parameter)
212             val name = parameter.publicName() ?: parameter.name()
213             writer.print(name)
214             writer.print(": ")
215             writeType(parameter.type())
216         }
217         writer.print(")")
218     }
219 
220     private fun generateThrowsList(method: MethodItem) {
221         // Note that throws types are already sorted internally to help comparison matching
222         val throws =
223             if (preFiltered) {
224                 method.throwsTypes().asSequence()
225             } else {
226                 method.filteredThrowsTypes(filterReference).asSequence()
227             }
228         if (throws.any()) {
229             writer.print("@Throws(")
230             throws.asSequence().sortedWith(ExceptionTypeItem.fullNameComparator).forEachIndexed {
231                 i,
232                 type ->
233                 if (i > 0) {
234                     writer.print(",")
235                 }
236                 writer.print(type.toTypeString())
237                 writer.print("::class")
238             }
239             writer.print(")")
240         }
241     }
242 }
243