1 /* 2 * Copyright (C) 2018 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.SdkConstants 20 import com.android.tools.metalava.model.AnnotationRetention 21 import com.android.tools.metalava.model.Codebase 22 import java.io.File 23 import kotlin.text.Charsets.UTF_8 24 25 /** 26 * Converts public stub annotation sources into package private annotation sources. 27 * This is needed for the stub sources, where we want to reference annotations that aren't 28 * public, but (a) they need to be public during compilation, and (b) they need to be 29 * package private when compiled and packaged on their own such that annotation processors 30 * can find them. See b/110532131 for details. 31 */ 32 class RewriteAnnotations { 33 /** Modifies annotation source files such that they are package private */ modifyAnnotationSourcesnull34 fun modifyAnnotationSources(codebase: Codebase?, source: File, target: File, pkg: String = "") { 35 val fileName = source.name 36 if (fileName.endsWith(SdkConstants.DOT_JAVA)) { 37 // Only copy non-source retention annotation classes 38 val qualifiedName = pkg + "." + fileName.substring(0, fileName.indexOf('.')) 39 if (hasSourceRetention(codebase, qualifiedName)) { 40 return 41 } 42 43 // Copy and convert 44 target.parentFile.mkdirs() 45 target.writeText( 46 source.readText(UTF_8).replace( 47 "\npublic @interface", 48 "\n@interface" 49 ) 50 ) 51 } else if (source.isDirectory) { 52 val newPackage = if (pkg.isEmpty()) fileName else "$pkg.$fileName" 53 source.listFiles()?.forEach { 54 modifyAnnotationSources(codebase, it, File(target, it.name), newPackage) 55 } 56 } 57 } 58 59 /** Returns true if the given annotation class name has source retention as far as the stub 60 * annotations are concerned. 61 */ hasSourceRetentionnull62 private fun hasSourceRetention(codebase: Codebase?, qualifiedName: String): Boolean { 63 when { 64 qualifiedName == RECENTLY_NULLABLE || 65 qualifiedName == RECENTLY_NONNULL || 66 qualifiedName == ANDROID_NULLABLE || 67 qualifiedName == ANDROID_NONNULL -> return false 68 qualifiedName.equals(ANDROID_SDK_CONSTANT) -> return true 69 qualifiedName.startsWith("androidx.annotation.") -> return true 70 } 71 72 // See if the annotation is pointing to an annotation class that is part of the API; if not, skip it. 73 if (codebase != null) { 74 val cls = codebase.findClass(qualifiedName) ?: return true 75 return cls.isAnnotationType() && cls.getRetention() == AnnotationRetention.SOURCE 76 } else { 77 error("Found annotation with unknown desired retention: " + qualifiedName) 78 } 79 } 80 } 81