• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.AnnotationItem
20 import com.android.tools.metalava.model.CallableItem
21 import com.android.tools.metalava.model.Codebase
22 import com.android.tools.metalava.model.FieldItem
23 import com.android.tools.metalava.model.Item
24 import com.android.tools.metalava.model.ParameterItem
25 import com.android.tools.metalava.model.RECENTLY_NONNULL
26 import com.android.tools.metalava.model.RECENTLY_NULLABLE
27 import com.android.tools.metalava.model.SUPPORT_TYPE_USE_ANNOTATIONS
28 import com.android.tools.metalava.model.TypeItem
29 import com.android.tools.metalava.model.findAnnotation
30 import com.android.tools.metalava.model.hasAnnotation
31 
32 /**
33  * Performs null migration analysis, looking at previous API signature files and new signature
34  * files, and replacing @Nullable and @NonNull annotations added to APIs that have previously been
35  * released with @RecentlyNullable and @RecentlyNonNull.
36  *
37  * TODO: Enforce compatibility across type use annotations, e.g. changing parameter value from
38  *   {@code @NonNull List<@Nullable String>} to {@code @NonNull List<@NonNull String>} is forbidden.
39  */
40 class NullnessMigration : ComparisonVisitor() {
compareItemsnull41     override fun compareItems(old: Item, new: Item) {
42         if (hasNullnessInformation(new) && !hasNullnessInformation(old)) {
43             new.markRecent()
44         }
45     }
46 
47     // Note: We don't override added(new: Item) to mark newly added methods as newly
48     // having nullness annotations: those APIs are themselves new, so there's no reason
49     // to mark the nullness contract as migration (warning- rather than error-severity)
50 
compareCallableItemsnull51     override fun compareCallableItems(old: CallableItem, new: CallableItem) {
52         @Suppress("ConstantConditionIf")
53         if (SUPPORT_TYPE_USE_ANNOTATIONS) {
54             val newType = new.returnType()
55             val oldType = old.returnType()
56             checkType(oldType, newType)
57         }
58     }
59 
compareFieldItemsnull60     override fun compareFieldItems(old: FieldItem, new: FieldItem) {
61         @Suppress("ConstantConditionIf")
62         if (SUPPORT_TYPE_USE_ANNOTATIONS) {
63             val newType = new.type()
64             val oldType = old.type()
65             checkType(oldType, newType)
66         }
67     }
68 
compareParameterItemsnull69     override fun compareParameterItems(old: ParameterItem, new: ParameterItem) {
70         @Suppress("ConstantConditionIf")
71         if (SUPPORT_TYPE_USE_ANNOTATIONS) {
72             val newType = new.type()
73             val oldType = old.type()
74             checkType(oldType, newType)
75         }
76     }
77 
78     @Suppress("UNUSED_PARAMETER")
hasNullnessInformationnull79     private fun hasNullnessInformation(type: TypeItem): Boolean {
80         return if (SUPPORT_TYPE_USE_ANNOTATIONS) {
81             // TODO: support type use
82             false
83         } else {
84             false
85         }
86     }
87 
88     @Suppress("UNUSED_PARAMETER")
checkTypenull89     private fun checkType(old: TypeItem, new: TypeItem) {
90         if (hasNullnessInformation(new)) {
91             assert(SUPPORT_TYPE_USE_ANNOTATIONS)
92             // TODO: support type use
93         }
94     }
95 
96     companion object {
migrateNullsnull97         fun migrateNulls(codebase: Codebase, previous: Codebase) {
98             CodebaseComparator().compare(NullnessMigration(), previous, codebase)
99         }
100 
hasNullnessInformationnull101         fun hasNullnessInformation(item: Item): Boolean {
102             return isNullable(item) || isNonNull(item)
103         }
104 
findNullnessAnnotationnull105         fun findNullnessAnnotation(item: Item): AnnotationItem? {
106             return item.modifiers.findAnnotation(AnnotationItem::isNullnessAnnotation)
107         }
108 
isNullablenull109         private fun isNullable(item: Item): Boolean {
110             return item.modifiers.hasAnnotation(AnnotationItem::isNullable)
111         }
112 
isNonNullnull113         private fun isNonNull(item: Item): Boolean {
114             return item.modifiers.hasAnnotation(AnnotationItem::isNonNull)
115         }
116     }
117 }
118 
119 /**
120  * Marks the nullability of this Item as Recent. That is, replaces @Nullable/@NonNull
121  * with @RecentlyNullable/@RecentlyNonNull
122  */
Itemnull123 fun Item.markRecent() {
124     val annotation = NullnessMigration.findNullnessAnnotation(this) ?: return
125     // Nullness information change: Add migration annotation
126     val annotationClass = if (annotation.isNullable()) RECENTLY_NULLABLE else RECENTLY_NONNULL
127 
128     val replacementAnnotation = codebase.createAnnotation("@$annotationClass", this)
129 
130     mutateModifiers {
131         mutateAnnotations {
132             remove(annotation)
133             replacementAnnotation?.let { add(it) }
134         }
135     }
136 }
137