• 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 (
110                 clazz.isPublic || clazz.isProtected ||
111                     (clazz.isInternal && clazz.hasShowAnnotation())
112                 )
113             hasShowAnnotation = hasShowAnnotation or (ignoreShown || clazz.hasShowAnnotation())
114             hidden = hidden or clazz.hidden
115             docOnly = docOnly or clazz.docOnly
116             removed = removed or clazz.removed
117             clazz = clazz.containingClass()
118         }
119 
120         if (ignoreRemoved) {
121             removed = matchRemoved
122         }
123 
124         if (docOnly && includeDocOnly) {
125             docOnly = false
126         }
127 
128         return visible && hasShowAnnotation && !hidden && !docOnly && removed == matchRemoved
129     }
130 
131     /**
132      * Returns true, if an item should be included only for "stub" purposes; that is,
133      * the item does *not* have a [Options.showAnnotations] annotation but
134      * has a [Options.showForStubPurposesAnnotations] annotation.
135      */
includeOnlyForStubPurposesnull136     private fun includeOnlyForStubPurposes(item: Item): Boolean {
137         if (options.showForStubPurposesAnnotations.isEmpty()) {
138             return false
139         }
140 
141         // If the item has a "show" annotation, then return whether it *only* has a "for stubs"
142         // show annotation or not.
143         //
144         // Note, If the item does not have a show annotation, then it can't have a "for stubs" one,
145         // because the later must be a subset of the former, which we don't detect in *this*
146         // run (unfortunately it's hard to do so due to how things work), but when metalava
147         // is executed for the parent API, we'd detect it as
148         // [Issues.SHOWING_MEMBER_IN_HIDDEN_CLASS].
149         if (item.hasShowAnnotationInherited()) {
150             return item.onlyShowForStubPurposesInherited()
151         }
152         // If this item has neither --show-annotation nor --parent-api-annotation,
153         // Then defer to the "parent" item (i.e. the enclosing class or package).
154         return item.parent()?.let { includeOnlyForStubPurposes(it) } ?: false
155     }
156 }
157