1 package com.android.tools.metalava.doclava1
2 
3 import com.android.tools.metalava.Options
4 import com.android.tools.metalava.model.ClassItem
5 import com.android.tools.metalava.model.Item
6 import com.android.tools.metalava.model.MemberItem
7 import com.android.tools.metalava.model.PackageItem
8 import com.android.tools.metalava.options
9 import java.util.function.Predicate
10 
11 // Ported from doclava1
12 
13 /**
14  * Predicate that decides if the given member should be considered part of an
15  * API surface area. To make the most accurate decision, it searches for
16  * signals on the member, all containing classes, and all containing packages.
17  */
18 class ApiPredicate(
19     /**
20      * Set if the value of [MemberItem.hasShowAnnotation] should be
21      * ignored. That is, this predicate will assume that all encountered members
22      * match the "shown" requirement.
23      *
24      * This is typically useful when generating "current.txt", when no
25      * [Options.showAnnotations] have been defined.
26      */
27     val ignoreShown: Boolean = options.showUnannotated,
28 
29     /**
30      * Set if the value of [MemberItem.removed] should be ignored.
31      * That is, this predicate will assume that all encountered members match
32      * the "removed" requirement.
33      *
34      * This is typically useful when generating "removed.txt", when it's okay to
35      * reference both current and removed APIs.
36      */
37     private val ignoreRemoved: Boolean = false,
38 
39     /**
40      * Set what the value of [MemberItem.removed] must be equal to in
41      * order for a member to match.
42      *
43      * This is typically useful when generating "removed.txt", when you only
44      * want to match members that have actually been removed.
45      */
46     private val matchRemoved: Boolean = false,
47 
48     /** Whether we allow matching items loaded from jar files instead of sources */
49     private val allowClassesFromClasspath: Boolean = options.allowClassesFromClasspath,
50 
51     /** Whether we should include doc-only items */
52     private val includeDocOnly: Boolean = false,
53 
54     /** Whether to include "for stub purposes" APIs. See [Options.showForStubPurposesAnnotations] */
55     private val includeApisForStubPurposes: Boolean = true
56 ) : Predicate<Item> {
57 
testnull58     override fun test(member: Item): Boolean {
59         // Type Parameter references (e.g. T) aren't actual types, skip all visibility checks
60         if (member is ClassItem && member.isTypeParameter) {
61             return true
62         }
63 
64         if (!allowClassesFromClasspath && member.isFromClassPath()) {
65             return false
66         }
67 
68         var visible = member.isPublic || member.isProtected || (member.isInternal && member.hasShowAnnotation()) // TODO: Should this use checkLevel instead?
69         var hidden = member.hidden
70         if (!visible || hidden) {
71             return false
72         }
73         if (!includeApisForStubPurposes && includeOnlyForStubPurposes(member)) {
74             return false
75         }
76 
77         var hasShowAnnotation = ignoreShown || member.hasShowAnnotation()
78         var docOnly = member.docOnly
79         var removed = member.removed
80 
81         var clazz: ClassItem? = when (member) {
82             is MemberItem -> member.containingClass()
83             is ClassItem -> member
84             else -> null
85         }
86 
87         if (clazz != null) {
88             var pkg: PackageItem? = clazz.containingPackage()
89             while (pkg != null) {
90                 hidden = hidden or pkg.hidden
91                 docOnly = docOnly or pkg.docOnly
92                 removed = removed or pkg.removed
93                 pkg = pkg.containingPackage()
94             }
95         }
96         while (clazz != null) {
97             visible = visible and (clazz.isPublic || clazz.isProtected ||
98                 (clazz.isInternal && clazz.hasShowAnnotation())
99                 )
100             hasShowAnnotation = hasShowAnnotation or (ignoreShown || clazz.hasShowAnnotation())
101             hidden = hidden or clazz.hidden
102             docOnly = docOnly or clazz.docOnly
103             removed = removed or clazz.removed
104             clazz = clazz.containingClass()
105         }
106 
107         if (ignoreRemoved) {
108             removed = matchRemoved
109         }
110 
111         if (docOnly && includeDocOnly) {
112             docOnly = false
113         }
114 
115         return visible && hasShowAnnotation && !hidden && !docOnly && removed == matchRemoved
116     }
117 
118     /**
119      * Returns true, if an item should be included only for "stub" purposes; that is,
120      * the item does *not* have a [Options.showAnnotations] annotation but
121      * has a [Options.showForStubPurposesAnnotations] annotation.
122      */
includeOnlyForStubPurposesnull123     private fun includeOnlyForStubPurposes(item: Item): Boolean {
124         if (options.showForStubPurposesAnnotations.isEmpty()) {
125             return false
126         }
127 
128         // If the item has a "show" annotation, then return whether it has a "for stubs" annotation
129         // or not.
130         //
131         // Note, If the item does not have a show annotation, then it can't have a "for stubs" one,
132         // because the later must be a subset of the former, which we don't detect in *this*
133         // run (unfortunately it's hard to do so due to how things work), but when metalava
134         // is executed for the parent API, we'd detect it as
135         // [Issues.SHOWING_MEMBER_IN_HIDDEN_CLASS].
136         if (item.hasShowAnnotationInherited()) {
137             return item.hasShowForStubPurposesAnnotationInherited()
138         }
139         // If this item has neither --show-annotation nor --parent-api-annotation,
140         // Then defer to the "parent" item (i.e. the enclosing class or package).
141         return item.parent()?.let { includeOnlyForStubPurposes(it) } ?: false
142     }
143 }
144