xref: /aosp_15_r20/tools/metalava/metalava-model/src/main/java/com/android/tools/metalava/model/ApiVariantSelectors.kt (revision 115816f9299ab6ddd6b9673b81f34e707f6bacab)
1 /*
<lambda>null2  * Copyright (C) 2024 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.model
18 
19 import com.android.tools.metalava.reporter.Issues
20 
21 /** A factory that will create an [ApiVariantSelectors] for a specific [Item]. */
22 typealias ApiVariantSelectorsFactory = (Item) -> ApiVariantSelectors
23 
24 /** Contains properties that select which, if any, variant of an API an [Item] belongs in. */
25 sealed class ApiVariantSelectors {
26     /**
27      * Indicates whether the item was explicitly hidden in the source, e.g. via an `@hide` javadoc
28      * tag in its [Item.documentation], or a hide annotation directly on the [Item].
29      */
30     abstract val originallyHidden: Boolean
31 
32     /**
33      * Indicates whether children of an [Item] should be hidden, i.e. should not be included in ANY
34      * API surface variant.
35      *
36      * Initially set to [originallyHidden] but updated due to inheritance.
37      */
38     internal abstract val inheritableHidden: Boolean
39 
40     /**
41      * Indicates whether the [Item] is accessible, and its enclosing classes are accessible.
42      *
43      * An [Item] is accessible if it is either `public` or `protected`. In Kotlin it is also
44      * accessible if it is `internal` as long as it is annotated with `@PublishedApi`. However, that
45      * annotation is not treated specially in Metalava, instead it relies on the user to specify
46      * `@PublishedApi` as a show annotation and this just assumes that any show annotation is enough
47      * to make it accessible.
48      */
49     abstract val accessible: Boolean
50 
51     /**
52      * Indicates whether the [Item] should be hidden, i.e. should not be included in ANY API surface
53      * variant.
54      *
55      * Initially set to [inheritableHidden] but updated due to show annotations.
56      */
57     abstract val hidden: Boolean
58 
59     /**
60      * Indicates whether the [Item] should be included in the doc only API surface variant.
61      *
62      * Initially set to `true` if the [Item.documentation] contains `@doconly` but updated due to
63      * inheritance.
64      */
65     abstract val docOnly: Boolean
66 
67     /**
68      * Indicates whether the [Item] should be in the removed API surface variant.
69      *
70      * Initially set to `true` if the [Item.documentation] contains `@removed` but updated due to
71      * inheritance.
72      */
73     abstract val removed: Boolean
74 
75     /** Determines whether this item will be shown as part of the API or not. */
76     abstract val showability: Showability
77 
78     /** Create a duplicate of this for the specified [Item]. */
79     abstract fun duplicate(item: Item): ApiVariantSelectors
80 
81     /**
82      * Update the mutable properties of this by inheriting state from the parent selectors, if
83      * available.
84      */
85     abstract fun inheritInto()
86 
87     companion object {
88         /**
89          * An [ApiVariantSelectors] factory that will always return an immutable
90          * [ApiVariantSelectors]. It will return `false` for all the properties and throw an error
91          * on any attempt to set a property.
92          */
93         val IMMUTABLE_FACTORY: ApiVariantSelectorsFactory = { Immutable }
94 
95         /**
96          * An [ApiVariantSelectors] factory that will return a new, mutable, [ApiVariantSelectors]
97          * for each [SelectableItem].
98          *
99          * This cannot be used on an [Item] that is not a [SelectableItem], use [IMMUTABLE_FACTORY]
100          * instead.
101          */
102         val MUTABLE_FACTORY: ApiVariantSelectorsFactory = {
103             if (it is SelectableItem) Mutable(it)
104             else error("Cannot create Mutable for non-SelectableItem, use Immutable instead")
105         }
106     }
107 
108     /**
109      * An immutable [ApiVariantSelectors] that will return `false` for all the properties and fail
110      * on any attempt to set the `var` properties.
111      */
112     @Suppress("ConvertObjectToDataObject") // Requires language level 1.9
113     private object Immutable : ApiVariantSelectors() {
114 
115         override val originallyHidden: Boolean
116             get() = false
117 
118         override val inheritableHidden: Boolean
119             get() = false
120 
121         /**
122          * Defaults to `true` as this is used by `Item`s loaded from an API signature file which
123          * typically only contains accessible `Item`s. It is possible that it could contain
124          * inaccessible `Item`s but at the moment that is not supported.
125          */
126         override val accessible: Boolean
127             get() = true
128 
129         override val hidden: Boolean
130             get() = false
131 
132         override val docOnly: Boolean
133             get() = false
134 
135         override var removed: Boolean
136             get() = false
137             set(value) {
138                 error("Cannot set `removed` to $value")
139             }
140 
141         override val showability: Showability
142             get() = Showability.NO_EFFECT
143 
144         override fun duplicate(item: Item): ApiVariantSelectors = this
145 
146         override fun inheritInto() = error("Cannot inheritInto() $this")
147 
148         override fun toString() = "Immutable"
149     }
150 
151     /**
152      * A mutable [ApiVariantSelectors].
153      *
154      * [originallyHidden] will be `true` if it's [item]'s documentation contains one of `@hide`,
155      * `@pending` or `@suppress` or its [SelectableItem] has a hide annotation associated with it.
156      *
157      * Unless [hidden] is written before reading then it will default to `true` if
158      * [originallyHidden] is `true` and it does not have any show annotations.
159      *
160      * [docOnly] will be initialized to `true` if it's [item]'s documentation contains `@doconly`.
161      *
162      * [removed] will be initialized to `true` if it's [item]'s documentation contains `@removed`.
163      *
164      * This uses bits in [propertyHasBeenSetBits] and [propertyValueBits] to handle lazy
165      * initialization and store the value. The main purpose of using bit masks is not primarily
166      * performance or to reduce storage (although keeping that down is a factor) but rather to
167      * support lazy initialization with optional setters without duplicating lots of complicated
168      * code.
169      */
170     private class Mutable(private val item: SelectableItem) : ApiVariantSelectors() {
171 
172         /**
173          * Contains a bit for each lazy boolean property indicating whether it has been set, either
174          * implicitly during initialization or explicitly via its setter.
175          *
176          * If a bit is set in here then the corresponding bit in [propertyValueBits] contains the
177          * value.
178          */
179         private var propertyHasBeenSetBits = 0
180 
181         /**
182          * Contains a bit for each lazy boolean property indicating its value.
183          *
184          * A bit in here represents the value of the property if and only if the corresponding bit
185          * has been set in [propertyHasBeenSetBits]. Otherwise, the value of the bit is undefined.
186          */
187         private var propertyValueBits = 0
188 
189         /**
190          * Get the value of a property from [propertyValueBits], initializing it if it has not yet
191          * been set.
192          *
193          * @param propertyBitMask the bit mask in [propertyHasBeenSetBits] and [propertyValueBits]
194          *   which indicates whether the associated property's value has been set and if so what its
195          *   value is.
196          * @param initialValueProvider a lambda which returns the initial value of the property if
197          *   it has not yet been set.
198          */
199         private inline fun lazyGet(propertyBitMask: Int, initialValueProvider: () -> Boolean) =
200             // Check to see if the property has been set first before accessing the value.
201             if ((propertyHasBeenSetBits and propertyBitMask) == 0) {
202                 // The property has not been set so get the initial value and store it.
203                 val result = initialValueProvider()
204                 // Record that the property has been set.
205                 propertyHasBeenSetBits = propertyHasBeenSetBits or propertyBitMask
206                 // Record the value.
207                 if (result) propertyValueBits = propertyValueBits or propertyBitMask
208                 // Return the result.
209                 result
210             } else {
211                 // The property has been set so get its value.
212                 (propertyValueBits and propertyBitMask) != 0
213             }
214 
215         /**
216          * Like [lazyGet] except that if the flag is not set it will invoke [inheritInto] if it has
217          * not already been called. It will then check to see if the property has been set and if it
218          * has then the value will be returned. Otherwise, it will invoke the [initialValueProvider]
219          * just as [lazyGet] does.
220          */
221         private inline fun lazyGetAfterInherit(
222             propertyBitMask: Int,
223             initialValueProvider: () -> Boolean
224         ): Boolean {
225             if ((propertyHasBeenSetBits and propertyBitMask) == 0) {
226                 // The property has not been set so first call `inheritInto()` to give it a chance
227                 // to initialize the property. It will return immediately if it had nothing to do.
228                 inheritInto()
229 
230                 // At this point inheritInfo may have been called and may have set the property,
231                 // but it also may not so check again and if it has not then set it to its initial
232                 // value.
233                 return lazyGet(propertyBitMask, initialValueProvider)
234             } else {
235                 // The property has been set so return its value.
236                 return (propertyValueBits and propertyBitMask) != 0
237             }
238         }
239 
240         /**
241          * Set the value of a property in [propertyValueBits], skipping initializing it that has not
242          * already been done.
243          *
244          * @param propertyBitMask the bit mask in [propertyHasBeenSetBits] and [propertyValueBits]
245          *   which indicates whether the associated property's value has been set and if so what its
246          *   value is.
247          * @param value the new value of the property.
248          */
249         private fun lazySet(propertyBitMask: Int, value: Boolean) {
250             // Record that the property has been set.
251             propertyHasBeenSetBits = propertyHasBeenSetBits or propertyBitMask
252             if (value) {
253                 // The value is true so set the bit.
254                 propertyValueBits = propertyValueBits or propertyBitMask
255             } else {
256                 // The value is false so clear the bit.
257                 propertyValueBits = propertyValueBits and propertyBitMask.inv()
258             }
259         }
260 
261         override val originallyHidden: Boolean
262             get() =
263                 lazyGet(ORIGINALLY_HIDDEN_BIT_MASK) {
264                     // The item is originally hidden if the javadoc contains @hide or similar, or
265                     // it is tagged with a hide annotation. That is true even if the hide annotation
266                     // is superseded by a show annotation.
267                     item.documentation.isHidden || item.hasHideAnnotation()
268                 }
269 
270         override var inheritableHidden: Boolean
271             get() =
272                 lazyGetAfterInherit(INHERITABLE_HIDDEN_BIT_MASK) {
273                     // By default, i.e. if the property has not been set, the contents of this item
274                     // will be hidden if this item was originally hidden and this item did not have
275                     // a show annotation that applies recursively to its contents. Otherwise, the
276                     // item's contents will be visible.
277                     originallyHidden && !showability.showRecursive()
278                 }
279             set(value) {
280                 lazySet(INHERITABLE_HIDDEN_BIT_MASK, value)
281             }
282 
283         override val accessible: Boolean
284             get() =
285                 lazyGet(ACCESSIBLE_BIT_MASK) {
286                     when (item) {
287                         // Packages are always accessible.
288                         is PackageItem -> true
289                         else ->
290                             // This is accessible if it is public, protected or internal (with show
291                             // annotation) and none of its containing classes, if any, are
292                             // inaccessible.
293                             (item.isPublic ||
294                                 item.isProtected ||
295                                 (item.isInternal && showability.show())) &&
296                                 item.containingClass()?.variantSelectors?.accessible != false
297                     }
298                 }
299 
300         override var hidden: Boolean
301             get() =
302                 lazyGetAfterInherit(HIDDEN_BIT_MASK) {
303                     // By default, i.e. if the property has not been set, this item will be hidden
304                     // if it inherits hidden from its parent (or was originally hidden) and this
305                     // item does not have a show annotation of any sort. Otherwise, this item is
306                     // visible.
307                     inheritableHidden && !showability.show()
308                 }
309             set(value) {
310                 lazySet(HIDDEN_BIT_MASK, value)
311             }
312 
313         override val docOnly: Boolean
314             get() =
315                 lazyGet(DOCONLY_BIT_MASK) {
316                     (item.parent()?.variantSelectors?.docOnly == true) ||
317                         item.documentation.isDocOnly
318                 }
319 
320         override var removed: Boolean
321             get() =
322                 lazyGet(REMOVED_BIT_MASK) {
323                     (item.parent()?.variantSelectors?.removed == true) ||
324                         item.documentation.isRemoved
325                 }
326             // This is only used for testing.
327             set(value) {
328                 lazySet(REMOVED_BIT_MASK, value)
329             }
330 
331         /** Cache of [showability]. */
332         internal var _showability: Showability? = null
333 
334         override val showability: Showability
335             get() =
336                 _showability
337                     ?: let {
338                         _showability = item.codebase.annotationManager.getShowabilityForItem(item)
339                         _showability!!
340                     }
341 
342         override fun duplicate(item: Item): ApiVariantSelectors = Mutable(item as SelectableItem)
343 
344         /**
345          * Records whether [inheritInto] was called as it must only be called once.
346          *
347          * This uses [lazyGet] and [lazySet] to be consistent with other properties and makes it
348          * easy to include the information in the [toString] result.
349          */
350         internal var inheritIntoWasCalled
351             get() = lazyGet(INHERIT_INTO_BIT_MASK) { false }
352             set(value) {
353                 lazySet(INHERIT_INTO_BIT_MASK, value)
354             }
355 
356         override fun inheritInto() {
357             // This must only be called once.
358             if (inheritIntoWasCalled) return
359             inheritIntoWasCalled = true
360 
361             // PackageItem behaves quite differently to the other Item types so do it first.
362             if (item is PackageItem) {
363                 showability.let { showability ->
364                     when {
365                         showability.show() -> inheritableHidden = false
366                         showability.hide() -> inheritableHidden = true
367                     }
368                 }
369                 val containingPackageSelectors =
370                     item.containingPackage()?.variantSelectors ?: return
371                 if (containingPackageSelectors.inheritableHidden) {
372                     inheritableHidden = true
373                 }
374                 return
375             }
376 
377             // Inheritance is only done on a few Item types, ignore the rest.
378             if (item !is ClassItem && item !is CallableItem && item !is FieldItem) return
379 
380             if (item is ClassItem) {
381                 // Workaround: we're pulling in .aidl files from .jar files. These are
382                 // marked @hide, but since we only see the .class files we don't know that.
383                 if (
384                     item.simpleName().startsWith("I") &&
385                         item.origin == ClassOrigin.CLASS_PATH &&
386                         item.interfaceTypes().any { it.qualifiedName == "android.os.IInterface" }
387                 ) {
388                     hidden = true
389                     return
390                 }
391             }
392 
393             if (showability.show()) {
394                 // If the showability is recursive then set inheritableHidden to false, that will
395                 // unhide any contents of this item too, unless they hide themselves.
396                 if (showability.showRecursive()) {
397                     inheritableHidden = false
398                 }
399                 // Whether the showability is recursive or not a show annotation of any sort will
400                 // always unhide this item.
401                 hidden = false
402 
403                 if (item is ClassItem) {
404                     // Make containing package non-hidden if it contains a show-annotation class.
405                     val containingPackageSelectors = item.containingPackage().variantSelectors
406                     // Only unhide the package, do not affect anything that might inherit from that
407                     // package.
408                     (containingPackageSelectors as Mutable).hidden = false
409                 }
410 
411                 if (item.containingClass() != null) {
412                     ensureParentVisible()
413                 }
414             } else if (showability.hide()) {
415                 inheritableHidden = true
416             } else {
417                 val containingClassSelectors = item.containingClass()?.variantSelectors
418                 if (containingClassSelectors != null) {
419                     if (item is FieldItem) {
420                         if (
421                             containingClassSelectors.originallyHidden &&
422                                 containingClassSelectors.showability.showNonRecursive()
423                         ) {
424                             // This is a member in a class that was hidden but then unhidden; but it
425                             // was
426                             // unhidden by a non-recursive (single) show annotation, so don't
427                             // inherit
428                             // the show annotation into this item.
429                             inheritableHidden = true
430                         }
431                     } else if (containingClassSelectors.inheritableHidden) {
432                         inheritableHidden = true
433                     }
434                 } else if (item is ClassItem) {
435                     // This will only be executed for top level classes, i.e. containing class is
436                     // null. They inherit their properties from the containing package.
437                     val containingPackageSelectors = item.containingPackage().variantSelectors
438                     if (containingPackageSelectors.inheritableHidden) {
439                         inheritableHidden = true
440                     }
441                 }
442             }
443         }
444 
445         /**
446          * Ensure that the parents of a visible [Item], i.e. one whose [Item.hidden] property is
447          * `false` are themselves visible.
448          *
449          * Note: This will only be called when [item] is a class, constructor, method or field. In
450          * particular, it does not apply to [PackageItem]s as they are completely separate from one
451          * another, i.e. you do not need to have package `abc.qrs` be visible in order to have
452          * `abc.qrs.xyz` be visible.
453          */
454         private fun ensureParentVisible() {
455             val parent = item.parent() ?: return
456 
457             // If the parent is not hidden then everything is fine.
458             if (!parent.hidden) {
459                 return
460             }
461 
462             // Otherwise, find a show annotation to blame it on and report the issue.
463             item.modifiers.findAnnotation(AnnotationItem::isShowAnnotation)?.let {
464                 violatingAnnotation ->
465                 item.codebase.reporter.report(
466                     Issues.SHOWING_MEMBER_IN_HIDDEN_CLASS,
467                     item,
468                     "Attempting to unhide ${item.describe()}, but surrounding ${parent.describe()} is " +
469                         "hidden and should also be annotated with $violatingAnnotation"
470                 )
471             }
472         }
473 
474         override fun toString(): String {
475             return buildString {
476                 append(item.describe())
477                 append(" {\n")
478                 for ((bitPosition, propertyName) in propertyNamePerBit.withIndex()) {
479                     val bitMask = 1 shl bitPosition
480                     append("    ")
481                     append(propertyName)
482                     append("=")
483                     if ((propertyHasBeenSetBits and bitMask) == 0) {
484                         append("<not-set>")
485                     } else if ((propertyValueBits and bitMask) == 0) {
486                         append("false")
487                     } else {
488                         append("true")
489                     }
490                     append(",\n")
491                 }
492                 append("    showability=")
493                 if (_showability == null) {
494                     append("<not-set>")
495                 } else {
496                     append(_showability)
497                 }
498                 append(",\n")
499                 append("}")
500             }
501         }
502 
503         override fun equals(other: Any?): Boolean {
504             if (this === other) return true
505             if (other !is Mutable) return false
506 
507             if (item != other.item) return false
508             if (propertyHasBeenSetBits != other.propertyHasBeenSetBits) return false
509             if (propertyValueBits != other.propertyValueBits) return false
510             if (_showability != other._showability) return false
511 
512             return true
513         }
514 
515         override fun hashCode(): Int {
516             var result = item.hashCode()
517             result = 31 * result + propertyHasBeenSetBits
518             result = 31 * result + propertyValueBits
519             result = 31 * result + _showability.hashCode()
520             return result
521         }
522 
523         companion object {
524             // `originallyHidden` related constants
525             private const val ORIGINALLY_HIDDEN_BIT_POSITION: Int = 0
526             private const val ORIGINALLY_HIDDEN_BIT_MASK: Int = 1 shl ORIGINALLY_HIDDEN_BIT_POSITION
527 
528             // `inheritableHidden` related constants
529             private const val INHERITABLE_HIDDEN_BIT_POSITION: Int =
530                 ORIGINALLY_HIDDEN_BIT_POSITION + 1
531             private const val INHERITABLE_HIDDEN_BIT_MASK: Int =
532                 1 shl INHERITABLE_HIDDEN_BIT_POSITION
533 
534             // `hidden` related constants
535             private const val HIDDEN_BIT_POSITION: Int = INHERITABLE_HIDDEN_BIT_POSITION + 1
536             private const val HIDDEN_BIT_MASK: Int = 1 shl HIDDEN_BIT_POSITION
537 
538             // `accessible` related constants
539             private const val ACCESSIBLE_BIT_POSITION: Int = HIDDEN_BIT_POSITION + 1
540             private const val ACCESSIBLE_BIT_MASK: Int = 1 shl ACCESSIBLE_BIT_POSITION
541 
542             // `docOnly` related constants
543             private const val DOCONLY_BIT_POSITION: Int = ACCESSIBLE_BIT_POSITION + 1
544             private const val DOCONLY_BIT_MASK: Int = 1 shl DOCONLY_BIT_POSITION
545 
546             // `removed` related constants
547             private const val REMOVED_BIT_POSITION: Int = DOCONLY_BIT_POSITION + 1
548             private const val REMOVED_BIT_MASK: Int = 1 shl REMOVED_BIT_POSITION
549 
550             /**
551              * Bit mask in [propertyHasBeenSetBits] that indicates whether [inheritInto] has been
552              * called.
553              */
554             private const val INHERIT_INTO_BIT_POSITION = REMOVED_BIT_POSITION + 1
555             private const val INHERIT_INTO_BIT_MASK = 1 shl INHERIT_INTO_BIT_POSITION
556 
557             /** The count of the number of bits used. */
558             private const val COUNT_BITS_USED = INHERIT_INTO_BIT_POSITION + 1
559 
560             /** Map from bit to the associated property name, used in toString() */
561             private val propertyNamePerBit =
562                 Array(COUNT_BITS_USED) { "" }
563                     .also { array ->
564                         array[ORIGINALLY_HIDDEN_BIT_POSITION] = "originallyHidden"
565                         array[INHERITABLE_HIDDEN_BIT_POSITION] = "inheritableHidden"
566                         array[HIDDEN_BIT_POSITION] = "hidden"
567                         array[ACCESSIBLE_BIT_POSITION] = "accessible"
568                         array[DOCONLY_BIT_POSITION] = "docOnly"
569                         array[REMOVED_BIT_POSITION] = "removed"
570                         array[INHERIT_INTO_BIT_POSITION] = "inheritIntoWasCalled"
571                     }
572         }
573     }
574 
575     /**
576      * Encapsulates the expected state of a [Mutable] instance.
577      *
578      * A data class was chosen for this because the nature of the [Mutable] class is such that
579      * generally, once a property has been set it is not changed (not strictly true for packages).
580      * Tests will typically, test the state, make a change (e.g. get the value of a property), check
581      * the new state and so on. The [copy] method generated for data classes makes it easy to
582      * incrementally modify the state without having to repeat all the previous changes.
583      *
584      * For `var` properties in [Mutable] each corresponding optional parameter will have no effect
585      * if `null` but otherwise will be used to set the corresponding `var` property in the returned
586      * object.
587      *
588      * The `val` properties like [originallyHidden] cannot be set to a specific value. So, all that
589      * this can do is force it to be initialized. That means that the [ApiVariantSelectors] returned
590      * from [createSelectorsforTesting] will only verify whether it is set or not-set as expected.
591      * It cannot test if the value is expected. That will need to be done by the caller.
592      */
593     data class TestableSelectorsState(
594         val item: SelectableItem,
595         val originallyHidden: Boolean? = null,
596         val inheritIntoWasCalled: Boolean = false,
597         val inheritableHidden: Boolean? = null,
598         val hidden: Boolean? = null,
599         val docOnly: Boolean? = null,
600         val removed: Boolean? = null,
601         val showability: Showability? = null,
602     ) {
603 
604         /**
605          * Create a [Mutable] instance whose state matches this that can be used as the expected
606          * state in a test.
607          */
608         fun createSelectorsforTesting(): ApiVariantSelectors =
609             Mutable(item).also { selectors ->
610                 // If originally hidden is set then force it to be initialized.
611                 originallyHidden?.let {
612                     // It is expected to be set so force it to be initialized.
613                     selectors.originallyHidden
614                 }
615                 if (inheritIntoWasCalled) selectors.inheritIntoWasCalled = true
616                 inheritableHidden?.let { selectors.inheritableHidden = it }
617                 hidden?.let { selectors.hidden = it }
618                 docOnly?.let {
619                     // It is expected to be set so force it to be initialized.
620                     selectors.docOnly
621                 }
622                 removed?.let { selectors.removed = it }
623                 showability?.let { selectors._showability = it }
624             }
625     }
626 }
627