• 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 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.isEmpty()) {
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     /** Returns true if this type references a type not matched by the given predicate */
138     fun referencesExcludedType(filter: Predicate<Item>): Boolean {
139         if (primitive) {
140             return false
141         }
142 
143         for (item in typeArgumentClasses()) {
144             if (!filter.test(item)) {
145                 return true
146             }
147         }
148 
149         return false
150     }
151 
152     fun defaultValueString(): String = defaultValue()?.toString() ?: "null"
153 
154     fun hasTypeArguments(): Boolean = toTypeString().contains("<")
155 
156     /**
157      * If this type is a type parameter, then return the corresponding [TypeParameterItem].
158      * The optional [context] provides the method or class where this type parameter
159      * appears, and can be used for example to resolve the bounds for a type variable
160      * used in a method that was specified on the class.
161      */
162     fun asTypeParameter(context: MemberItem? = null): TypeParameterItem?
163 
164     /**
165      * Mark nullness annotations in the type as recent.
166      * TODO: This isn't very clean; we should model individual annotations.
167      */
168     fun markRecent()
169 
170     /** Returns true if this type represents an array of one or more dimensions */
171     fun isArray(): Boolean = arrayDimensions() > 0
172 
173     /**
174      * Ensure that we don't include any annotations in the type strings for this type.
175      */
176     fun scrubAnnotations()
177 
178     companion object {
179         /** Shortens types, if configured */
180         fun shortenTypes(type: String): String {
181             if (compatibility.omitCommonPackages) {
182                 var cleaned = type
183                 if (cleaned.contains("@androidx.annotation.")) {
184                     cleaned = cleaned.replace("@androidx.annotation.", "@")
185                 }
186                 if (cleaned.contains("@android.support.annotation.")) {
187                     cleaned = cleaned.replace("@android.support.annotation.", "@")
188                 }
189 
190                 return stripJavaLangPrefix(cleaned)
191             }
192 
193             return type
194         }
195 
196         /**
197          * Removes java.lang. prefixes from types, unless it's in a subpackage such
198          * as java.lang.reflect. For simplicity we may also leave inner classes
199          * in the java.lang package untouched.
200          *
201          * NOTE: We only remove this from the front of the type; e.g. we'll replace
202          * java.lang.Class<java.lang.String> with Class<java.lang.String>.
203          * This is because the signature parsing of types is not 100% accurate
204          * and we don't want to run into trouble with more complicated generic
205          * type signatures where we end up not mapping the simplified types back
206          * to the real fully qualified type names.
207          */
208         fun stripJavaLangPrefix(type: String): String {
209             if (type.startsWith(JAVA_LANG_PREFIX)) {
210                 // Replacing java.lang is harder, since we don't want to operate in sub packages,
211                 // e.g. java.lang.String -> String, but java.lang.reflect.Method -> unchanged
212                 val start = JAVA_LANG_PREFIX.length
213                 val end = type.length
214                 for (index in start until end) {
215                     if (type[index] == '<') {
216                         return type.substring(start)
217                     } else if (type[index] == '.') {
218                         return type
219                     }
220                 }
221 
222                 return type.substring(start)
223             }
224 
225             return type
226         }
227 
228         fun formatType(type: String?): String {
229             if (type == null) {
230                 return ""
231             }
232 
233             var cleaned = type
234 
235             if (compatibility.spaceAfterCommaInTypes && cleaned.indexOf(',') != -1) {
236                 // The compat files have spaces after commas where we normally don't
237                 cleaned = cleaned.replace(",", ", ").replace(",  ", ", ")
238             }
239 
240             cleaned = cleanupGenerics(cleaned)
241             return cleaned
242         }
243 
244         fun cleanupGenerics(signature: String): String {
245             // <T extends java.lang.Object> is the same as <T>
246             //  but NOT for <T extends Object & java.lang.Comparable> -- you can't
247             //  shorten this to <T & java.lang.Comparable
248             // return type.replace(" extends java.lang.Object", "")
249             return signature.replace(" extends java.lang.Object>", ">")
250         }
251 
252         val comparator: Comparator<TypeItem> = Comparator { type1, type2 ->
253             val cls1 = type1.asClass()
254             val cls2 = type2.asClass()
255             if (cls1 != null && cls2 != null) {
256                 ClassItem.fullNameComparator.compare(cls1, cls2)
257             } else {
258                 type1.toTypeString().compareTo(type2.toTypeString())
259             }
260         }
261 
262         fun convertTypeString(typeString: String, replacementMap: Map<String, String>?): String {
263             var string = typeString
264             if (replacementMap != null && replacementMap.isNotEmpty()) {
265                 // This is a moved method (typically an implementation of an interface
266                 // method provided in a hidden superclass), with generics signatures.
267                 // We need to rewrite the generics variables in case they differ
268                 // between the classes.
269                 if (!replacementMap.isEmpty()) {
270                     replacementMap.forEach { from, to ->
271                         // We can't just replace one string at a time:
272                         // what if I have a map of {"A"->"B", "B"->"C"} and I tried to convert A,B,C?
273                         // If I do the replacements one letter at a time I end up with C,C,C; if I do the substitutions
274                         // simultaneously I get B,C,C. Therefore, we insert "___" as a magical prefix to prevent
275                         // scenarios like this, and then we'll drop them afterwards.
276                         string = string.replace(Regex(pattern = """\b$from\b"""), replacement = "___$to")
277                     }
278                 }
279                 string = string.replace("___", "")
280                 return string
281             } else {
282                 return string
283             }
284         }
285 
286         // Copied from doclava1
287         fun toSlashFormat(typeName: String): String {
288             var name = typeName
289             var dimension = ""
290             while (name.endsWith("[]")) {
291                 dimension += "["
292                 name = name.substring(0, name.length - 2)
293             }
294 
295             val base: String
296             base = when (name) {
297                 "void" -> "V"
298                 "byte" -> "B"
299                 "boolean" -> "Z"
300                 "char" -> "C"
301                 "short" -> "S"
302                 "int" -> "I"
303                 "long" -> "L"
304                 "float" -> "F"
305                 "double" -> "D"
306                 else -> "L" + ClassContext.getInternalName(name) + ";"
307             }
308 
309             return dimension + base
310         }
311 
312         /** Compares two strings, ignoring space diffs (spaces, not whitespace in general) */
313         fun equalsWithoutSpace(s1: String, s2: String): Boolean {
314             if (s1 == s2) {
315                 return true
316             }
317             val sp1 = s1.indexOf(' ') // first space
318             val sp2 = s2.indexOf(' ')
319             if (sp1 == -1 && sp2 == -1) {
320                 // no spaces in strings and aren't equal
321                 return false
322             }
323 
324             val l1 = s1.length
325             val l2 = s2.length
326             var i1 = 0
327             var i2 = 0
328 
329             while (i1 < l1 && i2 < l2) {
330                 var c1 = s1[i1++]
331                 var c2 = s2[i2++]
332 
333                 while (c1 == ' ' && i1 < l1) {
334                     c1 = s1[i1++]
335                 }
336                 while (c2 == ' ' && i2 < l2) {
337                     c2 = s2[i2++]
338                 }
339                 if (c1 != c2) {
340                     return false
341                 }
342             }
343             // Skip trailing spaces
344             while (i1 < l1 && s1[i1] == ' ') {
345                 i1++
346             }
347             while (i2 < l2 && s2[i2] == ' ') {
348                 i2++
349             }
350             return i1 == l1 && i2 == l2
351         }
352     }
353 }
354