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