1 /* <lambda>null2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.tools.metalava.model.item 18 19 import com.android.tools.metalava.model.PackageItem 20 import com.android.tools.metalava.model.PackageList 21 import com.android.tools.metalava.model.VisibilityLevel 22 import java.util.HashMap 23 24 private const val PACKAGE_ESTIMATE = 500 25 26 typealias PackageItemFactory = (String, PackageDoc, PackageItem?) -> DefaultPackageItem 27 28 class PackageTracker(private val packageItemFactory: PackageItemFactory) { 29 /** Map from package name to [DefaultPackageItem] of all packages in this. */ 30 private val packagesByName = HashMap<String, DefaultPackageItem>(PACKAGE_ESTIMATE) 31 32 val size 33 get() = packagesByName.size 34 35 fun getPackages(): PackageList { 36 val list = packagesByName.values.toMutableList() 37 list.sortWith(PackageItem.comparator) 38 return PackageList(list) 39 } 40 41 fun findPackage(pkgName: String): DefaultPackageItem? { 42 return packagesByName[pkgName] 43 } 44 45 /** 46 * Searches for the package with [packageName] in this tracker and if not found creates the 47 * corresponding [DefaultPackageItem], supply additional information from [packageDocs] and adds 48 * the newly created [DefaultPackageItem] to this tracker. 49 * 50 * If the [DefaultPackageItem] exists and [PackageDocs] contains [PackageDoc.modifiers] for the 51 * package then make sure that the existing [DefaultPackageItem] has the same 52 * [DefaultPackageItem.modifiers], if not throw an exception. 53 * 54 * @param packageName the name of the package to create. 55 * @param packageDocs provides additional information needed for creating a package. 56 * @return the [DefaultPackageItem] that was found or created. 57 */ 58 fun findOrCreatePackage( 59 packageName: String, 60 packageDocs: PackageDocs = PackageDocs.EMPTY, 61 ): DefaultPackageItem { 62 // Get the `PackageDoc`, if any, to use for creating this package. 63 val packageDoc = packageDocs[packageName] 64 65 // Check to see if the package already exists, if it does then return it. 66 findPackage(packageName)?.let { existing -> 67 // If the same package showed up multiple times, make sure they have the same modifiers. 68 // (Packages can't have public/private/etc., but they can have annotations, which are 69 // part of ModifierList.) 70 val modifiers = packageDoc.modifiers 71 if (modifiers != null && modifiers != existing.modifiers) { 72 error( 73 String.format( 74 "Contradicting declaration of package %s." + 75 " Previously seen with modifiers \"%s\", but now with \"%s\"", 76 packageName, 77 existing.modifiers, 78 modifiers 79 ), 80 ) 81 } 82 83 return existing 84 } 85 86 // Unless this is the root package, it has a containing package so get that before creating 87 // this package, so it can be passed into the `packageItemFactory`. 88 val containingPackageName = getContainingPackageName(packageName) 89 val containingPackage = 90 if (containingPackageName == null) null 91 else findOrCreatePackage(containingPackageName, packageDocs) 92 93 val packageItem = packageItemFactory(packageName, packageDoc, containingPackage) 94 95 // The packageItemFactory may provide its own modifiers so check to make sure that they are 96 // public. 97 if (packageItem.modifiers.getVisibilityLevel() != VisibilityLevel.PUBLIC) 98 error("Package $packageItem is not public") 99 100 addPackage(packageItem) 101 102 return packageItem 103 } 104 105 /** 106 * Gets the name of [packageName]'s containing package or `null` if [packageName] is `""`, i.e. 107 * the root package. 108 */ 109 private fun getContainingPackageName(packageName: String): String? = 110 if (packageName == "") null 111 else 112 packageName.lastIndexOf('.').let { index -> 113 if (index == -1) { 114 "" 115 } else { 116 packageName.substring(0, index) 117 } 118 } 119 120 /** Add the package to this. */ 121 fun addPackage(packageItem: DefaultPackageItem) { 122 packagesByName[packageItem.qualifiedName()] = packageItem 123 } 124 125 /** 126 * Create and track [PackageItem]s for every entry in [packageDocs] and make sure there is a 127 * root package. 128 */ 129 fun createInitialPackages(packageDocs: PackageDocs) { 130 // Create packages for all the documentation packages. 131 for (packageName in packageDocs.packageNames) { 132 findOrCreatePackage(packageName, packageDocs) 133 } 134 135 // Make sure that there is a root package. 136 findOrCreatePackage("", packageDocs) 137 } 138 } 139