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