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