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