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     /** Returns the internal name of the type, as seen in bytecode */
64     fun internalName(): String {
65         // Default implementation; PSI subclass is more accurate
66         return toSlashFormat(toErasedTypeString())
67     }
68 
69     /** Array dimensions of this type; for example, for String it's 0 and for String[][] it's 2. */
70     fun arrayDimensions(): Int
71 
72     fun asClass(): ClassItem?
73 
74     fun toSimpleType(): String {
75         return stripJavaLangPrefix(toTypeString())
76     }
77 
78     /**
79      * Helper methods to compare types, especially types from signature files with types
80      * from parsing, which may have slightly different formats, e.g. varargs ("...") versus
81      * arrays ("[]"), java.lang. prefixes removed in wildcard signatures, etc.
82      */
83     fun toCanonicalType(context: Item? = null): String {
84         var s = toTypeString(context = context)
85         while (s.contains(JAVA_LANG_PREFIX)) {
86             s = s.replace(JAVA_LANG_PREFIX, "")
87         }
88         if (s.contains("...")) {
89             s = s.replace("...", "[]")
90         }
91 
92         return s
93     }
94 
95     val primitive: Boolean
96 
97     fun typeArgumentClasses(): List<ClassItem>
98 
99     fun convertType(from: ClassItem, to: ClassItem): TypeItem {
100         val map = from.mapTypeVariables(to)
101         if (map.isNotEmpty()) {
102             return convertType(map)
103         }
104 
105         return this
106     }
107 
108     fun convertType(replacementMap: Map<String, String>?, owner: Item? = null): TypeItem
109 
110     fun convertTypeString(replacementMap: Map<String, String>?): String {
111         val typeString = toTypeString(outerAnnotations = true, innerAnnotations = true, kotlinStyleNulls = false)
112         return convertTypeString(typeString, replacementMap)
113     }
114 
115     fun isJavaLangObject(): Boolean {
116         return toTypeString() == JAVA_LANG_OBJECT
117     }
118 
119     fun isString(): Boolean {
120         return toTypeString() == JAVA_LANG_STRING
121     }
122 
123     fun defaultValue(): Any? {
124         return when (toTypeString()) {
125             "boolean" -> false
126             "byte" -> 0.toByte()
127             "char" -> '\u0000'
128             "short" -> 0.toShort()
129             "int" -> 0
130             "long" -> 0L
131             "float" -> 0f
132             "double" -> 0.0
133             else -> null
134         }
135     }
136 
137     fun defaultValueString(): String = defaultValue()?.toString() ?: "null"
138 
139     fun hasTypeArguments(): Boolean = toTypeString().contains("<")
140 
141     /**
142      * If this type is a type parameter, then return the corresponding [TypeParameterItem].
143      * The optional [context] provides the method or class where this type parameter
144      * appears, and can be used for example to resolve the bounds for a type variable
145      * used in a method that was specified on the class.
146      */
147     fun asTypeParameter(context: MemberItem? = null): TypeParameterItem?
148 
149     /**
150      * Whether this type is a type parameter.
151      */
152     fun isTypeParameter(context: MemberItem? = null): Boolean = asTypeParameter(context) != null
153 
154     /**
155      * Mark nullness annotations in the type as recent.
156      * TODO: This isn't very clean; we should model individual annotations.
157      */
158     fun markRecent()
159 
160     /** Returns true if this type represents an array of one or more dimensions */
161     fun isArray(): Boolean = arrayDimensions() > 0
162 
163     /**
164      * Ensure that we don't include any annotations in the type strings for this type.
165      */
166     fun scrubAnnotations()
167 
168     companion object {
169         /** Shortens types, if configured */
170         fun shortenTypes(type: String): String {
171             if (compatibility.omitCommonPackages) {
172                 var cleaned = type
173                 if (cleaned.contains("@androidx.annotation.")) {
174                     cleaned = cleaned.replace("@androidx.annotation.", "@")
175                 }
176                 if (cleaned.contains("@android.support.annotation.")) {
177                     cleaned = cleaned.replace("@android.support.annotation.", "@")
178                 }
179 
180                 return stripJavaLangPrefix(cleaned)
181             }
182 
183             return type
184         }
185 
186         /**
187          * Removes java.lang. prefixes from types, unless it's in a subpackage such
188          * as java.lang.reflect. For simplicity we may also leave inner classes
189          * in the java.lang package untouched.
190          *
191          * NOTE: We only remove this from the front of the type; e.g. we'll replace
192          * java.lang.Class<java.lang.String> with Class<java.lang.String>.
193          * This is because the signature parsing of types is not 100% accurate
194          * and we don't want to run into trouble with more complicated generic
195          * type signatures where we end up not mapping the simplified types back
196          * to the real fully qualified type names.
197          */
198         fun stripJavaLangPrefix(type: String): String {
199             if (type.startsWith(JAVA_LANG_PREFIX)) {
200                 // Replacing java.lang is harder, since we don't want to operate in sub packages,
201                 // e.g. java.lang.String -> String, but java.lang.reflect.Method -> unchanged
202                 val start = JAVA_LANG_PREFIX.length
203                 val end = type.length
204                 for (index in start until end) {
205                     if (type[index] == '<') {
206                         return type.substring(start)
207                     } else if (type[index] == '.') {
208                         return type
209                     }
210                 }
211 
212                 return type.substring(start)
213             }
214 
215             return type
216         }
217 
218         fun formatType(type: String?): String {
219             if (type == null) {
220                 return ""
221             }
222 
223             var cleaned = type
224 
225             if (compatibility.spaceAfterCommaInTypes && cleaned.indexOf(',') != -1) {
226                 // The compat files have spaces after commas where we normally don't
227                 cleaned = cleaned.replace(",", ", ").replace(",  ", ", ")
228             }
229 
230             cleaned = cleanupGenerics(cleaned)
231             return cleaned
232         }
233 
234         fun cleanupGenerics(signature: String): String {
235             // <T extends java.lang.Object> is the same as <T>
236             //  but NOT for <T extends Object & java.lang.Comparable> -- you can't
237             //  shorten this to <T & java.lang.Comparable
238             // return type.replace(" extends java.lang.Object", "")
239             return signature.replace(" extends java.lang.Object>", ">")
240         }
241 
242         val comparator: Comparator<TypeItem> = Comparator { type1, type2 ->
243             val cls1 = type1.asClass()
244             val cls2 = type2.asClass()
245             if (cls1 != null && cls2 != null) {
246                 ClassItem.fullNameComparator.compare(cls1, cls2)
247             } else {
248                 type1.toTypeString().compareTo(type2.toTypeString())
249             }
250         }
251 
252         fun convertTypeString(typeString: String, replacementMap: Map<String, String>?): String {
253             var string = typeString
254             if (replacementMap != null && replacementMap.isNotEmpty()) {
255                 // This is a moved method (typically an implementation of an interface
256                 // method provided in a hidden superclass), with generics signatures.
257                 // We need to rewrite the generics variables in case they differ
258                 // between the classes.
259                 if (replacementMap.isNotEmpty()) {
260                     replacementMap.forEach { (from, to) ->
261                         // We can't just replace one string at a time:
262                         // what if I have a map of {"A"->"B", "B"->"C"} and I tried to convert A,B,C?
263                         // If I do the replacements one letter at a time I end up with C,C,C; if I do the substitutions
264                         // simultaneously I get B,C,C. Therefore, we insert "___" as a magical prefix to prevent
265                         // scenarios like this, and then we'll drop them afterwards.
266                         string = string.replace(Regex(pattern = """\b$from\b"""), replacement = "___$to")
267                     }
268                 }
269                 string = string.replace("___", "")
270                 return string
271             } else {
272                 return string
273             }
274         }
275 
276         // Copied from doclava1
277         fun toSlashFormat(typeName: String): String {
278             var name = typeName
279             var dimension = ""
280             while (name.endsWith("[]")) {
281                 dimension += "["
282                 name = name.substring(0, name.length - 2)
283             }
284 
285             val base: String
286             base = when (name) {
287                 "void" -> "V"
288                 "byte" -> "B"
289                 "boolean" -> "Z"
290                 "char" -> "C"
291                 "short" -> "S"
292                 "int" -> "I"
293                 "long" -> "L"
294                 "float" -> "F"
295                 "double" -> "D"
296                 else -> "L" + ClassContext.getInternalName(name) + ";"
297             }
298 
299             return dimension + base
300         }
301 
302         /** Compares two strings, ignoring space diffs (spaces, not whitespace in general) */
303         fun equalsWithoutSpace(s1: String, s2: String): Boolean {
304             if (s1 == s2) {
305                 return true
306             }
307             val sp1 = s1.indexOf(' ') // first space
308             val sp2 = s2.indexOf(' ')
309             if (sp1 == -1 && sp2 == -1) {
310                 // no spaces in strings and aren't equal
311                 return false
312             }
313 
314             val l1 = s1.length
315             val l2 = s2.length
316             var i1 = 0
317             var i2 = 0
318 
319             while (i1 < l1 && i2 < l2) {
320                 var c1 = s1[i1++]
321                 var c2 = s2[i2++]
322 
323                 while (c1 == ' ' && i1 < l1) {
324                     c1 = s1[i1++]
325                 }
326                 while (c2 == ' ' && i2 < l2) {
327                     c2 = s2[i2++]
328                 }
329                 if (c1 != c2) {
330                     return false
331                 }
332             }
333             // Skip trailing spaces
334             while (i1 < l1 && s1[i1] == ' ') {
335                 i1++
336             }
337             while (i2 < l2 && s2[i2] == ' ') {
338                 i2++
339             }
340             return i1 == l1 && i2 == l2
341         }
342     }
343 }
344