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.compatibility
23 import com.android.tools.metalava.options
24 import java.util.function.Predicate
25 
26 /** Represents a type */
27 interface TypeItem {
28     /**
29      * Generates a string for this type.
30      *
31      * For a type like this: @Nullable java.util.List<@NonNull java.lang.String>,
32      * [outerAnnotations] controls whether the top level annotation like @Nullable
33      * is included, [innerAnnotations] controls whether annotations like @NonNull
34      * are included, and [erased] controls whether we return the string for
35      * the raw type, e.g. just "java.util.List"
36      *
37      * (The combination [outerAnnotations] = true and [innerAnnotations] = false
38      * is not allowed.)
39      */
40     fun toTypeString(
41         outerAnnotations: Boolean = false,
42         innerAnnotations: Boolean = outerAnnotations,
43         erased: Boolean = false
44     ): String
45 
46     /** Alias for [toTypeString] with erased=true */
47     fun toErasedTypeString(): String
48 
49     /** Returns the internal name of the type, as seen in bytecode */
50     fun internalName(): String {
51         // Default implementation; PSI subclass is more accurate
52         return toSlashFormat(toErasedTypeString())
53     }
54 
55     /** Array dimensions of this type; for example, for String it's 0 and for String[][] it's 2. */
56     fun arrayDimensions(): Int
57 
58     fun asClass(): ClassItem?
59 
60     fun toSimpleType(): String {
61         return stripJavaLangPrefix(toTypeString())
62     }
63 
64     /**
65      * Helper methods to compare types, especially types from signature files with types
66      * from parsing, which may have slightly different formats, e.g. varargs ("...") versus
67      * arrays ("[]"), java.lang. prefixes removed in wildcard signatures, etc.
68      */
69     fun toCanonicalType(): String {
70         var s = toTypeString()
71         while (s.contains(JAVA_LANG_PREFIX)) {
72             s = s.replace(JAVA_LANG_PREFIX, "")
73         }
74         if (s.contains("...")) {
75             s = s.replace("...", "[]")
76         }
77 
78         return s
79     }
80 
81     val primitive: Boolean
82 
83     fun typeArgumentClasses(): List<ClassItem>
84 
85     fun convertType(from: ClassItem, to: ClassItem): TypeItem {
86         val map = from.mapTypeVariables(to)
87         if (!map.isEmpty()) {
88             return convertType(map)
89         }
90 
91         return this
92     }
93 
94     fun convertType(replacementMap: Map<String, String>?, owner: Item? = null): TypeItem
95 
96     fun convertTypeString(replacementMap: Map<String, String>?): String {
97         return convertTypeString(toTypeString(outerAnnotations = true, innerAnnotations = true), replacementMap)
98     }
99 
100     fun isJavaLangObject(): Boolean {
101         return toTypeString() == JAVA_LANG_OBJECT
102     }
103 
104     fun defaultValue(): Any? {
105         return when (toTypeString()) {
106             "boolean" -> false
107             "byte" -> 0.toByte()
108             "char" -> '\u0000'
109             "short" -> 0.toShort()
110             "int" -> 0
111             "long" -> 0L
112             "float" -> 0f
113             "double" -> 0.0
114             else -> null
115         }
116     }
117 
118     /** Returns true if this type references a type not matched by the given predicate */
119     fun referencesExcludedType(filter: Predicate<Item>): Boolean {
120         if (primitive) {
121             return false
122         }
123 
124         for (item in typeArgumentClasses()) {
125             if (!filter.test(item)) {
126                 return true
127             }
128         }
129 
130         return false
131     }
132 
133     fun defaultValueString(): String = defaultValue()?.toString() ?: "null"
134 
135     fun hasTypeArguments(): Boolean = toTypeString().contains("<")
136 
137     /**
138      * If this type is a type parameter, then return the corresponding [TypeParameterItem].
139      * The optional [context] provides the method or class where this type parameter
140      * appears, and can be used for example to resolve the bounds for a type variable
141      * used in a method that was specified on the class.
142      */
143     fun asTypeParameter(context: MemberItem? = null): TypeParameterItem?
144 
145     /**
146      * Mark nullness annotations in the type as recent.
147      * TODO: This isn't very clean; we should model individual annotations.
148      */
149     fun markRecent()
150 
151     companion object {
152         /** Shortens types, if configured */
153         fun shortenTypes(type: String): String {
154             if (options.omitCommonPackages) {
155                 var cleaned = type
156                 if (cleaned.contains("@androidx.annotation.")) {
157                     cleaned = cleaned.replace("@androidx.annotation.", "@")
158                 }
159                 if (cleaned.contains("@android.support.annotation.")) {
160                     cleaned = cleaned.replace("@android.support.annotation.", "@")
161                 }
162 
163                 return stripJavaLangPrefix(cleaned)
164             }
165 
166             return type
167         }
168 
169         /**
170          * Removes java.lang. prefixes from types, unless it's in a subpackage such
171          * as java.lang.reflect. For simplicity we may also leave inner classes
172          * in the java.lang package untouched.
173          *
174          * NOTE: We only remove this from the front of the type; e.g. we'll replace
175          * java.lang.Class<java.lang.String> with Class<java.lang.String>.
176          * This is because the signature parsing of types is not 100% accurate
177          * and we don't want to run into trouble with more complicated generic
178          * type signatures where we end up not mapping the simplified types back
179          * to the real fully qualified type names.
180          */
181         fun stripJavaLangPrefix(type: String): String {
182             if (type.startsWith(JAVA_LANG_PREFIX)) {
183                 // Replacing java.lang is harder, since we don't want to operate in sub packages,
184                 // e.g. java.lang.String -> String, but java.lang.reflect.Method -> unchanged
185                 val start = JAVA_LANG_PREFIX.length
186                 val end = type.length
187                 for (index in start until end) {
188                     if (type[index] == '<') {
189                         return type.substring(start)
190                     } else if (type[index] == '.') {
191                         return type
192                     }
193                 }
194 
195                 return type.substring(start)
196             }
197 
198             return type
199         }
200 
201         fun formatType(type: String?): String {
202             if (type == null) {
203                 return ""
204             }
205 
206             var cleaned = type
207 
208             if (compatibility.spacesAfterCommas && cleaned.indexOf(',') != -1) {
209                 // The compat files have spaces after commas where we normally don't
210                 cleaned = cleaned.replace(",", ", ").replace(",  ", ", ")
211             }
212 
213             cleaned = cleanupGenerics(cleaned)
214             return cleaned
215         }
216 
217         fun cleanupGenerics(signature: String): String {
218             // <T extends java.lang.Object> is the same as <T>
219             //  but NOT for <T extends Object & java.lang.Comparable> -- you can't
220             //  shorten this to <T & java.lang.Comparable
221             // return type.replace(" extends java.lang.Object", "")
222             return signature.replace(" extends java.lang.Object>", ">")
223         }
224 
225         val comparator: Comparator<TypeItem> = Comparator { type1, type2 ->
226             val cls1 = type1.asClass()
227             val cls2 = type2.asClass()
228             if (cls1 != null && cls2 != null) {
229                 ClassItem.fullNameComparator.compare(cls1, cls2)
230             } else {
231                 type1.toTypeString().compareTo(type2.toTypeString())
232             }
233         }
234 
235         fun convertTypeString(typeString: String, replacementMap: Map<String, String>?): String {
236             var string = typeString
237             if (replacementMap != null && replacementMap.isNotEmpty()) {
238                 // This is a moved method (typically an implementation of an interface
239                 // method provided in a hidden superclass), with generics signatures.
240                 // We need to rewrite the generics variables in case they differ
241                 // between the classes.
242                 if (!replacementMap.isEmpty()) {
243                     replacementMap.forEach { from, to ->
244                         // We can't just replace one string at a time:
245                         // what if I have a map of {"A"->"B", "B"->"C"} and I tried to convert A,B,C?
246                         // If I do the replacements one letter at a time I end up with C,C,C; if I do the substitutions
247                         // simultaneously I get B,C,C. Therefore, we insert "___" as a magical prefix to prevent
248                         // scenarios like this, and then we'll drop them afterwards.
249                         string = string.replace(Regex(pattern = """\b$from\b"""), replacement = "___$to")
250                     }
251                 }
252                 string = string.replace("___", "")
253                 return string
254             } else {
255                 return string
256             }
257         }
258 
259         // Copied from doclava1
260         fun toSlashFormat(typeName: String): String {
261             var name = typeName
262             var dimension = ""
263             while (name.endsWith("[]")) {
264                 dimension += "["
265                 name = name.substring(0, name.length - 2)
266             }
267 
268             val base: String
269             base = when (name) {
270                 "void" -> "V"
271                 "byte" -> "B"
272                 "boolean" -> "Z"
273                 "char" -> "C"
274                 "short" -> "S"
275                 "int" -> "I"
276                 "long" -> "L"
277                 "float" -> "F"
278                 "double" -> "D"
279                 else -> "L" + ClassContext.getInternalName(name) + ";"
280             }
281 
282             return dimension + base
283         }
284     }
285 }