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