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