• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2017 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.model
18 
19 import com.android.tools.lint.detector.api.ClassContext
20 import com.android.tools.metalava.JAVA_LANG_OBJECT
21 import com.android.tools.metalava.JAVA_LANG_PREFIX
22 import com.android.tools.metalava.JAVA_LANG_STRING
23 import java.util.function.Predicate
24 
25 /**
26  * Whether metalava supports type use annotations.
27  * Note that you can't just turn this flag back on; you have to
28  * also add TYPE_USE back to the handful of nullness
29  * annotations in stub-annotations/src/main/java/.
30  */
31 const val SUPPORT_TYPE_USE_ANNOTATIONS = false
32 
33 /** Represents a {@link https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Type.html Type} */
34 interface TypeItem {
35     /**
36      * Generates a string for this type.
37      *
38      * For a type like this: @Nullable java.util.List<@NonNull java.lang.String>,
39      * [outerAnnotations] controls whether the top level annotation like @Nullable
40      * is included, [innerAnnotations] controls whether annotations like @NonNull
41      * are included, and [erased] controls whether we return the string for
42      * the raw type, e.g. just "java.util.List". The [kotlinStyleNulls] parameter
43      * controls whether it should return "@Nullable List<String>" as "List<String!>?".
44      * Finally, [filter] specifies a filter to apply to the type annotations, if
45      * any.
46      *
47      * (The combination [outerAnnotations] = true and [innerAnnotations] = false
48      * is not allowed.)
49      */
50     fun toTypeString(
51         outerAnnotations: Boolean = false,
52         innerAnnotations: Boolean = outerAnnotations,
53         erased: Boolean = false,
54         kotlinStyleNulls: Boolean = false,
55         context: Item? = null,
56         filter: Predicate<Item>? = null
57     ): String
58 
59     /** Alias for [toTypeString] with erased=true */
60     fun toErasedTypeString(context: Item? = null): String
61 
62     /**
63      * Returns the internal name of the type, as seen in bytecode. The optional [context]
64      * provides the method or class where this type appears, and can be used for example
65      * to resolve the bounds for a type variable used in a method that was specified on the class.
66      */
67     fun internalName(context: Item? = null): String {
68         // Default implementation; PSI subclass is more accurate
69         return toSlashFormat(toErasedTypeString(context))
70     }
71 
72     /** Array dimensions of this type; for example, for String it's 0 and for String[][] it's 2. */
73     fun arrayDimensions(): Int
74 
75     fun asClass(): ClassItem?
76 
77     fun toSimpleType(): String {
78         return stripJavaLangPrefix(toTypeString())
79     }
80 
81     /**
82      * Helper methods to compare types, especially types from signature files with types
83      * from parsing, which may have slightly different formats, e.g. varargs ("...") versus
84      * arrays ("[]"), java.lang. prefixes removed in wildcard signatures, etc.
85      */
86     fun toCanonicalType(context: Item? = null): String {
87         var s = toTypeString(context = context)
88         while (s.contains(JAVA_LANG_PREFIX)) {
89             s = s.replace(JAVA_LANG_PREFIX, "")
90         }
91         if (s.contains("...")) {
92             s = s.replace("...", "[]")
93         }
94 
95         return s
96     }
97 
98     /**
99      * Returns the element type if the type is an array or contains a vararg.
100      * If the element is not an array or does not contain a vararg,
101      * returns the original type string.
102      */
103     fun toElementType(): String {
104         return toErasedTypeString().replace("...", "").replace("[]", "")
105     }
106 
107     val primitive: Boolean
108 
109     fun typeArgumentClasses(): List<ClassItem>
110 
111     fun convertType(from: ClassItem, to: ClassItem): TypeItem {
112         val map = from.mapTypeVariables(to)
113         if (map.isNotEmpty()) {
114             return convertType(map)
115         }
116 
117         return this
118     }
119 
120     fun convertType(replacementMap: Map<String, String>?, owner: Item? = null): TypeItem
121 
122     fun convertTypeString(replacementMap: Map<String, String>?): String {
123         val typeString =
124             toTypeString(outerAnnotations = true, innerAnnotations = true, kotlinStyleNulls = false)
125         return convertTypeString(typeString, replacementMap)
126     }
127 
128     fun isJavaLangObject(): Boolean {
129         return toTypeString() == JAVA_LANG_OBJECT
130     }
131 
132     fun isString(): Boolean {
133         return toTypeString() == JAVA_LANG_STRING
134     }
135 
136     fun defaultValue(): Any? {
137         return when (toTypeString()) {
138             "boolean" -> false
139             "char", "int", "float", "double" -> 0
140             "byte" -> 0.toByte()
141             "short" -> 0.toShort()
142             "long" -> 0L
143             else -> null
144         }
145     }
146 
147     fun defaultValueString(): String = defaultValue()?.toString() ?: "null"
148 
149     fun hasTypeArguments(): Boolean = toTypeString().contains("<")
150 
151     /**
152      * If the item has type arguments, return a list of type arguments.
153      * If simplified is true, returns the simplified forms of the type arguments.
154      * e.g. when type arguments are <K, V extends some.arbitrary.Class>, [K, V] will be returned.
155      * If the item does not have any type arguments, return an empty list.
156      */
157     fun typeArguments(simplified: Boolean = false): List<String> {
158         if (!hasTypeArguments()) {
159             return emptyList()
160         }
161         val typeString = toTypeString()
162         val bracketRemovedTypeString = toTypeString().indexOf('<')
163             .let { typeString.substring(it + 1, typeString.length - 1) }
164         val typeArguments = mutableListOf<String>()
165         var builder = StringBuilder()
166         var balance = 0
167         var idx = 0
168         while (idx < bracketRemovedTypeString.length) {
169             when (val s = bracketRemovedTypeString[idx]) {
170                 ',' -> {
171                     if (balance == 0) {
172                         typeArguments.add(builder.toString())
173                         builder = StringBuilder()
174                     } else {
175                         builder.append(s)
176                     }
177                 }
178                 '<' -> {
179                     balance += 1
180                     builder.append(s)
181                 }
182                 '>' -> {
183                     balance -= 1
184                     builder.append(s)
185                 }
186                 else -> builder.append(s)
187             }
188             idx += 1
189         }
190         typeArguments.add(builder.toString())
191 
192         if (simplified) {
193             return typeArguments.map { it.substringBefore(" extends ").trim() }
194         }
195         return typeArguments.map { it.trim() }
196     }
197 
198     /**
199      * If this type is a type parameter, then return the corresponding [TypeParameterItem].
200      * The optional [context] provides the method or class where this type parameter
201      * appears, and can be used for example to resolve the bounds for a type variable
202      * used in a method that was specified on the class.
203      */
204     fun asTypeParameter(context: MemberItem? = null): TypeParameterItem?
205 
206     /**
207      * Whether this type is a type parameter.
208      */
209     fun isTypeParameter(context: MemberItem? = null): Boolean = asTypeParameter(context) != null
210 
211     /**
212      * Mark nullness annotations in the type as recent.
213      * TODO: This isn't very clean; we should model individual annotations.
214      */
215     fun markRecent()
216 
217     /** Returns true if this type represents an array of one or more dimensions */
218     fun isArray(): Boolean = arrayDimensions() > 0
219 
220     /**
221      * Ensure that we don't include any annotations in the type strings for this type.
222      */
223     fun scrubAnnotations()
224 
225     companion object {
226         /** Shortens types, if configured */
227         fun shortenTypes(type: String): String {
228             var cleaned = type
229             if (cleaned.contains("@androidx.annotation.")) {
230                 cleaned = cleaned.replace("@androidx.annotation.", "@")
231             }
232             return stripJavaLangPrefix(cleaned)
233         }
234 
235         /**
236          * Removes java.lang. prefixes from types, unless it's in a subpackage such
237          * as java.lang.reflect. For simplicity we may also leave inner classes
238          * in the java.lang package untouched.
239          *
240          * NOTE: We only remove this from the front of the type; e.g. we'll replace
241          * java.lang.Class<java.lang.String> with Class<java.lang.String>.
242          * This is because the signature parsing of types is not 100% accurate
243          * and we don't want to run into trouble with more complicated generic
244          * type signatures where we end up not mapping the simplified types back
245          * to the real fully qualified type names.
246          */
247         fun stripJavaLangPrefix(type: String): String {
248             if (type.startsWith(JAVA_LANG_PREFIX)) {
249                 // Replacing java.lang is harder, since we don't want to operate in sub packages,
250                 // e.g. java.lang.String -> String, but java.lang.reflect.Method -> unchanged
251                 val start = JAVA_LANG_PREFIX.length
252                 val end = type.length
253                 for (index in start until end) {
254                     if (type[index] == '<') {
255                         return type.substring(start)
256                     } else if (type[index] == '.') {
257                         return type
258                     }
259                 }
260 
261                 return type.substring(start)
262             }
263 
264             return type
265         }
266 
267         fun formatType(type: String?): String {
268             return if (type == null) {
269                 ""
270             } else cleanupGenerics(type)
271         }
272 
273         fun cleanupGenerics(signature: String): String {
274             // <T extends java.lang.Object> is the same as <T>
275             //  but NOT for <T extends Object & java.lang.Comparable> -- you can't
276             //  shorten this to <T & java.lang.Comparable
277             // return type.replace(" extends java.lang.Object", "")
278             return signature.replace(" extends java.lang.Object>", ">")
279         }
280 
281         val comparator: Comparator<TypeItem> = Comparator { type1, type2 ->
282             val cls1 = type1.asClass()
283             val cls2 = type2.asClass()
284             if (cls1 != null && cls2 != null) {
285                 ClassItem.fullNameComparator.compare(cls1, cls2)
286             } else {
287                 type1.toTypeString().compareTo(type2.toTypeString())
288             }
289         }
290 
291         fun convertTypeString(typeString: String, replacementMap: Map<String, String>?): String {
292             var string = typeString
293             if (replacementMap != null && replacementMap.isNotEmpty()) {
294                 // This is a moved method (typically an implementation of an interface
295                 // method provided in a hidden superclass), with generics signatures.
296                 // We need to rewrite the generics variables in case they differ
297                 // between the classes.
298                 if (replacementMap.isNotEmpty()) {
299                     replacementMap.forEach { (from, to) ->
300                         // We can't just replace one string at a time:
301                         // what if I have a map of {"A"->"B", "B"->"C"} and I tried to convert A,B,C?
302                         // If I do the replacements one letter at a time I end up with C,C,C; if I do the substitutions
303                         // simultaneously I get B,C,C. Therefore, we insert "___" as a magical prefix to prevent
304                         // scenarios like this, and then we'll drop them afterwards.
305                         string =
306                             string.replace(Regex(pattern = """\b$from\b"""), replacement = "___$to")
307                     }
308                 }
309                 string = string.replace("___", "")
310                 return string
311             } else {
312                 return string
313             }
314         }
315 
316         /**
317          * Convert a type string containing to its lambda representation or return the original.
318          *
319          * E.g.: `"kotlin.jvm.functions.Function1<Integer, String>"` to `"(Integer) -> String"`.
320          */
321         fun toLambdaFormat(typeName: String): String {
322             // Bail if this isn't a Kotlin function type
323             if (!typeName.startsWith(KOTLIN_FUNCTION_PREFIX)) {
324                 return typeName
325             }
326 
327             // Find the first character after the first opening angle bracket. This will either be
328             // the first character of the paramTypes of the lambda if it has parameters.
329             val paramTypesStart =
330                 typeName.indexOf('<', startIndex = KOTLIN_FUNCTION_PREFIX.length) + 1
331 
332             // The last type param is always the return type. We find and set these boundaries with
333             // the push down loop below.
334             var paramTypesEnd = -1
335             var returnTypeStart = -1
336 
337             // Get the exclusive end of the return type parameter by finding the last closing
338             // angle bracket.
339             val returnTypeEnd = typeName.lastIndexOf('>')
340 
341             // Bail if an an unexpected format broke the indexOf's above.
342             if (paramTypesStart <= 0 || paramTypesStart >= returnTypeEnd) {
343                 return typeName
344             }
345 
346             // This loop looks for the last comma that is not inside the type parameters of a type
347             // parameter. It's a simple push down state machine that stores its depth as a counter
348             // instead of a stack. It runs backwards from the last character of the type parameters
349             // just before the last closing angle bracket to the beginning just before the first
350             // opening angle bracket.
351             var depth = 0
352             for (i in returnTypeEnd - 1 downTo paramTypesStart) {
353                 val c = typeName[i]
354 
355                 // Increase or decrease stack depth on angle brackets
356                 when (c) {
357                     '>' -> depth++
358                     '<' -> depth--
359                 }
360 
361                 when {
362                     depth == 0 -> when { // At the top level
363                         c == ',' -> {
364                             // When top level comma is found, mark it as the exclusive end of the
365                             // parameter types and end the loop
366                             paramTypesEnd = i
367                             break
368                         }
369                         !c.isWhitespace() -> {
370                             // Keep moving the start of the return type back until whitespace
371                             returnTypeStart = i
372                         }
373                     }
374                     depth < 0 -> return typeName // Bail, unbalanced nesting
375                 }
376             }
377 
378             // Bail if some sort of unbalanced nesting occurred or the indices around the comma
379             // appear grossly incorrect.
380             if (depth > 0 || returnTypeStart < 0 || returnTypeStart <= paramTypesEnd) {
381                 return typeName
382             }
383 
384             return buildString(typeName.length) {
385                 append("(")
386 
387                 // Slice param types, if any, and append them between the parenthesis
388                 if (paramTypesEnd > 0) {
389                     append(typeName, paramTypesStart, paramTypesEnd)
390                 }
391 
392                 append(") -> ")
393 
394                 // Slice out the return type param and append it after the arrow
395                 append(typeName, returnTypeStart, returnTypeEnd)
396             }
397         }
398 
399         /** Prefix of Kotlin JVM function types, used for lambdas. */
400         private const val KOTLIN_FUNCTION_PREFIX = "kotlin.jvm.functions.Function"
401 
402         // Copied from doclava1
403         fun toSlashFormat(typeName: String): String {
404             var name = typeName
405             var dimension = ""
406             while (name.endsWith("[]")) {
407                 dimension += "["
408                 name = name.substring(0, name.length - 2)
409             }
410 
411             val base: String
412             base = when (name) {
413                 "void" -> "V"
414                 "byte" -> "B"
415                 "boolean" -> "Z"
416                 "char" -> "C"
417                 "short" -> "S"
418                 "int" -> "I"
419                 "long" -> "J"
420                 "float" -> "F"
421                 "double" -> "D"
422                 else -> "L" + ClassContext.getInternalName(name) + ";"
423             }
424 
425             return dimension + base
426         }
427 
428         /** Compares two strings, ignoring space diffs (spaces, not whitespace in general) */
429         fun equalsWithoutSpace(s1: String, s2: String): Boolean {
430             if (s1 == s2) {
431                 return true
432             }
433             val sp1 = s1.indexOf(' ') // first space
434             val sp2 = s2.indexOf(' ')
435             if (sp1 == -1 && sp2 == -1) {
436                 // no spaces in strings and aren't equal
437                 return false
438             }
439 
440             val l1 = s1.length
441             val l2 = s2.length
442             var i1 = 0
443             var i2 = 0
444 
445             while (i1 < l1 && i2 < l2) {
446                 var c1 = s1[i1++]
447                 var c2 = s2[i2++]
448 
449                 while (c1 == ' ' && i1 < l1) {
450                     c1 = s1[i1++]
451                 }
452                 while (c2 == ' ' && i2 < l2) {
453                     c2 = s2[i2++]
454                 }
455                 if (c1 != c2) {
456                     return false
457                 }
458             }
459             // Skip trailing spaces
460             while (i1 < l1 && s1[i1] == ' ') {
461                 i1++
462             }
463             while (i2 < l2 && s2[i2] == ' ') {
464                 i2++
465             }
466             return i1 == l1 && i2 == l2
467         }
468     }
469 }
470