1 package com.android.tools.metalava
2 
3 import com.android.tools.metalava.cli.common.MetalavaCliException
4 import com.android.tools.metalava.model.PackageItem
5 import java.io.File
6 import java.util.function.Predicate
7 
8 /**
9  * We permit a number of different styles:
10  * - exact match (foo)
11  * - prefix match (foo*, probably not intentional)
12  * - subpackage match (foo.*)
13  * - package and subpackage match (foo:foo.*)
14  * - explicit addition (+foo.*)
15  * - subtraction (+*:-foo.*)
16  *
17  * Real examples: args: "-stubpackages com.android.test.power ", args: "-stubpackages android.car*
18  * ", args: "-stubpackages com.android.ahat:com.android.ahat.*", args:
19  * "-force-convert-to-warning-nullability-annotations +*:-android.*:+android.icu.*:-dalvik.*
20  *
21  * Note that doclava does *not* include subpackages by default: -stubpackage foo will match only
22  * foo, not foo.bar. Note also that "foo.*" will not match "foo", so doclava required you to supply
23  * both: "foo:foo.*".
24  *
25  * In metalava we've changed that: it's not likely that you want to match any subpackage of foo but
26  * not foo itself, so foo.* is taken to mean "foo" and "foo.*".
27  */
28 class PackageFilter {
29     val components: MutableList<PackageFilterComponent> = mutableListOf()
30 
matchesnull31     fun matches(qualifiedName: String): Boolean {
32         for (component in components.reversed()) {
33             if (component.filter.test(qualifiedName)) {
34                 return component.treatAsPositiveMatch
35             }
36         }
37         return false
38     }
39 
addPackagesnull40     fun addPackages(path: String) {
41         for (arg in path.split(File.pathSeparatorChar)) {
42             val treatAsPositiveMatch = !arg.startsWith("-")
43             val pkg = arg.removePrefix("-").removePrefix("+")
44             val index = pkg.indexOf('*')
45             if (index != -1) {
46                 if (index < pkg.length - 1) {
47                     throw MetalavaCliException(
48                         stderr =
49                             "Wildcards in stub packages must be at the end of the package: $pkg)"
50                     )
51                 }
52                 val prefix = pkg.removeSuffix("*")
53                 if (prefix.endsWith(".")) {
54                     // In doclava, "foo.*" does not match "foo", but we want to do that.
55                     val exact = prefix.substring(0, prefix.length - 1)
56                     add(StringEqualsPredicate(exact), treatAsPositiveMatch)
57                 }
58                 add(StringPrefixPredicate(prefix), treatAsPositiveMatch)
59             } else {
60                 add(StringEqualsPredicate(pkg), treatAsPositiveMatch)
61             }
62         }
63     }
64 
addnull65     fun add(predicate: Predicate<String>, treatAsPositiveMatch: Boolean) {
66         components.add(PackageFilterComponent(predicate, treatAsPositiveMatch))
67     }
68 
matchesnull69     fun matches(packageItem: PackageItem): Boolean {
70         return matches(packageItem.qualifiedName())
71     }
72 
73     companion object {
parsenull74         fun parse(path: String): PackageFilter {
75             val filter = PackageFilter()
76             filter.addPackages(path)
77             return filter
78         }
79     }
80 }
81 
82 class StringPrefixPredicate(private val acceptedPrefix: String) : Predicate<String> {
testnull83     override fun test(candidatePackage: String): Boolean {
84         return candidatePackage.startsWith(acceptedPrefix)
85     }
86 }
87 
88 class StringEqualsPredicate(val acceptedPackage: String) : Predicate<String> {
testnull89     override fun test(candidatePackage: String): Boolean {
90         return candidatePackage == acceptedPackage
91     }
92 }
93 
94 /**
95  * One element of a PackageFilter. Detects packages and either either includes or excludes them from
96  * the filter
97  */
98 class PackageFilterComponent(val filter: Predicate<String>, val treatAsPositiveMatch: Boolean)
99