Home | History | Annotate | Download | only in metalava
      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.TypeItem
     25 
     26 /**
     27  * Performs null migration analysis, looking at previous API signature
     28  * files and new signature files, and replacing new @Nullable and @NonNull
     29  * annotations with @RecentlyNullable and @RecentlyNonNull.
     30  *
     31  * TODO: Enforce compatibility across type use annotations, e.g.
     32  * changing parameter value from
     33  *    {@code @NonNull List<@Nullable String>}
     34  * to
     35  *    {@code @NonNull List<@NonNull String>}
     36  * is forbidden.
     37  */
     38 class NullnessMigration : ComparisonVisitor(visitAddedItemsRecursively = true) {
     39     override fun compare(old: Item, new: Item) {
     40         if (hasNullnessInformation(new) && !hasNullnessInformation(old)) {
     41             markRecent(new)
     42         }
     43     }
     44 
     45     override fun added(new: Item) {
     46         // Translate newly added items into RecentlyNull/RecentlyNonNull
     47         if (hasNullnessInformation(new)) {
     48             markRecent(new)
     49         }
     50     }
     51     override fun compare(old: MethodItem, new: MethodItem) {
     52         val newType = new.returnType() ?: return
     53         val oldType = old.returnType() ?: return
     54         checkType(oldType, newType)
     55     }
     56 
     57     override fun compare(old: FieldItem, new: FieldItem) {
     58         val newType = new.type()
     59         val oldType = old.type()
     60         checkType(oldType, newType)
     61     }
     62 
     63     override fun compare(old: ParameterItem, new: ParameterItem) {
     64         val newType = new.type()
     65         val oldType = old.type()
     66         checkType(oldType, newType)
     67     }
     68 
     69     override fun added(new: MethodItem) {
     70         checkType(new.returnType() ?: return)
     71     }
     72 
     73     override fun added(new: FieldItem) {
     74         checkType(new.type())
     75     }
     76 
     77     override fun added(new: ParameterItem) {
     78         checkType(new.type())
     79     }
     80 
     81     private fun hasNullnessInformation(type: TypeItem): Boolean {
     82         val typeString = type.toTypeString(false, true, false)
     83         return typeString.contains(".Nullable") || typeString.contains(".NonNull")
     84     }
     85 
     86     private fun checkType(old: TypeItem, new: TypeItem) {
     87         if (hasNullnessInformation(new)) {
     88             if (old.toTypeString(false, true, false) !=
     89                 new.toTypeString(false, true, false)) {
     90                 new.markRecent()
     91             }
     92         }
     93     }
     94 
     95     private fun checkType(new: TypeItem) {
     96         if (hasNullnessInformation(new)) {
     97             new.markRecent()
     98         }
     99     }
    100 
    101     private fun markRecent(new: Item) {
    102         val annotation = findNullnessAnnotation(new) ?: return
    103         // Nullness information change: Add migration annotation
    104         val annotationClass = if (annotation.isNullable()) RECENTLY_NULLABLE else RECENTLY_NONNULL
    105 
    106         val modifiers = new.mutableModifiers()
    107         modifiers.removeAnnotation(annotation)
    108 
    109         // Don't map annotation names - this would turn newly non null back into non null
    110         modifiers.addAnnotation(new.codebase.createAnnotation("@$annotationClass", new, mapName = false))
    111     }
    112 
    113     companion object {
    114         fun hasNullnessInformation(item: Item): Boolean {
    115             return isNullable(item) || isNonNull(item)
    116         }
    117 
    118         fun findNullnessAnnotation(item: Item): AnnotationItem? {
    119             return item.modifiers.annotations().firstOrNull { it.isNullnessAnnotation() }
    120         }
    121 
    122         fun isNullable(item: Item): Boolean {
    123             return item.modifiers.annotations().any { it.isNullable() }
    124         }
    125 
    126         private fun isNonNull(item: Item): Boolean {
    127             return item.modifiers.annotations().any { it.isNonNull() }
    128         }
    129 
    130         private fun isRecentlyMigrated(item: Item): Boolean {
    131             return item.modifiers.annotations().any { isRecentlyMigrated(it.qualifiedName() ?: "") }
    132         }
    133 
    134         private fun isRecentlyMigrated(qualifiedName: String): Boolean {
    135             return qualifiedName.endsWith(".RecentlyNullable") ||
    136                 qualifiedName.endsWith(".RecentlyNonNull")
    137         }
    138     }
    139 }
    140