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