• 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     val primitive: Boolean
99 
100     fun typeArgumentClasses(): List<ClassItem>
101 
102     fun convertType(from: ClassItem, to: ClassItem): TypeItem {
103         val map = from.mapTypeVariables(to)
104         if (map.isNotEmpty()) {
105             return convertType(map)
106         }
107 
108         return this
109     }
110 
111     fun convertType(replacementMap: Map<String, String>?, owner: Item? = null): TypeItem
112 
113     fun convertTypeString(replacementMap: Map<String, String>?): String {
114         val typeString =
115             toTypeString(outerAnnotations = true, innerAnnotations = true, kotlinStyleNulls = false)
116         return convertTypeString(typeString, replacementMap)
117     }
118 
119     fun isJavaLangObject(): Boolean {
120         return toTypeString() == JAVA_LANG_OBJECT
121     }
122 
123     fun isString(): Boolean {
124         return toTypeString() == JAVA_LANG_STRING
125     }
126 
127     fun defaultValue(): Any? {
128         return when (toTypeString()) {
129             "boolean" -> false
130             "byte" -> 0.toByte()
131             "char" -> '\u0000'
132             "short" -> 0.toShort()
133             "int" -> 0
134             "long" -> 0L
135             "float" -> 0f
136             "double" -> 0.0
137             else -> null
138         }
139     }
140 
141     fun defaultValueString(): String = defaultValue()?.toString() ?: "null"
142 
143     fun hasTypeArguments(): Boolean = toTypeString().contains("<")
144 
145     /**
146      * If this type is a type parameter, then return the corresponding [TypeParameterItem].
147      * The optional [context] provides the method or class where this type parameter
148      * appears, and can be used for example to resolve the bounds for a type variable
149      * used in a method that was specified on the class.
150      */
151     fun asTypeParameter(context: MemberItem? = null): TypeParameterItem?
152 
153     /**
154      * Whether this type is a type parameter.
155      */
156     fun isTypeParameter(context: MemberItem? = null): Boolean = asTypeParameter(context) != null
157 
158     /**
159      * Mark nullness annotations in the type as recent.
160      * TODO: This isn't very clean; we should model individual annotations.
161      */
162     fun markRecent()
163 
164     /** Returns true if this type represents an array of one or more dimensions */
165     fun isArray(): Boolean = arrayDimensions() > 0
166 
167     /**
168      * Ensure that we don't include any annotations in the type strings for this type.
169      */
170     fun scrubAnnotations()
171 
172     companion object {
173         /** Shortens types, if configured */
174         fun shortenTypes(type: String): String {
175             var cleaned = type
176             if (cleaned.contains("@androidx.annotation.")) {
177                 cleaned = cleaned.replace("@androidx.annotation.", "@")
178             }
179             return stripJavaLangPrefix(cleaned)
180         }
181 
182         /**
183          * Removes java.lang. prefixes from types, unless it's in a subpackage such
184          * as java.lang.reflect. For simplicity we may also leave inner classes
185          * in the java.lang package untouched.
186          *
187          * NOTE: We only remove this from the front of the type; e.g. we'll replace
188          * java.lang.Class<java.lang.String> with Class<java.lang.String>.
189          * This is because the signature parsing of types is not 100% accurate
190          * and we don't want to run into trouble with more complicated generic
191          * type signatures where we end up not mapping the simplified types back
192          * to the real fully qualified type names.
193          */
194         fun stripJavaLangPrefix(type: String): String {
195             if (type.startsWith(JAVA_LANG_PREFIX)) {
196                 // Replacing java.lang is harder, since we don't want to operate in sub packages,
197                 // e.g. java.lang.String -> String, but java.lang.reflect.Method -> unchanged
198                 val start = JAVA_LANG_PREFIX.length
199                 val end = type.length
200                 for (index in start until end) {
201                     if (type[index] == '<') {
202                         return type.substring(start)
203                     } else if (type[index] == '.') {
204                         return type
205                     }
206                 }
207 
208                 return type.substring(start)
209             }
210 
211             return type
212         }
213 
214         fun formatType(type: String?): String {
215             return if (type == null) {
216                 ""
217             } else cleanupGenerics(type)
218         }
219 
220         fun cleanupGenerics(signature: String): String {
221             // <T extends java.lang.Object> is the same as <T>
222             //  but NOT for <T extends Object & java.lang.Comparable> -- you can't
223             //  shorten this to <T & java.lang.Comparable
224             // return type.replace(" extends java.lang.Object", "")
225             return signature.replace(" extends java.lang.Object>", ">")
226         }
227 
228         val comparator: Comparator<TypeItem> = Comparator { type1, type2 ->
229             val cls1 = type1.asClass()
230             val cls2 = type2.asClass()
231             if (cls1 != null && cls2 != null) {
232                 ClassItem.fullNameComparator.compare(cls1, cls2)
233             } else {
234                 type1.toTypeString().compareTo(type2.toTypeString())
235             }
236         }
237 
238         fun convertTypeString(typeString: String, replacementMap: Map<String, String>?): String {
239             var string = typeString
240             if (replacementMap != null && replacementMap.isNotEmpty()) {
241                 // This is a moved method (typically an implementation of an interface
242                 // method provided in a hidden superclass), with generics signatures.
243                 // We need to rewrite the generics variables in case they differ
244                 // between the classes.
245                 if (replacementMap.isNotEmpty()) {
246                     replacementMap.forEach { (from, to) ->
247                         // We can't just replace one string at a time:
248                         // what if I have a map of {"A"->"B", "B"->"C"} and I tried to convert A,B,C?
249                         // If I do the replacements one letter at a time I end up with C,C,C; if I do the substitutions
250                         // simultaneously I get B,C,C. Therefore, we insert "___" as a magical prefix to prevent
251                         // scenarios like this, and then we'll drop them afterwards.
252                         string =
253                             string.replace(Regex(pattern = """\b$from\b"""), replacement = "___$to")
254                     }
255                 }
256                 string = string.replace("___", "")
257                 return string
258             } else {
259                 return string
260             }
261         }
262 
263         /**
264          * Convert a type string containing to its lambda representation or return the original.
265          *
266          * E.g.: `"kotlin.jvm.functions.Function1<Integer, String>"` to `"(Integer) -> String"`.
267          */
268         fun toLambdaFormat(typeName: String): String {
269             // Bail if this isn't a Kotlin function type
270             if (!typeName.startsWith(KOTLIN_FUNCTION_PREFIX)) {
271                 return typeName
272             }
273 
274             // Find the first character after the first opening angle bracket. This will either be
275             // the first character of the paramTypes of the lambda if it has parameters.
276             val paramTypesStart =
277                 typeName.indexOf('<', startIndex = KOTLIN_FUNCTION_PREFIX.length) + 1
278 
279             // The last type param is always the return type. We find and set these boundaries with
280             // the push down loop below.
281             var paramTypesEnd = -1
282             var returnTypeStart = -1
283 
284             // Get the exclusive end of the return type parameter by finding the last closing
285             // angle bracket.
286             val returnTypeEnd = typeName.lastIndexOf('>')
287 
288             // Bail if an an unexpected format broke the indexOf's above.
289             if (paramTypesStart <= 0 || paramTypesStart >= returnTypeEnd) {
290                 return typeName
291             }
292 
293             // This loop looks for the last comma that is not inside the type parameters of a type
294             // parameter. It's a simple push down state machine that stores its depth as a counter
295             // instead of a stack. It runs backwards from the last character of the type parameters
296             // just before the last closing angle bracket to the beginning just before the first
297             // opening angle bracket.
298             var depth = 0
299             for (i in returnTypeEnd - 1 downTo paramTypesStart) {
300                 val c = typeName[i]
301 
302                 // Increase or decrease stack depth on angle brackets
303                 when (c) {
304                     '>' -> depth++
305                     '<' -> depth--
306                 }
307 
308                 when {
309                     depth == 0 -> when { // At the top level
310                         c == ',' -> {
311                             // When top level comma is found, mark it as the exclusive end of the
312                             // parameter types and end the loop
313                             paramTypesEnd = i
314                             break
315                         }
316                         !c.isWhitespace() -> {
317                             // Keep moving the start of the return type back until whitespace
318                             returnTypeStart = i
319                         }
320                     }
321                     depth < 0 -> return typeName // Bail, unbalanced nesting
322                 }
323             }
324 
325             // Bail if some sort of unbalanced nesting occurred or the indices around the comma
326             // appear grossly incorrect.
327             if (depth > 0 || returnTypeStart < 0 || returnTypeStart <= paramTypesEnd) {
328                 return typeName
329             }
330 
331             return buildString(typeName.length) {
332                 append("(")
333 
334                 // Slice param types, if any, and append them between the parenthesis
335                 if (paramTypesEnd > 0) {
336                     append(typeName, paramTypesStart, paramTypesEnd)
337                 }
338 
339                 append(") -> ")
340 
341                 // Slice out the return type param and append it after the arrow
342                 append(typeName, returnTypeStart, returnTypeEnd)
343             }
344         }
345 
346         /** Prefix of Kotlin JVM function types, used for lambdas. */
347         private const val KOTLIN_FUNCTION_PREFIX = "kotlin.jvm.functions.Function"
348 
349         // Copied from doclava1
350         fun toSlashFormat(typeName: String): String {
351             var name = typeName
352             var dimension = ""
353             while (name.endsWith("[]")) {
354                 dimension += "["
355                 name = name.substring(0, name.length - 2)
356             }
357 
358             val base: String
359             base = when (name) {
360                 "void" -> "V"
361                 "byte" -> "B"
362                 "boolean" -> "Z"
363                 "char" -> "C"
364                 "short" -> "S"
365                 "int" -> "I"
366                 "long" -> "J"
367                 "float" -> "F"
368                 "double" -> "D"
369                 else -> "L" + ClassContext.getInternalName(name) + ";"
370             }
371 
372             return dimension + base
373         }
374 
375         /** Compares two strings, ignoring space diffs (spaces, not whitespace in general) */
376         fun equalsWithoutSpace(s1: String, s2: String): Boolean {
377             if (s1 == s2) {
378                 return true
379             }
380             val sp1 = s1.indexOf(' ') // first space
381             val sp2 = s2.indexOf(' ')
382             if (sp1 == -1 && sp2 == -1) {
383                 // no spaces in strings and aren't equal
384                 return false
385             }
386 
387             val l1 = s1.length
388             val l2 = s2.length
389             var i1 = 0
390             var i2 = 0
391 
392             while (i1 < l1 && i2 < l2) {
393                 var c1 = s1[i1++]
394                 var c2 = s2[i2++]
395 
396                 while (c1 == ' ' && i1 < l1) {
397                     c1 = s1[i1++]
398                 }
399                 while (c2 == ' ' && i2 < l2) {
400                     c2 = s2[i2++]
401                 }
402                 if (c1 != c2) {
403                     return false
404                 }
405             }
406             // Skip trailing spaces
407             while (i1 < l1 && s1[i1] == ' ') {
408                 i1++
409             }
410             while (i2 < l2 && s2[i2] == ' ') {
411                 i2++
412             }
413             return i1 == l1 && i2 == l2
414         }
415     }
416 }
417