• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * 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.text
18 
19 import com.android.tools.metalava.JAVA_LANG_OBJECT
20 import com.android.tools.metalava.JAVA_LANG_PREFIX
21 import com.android.tools.metalava.doclava1.TextCodebase
22 import com.android.tools.metalava.model.AnnotationItem
23 import com.android.tools.metalava.model.ClassItem
24 import com.android.tools.metalava.model.Item
25 import com.android.tools.metalava.model.MemberItem
26 import com.android.tools.metalava.model.MethodItem
27 import com.android.tools.metalava.model.TypeItem
28 import com.android.tools.metalava.model.TypeParameterItem
29 import com.android.tools.metalava.model.TypeParameterList
30 import com.android.tools.metalava.model.TypeParameterListOwner
31 import java.util.function.Predicate
32 
33 const val ASSUME_TYPE_VARS_EXTEND_OBJECT = false
34 
35 class TextTypeItem(
36     val codebase: TextCodebase,
37     val type: String
38 ) : TypeItem {
39 
toStringnull40     override fun toString(): String = type
41 
42     override fun toErasedTypeString(context: Item?): String {
43         return toTypeString(
44             outerAnnotations = false,
45             innerAnnotations = false,
46             erased = true,
47             kotlinStyleNulls = false,
48             context = context
49         )
50     }
51 
toTypeStringnull52     override fun toTypeString(
53         outerAnnotations: Boolean,
54         innerAnnotations: Boolean,
55         erased: Boolean,
56         kotlinStyleNulls: Boolean,
57         context: Item?,
58         filter: Predicate<Item>?
59     ): String {
60         val typeString = toTypeString(type, outerAnnotations, innerAnnotations, erased, context)
61 
62         if (innerAnnotations && kotlinStyleNulls && !primitive && context != null) {
63             var nullable: Boolean? = AnnotationItem.getImplicitNullness(context)
64 
65             if (nullable == null) {
66                 for (annotation in context.modifiers.annotations()) {
67                     if (annotation.isNullable()) {
68                         nullable = true
69                     } else if (annotation.isNonNull()) {
70                         nullable = false
71                     }
72                 }
73             }
74             when (nullable) {
75                 null -> return "$typeString!"
76                 true -> return "$typeString?"
77                 // else: non-null: nothing to add
78             }
79         }
80         return typeString
81     }
82 
asClassnull83     override fun asClass(): ClassItem? {
84         if (primitive) {
85             return null
86         }
87         val cls = run {
88             val erased = toErasedTypeString()
89             // Also chop off array dimensions
90             val index = erased.indexOf('[')
91             if (index != -1) {
92                 erased.substring(0, index)
93             } else {
94                 erased
95             }
96         }
97         return codebase.getOrCreateClass(cls)
98     }
99 
qualifiedTypeNamenull100     fun qualifiedTypeName(): String = type
101 
102     override fun equals(other: Any?): Boolean {
103         if (this === other) return true
104 
105         return when (other) {
106             // Note: when we support type-use annotations, this is not safe: there could be a string
107             // literal inside which is significant
108             is TextTypeItem -> TypeItem.equalsWithoutSpace(toString(), other.toString())
109             is TypeItem -> {
110                 val thisString = toTypeString()
111                 val otherString = other.toTypeString()
112                 if (TypeItem.equalsWithoutSpace(thisString, otherString)) {
113                     return true
114                 }
115                 if (thisString.startsWith(JAVA_LANG_PREFIX) && thisString.endsWith(otherString) &&
116                     thisString.length == otherString.length + JAVA_LANG_PREFIX.length
117                 ) {
118                     // When reading signature files, it's sometimes ambiguous whether a name
119                     // references a java.lang. implicit class or a type parameter.
120                     return true
121                 }
122 
123                 return false
124             }
125             else -> false
126         }
127     }
128 
hashCodenull129     override fun hashCode(): Int {
130         return qualifiedTypeName().hashCode()
131     }
132 
arrayDimensionsnull133     override fun arrayDimensions(): Int {
134         val type = toErasedTypeString()
135         var dimensions = 0
136         for (c in type) {
137             if (c == '[') {
138                 dimensions++
139             }
140         }
141         return dimensions
142     }
143 
findTypeVariableBoundsnull144     private fun findTypeVariableBounds(typeParameterList: TypeParameterList, name: String): List<ClassItem> {
145         for (p in typeParameterList.typeParameters()) {
146             if (p.simpleName() == name) {
147                 val bounds = p.bounds()
148                 if (bounds.isNotEmpty()) {
149                     return bounds
150                 }
151             }
152         }
153 
154         return emptyList()
155     }
156 
findTypeVariableBoundsnull157     private fun findTypeVariableBounds(context: Item?, name: String): List<ClassItem> {
158         if (context is MethodItem) {
159             val bounds = findTypeVariableBounds(context.typeParameterList(), name)
160             if (bounds.isNotEmpty()) {
161                 return bounds
162             }
163             return findTypeVariableBounds(context.containingClass().typeParameterList(), name)
164         } else if (context is ClassItem) {
165             return findTypeVariableBounds(context.typeParameterList(), name)
166         }
167 
168         return emptyList()
169     }
170 
asTypeParameternull171     override fun asTypeParameter(context: MemberItem?): TypeParameterItem? {
172         return if (isLikelyTypeParameter(toTypeString())) {
173             val typeParameter =
174                 TextTypeParameterItem.create(codebase, context as? TypeParameterListOwner, toTypeString())
175 
176             if (context != null && typeParameter.bounds().isEmpty()) {
177                 val bounds = findTypeVariableBounds(context, typeParameter.simpleName())
178                 if (bounds.isNotEmpty()) {
179                     val filtered = bounds.filter { !it.isJavaLangObject() }
180                     if (filtered.isNotEmpty()) {
181                         return TextTypeParameterItem.create(
182                             codebase,
183                             context as? TypeParameterListOwner,
184                             toTypeString(),
185                             bounds
186                         )
187                     }
188                 }
189             }
190 
191             typeParameter
192         } else {
193             null
194         }
195     }
196 
197     override val primitive: Boolean
198         get() = isPrimitive(type)
199 
typeArgumentClassesnull200     override fun typeArgumentClasses(): List<ClassItem> = codebase.unsupported()
201 
202     override fun convertType(replacementMap: Map<String, String>?, owner: Item?): TypeItem {
203         return TextTypeItem(codebase, convertTypeString(replacementMap))
204     }
205 
markRecentnull206     override fun markRecent() = codebase.unsupported()
207 
208     override fun scrubAnnotations() = codebase.unsupported()
209 
210     companion object {
211         // heuristic to guess if a given type parameter is a type variable
212         fun isLikelyTypeParameter(typeString: String): Boolean {
213             val first = typeString[0]
214             if (!Character.isUpperCase((first)) && first != '_') {
215                 // This rules out primitives which otherwise don't have
216                 return false
217             }
218             for (c in typeString) {
219                 if (c == '.') {
220                     // This rules out qualified class names
221                     return false
222                 }
223                 if (c == ' ' || c == '[' || c == '<') {
224                     return true
225                 }
226                 // I'd like to check for all uppercase here but there are APIs which
227                 // violate this, such as AsyncTask where the type variable names include "Result"
228             }
229 
230             return true
231         }
232 
233         fun toTypeString(
234             type: String,
235             outerAnnotations: Boolean,
236             innerAnnotations: Boolean,
237             erased: Boolean,
238             context: Item? = null
239         ): String {
240             return if (erased) {
241                 val raw = eraseTypeArguments(type)
242                 val concrete = substituteTypeParameters(raw, context)
243                 if (outerAnnotations && innerAnnotations) {
244                     concrete
245                 } else {
246                     eraseAnnotations(concrete, outerAnnotations, innerAnnotations)
247                 }
248             } else {
249                 if (outerAnnotations && innerAnnotations) {
250                     type
251                 } else {
252                     eraseAnnotations(type, outerAnnotations, innerAnnotations)
253                 }
254             }
255         }
256 
257         private fun substituteTypeParameters(s: String, context: Item?): String {
258             if (context is TypeParameterListOwner) {
259                 var end = s.indexOf('[')
260                 if (end == -1) {
261                     end = s.length
262                 }
263                 if (s[0].isUpperCase() && s.lastIndexOf('.', end) == -1) {
264                     val v = s.substring(0, end)
265                     val parameter = context.resolveParameter(v)
266                     if (parameter != null) {
267                         val bounds = parameter.bounds()
268                         if (bounds.isNotEmpty()) {
269                             return bounds.first().qualifiedName() + s.substring(end)
270                         }
271                         @Suppress("ConstantConditionIf")
272                         if (ASSUME_TYPE_VARS_EXTEND_OBJECT) {
273                             return JAVA_LANG_OBJECT + s.substring(end)
274                         }
275                     }
276                 }
277             }
278 
279             return s
280         }
281 
282         fun eraseTypeArguments(s: String): String {
283             val index = s.indexOf('<')
284             if (index != -1) {
285                 var balance = 0
286                 for (i in index..s.length) {
287                     val c = s[i]
288                     if (c == '<') {
289                         balance++
290                     } else if (c == '>') {
291                         balance--
292                         if (balance == 0) {
293                             return if (i == s.length - 1) {
294                                 s.substring(0, index)
295                             } else {
296                                 s.substring(0, index) + s.substring(i + 1)
297                             }
298                         }
299                     }
300                 }
301 
302                 return s.substring(0, index)
303             }
304             return s
305         }
306 
307         private fun eraseAnnotations(type: String, outer: Boolean, inner: Boolean): String {
308             if (type.indexOf('@') == -1) {
309                 return type
310             }
311 
312             assert(inner || !outer) // Can't supply outer=true,inner=false
313 
314             // Assumption: top level annotations appear first
315             val length = type.length
316             var max = if (!inner)
317                 length
318             else {
319                 val space = type.indexOf(' ')
320                 val generics = type.indexOf('<')
321                 val first = if (space != -1) {
322                     if (generics != -1) {
323                         Math.min(space, generics)
324                     } else {
325                         space
326                     }
327                 } else {
328                     generics
329                 }
330                 if (first != -1) {
331                     first
332                 } else {
333                     length
334                 }
335             }
336 
337             var s = type
338             while (true) {
339                 val index = s.indexOf('@')
340                 if (index == -1 || index >= max) {
341                     break
342                 }
343 
344                 // Find end
345                 val end = findAnnotationEnd(s, index + 1)
346                 val oldLength = s.length
347                 s = s.substring(0, index).trim() + s.substring(end).trim()
348                 val newLength = s.length
349                 val removed = oldLength - newLength
350                 max -= removed
351             }
352 
353             // Sometimes we have a second type after the max, such as
354             // @androidx.annotation.NonNull java.lang.reflect.@androidx.annotation.NonNull TypeVariable<...>
355             for (i in 0 until s.length) {
356                 val c = s[i]
357                 if (Character.isJavaIdentifierPart(c) || c == '.') {
358                     continue
359                 } else if (c == '@') {
360                     // Found embedded annotation within the type
361                     val end = findAnnotationEnd(s, i + 1)
362                     if (end == -1 || end == length) {
363                         break
364                     }
365 
366                     s = s.substring(0, i).trim() + s.substring(end).trim()
367                     break
368                 } else {
369                     break
370                 }
371             }
372 
373             return s
374         }
375 
376         private fun findAnnotationEnd(type: String, start: Int): Int {
377             var index = start
378             val length = type.length
379             var balance = 0
380             while (index < length) {
381                 val c = type[index]
382                 if (c == '(') {
383                     balance++
384                 } else if (c == ')') {
385                     balance--
386                     if (balance == 0) {
387                         return index + 1
388                     }
389                 } else if (c == '.') {
390                 } else if (Character.isJavaIdentifierPart(c)) {
391                 } else if (balance == 0) {
392                     break
393                 }
394                 index++
395             }
396             return index
397         }
398 
399         fun isPrimitive(type: String): Boolean {
400             return when (type) {
401                 "byte", "char", "double", "float", "int", "long", "short", "boolean", "void", "null" -> true
402                 else -> false
403             }
404         }
405     }
406 }