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