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.AnnotationTarget
20 import com.android.tools.metalava.model.ClassItem
21 import com.android.tools.metalava.model.Item
22 import com.android.tools.metalava.model.Language
23 import com.android.tools.metalava.model.MemberItem
24 import com.android.tools.metalava.model.MethodItem
25 import com.android.tools.metalava.model.ModifierList
26 import com.android.tools.metalava.model.PackageItem
27 import com.android.tools.metalava.model.TypeItem
28 import com.android.tools.metalava.model.TypeParameterList
29 import com.android.tools.metalava.model.psi.PsiClassItem
30 import com.android.tools.metalava.model.visitors.ItemVisitor
31 import java.io.PrintWriter
32 import java.util.function.Predicate
33 
34 class KotlinStubWriter(
35     private val writer: PrintWriter,
36     private val filterEmit: Predicate<Item>,
37     private val filterReference: Predicate<Item>,
38     private val generateAnnotations: Boolean = false,
39     private val preFiltered: Boolean = true,
40     private val docStubs: Boolean
41 ) : ItemVisitor() {
42     private val annotationTarget = if (docStubs) AnnotationTarget.DOC_STUBS_FILE else AnnotationTarget.SDK_STUBS_FILE
43 
44     override fun visitClass(cls: ClassItem) {
45         if (cls.isTopLevelClass()) {
46             val qualifiedName = cls.containingPackage().qualifiedName()
47             if (qualifiedName.isNotBlank()) {
48                 writer.println("package $qualifiedName")
49                 writer.println()
50             }
51             val compilationUnit = cls.getCompilationUnit()
52             compilationUnit?.getImportStatements(filterReference)?.let {
53                 for (item in it) {
54                     when (item) {
55                         is PackageItem ->
56                             writer.println("import ${item.qualifiedName()}.*")
57                         is ClassItem ->
58                             writer.println("import ${item.qualifiedName()}")
59                         is MemberItem ->
60                             writer.println("import static ${item.containingClass().qualifiedName()}.${item.name()}")
61                     }
62                 }
63                 writer.println()
64             }
65         }
66         appendDocumentation(cls, writer, docStubs)
67 
68         writer.println("@file:Suppress(\"ALL\")")
69 
70         // Need to filter out abstract from the modifiers list and turn it
71         // into a concrete method to make the stub compile
72         val removeAbstract = cls.modifiers.isAbstract() && (cls.isEnum() || cls.isAnnotationType())
73 
74         appendModifiers(cls, cls.modifiers, removeAbstract)
75 
76         when {
77             cls.isAnnotationType() -> writer.print("annotation class")
78             cls.isInterface() -> writer.print("interface")
79             cls.isEnum() -> writer.print("enum class")
80             else -> writer.print("class")
81         }
82 
83         writer.print(" ")
84         writer.print(cls.simpleName())
85 
86         generateTypeParameterList(typeList = cls.typeParameterList(), addSpace = false)
87         val printedSuperClass = generateSuperClassDeclaration(cls)
88         generateInterfaceList(cls, printedSuperClass)
89         writer.print(" {\n")
90     }
91 
92     private fun generateTypeParameterList(
93         typeList: TypeParameterList,
94         addSpace: Boolean
95     ) {
96         val typeListString = typeList.toString()
97         if (typeListString.isNotEmpty()) {
98             writer.print(typeListString)
99 
100             if (addSpace) {
101                 writer.print(' ')
102             }
103         }
104     }
105 
106     private fun appendModifiers(
107         item: Item,
108         modifiers: ModifierList,
109         removeAbstract: Boolean,
110         removeFinal: Boolean = false,
111         addPublic: Boolean = false
112     ) {
113         val separateLines = item is ClassItem || item is MethodItem
114 
115         ModifierList.write(
116             writer, modifiers, item,
117             target = annotationTarget,
118             includeAnnotations = true,
119             skipNullnessAnnotations = true,
120             includeDeprecated = true,
121             runtimeAnnotationsOnly = !generateAnnotations,
122             removeAbstract = removeAbstract,
123             removeFinal = removeFinal,
124             addPublic = addPublic,
125             separateLines = separateLines,
126             language = Language.KOTLIN
127         )
128     }
129 
130     private fun generateSuperClassDeclaration(cls: ClassItem): Boolean {
131         if (cls.isEnum() || cls.isAnnotationType()) {
132             // No extends statement for enums and annotations; it's implied by the "enum" and "@interface" keywords
133             return false
134         }
135 
136         val superClass = if (preFiltered)
137             cls.superClassType()
138         else cls.filteredSuperClassType(filterReference)
139 
140         if (superClass != null && !superClass.isJavaLangObject()) {
141             val qualifiedName = superClass.toTypeString() // TODO start passing language = Language.KOTLIN
142             writer.print(" : ")
143 
144             if (qualifiedName.contains("<")) {
145                 // TODO: push this into the model at filter-time such that clients don't need
146                 // to remember to do this!!
147                 val s = superClass.asClass()
148                 if (s != null) {
149                     val map = cls.mapTypeVariables(s)
150                     val replaced = superClass.convertTypeString(map)
151                     writer.print(replaced)
152                     return true
153                 }
154             }
155             (cls as PsiClassItem).psiClass.superClassType
156             writer.print(qualifiedName)
157             // TODO: print out arguments to the parent constructor
158             writer.print("()")
159             return true
160         }
161         return false
162     }
163 
164     private fun generateInterfaceList(cls: ClassItem, printedSuperClass: Boolean) {
165         if (cls.isAnnotationType()) {
166             // No extends statement for annotations; it's implied by the "@interface" keyword
167             return
168         }
169 
170         val interfaces = if (preFiltered)
171             cls.interfaceTypes().asSequence()
172         else cls.filteredInterfaceTypes(filterReference).asSequence()
173 
174         if (interfaces.any()) {
175             if (printedSuperClass) {
176                 writer.print(",")
177             } else {
178                 writer.print(" :")
179             }
180             interfaces.forEachIndexed { index, type ->
181                 if (index > 0) {
182                     writer.print(",")
183                 }
184                 writer.print(" ")
185                 writer.print(type.toTypeString()) // TODO start passing language = Language.KOTLIN
186             }
187         }
188     }
189 
190     private fun writeType(
191         item: Item,
192         type: TypeItem?
193     ) {
194         type ?: return
195 
196         val typeString = type.toTypeString(
197             outerAnnotations = false,
198             innerAnnotations = generateAnnotations,
199             erased = false,
200             kotlinStyleNulls = true,
201             context = item,
202             filter = filterReference
203             // TODO pass in language = Language.KOTLIN
204         )
205 
206         writer.print(typeString)
207     }
208 
209     override fun visitMethod(method: MethodItem) {
210         if (method.isKotlinProperty()) return // will be handled by visitProperty
211         val containingClass = method.containingClass()
212         val modifiers = method.modifiers
213         val isEnum = containingClass.isEnum()
214         val isAnnotation = containingClass.isAnnotationType()
215 
216         writer.println()
217         appendDocumentation(method, writer, docStubs)
218 
219         // TODO: Should be an annotation
220         generateThrowsList(method)
221 
222         // Need to filter out abstract from the modifiers list and turn it
223         // into a concrete method to make the stub compile
224         val removeAbstract = modifiers.isAbstract() && (isEnum || isAnnotation)
225 
226         appendModifiers(method, modifiers, removeAbstract, false)
227         generateTypeParameterList(typeList = method.typeParameterList(), addSpace = true)
228 
229         writer.print("fun ")
230         writer.print(method.name())
231         generateParameterList(method)
232 
233         writer.print(": ")
234         val returnType = method.returnType()
235         writeType(method, returnType)
236 
237         if (isAnnotation) {
238             val default = method.defaultValue()
239             if (default.isNotEmpty()) {
240                 writer.print(" default ")
241                 writer.print(default)
242             }
243         }
244 
245         if (modifiers.isAbstract() && !isEnum || isAnnotation || modifiers.isNative()) {
246             // do nothing
247         } else {
248             writer.print(" = ")
249             writeThrowStub()
250         }
251         writer.println()
252     }
253 
254     override fun afterVisitClass(cls: ClassItem) {
255         writer.println("}\n\n")
256     }
257 
258     private fun writeThrowStub() {
259         writer.write("error(\"Stub!\")")
260     }
261 
262     private fun generateParameterList(method: MethodItem) {
263         writer.print("(")
264         method.parameters().asSequence().forEachIndexed { i, parameter ->
265             if (i > 0) {
266                 writer.print(", ")
267             }
268             appendModifiers(
269                 parameter,
270                 parameter.modifiers,
271                 removeAbstract = false,
272                 removeFinal = false,
273                 addPublic = false
274             )
275             val name = parameter.publicName() ?: parameter.name()
276             writer.print(name)
277             writer.print(": ")
278             writeType(method, parameter.type())
279         }
280         writer.print(")")
281     }
282 
283     private fun generateThrowsList(method: MethodItem) {
284         // Note that throws types are already sorted internally to help comparison matching
285         val throws = if (preFiltered) {
286             method.throwsTypes().asSequence()
287         } else {
288             method.filteredThrowsTypes(filterReference).asSequence()
289         }
290         if (throws.any()) {
291             writer.print("@Throws(")
292             throws.asSequence().sortedWith(ClassItem.fullNameComparator).forEachIndexed { i, type ->
293                 if (i > 0) {
294                     writer.print(",")
295                 }
296                 writer.print(type.qualifiedName())
297                 writer.print("::class")
298             }
299             writer.print(")")
300         }
301     }
302 }