1 /*
<lambda>null2  * Copyright (C) 2020 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 package com.android.tools.metalava.model.text
17 
18 import com.android.tools.metalava.model.ANDROIDX_NONNULL
19 import com.android.tools.metalava.model.ANDROIDX_NULLABLE
20 import com.android.tools.metalava.model.AnnotationItem
21 import com.android.tools.metalava.model.AnnotationItem.Companion.unshortenAnnotation
22 import com.android.tools.metalava.model.AnnotationManager
23 import com.android.tools.metalava.model.ArrayTypeItem
24 import com.android.tools.metalava.model.ClassItem
25 import com.android.tools.metalava.model.ClassKind
26 import com.android.tools.metalava.model.ClassResolver
27 import com.android.tools.metalava.model.ClassTypeItem
28 import com.android.tools.metalava.model.Codebase
29 import com.android.tools.metalava.model.DefaultAnnotationItem
30 import com.android.tools.metalava.model.DefaultModifierList
31 import com.android.tools.metalava.model.DefaultTypeParameterList
32 import com.android.tools.metalava.model.ExceptionTypeItem
33 import com.android.tools.metalava.model.Item
34 import com.android.tools.metalava.model.JAVA_LANG_DEPRECATED
35 import com.android.tools.metalava.model.MetalavaApi
36 import com.android.tools.metalava.model.ParameterItem
37 import com.android.tools.metalava.model.PrimitiveTypeItem
38 import com.android.tools.metalava.model.PrimitiveTypeItem.Primitive
39 import com.android.tools.metalava.model.TypeItem
40 import com.android.tools.metalava.model.TypeNullability
41 import com.android.tools.metalava.model.TypeParameterList
42 import com.android.tools.metalava.model.VisibilityLevel
43 import com.android.tools.metalava.model.javaUnescapeString
44 import com.android.tools.metalava.model.noOpAnnotationManager
45 import com.android.tools.metalava.model.type.MethodFingerprint
46 import com.android.tools.metalava.reporter.FileLocation
47 import java.io.File
48 import java.io.IOException
49 import java.io.InputStream
50 import java.io.StringReader
51 import java.nio.file.Path
52 import java.util.IdentityHashMap
53 import kotlin.text.Charsets.UTF_8
54 
55 /** Encapsulates information needed to process a signature file. */
56 data class SignatureFile(
57     /** The underlying signature [File]. */
58     val file: File,
59 
60     /**
61      * Indicates whether [file] is for the current API surface, i.e. the one that is being created.
62      *
63      * This will be stored in [Item.emit].
64      */
65     val forCurrentApiSurface: Boolean = true,
66 ) {
67     companion object {
68         /** Create a [SignatureFile] from a [File]. */
69         fun fromFile(file: File) = SignatureFile(file)
70 
71         /** Create a list of [SignatureFile]s from a list of [File]s. */
72         fun fromFiles(files: List<File>): List<SignatureFile> =
73             files.map {
74                 SignatureFile(
75                     it,
76                 )
77             }
78     }
79 }
80 
81 @MetalavaApi
82 class ApiFile
83 private constructor(
84     private val codebase: TextCodebase,
85     private val formatForLegacyFiles: FileFormat?,
86 ) {
87 
88     /**
89      * Provides support for parsing and caching [TypeItem]s.
90      *
91      * Defer creation until after the first file has been read and [kotlinStyleNulls] has been set
92      * to a non-null value to ensure that it picks up the correct setting of [kotlinStyleNulls].
93      */
94     private val typeParser by
<lambda>null95         lazy(LazyThreadSafetyMode.NONE) { TextTypeParser(codebase, kotlinStyleNulls!!) }
96 
97     /**
98      * Provides support for creating [TypeItem]s for specific uses.
99      *
100      * Defer creation as it depends on [typeParser].
101      */
102     private val globalTypeItemFactory by
<lambda>null103         lazy(LazyThreadSafetyMode.NONE) { TextTypeItemFactory(codebase, typeParser) }
104 
105     /**
106      * Whether types should be interpreted to be in Kotlin format (e.g. ? suffix means nullable, !
107      * suffix means unknown, and absence of a suffix means not nullable.
108      *
109      * Updated based on the header of the signature file being parsed.
110      */
111     private var kotlinStyleNulls: Boolean? = null
112 
113     /** The file format of the file being parsed. */
114     lateinit var format: FileFormat
115 
116     /**
117      * Indicates whether the file currently being parsed is for the current API surface, i.e. the
118      * one that is being created.
119      *
120      * See [SignatureFile.forCurrentApiSurface].
121      */
122     private var forCurrentApiSurface: Boolean = true
123 
124     /** Map from [ClassItem] to [TextTypeItemFactory]. */
125     private val classToTypeItemFactory = IdentityHashMap<ClassItem, TextTypeItemFactory>()
126 
127     companion object {
128         /**
129          * Parse API signature files.
130          *
131          * Used by non-Metalava Kotlin code.
132          */
133         @MetalavaApi
parseApinull134         fun parseApi(
135             files: List<File>,
136         ) = parseApi(SignatureFile.fromFiles(files))
137 
138         /**
139          * Same as `parseApi(List<SignatureFile>, ...)`, but takes a single file for convenience.
140          *
141          * @param signatureFile input signature file
142          */
143         fun parseApi(
144             signatureFile: SignatureFile,
145             annotationManager: AnnotationManager,
146             description: String? = null,
147         ) =
148             parseApi(
149                 signatureFiles = listOf(signatureFile),
150                 annotationManager = annotationManager,
151                 description = description,
152             )
153 
154         /**
155          * Read API signature files into a [TextCodebase].
156          *
157          * Note: when reading from them multiple files, [TextCodebase.location] would refer to the
158          * first file specified. each [com.android.tools.metalava.model.text.TextItem.fileLocation]
159          * would correctly point out the source file of each item.
160          *
161          * @param signatureFiles input signature files
162          */
163         fun parseApi(
164             signatureFiles: List<SignatureFile>,
165             annotationManager: AnnotationManager = noOpAnnotationManager,
166             description: String? = null,
167             classResolver: ClassResolver? = null,
168             formatForLegacyFiles: FileFormat? = null,
169             // Provides the called with access to the ApiFile.
170             apiStatsConsumer: (Stats) -> Unit = {},
171         ): Codebase {
<lambda>null172             require(signatureFiles.isNotEmpty()) { "files must not be empty" }
173             val api =
174                 TextCodebase(
175                     location = signatureFiles[0].file,
176                     annotationManager = annotationManager,
177                     classResolver = classResolver,
178                 )
179             val actualDescription =
180                 description
<lambda>null181                     ?: buildString {
182                         append("Codebase loaded from ")
183                         signatureFiles.joinTo(this)
184                     }
185             val parser = ApiFile(api, formatForLegacyFiles)
186             var first = true
187             for (signatureFile in signatureFiles) {
188                 val file = signatureFile.file
189                 val apiText: String =
190                     try {
191                         file.readText(UTF_8)
192                     } catch (ex: IOException) {
193                         throw ApiParseException(
194                             "Error reading API file",
195                             location = FileLocation.createLocation(file.toPath()),
196                             cause = ex
197                         )
198                     }
199                 parser.parseApiSingleFile(
200                     appending = !first,
201                     path = file.toPath(),
202                     apiText = apiText,
203                     forCurrentApiSurface = signatureFile.forCurrentApiSurface,
204                 )
205                 first = false
206             }
207             api.description = actualDescription
208             parser.postProcess()
209 
210             apiStatsConsumer(parser.stats)
211 
212             return api
213         }
214 
215         /** <p>DO NOT MODIFY - used by com/android/gts/api/ApprovedApis.java */
216         @Deprecated("Exists only for external callers.")
217         @JvmStatic
218         @MetalavaApi
219         @Throws(ApiParseException::class)
parseApinull220         fun parseApi(
221             filename: String,
222             apiText: String,
223             @Suppress("UNUSED_PARAMETER") kotlinStyleNulls: Boolean?,
224         ): Codebase {
225             return parseApi(
226                 filename,
227                 apiText,
228             )
229         }
230 
231         /**
232          * Parse the API signature file from the [inputStream].
233          *
234          * This will consume the whole contents of the [inputStream] but it is the caller's
235          * responsibility to close it.
236          */
237         @JvmStatic
238         @MetalavaApi
239         @Throws(ApiParseException::class)
parseApinull240         fun parseApi(filename: String, inputStream: InputStream): Codebase {
241             val apiText = inputStream.bufferedReader().readText()
242             return parseApi(filename, apiText)
243         }
244 
245         /** Entry point for testing. Take a filename and content separately. */
parseApinull246         fun parseApi(
247             filename: String,
248             apiText: String,
249             classResolver: ClassResolver? = null,
250             formatForLegacyFiles: FileFormat? = null,
251         ): Codebase {
252             val path = Path.of(filename)
253             val api =
254                 TextCodebase(
255                     location = path.toFile(),
256                     annotationManager = noOpAnnotationManager,
257                     classResolver = classResolver,
258                 )
259             api.description = "Codebase loaded from $filename"
260             val parser = ApiFile(api, formatForLegacyFiles)
261             parser.parseApiSingleFile(
262                 appending = false,
263                 path = path,
264                 apiText = apiText,
265                 forCurrentApiSurface = true,
266             )
267             parser.postProcess()
268             return api
269         }
270 
271         /**
272          * Extracts the bounds string list from the [typeParameterString].
273          *
274          * Given `T extends a.B & b.C<? super T>` this will return a list of `a.B` and `b.C<? super
275          * T>`.
276          */
extractTypeParameterBoundsStringListnull277         fun extractTypeParameterBoundsStringList(typeParameterString: String?): List<String> {
278             val s = typeParameterString ?: return emptyList()
279             val index = s.indexOf("extends ")
280             if (index == -1) {
281                 return emptyList()
282             }
283             val list = mutableListOf<String>()
284             var angleBracketBalance = 0
285             var start = index + "extends ".length
286             val length = s.length
287             for (i in start until length) {
288                 val c = s[i]
289                 if (c == '&' && angleBracketBalance == 0) {
290                     addNonBlankStringToList(list, typeParameterString, start, i)
291                     start = i + 1
292                 } else if (c == '<') {
293                     angleBracketBalance++
294                 } else if (c == '>') {
295                     angleBracketBalance--
296                     if (angleBracketBalance == 0) {
297                         addNonBlankStringToList(list, typeParameterString, start, i + 1)
298                         start = i + 1
299                     }
300                 }
301             }
302             if (start < length) {
303                 addNonBlankStringToList(list, typeParameterString, start, length)
304             }
305             return list
306         }
307 
addNonBlankStringToListnull308         private fun addNonBlankStringToList(
309             list: MutableList<String>,
310             s: String,
311             from: Int,
312             to: Int
313         ) {
314             val element = s.substring(from, to).trim()
315             if (element.isNotEmpty()) list.add(element)
316         }
317     }
318 
319     /**
320      * Mark this [Item] as being part of the current API surface, i.e. the one that is being
321      * created.
322      *
323      * See [SignatureFile.forCurrentApiSurface].
324      *
325      * This will set [Item.emit] to [forCurrentApiSurface] and should only be called on [Item]s
326      * which have been created from the current signature file.
327      */
Itemnull328     private fun Item.markForCurrentApiSurface() {
329         emit = forCurrentApiSurface
330     }
331 
332     /**
333      * It is only necessary to mark an existing class as being part of the current API surface, if
334      * it should be but is not already.
335      *
336      * This will set [Item.emit] to `true` iff it was previously `false` and [forCurrentApiSurface]
337      * is `true`. That ensures that a class that is not in the current API surface can be included
338      * in it by another signature file, but once it is included it cannot be removed.
339      *
340      * e.g. Imagine that there are two files, `public.txt` and `system.txt` where the second extends
341      * the first. When generating the system API classes in the `public.txt` will not be considered
342      * part of it but any classes defined in `system.txt` will be, even if they were initially
343      * created in `public.txt`. While `public.txt` should come first this ensures the correct
344      * behavior irrespective of the order.
345      */
ClassItemnull346     private fun ClassItem.markExistingClassForCurrentApiSurface() {
347         if (!emit && forCurrentApiSurface) {
348             markForCurrentApiSurface()
349         }
350     }
351 
352     /**
353      * Perform any final steps to initialize the [TextCodebase] after parsing the signature files.
354      */
postProcessnull355     private fun postProcess() {
356         codebase.resolveSuperTypes()
357     }
358 
parseApiSingleFilenull359     private fun parseApiSingleFile(
360         appending: Boolean,
361         path: Path,
362         apiText: String,
363         forCurrentApiSurface: Boolean = true,
364     ) {
365         // Parse the header of the signature file to determine the format. If the signature file is
366         // empty then `parseHeader` will return null, so it will default to `FileFormat.V2`.
367         format =
368             FileFormat.parseHeader(path, StringReader(apiText), formatForLegacyFiles)
369                 ?: FileFormat.V2
370 
371         // Disallow a mixture of kotlinStyleNulls settings.
372         if (kotlinStyleNulls == null) {
373             kotlinStyleNulls = format.kotlinStyleNulls
374         } else if (kotlinStyleNulls != format.kotlinStyleNulls) {
375             throw ApiParseException(
376                 "Cannot mix signature files with different settings of kotlinStyleNulls"
377             )
378         }
379 
380         if (appending) {
381             // When we're appending, and the content is empty, nothing to do.
382             if (apiText.isBlank()) {
383                 return
384             }
385         }
386 
387         // Remember whether the file being parsed is for the current API surface, so that Items
388         // created from it can be marked correctly.
389         this.forCurrentApiSurface = forCurrentApiSurface
390 
391         val tokenizer = Tokenizer(path, apiText.toCharArray())
392         while (true) {
393             val token = tokenizer.getToken() ?: break
394             // TODO: Accept annotations on packages.
395             if ("package" == token) {
396                 parsePackage(tokenizer)
397             } else {
398                 throw ApiParseException("expected package got $token", tokenizer)
399             }
400         }
401     }
402 
parsePackagenull403     private fun parsePackage(tokenizer: Tokenizer) {
404         var token: String = tokenizer.requireToken()
405 
406         // Metalava: including annotations in file now
407         val annotations = getAnnotations(tokenizer, token)
408         val modifiers = createModifiers(DefaultModifierList.PUBLIC, annotations)
409         token = tokenizer.current
410         tokenizer.assertIdent(token)
411         val name: String = token
412 
413         // If the same package showed up multiple times, make sure they have the same modifiers.
414         // (Packages can't have public/private/etc., but they can have annotations, which are part
415         // of ModifierList.)
416         val existing = codebase.findPackage(name)
417         val pkg =
418             if (existing != null) {
419                 if (modifiers != existing.modifiers) {
420                     throw ApiParseException(
421                         String.format(
422                             "Contradicting declaration of package %s. Previously seen with modifiers \"%s\", but now with \"%s\"",
423                             name,
424                             existing.modifiers,
425                             modifiers
426                         ),
427                         tokenizer
428                     )
429                 }
430                 existing
431             } else {
432                 val newPackageItem =
433                     TextPackageItem(codebase, name, modifiers, tokenizer.fileLocation())
434                 newPackageItem.markForCurrentApiSurface()
435                 codebase.addPackage(newPackageItem)
436                 newPackageItem
437             }
438 
439         token = tokenizer.requireToken()
440         if ("{" != token) {
441             throw ApiParseException("expected '{' got $token", tokenizer)
442         }
443         while (true) {
444             token = tokenizer.requireToken()
445             if ("}" == token) {
446                 break
447             } else {
448                 parseClass(pkg, tokenizer, token)
449             }
450         }
451     }
452 
parseClassnull453     private fun parseClass(pkg: TextPackageItem, tokenizer: Tokenizer, startingToken: String) {
454         var token = startingToken
455         var classKind = ClassKind.CLASS
456         var superClassType: ClassTypeItem? = null
457 
458         // Metalava: including annotations in file now
459         val annotations = getAnnotations(tokenizer, token)
460         token = tokenizer.current
461         val modifiers = parseModifiers(tokenizer, token, annotations)
462 
463         // Remember this position as this seems like a good place to use to report issues with the
464         // class item.
465         val classPosition = tokenizer.fileLocation()
466 
467         token = tokenizer.current
468         when (token) {
469             "class" -> {
470                 token = tokenizer.requireToken()
471             }
472             "interface" -> {
473                 classKind = ClassKind.INTERFACE
474                 modifiers.setAbstract(true)
475                 token = tokenizer.requireToken()
476             }
477             "@interface" -> {
478                 classKind = ClassKind.ANNOTATION_TYPE
479                 modifiers.setAbstract(true)
480                 token = tokenizer.requireToken()
481             }
482             "enum" -> {
483                 classKind = ClassKind.ENUM
484                 modifiers.setFinal(true)
485                 modifiers.setStatic(true)
486                 superClassType = globalTypeItemFactory.superEnumType
487                 token = tokenizer.requireToken()
488             }
489             else -> {
490                 throw ApiParseException("missing class or interface. got: $token", tokenizer)
491             }
492         }
493         tokenizer.assertIdent(token)
494 
495         // The declaredClassType consists of the full name (i.e. preceded by the containing class's
496         // full name followed by a '.' if there is one) plus the type parameter string.
497         val declaredClassType: String = token
498 
499         // Extract lots of information from the declared class type.
500         val (
501             className,
502             fullName,
503             qualifiedClassName,
504             outerClass,
505             typeParameterList,
506             typeItemFactory,
507         ) = parseDeclaredClassType(pkg, declaredClassType, classPosition)
508 
509         token = tokenizer.requireToken()
510 
511         if ("extends" == token && classKind != ClassKind.INTERFACE) {
512             val superClassTypeString = parseSuperTypeString(tokenizer, tokenizer.requireToken())
513             superClassType =
514                 typeItemFactory.getSuperClassType(
515                     superClassTypeString,
516                 )
517             token = tokenizer.current
518         }
519 
520         val interfaceTypes = mutableSetOf<ClassTypeItem>()
521         if ("implements" == token || "extends" == token) {
522             token = tokenizer.requireToken()
523             while (true) {
524                 if ("{" == token) {
525                     break
526                 } else if ("," != token) {
527                     val interfaceTypeString = parseSuperTypeString(tokenizer, token)
528                     val interfaceType = typeItemFactory.getInterfaceType(interfaceTypeString)
529                     interfaceTypes.add(interfaceType)
530                     token = tokenizer.current
531                 } else {
532                     token = tokenizer.requireToken()
533                 }
534             }
535         }
536         if (superClassType == globalTypeItemFactory.superEnumType) {
537             // This can be taken either for an enum class, or a normal class that extends
538             // java.lang.Enum (which was the old way of representing an enum in the API signature
539             // files.
540             classKind = ClassKind.ENUM
541         } else if (classKind == ClassKind.ANNOTATION_TYPE) {
542             // If the annotation was defined using @interface then add the implicit
543             // "implements java.lang.annotation.Annotation".
544             interfaceTypes.add(globalTypeItemFactory.superAnnotationType)
545         } else if (globalTypeItemFactory.superAnnotationType in interfaceTypes) {
546             // A normal class that implements java.lang.annotation.Annotation which was the old way
547             // of representing an annotation in the API signature files. So, update the class kind
548             // to match.
549             classKind = ClassKind.ANNOTATION_TYPE
550         }
551 
552         if ("{" != token) {
553             throw ApiParseException("expected {, was $token", tokenizer)
554         }
555 
556         // Above we marked all enums as static but for a top level class it's implicit
557         if (classKind == ClassKind.ENUM && !fullName.contains(".")) {
558             modifiers.setStatic(false)
559         }
560 
561         // Get the characteristics of the class being added as they may be needed to compare against
562         // the characteristics of the same class from a previously processed signature file.
563         val newClassCharacteristics =
564             ClassCharacteristics(
565                 fileLocation = classPosition,
566                 qualifiedName = qualifiedClassName,
567                 fullName = fullName,
568                 classKind = classKind,
569                 modifiers = modifiers,
570                 superClassType = superClassType,
571             )
572 
573         // Check to see if there is an existing class, if so merge this class definition into that
574         // one and return. Otherwise, drop through and create a whole new class.
575         if (tryMergingIntoExistingClass(tokenizer, newClassCharacteristics)) {
576             return
577         }
578 
579         // Create the TextClassItem and set its package but do not add it to the package or
580         // register it.
581         val cl =
582             TextClassItem(
583                 codebase = codebase,
584                 fileLocation = classPosition,
585                 modifiers = modifiers,
586                 classKind = classKind,
587                 qualifiedName = qualifiedClassName,
588                 simpleName = className,
589                 fullName = fullName,
590                 typeParameterList = typeParameterList,
591             )
592         cl.markForCurrentApiSurface()
593 
594         // Default the superClassType() to java.lang.Object for any class that is not an interface,
595         // annotation, or enum and which is not itself java.lang.Object.
596         if (classKind == ClassKind.CLASS && superClassType == null && !cl.isJavaLangObject()) {
597             superClassType = globalTypeItemFactory.superObjectType
598         }
599         cl.setSuperClassType(superClassType)
600 
601         cl.setInterfaceTypes(interfaceTypes.toList())
602 
603         // Store the [TypeItemFactory] for this [ClassItem] so it can be retrieved later in
604         // [typeItemFactoryForClass].
605         if (!typeItemFactory.typeParameterScope.isEmpty()) {
606             classToTypeItemFactory[cl] = typeItemFactory
607         }
608 
609         cl.setContainingPackage(pkg)
610         cl.containingClass = outerClass
611         if (outerClass == null) {
612             // Add the class to the package, it will only be added to the TextCodebase once the
613             // package body has been parsed.
614             pkg.addClass(cl)
615         } else {
616             outerClass.addInnerClass(cl)
617         }
618         codebase.registerClass(cl)
619 
620         // Parse the class body adding each member created to the class item being populated.
621         parseClassBody(tokenizer, cl, typeItemFactory)
622     }
623 
624     /**
625      * Try merging the new class into an existing class that was previously loaded from a separate
626      * signature file.
627      *
628      * Will throw an exception if there is an existing class but it is not compatible with the new
629      * class.
630      *
631      * @return `false` if there is no existing class, `true` if there is and the merge succeeded.
632      */
tryMergingIntoExistingClassnull633     private fun tryMergingIntoExistingClass(
634         tokenizer: Tokenizer,
635         newClassCharacteristics: ClassCharacteristics,
636     ): Boolean {
637         // Check for the existing class from a previously parsed file. If it could not be found
638         // then return.
639         val existingClass =
640             codebase.findClassInCodebase(newClassCharacteristics.qualifiedName) ?: return false
641 
642         // Make sure the new class characteristics are compatible with the old class
643         // characteristic.
644         val existingCharacteristics = ClassCharacteristics.of(existingClass)
645         if (!existingCharacteristics.isCompatible(newClassCharacteristics)) {
646             throw ApiParseException(
647                 "Incompatible $existingClass definitions",
648                 newClassCharacteristics.fileLocation
649             )
650         }
651 
652         // Add new annotations to the existing class
653         val newClassAnnotations = newClassCharacteristics.modifiers.annotations().toSet()
654         val existingClassAnnotations = existingCharacteristics.modifiers.annotations().toSet()
655         for (annotation in newClassAnnotations.subtract(existingClassAnnotations)) {
656             existingClass.addAnnotation(annotation)
657         }
658 
659         // Use the latest super class.
660         val newSuperClassType = newClassCharacteristics.superClassType
661         if (
662             newSuperClassType != null && existingCharacteristics.superClassType != newSuperClassType
663         ) {
664             // Duplicate class with conflicting superclass names are found. Since the class
665             // definition found later should be prioritized, overwrite the superclass type.
666             existingClass.setSuperClassType(newSuperClassType)
667         }
668 
669         // Parse the class body adding each member created to the existing class.
670         parseClassBody(tokenizer, existingClass, typeItemFactoryForClass(existingClass))
671 
672         // Although the class was first defined in a separate file it is being modified in the
673         // current file so that may include it in the current API surface.
674         existingClass.markExistingClassForCurrentApiSurface()
675 
676         return true
677     }
678 
679     /** Get the [TextTypeItemFactory] for a previously created [ClassItem]. */
typeItemFactoryForClassnull680     private fun typeItemFactoryForClass(classItem: ClassItem?): TextTypeItemFactory =
681         classItem?.let { classToTypeItemFactory[classItem] } ?: globalTypeItemFactory
682 
683     /** Parse the class body, adding members to [cl]. */
parseClassBodynull684     private fun parseClassBody(
685         tokenizer: Tokenizer,
686         cl: TextClassItem,
687         classTypeItemFactory: TextTypeItemFactory,
688     ) {
689         var token = tokenizer.requireToken()
690         while (true) {
691             if ("}" == token) {
692                 break
693             } else if ("ctor" == token) {
694                 token = tokenizer.requireToken()
695                 parseConstructor(tokenizer, cl, classTypeItemFactory, token)
696             } else if ("method" == token) {
697                 token = tokenizer.requireToken()
698                 parseMethod(tokenizer, cl, classTypeItemFactory, token)
699             } else if ("field" == token) {
700                 token = tokenizer.requireToken()
701                 parseField(tokenizer, cl, classTypeItemFactory, token, false)
702             } else if ("enum_constant" == token) {
703                 token = tokenizer.requireToken()
704                 parseField(tokenizer, cl, classTypeItemFactory, token, true)
705             } else if ("property" == token) {
706                 token = tokenizer.requireToken()
707                 parseProperty(tokenizer, cl, classTypeItemFactory, token)
708             } else {
709                 throw ApiParseException("expected ctor, enum_constant, field or method", tokenizer)
710             }
711             token = tokenizer.requireToken()
712         }
713     }
714 
715     /**
716      * Parse a super type string, i.e. a string representing a super class type or a super interface
717      * type.
718      */
parseSuperTypeStringnull719     private fun parseSuperTypeString(tokenizer: Tokenizer, initialToken: String): String {
720         var token = getAnnotationCompleteToken(tokenizer, initialToken)
721 
722         // Use the token directly if it is complete, otherwise construct the super class type
723         // string from as many tokens as necessary.
724         return if (!isIncompleteTypeToken(token)) {
725             token
726         } else {
727             buildString {
728                 append(token)
729 
730                 // Make sure full super class name is found if there are type use
731                 // annotations. This can't use [parseType] because the next token might be a
732                 // separate type (classes only have a single `extends` type, but all
733                 // interface supertypes are listed as `extends` instead of `implements`).
734                 // However, this type cannot be an array, so unlike [parseType] this does
735                 // not need to check if the next token has annotations.
736                 do {
737                     token = getAnnotationCompleteToken(tokenizer, tokenizer.current)
738                     append(" ")
739                     append(token)
740                 } while (isIncompleteTypeToken(token))
741             }
742         }
743     }
744 
745     /** Encapsulates multiple return values from [parseDeclaredClassType]. */
746     private data class DeclaredClassTypeComponents(
747         /** The simple name of the class, i.e. not including any outer class prefix. */
748         val simpleName: String,
749         /** The full name of the class, including outer class prefix. */
750         val fullName: String,
751         /** The fully qualified name, including package and full name. */
752         val qualifiedName: String,
753         /** The optional, resolved outer [ClassItem]. */
754         val outerClass: ClassItem?,
755         /** The set of type parameters. */
756         val typeParameterList: TypeParameterList,
757         /**
758          * The [TextTypeItemFactory] including any type parameters in the [typeParameterList] in its
759          * [TextTypeItemFactory.typeParameterScope].
760          */
761         val typeItemFactory: TextTypeItemFactory,
762     )
763 
764     /**
765      * Splits the declared class type into [DeclaredClassTypeComponents].
766      *
767      * For example "Foo" would split into full name "Foo" and an empty type parameter list, while
768      * `"Foo.Bar<A, B extends java.lang.String, C>"` would split into full name `"Foo.Bar"` and type
769      * parameter list with `"A"`,`"B extends java.lang.String"`, and `"C"` as type parameters.
770      *
771      * If the qualified name matches an existing class then return its information.
772      */
parseDeclaredClassTypenull773     private fun parseDeclaredClassType(
774         pkg: TextPackageItem,
775         declaredClassType: String,
776         classFileLocation: FileLocation,
777     ): DeclaredClassTypeComponents {
778         // Split the declared class type into full name and type parameters.
779         val paramIndex = declaredClassType.indexOf('<')
780         val (fullName, typeParameterListString) =
781             if (paramIndex == -1) {
782                 Pair(declaredClassType, "")
783             } else {
784                 Pair(
785                     declaredClassType.substring(0, paramIndex),
786                     declaredClassType.substring(paramIndex)
787                 )
788             }
789         val pkgName = pkg.name()
790         val qualifiedName = qualifiedName(pkgName, fullName)
791 
792         // Split the full name into an optional outer class and a simple name.
793         val nestedClassIndex = fullName.lastIndexOf('.')
794         val (outerClass, simpleName) =
795             if (nestedClassIndex == -1) {
796                 Pair(null, fullName)
797             } else {
798                 val outerClassFullName = fullName.substring(0, nestedClassIndex)
799                 val qualifiedOuterClassName = qualifiedName(pkgName, outerClassFullName)
800 
801                 // Search for the outer class in the codebase. This is safe as the outer class
802                 // always precedes its nested classes.
803                 val outerClass =
804                     codebase.getOrCreateClass(qualifiedOuterClassName, isOuterClass = true)
805 
806                 val innerClassName = fullName.substring(nestedClassIndex + 1)
807                 Pair(outerClass, innerClassName)
808             }
809 
810         // Get the [TextTypeItemFactory] for the outer class, if any, from a previously stored one,
811         // otherwise use the [globalTypeItemFactory] as the [ClassItem] is a stub and so has no type
812         // parameters.
813         val outerClassTypeItemFactory = typeItemFactoryForClass(outerClass)
814 
815         // Create type parameter list and factory from the string and optional outer class factory.
816         val (typeParameterList, typeItemFactory) =
817             if (typeParameterListString == "")
818                 Pair(TypeParameterList.NONE, outerClassTypeItemFactory)
819             else
820                 createTypeParameterList(
821                     outerClassTypeItemFactory,
822                     "class $qualifiedName",
823                     typeParameterListString,
824                 )
825 
826         // Decide which type parameter list and factory to actually use.
827         //
828         // If the class already exists then reuse its type parameter list and factory, otherwise use
829         // the newly created one.
830         //
831         // The reason for this is that otherwise any types parsed with the newly created factory
832         // would reference type parameters in the newly created list which are different to the ones
833         // belonging to the existing class.
834         val (actualTypeParameterList, actualTypeItemFactory) =
835             codebase.findClassInCodebase(qualifiedName)?.let { existingClass ->
836                 // Check to make sure that the type parameter lists are the same.
837                 val existingTypeParameterList = existingClass.typeParameterList
838                 val existingTypeParameterListString = existingTypeParameterList.toString()
839                 val normalizedTypeParameterListString = typeParameterList.toString()
840                 if (normalizedTypeParameterListString != existingTypeParameterListString) {
841                     val location = existingClass.fileLocation
842                     throw ApiParseException(
843                         "Inconsistent type parameter list for $qualifiedName, this has $normalizedTypeParameterListString but it was previously defined as $existingTypeParameterListString at $location",
844                         classFileLocation
845                     )
846                 }
847 
848                 Pair(existingTypeParameterList, typeItemFactoryForClass(existingClass))
849             }
850                 ?: Pair(typeParameterList, typeItemFactory)
851 
852         return DeclaredClassTypeComponents(
853             simpleName = simpleName,
854             fullName = fullName,
855             qualifiedName = qualifiedName,
856             outerClass = outerClass,
857             typeParameterList = actualTypeParameterList,
858             typeItemFactory = actualTypeItemFactory,
859         )
860     }
861 
862     /**
863      * If the [startingToken] contains the beginning of an annotation, pulls additional tokens from
864      * [tokenizer] to complete the annotation, returning the full token. If there isn't an
865      * annotation, returns the original [startingToken].
866      *
867      * When the method returns, the [tokenizer] will point to the token after the end of the
868      * returned string.
869      */
getAnnotationCompleteTokennull870     private fun getAnnotationCompleteToken(tokenizer: Tokenizer, startingToken: String): String {
871         return if (startingToken.contains('@')) {
872             val prefix = startingToken.substringBefore('@')
873             val annotationStart = startingToken.substring(startingToken.indexOf('@'))
874             val annotation = getAnnotationSource(tokenizer, annotationStart)
875             "$prefix$annotation"
876         } else {
877             tokenizer.requireToken()
878             startingToken
879         }
880     }
881 
882     /**
883      * If the [startingToken] is the beginning of an annotation, returns the annotation parsed from
884      * the [tokenizer]. Returns null otherwise.
885      *
886      * When the method returns, the [tokenizer] will point to the token after the annotation.
887      */
getAnnotationSourcenull888     private fun getAnnotationSource(tokenizer: Tokenizer, startingToken: String): String? {
889         var token = startingToken
890         if (token.startsWith('@')) {
891             // Annotation
892             var annotation = token
893 
894             // Restore annotations that were shortened on export
895             annotation = unshortenAnnotation(annotation)
896             token = tokenizer.requireToken()
897             if (token == "(") {
898                 // Annotation arguments; potentially nested
899                 var balance = 0
900                 val start = tokenizer.offset() - 1
901                 while (true) {
902                     if (token == "(") {
903                         balance++
904                     } else if (token == ")") {
905                         balance--
906                         if (balance == 0) {
907                             break
908                         }
909                     }
910                     token = tokenizer.requireToken()
911                 }
912                 annotation += tokenizer.getStringFromOffset(start)
913                 // Move the tokenizer so that when the method returns it points to the token after
914                 // the end of the annotation.
915                 tokenizer.requireToken()
916             }
917             return annotation
918         } else {
919             return null
920         }
921     }
922 
923     /**
924      * Collects all the sequential annotations from the [tokenizer] beginning with [startingToken],
925      * returning them as a (possibly empty) mutable list.
926      *
927      * When the method returns, the [tokenizer] will point to the token after the annotation list.
928      */
getAnnotationsnull929     private fun getAnnotations(
930         tokenizer: Tokenizer,
931         startingToken: String
932     ): MutableList<AnnotationItem> {
933         val annotations: MutableList<AnnotationItem> = mutableListOf()
934         var token = startingToken
935         while (true) {
936             val annotationSource = getAnnotationSource(tokenizer, token) ?: break
937             token = tokenizer.current
938             annotations.add(DefaultAnnotationItem.create(codebase, annotationSource))
939         }
940         return annotations
941     }
942 
parseConstructornull943     private fun parseConstructor(
944         tokenizer: Tokenizer,
945         containingClass: TextClassItem,
946         classTypeItemFactory: TextTypeItemFactory,
947         startingToken: String
948     ) {
949         var token = startingToken
950         val method: TextConstructorItem
951 
952         // Metalava: including annotations in file now
953         val annotations = getAnnotations(tokenizer, token)
954         token = tokenizer.current
955         val modifiers = parseModifiers(tokenizer, token, annotations)
956         token = tokenizer.current
957 
958         // Get a TypeParameterList and accompanying TypeItemFactory
959         val (typeParameterList, typeItemFactory) =
960             if ("<" == token) {
961                 parseTypeParameterList(tokenizer, classTypeItemFactory).also {
962                     token = tokenizer.requireToken()
963                 }
964             } else {
965                 Pair(TypeParameterList.NONE, classTypeItemFactory)
966             }
967 
968         tokenizer.assertIdent(token)
969         val name: String =
970             token.substring(
971                 token.lastIndexOf('.') + 1
972             ) // For inner classes, strip outer classes from name
973         val parameters = parseParameterList(tokenizer, typeItemFactory, name)
974         token = tokenizer.requireToken()
975         var throwsList = emptyList<ExceptionTypeItem>()
976         if ("throws" == token) {
977             throwsList = parseThrows(tokenizer, typeItemFactory)
978             token = tokenizer.current
979         }
980         if (";" != token) {
981             throw ApiParseException("expected ; found $token", tokenizer)
982         }
983 
984         method =
985             TextConstructorItem(
986                 codebase,
987                 name,
988                 containingClass,
989                 modifiers,
990                 containingClass.type(),
991                 parameters,
992                 tokenizer.fileLocation()
993             )
994         method.markForCurrentApiSurface()
995         method.typeParameterList = typeParameterList
996         method.setThrowsTypes(throwsList)
997 
998         if (!containingClass.constructors().contains(method)) {
999             containingClass.addConstructor(method)
1000         }
1001     }
1002 
1003     /**
1004      * Check whether the method is a synthetic enum method.
1005      *
1006      * i.e. `getEntries()` from Kotlin and `values()` and `valueOf(String)` from both Java and
1007      * Kotlin.
1008      */
isEnumSyntheticMethodnull1009     private fun isEnumSyntheticMethod(
1010         containingClass: ClassItem,
1011         name: String,
1012         parameters: List<ParameterItem>
1013     ): Boolean {
1014         if (!containingClass.isEnum()) return false
1015         val parameterCount = parameters.size
1016         return (parameterCount == 0 && (name == "values" || name == "getEntries")) ||
1017             (parameterCount == 1 && name == "valueOf" && parameters[0].type().isString())
1018     }
1019 
parseMethodnull1020     private fun parseMethod(
1021         tokenizer: Tokenizer,
1022         cl: TextClassItem,
1023         classTypeItemFactory: TextTypeItemFactory,
1024         startingToken: String
1025     ) {
1026         var token = startingToken
1027         val method: TextMethodItem
1028 
1029         // Metalava: including annotations in file now
1030         val annotations = getAnnotations(tokenizer, token)
1031         token = tokenizer.current
1032         val modifiers = parseModifiers(tokenizer, token, annotations)
1033         token = tokenizer.current
1034 
1035         // Get a TypeParameterList and accompanying TypeParameterScope
1036         val (typeParameterList, typeItemFactory) =
1037             if ("<" == token) {
1038                 parseTypeParameterList(tokenizer, classTypeItemFactory).also {
1039                     token = tokenizer.requireToken()
1040                 }
1041             } else {
1042                 Pair(TypeParameterList.NONE, classTypeItemFactory)
1043             }
1044 
1045         tokenizer.assertIdent(token)
1046 
1047         val returnTypeString: String
1048         val parameters: List<TextParameterItem>
1049         val name: String
1050         if (format.kotlinNameTypeOrder) {
1051             // Kotlin style: parse the name, the parameter list, then the return type.
1052             name = token
1053             parameters = parseParameterList(tokenizer, typeItemFactory, name)
1054             token = tokenizer.requireToken()
1055             if (token != ":") {
1056                 throw ApiParseException(
1057                     "Expecting \":\" after parameter list, found $token.",
1058                     tokenizer
1059                 )
1060             }
1061             token = tokenizer.requireToken()
1062             tokenizer.assertIdent(token)
1063             returnTypeString = scanForTypeString(tokenizer, token)
1064             token = tokenizer.current
1065         } else {
1066             // Java style: parse the return type, the name, and then the parameter list.
1067             returnTypeString = scanForTypeString(tokenizer, token)
1068             token = tokenizer.current
1069             tokenizer.assertIdent(token)
1070             name = token
1071             parameters = parseParameterList(tokenizer, typeItemFactory, name)
1072             token = tokenizer.requireToken()
1073         }
1074 
1075         val returnType =
1076             typeItemFactory.getMethodReturnType(
1077                 returnTypeString,
1078                 annotations,
1079                 MethodFingerprint(name, parameters.size),
1080                 cl.isAnnotationType()
1081             )
1082         synchronizeNullability(returnType, modifiers)
1083 
1084         if (cl.isInterface() && !modifiers.isDefault() && !modifiers.isStatic()) {
1085             modifiers.setAbstract(true)
1086         }
1087 
1088         var throwsList = emptyList<ExceptionTypeItem>()
1089         var defaultAnnotationMethodValue = ""
1090 
1091         when (token) {
1092             "throws" -> {
1093                 throwsList = parseThrows(tokenizer, typeItemFactory)
1094                 token = tokenizer.current
1095             }
1096             "default" -> {
1097                 defaultAnnotationMethodValue = parseDefault(tokenizer)
1098                 token = tokenizer.current
1099             }
1100         }
1101         if (";" != token) {
1102             throw ApiParseException("expected ; found $token", tokenizer)
1103         }
1104 
1105         // Ignore enum synthetic methods.
1106         if (isEnumSyntheticMethod(cl, name, parameters)) return
1107 
1108         method =
1109             TextMethodItem(
1110                 codebase,
1111                 name,
1112                 cl,
1113                 modifiers,
1114                 returnType,
1115                 parameters,
1116                 tokenizer.fileLocation()
1117             )
1118         method.markForCurrentApiSurface()
1119         method.typeParameterList = typeParameterList
1120         method.setThrowsTypes(throwsList)
1121         method.setAnnotationDefault(defaultAnnotationMethodValue)
1122 
1123         if (!cl.methods().contains(method)) {
1124             cl.addMethod(method)
1125         }
1126     }
1127 
parseFieldnull1128     private fun parseField(
1129         tokenizer: Tokenizer,
1130         cl: TextClassItem,
1131         classTypeItemFactory: TextTypeItemFactory,
1132         startingToken: String,
1133         isEnumConstant: Boolean,
1134     ) {
1135         var token = startingToken
1136         val annotations = getAnnotations(tokenizer, token)
1137         token = tokenizer.current
1138         val modifiers = parseModifiers(tokenizer, token, annotations)
1139         token = tokenizer.current
1140         tokenizer.assertIdent(token)
1141 
1142         val typeString: String
1143         val name: String
1144         if (format.kotlinNameTypeOrder) {
1145             // Kotlin style: parse the name, then the type.
1146             name = parseNameWithColon(token, tokenizer)
1147             token = tokenizer.requireToken()
1148             tokenizer.assertIdent(token)
1149             typeString = scanForTypeString(tokenizer, token)
1150             token = tokenizer.current
1151         } else {
1152             // Java style: parse the name, then the type.
1153             typeString = scanForTypeString(tokenizer, token)
1154             token = tokenizer.current
1155             tokenizer.assertIdent(token)
1156             name = token
1157             token = tokenizer.requireToken()
1158         }
1159 
1160         // Get the optional value.
1161         val valueString =
1162             if ("=" == token) {
1163                 token = tokenizer.requireToken(false)
1164                 token.also { token = tokenizer.requireToken() }
1165             } else null
1166 
1167         // Parse the type string and then synchronize the field's nullability with the type.
1168         val type =
1169             classTypeItemFactory.getFieldType(
1170                 underlyingType = typeString,
1171                 isEnumConstant = isEnumConstant,
1172                 isFinal = modifiers.isFinal(),
1173                 isInitialValueNonNull = { valueString != null && valueString != "null" },
1174                 itemAnnotations = annotations,
1175             )
1176         synchronizeNullability(type, modifiers)
1177 
1178         // Parse the value string.
1179         val value = valueString?.let { parseValue(type, valueString, tokenizer) }
1180 
1181         if (";" != token) {
1182             throw ApiParseException("expected ; found $token", tokenizer)
1183         }
1184         val field =
1185             TextFieldItem(codebase, name, cl, modifiers, type, value, tokenizer.fileLocation())
1186         field.markForCurrentApiSurface()
1187         if (isEnumConstant) {
1188             cl.addEnumConstant(field)
1189         } else {
1190             cl.addField(field)
1191         }
1192     }
1193 
parseModifiersnull1194     private fun parseModifiers(
1195         tokenizer: Tokenizer,
1196         startingToken: String?,
1197         annotations: MutableList<AnnotationItem>
1198     ): DefaultModifierList {
1199         var token = startingToken
1200         val modifiers = createModifiers(DefaultModifierList.PACKAGE_PRIVATE, annotations)
1201 
1202         processModifiers@ while (true) {
1203             token =
1204                 when (token) {
1205                     "public" -> {
1206                         modifiers.setVisibilityLevel(VisibilityLevel.PUBLIC)
1207                         tokenizer.requireToken()
1208                     }
1209                     "protected" -> {
1210                         modifiers.setVisibilityLevel(VisibilityLevel.PROTECTED)
1211                         tokenizer.requireToken()
1212                     }
1213                     "private" -> {
1214                         modifiers.setVisibilityLevel(VisibilityLevel.PRIVATE)
1215                         tokenizer.requireToken()
1216                     }
1217                     "internal" -> {
1218                         modifiers.setVisibilityLevel(VisibilityLevel.INTERNAL)
1219                         tokenizer.requireToken()
1220                     }
1221                     "static" -> {
1222                         modifiers.setStatic(true)
1223                         tokenizer.requireToken()
1224                     }
1225                     "final" -> {
1226                         modifiers.setFinal(true)
1227                         tokenizer.requireToken()
1228                     }
1229                     "deprecated" -> {
1230                         modifiers.setDeprecated(true)
1231                         tokenizer.requireToken()
1232                     }
1233                     "abstract" -> {
1234                         modifiers.setAbstract(true)
1235                         tokenizer.requireToken()
1236                     }
1237                     "transient" -> {
1238                         modifiers.setTransient(true)
1239                         tokenizer.requireToken()
1240                     }
1241                     "volatile" -> {
1242                         modifiers.setVolatile(true)
1243                         tokenizer.requireToken()
1244                     }
1245                     "sealed" -> {
1246                         modifiers.setSealed(true)
1247                         tokenizer.requireToken()
1248                     }
1249                     "default" -> {
1250                         modifiers.setDefault(true)
1251                         tokenizer.requireToken()
1252                     }
1253                     "synchronized" -> {
1254                         modifiers.setSynchronized(true)
1255                         tokenizer.requireToken()
1256                     }
1257                     "native" -> {
1258                         modifiers.setNative(true)
1259                         tokenizer.requireToken()
1260                     }
1261                     "strictfp" -> {
1262                         modifiers.setStrictFp(true)
1263                         tokenizer.requireToken()
1264                     }
1265                     "infix" -> {
1266                         modifiers.setInfix(true)
1267                         tokenizer.requireToken()
1268                     }
1269                     "operator" -> {
1270                         modifiers.setOperator(true)
1271                         tokenizer.requireToken()
1272                     }
1273                     "inline" -> {
1274                         modifiers.setInline(true)
1275                         tokenizer.requireToken()
1276                     }
1277                     "value" -> {
1278                         modifiers.setValue(true)
1279                         tokenizer.requireToken()
1280                     }
1281                     "suspend" -> {
1282                         modifiers.setSuspend(true)
1283                         tokenizer.requireToken()
1284                     }
1285                     "vararg" -> {
1286                         modifiers.setVarArg(true)
1287                         tokenizer.requireToken()
1288                     }
1289                     "fun" -> {
1290                         modifiers.setFunctional(true)
1291                         tokenizer.requireToken()
1292                     }
1293                     "data" -> {
1294                         modifiers.setData(true)
1295                         tokenizer.requireToken()
1296                     }
1297                     else -> break@processModifiers
1298                 }
1299         }
1300         return modifiers
1301     }
1302 
1303     /** Creates a [DefaultModifierList], setting the deprecation based on the [annotations]. */
createModifiersnull1304     private fun createModifiers(
1305         visibility: Int,
1306         annotations: MutableList<AnnotationItem>
1307     ): DefaultModifierList {
1308         val modifiers = DefaultModifierList(codebase, visibility, annotations)
1309         // @Deprecated is also treated as a "modifier"
1310         if (annotations.any { it.qualifiedName == JAVA_LANG_DEPRECATED }) {
1311             modifiers.setDeprecated(true)
1312         }
1313         return modifiers
1314     }
1315 
parseValuenull1316     private fun parseValue(
1317         type: TypeItem,
1318         value: String?,
1319         fileLocationTracker: FileLocationTracker,
1320     ): Any? {
1321         return if (value != null) {
1322             if (type is PrimitiveTypeItem) {
1323                 parsePrimitiveValue(type, value, fileLocationTracker)
1324             } else if (type.isString()) {
1325                 if ("null" == value) {
1326                     null
1327                 } else {
1328                     javaUnescapeString(value.substring(1, value.length - 1))
1329                 }
1330             } else {
1331                 value
1332             }
1333         } else null
1334     }
1335 
parsePrimitiveValuenull1336     private fun parsePrimitiveValue(
1337         type: PrimitiveTypeItem,
1338         value: String,
1339         fileLocationTracker: FileLocationTracker,
1340     ): Any {
1341         return when (type.kind) {
1342             Primitive.BOOLEAN ->
1343                 if ("true" == value) java.lang.Boolean.TRUE else java.lang.Boolean.FALSE
1344             Primitive.BYTE,
1345             Primitive.SHORT,
1346             Primitive.INT -> Integer.valueOf(value)
1347             Primitive.LONG -> java.lang.Long.valueOf(value.substring(0, value.length - 1))
1348             Primitive.FLOAT ->
1349                 when (value) {
1350                     "(1.0f/0.0f)",
1351                     "(1.0f / 0.0f)" -> Float.POSITIVE_INFINITY
1352                     "(-1.0f/0.0f)",
1353                     "(-1.0f / 0.0f)" -> Float.NEGATIVE_INFINITY
1354                     "(0.0f/0.0f)",
1355                     "(0.0f / 0.0f)" -> Float.NaN
1356                     else -> java.lang.Float.valueOf(value)
1357                 }
1358             Primitive.DOUBLE ->
1359                 when (value) {
1360                     "(1.0/0.0)",
1361                     "(1.0 / 0.0)" -> Double.POSITIVE_INFINITY
1362                     "(-1.0/0.0)",
1363                     "(-1.0 / 0.0)" -> Double.NEGATIVE_INFINITY
1364                     "(0.0/0.0)",
1365                     "(0.0 / 0.0)" -> Double.NaN
1366                     else -> java.lang.Double.valueOf(value)
1367                 }
1368             Primitive.CHAR -> value.toInt().toChar()
1369             Primitive.VOID ->
1370                 throw ApiParseException(
1371                     "Found value $value assigned to void type",
1372                     fileLocationTracker
1373                 )
1374         }
1375     }
1376 
parsePropertynull1377     private fun parseProperty(
1378         tokenizer: Tokenizer,
1379         cl: TextClassItem,
1380         classTypeItemFactory: TextTypeItemFactory,
1381         startingToken: String
1382     ) {
1383         var token = startingToken
1384 
1385         // Metalava: including annotations in file now
1386         val annotations = getAnnotations(tokenizer, token)
1387         token = tokenizer.current
1388         val modifiers = parseModifiers(tokenizer, token, annotations)
1389         token = tokenizer.current
1390         tokenizer.assertIdent(token)
1391 
1392         val typeString: String
1393         val name: String
1394         if (format.kotlinNameTypeOrder) {
1395             // Kotlin style: parse the name, then the type.
1396             name = parseNameWithColon(token, tokenizer)
1397             token = tokenizer.requireToken()
1398             tokenizer.assertIdent(token)
1399             typeString = scanForTypeString(tokenizer, token)
1400             token = tokenizer.current
1401         } else {
1402             // Java style: parse the type, then the name.
1403             typeString = scanForTypeString(tokenizer, token)
1404             token = tokenizer.current
1405             tokenizer.assertIdent(token)
1406             name = token
1407             token = tokenizer.requireToken()
1408         }
1409         val type = classTypeItemFactory.getGeneralType(typeString)
1410         synchronizeNullability(type, modifiers)
1411 
1412         if (";" != token) {
1413             throw ApiParseException("expected ; found $token", tokenizer)
1414         }
1415         val property =
1416             TextPropertyItem(codebase, name, cl, modifiers, type, tokenizer.fileLocation())
1417         property.markForCurrentApiSurface()
1418         cl.addProperty(property)
1419     }
1420 
parseTypeParameterListnull1421     private fun parseTypeParameterList(
1422         tokenizer: Tokenizer,
1423         enclosingTypeItemFactory: TextTypeItemFactory,
1424     ): Pair<TypeParameterList, TextTypeItemFactory> {
1425         var token: String
1426         val start = tokenizer.offset() - 1
1427         var balance = 1
1428         while (balance > 0) {
1429             token = tokenizer.requireToken()
1430             if (token == "<") {
1431                 balance++
1432             } else if (token == ">") {
1433                 balance--
1434             }
1435         }
1436         val typeParameterListString = tokenizer.getStringFromOffset(start)
1437         return if (typeParameterListString.isEmpty()) {
1438             Pair(TypeParameterList.NONE, enclosingTypeItemFactory)
1439         } else {
1440             // Use the file location as a part of the description of the scope as at this point
1441             // there is no other information available.
1442             val scopeDescription = "${tokenizer.fileLocation()}"
1443             createTypeParameterList(
1444                 enclosingTypeItemFactory,
1445                 scopeDescription,
1446                 typeParameterListString
1447             )
1448         }
1449     }
1450 
1451     /**
1452      * Creates a [TypeParameterList] and accompanying [TypeParameterScope].
1453      *
1454      * The [typeParameterListString] should be the string representation of a list of type
1455      * parameters, like "<A>" or "<A, B extends java.lang.String, C>".
1456      *
1457      * @return a [Pair] of [TypeParameterList] and [TextTypeItemFactory] that contains those type
1458      *   parameters.
1459      */
createTypeParameterListnull1460     private fun createTypeParameterList(
1461         enclosingTypeItemFactory: TextTypeItemFactory,
1462         scopeDescription: String,
1463         typeParameterListString: String
1464     ): Pair<TypeParameterList, TextTypeItemFactory> {
1465         // Split the type parameter list string into a list of strings, one for each type
1466         // parameter.
1467         val typeParameterStrings = TextTypeParser.typeParameterStrings(typeParameterListString)
1468 
1469         // Create the List<TypeParameterItem> and the corresponding TypeItemFactory that can be
1470         // used to resolve TypeParameterItems from the list. This performs the construction in two
1471         // stages to handle cycles between the parameters.
1472         val (typeParameters, typeItemFactory) =
1473             DefaultTypeParameterList.createTypeParameterItemsAndFactory(
1474                 enclosingTypeItemFactory,
1475                 scopeDescription,
1476                 typeParameterStrings,
1477                 // Create a `TextTypeParameterItem` from the type parameter string.
1478                 { TextTypeParameterItem.create(codebase, it) },
1479                 // Create, set and return the [BoundsTypeItem] list.
1480                 { typeItemFactory, item, typeParameterString ->
1481                     val boundsStringList = extractTypeParameterBoundsStringList(typeParameterString)
1482                     boundsStringList
1483                         .map { typeItemFactory.getBoundsType(it) }
1484                         .also { item.bounds = it }
1485                 },
1486             )
1487 
1488         return Pair(DefaultTypeParameterList(typeParameters), typeItemFactory)
1489     }
1490 
1491     /**
1492      * Parses a list of parameters. Before calling, [tokenizer] should point to the token *before*
1493      * the opening `(` of the parameter list (the method starts by calling
1494      * [Tokenizer.requireToken]).
1495      *
1496      * When the method returns, [tokenizer] will point to the closing `)` of the parameter list.
1497      */
parseParameterListnull1498     private fun parseParameterList(
1499         tokenizer: Tokenizer,
1500         typeItemFactory: TextTypeItemFactory,
1501         methodName: String,
1502     ): List<TextParameterItem> {
1503         val parameters = mutableListOf<ParameterInfo>()
1504         var token: String = tokenizer.requireToken()
1505         if ("(" != token) {
1506             throw ApiParseException("expected (, was $token", tokenizer)
1507         }
1508         token = tokenizer.requireToken()
1509         var index = 0
1510         while (true) {
1511             if (")" == token) {
1512                 // All parameters are parsed, create the actual TextParameterItems
1513                 val methodFingerprint = MethodFingerprint(methodName, parameters.size)
1514                 return parameters.map { it.create(codebase, typeItemFactory, methodFingerprint) }
1515             }
1516 
1517             // Each item can be
1518             // optional annotations optional-modifiers type-with-use-annotations-and-generics
1519             // optional-name optional-equals-default-value
1520 
1521             // Used to represent the presence of a default value, instead of showing the entire
1522             // default value
1523             var hasDefaultValue = token == "optional"
1524             if (hasDefaultValue) {
1525                 token = tokenizer.requireToken()
1526             }
1527 
1528             // Metalava: including annotations in file now
1529             val annotations = getAnnotations(tokenizer, token)
1530             token = tokenizer.current
1531             val modifiers = parseModifiers(tokenizer, token, annotations)
1532             token = tokenizer.current
1533 
1534             val typeString: String
1535             val name: String
1536             val publicName: String?
1537             if (format.kotlinNameTypeOrder) {
1538                 // Kotlin style: parse the name (only considered a public name if it is not `_`,
1539                 // which is used as a placeholder for params without public names), then the type.
1540                 name = parseNameWithColon(token, tokenizer)
1541                 publicName =
1542                     if (name == "_") {
1543                         null
1544                     } else {
1545                         name
1546                     }
1547                 token = tokenizer.requireToken()
1548                 // Token should now represent the type
1549                 typeString = scanForTypeString(tokenizer, token)
1550                 token = tokenizer.current
1551             } else {
1552                 // Java style: parse the type, then the public name if it has one.
1553                 typeString = scanForTypeString(tokenizer, token)
1554                 token = tokenizer.current
1555                 if (Tokenizer.isIdent(token) && token != "=") {
1556                     name = token
1557                     publicName = name
1558                     token = tokenizer.requireToken()
1559                 } else {
1560                     name = "arg" + (index + 1)
1561                     publicName = null
1562                 }
1563             }
1564 
1565             var defaultValue = UNKNOWN_DEFAULT_VALUE
1566             if ("=" == token) {
1567                 defaultValue = tokenizer.requireToken(true)
1568                 val sb = StringBuilder(defaultValue)
1569                 if (defaultValue == "{") {
1570                     var balance = 1
1571                     while (balance > 0) {
1572                         token = tokenizer.requireToken(parenIsSep = false, eatWhitespace = false)
1573                         sb.append(token)
1574                         if (token == "{") {
1575                             balance++
1576                         } else if (token == "}") {
1577                             balance--
1578                             if (balance == 0) {
1579                                 break
1580                             }
1581                         }
1582                     }
1583                     token = tokenizer.requireToken()
1584                 } else {
1585                     var balance = if (defaultValue == "(") 1 else 0
1586                     while (true) {
1587                         token = tokenizer.requireToken(parenIsSep = true, eatWhitespace = false)
1588                         if ((token.endsWith(",") || token.endsWith(")")) && balance <= 0) {
1589                             if (token.length > 1) {
1590                                 sb.append(token, 0, token.length - 1)
1591                                 token = token[token.length - 1].toString()
1592                             }
1593                             break
1594                         }
1595                         sb.append(token)
1596                         if (token == "(") {
1597                             balance++
1598                         } else if (token == ")") {
1599                             balance--
1600                         }
1601                     }
1602                 }
1603                 defaultValue = sb.toString()
1604             }
1605             if (defaultValue != UNKNOWN_DEFAULT_VALUE) {
1606                 hasDefaultValue = true
1607             }
1608             when (token) {
1609                 "," -> {
1610                     token = tokenizer.requireToken()
1611                 }
1612                 ")" -> {
1613                     // closing parenthesis
1614                 }
1615                 else -> {
1616                     throw ApiParseException("expected , or ), found $token", tokenizer)
1617                 }
1618             }
1619             parameters.add(
1620                 ParameterInfo(
1621                     name,
1622                     publicName,
1623                     hasDefaultValue,
1624                     defaultValue,
1625                     typeString,
1626                     modifiers,
1627                     tokenizer.fileLocation(),
1628                     index
1629                 )
1630             )
1631             index++
1632         }
1633     }
1634 
1635     /**
1636      * Container for parsed information on a parameter. This is an intermediate step before a
1637      * [TextParameterItem] is created, which is needed because
1638      * [TextTypeItemFactory.getMethodParameterType] requires a [MethodFingerprint] with the total
1639      * number of method parameters.
1640      */
1641     private inner class ParameterInfo(
1642         val name: String,
1643         val publicName: String?,
1644         val hasDefaultValue: Boolean,
1645         val defaultValue: String?,
1646         val typeString: String,
1647         val modifiers: DefaultModifierList,
1648         val location: FileLocation,
1649         val index: Int
1650     ) {
1651         /** Turn this [ParameterInfo] into a [TextParameterItem] by parsing the [typeString]. */
createnull1652         fun create(
1653             codebase: TextCodebase,
1654             typeItemFactory: TextTypeItemFactory,
1655             methodFingerprint: MethodFingerprint
1656         ): TextParameterItem {
1657             val type =
1658                 typeItemFactory.getMethodParameterType(
1659                     typeString,
1660                     modifiers.annotations(),
1661                     methodFingerprint,
1662                     index,
1663                     modifiers.isVarArg()
1664                 )
1665             synchronizeNullability(type, modifiers)
1666 
1667             val parameter =
1668                 TextParameterItem(
1669                     codebase,
1670                     name,
1671                     publicName,
1672                     hasDefaultValue,
1673                     defaultValue,
1674                     index,
1675                     type,
1676                     modifiers,
1677                     location
1678                 )
1679 
1680             parameter.markForCurrentApiSurface()
1681             if (type is ArrayTypeItem && type.isVarargs) {
1682                 modifiers.setVarArg(true)
1683             }
1684 
1685             return parameter
1686         }
1687     }
1688 
parseDefaultnull1689     private fun parseDefault(tokenizer: Tokenizer): String {
1690         return buildString {
1691             while (true) {
1692                 val token = tokenizer.requireToken()
1693                 if (";" == token) {
1694                     break
1695                 } else {
1696                     append(token)
1697                 }
1698             }
1699         }
1700     }
1701 
parseThrowsnull1702     private fun parseThrows(
1703         tokenizer: Tokenizer,
1704         typeItemFactory: TextTypeItemFactory,
1705     ): List<ExceptionTypeItem> {
1706         var token = tokenizer.requireToken()
1707         val throwsList = buildList {
1708             var comma = true
1709             while (true) {
1710                 when (token) {
1711                     ";" -> {
1712                         break
1713                     }
1714                     "," -> {
1715                         if (comma) {
1716                             throw ApiParseException("Expected exception, got ','", tokenizer)
1717                         }
1718                         comma = true
1719                     }
1720                     else -> {
1721                         if (!comma) {
1722                             throw ApiParseException("Expected ',' or ';' got $token", tokenizer)
1723                         }
1724                         comma = false
1725                         val exceptionType = typeItemFactory.getExceptionType(token)
1726                         add(exceptionType)
1727                     }
1728                 }
1729                 token = tokenizer.requireToken()
1730             }
1731         }
1732 
1733         return throwsList
1734     }
1735 
1736     /**
1737      * Scans the token stream from [tokenizer] for a type string, starting with the [startingToken]
1738      * and ensuring that the full type string is gathered, even when there are type-use annotations.
1739      *
1740      * After this method is called, `tokenizer.current` will point to the token after the type.
1741      *
1742      * Note: this **should not** be used when the token after the type could contain annotations,
1743      * such as when multiple types appear as consecutive tokens. (This happens in the `implements`
1744      * list of a class definition, e.g. `class Foo implements test.pkg.Bar test.pkg.@A Baz`.)
1745      *
1746      * To handle arrays with type-use annotations, this looks forward at the next token and includes
1747      * it if it contains an annotation. This is necessary to handle type strings like "Foo @A []".
1748      */
scanForTypeStringnull1749     private fun scanForTypeString(tokenizer: Tokenizer, startingToken: String): String {
1750         var prev = getAnnotationCompleteToken(tokenizer, startingToken)
1751         var type = prev
1752         var token = tokenizer.current
1753         // Look both at the last used token and the next one:
1754         // If the last token has annotations, the type string was broken up by annotations, and the
1755         // next token is also part of the type.
1756         // If the next token has annotations, this is an array type like "Foo @A []", so the next
1757         // token is part of the type.
1758         while (isIncompleteTypeToken(prev) || isIncompleteTypeToken(token)) {
1759             token = getAnnotationCompleteToken(tokenizer, token)
1760             type += " $token"
1761             prev = token
1762             token = tokenizer.current
1763         }
1764         return type
1765     }
1766 
1767     /**
1768      * Synchronize nullability annotations on the API item and [TypeNullability].
1769      *
1770      * If the type string uses a Kotlin nullability suffix, this adds an annotation representing
1771      * that nullability to [modifiers].
1772      *
1773      * @param typeItem the type of the API item.
1774      * @param modifiers the API item's modifiers.
1775      */
synchronizeNullabilitynull1776     private fun synchronizeNullability(typeItem: TypeItem, modifiers: DefaultModifierList) {
1777         if (typeParser.kotlinStyleNulls) {
1778             // Add an annotation to the context item for the type's nullability if applicable.
1779             val annotationToAdd =
1780                 // Treat varargs as non-null for consistency with the psi model.
1781                 if (typeItem is ArrayTypeItem && typeItem.isVarargs) {
1782                     ANDROIDX_NONNULL
1783                 } else {
1784                     val nullability = typeItem.modifiers.nullability()
1785                     if (typeItem !is PrimitiveTypeItem && nullability == TypeNullability.NONNULL) {
1786                         ANDROIDX_NONNULL
1787                     } else if (nullability == TypeNullability.NULLABLE) {
1788                         ANDROIDX_NULLABLE
1789                     } else {
1790                         // No annotation to add, return.
1791                         return
1792                     }
1793                 }
1794             modifiers.addAnnotation(codebase.createAnnotation("@$annotationToAdd"))
1795         }
1796     }
1797 
1798     /**
1799      * Determines whether the [type] is an incomplete type string broken up by annotations. This is
1800      * the case when there's an annotation that isn't contained within a parameter list (because
1801      * [Tokenizer.requireToken] handles not breaking in the middle of a parameter list).
1802      */
isIncompleteTypeTokennull1803     private fun isIncompleteTypeToken(type: String): Boolean {
1804         val firstAnnotationIndex = type.indexOf('@')
1805         val paramStartIndex = type.indexOf('<')
1806         val lastAnnotationIndex = type.lastIndexOf('@')
1807         val paramEndIndex = type.lastIndexOf('>')
1808         return firstAnnotationIndex != -1 &&
1809             (paramStartIndex == -1 ||
1810                 firstAnnotationIndex < paramStartIndex ||
1811                 paramEndIndex == -1 ||
1812                 paramEndIndex < lastAnnotationIndex)
1813     }
1814 
1815     /**
1816      * For Kotlin-style name/type ordering in signature files, the name is generally followed by a
1817      * colon (besides methods, where the colon comes after the parameter list). This method takes
1818      * the name [token] and removes the trailing colon, throwing an [ApiParseException] if one isn't
1819      * present (the [tokenizer] is only used for context for the error, if needed).
1820      */
parseNameWithColonnull1821     private fun parseNameWithColon(token: String, tokenizer: Tokenizer): String {
1822         if (!token.endsWith(':')) {
1823             throw ApiParseException("Expecting name ending with \":\" but found $token.", tokenizer)
1824         }
1825         return token.removeSuffix(":")
1826     }
1827 
qualifiedNamenull1828     private fun qualifiedName(pkg: String, className: String): String {
1829         return "$pkg.$className"
1830     }
1831 
1832     private val stats
1833         get() =
1834             Stats(
1835                 codebase.getPackages().allClasses().count(),
1836                 typeParser.requests,
1837                 typeParser.cacheSkip,
1838                 typeParser.cacheHit,
1839                 typeParser.cacheSize,
1840             )
1841 
1842     data class Stats(
1843         val totalClasses: Int,
1844         val typeCacheRequests: Int,
1845         val typeCacheSkip: Int,
1846         val typeCacheHit: Int,
1847         val typeCacheSize: Int,
1848     )
1849 }
1850