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