xref: /aosp_15_r20/tools/metalava/metalava-model/src/main/java/com/android/tools/metalava/model/item/PackageTracker.kt (revision 115816f9299ab6ddd6b9673b81f34e707f6bacab)
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