• 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.FieldItem
21 import com.android.tools.metalava.model.Item
22 import com.android.tools.metalava.model.MethodItem
23 import com.android.tools.metalava.model.ParameterItem
24 import com.android.tools.metalava.model.SUPPORT_TYPE_USE_ANNOTATIONS
25 import com.android.tools.metalava.model.TypeItem
26 
27 /**
28  * Performs null migration analysis, looking at previous API signature
29  * files and new signature files, and replacing new @Nullable and @NonNull
30  * annotations with @RecentlyNullable and @RecentlyNonNull.
31  *
32  * TODO: Enforce compatibility across type use annotations, e.g.
33  * changing parameter value from
34  *    {@code @NonNull List<@Nullable String>}
35  * to
36  *    {@code @NonNull List<@NonNull String>}
37  * is forbidden.
38  */
39 class NullnessMigration : ComparisonVisitor(visitAddedItemsRecursively = true) {
comparenull40     override fun compare(old: Item, new: Item) {
41         if (hasNullnessInformation(new) && !hasNullnessInformation(old)) {
42             markRecent(new)
43         }
44     }
45 
46     // Note: We don't override added(new: Item) to mark newly added methods as newly
47     // having nullness annotations: those APIs are themselves new, so there's no reason
48     // to mark the nullness contract as migration (warning- rather than error-severity)
49 
comparenull50     override fun compare(old: MethodItem, new: MethodItem) {
51         @Suppress("ConstantConditionIf")
52         if (SUPPORT_TYPE_USE_ANNOTATIONS) {
53             val newType = new.returnType() ?: return
54             val oldType = old.returnType() ?: return
55             checkType(oldType, newType)
56         }
57     }
58 
comparenull59     override fun compare(old: FieldItem, new: FieldItem) {
60         @Suppress("ConstantConditionIf")
61         if (SUPPORT_TYPE_USE_ANNOTATIONS) {
62             val newType = new.type()
63             val oldType = old.type()
64             checkType(oldType, newType)
65         }
66     }
67 
comparenull68     override fun compare(old: ParameterItem, new: ParameterItem) {
69         @Suppress("ConstantConditionIf")
70         if (SUPPORT_TYPE_USE_ANNOTATIONS) {
71             val newType = new.type()
72             val oldType = old.type()
73             checkType(oldType, newType)
74         }
75     }
76 
hasNullnessInformationnull77     private fun hasNullnessInformation(type: TypeItem): Boolean {
78         return if (SUPPORT_TYPE_USE_ANNOTATIONS) {
79             val typeString = type.toTypeString(outerAnnotations = false, innerAnnotations = true)
80             typeString.contains(".Nullable") || typeString.contains(".NonNull")
81         } else {
82             false
83         }
84     }
85 
checkTypenull86     private fun checkType(old: TypeItem, new: TypeItem) {
87         if (hasNullnessInformation(new)) {
88             assert(SUPPORT_TYPE_USE_ANNOTATIONS)
89             if (old.toTypeString(outerAnnotations = false, innerAnnotations = true) !=
90                 new.toTypeString(outerAnnotations = false, innerAnnotations = true)
91             ) {
92                 new.markRecent()
93             }
94         }
95     }
96 
markRecentnull97     private fun markRecent(new: Item) {
98         val annotation = findNullnessAnnotation(new) ?: return
99         // Nullness information change: Add migration annotation
100         val annotationClass = if (annotation.isNullable()) RECENTLY_NULLABLE else RECENTLY_NONNULL
101 
102         val modifiers = new.mutableModifiers()
103         modifiers.removeAnnotation(annotation)
104 
105         // Don't map annotation names - this would turn newly non null back into non null
106         modifiers.addAnnotation(new.codebase.createAnnotation("@$annotationClass", new, mapName = false))
107     }
108 
109     companion object {
hasNullnessInformationnull110         fun hasNullnessInformation(item: Item): Boolean {
111             return isNullable(item) || isNonNull(item)
112         }
113 
findNullnessAnnotationnull114         fun findNullnessAnnotation(item: Item): AnnotationItem? {
115             return item.modifiers.annotations().firstOrNull { it.isNullnessAnnotation() }
116         }
117 
isNullablenull118         fun isNullable(item: Item): Boolean {
119             return item.modifiers.annotations().any { it.isNullable() }
120         }
121 
isNonNullnull122         private fun isNonNull(item: Item): Boolean {
123             return item.modifiers.annotations().any { it.isNonNull() }
124         }
125     }
126 }
127