1 /*
<lambda>null2  * Copyright (C) 2023 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.model.AnnotationItem
20 import com.android.tools.metalava.model.ArrayTypeItem
21 import com.android.tools.metalava.model.BaseTypeVisitor
22 import com.android.tools.metalava.model.ClassTypeItem
23 import com.android.tools.metalava.model.Codebase
24 import com.android.tools.metalava.model.DefaultAnnotationItem
25 import com.android.tools.metalava.model.JAVA_LANG_OBJECT
26 import com.android.tools.metalava.model.PrimitiveTypeItem
27 import com.android.tools.metalava.model.ReferenceTypeItem
28 import com.android.tools.metalava.model.TypeArgumentTypeItem
29 import com.android.tools.metalava.model.TypeItem
30 import com.android.tools.metalava.model.TypeModifiers
31 import com.android.tools.metalava.model.TypeNullability
32 import com.android.tools.metalava.model.TypeParameterScope
33 import com.android.tools.metalava.model.TypeVisitor
34 import com.android.tools.metalava.model.VariableTypeItem
35 import com.android.tools.metalava.model.WildcardTypeItem
36 import com.android.tools.metalava.model.type.ContextNullability
37 import com.android.tools.metalava.model.type.DefaultArrayTypeItem
38 import com.android.tools.metalava.model.type.DefaultClassTypeItem
39 import com.android.tools.metalava.model.type.DefaultPrimitiveTypeItem
40 import com.android.tools.metalava.model.type.DefaultTypeModifiers
41 import com.android.tools.metalava.model.type.DefaultVariableTypeItem
42 import com.android.tools.metalava.model.type.DefaultWildcardTypeItem
43 import kotlin.collections.HashMap
44 
45 /** Parses and caches types for a [codebase]. */
46 internal class TextTypeParser(val codebase: Codebase, val kotlinStyleNulls: Boolean = false) {
47 
48     /**
49      * The cache key, incorporates some information from [ContextNullability] and [kotlinStyleNulls]
50      * as well as the type string as they can all affect the created [TypeItem].
51      *
52      * e.g. [ContextNullability.forceNonNull] will cause the type to always be
53      * [TypeNullability.NONNULL] even if [kotlinStyleNulls] is `false` which would normally cause it
54      * to be [TypeNullability.PLATFORM]. However, when [kotlinStyleNulls] is `true` then there is no
55      * difference between [ContextNullability.forceNonNull] and [ContextNullability.none] as they
56      * will both cause a class type with no nullability suffix to be treated as
57      * [TypeNullability.NONNULL].
58      *
59      * That information is encapsulated in the [forceClassToBeNonNull] property.
60      */
61     private data class Key(val forceClassToBeNonNull: Boolean, val type: String)
62 
63     /** The cache from [Key] to [CacheEntry]. */
64     private val typeCache = HashMap<Key, CacheEntry>()
65 
66     internal var requests = 0
67     internal var cacheSkip = 0
68     internal var cacheHit = 0
69     internal var cacheSize = 0
70 
71     /** A [TypeItem] representing `java.lang.Object`, suitable for general use. */
72     private val objectType: ReferenceTypeItem
73         get() = cachedParseType(JAVA_LANG_OBJECT, TypeParameterScope.empty) as ReferenceTypeItem
74 
75     /**
76      * Creates or retrieves from the cache a [TypeItem] representing [type], in the context of the
77      * type parameters from [typeParameterScope], if applicable.
78      */
79     fun obtainTypeFromString(
80         type: String,
81         typeParameterScope: TypeParameterScope,
82         contextNullability: ContextNullability = ContextNullability.none,
83     ): TypeItem = cachedParseType(type, typeParameterScope, emptyList(), contextNullability)
84 
85     /**
86      * Creates or retrieves from the cache a [TypeItem] representing [type], in the context of the
87      * type parameters from [typeParameterScope], if applicable.
88      *
89      * Used internally, as it has an extra [annotations] parameter that allows the annotations on
90      * array components to be correctly associated with the correct component. They are optional
91      * leading type-use annotations that have already been removed from the arrays type string.
92      */
93     private fun cachedParseType(
94         type: String,
95         typeParameterScope: TypeParameterScope,
96         annotations: List<AnnotationItem> = emptyList(),
97         contextNullability: ContextNullability = ContextNullability.none,
98     ): TypeItem {
99         requests++
100 
101         // Class types used as super types, i.e. in an extends or implements list are forced to be
102         // [TypeNullability.NONNULL], just as they would be if kotlinStyleNulls was true. Use the
103         // same cache key for both so that they reuse cached types where possible.
104         val forceClassToBeNonNull =
105             contextNullability.forcedNullability == TypeNullability.NONNULL || kotlinStyleNulls
106 
107         // Don't use the cache when there are type-use annotations not contained in the string.
108         return if (annotations.isEmpty()) {
109             val key = Key(forceClassToBeNonNull, type)
110 
111             // Get the cache entry for the supplied type and forceClassToBeNonNull.
112             val result =
113                 typeCache.computeIfAbsent(key) { CacheEntry(it.type, it.forceClassToBeNonNull) }
114 
115             // Get the appropriate [TypeItem], creating one if necessary.
116             result.getTypeItem(typeParameterScope)
117         } else {
118             cacheSkip++
119             parseType(type, typeParameterScope, annotations, forceClassToBeNonNull)
120         }
121     }
122 
123     /** Converts the [type] to a [TypeItem] in the context of the [typeParameterScope]. */
124     private fun parseType(
125         type: String,
126         typeParameterScope: TypeParameterScope,
127         annotations: List<AnnotationItem>,
128         // Forces a [ClassTypeItem] to have [TypeNullability.NONNULL]
129         forceClassToBeNonNull: Boolean = false,
130     ): TypeItem {
131         val (unannotated, annotationsFromString) = trimLeadingAnnotations(type)
132         val allAnnotations = annotations + annotationsFromString
133         val (withoutNullability, nullability) =
134             splitNullabilitySuffix(
135                 unannotated,
136                 // If forceClassToBeNonNull is true then a plain class type without any nullability
137                 // suffix must be treated as if it was not null, which is just how it would be
138                 // treated when kotlinStyleNulls is true. So, pretend that kotlinStyleNulls is true.
139                 kotlinStyleNulls || forceClassToBeNonNull
140             )
141         val trimmed = withoutNullability.trim()
142 
143         // Figure out what kind of type this is.
144         //
145         // Start with variable as the type parameter scope allows us to determine whether something
146         // is a type parameter or not. Also, if a type parameter has the same name as a primitive
147         // type (possible in Kotlin, but not Java) then it will be treated as a type parameter not a
148         // primitive.
149         //
150         // Then try parsing as a primitive as while Kotlin classes can shadow primitive types
151         // they would need to be fully qualified.
152         return asVariable(trimmed, typeParameterScope, allAnnotations, nullability)
153             ?: asPrimitive(type, trimmed, allAnnotations, nullability)
154             // Try parsing as a wildcard before trying to parse as an array.
155             // `? extends java.lang.String[]` should be parsed as a wildcard with an array bound,
156             // not as an array of wildcards, for consistency with how this would be compiled.
157             ?: asWildcard(trimmed, typeParameterScope, allAnnotations, nullability)
158             // Try parsing as an array.
159             ?: asArray(trimmed, allAnnotations, nullability, typeParameterScope)
160             // If it isn't anything else, parse the type as a class.
161             ?: asClass(trimmed, typeParameterScope, allAnnotations, nullability)
162     }
163 
164     /**
165      * Try parsing [type] as a primitive. This will return a non-null [PrimitiveTypeItem] if [type]
166      * exactly matches a primitive name.
167      *
168      * [type] should have annotations and nullability markers stripped, with [original] as the
169      * complete annotated type. Once annotations are properly handled (b/300081840), preserving
170      * [original] won't be necessary.
171      */
172     private fun asPrimitive(
173         original: String,
174         type: String,
175         annotations: List<AnnotationItem>,
176         nullability: TypeNullability?
177     ): PrimitiveTypeItem? {
178         val kind =
179             when (type) {
180                 "byte" -> PrimitiveTypeItem.Primitive.BYTE
181                 "char" -> PrimitiveTypeItem.Primitive.CHAR
182                 "double" -> PrimitiveTypeItem.Primitive.DOUBLE
183                 "float" -> PrimitiveTypeItem.Primitive.FLOAT
184                 "int" -> PrimitiveTypeItem.Primitive.INT
185                 "long" -> PrimitiveTypeItem.Primitive.LONG
186                 "short" -> PrimitiveTypeItem.Primitive.SHORT
187                 "boolean" -> PrimitiveTypeItem.Primitive.BOOLEAN
188                 "void" -> PrimitiveTypeItem.Primitive.VOID
189                 else -> return null
190             }
191         if (nullability != null && nullability != TypeNullability.NONNULL) {
192             throw ApiParseException("Invalid nullability suffix on primitive: $original")
193         }
194         return DefaultPrimitiveTypeItem(modifiers(annotations, TypeNullability.NONNULL), kind)
195     }
196 
197     /**
198      * Try parsing [type] as an array. This will return a non-null [ArrayTypeItem] if [type] ends
199      * with `[]` or `...`.
200      *
201      * The context [typeParameterScope] are used to parse the component type of the array.
202      */
203     private fun asArray(
204         type: String,
205         componentAnnotations: List<AnnotationItem>,
206         nullability: TypeNullability?,
207         typeParameterScope: TypeParameterScope
208     ): ArrayTypeItem? {
209         // Check if this is a regular array or varargs.
210         val (inner, varargs) =
211             if (type.endsWith("...")) {
212                 Pair(type.dropLast(3), true)
213             } else if (type.endsWith("[]")) {
214                 Pair(type.dropLast(2), false)
215             } else {
216                 return null
217             }
218 
219         // Create lists of the annotations and nullability markers for each dimension of the array.
220         // These are in separate lists because annotations appear in the type string in order from
221         // outermost array annotations to innermost array annotations (for `T @A [] @B [] @ C[]`,
222         // `@A` applies to the three-dimensional array, `@B` applies to the inner two-dimensional
223         // arrays, and `@C` applies to the inner one-dimensional arrays), while nullability markers
224         // appear in order from the innermost array nullability to the outermost array nullability
225         // (for `T[]![]?[]`, the three-dimensional array has no nullability marker, the inner
226         // two-dimensional arrays have `?` as the nullability marker, and the innermost arrays have
227         // `!` as a nullability marker.
228         val allAnnotations = mutableListOf<List<AnnotationItem>>()
229         // The nullability marker for the outer array is already known, include it in the list.
230         val allNullability = mutableListOf(nullability)
231 
232         // Remove annotations from the end of the string, add them to the list.
233         var annotationsResult = trimTrailingAnnotations(inner)
234         var componentString = annotationsResult.first
235         allAnnotations.add(annotationsResult.second)
236 
237         // Remove nullability marker from the component type, but don't add it to the list yet, as
238         // it might not be an array.
239         var nullabilityResult = splitNullabilitySuffix(componentString, kotlinStyleNulls)
240         componentString = nullabilityResult.first
241         var componentNullability = nullabilityResult.second
242 
243         // Work through all layers of arrays to get to the inner component type.
244         // Inner arrays can't be varargs.
245         while (componentString.endsWith("[]")) {
246             // The component is an array, add the nullability to the list.
247             allNullability.add(componentNullability)
248 
249             // Remove annotations from the end of the string, add them to the list.
250             annotationsResult = trimTrailingAnnotations(componentString.removeSuffix("[]"))
251             componentString = annotationsResult.first
252             allAnnotations.add(annotationsResult.second)
253 
254             // Remove nullability marker from the new component type, but don't add it to the list
255             // yet, as the next component type might not be an array.
256             nullabilityResult = splitNullabilitySuffix(componentString, kotlinStyleNulls)
257             componentString = nullabilityResult.first
258             componentNullability = nullabilityResult.second
259         }
260 
261         // Re-add the component's nullability suffix when parsing the component type, and include
262         // the leading annotations already removed from the type string.
263         componentString += componentNullability?.suffix.orEmpty()
264         val deepComponentType =
265             cachedParseType(componentString, typeParameterScope, componentAnnotations)
266 
267         // Join the annotations and nullability markers -- as described in the comment above, these
268         // appear in the string in reverse order of each other. The modifiers list will be ordered
269         // from innermost array modifiers to outermost array modifiers.
270         val allModifiers =
271             allAnnotations.zip(allNullability.reversed()).map { (annotations, nullability) ->
272                 modifiers(annotations, nullability)
273             }
274         // The final modifiers are in the list apply to the outermost array.
275         val componentModifiers = allModifiers.dropLast(1)
276         val arrayModifiers = allModifiers.last()
277         // Create the component type of the outermost array by building up the inner component type.
278         val componentType =
279             componentModifiers.fold(deepComponentType) { component, modifiers ->
280                 DefaultArrayTypeItem(modifiers, component, false)
281             }
282 
283         // Create the outer array.
284         return DefaultArrayTypeItem(arrayModifiers, componentType, varargs)
285     }
286 
287     /**
288      * Try parsing [type] as a wildcard. This will return a non-null [WildcardTypeItem] if [type]
289      * begins with `?`.
290      *
291      * The context [typeParameterScope] are needed to parse the bounds of the wildcard.
292      *
293      * [type] should have annotations and nullability markers stripped.
294      */
295     private fun asWildcard(
296         type: String,
297         typeParameterScope: TypeParameterScope,
298         annotations: List<AnnotationItem>,
299         nullability: TypeNullability?
300     ): WildcardTypeItem? {
301         // See if this is a wildcard
302         if (!type.startsWith("?")) return null
303 
304         // Unbounded wildcard type: there is an implicit Object extends bound
305         if (type == "?")
306             return DefaultWildcardTypeItem(
307                 modifiers(annotations, TypeNullability.UNDEFINED),
308                 objectType,
309                 null,
310             )
311 
312         // If there's a bound, the nullability suffix applies there instead.
313         val bound = type.substring(2) + nullability?.suffix.orEmpty()
314         return if (bound.startsWith("extends")) {
315             val extendsBound = bound.substring(8)
316             DefaultWildcardTypeItem(
317                 modifiers(annotations, TypeNullability.UNDEFINED),
318                 getWildcardBound(extendsBound, typeParameterScope),
319                 null,
320             )
321         } else if (bound.startsWith("super")) {
322             val superBound = bound.substring(6)
323             DefaultWildcardTypeItem(
324                 modifiers(annotations, TypeNullability.UNDEFINED),
325                 // All wildcards have an implicit Object extends bound
326                 objectType,
327                 getWildcardBound(superBound, typeParameterScope),
328             )
329         } else {
330             throw ApiParseException(
331                 "Type starts with \"?\" but doesn't appear to be wildcard: $type"
332             )
333         }
334     }
335 
336     private fun getWildcardBound(bound: String, typeParameterScope: TypeParameterScope) =
337         cachedParseType(bound, typeParameterScope) as ReferenceTypeItem
338 
339     /**
340      * Try parsing [type] as a type variable. This will return a non-null [VariableTypeItem] if
341      * [type] matches a parameter from [typeParameterScope].
342      *
343      * [type] should have annotations and nullability markers stripped.
344      */
345     private fun asVariable(
346         type: String,
347         typeParameterScope: TypeParameterScope,
348         annotations: List<AnnotationItem>,
349         nullability: TypeNullability?
350     ): VariableTypeItem? {
351         val param = typeParameterScope.findTypeParameter(type) ?: return null
352         return DefaultVariableTypeItem(modifiers(annotations, nullability), param)
353     }
354 
355     /**
356      * Parse the [type] as a class. This function will always return a non-null [ClassTypeItem], so
357      * it should only be used when it is certain that [type] is not a different kind of type.
358      *
359      * The context [typeParameterScope] are used to parse the parameters of the class type.
360      *
361      * [type] should have annotations and nullability markers stripped.
362      */
363     private fun asClass(
364         type: String,
365         typeParameterScope: TypeParameterScope,
366         annotations: List<AnnotationItem>,
367         nullability: TypeNullability?
368     ): ClassTypeItem {
369         return createClassType(type, null, typeParameterScope, annotations, nullability)
370     }
371 
372     /**
373      * Creates a class name for the class represented by [type] with optional [outerClassType].
374      *
375      * For instance, `test.pkg.Outer<P1>` would be the [outerClassType] when parsing `Inner<P2>`
376      * from the [original] type `test.pkg.Outer<P1>.Inner<P2>`.
377      */
378     private fun createClassType(
379         type: String,
380         outerClassType: ClassTypeItem?,
381         typeParameterScope: TypeParameterScope,
382         annotations: List<AnnotationItem>,
383         nullability: TypeNullability?
384     ): ClassTypeItem {
385         val (name, afterName, classAnnotations) = splitClassType(type)
386 
387         val qualifiedName =
388             if (outerClassType != null) {
389                 // This is an inner type, add the prefix of the outer name
390                 "${outerClassType.qualifiedName}.$name"
391             } else if (!name.contains('.')) {
392                 // Reverse the effect of [TypeItem.stripJavaLangPrefix].
393                 "java.lang.$name"
394             } else {
395                 name
396             }
397 
398         val (argumentStrings, remainder) = typeParameterStringsWithRemainder(afterName)
399         val arguments =
400             argumentStrings.map { cachedParseType(it, typeParameterScope) as TypeArgumentTypeItem }
401         // If this is an outer class type (there's a remainder), call it non-null and don't apply
402         // the leading annotations (they belong to the inner class type).
403         val classModifiers =
404             if (remainder != null) {
405                 modifiers(classAnnotations, TypeNullability.NONNULL)
406             } else {
407                 modifiers(classAnnotations + annotations, nullability)
408             }
409         val classType =
410             DefaultClassTypeItem(codebase, classModifiers, qualifiedName, arguments, outerClassType)
411 
412         if (remainder != null) {
413             if (!remainder.startsWith('.')) {
414                 throw ApiParseException(
415                     "Could not parse type `$type`. Found unexpected string after type parameters: $remainder"
416                 )
417             }
418             // This is an inner class type, recur with the new outer class
419             return createClassType(
420                 remainder.substring(1),
421                 classType,
422                 typeParameterScope,
423                 annotations,
424                 nullability
425             )
426         }
427 
428         return classType
429     }
430 
431     private fun modifiers(
432         annotations: List<AnnotationItem>,
433         nullability: TypeNullability?
434     ): TypeModifiers {
435         return DefaultTypeModifiers.create(
436             annotations,
437             nullability,
438             "Type modifiers created by the text model are immutable because they are cached",
439         )
440     }
441 
442     /**
443      * Removes all annotations at the beginning of the type, returning the trimmed type and list of
444      * annotations.
445      */
446     fun trimLeadingAnnotations(type: String): Pair<String, List<AnnotationItem>> {
447         val annotations = mutableListOf<AnnotationItem>()
448         var trimmed = type.trim()
449         while (trimmed.startsWith('@')) {
450             val end = findAnnotationEnd(trimmed, 1)
451             val annotationSource = trimmed.substring(0, end).trim()
452             annotations.add(DefaultAnnotationItem.create(codebase, annotationSource))
453             trimmed = trimmed.substring(end).trim()
454         }
455         return Pair(trimmed, annotations)
456     }
457 
458     /**
459      * Removes all annotations at the end of the [type], returning the trimmed type and list of
460      * annotations. This is for use with arrays where annotations applying to the array type go
461      * after the component type, for instance `String @A []`. The input [type] should **not**
462      * include the array suffix (`[]` or `...`).
463      */
464     fun trimTrailingAnnotations(type: String): Pair<String, List<AnnotationItem>> {
465         // The simple way to implement this would be to work from the end of the string, finding
466         // `@` and removing annotations from the end. However, it is possible for an annotation
467         // string to contain an `@`, so this is not a safe way to remove the annotations.
468         // Instead, this finds all annotations starting from the beginning of the string, then
469         // works backwards to find which ones are the trailing annotations.
470         val allAnnotationIndices = mutableListOf<Pair<Int, Int>>()
471         var trimmed = type.trim()
472 
473         // First find all annotations, saving the first and last index.
474         var currIndex = 0
475         while (currIndex < trimmed.length) {
476             if (trimmed[currIndex] == '@') {
477                 val endIndex = findAnnotationEnd(trimmed, currIndex + 1)
478                 allAnnotationIndices.add(Pair(currIndex, endIndex))
479                 currIndex = endIndex + 1
480             } else {
481                 currIndex++
482             }
483         }
484 
485         val annotations = mutableListOf<AnnotationItem>()
486         // Go through all annotations from the back, seeing if they're at the end of the string.
487         for ((start, end) in allAnnotationIndices.reversed()) {
488             // This annotation isn't at the end, so we've hit the last trailing annotation
489             if (end < trimmed.length) {
490                 break
491             }
492             val annotationSource = trimmed.substring(start)
493             annotations.add(DefaultAnnotationItem.create(codebase, annotationSource))
494             // Cut this annotation off, so now the next one can end at the last index.
495             trimmed = trimmed.substring(0, start).trim()
496         }
497         return Pair(trimmed, annotations.reversed())
498     }
499 
500     /**
501      * Given [type] which represents a class, splits the string into the qualified name of the
502      * class, the remainder of the type string, and a list of type-use annotations. The remainder of
503      * the type string might be the type parameter list, inner class names, or a combination
504      *
505      * For `java.util.@A @B List<java.lang.@C String>`, returns the triple ("java.util.List",
506      * "<java.lang.@C String", listOf("@A", "@B")).
507      *
508      * For `test.pkg.Outer.Inner`, returns the triple ("test.pkg.Outer", ".Inner", emptyList()).
509      *
510      * For `test.pkg.@test.pkg.A Outer<P1>.@test.pkg.B Inner<P2>`, returns the triple
511      * ("test.pkg.Outer", "<P1>.@test.pkg.B Inner<P2>", listOf("@test.pkg.A")).
512      */
513     fun splitClassType(type: String): Triple<String, String?, List<AnnotationItem>> {
514         // The constructed qualified type name
515         var name = ""
516         // The part of the type which still needs to be parsed
517         var remaining = type.trim()
518         // The annotations of the type, may be set later
519         var annotations = emptyList<AnnotationItem>()
520 
521         var dotIndex = remaining.indexOf('.')
522         var paramIndex = remaining.indexOf('<')
523         var annotationIndex = remaining.indexOf('@')
524 
525         // Find which of '.', '<', or '@' comes first, if any
526         var minIndex = minIndex(dotIndex, paramIndex, annotationIndex)
527         while (minIndex != null) {
528             when (minIndex) {
529                 // '.' is first, the next part is part of the qualified class name.
530                 dotIndex -> {
531                     val nextNameChunk = remaining.substring(0, dotIndex)
532                     name += nextNameChunk
533                     remaining = remaining.substring(dotIndex)
534                     // Assumes that package names are all lower case and class names will have
535                     // an upper class character (the [START_WITH_UPPER] API lint check should
536                     // make this a safe assumption). If the name is a class name, we've found
537                     // the complete class name, return.
538                     if (nextNameChunk.any { it.isUpperCase() }) {
539                         return Triple(name, remaining, annotations)
540                     }
541                 }
542                 // '<' is first, the end of the class name has been reached.
543                 paramIndex -> {
544                     name += remaining.substring(0, paramIndex)
545                     remaining = remaining.substring(paramIndex)
546                     return Triple(name, remaining, annotations)
547                 }
548                 // '@' is first, trim all annotations.
549                 annotationIndex -> {
550                     name += remaining.substring(0, annotationIndex)
551                     trimLeadingAnnotations(remaining.substring(annotationIndex)).let {
552                         (first, second) ->
553                         remaining = first
554                         annotations = second
555                     }
556                 }
557             }
558             // Reset indices -- the string may now start with '.' for the next chunk of the name
559             // but this should find the end of the next chunk.
560             dotIndex = remaining.indexOf('.', 1)
561             paramIndex = remaining.indexOf('<')
562             annotationIndex = remaining.indexOf('@')
563             minIndex = minIndex(dotIndex, paramIndex, annotationIndex)
564         }
565         // End of the name reached with no leftover string.
566         name += remaining
567         return Triple(name, null, annotations)
568     }
569 
570     companion object {
571         /**
572          * Splits the Kotlin-style nullability marker off the type string, returning a pair of the
573          * cleaned type string and the nullability suffix.
574          */
575         fun splitNullabilitySuffix(
576             type: String,
577             kotlinStyleNulls: Boolean
578         ): Pair<String, TypeNullability?> {
579             return if (kotlinStyleNulls) {
580                 // Don't interpret the wildcard type `?` as a nullability marker.
581                 if (type == "?") {
582                     Pair(type, TypeNullability.UNDEFINED)
583                 } else if (type.endsWith("?")) {
584                     Pair(type.dropLast(1), TypeNullability.NULLABLE)
585                 } else if (type.endsWith("!")) {
586                     Pair(type.dropLast(1), TypeNullability.PLATFORM)
587                 } else {
588                     Pair(type, TypeNullability.NONNULL)
589                 }
590             } else if (type.length > 1 && type.endsWith("?") || type.endsWith("!")) {
591                 throw ApiParseException(
592                     "Format does not support Kotlin-style null type syntax: $type"
593                 )
594             } else {
595                 Pair(type, null)
596             }
597         }
598 
599         /**
600          * Returns the minimum valid list index from the input, or null if there isn't one. -1 is
601          * not a valid index.
602          */
603         private fun minIndex(vararg index: Int): Int? = index.filter { it != -1 }.minOrNull()
604 
605         /**
606          * Given a string and the index in that string which is the start of an annotation (the
607          * character _after_ the `@`), returns the index of the end of the annotation.
608          */
609         fun findAnnotationEnd(type: String, start: Int): Int {
610             var index = start
611             val length = type.length
612             var balance = 0
613             while (index < length) {
614                 val c = type[index]
615                 if (c == '(') {
616                     balance++
617                 } else if (c == ')') {
618                     balance--
619                     if (balance == 0) {
620                         return index + 1
621                     }
622                 } else if (c != '.' && !Character.isJavaIdentifierPart(c) && balance == 0) {
623                     break
624                 }
625                 index++
626             }
627             return index
628         }
629 
630         /**
631          * Breaks a string representing type parameters into a list of the type parameter strings.
632          *
633          * E.g. `"<A, B, C>"` -> `["A", "B", "C"]` and `"<List<A>, B>"` -> `["List<A>", "B"]`.
634          */
635         fun typeParameterStrings(typeString: String?): List<String> {
636             return typeParameterStringsWithRemainder(typeString).first
637         }
638 
639         /**
640          * Breaks a string representing type parameters into a list of the type parameter strings,
641          * and also returns the remainder of the string after the closing ">".
642          *
643          * E.g. `"<A, B, C>.Inner"` -> `Pair(["A", "B", "C"], ".Inner")`
644          */
645         fun typeParameterStringsWithRemainder(typeString: String?): Pair<List<String>, String?> {
646             val s = typeString ?: return Pair(emptyList(), null)
647             if (!s.startsWith("<")) return Pair(emptyList(), s)
648             val list = mutableListOf<String>()
649             var balance = 0
650             var expect = false
651             var start = 0
652             var i = 0
653             while (i < s.length) {
654                 val c = s[i]
655                 if (c == '<') {
656                     balance++
657                     expect = balance == 1
658                 } else if (c == '>') {
659                     balance--
660                     if (balance == 0) {
661                         add(list, s, start, i)
662                         return if (i == s.length - 1) {
663                             Pair(list, null)
664                         } else {
665                             Pair(list, s.substring(i + 1))
666                         }
667                     }
668                 } else if (c == ',') {
669                     expect =
670                         if (balance == 1) {
671                             add(list, s, start, i)
672                             true
673                         } else {
674                             false
675                         }
676                 } else {
677                     // This is the start of a parameter
678                     if (expect && balance == 1) {
679                         start = i
680                         expect = false
681                     }
682 
683                     if (c == '@') {
684                         // Skip the entire text of the annotation
685                         i = findAnnotationEnd(typeString, i + 1)
686                         continue
687                     }
688                 }
689                 i++
690             }
691             return Pair(list, null)
692         }
693 
694         /**
695          * Adds the substring of [s] from [from] to [to] to the [list], trimming whitespace from the
696          * front.
697          */
698         private fun add(list: MutableList<String>, s: String, from: Int, to: Int) {
699             for (i in from until to) {
700                 if (!Character.isWhitespace(s[i])) {
701                     list.add(s.substring(i, to))
702                     return
703                 }
704             }
705         }
706     }
707 
708     /**
709      * The cache entry, that contains the [TypeItem] that has been produced from the [type] and
710      * [forceClassToBeNonNull] properties.
711      */
712     internal inner class CacheEntry(
713         /** The string type from which the [TypeItem] will be parsed. */
714         private val type: String,
715 
716         /**
717          * Indicates whether an outermost [ClassTypeItem] is forced to be [TypeNullability.NONNULL].
718          *
719          * It is passed into [parseType] and if `true` it will cause the top level class type to be
720          * treated as if it was being parsed when [kotlinStyleNulls] is `true` as that sets
721          * [TypeNullability.NONNULL] by default.
722          */
723         private val forceClassToBeNonNull: Boolean,
724     ) {
725         /**
726          * Map from [TypeParameterScope] to the [TypeItem] created for it.
727          *
728          * The [TypeParameterScope] that will be used to cache a type depends on the unqualified
729          * names used in the type. It will use the closest enclosing scope of the one supplied that
730          * adds at least one type parameter whose name is used in the type.
731          *
732          * See [TypeParameterScope.findSignificantScope].
733          */
734         private val scopeToItem = mutableMapOf<TypeParameterScope, TypeItem>()
735 
736         /**
737          * The set of unqualified names used by [type].
738          *
739          * This is determined solely by the contents of the [type] string and so will be the same
740          * for all [TypeItem]s cached in this entry.
741          *
742          * If this has not been set then no type items have been cached in this entry. It is set the
743          * first time that a [TypeItem] is cached.
744          */
745         private lateinit var unqualifiedNamesInType: Set<String>
746 
747         /** Get the [TypeItem] for this type depending on the setting of [forceClassToBeNonNull]. */
748         fun getTypeItem(typeParameterScope: TypeParameterScope): TypeItem {
749             // If this is not the first time through then check to see if anything suitable has been
750             // cached.
751             val scopeForCachingOrNull =
752                 if (::unqualifiedNamesInType.isInitialized) {
753                     // Find the scope to use for caching this type and then check to see if a
754                     // [TypeItem]
755                     // has been cached for that scope and if so return it. Otherwise, drop out.
756                     typeParameterScope.findSignificantScope(unqualifiedNamesInType).also {
757                         scopeForCaching ->
758                         scopeToItem[scopeForCaching]?.let {
759                             cacheHit++
760                             return it
761                         }
762                     }
763                 } else {
764                     // This is the first time through, so [unqualifiedNamesInType] is not available
765                     // so drop through and initialize later.
766                     null
767                 }
768 
769             // Parse the [type] to produce a [TypeItem].
770             val typeItem = createTypeItem(typeParameterScope)
771             cacheSize++
772 
773             // Find the scope for caching if it was not found above.
774             val scopeForCaching =
775                 scopeForCachingOrNull
776                     ?: let {
777                         // This will only happen if [unqualifiedNamesInType] is uninitialized so
778                         // make sure to initialize it.
779                         unqualifiedNamesInType = unqualifiedNameGatherer.gatherFrom(typeItem)
780 
781                         // Find the scope for caching. It could not be found before because
782                         // [unqualifiedNamesInType] was not initialized.
783                         typeParameterScope.findSignificantScope(unqualifiedNamesInType)
784                     }
785 
786             // Store the type item in the scope selected for caching.
787             scopeToItem[scopeForCaching] = typeItem
788 
789             // Return it.
790             return typeItem
791         }
792 
793         /**
794          * Create a new [TypeItem] for [type] with the given [forceClassToBeNonNull] setting and for
795          * the requested [typeParameterScope].
796          */
797         private fun createTypeItem(typeParameterScope: TypeParameterScope): TypeItem {
798             return parseType(type, typeParameterScope, emptyList(), forceClassToBeNonNull)
799         }
800     }
801 
802     /**
803      * A [TypeVisitor] that will extract all unqualified names from the type.
804      *
805      * These are the names that could be used as a type parameter name and so whose meaning could
806      * change depending on the [TypeParameterScope], i.e. the set of type parameters currently in
807      * scope.
808      */
809     private class UnqualifiedNameGatherer : BaseTypeVisitor() {
810 
811         private val unqualifiedNames = mutableSetOf<String>()
812 
813         override fun visit(primitiveType: PrimitiveTypeItem) {
814             // Primitive type names are added because Kotlin allows them to be shadowed by a type
815             // parameter.
816             unqualifiedNames.add(primitiveType.kind.primitiveName)
817         }
818 
819         override fun visitClassType(classType: ClassTypeItem) {
820             // Classes in java.lang package can be represented in the type without the leading
821             // package, all other types must be fully qualified. At this point it is not clear
822             // whether the type used in the input type string was qualified or not as the package
823             // has been prepended so this assumes that they all are just to be on the safe side.
824             // It is only for legacy reasons that all `java.lang` package prefixes are stripped
825             // when generating the API signature files. See b/324047248.
826             val name = classType.qualifiedName
827             if (!name.contains('.')) {
828                 unqualifiedNames.add(name)
829             } else {
830                 val trimmed = TypeItem.stripJavaLangPrefix(name)
831                 if (trimmed != name) {
832                     unqualifiedNames.add(trimmed)
833                 }
834             }
835         }
836 
837         override fun visitVariableType(variableType: VariableTypeItem) {
838             unqualifiedNames.add(variableType.name)
839         }
840 
841         /** Gather the names from [typeItem] returning an immutable set of the unqualified names. */
842         fun gatherFrom(typeItem: TypeItem): Set<String> {
843             unqualifiedNames.clear()
844             typeItem.accept(this)
845             return unqualifiedNames.toSet()
846         }
847     }
848 
849     /**
850      * An instance of [UnqualifiedNameGatherer] used for gathering all the unqualified names from
851      * all the [TypeItem]s cached by this.
852      */
853     private val unqualifiedNameGatherer = UnqualifiedNameGatherer()
854 }
855