• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.ArrayTypeItem
23 import com.android.tools.metalava.model.CallableItem
24 import com.android.tools.metalava.model.ClassItem
25 import com.android.tools.metalava.model.ClassKind
26 import com.android.tools.metalava.model.ClassOrigin
27 import com.android.tools.metalava.model.ClassResolver
28 import com.android.tools.metalava.model.ClassTypeItem
29 import com.android.tools.metalava.model.Codebase
30 import com.android.tools.metalava.model.ConstructorItem
31 import com.android.tools.metalava.model.DefaultAnnotationItem
32 import com.android.tools.metalava.model.DefaultTypeParameterList
33 import com.android.tools.metalava.model.ExceptionTypeItem
34 import com.android.tools.metalava.model.FixedFieldValue
35 import com.android.tools.metalava.model.Item
36 import com.android.tools.metalava.model.ItemDocumentation
37 import com.android.tools.metalava.model.JAVA_LANG_DEPRECATED
38 import com.android.tools.metalava.model.JAVA_LANG_OBJECT
39 import com.android.tools.metalava.model.MetalavaApi
40 import com.android.tools.metalava.model.MethodItem
41 import com.android.tools.metalava.model.MutableModifierList
42 import com.android.tools.metalava.model.ParameterItem
43 import com.android.tools.metalava.model.PrimitiveTypeItem
44 import com.android.tools.metalava.model.PrimitiveTypeItem.Primitive
45 import com.android.tools.metalava.model.SelectableItem
46 import com.android.tools.metalava.model.TypeItem
47 import com.android.tools.metalava.model.TypeNullability
48 import com.android.tools.metalava.model.TypeParameterItem
49 import com.android.tools.metalava.model.TypeParameterList
50 import com.android.tools.metalava.model.TypeParameterListAndFactory
51 import com.android.tools.metalava.model.VisibilityLevel
52 import com.android.tools.metalava.model.api.surface.ApiSurfaces
53 import com.android.tools.metalava.model.api.surface.ApiVariant
54 import com.android.tools.metalava.model.api.surface.ApiVariantType
55 import com.android.tools.metalava.model.createImmutableModifiers
56 import com.android.tools.metalava.model.createMutableModifiers
57 import com.android.tools.metalava.model.item.DefaultClassItem
58 import com.android.tools.metalava.model.item.DefaultCodebase
59 import com.android.tools.metalava.model.item.DefaultPackageItem
60 import com.android.tools.metalava.model.item.DefaultTypeParameterItem
61 import com.android.tools.metalava.model.item.MutablePackageDoc
62 import com.android.tools.metalava.model.item.PackageDocs
63 import com.android.tools.metalava.model.item.ParameterDefaultValue
64 import com.android.tools.metalava.model.javaUnescapeString
65 import com.android.tools.metalava.model.type.MethodFingerprint
66 import com.android.tools.metalava.reporter.FileLocation
67 import com.android.tools.metalava.reporter.Issues
68 import java.io.File
69 import java.io.IOException
70 import java.io.InputStream
71 import java.io.StringReader
72 import java.nio.file.Path
73 import java.util.IdentityHashMap
74 import kotlin.text.Charsets.UTF_8
75 
76 /** Encapsulates information needed to process a signature file. */
77 sealed class SignatureFile {
78     /** The underlying signature [File]. */
79     abstract val file: File
80 
81     /**
82      * Indicates whether [file] is for the main API surface, i.e. the one that is being created.
83      *
84      * This will be stored in [Item.emit].
85      */
86     protected open val forMainApiSurface: Boolean
87         get() = true
88 
89     /** The [ApiVariantType] of the signature files. */
90     protected open val apiVariantType: ApiVariantType
91         get() = ApiVariantType.CORE
92 
93     /**
94      * Get the [ApiVariant] that this signature file represents.
95      *
96      * If [forMainApiSurface] is `false` then [apiSurfaces] must provide a non-null value for
97      * [ApiSurfaces.base]. An exception will be thrown if it is not.
98      *
99      * @param apiSurfaces the [ApiSurfaces] the returned [Codebase] is required to support.
100      */
101     fun apiVariantFor(apiSurfaces: ApiSurfaces): ApiVariant {
102         val apiSurface =
103             if (forMainApiSurface) apiSurfaces.main
104             else
105                 apiSurfaces.base
106                     ?: error("$file expects a base API surface to be available but it is not")
107         return apiSurface.variantFor(apiVariantType)
108     }
109 
110     /** Read the contents of this signature file. */
111     abstract fun readContents(): String
112 
113     companion object {
114         /** Create a list of [SignatureFile]s from a varargs array of [File]s. */
115         fun fromFiles(vararg files: File): List<SignatureFile> =
116             files.map {
117                 SignatureFileFromFile(
118                     it,
119                 )
120             }
121 
122         /**
123          * Create a list of [SignatureFile]s from a list of [File]s.
124          *
125          * @param files the list of [File]s.
126          * @param apiVariantTypeChooser A lambda that will be called with the [File] of each item in
127          *   [files] and whose return value will be stored in [SignatureFile.apiVariantType].
128          * @param forMainApiSurfacePredicate A predicate that will be called with the index and
129          *   [File] of each item in [files] and whose return value will be stored in
130          *   [SignatureFile.forMainApiSurface].
131          */
132         fun fromFiles(
133             files: List<File>,
134             apiVariantTypeChooser: (File) -> ApiVariantType = { ApiVariantType.CORE },
135             forMainApiSurfacePredicate: (Int, File) -> Boolean = { _, _ -> true },
136         ): List<SignatureFile> =
137             files.mapIndexed { index, file ->
138                 SignatureFileFromFile(
139                     file,
140                     forMainApiSurface = forMainApiSurfacePredicate(index, file),
141                     apiVariantType = apiVariantTypeChooser(file),
142                 )
143             }
144 
145         /** Create a [SignatureFile] that wraps an [InputStream]. */
146         fun fromStream(filename: String, inputStream: InputStream): SignatureFile {
147             return SignatureFileFromStream(File(filename), inputStream)
148         }
149 
150         /**
151          * Create a [SignatureFile] that wraps a [String].
152          *
153          * @param filename the name of the file, used for error reporting.
154          * @param contents the contents of the file, will be trimmed using [String.trimIndent].
155          */
156         fun fromText(filename: String, contents: String): SignatureFile {
157             return SignatureFileFromText(File(filename), contents.trimIndent())
158         }
159     }
160 
161     /** A [SignatureFile] that will read the text from the [file]. */
162     private data class SignatureFileFromFile(
163         override val file: File,
164         override val forMainApiSurface: Boolean = true,
165         override val apiVariantType: ApiVariantType = ApiVariantType.CORE,
166     ) : SignatureFile() {
167         override fun readContents() =
168             try {
169                 file.readText(UTF_8)
170             } catch (ex: IOException) {
171                 throw ApiParseException(
172                     "Error reading API file",
173                     location = FileLocation.createLocation(file.toPath()),
174                     cause = ex
175                 )
176             }
177     }
178 
179     /** A [SignatureFile] that wraps an [InputStream]. */
180     private data class SignatureFileFromStream(
181         override val file: File,
182         val inputStream: InputStream,
183     ) : SignatureFile() {
184         override fun readContents() = inputStream.bufferedReader().readText()
185     }
186 
187     /** A [SignatureFile] that wraps a [String]. */
188     private data class SignatureFileFromText(
189         override val file: File,
190         val contents: String,
191     ) : SignatureFile() {
192         override fun readContents() = contents
193     }
194 }
195 
196 @MetalavaApi
197 class ApiFile
198 private constructor(
199     private val assembler: TextCodebaseAssembler,
200     private val formatForLegacyFiles: FileFormat?,
201 ) {
202 
203     private val codebase = assembler.codebase
204 
205     /**
206      * The [FileLocationTracker] for the current file being parsed.
207      *
208      * Set by [parseApiSingleFile].
209      */
210     private lateinit var fileLocationTracker: FileLocationTracker
211 
212     /**
213      * Report recoverable errors encountered while parsing.
214      *
215      * Retrieves the location of the error from [fileLocationTracker].
216      */
217     private val errorReporter =
218         object : SignatureErrorReporter {
reportnull219             override fun report(issue: Issues.Issue, message: String) {
220                 val location = fileLocationTracker.fileLocation()
221                 codebase.reporter.report(issue, null, message, location)
222             }
223         }
224 
225     /**
226      * Provides support for parsing and caching [TypeItem]s.
227      *
228      * Defer creation until after the first file has been read and [kotlinStyleNulls] has been set
229      * to a non-null value to ensure that it picks up the correct setting of [kotlinStyleNulls].
230      */
231     private val typeParser by
<lambda>null232         lazy(LazyThreadSafetyMode.NONE) {
233             TextTypeParser(codebase, kotlinStyleNulls!!, errorReporter)
234         }
235 
236     /**
237      * Provides support for creating [TypeItem]s for specific uses.
238      *
239      * Defer creation as it depends on [typeParser].
240      */
241     private val globalTypeItemFactory by
<lambda>null242         lazy(LazyThreadSafetyMode.NONE) { TextTypeItemFactory(assembler, typeParser) }
243 
244     /** Creates [Item] instances for [codebase]. */
245     private val itemFactory = assembler.itemFactory
246 
247     /**
248      * Whether types should be interpreted to be in Kotlin format (e.g. ? suffix means nullable, !
249      * suffix means unknown, and absence of a suffix means not nullable.
250      *
251      * Updated based on the header of the signature file being parsed.
252      */
253     private var kotlinStyleNulls: Boolean? = null
254 
255     /** The file format of the file being parsed. */
256     lateinit var format: FileFormat
257 
258     /**
259      * The [ApiVariant] which is defined within the current signature file being parsed.
260      *
261      * Set in [parseApiSingleFile].
262      */
263     private lateinit var apiVariant: ApiVariant
264 
265     /**
266      * True if this is appending information from one signature file to a [Codebase] created from
267      * another signature file.
268      */
269     private var appending: Boolean = false
270 
271     /** Map from [ClassItem] to [TextTypeItemFactory]. */
272     private val classToTypeItemFactory = IdentityHashMap<ClassItem, TextTypeItemFactory>()
273 
274     companion object {
275         /**
276          * Parse API signature files.
277          *
278          * Used by non-Metalava Kotlin code.
279          */
280         @MetalavaApi
parseApinull281         fun parseApi(
282             files: List<File>,
283         ) = parseApi(SignatureFile.fromFiles(files))
284 
285         /**
286          * Read API signature files into a [DefaultCodebase].
287          *
288          * Note: when reading from them multiple files, [DefaultCodebase.location] would refer to
289          * the first file specified. each [Item.fileLocation] would correctly point out the source
290          * file of each item.
291          *
292          * @param signatureFiles input signature files
293          */
294         fun parseApi(
295             signatureFiles: List<SignatureFile>,
296             codebaseConfig: Codebase.Config = Codebase.Config.NOOP,
297             description: String? = null,
298             classResolver: ClassResolver? = null,
299             formatForLegacyFiles: FileFormat? = null,
300             // Provides the called with access to the ApiFile.
301             apiStatsConsumer: (Stats) -> Unit = {},
302         ): Codebase {
<lambda>null303             require(signatureFiles.isNotEmpty()) { "files must not be empty" }
304             val actualDescription =
305                 description
<lambda>null306                     ?: buildString {
307                         append("Codebase loaded from ")
308                         signatureFiles.joinTo(this)
309                     }
310             val assembler =
311                 TextCodebaseAssembler.createAssembler(
312                     location = signatureFiles[0].file,
313                     description = actualDescription,
314                     codebaseConfig = codebaseConfig,
315                     classResolver = classResolver,
316                 )
317             val parser = ApiFile(assembler, formatForLegacyFiles)
318             val apiSurfaces = codebaseConfig.apiSurfaces
319             var first = true
320             for (signatureFile in signatureFiles) {
321                 val file = signatureFile.file
322                 val apiText = signatureFile.readContents()
323                 val apiVariant = signatureFile.apiVariantFor(apiSurfaces)
324                 parser.parseApiSingleFile(
325                     appending = !first,
326                     path = file.toPath(),
327                     apiText = apiText,
328                     apiVariant = apiVariant,
329                 )
330                 first = false
331             }
332 
333             apiStatsConsumer(parser.stats)
334 
335             return assembler.codebase
336         }
337 
338         /**
339          * Parse the API signature file from the [inputStream].
340          *
341          * This will consume the whole contents of the [inputStream] but it is the caller's
342          * responsibility to close it.
343          */
344         @JvmStatic
345         @MetalavaApi
346         @Throws(ApiParseException::class)
parseApinull347         fun parseApi(filename: String, inputStream: InputStream): Codebase {
348             val signatureFile = SignatureFile.fromStream(filename, inputStream)
349             return parseApi(listOf(signatureFile))
350         }
351 
352         /**
353          * Extracts the bounds string list from the [typeParameterString].
354          *
355          * Given `T extends a.B & b.C<? super T>` this will return a list of `a.B` and `b.C<? super
356          * T>`.
357          */
extractTypeParameterBoundsStringListnull358         fun extractTypeParameterBoundsStringList(typeParameterString: String?): List<String> {
359             val s = typeParameterString ?: return emptyList()
360             val index = s.indexOf("extends ")
361             if (index == -1) {
362                 return emptyList()
363             }
364             val list = mutableListOf<String>()
365             var angleBracketBalance = 0
366             var start = index + "extends ".length
367             val length = s.length
368             for (i in start until length) {
369                 val c = s[i]
370                 if (c == '&' && angleBracketBalance == 0) {
371                     addNonBlankStringToList(list, typeParameterString, start, i)
372                     start = i + 1
373                 } else if (c == '<') {
374                     angleBracketBalance++
375                 } else if (c == '>') {
376                     angleBracketBalance--
377                     if (angleBracketBalance == 0) {
378                         addNonBlankStringToList(list, typeParameterString, start, i + 1)
379                         start = i + 1
380                     }
381                 }
382             }
383             if (start < length) {
384                 addNonBlankStringToList(list, typeParameterString, start, length)
385             }
386             return list
387         }
388 
addNonBlankStringToListnull389         private fun addNonBlankStringToList(
390             list: MutableList<String>,
391             s: String,
392             from: Int,
393             to: Int
394         ) {
395             val element = s.substring(from, to).trim()
396             if (element.isNotEmpty()) list.add(element)
397         }
398     }
399 
400     /**
401      * Mark this [SelectableItem] as being part of the main API surface, i.e. the one that is being
402      * created.
403      *
404      * See [SignatureFile.forMainApiSurface].
405      *
406      * This will set [SelectableItem.emit] to [forMainApiSurface] and should only be called on
407      * [SelectableItem]s which have been created from the main signature file.
408      */
SelectableItemnull409     private fun SelectableItem.markForMainApiSurface() {
410         emit = apiVariant.surface.isMain
411         markSelectedApiVariant()
412     }
413 
414     /**
415      * Record that this [SelectableItem] was loaded from a signature file that contains
416      * [apiVariant].
417      */
markSelectedApiVariantnull418     private fun SelectableItem.markSelectedApiVariant() {
419         if (apiVariant !in selectedApiVariants) {
420             mutateSelectedApiVariants { add(apiVariant) }
421         }
422     }
423 
424     /**
425      * It is only necessary to mark an existing class as being part of the main API surface, if it
426      * should be but is not already.
427      *
428      * This will set [Item.emit] to `true` iff it was previously `false` and [forMainApiSurface] is
429      * `true`. That ensures that a class that is not in the main API surface can be included in it
430      * by another signature file, but once it is included it cannot be removed.
431      *
432      * e.g. Imagine that there are two files, `public.txt` and `system.txt` where the second extends
433      * the first. When generating the system API classes in the `public.txt` will not be considered
434      * part of it but any classes defined in `system.txt` will be, even if they were initially
435      * created in `public.txt`. While `public.txt` should come first this ensures the correct
436      * behavior irrespective of the order.
437      */
ClassItemnull438     private fun ClassItem.markExistingClassForMainApiSurface() {
439         if (!emit && apiVariant.surface.isMain) {
440             markForMainApiSurface()
441         }
442 
443         // Always record the ApiVariants to which this belongs, even if this was previously loaded.
444         // This is safe because unlike `emit` which is Boolean the `selectedApiVariants` property is
445         // a set of ApiVariants and this just adds an ApiVariant.
446         markSelectedApiVariant()
447     }
448 
parseApiSingleFilenull449     private fun parseApiSingleFile(
450         appending: Boolean,
451         path: Path,
452         apiText: String,
453         apiVariant: ApiVariant,
454     ) {
455         if (appending) {
456             // When we're appending, and the content is empty, nothing to do.
457             if (apiText.isBlank()) {
458                 return
459             }
460         }
461 
462         // The behavior is slightly different when appending to an existing Codebase.
463         this.appending = appending
464 
465         // Parse the header of the signature file to determine the format. If the signature file is
466         // empty then `parseHeader` will return null, so it will default to `FileFormat.V2`.
467         format =
468             FileFormat.parseHeader(path, StringReader(apiText), formatForLegacyFiles)
469                 ?: FileFormat.V2
470 
471         // Remember the API variant of the file being parsed.
472         this.apiVariant = apiVariant
473 
474         val tokenizer = Tokenizer(path, apiText.toCharArray())
475 
476         // Get the preceding tracker, if any.
477         val precedingTracker =
478             if (::fileLocationTracker.isInitialized) {
479                 fileLocationTracker
480             } else {
481                 null
482             }
483 
484         // Set the file location tracker to provide location information about the current file.
485         fileLocationTracker = tokenizer
486 
487         // Disallow a mixture of kotlinStyleNulls settings.
488         if (kotlinStyleNulls != null && kotlinStyleNulls != format.kotlinStyleNulls) {
489             val precedingFile = precedingTracker!!.fileLocation().path
490             errorReporter.report(
491                 "Preceding file $precedingFile has different setting of kotlin-style-nulls which may cause issues"
492             )
493         }
494         kotlinStyleNulls = format.kotlinStyleNulls
495 
496         while (true) {
497             val token = tokenizer.getToken() ?: break
498             // TODO: Accept annotations on packages.
499             if ("package" == token) {
500                 parsePackage(tokenizer)
501             } else {
502                 throw ApiParseException("expected package got $token", tokenizer)
503             }
504         }
505     }
506 
parsePackagenull507     private fun parsePackage(tokenizer: Tokenizer) {
508         var token: String = tokenizer.requireToken()
509 
510         // Metalava: including annotations in file now
511         val annotations = getAnnotations(tokenizer, token)
512         val modifiers = createModifiers(VisibilityLevel.PUBLIC, annotations)
513         token = tokenizer.current
514         tokenizer.assertIdent(token)
515         val name: String = token
516 
517         // Wrap the modifiers and file location in a PackageDocs so that findOrCreatePackage(...)
518         // will create a package with them and will check to make sure that an existing package, if
519         // any, has matching modifiers.
520         val packageDoc =
521             MutablePackageDoc(
522                 name,
523                 fileLocation = tokenizer.fileLocation(),
524                 modifiers = modifiers,
525             )
526         val packageDocs = PackageDocs(mapOf(name to packageDoc))
527         val pkg =
528             try {
529                 codebase.findOrCreatePackage(name, packageDocs)
530             } catch (e: IllegalStateException) {
531                 throw ApiParseException(e.message!!, tokenizer)
532             }
533 
534         // Make sure that the package records the ApiVariants to which it belongs.
535         pkg.markSelectedApiVariant()
536 
537         token = tokenizer.requireToken()
538         if ("{" != token) {
539             throw ApiParseException("expected '{' got $token", tokenizer)
540         }
541         while (true) {
542             token = tokenizer.requireToken()
543             if ("}" == token) {
544                 break
545             } else {
546                 parseClass(pkg, tokenizer, token)
547             }
548         }
549     }
550 
551     /**
552      * Creates a type alias in the [pkg] with the [modifiers].
553      *
554      * It is expected that the starting position of the [tokenizer] is the "typealias" keyword, and
555      * the next token will be the name and option type parameter list.
556      *
557      * When the method returns, the current [tokenizer] position will be the ";" at the end of the
558      * typealias line.
559      */
parseTypeAliasnull560     private fun parseTypeAlias(
561         pkg: DefaultPackageItem,
562         tokenizer: Tokenizer,
563         modifiers: MutableModifierList,
564         location: FileLocation
565     ) {
566         var token = tokenizer.requireToken()
567         tokenizer.assertIdent(token)
568 
569         val typeParameterListIndex = token.indexOf("<")
570 
571         val (name, typeParameterList, typeItemFactory) =
572             if (typeParameterListIndex == -1) {
573                 Triple(token, TypeParameterList.NONE, globalTypeItemFactory)
574             } else {
575                 val name = token.substring(0, typeParameterListIndex)
576                 val typeParameterListAndFactory =
577                     createTypeParameterList(
578                         globalTypeItemFactory,
579                         "typealias $name",
580                         token.substring(typeParameterListIndex)
581                     )
582                 Triple(
583                     name,
584                     typeParameterListAndFactory.typeParameterList,
585                     typeParameterListAndFactory.factory
586                 )
587             }
588 
589         token = tokenizer.requireToken()
590         if ("=" != token) {
591             throw ApiParseException("expected = found $token", tokenizer)
592         }
593 
594         val typeString = scanForTypeString(tokenizer, tokenizer.requireToken())
595         token = tokenizer.current
596         if (";" != token) {
597             throw ApiParseException("expected ; found $token", tokenizer)
598         }
599 
600         val type = typeItemFactory.getGeneralType(typeString)
601         itemFactory.createTypeAliasItem(
602             fileLocation = location,
603             modifiers = modifiers,
604             qualifiedName = pkg.qualifiedName() + "." + name,
605             containingPackage = pkg,
606             aliasedType = type,
607             typeParameterList = typeParameterList,
608         )
609     }
610 
parseClassnull611     private fun parseClass(pkg: DefaultPackageItem, tokenizer: Tokenizer, startingToken: String) {
612         var token = startingToken
613         var classKind = ClassKind.CLASS
614         var superClassType: ClassTypeItem? = null
615 
616         // Metalava: including annotations in file now
617         val annotations = getAnnotations(tokenizer, token)
618         token = tokenizer.current
619         val modifiers = parseModifiers(tokenizer, token, annotations)
620 
621         // Remember this position as this seems like a good place to use to report issues with the
622         // class item.
623         val classPosition = tokenizer.fileLocation()
624 
625         token = tokenizer.current
626         when (token) {
627             "class" -> {
628                 token = tokenizer.requireToken()
629             }
630             "interface" -> {
631                 classKind = ClassKind.INTERFACE
632                 modifiers.setAbstract(true)
633                 token = tokenizer.requireToken()
634             }
635             "@interface" -> {
636                 classKind = ClassKind.ANNOTATION_TYPE
637                 modifiers.setAbstract(true)
638                 token = tokenizer.requireToken()
639             }
640             "enum" -> {
641                 classKind = ClassKind.ENUM
642                 modifiers.setFinal(true)
643                 modifiers.setStatic(true)
644                 superClassType = globalTypeItemFactory.superEnumType
645                 token = tokenizer.requireToken()
646             }
647             "typealias" -> {
648                 // Type aliases aren't classes, but they are defined at the same level as classes
649                 parseTypeAlias(pkg, tokenizer, modifiers, classPosition)
650                 // Don't continue creating a class item
651                 return
652             }
653             else -> {
654                 throw ApiParseException(
655                     "expected one of class, interface, @interface, enum, or typealias; found: $token",
656                     tokenizer
657                 )
658             }
659         }
660         tokenizer.assertIdent(token)
661 
662         // The declaredClassType consists of the full name (i.e. preceded by the containing class's
663         // full name followed by a '.' if there is one) plus the type parameter string.
664         val declaredClassType: String = token
665 
666         // Extract lots of information from the declared class type.
667         val (
668             fullName,
669             qualifiedClassName,
670             outerClass,
671             typeParameterList,
672             typeItemFactory,
673         ) = parseDeclaredClassType(pkg, declaredClassType, classPosition)
674 
675         token = tokenizer.requireToken()
676 
677         if ("extends" == token && classKind != ClassKind.INTERFACE) {
678             val superClassTypeString = parseSuperTypeString(tokenizer, tokenizer.requireToken())
679             superClassType =
680                 typeItemFactory.getSuperClassType(
681                     superClassTypeString,
682                 )
683             token = tokenizer.current
684         }
685 
686         val interfaceTypes = mutableSetOf<ClassTypeItem>()
687         if ("implements" == token || "extends" == token) {
688             token = tokenizer.requireToken()
689             while (true) {
690                 if ("{" == token) {
691                     break
692                 } else if ("," != token) {
693                     val interfaceTypeString = parseSuperTypeString(tokenizer, token)
694                     val interfaceType = typeItemFactory.getInterfaceType(interfaceTypeString)
695                     interfaceTypes.add(interfaceType)
696                     token = tokenizer.current
697                 } else {
698                     token = tokenizer.requireToken()
699                 }
700             }
701         }
702         if (superClassType == globalTypeItemFactory.superEnumType) {
703             // This can be taken either for an enum class, or a normal class that extends
704             // java.lang.Enum (which was the old way of representing an enum in the API signature
705             // files.
706             classKind = ClassKind.ENUM
707         } else if (classKind == ClassKind.ANNOTATION_TYPE) {
708             // If the annotation was defined using @interface then add the implicit
709             // "implements java.lang.annotation.Annotation".
710             interfaceTypes.add(globalTypeItemFactory.superAnnotationType)
711         } else if (globalTypeItemFactory.superAnnotationType in interfaceTypes) {
712             // A normal class that implements java.lang.annotation.Annotation which was the old way
713             // of representing an annotation in the API signature files. So, update the class kind
714             // to match.
715             classKind = ClassKind.ANNOTATION_TYPE
716         }
717 
718         if ("{" != token) {
719             throw ApiParseException("expected {, was $token", tokenizer)
720         }
721 
722         // Above we marked all enums as static but for a top level class it's implicit
723         if (classKind == ClassKind.ENUM && !fullName.contains(".")) {
724             modifiers.setStatic(false)
725         }
726 
727         // Get the characteristics of the class being added as they may be needed to compare against
728         // the characteristics of the same class from a previously processed signature file.
729         val newClassCharacteristics =
730             ClassCharacteristics(
731                 fileLocation = classPosition,
732                 qualifiedName = qualifiedClassName,
733                 fullName = fullName,
734                 classKind = classKind,
735                 modifiers = modifiers.toImmutable(),
736                 superClassType = superClassType,
737             )
738 
739         // Check to see if there is an existing class, if so merge this class definition into that
740         // one and return. Otherwise, drop through and create a whole new class.
741         if (tryMergingIntoExistingClass(tokenizer, newClassCharacteristics)) {
742             return
743         }
744 
745         // Default the superClassType() to java.lang.Object for any class that is not an interface,
746         // annotation, or enum and which is not itself java.lang.Object.
747         if (
748             classKind == ClassKind.CLASS &&
749                 superClassType == null &&
750                 qualifiedClassName != JAVA_LANG_OBJECT
751         ) {
752             superClassType = globalTypeItemFactory.superObjectType
753         }
754 
755         // Create the DefaultClassItem and set its package but do not add it to the package or
756         // register it.
757         val cl =
758             itemFactory.createClassItem(
759                 fileLocation = classPosition,
760                 modifiers = modifiers,
761                 classKind = classKind,
762                 containingClass = outerClass,
763                 containingPackage = pkg,
764                 qualifiedName = qualifiedClassName,
765                 typeParameterList = typeParameterList,
766                 // All signature files have to be explicitly specified.
767                 origin = ClassOrigin.COMMAND_LINE,
768                 superClassType = superClassType,
769                 interfaceTypes = interfaceTypes.toList(),
770             )
771         cl.markForMainApiSurface()
772 
773         // Store the [TypeItemFactory] for this [ClassItem] so it can be retrieved later in
774         // [typeItemFactoryForClass].
775         if (!typeItemFactory.typeParameterScope.isEmpty()) {
776             classToTypeItemFactory[cl] = typeItemFactory
777         }
778 
779         // Parse the class body adding each member created to the class item being populated.
780         parseClassBody(tokenizer, cl, typeItemFactory)
781     }
782 
783     /**
784      * Try merging the new class into an existing class that was previously loaded from a separate
785      * signature file.
786      *
787      * Will throw an exception if there is an existing class but it is not compatible with the new
788      * class.
789      *
790      * @return `false` if there is no existing class, `true` if there is and the merge succeeded.
791      */
tryMergingIntoExistingClassnull792     private fun tryMergingIntoExistingClass(
793         tokenizer: Tokenizer,
794         newClassCharacteristics: ClassCharacteristics,
795     ): Boolean {
796         // Check for the existing class from a previously parsed file. If it could not be found
797         // then return.
798         val existingClass =
799             codebase.findClassInCodebase(newClassCharacteristics.qualifiedName) ?: return false
800 
801         // Make sure the new class characteristics are compatible with the old class
802         // characteristic.
803         val existingCharacteristics = ClassCharacteristics.of(existingClass)
804         if (!existingCharacteristics.isCompatible(newClassCharacteristics)) {
805             throw ApiParseException(
806                 "Incompatible $existingClass definitions",
807                 newClassCharacteristics.fileLocation
808             )
809         }
810 
811         // Add new annotations to the existing class
812         val newClassAnnotations = newClassCharacteristics.modifiers.annotations().toSet()
813         val existingClassAnnotations = existingCharacteristics.modifiers.annotations().toSet()
814 
815         val extraAnnotations = newClassAnnotations.subtract(existingClassAnnotations)
816         if (extraAnnotations.isNotEmpty()) {
817             existingClass.mutateModifiers { mutateAnnotations { addAll(extraAnnotations) } }
818         }
819 
820         // Use the latest super class.
821         val newSuperClassType = newClassCharacteristics.superClassType
822         if (
823             newSuperClassType != null && existingCharacteristics.superClassType != newSuperClassType
824         ) {
825             // Duplicate class with conflicting superclass names are found. Since the class
826             // definition found later should be prioritized, overwrite the superclass type.
827             existingClass.setSuperClassType(newSuperClassType)
828         }
829 
830         // Parse the class body adding each member created to the existing class.
831         parseClassBody(tokenizer, existingClass, typeItemFactoryForClass(existingClass))
832 
833         // Although the class was first defined in a separate file it is being modified in the
834         // current file so that may include it in the main API surface.
835         existingClass.markExistingClassForMainApiSurface()
836 
837         return true
838     }
839 
840     /** Get the [TextTypeItemFactory] for a previously created [ClassItem]. */
typeItemFactoryForClassnull841     private fun typeItemFactoryForClass(classItem: ClassItem?): TextTypeItemFactory =
842         classItem?.let { classToTypeItemFactory[classItem] } ?: globalTypeItemFactory
843 
844     /** Parse the class body, adding members to [cl]. */
parseClassBodynull845     private fun parseClassBody(
846         tokenizer: Tokenizer,
847         cl: DefaultClassItem,
848         classTypeItemFactory: TextTypeItemFactory,
849     ) {
850         var token = tokenizer.requireToken()
851         while (true) {
852             if ("}" == token) {
853                 break
854             } else if ("ctor" == token) {
855                 token = tokenizer.requireToken()
856                 parseConstructor(tokenizer, cl, classTypeItemFactory, token)
857             } else if ("method" == token) {
858                 token = tokenizer.requireToken()
859                 parseMethod(tokenizer, cl, classTypeItemFactory, token)
860             } else if ("field" == token) {
861                 token = tokenizer.requireToken()
862                 parseField(tokenizer, cl, classTypeItemFactory, token, false)
863             } else if ("enum_constant" == token) {
864                 token = tokenizer.requireToken()
865                 parseField(tokenizer, cl, classTypeItemFactory, token, true)
866             } else if ("property" == token) {
867                 token = tokenizer.requireToken()
868                 parseProperty(tokenizer, cl, classTypeItemFactory, token)
869             } else {
870                 throw ApiParseException("expected ctor, enum_constant, field or method", tokenizer)
871             }
872             token = tokenizer.requireToken()
873         }
874     }
875 
876     /**
877      * Parse a super type string, i.e. a string representing a super class type or a super interface
878      * type.
879      */
parseSuperTypeStringnull880     private fun parseSuperTypeString(tokenizer: Tokenizer, initialToken: String): String {
881         var token = getAnnotationCompleteToken(tokenizer, initialToken)
882 
883         // Use the token directly if it is complete, otherwise construct the super class type
884         // string from as many tokens as necessary.
885         return if (!isIncompleteTypeToken(token)) {
886             token
887         } else {
888             buildString {
889                 append(token)
890 
891                 // Make sure full super class name is found if there are type use
892                 // annotations. This can't use [parseType] because the next token might be a
893                 // separate type (classes only have a single `extends` type, but all
894                 // interface supertypes are listed as `extends` instead of `implements`).
895                 // However, this type cannot be an array, so unlike [parseType] this does
896                 // not need to check if the next token has annotations.
897                 do {
898                     token = getAnnotationCompleteToken(tokenizer, tokenizer.current)
899                     append(" ")
900                     append(token)
901                 } while (isIncompleteTypeToken(token))
902             }
903         }
904     }
905 
906     /** Encapsulates multiple return values from [parseDeclaredClassType]. */
907     private data class DeclaredClassTypeComponents(
908         /** The full name of the class, including outer class prefix. */
909         val fullName: String,
910         /** The fully qualified name, including package and full name. */
911         val qualifiedName: String,
912         /** The optional, resolved outer [ClassItem]. */
913         val outerClass: DefaultClassItem?,
914         /** The set of type parameters. */
915         val typeParameterList: TypeParameterList,
916         /**
917          * The [TextTypeItemFactory] including any type parameters in the [typeParameterList] in its
918          * [TextTypeItemFactory.typeParameterScope].
919          */
920         val typeItemFactory: TextTypeItemFactory,
921     )
922 
923     /**
924      * Splits the declared class type into [DeclaredClassTypeComponents].
925      *
926      * For example "Foo" would split into full name "Foo" and an empty type parameter list, while
927      * `"Foo.Bar<A, B extends java.lang.String, C>"` would split into full name `"Foo.Bar"` and type
928      * parameter list with `"A"`,`"B extends java.lang.String"`, and `"C"` as type parameters.
929      *
930      * If the qualified name matches an existing class then return its information.
931      */
parseDeclaredClassTypenull932     private fun parseDeclaredClassType(
933         pkg: DefaultPackageItem,
934         declaredClassType: String,
935         classFileLocation: FileLocation,
936     ): DeclaredClassTypeComponents {
937         // Split the declared class type into full name and type parameters.
938         val paramIndex = declaredClassType.indexOf('<')
939         val (fullName, typeParameterListString) =
940             if (paramIndex == -1) {
941                 Pair(declaredClassType, "")
942             } else {
943                 Pair(
944                     declaredClassType.substring(0, paramIndex),
945                     declaredClassType.substring(paramIndex)
946                 )
947             }
948         val pkgName = pkg.qualifiedName()
949         val qualifiedName = qualifiedName(pkgName, fullName)
950 
951         // Split the full name into an optional outer class and a simple name.
952         val nestedClassIndex = fullName.lastIndexOf('.')
953         val outerClass =
954             if (nestedClassIndex == -1) {
955                 null
956             } else {
957                 val outerClassFullName = fullName.substring(0, nestedClassIndex)
958                 val qualifiedOuterClassName = qualifiedName(pkgName, outerClassFullName)
959 
960                 // Search for the outer class in the codebase. This is safe as the outer class
961                 // always precedes its nested classes.
962                 assembler.getOrCreateClass(
963                     qualifiedOuterClassName,
964                     isOuterClassOfClassInThisCodebase = true
965                 ) as DefaultClassItem
966             }
967 
968         // Get the [TextTypeItemFactory] for the outer class, if any, from a previously stored one,
969         // otherwise use the [globalTypeItemFactory] as the [ClassItem] is a stub and so has no type
970         // parameters.
971         val outerClassTypeItemFactory = typeItemFactoryForClass(outerClass)
972 
973         // Create type parameter list and factory from the string and optional outer class factory.
974         val (typeParameterList, typeItemFactory) =
975             if (typeParameterListString == "")
976                 TypeParameterListAndFactory(TypeParameterList.NONE, outerClassTypeItemFactory)
977             else
978                 createTypeParameterList(
979                     outerClassTypeItemFactory,
980                     "class $qualifiedName",
981                     typeParameterListString,
982                 )
983 
984         // Decide which type parameter list and factory to actually use.
985         //
986         // If the class already exists then reuse its type parameter list and factory, otherwise use
987         // the newly created one.
988         //
989         // The reason for this is that otherwise any types parsed with the newly created factory
990         // would reference type parameters in the newly created list which are different to the ones
991         // belonging to the existing class.
992         val (actualTypeParameterList, actualTypeItemFactory) =
993             codebase.findClassInCodebase(qualifiedName)?.let { existingClass ->
994                 // Check to make sure that the type parameter lists are the same.
995                 val existingTypeParameterList = existingClass.typeParameterList
996                 val existingTypeParameterListString = existingTypeParameterList.toString()
997                 val normalizedTypeParameterListString = typeParameterList.toString()
998                 if (normalizedTypeParameterListString != existingTypeParameterListString) {
999                     val location = existingClass.fileLocation
1000                     throw ApiParseException(
1001                         "Inconsistent type parameter list for $qualifiedName, this has $normalizedTypeParameterListString but it was previously defined as $existingTypeParameterListString at $location",
1002                         classFileLocation
1003                     )
1004                 }
1005 
1006                 Pair(existingTypeParameterList, typeItemFactoryForClass(existingClass))
1007             }
1008                 ?: Pair(typeParameterList, typeItemFactory)
1009 
1010         return DeclaredClassTypeComponents(
1011             fullName = fullName,
1012             qualifiedName = qualifiedName,
1013             outerClass = outerClass,
1014             typeParameterList = actualTypeParameterList,
1015             typeItemFactory = actualTypeItemFactory,
1016         )
1017     }
1018 
1019     /**
1020      * If the [startingToken] contains the beginning of an annotation, pulls additional tokens from
1021      * [tokenizer] to complete the annotation, returning the full token. If there isn't an
1022      * annotation, returns the original [startingToken].
1023      *
1024      * When the method returns, the [tokenizer] will point to the token after the end of the
1025      * returned string.
1026      */
getAnnotationCompleteTokennull1027     private fun getAnnotationCompleteToken(tokenizer: Tokenizer, startingToken: String): String {
1028         return if (startingToken.contains('@')) {
1029             val prefix = startingToken.substringBefore('@')
1030             val annotationStart = startingToken.substring(startingToken.indexOf('@'))
1031             val annotation = getAnnotationSource(tokenizer, annotationStart)
1032             "$prefix$annotation"
1033         } else {
1034             tokenizer.requireToken()
1035             startingToken
1036         }
1037     }
1038 
1039     /**
1040      * If the [startingToken] is the beginning of an annotation, returns the annotation parsed from
1041      * the [tokenizer]. Returns null otherwise.
1042      *
1043      * When the method returns, the [tokenizer] will point to the token after the annotation.
1044      */
getAnnotationSourcenull1045     private fun getAnnotationSource(tokenizer: Tokenizer, startingToken: String): String? {
1046         var token = startingToken
1047         if (token.startsWith('@')) {
1048             // Annotation
1049             var annotation = token
1050 
1051             // Restore annotations that were shortened on export
1052             annotation = unshortenAnnotation(annotation)
1053             token = tokenizer.requireToken()
1054             if (token == "(") {
1055                 // Annotation arguments; potentially nested
1056                 var balance = 0
1057                 val start = tokenizer.offset() - 1
1058                 while (true) {
1059                     if (token == "(") {
1060                         balance++
1061                     } else if (token == ")") {
1062                         balance--
1063                         if (balance == 0) {
1064                             break
1065                         }
1066                     }
1067                     token = tokenizer.requireToken()
1068                 }
1069                 annotation += tokenizer.getStringFromOffset(start)
1070                 // Move the tokenizer so that when the method returns it points to the token after
1071                 // the end of the annotation.
1072                 tokenizer.requireToken()
1073             }
1074             return annotation
1075         } else {
1076             return null
1077         }
1078     }
1079 
1080     /**
1081      * Collects all the sequential annotations from the [tokenizer] beginning with [startingToken],
1082      * returning them as a (possibly empty) list.
1083      *
1084      * When the method returns, the [tokenizer] will point to the token after the annotation list.
1085      */
<lambda>null1086     private fun getAnnotations(tokenizer: Tokenizer, startingToken: String) = buildList {
1087         var token = startingToken
1088         while (true) {
1089             val annotationSource = getAnnotationSource(tokenizer, token) ?: break
1090             token = tokenizer.current
1091             DefaultAnnotationItem.create(codebase, annotationSource)?.let { annotationItem ->
1092                 add(annotationItem)
1093             }
1094         }
1095     }
1096 
1097     /**
1098      * Create [ParameterItem]s for the [containingCallable] from the [parameters] using the
1099      * [typeItemFactory] to create types.
1100      *
1101      * This is called from within the constructor of the [containingCallable] so must only access
1102      * its `name` and its reference. In particularly it must not access its
1103      * [CallableItem.parameters] property as this is called during its initialization.
1104      */
createParameterItemsnull1105     private fun createParameterItems(
1106         containingCallable: CallableItem,
1107         parameters: List<ParameterInfo>,
1108         typeItemFactory: TextTypeItemFactory
1109     ): List<ParameterItem> {
1110         val methodFingerprint = MethodFingerprint(containingCallable.name(), parameters.size)
1111         return parameters.map { it.create(containingCallable, typeItemFactory, methodFingerprint) }
1112     }
1113 
parseConstructornull1114     private fun parseConstructor(
1115         tokenizer: Tokenizer,
1116         containingClass: DefaultClassItem,
1117         classTypeItemFactory: TextTypeItemFactory,
1118         startingToken: String
1119     ) {
1120         var token = startingToken
1121         val method: ConstructorItem
1122 
1123         // Metalava: including annotations in file now
1124         val annotations = getAnnotations(tokenizer, token)
1125         token = tokenizer.current
1126         val modifiers = parseModifiers(tokenizer, token, annotations)
1127 
1128         // Get a TypeParameterList and accompanying TypeItemFactory
1129         val (typeParameterList, typeItemFactory) =
1130             parseTypeParameterList(tokenizer, classTypeItemFactory)
1131         token = tokenizer.current
1132 
1133         tokenizer.assertIdent(token)
1134         val name: String =
1135             token.substring(
1136                 token.lastIndexOf('.') + 1
1137             ) // For nested classes, strip outer classes from name
1138         val parameters = parseParameterList(tokenizer)
1139         token = tokenizer.requireToken()
1140         var throwsList = emptyList<ExceptionTypeItem>()
1141         if ("throws" == token) {
1142             throwsList = parseThrows(tokenizer, typeItemFactory)
1143             token = tokenizer.current
1144         }
1145         if (";" != token) {
1146             throw ApiParseException("expected ; found $token", tokenizer)
1147         }
1148 
1149         method =
1150             itemFactory.createConstructorItem(
1151                 fileLocation = tokenizer.fileLocation(),
1152                 modifiers = modifiers,
1153                 documentationFactory = ItemDocumentation.NONE_FACTORY,
1154                 name = name,
1155                 containingClass = containingClass,
1156                 typeParameterList = typeParameterList,
1157                 returnType = containingClass.type(),
1158                 parameterItemsFactory = { methodItem ->
1159                     createParameterItems(methodItem, parameters, typeItemFactory)
1160                 },
1161                 throwsTypes = throwsList,
1162                 // Signature files do not track implicit constructors, all constructors are treated
1163                 // the same as whether it was created by the compiler or in the source has no effect
1164                 // on the API surface.
1165                 implicitConstructor = false,
1166             )
1167         method.markForMainApiSurface()
1168 
1169         if (!containingClass.constructors().contains(method)) {
1170             containingClass.addConstructor(method)
1171         }
1172     }
1173 
parseMethodnull1174     private fun parseMethod(
1175         tokenizer: Tokenizer,
1176         cl: DefaultClassItem,
1177         classTypeItemFactory: TextTypeItemFactory,
1178         startingToken: String
1179     ) {
1180         var token = startingToken
1181         val method: MethodItem
1182 
1183         // Metalava: including annotations in file now
1184         val annotations = getAnnotations(tokenizer, token)
1185         token = tokenizer.current
1186         val modifiers = parseModifiers(tokenizer, token, annotations)
1187 
1188         // Get a TypeParameterList and accompanying TypeParameterScope
1189         val (typeParameterList, typeItemFactory) =
1190             parseTypeParameterList(tokenizer, classTypeItemFactory)
1191         token = tokenizer.current
1192         tokenizer.assertIdent(token)
1193 
1194         val returnTypeString: String
1195         val parameters: List<ParameterInfo>
1196         val name: String
1197         if (format.kotlinNameTypeOrder) {
1198             // Kotlin style: parse the name, the parameter list, then the return type.
1199             name = token
1200             parameters = parseParameterList(tokenizer)
1201             token = tokenizer.requireToken()
1202             if (token != ":") {
1203                 throw ApiParseException(
1204                     "Expecting \":\" after parameter list, found $token.",
1205                     tokenizer
1206                 )
1207             }
1208             token = tokenizer.requireToken()
1209             tokenizer.assertIdent(token)
1210             returnTypeString = scanForTypeString(tokenizer, token)
1211             token = tokenizer.current
1212         } else {
1213             // Java style: parse the return type, the name, and then the parameter list.
1214             returnTypeString = scanForTypeString(tokenizer, token)
1215             token = tokenizer.current
1216             tokenizer.assertIdent(token)
1217             name = token
1218             parameters = parseParameterList(tokenizer)
1219             token = tokenizer.requireToken()
1220         }
1221 
1222         val returnType =
1223             typeItemFactory.getMethodReturnType(
1224                 returnTypeString,
1225                 annotations,
1226                 MethodFingerprint(name, parameters.size),
1227                 cl.isAnnotationType()
1228             )
1229         synchronizeNullability(returnType, modifiers)
1230 
1231         if (cl.isInterface() && !modifiers.isDefault() && !modifiers.isStatic()) {
1232             modifiers.setAbstract(true)
1233         }
1234 
1235         var throwsList = emptyList<ExceptionTypeItem>()
1236         var defaultAnnotationMethodValue = ""
1237 
1238         when (token) {
1239             "throws" -> {
1240                 throwsList = parseThrows(tokenizer, typeItemFactory)
1241                 token = tokenizer.current
1242             }
1243             "default" -> {
1244                 defaultAnnotationMethodValue = parseDefault(tokenizer)
1245                 token = tokenizer.current
1246             }
1247         }
1248         if (";" != token) {
1249             throw ApiParseException("expected ; found $token", tokenizer)
1250         }
1251 
1252         method =
1253             itemFactory.createMethodItem(
1254                 fileLocation = tokenizer.fileLocation(),
1255                 modifiers = modifiers,
1256                 documentationFactory = ItemDocumentation.NONE_FACTORY,
1257                 name = name,
1258                 containingClass = cl,
1259                 typeParameterList = typeParameterList,
1260                 returnType = returnType,
1261                 parameterItemsFactory = { containingCallable ->
1262                     createParameterItems(containingCallable, parameters, typeItemFactory)
1263                 },
1264                 throwsTypes = throwsList,
1265                 annotationDefault = defaultAnnotationMethodValue,
1266             )
1267 
1268         // Ignore enum synthetic methods. They are no longer included in signature files as they add
1269         // no information. However, they did use to be included and so this filters them out to
1270         // ensure that the resulting Codebase is consistent with the original source Codebase.
1271         if (method.isEnumSyntheticMethod()) return
1272 
1273         method.markForMainApiSurface()
1274 
1275         if (appending) {
1276             // If the method already exists in the class item because it was defined in a previous
1277             // signature file then replace it with this one, otherwise just add this method.
1278             cl.replaceOrAddMethod(method)
1279         } else {
1280             // Just add the method to the class.
1281             cl.addMethod(method)
1282         }
1283     }
1284 
parseFieldnull1285     private fun parseField(
1286         tokenizer: Tokenizer,
1287         cl: DefaultClassItem,
1288         classTypeItemFactory: TextTypeItemFactory,
1289         startingToken: String,
1290         isEnumConstant: Boolean,
1291     ) {
1292         var token = startingToken
1293         val annotations = getAnnotations(tokenizer, token)
1294         token = tokenizer.current
1295         val modifiers = parseModifiers(tokenizer, token, annotations)
1296         token = tokenizer.current
1297         tokenizer.assertIdent(token)
1298 
1299         val typeString: String
1300         val name: String
1301         if (format.kotlinNameTypeOrder) {
1302             // Kotlin style: parse the name, then the type.
1303             name = parseNameWithColon(token, tokenizer)
1304             token = tokenizer.requireToken()
1305             tokenizer.assertIdent(token)
1306             typeString = scanForTypeString(tokenizer, token)
1307             token = tokenizer.current
1308         } else {
1309             // Java style: parse the name, then the type.
1310             typeString = scanForTypeString(tokenizer, token)
1311             token = tokenizer.current
1312             tokenizer.assertIdent(token)
1313             name = token
1314             token = tokenizer.requireToken()
1315         }
1316 
1317         // Get the optional value.
1318         val valueString =
1319             if ("=" == token) {
1320                 token = tokenizer.requireToken(false)
1321                 token.also { token = tokenizer.requireToken() }
1322             } else null
1323 
1324         // Parse the type string and then synchronize the field's nullability with the type.
1325         val type =
1326             classTypeItemFactory.getFieldType(
1327                 underlyingType = typeString,
1328                 isEnumConstant = isEnumConstant,
1329                 isFinal = modifiers.isFinal(),
1330                 isInitialValueNonNull = { valueString != null && valueString != "null" },
1331                 itemAnnotations = annotations,
1332             )
1333         synchronizeNullability(type, modifiers)
1334 
1335         // Parse the value string.
1336         val fieldValue =
1337             valueString?.let { FixedFieldValue(parseValue(type, valueString, tokenizer)) }
1338 
1339         if (";" != token) {
1340             throw ApiParseException("expected ; found $token", tokenizer)
1341         }
1342         val field =
1343             itemFactory.createFieldItem(
1344                 fileLocation = tokenizer.fileLocation(),
1345                 modifiers = modifiers,
1346                 documentationFactory = ItemDocumentation.NONE_FACTORY,
1347                 name = name,
1348                 containingClass = cl,
1349                 type = type,
1350                 isEnumConstant = isEnumConstant,
1351                 fieldValue = fieldValue,
1352             )
1353         field.markForMainApiSurface()
1354         cl.addField(field)
1355     }
1356 
parseModifiersnull1357     private fun parseModifiers(
1358         tokenizer: Tokenizer,
1359         startingToken: String?,
1360         annotations: List<AnnotationItem>
1361     ): MutableModifierList {
1362         var token = startingToken
1363         val modifiers = createModifiers(VisibilityLevel.PACKAGE_PRIVATE, annotations)
1364 
1365         processModifiers@ while (true) {
1366             token =
1367                 when (token) {
1368                     "public" -> {
1369                         modifiers.setVisibilityLevel(VisibilityLevel.PUBLIC)
1370                         tokenizer.requireToken()
1371                     }
1372                     "protected" -> {
1373                         modifiers.setVisibilityLevel(VisibilityLevel.PROTECTED)
1374                         tokenizer.requireToken()
1375                     }
1376                     "private" -> {
1377                         modifiers.setVisibilityLevel(VisibilityLevel.PRIVATE)
1378                         tokenizer.requireToken()
1379                     }
1380                     "internal" -> {
1381                         modifiers.setVisibilityLevel(VisibilityLevel.INTERNAL)
1382                         tokenizer.requireToken()
1383                     }
1384                     "static" -> {
1385                         modifiers.setStatic(true)
1386                         tokenizer.requireToken()
1387                     }
1388                     "final" -> {
1389                         modifiers.setFinal(true)
1390                         tokenizer.requireToken()
1391                     }
1392                     "deprecated" -> {
1393                         modifiers.setDeprecated(true)
1394                         tokenizer.requireToken()
1395                     }
1396                     "abstract" -> {
1397                         modifiers.setAbstract(true)
1398                         tokenizer.requireToken()
1399                     }
1400                     "transient" -> {
1401                         modifiers.setTransient(true)
1402                         tokenizer.requireToken()
1403                     }
1404                     "volatile" -> {
1405                         modifiers.setVolatile(true)
1406                         tokenizer.requireToken()
1407                     }
1408                     "sealed" -> {
1409                         modifiers.setSealed(true)
1410                         tokenizer.requireToken()
1411                     }
1412                     "default" -> {
1413                         modifiers.setDefault(true)
1414                         tokenizer.requireToken()
1415                     }
1416                     "synchronized" -> {
1417                         modifiers.setSynchronized(true)
1418                         tokenizer.requireToken()
1419                     }
1420                     "native" -> {
1421                         modifiers.setNative(true)
1422                         tokenizer.requireToken()
1423                     }
1424                     "strictfp" -> {
1425                         modifiers.setStrictFp(true)
1426                         tokenizer.requireToken()
1427                     }
1428                     "infix" -> {
1429                         modifiers.setInfix(true)
1430                         tokenizer.requireToken()
1431                     }
1432                     "operator" -> {
1433                         modifiers.setOperator(true)
1434                         tokenizer.requireToken()
1435                     }
1436                     "inline" -> {
1437                         modifiers.setInline(true)
1438                         tokenizer.requireToken()
1439                     }
1440                     "value" -> {
1441                         modifiers.setValue(true)
1442                         tokenizer.requireToken()
1443                     }
1444                     "suspend" -> {
1445                         modifiers.setSuspend(true)
1446                         tokenizer.requireToken()
1447                     }
1448                     "vararg" -> {
1449                         modifiers.setVarArg(true)
1450                         tokenizer.requireToken()
1451                     }
1452                     "fun" -> {
1453                         modifiers.setFunctional(true)
1454                         tokenizer.requireToken()
1455                     }
1456                     "data" -> {
1457                         modifiers.setData(true)
1458                         tokenizer.requireToken()
1459                     }
1460                     else -> break@processModifiers
1461                 }
1462         }
1463         return modifiers
1464     }
1465 
1466     /** Creates a [MutableModifierList], setting the deprecation based on the [annotations]. */
createModifiersnull1467     private fun createModifiers(
1468         visibility: VisibilityLevel,
1469         annotations: List<AnnotationItem>
1470     ): MutableModifierList {
1471         val modifiers = createMutableModifiers(visibility, annotations)
1472         // @Deprecated is also treated as a "modifier"
1473         if (annotations.any { it.qualifiedName == JAVA_LANG_DEPRECATED }) {
1474             modifiers.setDeprecated(true)
1475         }
1476         return modifiers
1477     }
1478 
parseValuenull1479     private fun parseValue(
1480         type: TypeItem,
1481         value: String?,
1482         fileLocationTracker: FileLocationTracker,
1483     ): Any? {
1484         return if (value != null) {
1485             if (type is PrimitiveTypeItem) {
1486                 parsePrimitiveValue(type, value, fileLocationTracker)
1487             } else if (type.isString()) {
1488                 if ("null" == value) {
1489                     null
1490                 } else {
1491                     javaUnescapeString(value.substring(1, value.length - 1))
1492                 }
1493             } else {
1494                 value
1495             }
1496         } else null
1497     }
1498 
parsePrimitiveValuenull1499     private fun parsePrimitiveValue(
1500         type: PrimitiveTypeItem,
1501         value: String,
1502         fileLocationTracker: FileLocationTracker,
1503     ): Any {
1504         return when (type.kind) {
1505             Primitive.BOOLEAN ->
1506                 if ("true" == value) java.lang.Boolean.TRUE else java.lang.Boolean.FALSE
1507             Primitive.BYTE,
1508             Primitive.SHORT,
1509             Primitive.INT -> Integer.valueOf(value)
1510             Primitive.LONG -> java.lang.Long.valueOf(value.substring(0, value.length - 1))
1511             Primitive.FLOAT ->
1512                 when (value) {
1513                     "(1.0f/0.0f)",
1514                     "(1.0f / 0.0f)" -> Float.POSITIVE_INFINITY
1515                     "(-1.0f/0.0f)",
1516                     "(-1.0f / 0.0f)" -> Float.NEGATIVE_INFINITY
1517                     "(0.0f/0.0f)",
1518                     "(0.0f / 0.0f)" -> Float.NaN
1519                     else -> java.lang.Float.valueOf(value)
1520                 }
1521             Primitive.DOUBLE ->
1522                 when (value) {
1523                     "(1.0/0.0)",
1524                     "(1.0 / 0.0)" -> Double.POSITIVE_INFINITY
1525                     "(-1.0/0.0)",
1526                     "(-1.0 / 0.0)" -> Double.NEGATIVE_INFINITY
1527                     "(0.0/0.0)",
1528                     "(0.0 / 0.0)" -> Double.NaN
1529                     else -> java.lang.Double.valueOf(value)
1530                 }
1531             Primitive.CHAR -> value.toInt().toChar()
1532             Primitive.VOID ->
1533                 throw ApiParseException(
1534                     "Found value $value assigned to void type",
1535                     fileLocationTracker
1536                 )
1537         }
1538     }
1539 
parsePropertynull1540     private fun parseProperty(
1541         tokenizer: Tokenizer,
1542         cl: DefaultClassItem,
1543         classTypeItemFactory: TextTypeItemFactory,
1544         startingToken: String
1545     ) {
1546         var token = startingToken
1547 
1548         // Metalava: including annotations in file now
1549         val annotations = getAnnotations(tokenizer, token)
1550         token = tokenizer.current
1551         val modifiers = parseModifiers(tokenizer, token, annotations)
1552 
1553         // Get a TypeParameterList and accompanying TypeParameterScope
1554         val (typeParameterList, typeItemFactory) =
1555             parseTypeParameterList(tokenizer, classTypeItemFactory)
1556         token = tokenizer.current
1557 
1558         val typeString: String
1559         val receiverNamePair: Pair<TypeItem?, String>
1560         if (format.kotlinNameTypeOrder) {
1561             // Kotlin style: parse the name, then the type.
1562             receiverNamePair = parsePropertyReceiverAndName(tokenizer, typeItemFactory)
1563             token = tokenizer.current
1564             typeString = scanForTypeString(tokenizer, token)
1565             token = tokenizer.current
1566         } else {
1567             // Java style: parse the type, then the name.
1568             typeString = scanForTypeString(tokenizer, token)
1569             receiverNamePair = parsePropertyReceiverAndName(tokenizer, typeItemFactory)
1570             token = tokenizer.current
1571         }
1572         val type = typeItemFactory.getGeneralType(typeString)
1573         synchronizeNullability(type, modifiers)
1574 
1575         if (";" != token) {
1576             throw ApiParseException("expected ; found $token", tokenizer)
1577         }
1578         val property =
1579             itemFactory.createPropertyItem(
1580                 fileLocation = tokenizer.fileLocation(),
1581                 modifiers = modifiers,
1582                 name = receiverNamePair.second,
1583                 containingClass = cl,
1584                 type = type,
1585                 receiver = receiverNamePair.first,
1586                 typeParameterList = typeParameterList,
1587             )
1588         property.markForMainApiSurface()
1589         cl.addProperty(property)
1590     }
1591 
1592     /**
1593      * Starting from the current token of [tokenizer], parses the optional receiver type and then
1594      * the name of a property.
1595      *
1596      * After the method returns, the caller should continue processing at the new current token of
1597      * [tokenizer], which will be the token after
1598      */
parsePropertyReceiverAndNamenull1599     private fun parsePropertyReceiverAndName(
1600         tokenizer: Tokenizer,
1601         typeItemFactory: TextTypeItemFactory
1602     ): Pair<TypeItem?, String> {
1603         // If there's no receiver, scanning for the type string should just return the name.
1604         // If there is a receiver, because of how the tokens are broken up, it should return
1605         // "receiver.name", which can then be split on the last "." to the receiver and name.
1606         val receiverAndName = scanForTypeString(tokenizer, tokenizer.current)
1607         val namePossiblyWithColon: String
1608         val receiverTypeString: String?
1609         if (receiverAndName.contains(".")) {
1610             namePossiblyWithColon = receiverAndName.substringAfterLast(".")
1611             receiverTypeString = receiverAndName.substringBeforeLast(".")
1612         } else {
1613             namePossiblyWithColon = receiverAndName
1614             receiverTypeString = null
1615         }
1616 
1617         val name =
1618             if (format.kotlinNameTypeOrder) {
1619                 parseNameWithColon(namePossiblyWithColon, tokenizer)
1620             } else {
1621                 tokenizer.assertIdent(namePossiblyWithColon)
1622                 namePossiblyWithColon
1623             }
1624         val receiverType = receiverTypeString?.let { typeItemFactory.getGeneralType(it) }
1625 
1626         return receiverType to name
1627     }
1628 
1629     /**
1630      * Parses a type parameter list enclosed in "<>", if one exists.
1631      *
1632      * Starts processing from the current token of [tokenizer]. If that token is not "<", returns an
1633      * empty type parameter list.
1634      *
1635      * After the method returns, the caller should continue processing at the new current token of
1636      * [tokenizer], which will be the token after the type parameter list, if it exists, or the same
1637      * as the original current token, if there was no type parameter list.
1638      */
parseTypeParameterListnull1639     private fun parseTypeParameterList(
1640         tokenizer: Tokenizer,
1641         enclosingTypeItemFactory: TextTypeItemFactory,
1642     ): TypeParameterListAndFactory<TextTypeItemFactory> {
1643         var token: String = tokenizer.current
1644         // No type parameters to parse. The current token is unchanged
1645         if ("<" != token) {
1646             return TypeParameterListAndFactory(TypeParameterList.NONE, enclosingTypeItemFactory)
1647         }
1648 
1649         val start = tokenizer.offset() - 1
1650         var balance = 1
1651         while (balance > 0) {
1652             token = tokenizer.requireToken()
1653             if (token == "<") {
1654                 balance++
1655             } else if (token == ">") {
1656                 balance--
1657             }
1658         }
1659         val typeParameterListString = tokenizer.getStringFromOffset(start)
1660         // Set the tokenizer to the next token, so that the caller should continue processing at
1661         // tokenizer.current (in alignment with the no type parameter case).
1662         tokenizer.requireToken()
1663         return if (typeParameterListString.isEmpty()) {
1664             TypeParameterListAndFactory(TypeParameterList.NONE, enclosingTypeItemFactory)
1665         } else {
1666             // Use the file location as a part of the description of the scope as at this point
1667             // there is no other information available.
1668             val scopeDescription = "${tokenizer.fileLocation()}"
1669             createTypeParameterList(
1670                 enclosingTypeItemFactory,
1671                 scopeDescription,
1672                 typeParameterListString
1673             )
1674         }
1675     }
1676 
1677     /**
1678      * Creates a [TypeParameterList] and accompanying [TypeParameterScope].
1679      *
1680      * The [typeParameterListString] should be the string representation of a list of type
1681      * parameters, like "<A>" or "<A, B extends java.lang.String, C>".
1682      *
1683      * @return a [Pair] of [TypeParameterList] and [TextTypeItemFactory] that contains those type
1684      *   parameters.
1685      */
createTypeParameterListnull1686     private fun createTypeParameterList(
1687         enclosingTypeItemFactory: TextTypeItemFactory,
1688         scopeDescription: String,
1689         typeParameterListString: String
1690     ): TypeParameterListAndFactory<TextTypeItemFactory> {
1691         // Split the type parameter list string into a list of strings, one for each type
1692         // parameter.
1693         val typeParameterStrings = TextTypeParser.typeParameterStrings(typeParameterListString)
1694 
1695         // Create the List<TypeParameterItem> and the corresponding TypeItemFactory that can be
1696         // used to resolve TypeParameterItems from the list. This performs the construction in two
1697         // stages to handle cycles between the parameters.
1698         return DefaultTypeParameterList.createTypeParameterItemsAndFactory(
1699             enclosingTypeItemFactory,
1700             scopeDescription,
1701             typeParameterStrings,
1702             // Create a `TextTypeParameterItem` from the type parameter string.
1703             { createTypeParameterItem(it) },
1704             // Create, set and return the [BoundsTypeItem] list.
1705             { typeItemFactory, typeParameterString ->
1706                 val boundsStringList = extractTypeParameterBoundsStringList(typeParameterString)
1707                 boundsStringList.map { typeItemFactory.getBoundsType(it) }
1708             },
1709         )
1710     }
1711 
1712     /**
1713      * Create a partially initialized [DefaultTypeParameterItem].
1714      *
1715      * This extracts the [TypeParameterItem.isReified] and [TypeParameterItem.name] from the
1716      * [typeParameterString] and creates a [DefaultTypeParameterItem] with those properties
1717      * initialized but the [DefaultTypeParameterItem.bounds] is not.
1718      */
createTypeParameterItemnull1719     private fun createTypeParameterItem(typeParameterString: String): DefaultTypeParameterItem {
1720         val length = typeParameterString.length
1721         var nameEnd = length
1722 
1723         val isReified = typeParameterString.startsWith("reified ")
1724         val nameStart =
1725             if (isReified) {
1726                 8 // "reified ".length
1727             } else {
1728                 0
1729             }
1730 
1731         for (i in nameStart until length) {
1732             val c = typeParameterString[i]
1733             if (!Character.isJavaIdentifierPart(c)) {
1734                 nameEnd = i
1735                 break
1736             }
1737         }
1738         val name = typeParameterString.substring(nameStart, nameEnd)
1739 
1740         // TODO: Type use annotations support will need to handle annotations on the parameter.
1741         val modifiers = createImmutableModifiers(VisibilityLevel.PUBLIC)
1742 
1743         return itemFactory.createTypeParameterItem(
1744             modifiers = modifiers,
1745             name = name,
1746             isReified = isReified,
1747         )
1748     }
1749 
1750     /**
1751      * Parses a list of parameters. Before calling, [tokenizer] should point to the token *before*
1752      * the opening `(` of the parameter list (the method starts by calling
1753      * [Tokenizer.requireToken]).
1754      *
1755      * When the method returns, [tokenizer] will point to the closing `)` of the parameter list.
1756      */
parseParameterListnull1757     private fun parseParameterList(
1758         tokenizer: Tokenizer,
1759     ): List<ParameterInfo> {
1760         val parameters = mutableListOf<ParameterInfo>()
1761         var token: String = tokenizer.requireToken()
1762         if ("(" != token) {
1763             throw ApiParseException("expected (, was $token", tokenizer)
1764         }
1765         token = tokenizer.requireToken()
1766         var index = 0
1767         while (true) {
1768             if (")" == token) {
1769                 // All parameters are parsed, return them.
1770                 return parameters
1771             }
1772 
1773             // Each item can be:
1774             //   optional-"optional" annotations optional-modifiers
1775             //   type-with-use-annotations-and-generics optional-name
1776 
1777             // Used to represent the presence of a default value, instead of showing the entire
1778             // default value
1779             val hasOptionalKeyword = token == "optional"
1780             if (hasOptionalKeyword) {
1781                 token = tokenizer.requireToken()
1782             }
1783 
1784             // Metalava: including annotations in file now
1785             val annotations = getAnnotations(tokenizer, token)
1786             token = tokenizer.current
1787             val modifiers = parseModifiers(tokenizer, token, annotations)
1788             token = tokenizer.current
1789 
1790             val typeString: String
1791             val name: String
1792             val publicName: String?
1793             if (format.kotlinNameTypeOrder) {
1794                 // Kotlin style: parse the name (only considered a public name if it is not `_`,
1795                 // which is used as a placeholder for params without public names), then the type.
1796                 name = parseNameWithColon(token, tokenizer)
1797                 publicName =
1798                     if (name == "_") {
1799                         null
1800                     } else {
1801                         name
1802                     }
1803                 token = tokenizer.requireToken()
1804                 // Token should now represent the type
1805                 typeString = scanForTypeString(tokenizer, token)
1806                 token = tokenizer.current
1807             } else {
1808                 // Java style: parse the type, then the public name if it has one.
1809                 typeString = scanForTypeString(tokenizer, token)
1810                 token = tokenizer.current
1811                 if (Tokenizer.isIdent(token) && token != "=") {
1812                     name = token
1813                     publicName = name
1814                     token = tokenizer.requireToken()
1815                 } else {
1816                     name = "arg" + (index + 1)
1817                     publicName = null
1818                 }
1819             }
1820 
1821             when (token) {
1822                 "," -> {
1823                     token = tokenizer.requireToken()
1824                 }
1825                 ")" -> {
1826                     // closing parenthesis
1827                 }
1828                 else -> {
1829                     throw ApiParseException("expected , or ), found $token", tokenizer)
1830                 }
1831             }
1832 
1833             // Select the DefaultValue for the parameter.
1834             val defaultValue =
1835                 if (hasOptionalKeyword) {
1836                     // It has an optional keyword, so it has a default value but the actual value is
1837                     // not known.
1838                     ParameterDefaultValue.UNKNOWN
1839                 } else {
1840                     // It does not have an optional keyword so it has no default value.
1841                     ParameterDefaultValue.NONE
1842                 }
1843             parameters.add(
1844                 ParameterInfo(
1845                     name,
1846                     publicName,
1847                     defaultValue,
1848                     typeString,
1849                     modifiers,
1850                     tokenizer.fileLocation(),
1851                     index
1852                 )
1853             )
1854             index++
1855         }
1856     }
1857 
1858     /**
1859      * Container for parsed information on a parameter. This is an intermediate step before a
1860      * [ParameterItem] is created, which is needed because
1861      * [TextTypeItemFactory.getMethodParameterType] requires a [MethodFingerprint] with the total
1862      * number of method parameters.
1863      */
1864     private inner class ParameterInfo(
1865         val name: String,
1866         val publicName: String?,
1867         val defaultValue: ParameterDefaultValue,
1868         val typeString: String,
1869         val modifiers: MutableModifierList,
1870         val location: FileLocation,
1871         val index: Int
1872     ) {
1873         /** Turn this [ParameterInfo] into a [ParameterItem] by parsing the [typeString]. */
createnull1874         fun create(
1875             containingCallable: CallableItem,
1876             typeItemFactory: TextTypeItemFactory,
1877             methodFingerprint: MethodFingerprint
1878         ): ParameterItem {
1879             val type =
1880                 typeItemFactory.getMethodParameterType(
1881                     typeString,
1882                     modifiers.annotations(),
1883                     methodFingerprint,
1884                     index,
1885                     modifiers.isVarArg()
1886                 )
1887             synchronizeNullability(type, modifiers)
1888 
1889             val parameter =
1890                 itemFactory.createParameterItem(
1891                     fileLocation = location,
1892                     modifiers = modifiers,
1893                     name = name,
1894                     publicNameProvider = { publicName },
1895                     containingCallable = containingCallable,
1896                     parameterIndex = index,
1897                     type = type,
1898                     defaultValueFactory = { defaultValue },
1899                 )
1900 
1901             return parameter
1902         }
1903     }
1904 
parseDefaultnull1905     private fun parseDefault(tokenizer: Tokenizer): String {
1906         return buildString {
1907             while (true) {
1908                 val token = tokenizer.requireToken()
1909                 if (";" == token) {
1910                     break
1911                 } else {
1912                     append(token)
1913                 }
1914             }
1915         }
1916     }
1917 
parseThrowsnull1918     private fun parseThrows(
1919         tokenizer: Tokenizer,
1920         typeItemFactory: TextTypeItemFactory,
1921     ): List<ExceptionTypeItem> {
1922         var token = tokenizer.requireToken()
1923         val throwsList = buildList {
1924             var comma = true
1925             while (true) {
1926                 when (token) {
1927                     ";" -> {
1928                         break
1929                     }
1930                     "," -> {
1931                         if (comma) {
1932                             throw ApiParseException("Expected exception, got ','", tokenizer)
1933                         }
1934                         comma = true
1935                     }
1936                     else -> {
1937                         if (!comma) {
1938                             throw ApiParseException("Expected ',' or ';' got $token", tokenizer)
1939                         }
1940                         comma = false
1941                         val exceptionType = typeItemFactory.getExceptionType(token)
1942                         add(exceptionType)
1943                     }
1944                 }
1945                 token = tokenizer.requireToken()
1946             }
1947         }
1948 
1949         return throwsList
1950     }
1951 
1952     /**
1953      * Scans the token stream from [tokenizer] for a type string, starting with the [startingToken]
1954      * and ensuring that the full type string is gathered, even when there are type-use annotations.
1955      *
1956      * After this method is called, `tokenizer.current` will point to the token after the type.
1957      *
1958      * Note: this **should not** be used when the token after the type could contain annotations,
1959      * such as when multiple types appear as consecutive tokens. (This happens in the `implements`
1960      * list of a class definition, e.g. `class Foo implements test.pkg.Bar test.pkg.@A Baz`.)
1961      *
1962      * To handle arrays with type-use annotations, this looks forward at the next token and includes
1963      * it if it contains an annotation. This is necessary to handle type strings like "Foo @A []".
1964      */
scanForTypeStringnull1965     private fun scanForTypeString(tokenizer: Tokenizer, startingToken: String): String {
1966         var prev = getAnnotationCompleteToken(tokenizer, startingToken)
1967         var type = prev
1968         var token = tokenizer.current
1969         // Look both at the last used token and the next one:
1970         // If the last token has annotations, the type string was broken up by annotations, and the
1971         // next token is also part of the type.
1972         // If the next token has annotations, this is an array type like "Foo @A []", so the next
1973         // token is part of the type.
1974         while (isIncompleteTypeToken(prev) || isIncompleteTypeToken(token)) {
1975             token = getAnnotationCompleteToken(tokenizer, token)
1976             type += " $token"
1977             prev = token
1978             token = tokenizer.current
1979         }
1980         return type
1981     }
1982 
1983     /**
1984      * Synchronize nullability annotations on the API item and [TypeNullability].
1985      *
1986      * If the type string uses a Kotlin nullability suffix, this adds an annotation representing
1987      * that nullability to [modifiers].
1988      *
1989      * @param typeItem the type of the API item.
1990      * @param modifiers the API item's modifiers.
1991      */
synchronizeNullabilitynull1992     private fun synchronizeNullability(typeItem: TypeItem, modifiers: MutableModifierList) {
1993         if (typeParser.kotlinStyleNulls) {
1994             // Add an annotation to the context item for the type's nullability if applicable.
1995             val annotationToAdd =
1996                 // Treat varargs as non-null for consistency with the psi model.
1997                 if (typeItem is ArrayTypeItem && typeItem.isVarargs) {
1998                     ANDROIDX_NONNULL
1999                 } else {
2000                     val nullability = typeItem.modifiers.nullability
2001                     if (typeItem !is PrimitiveTypeItem && nullability == TypeNullability.NONNULL) {
2002                         ANDROIDX_NONNULL
2003                     } else if (nullability == TypeNullability.NULLABLE) {
2004                         ANDROIDX_NULLABLE
2005                     } else {
2006                         // No annotation to add, return.
2007                         return
2008                     }
2009                 }
2010             modifiers.addAnnotation(codebase.createAnnotation("@$annotationToAdd"))
2011         }
2012     }
2013 
2014     /**
2015      * Determines whether the [type] is an incomplete type string broken up by annotations. This is
2016      * the case when there's an annotation that isn't contained within a parameter list (because
2017      * [Tokenizer.requireToken] handles not breaking in the middle of a parameter list).
2018      */
isIncompleteTypeTokennull2019     private fun isIncompleteTypeToken(type: String): Boolean {
2020         val firstAnnotationIndex = type.indexOf('@')
2021         val paramStartIndex = type.indexOf('<')
2022         val lastAnnotationIndex = type.lastIndexOf('@')
2023         val paramEndIndex = type.lastIndexOf('>')
2024         return firstAnnotationIndex != -1 &&
2025             (paramStartIndex == -1 ||
2026                 firstAnnotationIndex < paramStartIndex ||
2027                 paramEndIndex == -1 ||
2028                 paramEndIndex < lastAnnotationIndex)
2029     }
2030 
2031     /**
2032      * For Kotlin-style name/type ordering in signature files, the name is generally followed by a
2033      * colon (besides methods, where the colon comes after the parameter list). This method takes
2034      * the name [token] and removes the trailing colon, throwing an [ApiParseException] if one isn't
2035      * present (the [tokenizer] is only used for context for the error, if needed).
2036      */
parseNameWithColonnull2037     private fun parseNameWithColon(token: String, tokenizer: Tokenizer): String {
2038         if (!token.endsWith(':')) {
2039             throw ApiParseException("Expecting name ending with \":\" but found $token.", tokenizer)
2040         }
2041         return token.removeSuffix(":")
2042     }
2043 
qualifiedNamenull2044     private fun qualifiedName(pkg: String, className: String): String {
2045         return "$pkg.$className"
2046     }
2047 
2048     private val stats
2049         get() =
2050             Stats(
2051                 codebase.getPackages().allClasses().count(),
2052                 typeParser.requests,
2053                 typeParser.cacheSkip,
2054                 typeParser.cacheHit,
2055                 typeParser.cacheSize,
2056             )
2057 
2058     data class Stats(
2059         val totalClasses: Int,
2060         val typeCacheRequests: Int,
2061         val typeCacheSkip: Int,
2062         val typeCacheHit: Int,
2063         val typeCacheSize: Int,
2064     )
2065 }
2066