diff --git a/.gitignore b/.gitignore index 8ddfee5ae9ba47c09482c9bdd4065cebb10419c5..2e527fdf7ecd7a9c4637cdbac1ca3fa37207b3bd 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,9 @@ /.idea/libraries /.idea/modules.xml /.idea/workspace.xml +/.idea/runConfigurations +/.idea/usage.statistics.xml /out +build/ +stub-annotations/out .DS_Store diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000000000000000000000000000000000000..7b2b7ade0c29fcaee1bb6303e01c96ddc8a3a0f2 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,39 @@ +<component name="ProjectCodeStyleConfiguration"> + <code_scheme name="Project" version="173"> + <JavaCodeStyleSettings> + <option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99999" /> + <option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99999" /> + <option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND"> + <value /> + </option> + </JavaCodeStyleSettings> + <JetCodeStyleSettings> + <option name="PACKAGES_TO_USE_STAR_IMPORTS"> + <value> + <package name="kotlinx.android.synthetic" withSubpackages="true" static="false" /> + </value> + </option> + <option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" /> + <option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" /> + <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> + </JetCodeStyleSettings> + <codeStyleSettings language="JAVA"> + <option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" /> + <option name="KEEP_BLANK_LINES_IN_CODE" value="1" /> + <option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" /> + <indentOptions> + <option name="CONTINUATION_INDENT_SIZE" value="4" /> + </indentOptions> + </codeStyleSettings> + <codeStyleSettings language="kotlin"> + <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> + <option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" /> + <option name="KEEP_BLANK_LINES_IN_CODE" value="1" /> + <option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" /> + <option name="ALIGN_MULTILINE_PARAMETERS" value="false" /> + <indentOptions> + <option name="CONTINUATION_INDENT_SIZE" value="4" /> + </indentOptions> + </codeStyleSettings> + </code_scheme> +</component> \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml index a55e7a179bde3e4e772c29c0c85e53354aa54618..79ee123c2b23e069e35ed634d687e17f731cc702 100644 --- a/.idea/codeStyles/codeStyleConfig.xml +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -1,5 +1,5 @@ <component name="ProjectCodeStyleConfiguration"> <state> - <option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" /> + <option name="USE_PER_PROJECT_SETTINGS" value="true" /> </state> </component> \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 304a161149f7c66ca3cb0eba37cf70dfe1526250..4eefbdf2856d2cd9e7aa3fe3ce2640b6983a646a 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -4,6 +4,8 @@ <bytecodeTargetLevel> <module name="metalava_main" target="1.8" /> <module name="metalava_test" target="1.8" /> + <module name="stub-annotations_main" target="1.8" /> + <module name="stub-annotations_test" target="1.8" /> </bytecodeTargetLevel> </component> </project> \ No newline at end of file diff --git a/.idea/dictionaries/metalava.xml b/.idea/dictionaries/metalava.xml index bf8e8982a93e77ab643f62705c6437073726c30e..6b14ab559968878c961737867ca04abb40564b1f 100644 --- a/.idea/dictionaries/metalava.xml +++ b/.idea/dictionaries/metalava.xml @@ -1,6 +1,7 @@ <component name="ProjectDictionaryState"> <dictionary name="metalava"> <words> + <w>androidx</w> <w>apidocsdir</w> <w>argnum</w> <w>bootclasspath</w> @@ -23,6 +24,7 @@ <w>includeable</w> <w>inheritdoc</w> <w>interop</w> + <w>jaif</w> <w>javadocs</w> <w>jvmstatic</w> <w>knowntags</w> diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 267d99c598ebbe3300ab6f14a8d02cdceb5cfe67..8b90e76ece556a2841f02bdfa9290a0fa911e54a 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -8,6 +8,7 @@ <option name="modules"> <set> <option value="$PROJECT_DIR$" /> + <option value="$PROJECT_DIR$/stub-annotations" /> </set> </option> <option name="useAutoImport" value="true" /> diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index a78c4684cadd20d3f41d04a6b2635179c1055ed4..42f9f3aa07739e4c8dffc0469320abc5470fdf57 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -1,6 +1,9 @@ <component name="InspectionProjectProfileManager"> <profile version="1.0"> <option name="myName" value="Project Default" /> + <inspection_tool class="KotlinUnusedImport" enabled="true" level="ERROR" enabled_by_default="true" /> <inspection_tool class="LoopToCallChain" enabled="false" level="INFO" enabled_by_default="false" /> + <inspection_tool class="RedundantSemicolon" enabled="true" level="ERROR" enabled_by_default="true" /> + <inspection_tool class="UnstableApiUsage" enabled="false" level="WARNING" enabled_by_default="false" /> </profile> </component> \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 84da703c3b6737ae27e4c89f3e04cb1712cf3a17..f6d5d5fb9089ae76140ed563625a0ab2b7a37c10 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,5 +1,37 @@ <?xml version="1.0" encoding="UTF-8"?> <project version="4"> + <component name="NullableNotNullManager"> + <option name="myNullables"> + <value> + <list size="9"> + <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" /> + <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" /> + <item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" /> + <item index="3" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" /> + <item index="4" class="java.lang.String" itemvalue="android.support.annotation.Nullable" /> + <item index="5" class="java.lang.String" itemvalue="androidx.annotation.Nullable" /> + <item index="6" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" /> + <item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" /> + <item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" /> + </list> + </value> + </option> + <option name="myNotNulls"> + <value> + <list size="9"> + <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" /> + <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" /> + <item index="2" class="java.lang.String" itemvalue="javax.validation.constraints.NotNull" /> + <item index="3" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" /> + <item index="4" class="java.lang.String" itemvalue="android.support.annotation.NonNull" /> + <item index="5" class="java.lang.String" itemvalue="androidx.annotation.NonNull" /> + <item index="6" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" /> + <item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" /> + <item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" /> + </list> + </value> + </option> + </component> <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK"> <output url="file://$PROJECT_DIR$/classes" /> </component> diff --git a/build.gradle b/build.gradle index f9e7119ba74dbc629e541990158f0a344aea9286..4456034a1bea96b1d9de0dc966b0944b45e93b69 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ buildscript { - ext.gradle_version = '3.1.0-beta4' - ext.studio_version = '26.1.0-beta4' - ext.kotlin_version = '1.2.30' + ext.gradle_version = '3.2.0-alpha16' + ext.studio_version = '26.2.0-alpha16' + ext.kotlin_version = '1.2.41' repositories { google() jcenter() @@ -12,6 +12,11 @@ buildscript { } } +repositories { + google() + jcenter() +} + apply plugin: 'application' apply plugin: 'java' apply plugin: 'kotlin' @@ -42,11 +47,6 @@ compileKotlin { } } -repositories { - google() - jcenter() -} - dependencies { implementation "com.android.tools.external.org-jetbrains:uast:$studio_version" implementation "com.android.tools.external.com-intellij:intellij-core:$studio_version" @@ -54,8 +54,8 @@ dependencies { implementation "com.android.tools.lint:lint-checks:$studio_version" implementation "com.android.tools.lint:lint-gradle:$studio_version" implementation "com.android.tools.lint:lint:$studio_version" - implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - implementation "com.android.tools.lint:lint-tests:$studio_version" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + testImplementation "com.android.tools.lint:lint-tests:$studio_version" testImplementation 'junit:junit:4.11' } @@ -101,3 +101,27 @@ if (System.env.DIST_DIR != null && System.env.OUT_DIR != null) { version = "${version}-SNAPSHOT" } +// KtLint: https://github.com/shyiko/ktlint + +configurations { + ktlint +} + +dependencies { + ktlint "com.github.shyiko:ktlint:0.23.1" +} + +task ktlint(type: JavaExec, group: "verification") { + description = "Check Kotlin code style." + main = "com.github.shyiko.ktlint.Main" + classpath = configurations.ktlint + args "src/**/*.kt" +} +check.dependsOn ktlint + +task format(type: JavaExec, group: "formatting") { + description = "Fix Kotlin code style deviations." + main = "com.github.shyiko.ktlint.Main" + classpath = configurations.ktlint + args "-F", "src/**/*.kt" +} diff --git a/manifest.txt b/manifest.txt new file mode 100644 index 0000000000000000000000000000000000000000..2128bdd9c3eb95394b520601cf574d217d84ad3c --- /dev/null +++ b/manifest.txt @@ -0,0 +1 @@ +Main-Class: com.android.tools.metalava.Driver diff --git a/manual/android/util/annotations.xml b/manual/android/util/annotations.xml new file mode 100644 index 0000000000000000000000000000000000000000..c4e8392e5e35a3929d527eb7acc8fd88c8355dde --- /dev/null +++ b/manual/android/util/annotations.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> +<root> + <item name="android.util.Log boolean isLoggable(java.lang.String, int) 0"> + <annotation name="android.support.annotation.Size"> + <val name="max" val="23" /> + <val name="apis" val=""..23"" /> + </annotation> + </item> +</root> + diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000000000000000000000000000000000000..4be43e74b520fd976b91e38b535d43315f934469 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include ':stub-annotations' diff --git a/src/main/java/com/android/tools/lint/checks/infrastructure/ClassName.kt b/src/main/java/com/android/tools/lint/checks/infrastructure/ClassName.kt new file mode 100644 index 0000000000000000000000000000000000000000..a6b79c52c5e7ae2b1c2d6a3598c655eae33a950a --- /dev/null +++ b/src/main/java/com/android/tools/lint/checks/infrastructure/ClassName.kt @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tools.lint.checks.infrastructure + +import java.util.regex.Pattern + +// Copy in metalava from lint to avoid compilation dependency directly on lint-tests + +/** A pair of package name and class name inferred from Java or Kotlin source code */ +class ClassName(source: String) { + val packageName: String? + val className: String? + + init { + val withoutComments = stripComments(source) + packageName = getPackage(withoutComments) + className = getClassName(withoutComments) + } + + fun packageNameWithDefault() = packageName ?: "" +} + +/** + * Strips line and block comments from the given Java or Kotlin source file + */ +@Suppress("LocalVariableName") +fun stripComments(source: String, stripLineComments: Boolean = true): String { + val sb = StringBuilder(source.length) + var state = 0 + val INIT = 0 + val INIT_SLASH = 1 + val LINE_COMMENT = 2 + val BLOCK_COMMENT = 3 + val BLOCK_COMMENT_ASTERISK = 4 + val IN_STRING = 5 + val IN_STRING_ESCAPE = 6 + val IN_CHAR = 7 + val AFTER_CHAR = 8 + for (i in 0 until source.length) { + val c = source[i] + when (state) { + INIT -> { + when (c) { + '/' -> state = INIT_SLASH + '"' -> { + state = IN_STRING + sb.append(c) + } + '\'' -> { + state = IN_CHAR + sb.append(c) + } + else -> sb.append(c) + } + } + INIT_SLASH -> { + when { + c == '*' -> state = BLOCK_COMMENT + c == '/' && stripLineComments -> state = LINE_COMMENT + else -> { + state = INIT + sb.append('/') // because we skipped it in init + sb.append(c) + } + } + } + LINE_COMMENT -> { + when (c) { + '\n' -> state = INIT + } + } + BLOCK_COMMENT -> { + when (c) { + '*' -> state = BLOCK_COMMENT_ASTERISK + } + } + BLOCK_COMMENT_ASTERISK -> { + state = when (c) { + '/' -> INIT + '*' -> BLOCK_COMMENT_ASTERISK + else -> BLOCK_COMMENT + } + } + IN_STRING -> { + when (c) { + '\\' -> state = IN_STRING_ESCAPE + '"' -> state = INIT + } + sb.append(c) + } + IN_STRING_ESCAPE -> { + sb.append(c) + state = IN_STRING + } + IN_CHAR -> { + if (c != '\\') { + state = AFTER_CHAR + } + sb.append(c) + } + AFTER_CHAR -> { + sb.append(c) + if (c == '\\') { + state = INIT + } + } + } + } + + return sb.toString() +} + +private val PACKAGE_PATTERN = Pattern.compile("""package\s+([\S&&[^;]]*)""") + +private val CLASS_PATTERN = Pattern.compile( + """(class|interface|enum|object)+?\s*([^\s:(]+)""", + Pattern.MULTILINE +) + +fun getPackage(source: String): String? { + val matcher = PACKAGE_PATTERN.matcher(source) + return if (matcher.find()) { + matcher.group(1).trim { it <= ' ' } + } else { + null + } +} + +fun getClassName(source: String): String? { + val matcher = CLASS_PATTERN.matcher(source.replace('\n', ' ')) + var start = 0 + while (matcher.find(start)) { + val cls = matcher.group(2) + val groupStart = matcher.start(2) + + // Make sure this "class" reference isn't part of an annotation on the class + // referencing a class literal -- Foo.class, or in Kotlin, Foo::class.java) + if (groupStart == 0 || source[groupStart - 1] != '.' && source[groupStart - 1] != ':') { + val trimmed = cls.trim { it <= ' ' } + val typeParameter = trimmed.indexOf('<') + return if (typeParameter != -1) { + trimmed.substring(0, typeParameter) + } else { + trimmed + } + } + start = matcher.end(2) + } + + return null +} diff --git a/src/main/java/com/android/tools/metalava/AndroidApiChecks.kt b/src/main/java/com/android/tools/metalava/AndroidApiChecks.kt index 7006579eca577a38f904d0b87be117ac7de8c25d..78ad9c7262b28f2362b8c45429141bccb782f554 100644 --- a/src/main/java/com/android/tools/metalava/AndroidApiChecks.kt +++ b/src/main/java/com/android/tools/metalava/AndroidApiChecks.kt @@ -134,7 +134,6 @@ class AndroidApiChecks { // found beginning break } - } begin += tag.length } @@ -152,8 +151,8 @@ class AndroidApiChecks { val c = doc[i] if (c == '@' && (isLinePrefix || - doc.startsWith("@param", i, true) || - doc.startsWith("@return", i, true)) + doc.startsWith("@param", i, true) || + doc.startsWith("@return", i, true)) ) { // Found it end = i @@ -191,23 +190,23 @@ class AndroidApiChecks { } for (value in values) { - //var perm = String.valueOf(value.value()) + // var perm = String.valueOf(value.value()) var perm = value.toSource() if (perm.indexOf('.') >= 0) perm = perm.substring(perm.lastIndexOf('.') + 1) if (text.contains(perm)) { reporter.report( // Why is that a problem? Sometimes you want to describe // particular use cases. - Errors.REQUIRES_PERMISSION, method, "Method '" + method.name() - + "' documentation mentions permissions already declared by @RequiresPermission" + Errors.REQUIRES_PERMISSION, method, "Method '" + method.name() + + "' documentation mentions permissions already declared by @RequiresPermission" ) } } } } else if (text.contains("android.Manifest.permission") || text.contains("android.permission.")) { reporter.report( - Errors.REQUIRES_PERMISSION, method, "Method '" + method.name() - + "' documentation mentions permissions without declaring @RequiresPermission" + Errors.REQUIRES_PERMISSION, method, "Method '" + method.name() + + "' documentation mentions permissions without declaring @RequiresPermission" ) } } @@ -234,8 +233,8 @@ class AndroidApiChecks { } if (!hasSdkConstant) { reporter.report( - Errors.SDK_CONSTANT, field, "Field '" + field.name() - + "' is missing @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)" + Errors.SDK_CONSTANT, field, "Field '" + field.name() + + "' is missing @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)" ) } } @@ -243,8 +242,8 @@ class AndroidApiChecks { if (text.contains("Activity Action:")) { if (!hasSdkConstant) { reporter.report( - Errors.SDK_CONSTANT, field, "Field '" + field.name() - + "' is missing @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)" + Errors.SDK_CONSTANT, field, "Field '" + field.name() + + "' is missing @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)" ) } } @@ -261,7 +260,10 @@ class AndroidApiChecks { var foundTypeDef = false for (annotation in item.modifiers.annotations()) { val cls = annotation.resolve() ?: continue - if (cls.modifiers.findAnnotation(SdkConstants.INT_DEF_ANNOTATION) != null) { + val modifiers = cls.modifiers + if (modifiers.findAnnotation(SdkConstants.INT_DEF_ANNOTATION.oldName()) != null || + modifiers.findAnnotation(SdkConstants.INT_DEF_ANNOTATION.newName()) != null + ) { // TODO: Check that all the constants listed in the documentation are included in the // annotation? foundTypeDef = true @@ -273,7 +275,7 @@ class AndroidApiChecks { reporter.report( Errors.INT_DEF, item, // TODO: Include source code you can paste right into the code? - ident + " documentation mentions constants without declaring an @IntDef" + "$ident documentation mentions constants without declaring an @IntDef" ) } } @@ -283,7 +285,7 @@ class AndroidApiChecks { ) { reporter.report( Errors.NULLABLE, item, - ident + " documentation mentions 'null' without declaring @NonNull or @Nullable" + "$ident documentation mentions 'null' without declaring @NonNull or @Nullable" ) } } @@ -293,4 +295,3 @@ class AndroidApiChecks { val nullPattern: Pattern = Pattern.compile("\\bnull\\b") } } - diff --git a/src/main/java/com/android/tools/metalava/AnnotationStatistics.kt b/src/main/java/com/android/tools/metalava/AnnotationStatistics.kt index 1f2e4aa661e95deccb4f2dc8a498df22b43beb7e..c229bcac652a7f4ede41273a20e4bb9d8f59b7ac 100644 --- a/src/main/java/com/android/tools/metalava/AnnotationStatistics.kt +++ b/src/main/java/com/android/tools/metalava/AnnotationStatistics.kt @@ -35,14 +35,17 @@ import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.FieldInsnNode import org.objectweb.asm.tree.MethodInsnNode import org.objectweb.asm.tree.MethodNode +import java.io.BufferedWriter import java.io.File +import java.io.FileWriter import java.io.IOException import java.io.PrintWriter import java.util.zip.ZipFile const val CLASS_COLUMN_WIDTH = 60 const val COUNT_COLUMN_WIDTH = 16 -const val USAGE_REPORT_MAX_ROWS = 15 +const val USAGE_REPORT_MAX_ROWS = 3000 + /** Sadly gitiles' markdown support doesn't handle tables with top/bottom horizontal edges */ const val INCLUDE_HORIZONTAL_EDGES = false @@ -74,6 +77,9 @@ class AnnotationStatistics(val api: Codebase) { } override fun visitParameter(parameter: ParameterItem) { + if (!parameter.requiresNullnessInfo()) { + return + } allParameters++ if (parameter.modifiers.annotations().any { it.isNonNull() || it.isNullable() }) { annotatedParameters++ @@ -81,6 +87,9 @@ class AnnotationStatistics(val api: Codebase) { } override fun visitField(field: FieldItem) { + if (!field.requiresNullnessInfo()) { + return + } allFields++ if (field.modifiers.annotations().any { it.isNonNull() || it.isNullable() }) { annotatedFields++ @@ -88,6 +97,9 @@ class AnnotationStatistics(val api: Codebase) { } override fun visitMethod(method: MethodItem) { + if (!method.requiresNullnessInfo()) { + return + } allMethods++ if (method.modifiers.annotations().any { it.isNonNull() || it.isNullable() }) { annotatedMethods++ @@ -111,7 +123,7 @@ class AnnotationStatistics(val api: Codebase) { private fun percent(numerator: Int, denominator: Int): Int { return if (denominator == 0) { - 0 + 100 } else { numerator * 100 / denominator } @@ -147,7 +159,7 @@ class AnnotationStatistics(val api: Codebase) { options.stdout.println() options.stdout.println( "$missingCount methods and fields were missing nullness annotations out of " + - "$referenceCount total API references." + "$referenceCount total API references." ) options.stdout.println("API nullness coverage is ${percent(annotatedCount, referenceCount)}%") options.stdout.println() @@ -227,17 +239,44 @@ class AnnotationStatistics(val api: Codebase) { } private fun printClassTable(classes: List<Item>, classCount: MutableMap<Item, Int>) { + val reportFile = options.annotationCoverageClassReport + val printer = + if (reportFile != null) { + reportFile.parentFile?.mkdirs() + PrintWriter(BufferedWriter(FileWriter(reportFile))) + } else { + options.stdout + } + + // Top APIs + printer.println("\nTop referenced un-annotated classes:\n") + printTable("Qualified Class Name", "Usage Count", classes, { (it as ClassItem).qualifiedName() }, - { classCount[it]!! }) + { classCount[it]!! }, + printer) + + if (reportFile != null) { + printer.close() + progress("\n$PROGRAM_NAME wrote class annotation coverage report to $reportFile") + } } private fun printMemberTable( - sorted: List<MemberItem>, used: HashMap<MemberItem, Int>, - printer: PrintWriter = options.stdout + sorted: List<MemberItem>, + used: HashMap<MemberItem, Int> ) { + val reportFile = options.annotationCoverageMemberReport + val printer = + if (reportFile != null) { + reportFile.parentFile?.mkdirs() + PrintWriter(BufferedWriter(FileWriter(reportFile))) + } else { + options.stdout + } + // Top APIs printer.println("\nTop referenced un-annotated members:\n") @@ -254,6 +293,11 @@ class AnnotationStatistics(val api: Codebase) { { used[it]!! }, printer ) + + if (reportFile != null) { + printer.close() + progress("\n$PROGRAM_NAME wrote member annotation coverage report to $reportFile") + } } private fun dashes(printer: PrintWriter, max: Int) { @@ -302,24 +346,24 @@ class AnnotationStatistics(val api: Codebase) { private fun recordUsages(used: MutableMap<MemberItem, Int>, file: File, path: String) { when { file.name.endsWith(SdkConstants.DOT_JAR) -> try { - ZipFile(file).use({ jar -> + ZipFile(file).use { jar -> val enumeration = jar.entries() while (enumeration.hasMoreElements()) { val entry = enumeration.nextElement() if (entry.name.endsWith(SdkConstants.DOT_CLASS)) { try { - jar.getInputStream(entry).use({ `is` -> + jar.getInputStream(entry).use { `is` -> val bytes = ByteStreams.toByteArray(`is`) if (bytes != null) { recordUsages(used, bytes, path + ":" + entry.name) } - }) + } } catch (e: Exception) { options.stdout.println("Could not read jar file entry ${entry.name} from $file: $e") } } } - }) + } } catch (e: IOException) { options.stdout.println("Could not read jar file contents from $file: $e") } @@ -392,9 +436,9 @@ class AnnotationStatistics(val api: Codebase) { private fun isSkippableOwner(owner: String) = owner.startsWith("java/") || - owner.startsWith("javax/") || - owner.startsWith("kotlin") || - owner.startsWith("kotlinx/") + owner.startsWith("javax/") || + owner.startsWith("kotlin") || + owner.startsWith("kotlinx/") private fun findField(node: FieldInsnNode): FieldItem? { val cls = findClass(node.owner) ?: return null diff --git a/src/main/java/com/android/tools/metalava/AnnotationsDiffer.kt b/src/main/java/com/android/tools/metalava/AnnotationsDiffer.kt new file mode 100644 index 0000000000000000000000000000000000000000..8147ef8bea97ce1cb575b7f0bcc713e5af07d4a6 --- /dev/null +++ b/src/main/java/com/android/tools/metalava/AnnotationsDiffer.kt @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tools.metalava + +import com.android.tools.metalava.doclava1.ApiPredicate +import com.android.tools.metalava.doclava1.Errors +import com.android.tools.metalava.doclava1.FilterPredicate +import com.android.tools.metalava.model.Codebase +import com.android.tools.metalava.model.Item +import java.io.File +import java.io.IOException +import java.io.PrintWriter +import java.io.StringWriter +import java.util.function.Predicate + +/** + * The [AnnotationsDiffer] can take a codebase with annotations, and subtract + * another codebase with annotations, and emit a signature file that contains + * *only* the signatures that have annotations that were not present in the + * second codebase. + * + * The usecase for this a scenario like the following: + * - A lot of new annotations are added to the master branch + * - These annotations also apply to older APIs/branches, but for practical + * reasons we can't cherrypick the CLs that added these annotations to the + * older branches -- sometimes because those branches are under more strict + * access control, sometimes because the changes contain non-annotation + * changes as well, and sometimes because even a pure annotation CL won't + * cleanly apply to older branches since other content around the annotations + * have changed. + * - We want to merge these annotations into the older codebase as an external + * annotation file. However, we don't really want to check in the *entire* + * signature file, since it's massive (which will slow down build times in + * that older branch), may leak new APIs etc. + * - Solution: We can produce a "diff": create a signature file which contains + * *only* the signatures that have annotations from the new branch where + * (a) the signature is also present in the older codebase, and (b) where + * the annotation is not also present in the older codebase. + * + * That's what this codebase is used for: "take the master signature files + * with annotations, subtract out the signature files from say the P release, + * and check that in as the "annotations to import from master into P" delta + * file. + */ +class AnnotationsDiffer( + superset: Codebase, + private val codebase: Codebase +) { + private val relevant = HashSet<Item>(1000) + + private val predicate = object : Predicate<Item> { + override fun test(item: Item): Boolean { + if (relevant.contains(item)) { + return true + } + + val parent = item.parent() ?: return false + return test(parent) + } + } + + init { + // Limit the API to the codebase, and look at the super set codebase + // for annotations that are only there (not in the current codebase) + // and emit those + val visitor = object : ComparisonVisitor() { + override fun compare(old: Item, new: Item) { + val newModifiers = new.modifiers + for (annotation in old.modifiers.annotations()) { + var addAnnotation = false + if (annotation.isNullnessAnnotation()) { + if (!newModifiers.hasNullnessInfo()) { + addAnnotation = true + } + } else { + // TODO: Check for other incompatibilities than nullness? + val qualifiedName = annotation.qualifiedName() ?: continue + if (newModifiers.findAnnotation(qualifiedName) == null) { + addAnnotation = true + } + } + + if (addAnnotation) { + relevant.add(new) + } + } + } + } + CodebaseComparator().compare(visitor, superset, codebase, ApiPredicate(codebase)) + } + + fun writeDiffSignature(apiFile: File) { + val apiFilter = FilterPredicate(ApiPredicate(codebase)) + val apiReference = ApiPredicate(codebase, ignoreShown = true) + val apiEmit = apiFilter.and(predicate) + + progress("\nWriting annotation diff file: ") + try { + val stringWriter = StringWriter() + val writer = PrintWriter(stringWriter) + writer.use { printWriter -> + val preFiltered = codebase.original != null + val apiWriter = SignatureWriter(printWriter, apiEmit, apiReference, preFiltered) + codebase.accept(apiWriter) + } + + // Clean up blank lines + var prev: Char = ' ' + val cleanedUp = stringWriter.toString().filter { + if (it == '\n' && prev == '\n') + false + else { + prev = it + true + } + } + + apiFile.writeText(cleanedUp, Charsets.UTF_8) + } catch (e: IOException) { + reporter.report(Errors.IO_ERROR, apiFile, "Cannot open file for write.") + } + } +} \ No newline at end of file diff --git a/src/main/java/com/android/tools/metalava/AnnotationsMerger.kt b/src/main/java/com/android/tools/metalava/AnnotationsMerger.kt index cea88d63f85f5fac6eff26fc00699a08b72337ef..11ff5d5cdbdca6d018a6debe3287fa373c45d192 100644 --- a/src/main/java/com/android/tools/metalava/AnnotationsMerger.kt +++ b/src/main/java/com/android/tools/metalava/AnnotationsMerger.kt @@ -31,8 +31,6 @@ import com.android.SdkConstants.STRING_DEF_ANNOTATION import com.android.SdkConstants.TYPE_DEF_FLAG_ATTRIBUTE import com.android.SdkConstants.TYPE_DEF_VALUE_ATTRIBUTE import com.android.SdkConstants.VALUE_TRUE -import com.android.annotations.NonNull -import com.android.tools.lint.annotations.ApiDatabase import com.android.tools.lint.annotations.Extractor.ANDROID_INT_DEF import com.android.tools.lint.annotations.Extractor.ANDROID_NOTNULL import com.android.tools.lint.annotations.Extractor.ANDROID_NULLABLE @@ -45,7 +43,11 @@ import com.android.tools.lint.annotations.Extractor.IDEA_NOTNULL import com.android.tools.lint.annotations.Extractor.IDEA_NULLABLE import com.android.tools.lint.annotations.Extractor.SUPPORT_NOTNULL import com.android.tools.lint.annotations.Extractor.SUPPORT_NULLABLE -import com.android.tools.lint.detector.api.LintUtils.getChildren +import com.android.tools.lint.checks.AnnotationDetector +import com.android.tools.lint.detector.api.getChildren +import com.android.tools.metalava.doclava1.ApiFile +import com.android.tools.metalava.doclava1.ApiParseException +import com.android.tools.metalava.doclava1.ApiPredicate import com.android.tools.metalava.model.AnnotationAttribute import com.android.tools.metalava.model.AnnotationAttributeValue import com.android.tools.metalava.model.AnnotationItem @@ -54,41 +56,34 @@ import com.android.tools.metalava.model.Codebase import com.android.tools.metalava.model.DefaultAnnotationValue import com.android.tools.metalava.model.Item import com.android.tools.metalava.model.MethodItem +import com.android.tools.metalava.model.PackageItem import com.android.tools.metalava.model.psi.PsiAnnotationItem +import com.android.tools.metalava.model.visitors.ApiVisitor import com.android.utils.XmlUtils import com.google.common.base.Charsets -import com.google.common.base.Splitter -import com.google.common.collect.ImmutableSet import com.google.common.io.ByteStreams import com.google.common.io.Closeables import com.google.common.io.Files -import com.google.common.xml.XmlEscapers import org.w3c.dom.Document import org.w3c.dom.Element -import org.w3c.dom.Node import org.xml.sax.SAXParseException import java.io.File import java.io.FileInputStream import java.io.IOException import java.lang.reflect.Field -import java.util.ArrayList -import java.util.HashMap import java.util.jar.JarInputStream import java.util.regex.Pattern import java.util.zip.ZipEntry -import kotlin.Comparator /** Merges annotations into classes already registered in the given [Codebase] */ class AnnotationsMerger( - private val codebase: Codebase, - private val apiFilter: ApiDatabase?, - private val listIgnored: Boolean = true + private val codebase: Codebase ) { fun merge(mergeAnnotations: List<File>) { mergeAnnotations.forEach { mergeExisting(it) } } - private fun mergeExisting(@NonNull file: File) { + private fun mergeExisting(file: File) { if (file.isDirectory) { val files = file.listFiles() if (files != null) { @@ -106,12 +101,29 @@ class AnnotationsMerger( } catch (e: IOException) { error("Aborting: I/O problem during transform: " + e.toString()) } - + } else if (file.path.endsWith(".jaif")) { + try { + val jaif = Files.asCharSource(file, Charsets.UTF_8).read() + mergeAnnotationsJaif(file.path, jaif) + } catch (e: IOException) { + error("Aborting: I/O problem during transform: " + e.toString()) + } + } else if (file.path.endsWith(".txt") || + file.path.endsWith(".signatures") || + file.path.endsWith(".api") + ) { + try { + // .txt: Old style signature files + // Others: new signature files (e.g. kotlin-style nullness info) + mergeAnnotationsSignatureFile(file.path) + } catch (e: IOException) { + error("Aborting: I/O problem during transform: " + e.toString()) + } } } } - private fun mergeFromJar(@NonNull jar: File) { + private fun mergeFromJar(jar: File) { // Reads in an existing annotations jar and merges in entries found there // with the annotations analyzed from source. var zis: JarInputStream? = null @@ -138,7 +150,7 @@ class AnnotationsMerger( } } - private fun mergeAnnotationsXml(@NonNull path: String, @NonNull xml: String) { + private fun mergeAnnotationsXml(path: String, xml: String) { try { val document = XmlUtils.parseDocument(xml, false) mergeDocument(document) @@ -154,23 +166,167 @@ class AnnotationsMerger( } } + private fun mergeAnnotationsSignatureFile(path: String) { + try { + // Old style signature files don't support annotations anyway, so we might as well + // accept + val kotlinStyleNulls = true + val supportsStagedNullability = true + val signatureCodebase = ApiFile.parseApi(File(path), kotlinStyleNulls, supportsStagedNullability) + signatureCodebase.description = "Signature files for annotation merger: loaded from $path" + val visitor = object : ComparisonVisitor() { + override fun compare(old: Item, new: Item) { + val newModifiers = new.modifiers + for (annotation in old.modifiers.annotations()) { + var addAnnotation = false + if (annotation.isNullnessAnnotation()) { + if (!newModifiers.hasNullnessInfo()) { + addAnnotation = true + } + } else { + // TODO: Check for other incompatibilities than nullness? + val qualifiedName = annotation.qualifiedName() ?: continue + if (newModifiers.findAnnotation(qualifiedName) == null) { + addAnnotation = true + } + } + + if (addAnnotation) { + // Don't map annotation names - this would turn newly non null back into non null + new.mutableModifiers().addAnnotation( + new.codebase.createAnnotation( + annotation.toSource(), + new, + mapName = false + ) + ) + } + } + } + } + CodebaseComparator().compare(visitor, signatureCodebase, codebase, ApiPredicate(signatureCodebase)) + } catch (ex: ApiParseException) { + val message = "Unable to parse signature file $path: ${ex.message}" + throw DriverException(message) + } + } + + private fun mergeAnnotationsJaif(path: String, jaif: String) { + var pkgItem: PackageItem? = null + var clsItem: ClassItem? = null + var methodItem: MethodItem? = null + var curr: Item? = null + + for (rawLine in jaif.split("\n")) { + val line = rawLine.trim() + if (line.isEmpty()) { + continue + } + if (line.startsWith("//")) { + continue + } + if (line.startsWith("package ")) { + val pkg = line.substring("package ".length, line.length - 1) + pkgItem = codebase.findPackage(pkg) + curr = pkgItem + } else if (line.startsWith("class ")) { + val cls = line.substring("class ".length, line.length - 1) + clsItem = if (pkgItem != null) + codebase.findClass(pkgItem.qualifiedName() + "." + cls) + else + null + curr = clsItem + } else if (line.startsWith("annotation ")) { + val cls = line.substring("annotation ".length, line.length - 1) + clsItem = if (pkgItem != null) + codebase.findClass(pkgItem.qualifiedName() + "." + cls) + else + null + curr = clsItem + } else if (line.startsWith("method ")) { + val method = line.substring("method ".length, line.length - 1) + methodItem = null + if (clsItem != null) { + val index = method.indexOf('(') + if (index != -1) { + val name = method.substring(0, index) + val desc = method.substring(index) + methodItem = clsItem.findMethodByDesc(name, desc, true, true) + } + } + curr = methodItem + } else if (line.startsWith("field ")) { + val field = line.substring("field ".length, line.length - 1) + val fieldItem = clsItem?.findField(field, true, true) + curr = fieldItem + } else if (line.startsWith("parameter #")) { + val parameterIndex = line.substring("parameter #".length, line.length - 1).toInt() + val parameterItem = if (methodItem != null) { + methodItem.parameters()[parameterIndex] + } else { + null + } + curr = parameterItem + } else if (line.startsWith("type: ")) { + val typeAnnotation = line.substring("type: ".length) + if (curr != null) { + mergeJaifAnnotation(path, curr, typeAnnotation) + } + } else if (line.startsWith("return: ")) { + val annotation = line.substring("return: ".length) + if (methodItem != null) { + mergeJaifAnnotation(path, methodItem, annotation) + } + } else if (line.startsWith("inner-type")) { + warning("$path: Skipping inner-type annotations for now ($line)") + } else if (line.startsWith("int ")) { + // warning("Skipping int attribute definitions for annotations now ($line)") + } + } + } + + private fun mergeJaifAnnotation( + path: String, + item: Item, + annotationSource: String + ) { + if (annotationSource.isEmpty()) { + return + } + + if (annotationSource.contains("(")) { + warning("$path: Can't merge complex annotations from jaif yet: $annotationSource") + return + } + val originalName = annotationSource.substring(1) // remove "@" + val qualifiedName = AnnotationItem.mapName(codebase, originalName) ?: originalName + if (hasNullnessConflicts(item, qualifiedName)) { + return + } + + val annotationItem = codebase.createAnnotation("@$qualifiedName") + item.mutableModifiers().addAnnotation(annotationItem) + } + internal fun error(message: String) { // TODO: Integrate with metalava error facility - options.stderr.println("Error: " + message) + options.stderr.println("Error: $message") } internal fun warning(message: String) { - options.stdout.println("Warning: " + message) + if (options.verbose) { + options.stdout.println("Warning: $message") + } } @Suppress("PrivatePropertyName") private val XML_SIGNATURE: Pattern = Pattern.compile( // Class (FieldName | Type? Name(ArgList) Argnum?) - //"(\\S+) (\\S+|(.*)\\s+(\\S+)\\((.*)\\)( \\d+)?)"); + // "(\\S+) (\\S+|(.*)\\s+(\\S+)\\((.*)\\)( \\d+)?)"); "(\\S+) (\\S+|((.*)\\s+)?(\\S+)\\((.*)\\)( \\d+)?)" ) - private fun mergeDocument(@NonNull document: Document) { + private fun mergeDocument(document: Document) { val root = document.documentElement val rootTag = root.tagName @@ -186,9 +342,9 @@ class AnnotationsMerger( if (signature == "java.util.Calendar int get(int)") { // https://youtrack.jetbrains.com/issue/IDEA-137385 continue - } else if (signature == "java.util.Calendar void set(int, int, int) 1" - || signature == "java.util.Calendar void set(int, int, int, int, int) 1" - || signature == "java.util.Calendar void set(int, int, int, int, int, int) 1" + } else if (signature == "java.util.Calendar void set(int, int, int) 1" || + signature == "java.util.Calendar void set(int, int, int, int, int) 1" || + signature == "java.util.Calendar void set(int, int, int, int, int, int) 1" ) { // http://b.android.com/76090 continue @@ -198,23 +354,19 @@ class AnnotationsMerger( if (matcher.matches()) { val containingClass = matcher.group(1) if (containingClass == null) { - warning("Could not find class for " + signature) - continue - } - - if (apiFilter != null && - !hasHistoricData(item) && - !apiFilter.hasClass(containingClass) - ) { - if (listIgnored) { - warning("Skipping imported element because it is not part of the API file: $containingClass") - } + warning("Could not find class for $signature") continue } val classItem = codebase.findClass(containingClass) if (classItem == null) { - warning("Could not find class $containingClass; omitting annotations merge") + // Well known exceptions from IntelliJ's external annotations + // we won't complain loudly about + if (wellKnownIgnoredImport(containingClass)) { + continue + } + + warning("Could not find class $containingClass; omitting annotation from merge") continue } @@ -235,29 +387,34 @@ class AnnotationsMerger( } else if (signature.indexOf(' ') == -1 && signature.indexOf('.') != -1) { // Must be just a class val containingClass = signature - if (apiFilter != null && - !hasHistoricData(item) && - !apiFilter.hasClass(containingClass) - ) { - if (listIgnored) { - warning("Skipping imported element because it is not part of the API file: $containingClass") - } - continue - } - val classItem = codebase.findClass(containingClass) if (classItem == null) { - warning("Could not find class $containingClass; omitting annotations merge") + if (wellKnownIgnoredImport(containingClass)) { + continue + } + + warning("Could not find class $containingClass; omitting annotation from merge") continue } mergeAnnotations(item, classItem) } else { - warning("No merge match for signature " + signature) + warning("No merge match for signature $signature") } } } + private fun wellKnownIgnoredImport(containingClass: String): Boolean { + if (containingClass.startsWith("javax.swing.") || + containingClass.startsWith("javax.naming.") || + containingClass.startsWith("java.awt.") || + containingClass.startsWith("org.jdom.") + ) { + return true + } + return false + } + // The parameter declaration used in XML files should not have duplicated spaces, // and there should be no space after commas (we can't however strip out all spaces, // since for example the spaces around the "extends" keyword needs to be there in @@ -268,37 +425,31 @@ class AnnotationsMerger( } private fun mergeMethodOrParameter( - item: Element, containingClass: String, classItem: ClassItem, - methodName: String, parameterIndex: Int, + item: Element, + containingClass: String, + classItem: ClassItem, + methodName: String, + parameterIndex: Int, parameters: String ) { @Suppress("NAME_SHADOWING") val parameters = fixParameterString(parameters) - if (apiFilter != null && - !hasHistoricData(item) && - !apiFilter.hasMethod(containingClass, methodName, parameters) - ) { - if (listIgnored) { - warning( - "Skipping imported element because it is not part of the API file: " - + containingClass + "#" + methodName + "(" + parameters + ")" - ) - } - return - } - val methodItem: MethodItem? = classItem.findMethod(methodName, parameters) if (methodItem == null) { - warning("Could not find class $methodName($parameters) in $containingClass; omitting annotations merge") + if (wellKnownIgnoredImport(containingClass)) { + return + } + + warning("Could not find method $methodName($parameters) in $containingClass; omitting annotation from merge") return } if (parameterIndex != -1) { val parameterItem = methodItem.parameters()[parameterIndex] - if ("java.util.Calendar" == containingClass && "set" == methodName - && parameterIndex > 0 + if ("java.util.Calendar" == containingClass && "set" == methodName && + parameterIndex > 0 ) { // Skip the metadata for Calendar.set(int, int, int+); see // https://code.google.com/p/android/issues/detail?id=73982 @@ -313,25 +464,18 @@ class AnnotationsMerger( } private fun mergeField(item: Element, containingClass: String, classItem: ClassItem, fieldName: String) { - if (apiFilter != null && - !hasHistoricData(item) && - !apiFilter.hasField(containingClass, fieldName) - ) { - if (listIgnored) { - warning( - "Skipping imported element because it is not part of the API file: " - + containingClass + "#" + fieldName - ) - } - } else { - val fieldItem = classItem.findField(fieldName) - if (fieldItem == null) { - warning("Could not find field $fieldName in $containingClass; omitting annotations merge") + + val fieldItem = classItem.findField(fieldName) + if (fieldItem == null) { + if (wellKnownIgnoredImport(containingClass)) { return } - mergeAnnotations(item, fieldItem) + warning("Could not find field $fieldName in $containingClass; omitting annotation from merge") + return } + + mergeAnnotations(item, fieldItem) } private fun getAnnotationName(element: Element): String { @@ -343,53 +487,82 @@ class AnnotationsMerger( return qualifiedName } - private fun mergeAnnotations(xmlElement: Element, item: Item): Int { - var count = 0 - + private fun mergeAnnotations(xmlElement: Element, item: Item) { loop@ for (annotationElement in getChildren(xmlElement)) { - val qualifiedName = getAnnotationName(annotationElement) - if (!AnnotationItem.isSignificantAnnotation(qualifiedName)) { - continue - } - var haveNullable = false - var haveNotNull = false - for (existing in item.modifiers.annotations()) { - val name = existing.qualifiedName() ?: continue - if (isNonNull(name)) { - haveNotNull = true - } - if (isNullable(name)) { - haveNullable = true - } - if (name == qualifiedName) { - continue@loop - } - } - - // Make sure we don't have a conflict between nullable and not nullable - if (isNonNull(qualifiedName) && haveNullable || isNullable(qualifiedName) && haveNotNull) { - warning("Found both @Nullable and @NonNull after import for " + item) - continue + val originalName = getAnnotationName(annotationElement) + val qualifiedName = AnnotationItem.mapName(codebase, originalName) ?: originalName + if (hasNullnessConflicts(item, qualifiedName)) { + continue@loop } val annotationItem = createAnnotation(annotationElement) ?: continue item.mutableModifiers().addAnnotation(annotationItem) - count++ } + } - return count + private fun hasNullnessConflicts( + item: Item, + qualifiedName: String + ): Boolean { + var haveNullable = false + var haveNotNull = false + for (existing in item.modifiers.annotations()) { + val name = existing.qualifiedName() ?: continue + if (isNonNull(name)) { + haveNotNull = true + } + if (isNullable(name)) { + haveNullable = true + } + if (name == qualifiedName) { + return true + } + } + + // Make sure we don't have a conflict between nullable and not nullable + if (isNonNull(qualifiedName) && haveNullable || isNullable(qualifiedName) && haveNotNull) { + warning("Found both @Nullable and @NonNull after import for $item") + return true + } + return false } /** Reads in annotation data from an XML item (using IntelliJ IDE's external annotations XML format) and * creates a corresponding [AnnotationItem], performing some "translations" in the process (e.g. mapping - * from IntelliJ annotations like `org.jetbrains.annotations.Nullable` to `android.support.annotation.Nullable`, - * as well as dropping constants from typedefs that aren't included according to the [apiFilter]. */ + * from IntelliJ annotations like `org.jetbrains.annotations.Nullable` to `android.support.annotation.Nullable`. */ private fun createAnnotation(annotationElement: Element): AnnotationItem? { val tagName = annotationElement.tagName assert(tagName == "annotation") { tagName } val name = annotationElement.getAttribute(ATTR_NAME) assert(name != null && !name.isEmpty()) when { + name == "org.jetbrains.annotations.Range" -> { + val children = getChildren(annotationElement) + assert(children.size == 2) { children.size } + val valueElement1 = children[0] + val valueElement2 = children[1] + val valName1 = valueElement1.getAttribute(ATTR_NAME) + val value1 = valueElement1.getAttribute(ATTR_VAL) + val valName2 = valueElement2.getAttribute(ATTR_NAME) + val value2 = valueElement2.getAttribute(ATTR_VAL) + return PsiAnnotationItem.create( + codebase, XmlBackedAnnotationItem( + codebase, AnnotationDetector.INT_RANGE_ANNOTATION.newName(), + listOf( + // Add "L" suffix to ensure that we don't for example interpret "-1" as + // an integer -1 and then end up recording it as "ffffffff" instead of -1L + XmlBackedAnnotationAttribute( + valName1, + value1 + (if (value1.last().isDigit()) "L" else "") + ), + XmlBackedAnnotationAttribute( + valName2, + value2 + (if (value2.last().isDigit()) "L" else "") + ) + ) + ) + ) + } name == IDEA_MAGIC -> { val children = getChildren(annotationElement) assert(children.size == 1) { children.size } @@ -415,66 +588,20 @@ class AnnotationsMerger( // It's mainly used for sorting anyway. } - if (apiFilter != null) { - // Search in API database - var fields: Set<String>? = apiFilter.getDeclaredIntFields(clsName) - if ("java.util.zip.ZipEntry" == clsName) { - // The metadata says valuesFromClass ZipEntry, and unfortunately - // that class implements ZipConstants and therefore imports a large - // number of irrelevant constants that aren't valid here. Instead, - // only allow these two: - fields = ImmutableSet.of("STORED", "DEFLATED") - } - - if (fields != null) { - val sorted = ArrayList(fields) - sorted.sort() - if (reflectionFields != null) { - val rank = HashMap<String, Int>() - run { - var i = 0 - val n = sorted.size - while (i < n) { - rank[sorted[i]] = reflectionFields.size + i - i++ - - } - } - var i = 0 - val n = reflectionFields.size - while (i < n) { - rank[reflectionFields[i].name] = i - i++ - } - sorted.sortWith(Comparator { o1, o2 -> - val rank1 = rank[o1] - val rank2 = rank[o2] - val delta = rank1!! - rank2!! - if (delta != 0) { - return@Comparator delta - - } - o1.compareTo(o2) - }) - } - var first = true - for (field in sorted) { - if (first) { - first = false - } else { - sb.append(',').append(' ') - } - sb.append(clsName).append('.').append(field) - } - found = true - } - } // Attempt to sort in reflection order - if (!found && reflectionFields != null && (apiFilter == null || apiFilter.hasClass(clsName))) { + if (!found && reflectionFields != null) { + val filterEmit = ApiVisitor(codebase).filterEmit + // Attempt with reflection var first = true for (field in reflectionFields) { if (field.type == Integer.TYPE || field.type == Int::class.javaPrimitiveType) { + // Make sure this field is included in our API too + val fieldItem = codebase.findClass(clsName)?.findField(field.name) + if (fieldItem == null || !filterEmit.test(fieldItem)) { + continue + } + if (first) { first = false } else { @@ -496,16 +623,6 @@ class AnnotationsMerger( } } - if (apiFilter != null) { - value = removeFiltered(value) - while (value.contains(", ,")) { - value = value.replace(", ,", ",") - } - if (value.startsWith(", ")) { - value = value.substring(2) - } - } - val attributes = mutableListOf<XmlBackedAnnotationAttribute>() attributes.add(XmlBackedAnnotationAttribute(TYPE_DEF_VALUE_ATTRIBUTE, value)) if (flag) { @@ -514,15 +631,18 @@ class AnnotationsMerger( return PsiAnnotationItem.create( codebase, XmlBackedAnnotationItem( codebase, - if (valName == "stringValues") STRING_DEF_ANNOTATION else INT_DEF_ANNOTATION, attributes + if (valName == "stringValues") STRING_DEF_ANNOTATION.newName() else INT_DEF_ANNOTATION.newName(), + attributes ) ) } - name == STRING_DEF_ANNOTATION || - name == ANDROID_STRING_DEF || - name == INT_DEF_ANNOTATION || - name == ANDROID_INT_DEF -> { + name == STRING_DEF_ANNOTATION.oldName() || + name == STRING_DEF_ANNOTATION.newName() || + name == ANDROID_STRING_DEF || + name == INT_DEF_ANNOTATION.oldName() || + name == INT_DEF_ANNOTATION.newName() || + name == ANDROID_INT_DEF -> { val children = getChildren(annotationElement) var valueElement = children[0] val valName = valueElement.getAttribute(ATTR_NAME) @@ -534,7 +654,9 @@ class AnnotationsMerger( assert(TYPE_DEF_FLAG_ATTRIBUTE == valueElement.getAttribute(ATTR_NAME)) flag = VALUE_TRUE == valueElement.getAttribute(ATTR_VAL) } - val intDef = INT_DEF_ANNOTATION == name || ANDROID_INT_DEF == name + val intDef = INT_DEF_ANNOTATION.oldName() == name || + INT_DEF_ANNOTATION.newName() == name || + ANDROID_INT_DEF == name val attributes = mutableListOf<XmlBackedAnnotationAttribute>() attributes.add(XmlBackedAnnotationAttribute(TYPE_DEF_VALUE_ATTRIBUTE, value)) @@ -544,7 +666,7 @@ class AnnotationsMerger( return PsiAnnotationItem.create( codebase, XmlBackedAnnotationItem( codebase, - if (intDef) INT_DEF_ANNOTATION else STRING_DEF_ANNOTATION, attributes + if (intDef) INT_DEF_ANNOTATION.newName() else STRING_DEF_ANNOTATION.newName(), attributes ) ) } @@ -574,9 +696,9 @@ class AnnotationsMerger( } } - isNonNull(name) -> return codebase.createAnnotation("@$SUPPORT_NOTNULL") + isNonNull(name) -> return codebase.createAnnotation("@$ANDROIDX_NOTNULL") - isNullable(name) -> return codebase.createAnnotation("@$SUPPORT_NULLABLE") + isNullable(name) -> return codebase.createAnnotation("@$ANDROIDX_NULLABLE") else -> { val children = getChildren(annotationElement) @@ -597,91 +719,21 @@ class AnnotationsMerger( } } - private fun removeFiltered(originalValue: String): String { - var value = originalValue - assert(apiFilter != null) - if (value.startsWith("{")) { - value = value.substring(1) - } - if (value.endsWith("}")) { - value = value.substring(0, value.length - 1) - } - value = value.trim { it <= ' ' } - val sb = StringBuilder(value.length) - sb.append('{') - for (escaped in Splitter.on(',').omitEmptyStrings().trimResults().split(value)) { - val fqn = unescapeXml(escaped) - if (fqn.startsWith("\"")) { - continue - } - val index = fqn.lastIndexOf('.') - val cls = fqn.substring(0, index) - val field = fqn.substring(index + 1) - if (apiFilter?.hasField(cls, field) != false) { - if (sb.length > 1) { // 0: '{' - sb.append(", ") - } - sb.append(fqn) - } else if (listIgnored) { - warning("Skipping constant from typedef because it is not part of the SDK: " + fqn) - } - } - sb.append('}') - return escapeXml(sb.toString()) - } - private fun isNonNull(name: String): Boolean { - return name == IDEA_NOTNULL - || name == ANDROID_NOTNULL - || name == SUPPORT_NOTNULL + return name == IDEA_NOTNULL || + name == ANDROID_NOTNULL || + name == ANDROIDX_NOTNULL || + name == SUPPORT_NOTNULL } private fun isNullable(name: String): Boolean { - return name == IDEA_NULLABLE - || name == ANDROID_NULLABLE - || name == SUPPORT_NULLABLE - } - - /** - * Returns true if this XML entry contains historic metadata, e.g. has - * an api attribute which designates that this API may no longer be in the SDK, - * but the annotations should be preserved for older API levels - */ - private fun hasHistoricData(@NonNull item: Element): Boolean { - var curr: Node? = item.firstChild - while (curr != null) { - // Example: - // <item name="android.provider.Browser BOOKMARKS_URI"> - // <annotation name="android.support.annotation.RequiresPermission.Read"> - // <val name="value" val=""com.android.browser.permission.READ_HISTORY_BOOKMARKS"" /> - // <val name="apis" val=""..22"" /> - // </annotation> - // .. - if (curr.nodeType == Node.ELEMENT_NODE && "annotation" == curr.nodeName) { - var inner: Node? = curr.firstChild - while (inner != null) { - if (inner.nodeType == Node.ELEMENT_NODE && - "val" == inner.nodeName && - "apis" == (inner as Element).getAttribute("name") - ) { - return true - } - inner = inner.nextSibling - } - } - curr = curr.nextSibling - } - - return false - } - - @NonNull - private fun escapeXml(@NonNull unescaped: String): String { - return XmlEscapers.xmlAttributeEscaper().escape(unescaped) + return name == IDEA_NULLABLE || + name == ANDROID_NULLABLE || + name == ANDROIDX_NULLABLE || + name == SUPPORT_NULLABLE } - @NonNull - private fun unescapeXml(@NonNull escaped: String): String { + private fun unescapeXml(escaped: String): String { var workingString = escaped.replace(QUOT_ENTITY, "\"") workingString = workingString.replace(LT_ENTITY, "<") workingString = workingString.replace(GT_ENTITY, ">") @@ -718,7 +770,7 @@ class XmlBackedAnnotationItem( val qualifiedName = qualifiedName() ?: return "" if (attributes.isEmpty()) { - return "@" + qualifiedName + return "@$qualifiedName" } val sb = StringBuilder(30) diff --git a/src/main/java/com/android/tools/metalava/ApiAnalyzer.kt b/src/main/java/com/android/tools/metalava/ApiAnalyzer.kt index 810e7e1d93b7361d77ff53c07bab70beece6860e..6cf812705b384eb72fafe4f3610541a19b5aacc5 100644 --- a/src/main/java/com/android/tools/metalava/ApiAnalyzer.kt +++ b/src/main/java/com/android/tools/metalava/ApiAnalyzer.kt @@ -16,6 +16,7 @@ package com.android.tools.metalava +import com.android.tools.metalava.doclava1.ApiPredicate import com.android.tools.metalava.doclava1.Errors import com.android.tools.metalava.model.AnnotationAttributeValue import com.android.tools.metalava.model.ClassItem @@ -30,7 +31,6 @@ import com.android.tools.metalava.model.ParameterItem import com.android.tools.metalava.model.TypeItem import com.android.tools.metalava.model.visitors.ApiVisitor import com.android.tools.metalava.model.visitors.ItemVisitor -import com.android.tools.metalava.model.visitors.VisibleItemVisitor import java.util.ArrayList import java.util.HashMap import java.util.HashSet @@ -183,7 +183,7 @@ class ApiAnalyzer( val superConstructor = constructor.superConstructor if (superConstructor == null || (superConstructor.containingClass() != superClass && - superConstructor.containingClass() != cls) + superConstructor.containingClass() != cls) ) { constructor.superConstructor = superDefaultConstructor } @@ -195,7 +195,7 @@ class ApiAnalyzer( if (!isLeaf || cls.hasPrivateConstructor || cls.constructors().isNotEmpty()) { val constructors = cls.constructors() for (constructor in constructors) { - if (constructor.parameters().isEmpty() && constructor.isPublic) { + if (constructor.parameters().isEmpty() && constructor.isPublic && !constructor.hidden) { cls.defaultConstructor = constructor return } @@ -274,7 +274,7 @@ class ApiAnalyzer( } val currentUsesAvailableTypes = !referencesExcludedType(current, filter) - val nextUsesAvailableTypes = !referencesExcludedType(current, filter) + val nextUsesAvailableTypes = !referencesExcludedType(next, filter) if (currentUsesAvailableTypes != nextUsesAvailableTypes) { return if (currentUsesAvailableTypes) { current @@ -295,8 +295,7 @@ class ApiAnalyzer( val nextParameterCount = next.parameters().size if (currentParameterCount <= nextParameterCount) { current - } else - next + } else next } } @@ -321,7 +320,6 @@ class ApiAnalyzer( addInheritedStubsFrom(cls, hiddenSuperClasses, superClasses, filterEmit, filterReference) addInheritedInterfacesFrom(cls, hiddenSuperClasses, filterReference) - } private fun addInheritedInterfacesFrom( @@ -338,7 +336,7 @@ class ApiAnalyzer( if (interfaceTypes == null) { interfaceTypes = cls.interfaceTypes().toMutableList() interfaceTypeClasses = - interfaceTypes.asSequence().map { it.asClass() }.filterNotNull().toMutableList() + interfaceTypes.asSequence().map { it.asClass() }.filterNotNull().toMutableList() if (cls.isInterface()) { cls.superClass()?.let { interfaceTypeClasses.add(it) } } @@ -369,7 +367,8 @@ class ApiAnalyzer( cls: ClassItem, hiddenSuperClasses: Sequence<ClassItem>, superClasses: Sequence<ClassItem>, - filterEmit: Predicate<Item>, filterReference: Predicate<Item> + filterEmit: Predicate<Item>, + filterReference: Predicate<Item> ) { // Also generate stubs for any methods we would have inherited from abstract parents @@ -412,21 +411,19 @@ class ApiAnalyzer( } } - if (compatibility.includePublicMethodsFromHiddenSuperClasses) { - // Also add in any concrete public methods from hidden super classes - for (superClass in hiddenSuperClasses) { - for (method in superClass.methods()) { - if (method.modifiers.isAbstract()) { - continue - } - val name = method.name() - val list = interfaceNames[name] ?: run { - val list = ArrayList<MethodItem>() - interfaceNames[name] = list - list - } - list.add(method) + // Also add in any concrete public methods from hidden super classes + for (superClass in hiddenSuperClasses) { + for (method in superClass.methods()) { + if (method.modifiers.isAbstract()) { + continue + } + val name = method.name() + val list = interfaceNames[name] ?: run { + val list = ArrayList<MethodItem>() + interfaceNames[name] = list + list } + list.add(method) } } @@ -493,9 +490,7 @@ class ApiAnalyzer( method.documentation = "// Inlined stub from hidden parent class ${it.containingClass().qualifiedName()}\n" + method.documentation */ - if (it.containingClass().isInterface()) { - method.inheritedInterfaceMethod = true - } + method.inheritedMethod = true cls.addMethod(method) } } @@ -521,7 +516,7 @@ class ApiAnalyzer( fun mergeExternalAnnotations() { val mergeAnnotations = options.mergeAnnotations if (!mergeAnnotations.isEmpty()) { - AnnotationsMerger(codebase, options.apiFilter).merge(mergeAnnotations) + AnnotationsMerger(codebase).merge(mergeAnnotations) } } @@ -531,15 +526,12 @@ class ApiAnalyzer( */ private fun propagateHiddenRemovedAndDocOnly(includingFields: Boolean) { packages.accept(object : ItemVisitor(visitConstructorsAsMethods = true, nestInnerClasses = true) { - override fun visitItem(item: Item) { - if (item.modifiers.hasShowAnnotation()) { - item.hidden = false - } else if (item.modifiers.hasHideAnnotations()) { - item.hidden = true - } - } - override fun visitPackage(pkg: PackageItem) { + if (pkg.modifiers.hasShowAnnotation()) { + pkg.hidden = false + } else if (pkg.modifiers.hasHideAnnotations()) { + pkg.hidden = true + } val containingPackage = pkg.containingPackage() if (containingPackage != null) { if (containingPackage.hidden) { @@ -553,7 +545,14 @@ class ApiAnalyzer( override fun visitClass(cls: ClassItem) { val containingClass = cls.containingClass() - if (containingClass != null) { + if (cls.modifiers.hasShowAnnotation()) { + cls.hidden = false + // Make containing package non-hidden if it contains a show-annotation + // class. Doclava does this in PackageInfo.isHidden(). + cls.containingPackage().hidden = false + } else if (cls.modifiers.hasHideAnnotations()) { + cls.hidden = true + } else if (containingClass != null) { if (containingClass.hidden) { cls.hidden = true } @@ -578,72 +577,49 @@ class ApiAnalyzer( } override fun visitMethod(method: MethodItem) { - val containingClass = method.containingClass() - if (containingClass.hidden) { + if (method.modifiers.hasShowAnnotation()) { + method.hidden = false + } else if (method.modifiers.hasHideAnnotations()) { method.hidden = true - } - if (containingClass.docOnly) { - method.docOnly = true - } - if (containingClass.removed) { - method.removed = true + } else { + val containingClass = method.containingClass() + if (containingClass.hidden) { + method.hidden = true + } + if (containingClass.docOnly) { + method.docOnly = true + } + if (containingClass.removed) { + method.removed = true + } } } override fun visitField(field: FieldItem) { - val containingClass = field.containingClass() - /* We don't always propagate field visibility down to the fields - because we sometimes move fields around, and in that - case we don't want to carry forward the "hidden" attribute - from the field that wasn't marked on the field but its - container interface. - */ - if (includingFields && containingClass.hidden) { + if (field.modifiers.hasShowAnnotation()) { + field.hidden = false + } else if (field.modifiers.hasHideAnnotations()) { field.hidden = true - } - if (containingClass.docOnly) { - field.docOnly = true - } - if (containingClass.removed) { - field.removed = true - } - } - }) - } - - private fun applyApiFilter() { - options.apiFilter?.let { filter -> - packages.accept(object : VisibleItemVisitor() { - - override fun visitPackage(pkg: PackageItem) { - if (!filter.hasPackage(pkg.qualifiedName())) { - pkg.included = false - } - } - - override fun visitClass(cls: ClassItem) { - if (!filter.hasClass(cls.qualifiedName())) { - cls.included = false + } else { + val containingClass = field.containingClass() + /* We don't always propagate field visibility down to the fields + because we sometimes move fields around, and in that + case we don't want to carry forward the "hidden" attribute + from the field that wasn't marked on the field but its + container interface. + */ + if (includingFields && containingClass.hidden) { + field.hidden = true } - } - - override fun visitMethod(method: MethodItem) { - if (!filter.hasMethod( - method.containingClass().qualifiedName(), method.name(), - method.formatParameters() - ) - ) { - method.included = false + if (containingClass.docOnly) { + field.docOnly = true } - } - - override fun visitField(field: FieldItem) { - if (!filter.hasField(field.containingClass().qualifiedName(), field.name())) { - field.included = false + if (containingClass.removed) { + field.removed = true } } - }) - } + } + }) } private fun checkHiddenTypes() { @@ -769,8 +745,8 @@ class ApiAnalyzer( ) continue } - if (level.contains("normal") || level.contains("dangerous") - || level.contains("ephemeral") + if (level.contains("normal") || level.contains("dangerous") || + level.contains("ephemeral") ) { nonSystem.add(perm) } else { @@ -781,7 +757,7 @@ class ApiAnalyzer( reporter.report( Errors.REMOVED_FIELD, method, "None of the permissions ${missing.joinToString()} are defined by manifest " + - "${codebase.manifest}." + "${codebase.manifest}." ) } @@ -789,9 +765,9 @@ class ApiAnalyzer( hasAnnotation = false } else if (any && !nonSystem.isEmpty() || !any && system.isEmpty()) { reporter.report( - Errors.REQUIRES_PERMISSION, method, "Method '" + method.name() - + "' must be protected with a system permission; it currently" - + " allows non-system callers holding " + nonSystem.toString() + Errors.REQUIRES_PERMISSION, method, "Method '" + method.name() + + "' must be protected with a system permission; it currently" + + " allows non-system callers holding " + nonSystem.toString() ) } } @@ -799,8 +775,8 @@ class ApiAnalyzer( if (!hasAnnotation) { reporter.report( - Errors.REQUIRES_PERMISSION, method, "Method '" + method.name() - + "' must be protected with a system permission." + Errors.REQUIRES_PERMISSION, method, "Method '" + method.name() + + "' must be protected with a system permission." ) } } @@ -832,7 +808,7 @@ class ApiAnalyzer( fun handleStripping() { // TODO: Switch to visitor iteration - //val stubPackages = options.stubPackages + // val stubPackages = options.stubPackages val stubImportPackages = options.stubImportPackages handleStripping(stubImportPackages) } @@ -840,13 +816,15 @@ class ApiAnalyzer( private fun handleStripping(stubImportPackages: Set<String>) { val notStrippable = HashSet<ClassItem>(5000) + val filter = ApiPredicate(codebase, ignoreShown = true) + // If a class is public or protected, not hidden, not imported and marked as included, // then we can't strip it val allTopLevelClasses = codebase.getPackages().allTopLevelClasses().toList() allTopLevelClasses .filter { it.checkLevel() && it.emit && !it.hidden() } .forEach { - cantStripThis(it, notStrippable, stubImportPackages) + cantStripThis(it, filter, notStrippable, stubImportPackages) } // complain about anything that looks includeable but is not supposed to @@ -866,8 +844,8 @@ class ApiAnalyzer( // don't bother reporting deprecated methods // unless they are public reporter.report( - Errors.DEPRECATED, m, "Method " + cl.qualifiedName() + "." - + m.name() + " is deprecated" + Errors.DEPRECATED, m, "Method " + cl.qualifiedName() + "." + + m.name() + " is deprecated" ) } @@ -879,14 +857,14 @@ class ApiAnalyzer( reporter.report( Errors.UNAVAILABLE_SYMBOL, m, "Method ${cl.qualifiedName()}.${m.name()} returns unavailable " + - "type ${hiddenClass.simpleName()}" + "type ${hiddenClass.simpleName()}" ) } else { // Return type contains a generic parameter reporter.report( Errors.HIDDEN_TYPE_PARAMETER, m, "Method ${cl.qualifiedName()}.${m.name()} returns unavailable " + - "type ${hiddenClass.simpleName()} as a type parameter" + "type ${hiddenClass.simpleName()} as a type parameter" ) } } @@ -916,12 +894,17 @@ class ApiAnalyzer( } else if (cl.deprecated) { // not hidden, but deprecated reporter.report(Errors.DEPRECATED, cl, "Class ${cl.qualifiedName()} is deprecated") + } else { + // Bring this class back + cl.hidden = false + cl.removed = false } } } private fun cantStripThis( cl: ClassItem, + filter: Predicate<Item>, notStrippable: MutableSet<ClassItem>, stubImportPackages: Set<String>? ) { @@ -942,43 +925,36 @@ class ApiAnalyzer( // cant strip any public fields or their generics for (field in cl.fields()) { - if (!field.checkLevel()) { + if (!filter.test(field)) { continue } val fieldType = field.type() if (!fieldType.primitive) { val typeClass = fieldType.asClass() if (typeClass != null) { - cantStripThis( - typeClass, notStrippable, stubImportPackages - ) + cantStripThis(typeClass, filter, notStrippable, stubImportPackages) } for (cls in fieldType.typeArgumentClasses()) { - cantStripThis( - cls, notStrippable, stubImportPackages - ) + cantStripThis(cls, filter, notStrippable, stubImportPackages) } } } // cant strip any of the type's generics for (cls in cl.typeArgumentClasses()) { - cantStripThis( - cls, notStrippable, stubImportPackages - ) + cantStripThis(cls, filter, notStrippable, stubImportPackages) } // cant strip any of the annotation elements // cantStripThis(cl.annotationElements(), notStrippable); // take care of methods - cantStripThis(cl.methods(), notStrippable, stubImportPackages) - cantStripThis(cl.constructors(), notStrippable, stubImportPackages) + cantStripThis(cl.methods(), filter, notStrippable, stubImportPackages) + cantStripThis(cl.constructors(), filter, notStrippable, stubImportPackages) // blow the outer class open if this is an inner class val containingClass = cl.containingClass() if (containingClass != null) { - cantStripThis( - containingClass, notStrippable, stubImportPackages - ) + cantStripThis(containingClass, filter, notStrippable, stubImportPackages) } // blow open super class and interfaces + // TODO: Consider using val superClass = cl.filteredSuperclass(filter) val superClass = cl.superClass() if (superClass != null) { if (superClass.isHiddenOrRemoved()) { @@ -991,18 +967,16 @@ class ApiAnalyzer( cl.setSuperClass(publicSuper) if (!superClass.isFromClassPath()) { reporter.report( - Errors.HIDDEN_SUPERCLASS, cl, "Public class " + cl.qualifiedName() - + " stripped of unavailable superclass " + superClass.qualifiedName() + Errors.HIDDEN_SUPERCLASS, cl, "Public class " + cl.qualifiedName() + + " stripped of unavailable superclass " + superClass.qualifiedName() ) } } else { - cantStripThis( - superClass, notStrippable, stubImportPackages - ) + cantStripThis(superClass, filter, notStrippable, stubImportPackages) if (superClass.isPrivate && !superClass.isFromClassPath()) { reporter.report( - Errors.PRIVATE_SUPERCLASS, cl, "Public class " - + cl.qualifiedName() + " extends private class " + superClass.qualifiedName() + Errors.PRIVATE_SUPERCLASS, cl, "Public class " + + cl.qualifiedName() + " extends private class " + superClass.qualifiedName() ) } } @@ -1010,60 +984,46 @@ class ApiAnalyzer( } private fun cantStripThis( - methods: List<MethodItem>, notStrippable: MutableSet<ClassItem>, + methods: List<MethodItem>, + filter: Predicate<Item>, + notStrippable: MutableSet<ClassItem>, stubImportPackages: Set<String>? ) { // for each method, blow open the parameters, throws and return types. also blow open their // generics for (method in methods) { - if (!method.checkLevel()) { + if (!filter.test(method)) { continue } for (typeParameterClass in method.typeArgumentClasses()) { - cantStripThis( - typeParameterClass, notStrippable, - stubImportPackages - ) + cantStripThis(typeParameterClass, filter, notStrippable, stubImportPackages) } for (parameter in method.parameters()) { for (parameterTypeClass in parameter.type().typeArgumentClasses()) { - cantStripThis( - parameterTypeClass, notStrippable, stubImportPackages - ) + cantStripThis(parameterTypeClass, filter, notStrippable, stubImportPackages) for (tcl in parameter.type().typeArgumentClasses()) { if (tcl.isHiddenOrRemoved()) { reporter.report( Errors.UNAVAILABLE_SYMBOL, method, "Parameter of hidden type ${tcl.fullName()}" + - "in ${method.containingClass().qualifiedName()}.${method.name()}()" + "in ${method.containingClass().qualifiedName()}.${method.name()}()" ) } else { - cantStripThis( - tcl, notStrippable, - stubImportPackages - ) + cantStripThis(tcl, filter, notStrippable, stubImportPackages) } } } } for (thrown in method.throwsTypes()) { - cantStripThis( - thrown, notStrippable, stubImportPackages - ) + cantStripThis(thrown, filter, notStrippable, stubImportPackages) } val returnType = method.returnType() if (returnType != null && !returnType.primitive) { val returnTypeClass = returnType.asClass() if (returnTypeClass != null) { - cantStripThis( - returnTypeClass, notStrippable, - stubImportPackages - ) + cantStripThis(returnTypeClass, filter, notStrippable, stubImportPackages) for (tyItem in returnType.typeArgumentClasses()) { - cantStripThis( - tyItem, notStrippable, - stubImportPackages - ) + cantStripThis(tyItem, filter, notStrippable, stubImportPackages) } } } diff --git a/src/main/java/com/android/tools/metalava/ArtifactTagger.kt b/src/main/java/com/android/tools/metalava/ArtifactTagger.kt new file mode 100644 index 0000000000000000000000000000000000000000..23c9be7115296b0e631c1ae90afee9eb975bad21 --- /dev/null +++ b/src/main/java/com/android/tools/metalava/ArtifactTagger.kt @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tools.metalava + +import com.android.tools.metalava.doclava1.ApiFile +import com.android.tools.metalava.doclava1.ApiParseException +import com.android.tools.metalava.doclava1.Errors +import com.android.tools.metalava.doclava1.TextCodebase +import com.android.tools.metalava.model.ClassItem +import com.android.tools.metalava.model.Codebase +import com.android.tools.metalava.model.visitors.ApiVisitor +import java.io.File + +/** Registry of signature files and the corresponding artifact descriptions */ +class ArtifactTagger { + /** Ordered map from signature file to artifact description */ + private val artifacts = LinkedHashMap<File, String>() + + /** Registers the given [artifactId] for the APIs found in the given [signatureFile] */ + fun register(artifactId: String, signatureFile: File) { + artifacts[signatureFile] = artifactId + } + + /** Any registered artifacts? */ + fun any() = artifacts.isNotEmpty() + + /** Returns the artifacts */ + private fun getRegistrations(): Collection<Map.Entry<File, String>> = artifacts.entries + + /** + * Applies the artifact registrations in this map to the given [codebase]. + * If [warnAboutMissing] is true, it will complain if any classes in the API + * are found that have not been tagged (e.g. where no artifact signature file + * referenced the API. + */ + fun tag(codebase: Codebase, warnAboutMissing: Boolean = true) { + if (!any()) { + return + } + + // Read through the XML files in order, applying their artifact information + // to the Javadoc models. + for (artifactSpec in getRegistrations()) { + val xmlFile = artifactSpec.key + val artifactName = artifactSpec.value + + val specApi: TextCodebase + try { + val kotlinStyleNulls = options.inputKotlinStyleNulls + specApi = ApiFile.parseApi(xmlFile, kotlinStyleNulls, false) + } catch (e: ApiParseException) { + reporter.report( + Errors.BROKEN_ARTIFACT_FILE, xmlFile, + "Failed to parse $xmlFile for $artifactName artifact data.\n" + ) + continue + } + + applyArtifactsFromSpec(artifactName, specApi, codebase) + } + + if (warnAboutMissing) { + codebase.accept(object : ApiVisitor(codebase) { + override fun visitClass(cls: ClassItem) { + if (cls.artifact == null && cls.isTopLevelClass()) { + reporter.report( + Errors.NO_ARTIFACT_DATA, cls, + "No registered artifact signature file referenced class ${cls.qualifiedName()}" + ) + } + } + }) + } + } + + private fun applyArtifactsFromSpec( + mavenSpec: String, + specApi: TextCodebase, + codebase: Codebase + ) { + for (specPkg in specApi.getPackages().packages) { + val pkg = codebase.findPackage(specPkg.qualifiedName()) ?: continue + for (cls in pkg.allClasses()) { + if (cls.artifact == null) { + cls.artifact = mavenSpec + } else { + reporter.report( + Errors.BROKEN_ARTIFACT_FILE, cls, + "Class ${cls.qualifiedName()} belongs to multiple artifacts: ${cls.artifact} and $mavenSpec" + ) + } + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/android/tools/metalava/ComparisonVisitor.kt b/src/main/java/com/android/tools/metalava/ComparisonVisitor.kt index ff1a4305cb9b4b651662c456cea759504032602e..1c6e2f6381816a3ecbe92a36fcf49bd232f81052 100644 --- a/src/main/java/com/android/tools/metalava/ComparisonVisitor.kt +++ b/src/main/java/com/android/tools/metalava/ComparisonVisitor.kt @@ -26,6 +26,7 @@ import com.android.tools.metalava.model.MethodItem import com.android.tools.metalava.model.PackageItem import com.android.tools.metalava.model.ParameterItem import com.android.tools.metalava.model.visitors.ApiVisitor +import com.android.tools.metalava.model.visitors.VisibleItemVisitor import com.intellij.util.containers.Stack import java.util.Comparator import java.util.function.Predicate @@ -41,11 +42,18 @@ open class ComparisonVisitor( * instead of just a [#visitConstructor] call. Helps simplify visitors that * don't care to distinguish between the two cases. Defaults to true. */ - val visitConstructorsAsMethods: Boolean = true + val visitConstructorsAsMethods: Boolean = true, + /** + * Normally if a new item is found, the visitor will + * only visit the top level newly added item, not all + * of its children. This flags enables you to request + * all individual items to also be visited. + */ + val visitAddedItemsRecursively: Boolean = false ) { open fun compare(old: Item, new: Item) {} - open fun added(item: Item) {} - open fun removed(item: Item, from: Item?) {} + open fun added(new: Item) {} + open fun removed(old: Item, from: Item?) {} open fun compare(old: PackageItem, new: PackageItem) {} open fun compare(old: ClassItem, new: ClassItem) {} @@ -54,19 +62,19 @@ open class ComparisonVisitor( open fun compare(old: FieldItem, new: FieldItem) {} open fun compare(old: ParameterItem, new: ParameterItem) {} - open fun added(item: PackageItem) {} - open fun added(item: ClassItem) {} - open fun added(item: ConstructorItem) {} - open fun added(item: MethodItem) {} - open fun added(item: FieldItem) {} - open fun added(item: ParameterItem) {} - - open fun removed(item: PackageItem, from: Item?) {} - open fun removed(item: ClassItem, from: Item?) {} - open fun removed(item: ConstructorItem, from: ClassItem?) {} - open fun removed(item: MethodItem, from: ClassItem?) {} - open fun removed(item: FieldItem, from: ClassItem?) {} - open fun removed(item: ParameterItem, from: MethodItem?) {} + open fun added(new: PackageItem) {} + open fun added(new: ClassItem) {} + open fun added(new: ConstructorItem) {} + open fun added(new: MethodItem) {} + open fun added(new: FieldItem) {} + open fun added(new: ParameterItem) {} + + open fun removed(old: PackageItem, from: Item?) {} + open fun removed(old: ClassItem, from: Item?) {} + open fun removed(old: ConstructorItem, from: ClassItem?) {} + open fun removed(old: MethodItem, from: ClassItem?) {} + open fun removed(old: FieldItem, from: ClassItem?) {} + open fun removed(old: ParameterItem, from: MethodItem?) {} } class CodebaseComparator { @@ -83,7 +91,9 @@ class CodebaseComparator { } private fun compare( - visitor: ComparisonVisitor, oldList: List<ItemTree>, newList: List<ItemTree>, + visitor: ComparisonVisitor, + oldList: List<ItemTree>, + newList: List<ItemTree>, newParent: Item? ) { // Debugging tip: You can print out a tree like this: ItemTree.prettyPrint(list) @@ -121,7 +131,6 @@ class CodebaseComparator { index2++ } } - } else { // All the remaining items in oldList have been deleted while (index1 < length1) { @@ -139,8 +148,20 @@ class CodebaseComparator { } } + private fun visitAdded(visitor: ComparisonVisitor, new: Item) { + if (visitor.visitAddedItemsRecursively) { + new.accept(object : VisibleItemVisitor() { + override fun visitItem(item: Item) { + doVisitAdded(visitor, item) + } + }) + } else { + doVisitAdded(visitor, new) + } + } + @Suppress("USELESS_CAST") // Overloaded visitor methods: be explicit about which one is being invoked - private fun visitAdded(visitor: ComparisonVisitor, item: Item) { + private fun doVisitAdded(visitor: ComparisonVisitor, item: Item) { visitor.added(item) when (item) { diff --git a/src/main/java/com/android/tools/metalava/Compatibility.kt b/src/main/java/com/android/tools/metalava/Compatibility.kt index 564e4b94cb94cdb0da545cb4d8ed03ca6ca1f59e..5c21e38fb94f349bb9f15b9e626aa3c6dddb7274 100644 --- a/src/main/java/com/android/tools/metalava/Compatibility.kt +++ b/src/main/java/com/android/tools/metalava/Compatibility.kt @@ -65,6 +65,9 @@ class Compatibility( /** Include spaces after commas in type strings */ var spacesAfterCommas: Boolean = compat + /** Use two spaces after type for package private elements in signature files */ + var doubleSpaceForPackagePrivate: Boolean = compat + /** * In signature files, whether interfaces should also be described as "abstract" */ @@ -88,6 +91,13 @@ class Compatibility( * */ var useErasureInThrows: Boolean = compat + /** + * Whether throws classes in methods should be filtered. This should definitely + * be the case, but doclava1 doesn't. Note that this only applies to signature + * files, not stub files. + */ + var filterThrowsClasses: Boolean = !compat + /** * Include a single space in front of package private classes with no other modifiers * (this doesn't align well, but is supported to make the output 100% identical to the @@ -150,8 +160,11 @@ class Compatibility( * included in the stubs etc (since otherwise subclasses would believe they need * to implement that method and can't just inherit it). However, doclava1 does not * list these methods. This flag controls this compatibility behavior. + * Not that this refers only to the signature files, not the stub file generation. + * + * An example is StringBuilder#setLength. */ - var skipInheritedInterfaceMethods: Boolean = compat + var skipInheritedMethods: Boolean = compat /** * Whether to include parameter names in the signature file @@ -159,12 +172,11 @@ class Compatibility( var parameterNames: Boolean = true /** - * Whether we should include public methods from super classes. - * Doclava1 did not do this in its signature files, but they - * were included in stub files. An example of this scenario - * is StringBuilder#setLength. + * *Some* signatures for doclava1 wrote "<?>" as "<? extends java.lang.Object>", + * which is equivalent. Metalava does not do that. This flags ensures that the + * signature files look like the old ones for the specific methods which did this. */ - var includePublicMethodsFromHiddenSuperClasses = !compat + var includeExtendsObjectInWildcard = compat // Other examples: sometimes we sort by qualified name, sometimes by full name } \ No newline at end of file diff --git a/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt b/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt index ba19c59bc235abd09b7b90127300d66867dcacb4..ab7765718867bfdc40d3347bec8e9d586b78a307 100644 --- a/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt +++ b/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt @@ -67,14 +67,14 @@ class CompatibilityCheck : ComparisonVisitor() { Errors.INVALID_NULL_CONVERSION, new, "Attempted to change parameter from @Nullable to @NonNull: " + - "incompatible change for ${describe(new)}" + "incompatible change for ${describe(new)}" ) } else if (!oldNullable && old is MethodItem) { report( Errors.INVALID_NULL_CONVERSION, new, "Attempted to change method return from @NonNull to @Nullable: " + - "incompatible change for ${describe(new)}" + "incompatible change for ${describe(new)}" ) } } @@ -172,7 +172,6 @@ class CompatibilityCheck : ComparisonVisitor() { } } - if (!oldModifiers.isSealed() && newModifiers.isSealed()) { report(Errors.ADD_SEALED, new, "Cannot add `sealed` modifier to ${describe(new)}: Incompatible change") } else if (oldModifiers.isAbstract() != newModifiers.isAbstract()) { @@ -273,8 +272,8 @@ class CompatibilityCheck : ComparisonVisitor() { val oldTypeParameter = oldReturnType.asTypeParameter(old) val newTypeParameter = newReturnType.asTypeParameter(new) var compatible = true - if (oldTypeParameter == null - && newTypeParameter == null + if (oldTypeParameter == null && + newTypeParameter == null ) { if (oldReturnType != newReturnType || oldReturnType.arrayDimensions() != newReturnType.arrayDimensions() @@ -429,11 +428,11 @@ class CompatibilityCheck : ComparisonVisitor() { constraints: List<ClassItem> ): String { return type.toSimpleType() + - if (constraints.isEmpty()) { - " (extends java.lang.Object)" - } else { - " (extends ${constraints.joinToString(separator = " & ") { it.qualifiedName() }})" - } + if (constraints.isEmpty()) { + " (extends java.lang.Object)" + } else { + " (extends ${constraints.joinToString(separator = " & ") { it.qualifiedName() }})" + } } override fun compare(old: FieldItem, new: FieldItem) { @@ -527,23 +526,23 @@ class CompatibilityCheck : ComparisonVisitor() { report(error, item, "Removed ${if (item.deprecated) "deprecated " else ""}${describe(item)}") } - override fun added(item: PackageItem) { - handleAdded(Errors.ADDED_PACKAGE, item) + override fun added(new: PackageItem) { + handleAdded(Errors.ADDED_PACKAGE, new) } - override fun added(item: ClassItem) { - val error = if (item.isInterface()) { + override fun added(new: ClassItem) { + val error = if (new.isInterface()) { Errors.ADDED_INTERFACE } else { Errors.ADDED_CLASS } - handleAdded(error, item) + handleAdded(error, new) } - override fun added(item: MethodItem) { + override fun added(new: MethodItem) { // *Overriding* methods from super classes that are outside the // API is OK (e.g. overriding toString() from java.lang.Object) - val superMethods = item.superMethods() + val superMethods = new.superMethods() for (superMethod in superMethods) { if (superMethod.isFromClassPath()) { return @@ -554,64 +553,64 @@ class CompatibilityCheck : ComparisonVisitor() { // existing superclass method, but we should fail if this is overriding // an abstract method, because method's abstractness affects how users use it. // See if there's a member from inherited class - val inherited = if (item.isConstructor()) { + val inherited = if (new.isConstructor()) { null } else { - item.containingClass().findMethod( - item, + new.containingClass().findMethod( + new, includeSuperClasses = true, includeInterfaces = false ) } if (inherited != null && !inherited.modifiers.isAbstract()) { - val error = if (item.modifiers.isAbstract()) Errors.ADDED_ABSTRACT_METHOD else Errors.ADDED_METHOD - handleAdded(error, item) + val error = if (new.modifiers.isAbstract()) Errors.ADDED_ABSTRACT_METHOD else Errors.ADDED_METHOD + handleAdded(error, new) } } - override fun added(item: FieldItem) { - handleAdded(Errors.ADDED_FIELD, item) + override fun added(new: FieldItem) { + handleAdded(Errors.ADDED_FIELD, new) } - override fun removed(item: PackageItem, from: Item?) { - handleRemoved(Errors.REMOVED_PACKAGE, item) + override fun removed(old: PackageItem, from: Item?) { + handleRemoved(Errors.REMOVED_PACKAGE, old) } - override fun removed(item: ClassItem, from: Item?) { + override fun removed(old: ClassItem, from: Item?) { val error = when { - item.isInterface() -> Errors.REMOVED_INTERFACE - item.deprecated -> Errors.REMOVED_DEPRECATED_CLASS + old.isInterface() -> Errors.REMOVED_INTERFACE + old.deprecated -> Errors.REMOVED_DEPRECATED_CLASS else -> Errors.REMOVED_CLASS } - handleRemoved(error, item) + handleRemoved(error, old) } - override fun removed(item: MethodItem, from: ClassItem?) { + override fun removed(old: MethodItem, from: ClassItem?) { // See if there's a member from inherited class - val inherited = if (item.isConstructor()) { + val inherited = if (old.isConstructor()) { null } else { from?.findMethod( - item, + old, includeSuperClasses = true, includeInterfaces = from.isInterface() ) } if (inherited == null) { - val error = if (item.deprecated) Errors.REMOVED_DEPRECATED_METHOD else Errors.REMOVED_METHOD - handleRemoved(error, item) + val error = if (old.deprecated) Errors.REMOVED_DEPRECATED_METHOD else Errors.REMOVED_METHOD + handleRemoved(error, old) } } - override fun removed(item: FieldItem, from: ClassItem?) { + override fun removed(old: FieldItem, from: ClassItem?) { val inherited = from?.findField( - item.name(), + old.name(), includeSuperClasses = true, includeInterfaces = from.isInterface() ) if (inherited == null) { - val error = if (item.deprecated) Errors.REMOVED_DEPRECATED_FIELD else Errors.REMOVED_FIELD - handleRemoved(error, item) + val error = if (old.deprecated) Errors.REMOVED_DEPRECATED_FIELD else Errors.REMOVED_FIELD + handleRemoved(error, old) } } diff --git a/src/main/java/com/android/tools/metalava/Constants.kt b/src/main/java/com/android/tools/metalava/Constants.kt index 7badba36ef57f8da227aaf339a55903a797b621a..d863b9bc5eb278b7aaaef1120a7203334c9936a9 100644 --- a/src/main/java/com/android/tools/metalava/Constants.kt +++ b/src/main/java/com/android/tools/metalava/Constants.kt @@ -22,4 +22,9 @@ const val JAVA_LANG_STRING = "java.lang.String" const val JAVA_LANG_ENUM = "java.lang.Enum" const val JAVA_LANG_ANNOTATION = "java.lang.annotation.Annotation" const val ANDROID_SUPPORT_ANNOTATION_PREFIX = "android.support.annotation." - +const val ANDROID_ANNOTATION_PREFIX = "android.annotation." +const val ANDROIDX_ANNOTATION_PREFIX = "androidx.annotation." +const val ANDROIDX_NOTNULL = "androidx.annotation.NonNull" +const val ANDROIDX_NULLABLE = "androidx.annotation.Nullable" +const val RECENTLY_NULLABLE = "androidx.annotation.RecentlyNullable" +const val RECENTLY_NONNULL = "androidx.annotation.RecentlyNonNull" \ No newline at end of file diff --git a/src/main/java/com/android/tools/metalava/DexApiWriter.kt b/src/main/java/com/android/tools/metalava/DexApiWriter.kt index 15b4573fcab15701ad67793b8efd450ecf48ce39..27952a5f6f255d4d32b10675d62e18dc72992eb3 100644 --- a/src/main/java/com/android/tools/metalava/DexApiWriter.kt +++ b/src/main/java/com/android/tools/metalava/DexApiWriter.kt @@ -27,11 +27,12 @@ import java.util.function.Predicate class DexApiWriter( private val writer: PrintWriter, filterEmit: Predicate<Item>, - filterReference: Predicate<Item> + filterReference: Predicate<Item>, + inlineInheritedFields: Boolean = true ) : ApiVisitor( visitConstructorsAsMethods = true, nestInnerClasses = false, - inlineInheritedFields = true, + inlineInheritedFields = inlineInheritedFields, filterEmit = filterEmit, filterReference = filterReference ) { @@ -43,6 +44,10 @@ class DexApiWriter( } override fun visitMethod(method: MethodItem) { + if (method.inheritedMethod) { + return + } + writer.print(method.containingClass().toType().internalName()) writer.print("->") writer.print(method.internalName()) diff --git a/src/main/java/com/android/tools/metalava/DocAnalyzer.kt b/src/main/java/com/android/tools/metalava/DocAnalyzer.kt index 1e8d470b3b19f83480d506289a1ff7d65cfd1629..2c91eaa27f75e2fb32a2db1cb77919cd476893b0 100644 --- a/src/main/java/com/android/tools/metalava/DocAnalyzer.kt +++ b/src/main/java/com/android/tools/metalava/DocAnalyzer.kt @@ -48,11 +48,29 @@ class DocAnalyzer( tweakGrammar() + injectArtifactIds() + // TODO: // insertMissingDocFromHiddenSuperclasses() } - //noinspection SpellCheckingInspection + private fun injectArtifactIds() { + val artifacts = options.artifactRegistrations + if (!artifacts.any()) { + return + } + + artifacts.tag(codebase) + + codebase.accept(object : VisibleItemVisitor() { + override fun visitClass(cls: ClassItem) { + cls.artifact?.let { + cls.appendDocumentation(it, "@artifactId") + } + } + }) + } + val mentionsNull: Pattern = Pattern.compile("\\bnull\\b") /** Hide packages explicitly listed in [Options.hidePackages] */ @@ -104,7 +122,7 @@ class DocAnalyzer( if (findThreadAnnotations(annotations).size > 1) { reporter.warning( item, "Found more than one threading annotation on $item; " + - "the auto-doc feature does not handle this correctly", + "the auto-doc feature does not handle this correctly", Errors.MULTIPLE_THREAD_ANNOTATIONS ) } @@ -114,7 +132,10 @@ class DocAnalyzer( var result: MutableList<String>? = null for (annotation in annotations) { val name = annotation.qualifiedName() - if (name != null && name.endsWith("Thread") && name.startsWith(ANDROID_SUPPORT_ANNOTATION_PREFIX)) { + if (name != null && name.endsWith("Thread") && + (name.startsWith(ANDROID_SUPPORT_ANNOTATION_PREFIX) || + name.startsWith(ANDROIDX_ANNOTATION_PREFIX)) + ) { if (result == null) { result = mutableListOf() } @@ -141,7 +162,8 @@ class DocAnalyzer( private fun handleAnnotation( annotation: AnnotationItem, - item: Item, depth: Int + item: Item, + depth: Int ) { val name = annotation.qualifiedName() if (name == null || name.startsWith(JAVA_LANG_PREFIX)) { @@ -180,10 +202,11 @@ class DocAnalyzer( } // Document required permissions - if (item is MemberItem && name == "android.support.annotation.RequiresPermission") { + if (item is MemberItem && name == "androidx.annotation.RequiresPermission") { + var values: List<AnnotationAttributeValue>? = null + var any = false + var conditional = false for (attribute in annotation.attributes()) { - var values: List<AnnotationAttributeValue>? = null - var any = false when (attribute.name) { "value", "allOf" -> { values = attribute.leafValues() @@ -192,12 +215,13 @@ class DocAnalyzer( any = true values = attribute.leafValues() } + "conditional" -> { + conditional = attribute.value.value() == true + } } + } - if (values == null || values.isEmpty()) { - continue - } - + if (values != null && values.isNotEmpty() && !conditional) { // Look at macros_override.cs for the usage of these // tags. In particular, search for def:dump_permission @@ -223,9 +247,7 @@ class DocAnalyzer( Errors.MISSING_PERMISSION, item, "Cannot find permission field for $value required by $item (may be hidden or removed)" ) - //return sb.append(value.toSource()) - } else { if (field.isHiddenOrRemoved()) { reporter.report( @@ -242,7 +264,7 @@ class DocAnalyzer( } // Document value ranges - if (name == "android.support.annotation.IntRange" || name == "android.support.annotation.FloatRange") { + if (name == "androidx.annotation.IntRange" || name == "androidx.annotation.FloatRange") { val from: String? = annotation.findAttribute("from")?.value?.toSource() val to: String? = annotation.findAttribute("to")?.value?.toSource() // TODO: inclusive/exclusive attributes on FloatRange! @@ -265,8 +287,8 @@ class DocAnalyzer( } // Document expected constants - if (name == "android.support.annotation.IntDef" || name == "android.support.annotation.LongDef" - || name == "android.support.annotation.StringDef" + if (name == "androidx.annotation.IntDef" || name == "androidx.annotation.LongDef" || + name == "androidx.annotation.StringDef" ) { val values = annotation.findAttribute("value")?.leafValues() ?: return val flag = annotation.findAttribute("flag")?.value?.toSource() == "true" @@ -317,12 +339,7 @@ class DocAnalyzer( val value = annotation.findAttribute("value")?.leafValues()?.firstOrNull() ?: return val sb = StringBuilder(100) val resolved = value.resolve() - val field = if (resolved is FieldItem) - resolved - else { - val v: Any = value.value() ?: value.toSource() - findPermissionField(codebase, v) - } + val field = resolved as? FieldItem sb.append("Requires the ") if (field == null) { reporter.report( @@ -330,7 +347,6 @@ class DocAnalyzer( "Cannot find feature field for $value required by $item (may be hidden or removed)" ) sb.append("{@link ${value.toSource()}}") - } else { if (field.isHiddenOrRemoved()) { reporter.report( @@ -348,6 +364,22 @@ class DocAnalyzer( appendDocumentation(sb.toString(), item, false) } + // Required API levels + if (name == "androidx.annotation.RequiresApi") { + val level = run { + val api = annotation.findAttribute("api")?.leafValues()?.firstOrNull()?.value() + if (api == null || api == 1) { + annotation.findAttribute("value")?.leafValues()?.firstOrNull()?.value() ?: return + } else { + api + } + } + + if (level is Int) { + addApiLevelDocumentation(level, item) + } + } + // Thread annotations are ignored here because they're handled as a group afterwards // TODO: Resource type annotations @@ -357,7 +389,7 @@ class DocAnalyzer( if (depth == 20) { // Temp debugging throw StackOverflowError( "Unbounded recursion, processing annotation " + - "${annotation.toSource()} in $item in ${item.compilationUnit()} " + "${annotation.toSource()} in $item in ${item.compilationUnit()} " ) } handleAnnotation(nested, item, depth + 1) @@ -396,7 +428,7 @@ class DocAnalyzer( val documentation = cls.findTagDocumentation(tag) if (documentation != null) { - assert(documentation.startsWith("@$tag"), { documentation }) + assert(documentation.startsWith("@$tag")) { documentation } // TODO: Insert it in the right place (@return or @param) val section = when { documentation.startsWith("@returnDoc") -> "@return" @@ -421,7 +453,6 @@ class DocAnalyzer( /** Replacements to perform in documentation */ val typos = mapOf( - //noinspection SpellCheckingInspection "Andriod" to "Android", "Kitkat" to "KitKat", "LemonMeringuePie" to "Lollipop", @@ -498,33 +529,33 @@ class DocAnalyzer( addApiLevelDocumentation(apiLookup.getFieldVersion(psiField), field) addDeprecatedDocumentation(apiLookup.getFieldDeprecatedIn(psiField), field) } + }) + } - private fun addApiLevelDocumentation(level: Int, item: Item) { - if (level > 1) { - appendDocumentation("Requires API level $level", item, false) - // Also add @since tag, unless already manually entered. - // TODO: Override it everywhere in case the existing doc is wrong (we know - // better), and at least for OpenJDK sources we *should* since the since tags - // are talking about language levels rather than API levels! - if (!item.documentation.contains("@since")) { - item.appendDocumentation(describeApiLevel(level), "@since") - } - } + private fun addApiLevelDocumentation(level: Int, item: Item) { + if (level > 1) { + appendDocumentation("Requires API level $level", item, false) + // Also add @since tag, unless already manually entered. + // TODO: Override it everywhere in case the existing doc is wrong (we know + // better), and at least for OpenJDK sources we *should* since the since tags + // are talking about language levels rather than API levels! + if (!item.documentation.contains("@since")) { + item.appendDocumentation(describeApiLevel(level), "@since") } + } + } - private fun addDeprecatedDocumentation(level: Int, item: Item) { - if (level > 1) { - // TODO: *pre*pend instead! - val description = - "<p class=\"caution\"><strong>This class was deprecated in API level 21.</strong></p>" - item.appendDocumentation(description, "@deprecated", append = false) - } - } + private fun addDeprecatedDocumentation(level: Int, item: Item) { + if (level > 1) { + // TODO: *pre*pend instead! + val description = + "<p class=\"caution\"><strong>This class was deprecated in API level 21.</strong></p>" + item.appendDocumentation(description, "@deprecated", append = false) + } + } - private fun describeApiLevel(level: Int): String { - return "${SdkVersionInfo.getVersionString(level)} ${SdkVersionInfo.getCodeName(level)} ($level)" - } - }) + private fun describeApiLevel(level: Int): String { + return "${SdkVersionInfo.getVersionString(level)} ${SdkVersionInfo.getCodeName(level)} ($level)" } } diff --git a/src/main/java/com/android/tools/metalava/Driver.kt b/src/main/java/com/android/tools/metalava/Driver.kt index cdf2b7024248b7351ba34bfb86493885612f3f15..c5381263f72d1702ec2df910ec1c2f60d800799d 100644 --- a/src/main/java/com/android/tools/metalava/Driver.kt +++ b/src/main/java/com/android/tools/metalava/Driver.kt @@ -18,7 +18,6 @@ package com.android.tools.metalava import com.android.SdkConstants -import com.android.SdkConstants.DOT_JAR import com.android.SdkConstants.DOT_JAVA import com.android.SdkConstants.DOT_KT import com.android.ide.common.process.CachedProcessOutputHandler @@ -28,6 +27,7 @@ import com.android.tools.lint.KotlinLintAnalyzerFacade import com.android.tools.lint.LintCoreApplicationEnvironment import com.android.tools.lint.LintCoreProjectEnvironment import com.android.tools.lint.annotations.Extractor +import com.android.tools.lint.checks.infrastructure.ClassName import com.android.tools.metalava.apilevels.ApiGenerator import com.android.tools.metalava.doclava1.ApiFile import com.android.tools.metalava.doclava1.ApiParseException @@ -45,8 +45,8 @@ import com.android.utils.StdLogger.Level.ERROR import com.google.common.base.Stopwatch import com.google.common.collect.Lists import com.google.common.io.Files +import com.intellij.openapi.roots.LanguageLevelProjectExtension import com.intellij.openapi.util.Disposer -import com.intellij.psi.PsiClassOwner import java.io.File import java.io.IOException import java.io.OutputStreamWriter @@ -54,12 +54,11 @@ import java.io.PrintWriter import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit.SECONDS import java.util.function.Predicate -import java.util.regex.Pattern import kotlin.text.Charsets.UTF_8 const val PROGRAM_NAME = "metalava" const val HELP_PROLOGUE = "$PROGRAM_NAME extracts metadata from source code to generate artifacts such as the " + - "signature files, the SDK stub files, external annotations etc." + "signature files, the SDK stub files, external annotations etc." @Suppress("PropertyName") // Can't mark const because trimIndent() :-( val BANNER: String = """ @@ -145,19 +144,6 @@ private fun exit(exitCode: Int = 0) { private fun processFlags() { val stopwatch = Stopwatch.createStarted() - val androidApiLevelXml = options.generateApiLevelXml - val apiLevelJars = options.apiLevelJars - if (androidApiLevelXml != null && apiLevelJars != null) { - ApiGenerator.generate(apiLevelJars, androidApiLevelXml) - - if (options.apiJar == null && options.sources.isEmpty() && - options.sourcePath.isEmpty() && options.previousApi == null - ) { - // Done - return - } - } - val codebase = if (options.sources.size == 1 && options.sources[0].path.endsWith(SdkConstants.DOT_TXT)) { loadFromSignatureFiles( @@ -177,10 +163,50 @@ private fun processFlags() { options.stdout.println("\n$PROGRAM_NAME analyzed API in ${stopwatch.elapsed(TimeUnit.SECONDS)} seconds") } + val androidApiLevelXml = options.generateApiLevelXml + val apiLevelJars = options.apiLevelJars + if (androidApiLevelXml != null && apiLevelJars != null) { + progress("\nGenerating API levels XML descriptor file, ${androidApiLevelXml.name}: ") + ApiGenerator.generate(apiLevelJars, androidApiLevelXml, codebase) + } + + if ((options.stubsDir != null || options.docStubsDir != null) && codebase.supportsDocumentation()) { + progress("\nEnhancing docs: ") + val docAnalyzer = DocAnalyzer(codebase) + docAnalyzer.enhance() + + val applyApiLevelsXml = options.applyApiLevelsXml + if (applyApiLevelsXml != null) { + progress("\nApplying API levels") + docAnalyzer.applyApiLevels(applyApiLevelsXml) + } + } + + // Generate the documentation stubs *before* we migrate nullness information. + options.docStubsDir?.let { createStubFiles(it, codebase, docStubs = true, + writeStubList = options.docStubsSourceList != null) } + + val currentApiFile = options.currentApi + if (currentApiFile != null && options.checkCompatibility) { + val current = + if (currentApiFile.path.endsWith(SdkConstants.DOT_JAR)) { + loadFromJarFile(currentApiFile) + } else { + loadFromSignatureFiles( + currentApiFile, options.inputKotlinStyleNulls, + supportsStagedNullability = true + ) + } + + // If configured, compares the new API with the previous API and reports + // any incompatibilities. + CompatibilityCheck.checkCompatibility(codebase, current) + } + val previousApiFile = options.previousApi if (previousApiFile != null) { val previous = - if (previousApiFile.path.endsWith(DOT_JAR)) { + if (previousApiFile.path.endsWith(SdkConstants.DOT_JAR)) { loadFromJarFile(previousApiFile) } else { loadFromSignatureFiles( @@ -191,7 +217,7 @@ private fun processFlags() { // If configured, compares the new API with the previous API and reports // any incompatibilities. - if (options.checkCompatibility) { + if (options.checkCompatibility && options.currentApi == null) { // otherwise checked against currentApi above CompatibilityCheck.checkCompatibility(codebase, previous) } @@ -205,7 +231,119 @@ private fun processFlags() { // Based on the input flags, generates various output files such // as signature files and/or stubs files - generateOutputs(codebase) + options.apiFile?.let { apiFile -> + val apiFilter = FilterPredicate(ApiPredicate(codebase)) + val apiReference = ApiPredicate(codebase, ignoreShown = true) + val apiEmit = apiFilter.and(ElidingPredicate(apiReference)) + + createReportFile(codebase, apiFile, "API") { printWriter -> + val preFiltered = codebase.original != null + SignatureWriter(printWriter, apiEmit, apiReference, preFiltered) + } + } + + options.dexApiFile?.let { apiFile -> + val apiFilter = FilterPredicate(ApiPredicate(codebase)) + val memberIsNotCloned: Predicate<Item> = Predicate { !it.isCloned() } + val apiReference = ApiPredicate(codebase, ignoreShown = true) + val dexApiEmit = memberIsNotCloned.and(apiFilter) + + createReportFile( + codebase, apiFile, "DEX API" + ) { printWriter -> DexApiWriter(printWriter, dexApiEmit, apiReference) } + } + + options.removedApiFile?.let { apiFile -> + val unfiltered = codebase.original ?: codebase + + val removedFilter = FilterPredicate(ApiPredicate(codebase, matchRemoved = true)) + val removedReference = ApiPredicate(codebase, ignoreShown = true, ignoreRemoved = true) + val removedEmit = removedFilter.and(ElidingPredicate(removedReference)) + + createReportFile(unfiltered, apiFile, "removed API") { printWriter -> + SignatureWriter(printWriter, removedEmit, removedReference, codebase.original != null) + } + } + + options.removedDexApiFile?.let { apiFile -> + val unfiltered = codebase.original ?: codebase + + val removedFilter = FilterPredicate(ApiPredicate(codebase, matchRemoved = true)) + val removedReference = ApiPredicate(codebase, ignoreShown = true, ignoreRemoved = true) + val memberIsNotCloned: Predicate<Item> = Predicate { !it.isCloned() } + val removedDexEmit = memberIsNotCloned.and(removedFilter) + + createReportFile( + unfiltered, apiFile, "removed DEX API" + ) { printWriter -> DexApiWriter(printWriter, removedDexEmit, removedReference) } + } + + options.privateApiFile?.let { apiFile -> + val apiFilter = FilterPredicate(ApiPredicate(codebase)) + val memberIsNotCloned: Predicate<Item> = Predicate { !it.isCloned() } + val privateEmit = memberIsNotCloned.and(apiFilter.negate()) + val privateReference = Predicate<Item> { true } + + createReportFile(codebase, apiFile, "private API") { printWriter -> + SignatureWriter(printWriter, privateEmit, privateReference, codebase.original != null) + } + } + + options.privateDexApiFile?.let { apiFile -> + val apiFilter = FilterPredicate(ApiPredicate(codebase)) + val privateEmit = apiFilter.negate() + val privateReference = Predicate<Item> { true } + + createReportFile( + codebase, apiFile, "private DEX API" + ) { printWriter -> + DexApiWriter( + printWriter, privateEmit, privateReference, inlineInheritedFields = false + ) + } + } + + options.proguard?.let { proguard -> + val apiEmit = FilterPredicate(ApiPredicate(codebase)) + val apiReference = ApiPredicate(codebase, ignoreShown = true) + createReportFile( + codebase, proguard, "Proguard file" + ) { printWriter -> ProguardWriter(printWriter, apiEmit, apiReference) } + } + + options.sdkValueDir?.let { dir -> + dir.mkdirs() + SdkFileWriter(codebase, dir).generate() + } + + // Now that we've migrated nullness information we can proceed to write non-doc stubs, if any. + + options.stubsDir?.let { + createStubFiles( + it, codebase, docStubs = false, + writeStubList = options.stubsSourceList != null + ) + } + if (options.docStubsDir == null && options.stubsDir == null) { + val writeStubsFile: (File) -> Unit = { file -> + val root = File("").absoluteFile + val sources = options.sources + val rootPath = root.path + val contents = sources.joinToString(" ") { + val path = it.path + if (path.startsWith(rootPath)) { + path.substring(rootPath.length) + } else { + path + } + } + Files.asCharSink(file, UTF_8).write(contents) + } + options.stubsSourceList?.let(writeStubsFile) + options.docStubsSourceList?.let(writeStubsFile) + } + options.externalAnnotations?.let { extractAnnotations(codebase, it) } + progress("\n") // Coverage stats? if (options.dumpAnnotationStatistics) { @@ -271,21 +409,25 @@ fun invokeDocumentationTool() { private fun migrateNulls(codebase: Codebase, previous: Codebase) { if (options.migrateNulls) { - val prev = previous.supportsStagedNullability + val codebaseSupportsNullability = previous.supportsStagedNullability + val prevSupportsNullability = previous.supportsStagedNullability try { previous.supportsStagedNullability = true + codebase.supportsStagedNullability = true previous.compareWith( NullnessMigration(), codebase, ApiPredicate(codebase) ) } finally { - previous.supportsStagedNullability = prev + previous.supportsStagedNullability = prevSupportsNullability + codebase.supportsStagedNullability = codebaseSupportsNullability } } } private fun loadFromSignatureFiles( - file: File, kotlinStyleNulls: Boolean, + file: File, + kotlinStyleNulls: Boolean, manifest: File? = null, performChecks: Boolean = false, supportsStagedNullability: Boolean = false @@ -309,6 +451,10 @@ private fun loadFromSignatureFiles( private fun loadFromSources(): Codebase { val projectEnvironment = createProjectEnvironment() + // Push language level to PSI handler + projectEnvironment.project.getComponent(LanguageLevelProjectExtension::class.java)?.languageLevel = + options.javaLanguageLevel + progress("\nProcessing sources: ") val sources = if (options.sources.isEmpty()) { @@ -331,7 +477,7 @@ private fun loadFromSources(): Codebase { val project = projectEnvironment.project val kotlinFiles = sources.filter { it.path.endsWith(SdkConstants.DOT_KT) } - KotlinLintAnalyzerFacade.analyze(kotlinFiles, joined, project) + KotlinLintAnalyzerFacade().analyze(kotlinFiles, joined, project) val units = Extractor.createUnitsForFiles(project, sources) val packageDocs = gatherHiddenPackagesFromJavaDocs(options.sourcePath) @@ -341,6 +487,7 @@ private fun loadFromSources(): Codebase { val codebase = PsiBasedCodebase("Codebase loaded from source folders") codebase.initialize(project, units, packageDocs) codebase.manifest = options.manifest + codebase.apiLevel = options.currentApiLevel progress("\nAnalyzing API: ") @@ -356,10 +503,8 @@ private fun loadFromSources(): Codebase { // General API checks for Android APIs AndroidApiChecks().check(codebase) - val ignoreShown = options.showUnannotated - - val filterEmit = ApiPredicate(codebase, ignoreShown = ignoreShown, ignoreRemoved = false) - val apiEmit = ApiPredicate(codebase, ignoreShown = ignoreShown) + val filterEmit = ApiPredicate(codebase, ignoreShown = true, ignoreRemoved = false) + val apiEmit = ApiPredicate(codebase, ignoreShown = true) val apiReference = ApiPredicate(codebase, ignoreShown = true) // Copy methods from soon-to-be-hidden parents into descendant classes, when necessary @@ -368,19 +513,9 @@ private fun loadFromSources(): Codebase { // Compute default constructors (and add missing package private constructors // to make stubs compilable if necessary) - if (options.stubsDir != null) { + if (options.stubsDir != null || options.docStubsDir != null) { progress("\nInsert missing constructors: ") analyzer.addConstructors(filterEmit) - - progress("\nEnhancing docs: ") - val docAnalyzer = DocAnalyzer(codebase) - docAnalyzer.enhance() - - val applyApiLevelsXml = options.applyApiLevelsXml - if (applyApiLevelsXml != null) { - progress("\nApplying API levels") - docAnalyzer.applyApiLevels(applyApiLevelsXml) - } } progress("\nPerforming misc API checks: ") @@ -389,6 +524,7 @@ private fun loadFromSources(): Codebase { return codebase } +@Suppress("unused") // Planning to restore for performance optimizations private fun filterCodebase(codebase: PsiBasedCodebase): Codebase { val ignoreShown = options.showAnnotations.isEmpty() @@ -410,7 +546,7 @@ private fun loadFromJarFile(apiJar: File, manifest: File? = null): Codebase { projectEnvironment.registerPaths(listOf(apiJar)) val kotlinFiles = emptyList<File>() - KotlinLintAnalyzerFacade.analyze(kotlinFiles, listOf(apiJar), project) + KotlinLintAnalyzerFacade().analyze(kotlinFiles, listOf(apiJar), project) val codebase = PsiBasedCodebase() codebase.description = "Codebase loaded from $apiJar" @@ -431,116 +567,45 @@ private fun createProjectEnvironment(): LintCoreProjectEnvironment { } private fun ensurePsiFileCapacity() { - //noinspection SpellCheckingInspection val fileSize = System.getProperty("idea.max.intellisense.filesize") if (fileSize == null) { // Ensure we can handle large compilation units like android.R - //noinspection SpellCheckingInspection System.setProperty("idea.max.intellisense.filesize", "100000") } } -private fun generateOutputs(codebase: Codebase) { - - options.apiFile?.let { apiFile -> - val apiFilter = FilterPredicate(ApiPredicate(codebase)) - val apiReference = ApiPredicate(codebase, ignoreShown = true) - val apiEmit = apiFilter.and(ElidingPredicate(apiReference)) - - createReportFile(codebase, apiFile, "API", { printWriter -> - val preFiltered = codebase.original != null - SignatureWriter(printWriter, apiEmit, apiReference, preFiltered) - }) - } - - options.removedApiFile?.let { apiFile -> - val unfiltered = codebase.original ?: codebase - - val removedFilter = FilterPredicate(ApiPredicate(codebase, matchRemoved = true)) - val removedReference = ApiPredicate(codebase, ignoreShown = true, ignoreRemoved = true) - val removedEmit = removedFilter.and(ElidingPredicate(removedReference)) - - createReportFile(unfiltered, apiFile, "removed API", { printWriter -> - SignatureWriter(printWriter, removedEmit, removedReference, codebase.original != null) - }) - } - - options.privateApiFile?.let { apiFile -> - val apiFilter = FilterPredicate(ApiPredicate(codebase)) - val privateEmit = apiFilter.negate() - val privateReference = Predicate<Item> { true } - - createReportFile(codebase, apiFile, "private API", { printWriter -> - SignatureWriter(printWriter, privateEmit, privateReference, codebase.original != null) - }) - } - - options.privateDexApiFile?.let { apiFile -> - val apiFilter = FilterPredicate(ApiPredicate(codebase)) - val privateEmit = apiFilter.negate() - val privateReference = Predicate<Item> { true } - - createReportFile(codebase, apiFile, "DEX API", - { printWriter -> DexApiWriter(printWriter, privateEmit, privateReference) }) - } - - options.proguard?.let { proguard -> - val apiEmit = FilterPredicate(ApiPredicate(codebase)) - val apiReference = ApiPredicate(codebase, ignoreShown = true) - createReportFile(codebase, proguard, "Proguard file", - { printWriter -> ProguardWriter(printWriter, apiEmit, apiReference) }) - } - - options.sdkValueDir?.let { dir -> - dir.mkdirs() - SdkFileWriter(codebase, dir).generate() - } - - options.stubsDir?.let { createStubFiles(it, codebase) } - // Otherwise, if we've asked to write out a file list, write out the - // input file list instead - ?: options.stubsSourceList?.let { file -> - val root = File("").absoluteFile - val sources = options.sources - val rootPath = root.path - val contents = sources.joinToString(" ") { - val path = it.path - if (path.startsWith(rootPath)) { - path.substring(rootPath.length) - } else { - path - } - } - Files.asCharSink(file, UTF_8).write(contents) - } - - options.externalAnnotations?.let { extractAnnotations(codebase, it) } - progress("\n") -} - private fun extractAnnotations(codebase: Codebase, file: File) { val localTimer = Stopwatch.createStarted() - val units = codebase.units - @Suppress("UNCHECKED_CAST") - ExtractAnnotations().extractAnnotations(units.asSequence().filter { it is PsiClassOwner }.toList() as List<PsiClassOwner>) - if (options.verbose) { - options.stdout.print("\n$PROGRAM_NAME extracted annotations into $file in $localTimer") - options.stdout.flush() + options.externalAnnotations?.let { outputFile -> + @Suppress("UNCHECKED_CAST") + ExtractAnnotations( + codebase, + outputFile + ).extractAnnotations() + if (options.verbose) { + options.stdout.print("\n$PROGRAM_NAME extracted annotations into $file in $localTimer") + options.stdout.flush() + } } } -private fun createStubFiles(stubDir: File, codebase: Codebase) { +private fun createStubFiles(stubDir: File, codebase: Codebase, docStubs: Boolean, writeStubList: Boolean) { // Generating stubs from a sig-file-based codebase is problematic assert(codebase.supportsDocumentation()) - progress("\nGenerating stub files: ") + if (docStubs) { + progress("\nGenerating documentation stub files: ") + } else { + progress("\nGenerating stub files: ") + } + val localTimer = Stopwatch.createStarted() val prevCompatibility = compatibility if (compatibility.compat) { - //if (!options.quiet) { + // if (!options.quiet) { // options.stderr.println("Warning: Turning off compat mode when generating stubs") - //} + // } compatibility = Compatibility(false) // But preserve the setting for whether we want to erase throws signatures (to ensure the API // stays compatible) @@ -549,56 +614,37 @@ private fun createStubFiles(stubDir: File, codebase: Codebase) { val stubWriter = StubWriter( - codebase = codebase, stubsDir = stubDir, generateAnnotations = options.generateAnnotations, - preFiltered = codebase.original != null + codebase = codebase, + stubsDir = stubDir, + generateAnnotations = options.generateAnnotations, + preFiltered = codebase.original != null, + docStubs = docStubs ) codebase.accept(stubWriter) - // Optionally also write out a list of source files that were generated; used - // for example to point javadoc to the stubs output to generate documentation - options.stubsSourceList?.let { - val root = File("").absoluteFile - stubWriter.writeSourceList(it, root) - } - - /* - // Temporary hack: Also write out annotations to make stub compilation work. This is - // just temporary: the Makefiles for the platform should be updated to supply a - // boot classpath instead. - val nullable = File(stubDir, "android/support/annotation/Nullable.java") - val nonnull = File(stubDir, "android/support/annotation/NonNull.java") - nullable.parentFile.mkdirs() - nonnull.parentFile.mkdirs() - Files.asCharSink(nullable, UTF_8).write( - "package android.support.annotation;\n" + - "import java.lang.annotation.*;\n" + - "import static java.lang.annotation.ElementType.*;\n" + - "import static java.lang.annotation.RetentionPolicy.SOURCE;\n" + - "@SuppressWarnings(\"WeakerAccess\")\n" + - "@Retention(SOURCE)\n" + - "@Target({METHOD, PARAMETER, FIELD})\n" + - "public @interface Nullable {\n" + - "}\n" - ) - Files.asCharSink(nonnull, UTF_8).write( - "package android.support.annotation;\n" + - "import java.lang.annotation.*;\n" + - "import static java.lang.annotation.ElementType.*;\n" + - "import static java.lang.annotation.RetentionPolicy.SOURCE;\n" + - "@SuppressWarnings(\"WeakerAccess\")\n" + - "@Retention(SOURCE)\n" + - "@Target({METHOD, PARAMETER, FIELD})\n" + - "public @interface NonNull {\n" + - "}\n" - ) - */ + if (writeStubList) { + // Optionally also write out a list of source files that were generated; used + // for example to point javadoc to the stubs output to generate documentation + val file = if (docStubs) { + options.docStubsSourceList ?: options.stubsSourceList + } else { + options.stubsSourceList + } + file?.let { + val root = File("").absoluteFile + stubWriter.writeSourceList(it, root) + } + } compatibility = prevCompatibility - progress("\n$PROGRAM_NAME wrote stubs directory $stubDir in ${localTimer.elapsed(SECONDS)} seconds") + progress( + "\n$PROGRAM_NAME wrote ${if (docStubs) "documentation" else ""} stubs directory $stubDir in ${ + localTimer.elapsed(SECONDS)} seconds" + ) } -private fun progress(message: String) { +fun progress(message: String) { if (options.verbose) { options.stdout.print(message) options.stdout.flush() @@ -639,6 +685,9 @@ fun resetTicker() { fun tick() { tick++ if (tick % 100 == 0) { + if (!options.verbose) { + return + } options.stdout.print(".") options.stdout.flush() } @@ -681,10 +730,8 @@ private fun addHiddenPackages( if (child.isDirectory) if (pkg.isEmpty()) child.name - else - pkg + "." + child.name - else - pkg + else pkg + "." + child.name + else pkg addHiddenPackages(packageToDoc, hiddenPackages, child, subPkg) } } @@ -737,7 +784,13 @@ private fun findRoot(file: File): File? { if (path.endsWith(DOT_JAVA) || path.endsWith(DOT_KT)) { val pkg = findPackage(file) ?: return null val parent = file.parentFile ?: return null - return File(path.substring(0, parent.path.length - pkg.length)) + val endIndex = parent.path.length - pkg.length + val before = path[endIndex - 1] + if (before == '/' || before == '\\') { + return File(path.substring(0, endIndex)) + } else { + reporter.report(Errors.IO_ERROR, file, "$PROGRAM_NAME was unable to determine the package name") + } } return null @@ -749,16 +802,7 @@ fun findPackage(file: File): String? { return findPackage(source) } -@Suppress("PrivatePropertyName") -private val PACKAGE_PATTERN = Pattern.compile("package\\s+([\\S&&[^;]]*)") - /** Finds the package of the given Java/Kotlin source code, if possible */ fun findPackage(source: String): String? { - val matcher = PACKAGE_PATTERN.matcher(source) - val foundPackage = matcher.find() - return if (foundPackage) { - matcher.group(1).trim { it <= ' ' } - } else { - null - } + return ClassName(source).packageName } diff --git a/src/main/java/com/android/tools/metalava/OptionsException.kt b/src/main/java/com/android/tools/metalava/DriverException.kt similarity index 100% rename from src/main/java/com/android/tools/metalava/OptionsException.kt rename to src/main/java/com/android/tools/metalava/DriverException.kt diff --git a/src/main/java/com/android/tools/metalava/ExtractAnnotations.kt b/src/main/java/com/android/tools/metalava/ExtractAnnotations.kt index f97c112ee1d7b8cd37a9745323b9b0f51eb87c1a..dd3fb6788f89612e2b500b61bf09eb8d54c6d0ef 100644 --- a/src/main/java/com/android/tools/metalava/ExtractAnnotations.kt +++ b/src/main/java/com/android/tools/metalava/ExtractAnnotations.kt @@ -16,36 +16,609 @@ package com.android.tools.metalava +import com.android.SdkConstants import com.android.tools.lint.annotations.Extractor -import com.intellij.psi.PsiClassOwner +import com.android.tools.lint.client.api.AnnotationLookup +import com.android.tools.lint.detector.api.ConstantEvaluator +import com.android.tools.metalava.doclava1.Errors +import com.android.tools.metalava.model.AnnotationItem +import com.android.tools.metalava.model.ClassItem +import com.android.tools.metalava.model.Codebase +import com.android.tools.metalava.model.FieldItem +import com.android.tools.metalava.model.Item +import com.android.tools.metalava.model.MemberItem +import com.android.tools.metalava.model.MethodItem +import com.android.tools.metalava.model.PackageItem +import com.android.tools.metalava.model.ParameterItem +import com.android.tools.metalava.model.psi.PsiAnnotationItem +import com.android.tools.metalava.model.psi.PsiClassItem +import com.android.tools.metalava.model.visitors.ApiVisitor +import com.android.utils.XmlUtils +import com.google.common.base.Charsets +import com.google.common.xml.XmlEscapers +import com.intellij.psi.PsiAnnotation +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiField +import com.intellij.psi.PsiNameValuePair +import org.jetbrains.uast.UAnnotation +import org.jetbrains.uast.UBinaryExpressionWithType +import org.jetbrains.uast.UCallExpression +import org.jetbrains.uast.UExpression +import org.jetbrains.uast.ULiteralExpression +import org.jetbrains.uast.UNamedExpression +import org.jetbrains.uast.UReferenceExpression +import org.jetbrains.uast.UastEmptyExpression +import org.jetbrains.uast.java.JavaUAnnotation +import org.jetbrains.uast.java.expressions.JavaUAnnotationCallExpression +import org.jetbrains.uast.util.isArrayInitializer +import org.jetbrains.uast.util.isTypeCast +import java.io.BufferedOutputStream +import java.io.File +import java.io.FileOutputStream +import java.io.PrintWriter +import java.io.StringWriter +import java.util.ArrayList +import java.util.jar.JarEntry +import java.util.jar.JarOutputStream -class ExtractAnnotations { - fun extractAnnotations(units: List<PsiClassOwner>) { - val rmTypeDefs = if (options.rmTypeDefs != null) listOf(options.rmTypeDefs) else emptyList() - val typedefFile = options.typedefFile - val filter = options.apiFilter +// Like the tools/base Extractor class, but limited to our own (mapped) AnnotationItems, +// and only those with source retention (and in particular right now that just means the +// typedef annotations.) +class ExtractAnnotations( + private val codebase: Codebase, + private val outputFile: File +) : ApiVisitor(codebase) { + // Used linked hash map for order such that we always emit parameters after their surrounding method etc + private val packageToAnnotationPairs = LinkedHashMap<PackageItem, MutableList<Pair<Item, AnnotationHolder>>>() - val verbose = !options.quiet - val skipClassRetention = options.skipClassRetention - val extractor = Extractor(filter, rmTypeDefs, verbose, !skipClassRetention, true) - extractor.isListIgnored = !options.hideFiltered - extractor.extractFromProjectSource(units) - for (jar in options.mergeAnnotations) { - extractor.mergeExisting(jar) + private val annotationLookup = AnnotationLookup() + + private data class AnnotationHolder( + val annotationClass: ClassItem?, + val annotationItem: AnnotationItem, + val uAnnotation: UAnnotation? + ) + + private val classToAnnotationHolder = mutableMapOf<String, AnnotationHolder>() + + fun extractAnnotations() { + codebase.accept(this) + + // Write external annotations + FileOutputStream(outputFile).use { fileOutputStream -> + JarOutputStream(BufferedOutputStream(fileOutputStream)).use { zos -> + val sortedPackages = + packageToAnnotationPairs.keys.asSequence().sortedBy { it.qualifiedName() }.toList() + + for (pkg in sortedPackages) { + // Note: Using / rather than File.separator: jar lib requires it + val name = pkg.qualifiedName().replace('.', '/') + "/annotations.xml" + + val outEntry = JarEntry(name) + outEntry.time = 0 + zos.putNextEntry(outEntry) + + val pairs = packageToAnnotationPairs[pkg] ?: continue + + StringPrintWriter.create().use { writer -> + writer.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<root>") + + var prev: Item? = null + for ((item, annotation) in pairs) { + // that we only do analysis for IntDef/LongDef + assert(item != prev) // should be only one annotation per element now + prev = item + + writer.print(" <item name=\"") + writer.print(item.getExternalAnnotationSignature()) + writer.println("\">") + + writeAnnotation(writer, item, annotation) + + writer.print(" </item>") + writer.println() + } + + writer.println("</root>\n") + writer.close() + val bytes = writer.contents.toByteArray(Charsets.UTF_8) + zos.write(bytes) + zos.closeEntry() + } + } + } + } + } + + /** For a given item, extract the relevant annotations for that item. + * + * Currently, we're only extracting typedef annotations. Everything else + * has class retention. + */ + private fun checkItem(item: Item) { + // field, method or parameter + val typedef = findTypeDef(item) ?: return + + val pkg = when (item) { + is MemberItem -> item.containingClass().containingPackage() + is ParameterItem -> item.containingMethod().containingClass().containingPackage() + else -> return + } + + val list = packageToAnnotationPairs[pkg] ?: run { + val new = + mutableListOf<Pair<Item, AnnotationHolder>>() + packageToAnnotationPairs[pkg] = new + new + } + list.add(Pair(item, typedef)) + } + + override fun visitField(field: FieldItem) { + checkItem(field) + } + + override fun visitMethod(method: MethodItem) { + checkItem(method) + } + + override fun visitParameter(parameter: ParameterItem) { + checkItem(parameter) + } + + private fun findTypeDef(item: Item): AnnotationHolder? { + for (annotation in item.modifiers.annotations()) { + val qualifiedName = annotation.qualifiedName() ?: continue + if (qualifiedName.startsWith(JAVA_LANG_PREFIX) || + qualifiedName.startsWith(ANDROIDX_ANNOTATION_PREFIX) || + qualifiedName.startsWith(ANDROID_ANNOTATION_PREFIX) || + qualifiedName.startsWith(ANDROID_SUPPORT_ANNOTATION_PREFIX) + ) { + if (annotation.isTypeDefAnnotation()) { + // Imported typedef + return AnnotationHolder(null, annotation, null) + } + + continue + } + + val typeDefClass = annotation.resolve() ?: continue + val className = typeDefClass.qualifiedName() + if (typeDefClass.isAnnotationType()) { + val cached = classToAnnotationHolder[className] + if (cached != null) { + return cached + } + + val typeDefAnnotation = typeDefClass.modifiers.annotations().firstOrNull { + it.isTypeDefAnnotation() + } + if (typeDefAnnotation != null) { + // Make sure it has the right retention + if (!hasSourceRetention(typeDefClass)) { + reporter.report( + Errors.ANNOTATION_EXTRACTION, typeDefClass, + "This typedef annotation class should have @Retention(RetentionPolicy.SOURCE)" + ) + } + + if (filterEmit.test(typeDefClass)) { + reporter.report( + Errors.ANNOTATION_EXTRACTION, typeDefClass, + "This typedef annotation class should be marked @hide or should not be marked public" + ) + } + + if (typeDefAnnotation is PsiAnnotationItem && typeDefClass is PsiClassItem) { + val result = AnnotationHolder( + typeDefClass, typeDefAnnotation, + annotationLookup.findRealAnnotation( + typeDefAnnotation.psiAnnotation, + typeDefClass.psiClass, + null + ) + ) + classToAnnotationHolder[className] = result + return result + } + } + } + } + return null + } + + private fun hasSourceRetention(annotationClass: ClassItem): Boolean { + if (annotationClass is PsiClassItem) { + return hasSourceRetention(annotationClass.psiClass) + } + return false + } + + private fun hasSourceRetention(cls: PsiClass): Boolean { + val modifierList = cls.modifierList + if (modifierList != null) { + for (psiAnnotation in modifierList.annotations) { + val annotation = JavaUAnnotation.wrap(psiAnnotation) + if (hasSourceRetention(annotation)) { + return true + } + } } - extractor.export(options.externalAnnotations, null) + return false + } - if (typedefFile != null) { - extractor.writeTypedefFile(typedefFile) + private fun hasSourceRetention(annotation: UAnnotation): Boolean { + val qualifiedName = annotation.qualifiedName + if ("java.lang.annotation.Retention" == qualifiedName || "kotlin.annotation.Retention" == qualifiedName) { + val attributes = annotation.attributeValues + if (attributes.size != 1) { + error("Expected exactly one parameter passed to @Retention") + return false + } + val value = attributes[0].expression + if (value is UReferenceExpression) { + try { + val element = value.resolve() + if (element is PsiField) { + val field = element as PsiField? + if ("SOURCE" == field!!.name) { + return true + } + } + } catch (t: Throwable) { + val s = value.asSourceString() + return s.contains("SOURCE") + } + } } - if (rmTypeDefs.isNotEmpty()) { - if (typedefFile != null) { - Extractor.removeTypedefClasses(rmTypeDefs, typedefFile) + return false + } + + /** + * A writer which stores all its contents into a string and has the ability to mark a certain + * freeze point and then reset back to it + */ + private class StringPrintWriter constructor(private val stringWriter: StringWriter) : + PrintWriter(stringWriter) { + private var mark: Int = 0 + + val contents: String get() = stringWriter.toString() + + fun mark() { + flush() + mark = stringWriter.buffer.length + } + + fun reset() { + stringWriter.buffer.setLength(mark) + } + + override fun toString(): String { + return contents + } + + companion object { + fun create(): StringPrintWriter { + return StringPrintWriter(StringWriter(1000)) + } + } + } + + private fun escapeXml(unescaped: String): String { + return XmlEscapers.xmlAttributeEscaper().escape(unescaped) + } + + private fun Item.getExternalAnnotationSignature(): String? { + when (this) { + is PackageItem -> { + return escapeXml(qualifiedName()) + } + + is ClassItem -> { + return escapeXml(qualifiedName()) + } + + is MethodItem -> { + val sb = StringBuilder(100) + sb.append(escapeXml(containingClass().qualifiedName())) + sb.append(' ') + + if (isConstructor()) { + sb.append(escapeXml(containingClass().simpleName())) + } else if (returnType() != null) { + sb.append(escapeXml(returnType()!!.toTypeString())) + sb.append(' ') + sb.append(escapeXml(name())) + } + + sb.append('(') + + // The signature must match *exactly* the formatting used by IDEA, + // since it looks up external annotations in a map by this key. + // Therefore, it is vital that the parameter list uses exactly one + // space after each comma between parameters, and *no* spaces between + // generics variables, e.g. foo(Map<A,B>, int) + var i = 0 + val parameterList = parameters() + val n = parameterList.size + while (i < n) { + if (i > 0) { + sb.append(',').append(' ') + } + val type = parameterList[i].type().toTypeString().replace(" ", "") + sb.append(type) + i++ + } + sb.append(')') + return sb.toString() + } + + is FieldItem -> { + return escapeXml(containingClass().qualifiedName()) + ' '.toString() + name() + } + + is ParameterItem -> { + return containingMethod().getExternalAnnotationSignature() + ' '.toString() + this.parameterIndex + } + } + + return null + } + + private fun writeAnnotation( + writer: StringPrintWriter, + item: Item, + annotationHolder: AnnotationHolder + ) { + val annotationItem = annotationHolder.annotationItem + val qualifiedName = annotationItem.qualifiedName() + + writer.mark() + writer.print(" <annotation name=\"") + writer.print(qualifiedName) + + writer.print("\">") + writer.println() + + val uAnnotation = annotationHolder.uAnnotation + ?: if (annotationItem is PsiAnnotationItem) { + // Imported annotation + JavaUAnnotation.wrap(annotationItem.psiAnnotation) } else { - extractor.removeTypedefClasses() + null + } + if (uAnnotation != null) { + var attributes = uAnnotation.attributeValues + + // noinspection PointlessBooleanExpression,ConstantConditions + if (attributes.size > 1 && sortAnnotations) { + // Ensure mutable + attributes = ArrayList(attributes) + + // Ensure that the value attribute is written first + attributes.sortedWith(object : Comparator<UNamedExpression> { + private fun getName(pair: UNamedExpression): String { + val name = pair.name + return name ?: SdkConstants.ATTR_VALUE + } + + private fun rank(pair: UNamedExpression): Int { + return if (SdkConstants.ATTR_VALUE == getName(pair)) -1 else 0 + } + + override fun compare(o1: UNamedExpression, o2: UNamedExpression): Int { + val r1 = rank(o1) + val r2 = rank(o2) + val delta = r1 - r2 + return if (delta != 0) { + delta + } else getName(o1).compareTo(getName(o2)) + } + }) } + + if (attributes.size == 1 && Extractor.REQUIRES_PERMISSION.isPrefix(qualifiedName, true)) { + val expression = attributes[0].expression + if (expression is UAnnotation) { + // The external annotations format does not allow for nested/complex annotations. + // However, these special annotations (@RequiresPermission.Read, + // @RequiresPermission.Write, etc) are known to only be simple containers with a + // single permission child, so instead we "inline" the content: + // @Read(@RequiresPermission(allOf={P1,P2},conditional=true) + // => + // @RequiresPermission.Read(allOf({P1,P2},conditional=true) + // That's setting attributes that don't actually exist on the container permission, + // but we'll counteract that on the read-annotations side. + val annotation = expression as UAnnotation + attributes = annotation.attributeValues + } else if (expression is JavaUAnnotationCallExpression) { + val annotation = expression.uAnnotation + attributes = annotation.attributeValues + } else if (expression is UastEmptyExpression && attributes[0].sourcePsi is PsiNameValuePair) { + val memberValue = (attributes[0].sourcePsi as PsiNameValuePair).value + if (memberValue is PsiAnnotation) { + val annotation = JavaUAnnotation.wrap(memberValue) + attributes = annotation.attributeValues + } + } + } + + val inlineConstants = isInlinedConstant(annotationItem) + var empty = true + for (pair in attributes) { + val expression = pair.expression + val value = attributeString(expression, inlineConstants) ?: continue + empty = false + var name = pair.name + if (name == null) { + name = SdkConstants.ATTR_VALUE // default name + } + + // Platform typedef annotations now declare a prefix attribute for + // documentation generation purposes; this should not be part of the + // extracted metadata. + if (("prefix" == name || "suffix" == name) && annotationItem.isTypeDefAnnotation()) { + reporter.report( + Errors.SUPERFLUOUS_PREFIX, item, + "Superfluous $name attribute on typedef" + ) + continue + } + + writer.print(" <val name=\"") + writer.print(name) + writer.print("\" val=\"") + writer.print(escapeXml(value)) + writer.println("\" />") + } + + if (empty) { + // All items were filtered out: don't write the annotation at all + writer.reset() + return + } + } + + writer.println(" </annotation>") + } + + private fun attributeString(value: UExpression?, inlineConstants: Boolean): String? { + value ?: return null + val sb = StringBuilder() + return if (appendExpression(sb, value, inlineConstants)) { + sb.toString() + } else { + null + } + } + + private fun appendExpression( + sb: StringBuilder, + expression: UExpression, + inlineConstants: Boolean + ): Boolean { + if (expression.isArrayInitializer()) { + val call = expression as UCallExpression + val initializers = call.valueArguments + sb.append('{') + var first = true + val initialLength = sb.length + for (e in initializers) { + val length = sb.length + if (first) { + first = false + } else { + sb.append(", ") + } + val appended = appendExpression(sb, e, inlineConstants) + if (!appended) { + // trunk off comma if it bailed for some reason (e.g. constant + // filtered out by API etc) + sb.setLength(length) + if (length == initialLength) { + first = true + } + } + } + sb.append('}') + return sb.length != 2 + } else if (expression is UReferenceExpression) { + val resolved = expression.resolve() + if (resolved is PsiField) { + val field = resolved as PsiField? + if (!inlineConstants) { + // Inline constants + val value = field!!.computeConstantValue() + if (appendLiteralValue(sb, value)) { + return true + } + } + + val declaringClass = field!!.containingClass + if (declaringClass == null) { + error("No containing class found for " + field.name) + return false + } + val qualifiedName = declaringClass.qualifiedName + val fieldName = field.name + + if (qualifiedName != null) { + val cls = codebase.findClass(qualifiedName) + val fld = cls?.findField(fieldName, true) + if (fld == null || !filterReference.test(fld)) { + // This field is not visible: remove from typedef + if (fld != null) { + reporter.report( + Errors.HIDDEN_TYPEDEF_CONSTANT, fld, + "Typedef class references hidden field $fld: removed from typedef metadata" + ) + } + return false + } + sb.append(qualifiedName) + sb.append('.') + sb.append(fieldName) + return true + } + return false + } else { + warning("Unexpected reference to $expression") + return false + } + } else if (expression is ULiteralExpression) { + val literalValue = expression.value + if (appendLiteralValue(sb, literalValue)) { + return true + } + } else if (expression is UBinaryExpressionWithType) { + if ((expression).isTypeCast()) { + val operand = expression.operand + return appendExpression(sb, operand, inlineConstants) + } + return false } + + // For example, binary expressions like 3 + 4 + val literalValue = ConstantEvaluator.evaluate(null, expression) + if (literalValue != null) { + if (appendLiteralValue(sb, literalValue)) { + return true + } + } + + warning("Unexpected annotation expression of type ${expression.javaClass} and is $expression") + + return false + } + + private fun appendLiteralValue(sb: StringBuilder, literalValue: Any?): Boolean { + if (literalValue is Number || literalValue is Boolean) { + sb.append(literalValue.toString()) + return true + } else if (literalValue is String || literalValue is Char) { + sb.append('"') + XmlUtils.appendXmlAttributeValue(sb, literalValue.toString()) + sb.append('"') + return true + } + return false + } + + private fun isInlinedConstant(annotationItem: AnnotationItem): Boolean { + return annotationItem.isTypeDefAnnotation() + } + + /** Whether to sort annotation attributes (otherwise their declaration order is used) */ + private val sortAnnotations: Boolean = true + + private fun warning(string: String) { + reporter.report(Severity.ERROR, null as PsiElement?, string, Errors.ANNOTATION_EXTRACTION) + } + + private fun error(string: String) { + reporter.report(Severity.WARNING, null as PsiElement?, string, Errors.ANNOTATION_EXTRACTION) } } diff --git a/src/main/java/com/android/tools/metalava/KotlinInteropChecks.kt b/src/main/java/com/android/tools/metalava/KotlinInteropChecks.kt index ebda9ad1ec6047da39f1f246f130a8e08e410e64..7af08d1cf3deeb0e91e1201a4eca58650e0f18d7 100644 --- a/src/main/java/com/android/tools/metalava/KotlinInteropChecks.kt +++ b/src/main/java/com/android/tools/metalava/KotlinInteropChecks.kt @@ -16,7 +16,6 @@ package com.android.tools.metalava -import com.android.annotations.NonNull import com.android.tools.metalava.doclava1.Errors import com.android.tools.metalava.model.Codebase import com.android.tools.metalava.model.FieldItem @@ -79,15 +78,18 @@ class KotlinInteropChecks { val doc = method.documentation for (exception in exceptions.sortedBy { it.qualifiedName() }) { val checked = !(exception.extends("java.lang.RuntimeException") || - exception.extends("java.lang.Error")) + exception.extends("java.lang.Error")) if (checked) { val annotation = method.modifiers.findAnnotation("kotlin.jvm.Throws") if (annotation != null) { - val attribute = annotation.findAttribute("exceptionClasses") + annotation.attributes().first().name + val attribute = + annotation.findAttribute("exceptionClasses") ?: annotation.findAttribute("value") + ?: annotation.attributes().firstOrNull() if (attribute != null) { for (v in attribute.leafValues()) { val source = v.toSource() - if (source == exception.qualifiedName() + ".class") { // contains: is + if (source.endsWith(exception.simpleName() + "::class")) { return } } @@ -152,7 +154,7 @@ class KotlinInteropChecks { } parameters (such as parameter ${i + 1}, \"${parameter.name()}\", in ${ method.containingClass().qualifiedName()}.${method.name() }) should be last to improve Kotlin interoperability; see " + - "https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions" + "https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions" reporter.report(Errors.SAM_SHOULD_BE_LAST, method, message) break } @@ -343,7 +345,7 @@ class KotlinInteropChecks { } /** Returns true if the given string is a reserved Java keyword */ - fun isJavaKeyword(@NonNull keyword: String): Boolean { + fun isJavaKeyword(keyword: String): Boolean { // TODO when we built on top of IDEA core replace this with // JavaLexer.isKeyword(candidate, LanguageLevel.JDK_1_5) when (keyword) { diff --git a/src/main/java/com/android/tools/metalava/NullnessMigration.kt b/src/main/java/com/android/tools/metalava/NullnessMigration.kt index 60c727ffd7fe87faa82620137204a54b9288a626..8cb8d6b1f4de90732b4267dab72535df1c3d3f96 100644 --- a/src/main/java/com/android/tools/metalava/NullnessMigration.kt +++ b/src/main/java/com/android/tools/metalava/NullnessMigration.kt @@ -17,23 +17,16 @@ package com.android.tools.metalava import com.android.tools.metalava.model.AnnotationItem +import com.android.tools.metalava.model.FieldItem import com.android.tools.metalava.model.Item +import com.android.tools.metalava.model.MethodItem +import com.android.tools.metalava.model.ParameterItem +import com.android.tools.metalava.model.TypeItem /** * Performs null migration analysis, looking at previous API signature * files and new signature files, and replacing new @Nullable and @NonNull - * annotations with @NewlyNullable and @NewlyNonNull, and similarly - * moving @NewlyNullable and @NewlyNonNull to @RecentlyNullable and @RecentlyNonNull - * (and finally once the annotations have been there for another API level, - * finally moving them to unconditionally nullable/nonnull.) - * - * (Newly null is the initial level; user code is marked as warnings if in - * conflict with the annotation. Recently null is the next level; once an - * API has had newly-null metadata in one API level, it gets promoted to - * recently, which generates errors instead of warnings. The reason we have - * this instead of just making it unconditional is that you can still invoke - * the compiler with a flag to defeat it, so the Kotlin team suggested we do - * this. + * annotations with @RecentlyNullable and @RecentlyNonNull. * * TODO: Enforce compatibility across type use annotations, e.g. * changing parameter value from @@ -42,40 +35,81 @@ import com.android.tools.metalava.model.Item * {@code @NonNull List<@NonNull String>} * is forbidden. */ -class NullnessMigration : ComparisonVisitor() { +class NullnessMigration : ComparisonVisitor(visitAddedItemsRecursively = true) { override fun compare(old: Item, new: Item) { + if (hasNullnessInformation(new) && !hasNullnessInformation(old)) { + markRecent(new) + } + } + + override fun added(new: Item) { + // Translate newly added items into RecentlyNull/RecentlyNonNull if (hasNullnessInformation(new)) { - if (!hasNullnessInformation(old)) { - // Nullness information change: Add migration annotation - val annotation = if (isNullable(new)) NEWLY_NULLABLE else NEWLY_NONNULL - - val migration = findNullnessAnnotation(new) ?: return - val modifiers = new.mutableModifiers() - modifiers.removeAnnotation(migration) - - // Don't map annotation names - this would turn newly non null back into non null - modifiers.addAnnotation(new.codebase.createAnnotation("@" + annotation, new, mapName = false)) - } else if (hasMigrationAnnotation(old)) { - // Already marked migration before: Now we can promote it to - // no longer migrated! - val nullAnnotation = findNullnessAnnotation(new) ?: return - val migration = findMigrationAnnotation(old)?.toSource() ?: return - val modifiers = new.mutableModifiers() - modifiers.removeAnnotation(nullAnnotation) - - if (isNewlyMigrated(old)) { - // Move from newly to recently - val source = migration.replace("Newly", "Recently") - modifiers.addAnnotation(new.codebase.createAnnotation(source, new, mapName = false)) - } else { - // Move from recently to no longer marked as migrated - val source = migration.replace("Newly", "").replace("Recently", "") - modifiers.addAnnotation(new.codebase.createAnnotation(source, new, mapName = false)) - } + markRecent(new) + } + } + override fun compare(old: MethodItem, new: MethodItem) { + val newType = new.returnType() ?: return + val oldType = old.returnType() ?: return + checkType(oldType, newType) + } + + override fun compare(old: FieldItem, new: FieldItem) { + val newType = new.type() + val oldType = old.type() + checkType(oldType, newType) + } + + override fun compare(old: ParameterItem, new: ParameterItem) { + val newType = new.type() + val oldType = old.type() + checkType(oldType, newType) + } + + override fun added(new: MethodItem) { + checkType(new.returnType() ?: return) + } + + override fun added(new: FieldItem) { + checkType(new.type()) + } + + override fun added(new: ParameterItem) { + checkType(new.type()) + } + + private fun hasNullnessInformation(type: TypeItem): Boolean { + val typeString = type.toTypeString(false, true, false) + return typeString.contains(".Nullable") || typeString.contains(".NonNull") + } + + private fun checkType(old: TypeItem, new: TypeItem) { + if (hasNullnessInformation(new)) { + if (old.toTypeString(false, true, false) != + new.toTypeString(false, true, false)) { + new.markRecent() } } } + private fun checkType(new: TypeItem) { + if (hasNullnessInformation(new)) { + new.markRecent() + } + } + + private fun markRecent(new: Item) { + val annotation = findNullnessAnnotation(new) ?: return + // Nullness information change: Add migration annotation + val annotationClass = if (annotation.isNullable()) RECENTLY_NULLABLE else RECENTLY_NONNULL + + val modifiers = new.mutableModifiers() + modifiers.removeAnnotation(annotation) + + // Don't map annotation names - this would turn newly non null back into non null + modifiers.addAnnotation(new.codebase.createAnnotation("@$annotationClass", new, mapName = false)) + } + companion object { fun hasNullnessInformation(item: Item): Boolean { return isNullable(item) || isNonNull(item) @@ -85,86 +119,21 @@ class NullnessMigration : ComparisonVisitor() { return item.modifiers.annotations().firstOrNull { it.isNullnessAnnotation() } } - fun findMigrationAnnotation(item: Item): AnnotationItem? { - return item.modifiers.annotations().firstOrNull { - val qualifiedName = it.qualifiedName() ?: "" - isMigrationAnnotation(qualifiedName) - } - } - fun isNullable(item: Item): Boolean { return item.modifiers.annotations().any { it.isNullable() } } - fun isNonNull(item: Item): Boolean { + private fun isNonNull(item: Item): Boolean { return item.modifiers.annotations().any { it.isNonNull() } } - fun hasMigrationAnnotation(item: Item): Boolean { - return item.modifiers.annotations().any { isMigrationAnnotation(it.qualifiedName() ?: "") } - } - - fun isNewlyMigrated(item: Item): Boolean { - return item.modifiers.annotations().any { isNewlyMigrated(it.qualifiedName() ?: "") } - } - - fun isRecentlyMigrated(item: Item): Boolean { + private fun isRecentlyMigrated(item: Item): Boolean { return item.modifiers.annotations().any { isRecentlyMigrated(it.qualifiedName() ?: "") } } - fun isNewlyMigrated(qualifiedName: String): Boolean { - return qualifiedName.endsWith(".NewlyNullable") || - qualifiedName.endsWith(".NewlyNonNull") - } - - fun isRecentlyMigrated(qualifiedName: String): Boolean { + private fun isRecentlyMigrated(qualifiedName: String): Boolean { return qualifiedName.endsWith(".RecentlyNullable") || - qualifiedName.endsWith(".RecentlyNonNull") - } - - fun isMigrationAnnotation(qualifiedName: String): Boolean { - return isNewlyMigrated(qualifiedName) || isRecentlyMigrated(qualifiedName) + qualifiedName.endsWith(".RecentlyNonNull") } } } - -/** - * @TypeQualifierNickname - * @NonNull - * @kotlin.annotations.jvm.UnderMigration(status = kotlin.annotations.jvm.MigrationStatus.WARN) - * @Retention(RetentionPolicy.CLASS) - * public @interface NewlyNullable { - * } - */ -const val NEWLY_NULLABLE = "android.support.annotation.NewlyNullable" - -/** - * @TypeQualifierNickname - * @NonNull - * @kotlin.annotations.jvm.UnderMigration(status = kotlin.annotations.jvm.MigrationStatus.WARN) - * @Retention(RetentionPolicy.CLASS) - * public @interface NewlyNonNull { - * } - */ -const val NEWLY_NONNULL = "android.support.annotation.NewlyNonNull" - -/** - * @TypeQualifierNickname - * @NonNull - * @kotlin.annotations.jvm.UnderMigration(status = kotlin.annotations.jvm.MigrationStatus.STRICT) - * @Retention(RetentionPolicy.CLASS) - * public @interface NewlyNullable { - * } - */ - -const val RECENTLY_NULLABLE = "android.support.annotation.RecentlyNullable" -/** - * @TypeQualifierNickname - * @NonNull - * @kotlin.annotations.jvm.UnderMigration(status = kotlin.annotations.jvm.MigrationStatus.STRICT) - * @Retention(RetentionPolicy.CLASS) - * public @interface NewlyNonNull { - * } - */ -const val RECENTLY_NONNULL = "android.support.annotation.RecentlyNonNull" - diff --git a/src/main/java/com/android/tools/metalava/Options.kt b/src/main/java/com/android/tools/metalava/Options.kt index 8fc443b4dba694e2ffd9e47f2fe49131b6eafc97..b2c1c680fb8a43190567362d6f43865e857f0eda 100644 --- a/src/main/java/com/android/tools/metalava/Options.kt +++ b/src/main/java/com/android/tools/metalava/Options.kt @@ -18,13 +18,12 @@ package com.android.tools.metalava import com.android.SdkConstants import com.android.sdklib.SdkVersionInfo -import com.android.tools.lint.annotations.ApiDatabase import com.android.tools.metalava.doclava1.Errors import com.android.utils.SdkUtils.wrap import com.google.common.base.CharMatcher import com.google.common.base.Splitter -import com.google.common.collect.Lists import com.google.common.io.Files +import com.intellij.pom.java.LanguageLevel import java.io.File import java.io.IOException import java.io.OutputStreamWriter @@ -48,25 +47,25 @@ private const val ARG_SOURCE_PATH = "--source-path" private const val ARG_SOURCE_FILES = "--source-files" private const val ARG_API = "--api" private const val ARG_PRIVATE_API = "--private-api" +private const val ARG_DEX_API = "--dex-api" private const val ARG_PRIVATE_DEX_API = "--private-dex-api" private const val ARG_SDK_VALUES = "--sdk-values" private const val ARG_REMOVED_API = "--removed-api" +private const val ARG_REMOVED_DEX_API = "--removed-dex-api" private const val ARG_MERGE_ANNOTATIONS = "--merge-annotations" private const val ARG_INPUT_API_JAR = "--input-api-jar" private const val ARG_EXACT_API = "--exact-api" private const val ARG_STUBS = "--stubs" +private const val ARG_DOC_STUBS = "--doc-stubs" private const val ARG_STUBS_SOURCE_LIST = "--write-stubs-source-list" +private const val ARG_DOC_STUBS_SOURCE_LIST = "--write-doc-stubs-source-list" private const val ARG_PROGUARD = "--proguard" private const val ARG_EXTRACT_ANNOTATIONS = "--extract-annotations" private const val ARG_EXCLUDE_ANNOTATIONS = "--exclude-annotations" -private const val ARG_API_FILTER = "--api-filter" -private const val ARG_RM_TYPEDEFS = "--rmtypedefs" -private const val ARG_TYPEDEF_FILE = "--typedef-file" -private const val ARG_SKIP_CLASS_RETENTION = "--skip-class-retention" -private const val ARG_HIDE_FILTERED = "--hide-filtered" private const val ARG_HIDE_PACKAGE = "--hide-package" private const val ARG_MANIFEST = "--manifest" private const val ARG_PREVIOUS_API = "--previous-api" +private const val ARG_CURRENT_API = "--current-api" private const val ARG_MIGRATE_NULLNESS = "--migrate-nullness" private const val ARG_CHECK_COMPATIBILITY = "--check-compatibility" private const val ARG_INPUT_KOTLIN_NULLS = "--input-kotlin-nulls" @@ -74,6 +73,8 @@ private const val ARG_OUTPUT_KOTLIN_NULLS = "--output-kotlin-nulls" private const val ARG_OUTPUT_DEFAULT_VALUES = "--output-default-values" private const val ARG_ANNOTATION_COVERAGE_STATS = "--annotation-coverage-stats" private const val ARG_ANNOTATION_COVERAGE_OF = "--annotation-coverage-of" +private const val ARG_WRITE_CLASS_COVERAGE_TO = "--write-class-coverage-to" +private const val ARG_WRITE_MEMBER_COVERAGE_TO = "--write-member-coverage-to" private const val ARG_WARNINGS_AS_ERRORS = "--warnings-as-errors" private const val ARG_LINTS_AS_ERRORS = "--lints-as-errors" private const val ARG_SHOW_ANNOTATION = "--show-annotation" @@ -90,7 +91,6 @@ private const val ARG_HIDE = "--hide" private const val ARG_UNHIDE_CLASSPATH_CLASSES = "--unhide-classpath-classes" private const val ARG_ALLOW_REFERENCING_UNKNOWN_CLASSES = "--allow-referencing-unknown-classes" private const val ARG_NO_UNKNOWN_CLASSES = "--no-unknown-classes" -private const val ARG_INCLUDE_DOC_ONLY = "--include-doconly" private const val ARG_APPLY_API_LEVELS = "--apply-api-levels" private const val ARG_GENERATE_API_LEVELS = "--generate-api-levels" private const val ARG_ANDROID_JAR_PATTERN = "--android-jar-pattern" @@ -105,6 +105,8 @@ private const val ARG_PRIVATE = "--private" private const val ARG_HIDDEN = "--hidden" private const val ARG_NO_DOCS = "--no-docs" private const val ARG_GENERATE_DOCUMENTATION = "--generate-documentation" +private const val ARG_JAVA_SOURCE = "--java-source" +private const val ARG_REGISTER_ARTIFACT = "--register-artifact" class Options( args: Array<String>, @@ -224,10 +226,17 @@ class Options( /** If set, a directory to write stub files to. Corresponds to the --stubs/-stubs flag. */ var stubsDir: File? = null + /** If set, a directory to write documentation stub files to. Corresponds to the --stubs/-stubs flag. */ + var docStubsDir: File? = null + /** If set, a source file to write the stub index (list of source files) to. Can be passed to * other tools like javac/javadoc using the special @-syntax. */ var stubsSourceList: File? = null + /** If set, a source file to write the doc stub index (list of source files) to. Can be passed to + * other tools like javac/javadoc using the special @-syntax. */ + var docStubsSourceList: File? = null + /** Proguard Keep list file to write */ var proguard: File? = null @@ -237,6 +246,9 @@ class Options( /** If set, a file to write the private API file to. Corresponds to the --private-api/-privateApi flag. */ var privateApiFile: File? = null + /** If set, a file to write the DEX signatures to. Corresponds to --dex-api. */ + var dexApiFile: File? = null + /** If set, a file to write the private DEX signatures to. Corresponds to --private-dex-api. */ var privateDexApiFile: File? = null @@ -249,27 +261,32 @@ class Options( /** A manifest file to read to for example look up available permissions */ var manifest: File? = null - /** If set, a file to write an API file to. Corresponds to the --removed-api/-removedApi flag. */ + /** If set, a file to write a dex API file to. Corresponds to the --removed-dex-api/-removedDexApi flag. */ var removedApiFile: File? = null + /** If set, a file to write an API file to. Corresponds to the --removed-api/-removedApi flag. */ + var removedDexApiFile: File? = null + /** Whether output should be colorized */ var color = System.getenv("TERM")?.startsWith("xterm") ?: false /** Whether to omit Java and Kotlin runtime library packages from annotation coverage stats */ - var omitRuntimePackageStats = true - - /** Whether to include doc-only-marked items */ - var includeDocOnly = false + var omitRuntimePackageStats = false /** Whether to generate annotations into the stubs */ var generateAnnotations = true /** - * A signature file for the previous version of this API (for compatibility checks, nullness - * migration, etc.) + * A signature file for the previous version of this API (for nullness + * migration, possibly for compatibility checking (if [currentApi] is not defined), etc.) */ var previousApi: File? = null + /** + * A signature file for the current version of this API (for compatibility checks). + */ + var currentApi: File? = null + /** Whether we should check API compatibility based on the previous API in [previousApi] */ var checkCompatibility: Boolean = false @@ -282,20 +299,11 @@ class Options( /** Set of jars and class files for existing apps that we want to measure coverage of */ var annotationCoverageOf: List<File> = mutableAnnotationCoverageOf - /** Framework API definition to restrict included APIs to */ - var apiFilter: ApiDatabase? = null + /** File to write the annotation class coverage report to, if any */ + var annotationCoverageClassReport: File? = null - /** If filtering out non-APIs, supply this flag to hide listing matches */ - var hideFiltered: Boolean = false - - /** Don't extract annotations that have class retention */ - var skipClassRetention: Boolean = false - - /** Remove typedef classes found in the given folder */ - var rmTypeDefs: File? = null - - /** Framework API definition to restrict included APIs to */ - var typedefFile: File? = null + /** File to write the annotation member coverage report to, if any */ + var annotationCoverageMemberReport: File? = null /** An optional <b>jar</b> file to load classes from instead of from source. * This is similar to the [classpath] attribute except we're explicitly saying @@ -330,6 +338,9 @@ class Options( */ var apiLevelJars: Array<File>? = null + /** The api level of the codebase, or -1 if not known/specified */ + var currentApiLevel = -1 + /** API level XML file to generate */ var generateApiLevelXml: File? = null @@ -346,6 +357,14 @@ class Options( */ var omitLocations = false + /** + * The language level to use for Java files, set with [ARG_JAVA_SOURCE] + */ + var javaLanguageLevel: LanguageLevel = LanguageLevel.JDK_1_8 + + /** Map from XML API descriptor file to corresponding artifact id name */ + val artifactRegistrations = ArtifactTagger() + init { // Pre-check whether --color/--no-color is present and use that to decide how // to emit the banner even before we emit errors @@ -369,9 +388,7 @@ class Options( stdout.println() stdout.flush() - val apiFilters = mutableListOf<File>() var androidJarPatterns: MutableList<String>? = null - var currentApiLevel: Int = -1 var currentCodeName: String? = null var currentJar: File? = null @@ -428,13 +445,16 @@ class Options( "-sdkvalues", ARG_SDK_VALUES -> sdkValueDir = stringToNewDir(getValue(args, ++index)) ARG_API, "-api" -> apiFile = stringToNewFile(getValue(args, ++index)) + ARG_DEX_API, "-dexApi" -> dexApiFile = stringToNewFile(getValue(args, ++index)) ARG_PRIVATE_API, "-privateApi" -> privateApiFile = stringToNewFile(getValue(args, ++index)) ARG_PRIVATE_DEX_API, "-privateDexApi" -> privateDexApiFile = stringToNewFile(getValue(args, ++index)) ARG_REMOVED_API, "-removedApi" -> removedApiFile = stringToNewFile(getValue(args, ++index)) + ARG_REMOVED_DEX_API, "-removedDexApi" -> removedDexApiFile = stringToNewFile(getValue(args, ++index)) ARG_EXACT_API, "-exactApi" -> { + getValue(args, ++index) // prevent next arg from tripping up parser unimplemented(arg) // Not yet implemented (because it seems to no longer be hooked up in doclava1) } @@ -452,7 +472,9 @@ class Options( "--hideAnnotations", "-hideAnnotation" -> mutableHideAnnotations.add(getValue(args, ++index)) ARG_STUBS, "-stubs" -> stubsDir = stringToNewDir(getValue(args, ++index)) + ARG_DOC_STUBS -> docStubsDir = stringToNewDir(getValue(args, ++index)) ARG_STUBS_SOURCE_LIST -> stubsSourceList = stringToNewFile(getValue(args, ++index)) + ARG_DOC_STUBS_SOURCE_LIST -> docStubsSourceList = stringToNewFile(getValue(args, ++index)) ARG_EXCLUDE_ANNOTATIONS -> generateAnnotations = false @@ -503,6 +525,7 @@ class Options( ARG_EXTRACT_ANNOTATIONS -> externalAnnotations = stringToNewFile(getValue(args, ++index)) ARG_PREVIOUS_API -> previousApi = stringToExistingFile(getValue(args, ++index)) + ARG_CURRENT_API -> currentApi = stringToExistingFile(getValue(args, ++index)) ARG_MIGRATE_NULLNESS -> migrateNulls = true @@ -511,19 +534,35 @@ class Options( } ARG_ANNOTATION_COVERAGE_STATS -> dumpAnnotationStatistics = true - ARG_ANNOTATION_COVERAGE_OF -> mutableAnnotationCoverageOf.add( - stringToExistingFileOrDir( + ARG_ANNOTATION_COVERAGE_OF -> mutableAnnotationCoverageOf.addAll( + stringToExistingDirsOrJars( getValue(args, ++index) ) ) + ARG_WRITE_CLASS_COVERAGE_TO -> { + annotationCoverageClassReport = stringToNewFile(getValue(args, ++index)) + } + ARG_WRITE_MEMBER_COVERAGE_TO -> { + annotationCoverageMemberReport = stringToNewFile(getValue(args, ++index)) + } ARG_ERROR, "-error" -> Errors.setErrorLevel(getValue(args, ++index), Severity.ERROR) ARG_WARNING, "-warning" -> Errors.setErrorLevel(getValue(args, ++index), Severity.WARNING) ARG_LINT, "-lint" -> Errors.setErrorLevel(getValue(args, ++index), Severity.LINT) ARG_HIDE, "-hide" -> Errors.setErrorLevel(getValue(args, ++index), Severity.HIDDEN) - ARG_WARNINGS_AS_ERRORS, "-werror" -> warningsAreErrors = true - ARG_LINTS_AS_ERRORS, "-lerror" -> lintsAreErrors = true + ARG_WARNINGS_AS_ERRORS -> warningsAreErrors = true + ARG_LINTS_AS_ERRORS -> lintsAreErrors = true + "-werror" -> { + // Temporarily disabled; this is used in various builds but is pretty much + // never what we want. + //warningsAreErrors = true + } + "-lerror" -> { + // Temporarily disabled; this is used in various builds but is pretty much + // never what we want. + //lintsAreErrors = true + } ARG_CHECK_KOTLIN_INTEROP -> checkKotlinInterop = true @@ -542,15 +581,6 @@ class Options( ARG_ALLOW_REFERENCING_UNKNOWN_CLASSES -> allowReferencingUnknownClasses = true ARG_NO_UNKNOWN_CLASSES -> noUnknownClasses = true - ARG_INCLUDE_DOC_ONLY -> includeDocOnly = true - - // Annotation extraction flags - ARG_API_FILTER -> apiFilters.add(stringToExistingFile(getValue(args, ++index))) - ARG_RM_TYPEDEFS -> rmTypeDefs = stringToExistingDir(getValue(args, ++index)) - ARG_TYPEDEF_FILE -> typedefFile = stringToNewFile(getValue(args, ++index)) - ARG_HIDE_FILTERED -> hideFiltered = true - ARG_SKIP_CLASS_RETENTION -> skipClassRetention = true - // Extracting API levels ARG_ANDROID_JAR_PATTERN -> { val list = androidJarPatterns ?: run { @@ -576,7 +606,13 @@ class Options( generateApiLevelXml = stringToNewFile(getValue(args, ++index)) } ARG_APPLY_API_LEVELS -> { - applyApiLevelsXml = stringToExistingFile(getValue(args, ++index)) + applyApiLevelsXml = if (args.contains(ARG_GENERATE_API_LEVELS)) { + // If generating the API file at the same time, it doesn't have + // to already exist + stringToNewFile(getValue(args, ++index)) + } else { + stringToExistingFile(getValue(args, ++index)) + } } ARG_NO_DOCS, "-nodocs" -> noDocs = true @@ -585,10 +621,16 @@ class Options( // Digest all the remaining arguments. // Allow "STUBS_DIR" to reference the stubs directory. invokeDocumentationToolArguments = args.slice(++index until args.size).mapNotNull { - if (it == "STUBS_DIR" && stubsDir != null) { + if (it == "STUBS_DIR" && docStubsDir != null) { + docStubsDir?.path + } else if (it == "STUBS_DIR" && stubsDir != null) { stubsDir?.path + } else if (it == "DOC_STUBS_SOURCE_LIST" && docStubsSourceList != null) { + "@${docStubsSourceList?.path}" } else if (it == "STUBS_SOURCE_LIST" && stubsSourceList != null) { "@${stubsSourceList?.path}" + } else if (it == "STUBS_SOURCE_LIST" && docStubsSourceList != null) { + "@${docStubsSourceList?.path}" } else { it } @@ -597,8 +639,15 @@ class Options( index = args.size // jump to end of argument loop } + ARG_REGISTER_ARTIFACT, "-artifact" -> { + val descriptor = stringToExistingFile(getValue(args, ++index)) + val artifactId = getValue(args, ++index) + artifactRegistrations.register(artifactId, descriptor) + } + // Unimplemented doclava1 flags (no arguments) - "-quiet" -> { + "-quiet", + "-yamlV2" -> { unimplemented(arg) } @@ -624,10 +673,13 @@ class Options( } } - "-source" -> { + ARG_JAVA_SOURCE, "-source" -> { val value = getValue(args, ++index) - if (value != "1.8") { - throw DriverException("$value: Only source 1.8 is supported") + val level = LanguageLevel.parse(value) + when { + level == null -> throw DriverException("$value is not a valid or supported Java language level") + level.isLessThan(LanguageLevel.JDK_1_7) -> throw DriverException("$arg must be at least 1.7") + else -> javaLanguageLevel = level } } @@ -680,7 +732,6 @@ class Options( // doclava1 flags with two arguments "-federate", "-federationapi", - "-artifact", "-htmldir2" -> { javadoc(arg) index += 2 @@ -739,8 +790,7 @@ class Options( } else if (arg.startsWith(ARGS_COMPAT_OUTPUT)) { compatOutput = if (arg == ARGS_COMPAT_OUTPUT) true - else - yesNo(arg.substring(ARGS_COMPAT_OUTPUT.length + 1)) + else yesNo(arg.substring(ARGS_COMPAT_OUTPUT.length + 1)) } else if (arg.startsWith("-")) { // Compatibility flag; map to mutable properties in the Compatibility // class and assign it @@ -769,26 +819,11 @@ class Options( ++index } - if (!apiFilters.isEmpty()) { - apiFilter = try { - val lines = Lists.newArrayList<String>() - for (file in apiFilters) { - lines.addAll(Files.readLines(file, com.google.common.base.Charsets.UTF_8)) - } - ApiDatabase(lines) - } catch (e: IOException) { - throw DriverException("Could not open API database $apiFilters: ${e.localizedMessage}") - } - } - if (generateApiLevelXml != null) { - if (currentJar != null && currentApiLevel == -1 || currentJar == null && currentApiLevel != -1) { - throw DriverException("You must specify both --current-jar and --current-version (or neither one)") - } if (androidJarPatterns == null) { androidJarPatterns = mutableListOf( "prebuilts/tools/common/api-versions/android-%/android.jar", - "prebuilts/sdk/%/android.jar" + "prebuilts/sdk/%/public/android.jar" ) } apiLevelJars = findAndroidJars(androidJarPatterns!!, currentApiLevel, currentCodeName, currentJar) @@ -822,8 +857,10 @@ class Options( } private fun findAndroidJars( - androidJarPatterns: List<String>, currentApiLevel: Int, - currentCodeName: String?, currentJar: File? + androidJarPatterns: List<String>, + currentApiLevel: Int, + currentCodeName: String?, + currentJar: File? ): Array<File> { @Suppress("NAME_SHADOWING") @@ -883,8 +920,8 @@ class Options( /** Makes sure that the flag combinations make sense */ private fun checkFlagConsistency() { - if (checkCompatibility && previousApi == null) { - throw DriverException(stderr = "$ARG_CHECK_COMPATIBILITY requires $ARG_PREVIOUS_API") + if (checkCompatibility && currentApi == null && previousApi == null) { + throw DriverException(stderr = "$ARG_CHECK_COMPATIBILITY requires $ARG_CURRENT_API") } if (migrateNulls && previousApi == null) { @@ -898,20 +935,16 @@ class Options( if (compatOutput && outputKotlinStyleNulls) { throw DriverException( stderr = "$ARG_OUTPUT_KOTLIN_NULLS should not be combined with " + - "$ARGS_COMPAT_OUTPUT=yes" + "$ARGS_COMPAT_OUTPUT=yes" ) } if (compatOutput && outputDefaultValues) { throw DriverException( stderr = "$ARG_OUTPUT_DEFAULT_VALUES should not be combined with " + - "$ARGS_COMPAT_OUTPUT=yes" + "$ARGS_COMPAT_OUTPUT=yes" ) } - -// if (stubsSourceList != null && stubsDir == null) { -// throw OptionsException(stderr = "$ARG_STUBS_SOURCE_LIST should only be used when $ARG_STUBS is set") -// } } private fun javadoc(arg: String) { @@ -932,11 +965,11 @@ class Options( } if (!options.quiet) { val message = "Ignoring unimplemented doclava1 flag $arg" + - when (arg) { - "-encoding" -> " (UTF-8 assumed)" - "-source" -> " (1.8 assumed)" - else -> "" - } + when (arg) { + "-encoding" -> " (UTF-8 assumed)" + "-source" -> " (1.8 assumed)" + else -> "" + } reporter.report(Severity.WARNING, null as String?, message, color = color) } } @@ -1124,28 +1157,30 @@ class Options( ARG_NO_COLOR, "Do not attempt to colorize the output", "", "\nAPI sources:", - ARG_SOURCE_FILES + " <files>", "A comma separated list of source files to be parsed. Can also be " + - "@ followed by a path to a text file containing paths to the full set of files to parse.", + "$ARG_SOURCE_FILES <files>", "A comma separated list of source files to be parsed. Can also be " + + "@ followed by a path to a text file containing paths to the full set of files to parse.", - ARG_SOURCE_PATH + " <paths>", "One or more directories (separated by `${File.pathSeparator}`) " + - "containing source files (within a package hierarchy)", + "$ARG_SOURCE_PATH <paths>", "One or more directories (separated by `${File.pathSeparator}`) " + + "containing source files (within a package hierarchy)", - ARG_CLASS_PATH + " <paths>", "One or more directories or jars (separated by " + - "`${File.pathSeparator}`) containing classes that should be on the classpath when parsing the " + - "source files", + "$ARG_CLASS_PATH <paths>", "One or more directories or jars (separated by " + + "`${File.pathSeparator}`) containing classes that should be on the classpath when parsing the " + + "source files", - ARG_MERGE_ANNOTATIONS + " <file>", "An external annotations file (using IntelliJ's external " + - "annotations database format) to merge and overlay the sources", + "$ARG_MERGE_ANNOTATIONS <file>", "An external annotations file (using IntelliJ's external " + + "annotations database format) to merge and overlay the sources. A subset of .jaif files " + + "is also supported.", - ARG_INPUT_API_JAR + " <file>", "A .jar file to read APIs from directly", + "$ARG_INPUT_API_JAR <file>", "A .jar file to read APIs from directly", - ARG_MANIFEST + " <file>", "A manifest file, used to for check permissions to cross check APIs", + "$ARG_MANIFEST <file>", "A manifest file, used to for check permissions to cross check APIs", - ARG_HIDE_PACKAGE + " <package>", "Remove the given packages from the API even if they have not been " + - "marked with @hide", + "$ARG_HIDE_PACKAGE <package>", "Remove the given packages from the API even if they have not been " + + "marked with @hide", - ARG_SHOW_ANNOTATION + " <annotation class>", "Include the given annotation in the API analysis", + "$ARG_SHOW_ANNOTATION <annotation class>", "Include the given annotation in the API analysis", ARG_SHOW_UNANNOTATED, "Include un-annotated public APIs in the signature file as well", + "$ARG_JAVA_SOURCE <level>", "Sets the source level for Java source files; default is 1.8.", "", "\nDocumentation:", ARG_PUBLIC, "Only include elements that are public", @@ -1156,83 +1191,97 @@ class Options( "", "\nExtracting Signature Files:", // TODO: Document --show-annotation! - ARG_API + " <file>", "Generate a signature descriptor file", - ARG_PRIVATE_API + " <file>", "Generate a signature descriptor file listing the exact private APIs", - ARG_PRIVATE_DEX_API + " <file>", "Generate a DEX signature descriptor file listing the exact private APIs", - ARG_REMOVED_API + " <file>", "Generate a signature descriptor file for APIs that have been removed", - ARG_OUTPUT_KOTLIN_NULLS + "[=yes|no]", "Controls whether nullness annotations should be formatted as " + - "in Kotlin (with \"?\" for nullable types, \"\" for non nullable types, and \"!\" for unknown. " + - "The default is yes.", - ARG_OUTPUT_DEFAULT_VALUES + "[=yes|no]", "Controls whether default values should be included in " + - "signature files. The default is yes.", - ARGS_COMPAT_OUTPUT + "=[yes|no]", "Controls whether to keep signature files compatible with the " + - "historical format (with its various quirks) or to generate the new format (which will also include " + - "annotations that are part of the API, etc.)", - ARG_OMIT_COMMON_PACKAGES + "[=yes|no]", "Skip common package prefixes like java.lang.* and " + - "kotlin.* in signature files, along with packages for well known annotations like @Nullable and " + - "@NonNull.", - - ARG_PROGUARD + " <file>", "Write a ProGuard keep file for the API", - ARG_SDK_VALUES + " <dir>", "Write SDK values files to the given directory", + "$ARG_API <file>", "Generate a signature descriptor file", + "$ARG_PRIVATE_API <file>", "Generate a signature descriptor file listing the exact private APIs", + "$ARG_DEX_API <file>", "Generate a DEX signature descriptor file listing the APIs", + "$ARG_PRIVATE_DEX_API <file>", "Generate a DEX signature descriptor file listing the exact private APIs", + "$ARG_REMOVED_API <file>", "Generate a signature descriptor file for APIs that have been removed", + "$ARG_OUTPUT_KOTLIN_NULLS[=yes|no]", "Controls whether nullness annotations should be formatted as " + + "in Kotlin (with \"?\" for nullable types, \"\" for non nullable types, and \"!\" for unknown. " + + "The default is yes.", + "$ARG_OUTPUT_DEFAULT_VALUES[=yes|no]", "Controls whether default values should be included in " + + "signature files. The default is yes.", + "$ARGS_COMPAT_OUTPUT=[yes|no]", "Controls whether to keep signature files compatible with the " + + "historical format (with its various quirks) or to generate the new format (which will also include " + + "annotations that are part of the API, etc.)", + "$ARG_OMIT_COMMON_PACKAGES[=yes|no]", "Skip common package prefixes like java.lang.* and " + + "kotlin.* in signature files, along with packages for well known annotations like @Nullable and " + + "@NonNull.", + + "$ARG_PROGUARD <file>", "Write a ProGuard keep file for the API", + "$ARG_SDK_VALUES <dir>", "Write SDK values files to the given directory", "", "\nGenerating Stubs:", - ARG_STUBS + " <dir>", "Generate stub source files for the API", + "$ARG_STUBS <dir>", "Generate stub source files for the API", + "$ARG_DOC_STUBS <dir>", "Generate documentation stub source files for the API. Documentation stub " + + "files are similar to regular stub files, but there are some differences. For example, in " + + "the stub files, we'll use special annotations like @RecentlyNonNull instead of @NonNull to " + + "indicate that an element is recently marked as non null, whereas in the documentation stubs we'll " + + "just list this as @NonNull. Another difference is that @doconly elements are included in " + + "documentation stubs, but not regular stubs, etc.", ARG_EXCLUDE_ANNOTATIONS, "Exclude annotations such as @Nullable from the stub files", - ARG_STUBS_SOURCE_LIST + " <file>", "Write the list of generated stub files into the given source " + - "list file", + "$ARG_STUBS_SOURCE_LIST <file>", "Write the list of generated stub files into the given source " + + "list file. If generating documentation stubs and you haven't also specified " + + "$ARG_DOC_STUBS_SOURCE_LIST, this list will refer to the documentation stubs; " + + "otherwise it's the non-documentation stubs.", + "$ARG_DOC_STUBS_SOURCE_LIST <file>", "Write the list of generated doc stub files into the given source " + + "list file", + "$ARG_REGISTER_ARTIFACT <api-file> <id>", "Registers the given id for the packages found in " + + "the given signature file. $PROGRAM_NAME will inject an @artifactId <id> tag into every top " + + "level stub class in that API.", "", "\nDiffs and Checks:", - ARG_PREVIOUS_API + " <signature file>", "A signature file for the previous version of this " + - "API to apply diffs with", - ARG_INPUT_KOTLIN_NULLS + "[=yes|no]", "Whether the signature file being read should be " + - "interpreted as having encoded its types using Kotlin style types: a suffix of \"?\" for nullable " + - "types, no suffix for non nullable types, and \"!\" for unknown. The default is no.", + "$ARG_PREVIOUS_API <signature file>", "A signature file for the previous version of this " + + "API to apply diffs with", + "$ARG_INPUT_KOTLIN_NULLS[=yes|no]", "Whether the signature file being read should be " + + "interpreted as having encoded its types using Kotlin style types: a suffix of \"?\" for nullable " + + "types, no suffix for non nullable types, and \"!\" for unknown. The default is no.", ARG_CHECK_COMPATIBILITY, "Check compatibility with the previous API", ARG_CHECK_KOTLIN_INTEROP, "Check API intended to be used from both Kotlin and Java for interoperability " + - "issues", + "issues", + "$ARG_CURRENT_API <signature file>", "A signature file for the current version of this " + + "API to check compatibility with. If not specified, $ARG_PREVIOUS_API will be used " + + "instead.", ARG_MIGRATE_NULLNESS, "Compare nullness information with the previous API and mark newly " + - "annotated APIs as under migration.", + "annotated APIs as under migration.", ARG_WARNINGS_AS_ERRORS, "Promote all warnings to errors", ARG_LINTS_AS_ERRORS, "Promote all API lint warnings to errors", - ARG_ERROR + " <id>", "Report issues of the given id as errors", - ARG_WARNING + " <id>", "Report issues of the given id as warnings", - ARG_LINT + " <id>", "Report issues of the given id as having lint-severity", - ARG_HIDE + " <id>", "Hide/skip issues of the given id", + "$ARG_ERROR <id>", "Report issues of the given id as errors", + "$ARG_WARNING <id>", "Report issues of the given id as warnings", + "$ARG_LINT <id>", "Report issues of the given id as having lint-severity", + "$ARG_HIDE <id>", "Hide/skip issues of the given id", "", "\nStatistics:", ARG_ANNOTATION_COVERAGE_STATS, "Whether $PROGRAM_NAME should emit coverage statistics for " + - "annotations, listing the percentage of the API that has been annotated with nullness information.", + "annotations, listing the percentage of the API that has been annotated with nullness information.", - ARG_ANNOTATION_COVERAGE_OF + " <paths>", "One or more jars (separated by `${File.pathSeparator}`) " + - "containing existing apps that we want to measure annotation coverage statistics for. The set of " + - "API usages in those apps are counted up and the most frequently used APIs that are missing " + - "annotation metadata are listed in descending order.", + "$ARG_ANNOTATION_COVERAGE_OF <paths>", "One or more jars (separated by `${File.pathSeparator}`) " + + "containing existing apps that we want to measure annotation coverage statistics for. The set of " + + "API usages in those apps are counted up and the most frequently used APIs that are missing " + + "annotation metadata are listed in descending order.", ARG_SKIP_JAVA_IN_COVERAGE_REPORT, "In the coverage annotation report, skip java.** and kotlin.** to " + - "narrow the focus down to the Android framework APIs.", + "narrow the focus down to the Android framework APIs.", - "", "\nExtracting Annotations:", - ARG_EXTRACT_ANNOTATIONS + " <zipfile>", "Extracts annotations from the source files and writes them " + - "into the given zip file", + "$ARG_WRITE_CLASS_COVERAGE_TO <path>", "Specifies a file to write the annotation " + + "coverage report for classes to.", + "$ARG_WRITE_MEMBER_COVERAGE_TO <path>", "Specifies a file to write the annotation " + + "coverage report for members to.", - ARG_API_FILTER + " <file>", "Applies the given signature file as a filter (which means no classes," + - "methods or fields not found in the filter will be included.)", - ARG_HIDE_FILTERED, "Omit listing APIs that were skipped because of the $ARG_API_FILTER", - - ARG_SKIP_CLASS_RETENTION, "Do not extract annotations that have class file retention", - ARG_RM_TYPEDEFS, "Delete all the typedef .class files", - ARG_TYPEDEF_FILE + " <file>", "Writes an typedef annotation class names into the given file", + "", "\nExtracting Annotations:", + "$ARG_EXTRACT_ANNOTATIONS <zipfile>", "Extracts source annotations from the source files and writes " + + "them into the given zip file", "", "\nInjecting API Levels:", - ARG_APPLY_API_LEVELS + " <api-versions.xml>", "Reads an XML file containing API level descriptions " + - "and merges the information into the documentation", + "$ARG_APPLY_API_LEVELS <api-versions.xml>", "Reads an XML file containing API level descriptions " + + "and merges the information into the documentation", "", "\nExtracting API Levels:", - ARG_GENERATE_API_LEVELS + " <xmlfile>", + "$ARG_GENERATE_API_LEVELS <xmlfile>", "Reads android.jar SDK files and generates an XML file recording " + - "the API level for each class, method and field", - ARG_ANDROID_JAR_PATTERN + " <pattern>", "Patterns to use to locate Android JAR files. The default " + - "is \$ANDROID_HOME/platforms/android-%/android.jar.", + "the API level for each class, method and field", + "$ARG_ANDROID_JAR_PATTERN <pattern>", "Patterns to use to locate Android JAR files. The default " + + "is \$ANDROID_HOME/platforms/android-%/android.jar.", ARG_CURRENT_VERSION, "Sets the current API level of the current source code", ARG_CURRENT_CODENAME, "Sets the code name for the current source code", ARG_CURRENT_JAR, "Points to the current API jar, if any" diff --git a/src/main/java/com/android/tools/metalava/Reporter.kt b/src/main/java/com/android/tools/metalava/Reporter.kt index c8d1f0c95057700cd96b3337a8936bac3571c44c..846b9ae34a1535eaaf80c8ee177bcbe66d2a7714 100644 --- a/src/main/java/com/android/tools/metalava/Reporter.kt +++ b/src/main/java/com/android/tools/metalava/Reporter.kt @@ -188,7 +188,7 @@ open class Reporter(private val rootFolder: File? = null) { path } else { val lineNumber = getLineNumber(psiFile.text, range.startOffset) + 1 - path + ":" + lineNumber + "$path:$lineNumber" } } @@ -214,7 +214,10 @@ open class Reporter(private val rootFolder: File? = null) { } open fun report( - severity: Severity, location: String?, message: String, id: Errors.Error? = null, + severity: Severity, + location: String?, + message: String, + id: Errors.Error? = null, color: Boolean = options.color ) { if (severity == HIDDEN) { diff --git a/src/main/java/com/android/tools/metalava/SdkFileWriter.kt b/src/main/java/com/android/tools/metalava/SdkFileWriter.kt index 4f88b629d4b070357664a504347f3f7aeefac2bc..0d68845d3b18794b11d7a473505c7cf2887131ea 100644 --- a/src/main/java/com/android/tools/metalava/SdkFileWriter.kt +++ b/src/main/java/com/android/tools/metalava/SdkFileWriter.kt @@ -80,7 +80,7 @@ class SdkFileWriter(val codebase: Codebase, private val outputDir: java.io.File) if (SDK_CONSTANT_ANNOTATION == annotation.qualifiedName()) { val resolved = annotation.findAttribute(null)?.leafValues()?.firstOrNull()?.resolve() as? FieldItem - ?: continue + ?: continue val type = resolved.containingClass().qualifiedName() + "." + resolved.name() when { SDK_CONSTANT_TYPE_ACTIVITY_ACTION == type -> activityActions.add(value.toString()) diff --git a/src/main/java/com/android/tools/metalava/SignatureWriter.kt b/src/main/java/com/android/tools/metalava/SignatureWriter.kt index 6d651d1374253e8e3aba23c1cfcb3737fe03bbae..7fca3d862a17348a65ef80747290a7312d2436ab 100644 --- a/src/main/java/com/android/tools/metalava/SignatureWriter.kt +++ b/src/main/java/com/android/tools/metalava/SignatureWriter.kt @@ -23,6 +23,7 @@ import com.android.tools.metalava.model.Item import com.android.tools.metalava.model.MethodItem import com.android.tools.metalava.model.ModifierList import com.android.tools.metalava.model.PackageItem +import com.android.tools.metalava.model.ParameterItem import com.android.tools.metalava.model.TypeItem import com.android.tools.metalava.model.TypeParameterList import com.android.tools.metalava.model.javaEscapeString @@ -57,7 +58,7 @@ class SignatureWriter( writer.print(" ctor ") writeModifiers(constructor) // Note - we don't write out the type parameter list (constructor.typeParameterList()) in signature files! - //writeTypeParameterList(constructor.typeParameterList(), addSpace = true) + // writeTypeParameterList(constructor.typeParameterList(), addSpace = true) writer.print(constructor.containingClass().fullName()) writeParameterList(constructor) writeThrowsList(constructor) @@ -70,7 +71,7 @@ class SignatureWriter( writer.print(name) writer.print(" ") writeModifiers(field) - writeType(field.type(), field.modifiers) + writeType(field, field.type(), field.modifiers) writer.print(' ') writer.print(field.name()) field.writeValueWithSemicolon(writer, allowDefaultValue = false, requireInitialValue = false) @@ -84,7 +85,7 @@ class SignatureWriter( return } - if (compatibility.skipInheritedInterfaceMethods && method.inheritedInterfaceMethod) { + if (compatibility.skipInheritedMethods && method.inheritedMethod) { return } @@ -92,7 +93,7 @@ class SignatureWriter( writeModifiers(method) writeTypeParameterList(method.typeParameterList(), addSpace = true) - writeType(method.returnType(), method.modifiers) + writeType(method, method.returnType(), method.modifiers) writer.print(' ') writer.print(method.name()) writeParameterList(method) @@ -148,7 +149,8 @@ class SignatureWriter( includeDeprecated = true, includeAnnotations = compatibility.annotationsInSignatures, skipNullnessAnnotations = options.outputKotlinStyleNulls, - omitCommonPackages = options.omitCommonPackages + omitCommonPackages = options.omitCommonPackages, + onlyIncludeSignatureAnnotations = true ) } @@ -164,8 +166,7 @@ class SignatureWriter( val superClass = if (preFiltered) cls.superClassType() - else - cls.filteredSuperClassType(filterReference) + else cls.filteredSuperClassType(filterReference) if (superClass != null && !superClass.isJavaLangObject()) { val superClassString = superClass.toTypeString(erased = compatibility.omitTypeParametersInInterfaces) @@ -185,8 +186,7 @@ class SignatureWriter( val interfaces = if (preFiltered) cls.interfaceTypes().asSequence() - else - cls.filteredInterfaceTypes(filterReference).asSequence() + else cls.filteredInterfaceTypes(filterReference).asSequence() val all: Sequence<TypeItem> = if (isInterface && compatibility.extendsForInterfaceSuperClass) { val superClassType = cls.superClassType() if (superClassType != null && !superClassType.isJavaLangObject()) { @@ -227,7 +227,7 @@ class SignatureWriter( writer.print(", ") } writeModifiers(parameter) - writeType(parameter.type(), parameter.modifiers) + writeType(parameter, parameter.type(), parameter.modifiers) if (emitParameterNames) { val name = parameter.publicName() if (name != null) { @@ -250,7 +250,11 @@ class SignatureWriter( writer.print(")") } - private fun writeType(type: TypeItem?, modifiers: ModifierList) { + private fun writeType( + item: Item, + type: TypeItem?, + modifiers: ModifierList + ) { type ?: return var typeString = type.toTypeString( @@ -264,6 +268,24 @@ class SignatureWriter( typeString = TypeItem.shortenTypes(typeString) } + if (typeString.endsWith(", ?>") && compatibility.includeExtendsObjectInWildcard && item is ParameterItem) { + // This wasn't done universally; just in a few places, so replicate it for those exact places + val methodName = item.containingMethod().name() + when (methodName) { + "computeIfAbsent" -> { + if (typeString == "java.util.function.Function<? super java.lang.Object, ?>") { + typeString = "java.util.function.Function<? super java.lang.Object, ? extends java.lang.Object>" + } + } + "computeIfPresent", "merge", "replaceAll", "compute" -> { + if (typeString == "java.util.function.BiFunction<? super java.lang.Object, ? super java.lang.Object, ?>") { + typeString = + "java.util.function.BiFunction<? super java.lang.Object, ? super java.lang.Object, ? extends java.lang.Object>" + } + } + } + } + writer.print(typeString) if (options.outputKotlinStyleNulls && !type.primitive) { @@ -284,15 +306,14 @@ class SignatureWriter( } private fun writeThrowsList(method: MethodItem) { - val throws = if (preFiltered) - method.throwsTypes().asSequence().sortedWith(ClassItem.fullNameComparator) - else - method.throwsTypes().asSequence() - .filter { filterReference.test(it) } - .sortedWith(ClassItem.fullNameComparator) + val throws = when { + preFiltered -> method.throwsTypes().asSequence() + compatibility.filterThrowsClasses -> method.filteredThrowsTypes(filterReference).asSequence() + else -> method.throwsTypes().asSequence() + } if (throws.any()) { writer.print(" throws ") - throws.asSequence().forEachIndexed { i, type -> + throws.asSequence().sortedWith(ClassItem.fullNameComparator).forEachIndexed { i, type -> if (i > 0) { writer.print(", ") } diff --git a/src/main/java/com/android/tools/metalava/StubWriter.kt b/src/main/java/com/android/tools/metalava/StubWriter.kt index 832ff92aafa7e5e7f437b3da4146ee1718e6e94a..40a6a5a46189ed6770c350b580561b936c50163d 100644 --- a/src/main/java/com/android/tools/metalava/StubWriter.kt +++ b/src/main/java/com/android/tools/metalava/StubWriter.kt @@ -44,7 +44,8 @@ class StubWriter( private val codebase: Codebase, private val stubsDir: File, private val generateAnnotations: Boolean = false, - private val preFiltered: Boolean = true + private val preFiltered: Boolean = true, + docStubs: Boolean ) : ApiVisitor( visitConstructorsAsMethods = false, nestInnerClasses = true, @@ -53,8 +54,9 @@ class StubWriter( // Methods are by default sorted in source order in stubs, to encourage methods // that are near each other in the source to show up near each other in the documentation methodComparator = MethodItem.sourceOrderComparator, - filterEmit = FilterPredicate(ApiPredicate(codebase)), - filterReference = ApiPredicate(codebase, ignoreShown = true) + filterEmit = FilterPredicate(ApiPredicate(codebase, ignoreShown = true, includeDocOnly = docStubs)), + filterReference = ApiPredicate(codebase, ignoreShown = true, includeDocOnly = docStubs), + includeEmptyOuterClasses = true ) { private val sourceList = StringBuilder(20000) @@ -135,11 +137,14 @@ class StubWriter( ModifierList.writeAnnotations( list = pkg.modifiers, separateLines = true, + // Some bug in UAST triggers duplicate nullability annotations + // here; make sure the are filtered out + filterDuplicates = true, + onlyIncludeSignatureAnnotations = true, writer = writer ) writer.println("package ${pkg.qualifiedName()};") - writer.flush() writer.close() } @@ -159,7 +164,7 @@ class StubWriter( } private fun getClassFile(classItem: ClassItem): File { - assert(classItem.containingClass() == null, { "Should only be called on top level classes" }) + assert(classItem.containingClass() == null) { "Should only be called on top level classes" } // TODO: Look up compilation unit language return File(getPackageDir(classItem.containingPackage()), "${classItem.simpleName()}.java") } @@ -296,7 +301,8 @@ class StubWriter( ModifierList.write( writer, modifiers, item, removeAbstract = removeAbstract, removeFinal = removeFinal, - addPublic = addPublic, includeAnnotations = generateAnnotations + addPublic = addPublic, includeAnnotations = generateAnnotations, + onlyIncludeSignatureAnnotations = true ) } @@ -308,9 +314,7 @@ class StubWriter( val superClass = if (preFiltered) cls.superClassType() - else - cls.filteredSuperClassType(filterReference) - + else cls.filteredSuperClassType(filterReference) if (superClass != null && !superClass.isJavaLangObject()) { val qualifiedName = superClass.toTypeString() @@ -340,14 +344,12 @@ class StubWriter( val interfaces = if (preFiltered) cls.interfaceTypes().asSequence() - else - cls.filteredInterfaceTypes(filterReference).asSequence() + else cls.filteredInterfaceTypes(filterReference).asSequence() if (interfaces.any()) { if (cls.isInterface() && cls.superClassType() != null) writer.print(", ") - else - writer.print(" implements") + else writer.print(" implements") interfaces.forEachIndexed { index, type -> if (index > 0) { writer.print(",") @@ -409,7 +411,7 @@ class StubWriter( val invokeOnThis = constructor != null && constructor.containingClass() == it.containingClass() if (invokeOnThis || parameters.isNotEmpty()) { val includeCasts = parameters.isNotEmpty() && - it.containingClass().constructors().filter { filterReference.test(it) }.size > 1 + it.containingClass().constructors().filter { filterReference.test(it) }.size > 1 if (invokeOnThis) { writer.print("this(") } else { @@ -434,7 +436,6 @@ class StubWriter( writer.write(")") } writer.write("null") - } else { if (typeString != "boolean" && typeString != "int" && typeString != "long") { writer.write("(") @@ -471,8 +472,8 @@ class StubWriter( val isAnnotation = containingClass.isAnnotationType() if (isEnum && (method.name() == "values" || - method.name() == "valueOf" && method.parameters().size == 1 && - method.parameters()[0].type().toTypeString() == JAVA_LANG_STRING) + method.name() == "valueOf" && method.parameters().size == 1 && + method.parameters()[0].type().toTypeString() == JAVA_LANG_STRING) ) { // Skip the values() and valueOf(String) methods in enums: these are added by // the compiler for enums anyway, but was part of the doclava1 signature files @@ -563,10 +564,11 @@ class StubWriter( private fun generateThrowsList(method: MethodItem) { // Note that throws types are already sorted internally to help comparison matching - val throws = if (preFiltered) + val throws = if (preFiltered) { method.throwsTypes().asSequence() - else - method.throwsTypes().asSequence().filter { filterReference.test(it) } + } else { + method.filteredThrowsTypes(filterReference).asSequence() + } if (throws.any()) { writer.print(" throws ") throws.asSequence().sortedWith(ClassItem.fullNameComparator).forEachIndexed { i, type -> diff --git a/src/main/java/com/android/tools/metalava/Terminal.kt b/src/main/java/com/android/tools/metalava/Terminal.kt index 8a32691458936a0c5d0d592c270c785e5ecd0540..8ba75514e6a7ee89a3c6ad185b89295c5ceec120 100644 --- a/src/main/java/com/android/tools/metalava/Terminal.kt +++ b/src/main/java/com/android/tools/metalava/Terminal.kt @@ -30,8 +30,11 @@ enum class TerminalColor(val value: Int) { } fun terminalAttributes( - bold: Boolean = false, underline: Boolean = false, reverse: Boolean = false, - foreground: TerminalColor? = null, background: TerminalColor? = null + bold: Boolean = false, + underline: Boolean = false, + reverse: Boolean = false, + foreground: TerminalColor? = null, + background: TerminalColor? = null ): String { val sb = StringBuilder() sb.append("\u001B[") @@ -81,8 +84,11 @@ fun bold(string: String): String { fun PrintWriter.terminalPrint( string: String, - bold: Boolean = false, underline: Boolean = false, reverse: Boolean = false, - foreground: TerminalColor? = null, background: TerminalColor? = null + bold: Boolean = false, + underline: Boolean = false, + reverse: Boolean = false, + foreground: TerminalColor? = null, + background: TerminalColor? = null ) { print( terminalAttributes( diff --git a/src/main/java/com/android/tools/metalava/apilevels/AddApisFromCodebase.kt b/src/main/java/com/android/tools/metalava/apilevels/AddApisFromCodebase.kt new file mode 100644 index 0000000000000000000000000000000000000000..f46ca3a12e319eb64ce0bafe2c046ad5b49a9f51 --- /dev/null +++ b/src/main/java/com/android/tools/metalava/apilevels/AddApisFromCodebase.kt @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tools.metalava.apilevels + +import com.android.tools.metalava.model.ClassItem +import com.android.tools.metalava.model.Codebase +import com.android.tools.metalava.model.FieldItem +import com.android.tools.metalava.model.MethodItem +import com.android.tools.metalava.model.visitors.ApiVisitor + +/** Visits the API codebase and inserts into the [Api] the classes, methods and fields */ +fun addApisFromCodebase(api: Api, apiLevel: Int, codebase: Codebase) { + codebase.accept(object : ApiVisitor( + codebase, + visitConstructorsAsMethods = true, + nestInnerClasses = false + ) { + + var currentClass: ApiClass? = null + + override fun afterVisitClass(cls: ClassItem) { + currentClass = null + } + + override fun visitClass(cls: ClassItem) { + val newClass = api.addClass(cls.internalName(), apiLevel, cls.deprecated) + currentClass = newClass + + if (cls.isClass()) { + // Sadly it looks like the signature files use the non-public references instead + val filteredSuperClass = cls.filteredSuperclass(filterReference) + val superClass = cls.superClass() + if (filteredSuperClass != superClass && filteredSuperClass != null) { + val existing = newClass.superClasses.firstOrNull()?.name + if (existing == superClass?.internalName()) { + newClass.addSuperClass(superClass?.internalName(), apiLevel) + } else { + newClass.addSuperClass(filteredSuperClass.internalName(), apiLevel) + } + } else if (superClass != null) { + newClass.addSuperClass(superClass.internalName(), apiLevel) + } + } + + for (interfaceType in cls.filteredInterfaceTypes(filterReference)) { + val interfaceClass = interfaceType.asClass() ?: return + newClass.addInterface(interfaceClass.internalName(), apiLevel) + } + } + + override fun visitMethod(method: MethodItem) { + currentClass?.addMethod( + method.internalName() + + // Use "V" instead of the type of the constructor for backwards compatibility + // with the older bytecode + method.internalDesc(voidConstructorTypes = true), apiLevel, method.deprecated + ) + } + + override fun visitField(field: FieldItem) { + // We end up moving constants from interfaces in the codebase but that's not the + // case in older bytecode + if (field.isCloned()) { + return + } + currentClass?.addField(field.internalName(), apiLevel, field.deprecated) + } + }) +} \ No newline at end of file diff --git a/src/main/java/com/android/tools/metalava/apilevels/AndroidJarReader.java b/src/main/java/com/android/tools/metalava/apilevels/AndroidJarReader.java index 378d536bb4d596f45e6153c241e7f5b29eca458e..c5a7d5941576ef580fea16a9f8e51189fc732057 100644 --- a/src/main/java/com/android/tools/metalava/apilevels/AndroidJarReader.java +++ b/src/main/java/com/android/tools/metalava/apilevels/AndroidJarReader.java @@ -15,9 +15,11 @@ */ package com.android.tools.metalava.apilevels; -import com.android.annotations.NonNull; +import com.android.tools.metalava.model.Codebase; import com.google.common.io.ByteStreams; import com.google.common.io.Closeables; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.objectweb.asm.ClassReader; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.ClassNode; @@ -40,16 +42,23 @@ public class AndroidJarReader { private File mCurrentJar; private List<String> mPatterns; private File[] mApiLevels; + private final Codebase mCodebase; - AndroidJarReader(@NonNull List<String> patterns, int minApi, @NonNull File currentJar, int currentApi) { + AndroidJarReader(@NotNull List<String> patterns, + int minApi, + @NotNull File currentJar, + int currentApi, + @Nullable Codebase codebase) { mPatterns = patterns; mMinApi = minApi; mCurrentJar = currentJar; mCurrentApi = currentApi; + mCodebase = codebase; } - AndroidJarReader(@NonNull File[] apiLevels) { + AndroidJarReader(@NotNull File[] apiLevels, @Nullable Codebase codebase) { mApiLevels = apiLevels; + mCodebase = codebase; } public Api getApi() throws IOException { @@ -59,6 +68,12 @@ public class AndroidJarReader { File jar = getAndroidJarFile(apiLevel); readJar(api, apiLevel, jar); } + if (mCodebase != null) { + int apiLevel = mCodebase.getApiLevel(); + if (apiLevel != -1) { + processCodebase(api, apiLevel); + } + } } else { // Get all the android.jar. They are in platforms-# int apiLevel = mMinApi - 1; @@ -72,10 +87,11 @@ public class AndroidJarReader { jar = getAndroidJarFile(apiLevel); } if (jar == null || !jar.isFile()) { - System.out.println("Last API level found: " + (apiLevel - 1)); + if (mCodebase != null) { + processCodebase(api, apiLevel); + } break; } - System.out.println("Found API " + apiLevel + " at " + jar.getPath()); readJar(api, apiLevel, jar); } } @@ -86,6 +102,13 @@ public class AndroidJarReader { return api; } + private void processCodebase(Api api, int apiLevel) { + if (mCodebase == null) { + return; + } + AddApisFromCodebaseKt.addApisFromCodebase(api, apiLevel, mCodebase); + } + private void readJar(Api api, int apiLevel, File jar) throws IOException { api.update(apiLevel); @@ -108,7 +131,7 @@ public class AndroidJarReader { reader.accept(classNode, 0 /*flags*/); ApiClass theClass = api.addClass(classNode.name, apiLevel, - (classNode.access & Opcodes.ACC_DEPRECATED) != 0); + (classNode.access & Opcodes.ACC_DEPRECATED) != 0); // super class if (classNode.superName != null) { @@ -127,7 +150,7 @@ public class AndroidJarReader { continue; } if (!fieldNode.name.startsWith("this$") && - !fieldNode.name.equals("$VALUES")) { + !fieldNode.name.equals("$VALUES")) { boolean deprecated = (fieldNode.access & Opcodes.ACC_DEPRECATED) != 0; theClass.addField(fieldNode.name, apiLevel, deprecated); } diff --git a/src/main/java/com/android/tools/metalava/apilevels/Api.java b/src/main/java/com/android/tools/metalava/apilevels/Api.java index 30dc5f76aef39253d8a342d5674b40fe65128167..f37e9c31d5fe972cc416cc4907ebef0d60a3a5f9 100644 --- a/src/main/java/com/android/tools/metalava/apilevels/Api.java +++ b/src/main/java/com/android/tools/metalava/apilevels/Api.java @@ -23,7 +23,7 @@ import java.util.Map; * Represents the whole Android API. */ public class Api extends ApiElement { - private final Map<String, ApiClass> mClasses = new HashMap<String, ApiClass>(); + private final Map<String, ApiClass> mClasses = new HashMap<>(); public Api() { // Pretend that API started from version 0 to make sure that classes existed in the first version diff --git a/src/main/java/com/android/tools/metalava/apilevels/ApiClass.java b/src/main/java/com/android/tools/metalava/apilevels/ApiClass.java index 90dd511590bf6f0e65bb4be3251ebff30070a640..d215ec017bd95e508047190b680aa3572410efcd 100644 --- a/src/main/java/com/android/tools/metalava/apilevels/ApiClass.java +++ b/src/main/java/com/android/tools/metalava/apilevels/ApiClass.java @@ -16,6 +16,7 @@ package com.android.tools.metalava.apilevels; import com.google.common.collect.Iterables; +import org.jetbrains.annotations.NotNull; import java.io.PrintStream; import java.util.ArrayList; @@ -30,11 +31,11 @@ import java.util.Map; * This is used to write the simplified XML file containing all the public API. */ public class ApiClass extends ApiElement { - private final List<ApiElement> mSuperClasses = new ArrayList<ApiElement>(); - private final List<ApiElement> mInterfaces = new ArrayList<ApiElement>(); + private final List<ApiElement> mSuperClasses = new ArrayList<>(); + private final List<ApiElement> mInterfaces = new ArrayList<>(); - private final Map<String, ApiElement> mFields = new HashMap<String, ApiElement>(); - private final Map<String, ApiElement> mMethods = new HashMap<String, ApiElement>(); + private final Map<String, ApiElement> mFields = new HashMap<>(); + private final Map<String, ApiElement> mMethods = new HashMap<>(); public ApiClass(String name, int version, boolean deprecated) { super(name, version, deprecated); @@ -52,6 +53,11 @@ public class ApiClass extends ApiElement { addToArray(mSuperClasses, superClass, since); } + @NotNull + List<ApiElement> getSuperClasses() { + return mSuperClasses; + } + public void addInterface(String interfaceClass, int since) { addToArray(mInterfaces, interfaceClass, since); } diff --git a/src/main/java/com/android/tools/metalava/apilevels/ApiElement.java b/src/main/java/com/android/tools/metalava/apilevels/ApiElement.java index a5b38dd443a0675b475b69f83e80cae03ee9ff1d..ab5af662b8523e679952620898f4e2c2b40037c8 100644 --- a/src/main/java/com/android/tools/metalava/apilevels/ApiElement.java +++ b/src/main/java/com/android/tools/metalava/apilevels/ApiElement.java @@ -15,7 +15,7 @@ */ package com.android.tools.metalava.apilevels; -import com.android.annotations.NonNull; +import org.jetbrains.annotations.NotNull; import java.io.PrintStream; import java.util.ArrayList; @@ -182,7 +182,7 @@ public class ApiElement implements Comparable<ApiElement> { } private <T extends ApiElement> List<T> sortedList(Collection<T> elements) { - List<T> list = new ArrayList<T>(elements); + List<T> list = new ArrayList<>(elements); Collections.sort(list); return list; } @@ -225,7 +225,7 @@ public class ApiElement implements Comparable<ApiElement> { } @Override - public int compareTo(@NonNull ApiElement other) { + public int compareTo(@NotNull ApiElement other) { return mName.compareTo(other.mName); } } diff --git a/src/main/java/com/android/tools/metalava/apilevels/ApiGenerator.java b/src/main/java/com/android/tools/metalava/apilevels/ApiGenerator.java index e3b3225028751c41f477ae1cde9d811f356d0d71..afcac41265720ddb4ba0b8472b12d6410880c822 100644 --- a/src/main/java/com/android/tools/metalava/apilevels/ApiGenerator.java +++ b/src/main/java/com/android/tools/metalava/apilevels/ApiGenerator.java @@ -16,7 +16,9 @@ package com.android.tools.metalava.apilevels; -import com.android.annotations.NonNull; +import com.android.tools.metalava.model.Codebase; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; @@ -93,10 +95,8 @@ public class ApiGenerator { } else if (arg.length() >= 2 && arg.substring(0, 2).equals("--")) { System.err.println("Unknown argument: " + arg); error = true; - } else if (outPath == null) { outPath = arg; - } else if (new File(arg).isDirectory()) { String pattern = arg; if (!pattern.endsWith(File.separator)) { @@ -104,7 +104,6 @@ public class ApiGenerator { } pattern += "platforms" + File.separator + "android-%" + File.separator + "android.jar"; patterns.add(pattern); - } else { System.err.println("Unknown argument: " + arg); error = true; @@ -137,7 +136,7 @@ public class ApiGenerator { } try { - if (!generate(minApi, currentApi, currentJar, patterns, outPath)) { + if (!generate(minApi, currentApi, currentJar, patterns, outPath, null)) { System.exit(1); } } catch (IOException e) { @@ -148,16 +147,19 @@ public class ApiGenerator { private static boolean generate(int minApi, int currentApi, - @NonNull File currentJar, - @NonNull List<String> patterns, - @NonNull String outPath) throws IOException { - AndroidJarReader reader = new AndroidJarReader(patterns, minApi, currentJar, currentApi); + @NotNull File currentJar, + @NotNull List<String> patterns, + @NotNull String outPath, + @Nullable Codebase codebase) throws IOException { + AndroidJarReader reader = new AndroidJarReader(patterns, minApi, currentJar, currentApi, codebase); Api api = reader.getApi(); return createApiFile(new File(outPath), api); } - public static boolean generate(@NonNull File[] apiLevels, @NonNull File outputFile) throws IOException { - AndroidJarReader reader = new AndroidJarReader(apiLevels); + public static boolean generate(@NotNull File[] apiLevels, + @NotNull File outputFile, + @Nullable Codebase codebase) throws IOException { + AndroidJarReader reader = new AndroidJarReader(apiLevels, codebase); Api api = reader.getApi(); return createApiFile(outputFile, api); } @@ -165,16 +167,16 @@ public class ApiGenerator { private static void printUsage() { System.err.println("\nGenerates a single API file from the content of an SDK."); System.err.println("Usage:"); - System.err.println("\tApiCheck [--min-api=1] OutFile [SdkFolder | --pattern sdk/%/android.jar]+"); + System.err.println("\tApiCheck [--min-api=1] OutFile [SdkFolder | --pattern sdk/%/public/android.jar]+"); System.err.println("Options:"); System.err.println("--min-api <int> : The first API level to consider (>=1)."); System.err.println("--pattern <pattern>: Path pattern to find per-API android.jar files, where\n" + - " '%' is replaced by the API level."); + " '%' is replaced by the API level."); System.err.println("--current-jar <path>: Path pattern to find the current android.jar"); System.err.println("--current-version <int>: The API level for the current API"); System.err.println("--current-codename <name>: REL, if a release, or codename for previews"); System.err.println("SdkFolder: if given, this adds the pattern\n" + - " '$SdkFolder/platforms/android-%/android.jar'"); + " '$SdkFolder/platforms/android-%/android.jar'"); System.err.println("If multiple --pattern are specified, they are tried in the order given.\n"); } @@ -185,26 +187,20 @@ public class ApiGenerator { * @param api the api to write */ private static boolean createApiFile(File outFile, Api api) { - PrintStream stream = null; - try { - File parentFile = outFile.getParentFile(); - if (!parentFile.exists()) { - boolean ok = parentFile.mkdirs(); - if (!ok) { - System.err.println("Could not create directory " + parentFile); - return false; - } + File parentFile = outFile.getParentFile(); + if (!parentFile.exists()) { + boolean ok = parentFile.mkdirs(); + if (!ok) { + System.err.println("Could not create directory " + parentFile); + return false; } - stream = new PrintStream(outFile, "UTF-8"); + } + try (PrintStream stream = new PrintStream(outFile, "UTF-8")) { stream.println("<?xml version=\"1.0\" encoding=\"utf-8\"?>"); api.print(stream); } catch (Exception e) { e.printStackTrace(); return false; - } finally { - if (stream != null) { - stream.close(); - } } return true; diff --git a/src/main/java/com/android/tools/metalava/doclava1/ApiFile.java b/src/main/java/com/android/tools/metalava/doclava1/ApiFile.java index 88bfbbb6233768af91eb0611f7e42eaa51b602ae..fca21ffe2b43970ed5a8ffdccf991a194dfbc1a2 100644 --- a/src/main/java/com/android/tools/metalava/doclava1/ApiFile.java +++ b/src/main/java/com/android/tools/metalava/doclava1/ApiFile.java @@ -16,7 +16,6 @@ package com.android.tools.metalava.doclava1; -import com.android.tools.lint.annotations.Extractor; import com.android.tools.lint.checks.infrastructure.ClassNameKt; import com.android.tools.metalava.model.AnnotationItem; import com.android.tools.metalava.model.TypeParameterList; @@ -38,6 +37,8 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import static com.android.tools.metalava.ConstantsKt.ANDROIDX_NOTNULL; +import static com.android.tools.metalava.ConstantsKt.ANDROIDX_NULLABLE; import static com.android.tools.metalava.ConstantsKt.JAVA_LANG_ANNOTATION; import static com.android.tools.metalava.ConstantsKt.JAVA_LANG_ENUM; import static com.android.tools.metalava.ConstantsKt.JAVA_LANG_STRING; @@ -89,7 +90,7 @@ public class ApiFile { } private static void parsePackage(TextCodebase api, Tokenizer tokenizer) - throws ApiParseException { + throws ApiParseException { String token; String name; TextPackageItem pkg; @@ -114,7 +115,7 @@ public class ApiFile { } private static void parseClass(TextCodebase api, TextPackageItem pkg, Tokenizer tokenizer, String token) - throws ApiParseException { + throws ApiParseException { boolean isPublic = false; boolean isProtected = false; boolean isPrivate = false; @@ -217,9 +218,9 @@ public class ApiFile { token = tokenizer.requireToken(); cl = new TextClassItem(api, tokenizer.pos(), isPublic, isProtected, - isPrivate, internal, isStatic, isInterface, isAbstract, isEnum, isAnnotation, - isFinal, sealed, typeInfo.toErasedTypeString(), typeInfo.qualifiedTypeName(), - rawName, annotations); + isPrivate, internal, isStatic, isInterface, isAbstract, isEnum, isAnnotation, + isFinal, sealed, typeInfo.toErasedTypeString(), typeInfo.qualifiedTypeName(), + rawName, annotations); cl.setContainingPackage(pkg); cl.setTypeInfo(typeInfo); cl.setDeprecated(isDeprecated); @@ -282,17 +283,17 @@ public class ApiFile { if (api.getKotlinStyleNulls()) { if (token.endsWith("?")) { token = token.substring(0, token.length() - 1); - annotations = mergeAnnotations(annotations, Extractor.SUPPORT_NULLABLE); + annotations = mergeAnnotations(annotations, ANDROIDX_NULLABLE); } else if (token.endsWith("!")) { token = token.substring(0, token.length() - 1); } else if (!token.endsWith("!")) { if (!TextTypeItem.Companion.isPrimitive(token)) { // Don't add nullness on primitive types like void - annotations = mergeAnnotations(annotations, Extractor.SUPPORT_NOTNULL); + annotations = mergeAnnotations(annotations, ANDROIDX_NOTNULL); } } } else if (token.endsWith("?") || token.endsWith("!")) { throw new ApiParseException("Did you forget to supply --input-kotlin-nulls? Found Kotlin-style null type suffix when parser was not configured " + - "to interpret signature file that way: " + token); + "to interpret signature file that way: " + token); } //noinspection unchecked return new Pair<>(token, annotations); @@ -337,7 +338,7 @@ public class ApiFile { } private static void parseConstructor(TextCodebase api, Tokenizer tokenizer, TextClassItem cl, String token) - throws ApiParseException { + throws ApiParseException { boolean isPublic = false; boolean isProtected = false; boolean isPrivate = false; @@ -389,12 +390,12 @@ public class ApiFile { throw new ApiParseException("expected (", tokenizer.getLine()); } method = new TextConstructorItem(api, /*typeParameters*/ - name, /*signature*/ cl, isPublic, isProtected, isPrivate, isInternal, false/*isFinal*/, - false/*isStatic*/, /*isSynthetic*/ false/*isAbstract*/, false/*isSynthetic*/, - false/*isNative*/, false/* isDefault */, - /*isAnnotationElement*/ /*flatSignature*/ - /*overriddenMethod*/ cl.asTypeInfo(), - /*thrownExceptions*/ tokenizer.pos(), annotations); + name, /*signature*/ cl, isPublic, isProtected, isPrivate, isInternal, false/*isFinal*/, + false/*isStatic*/, /*isSynthetic*/ false/*isAbstract*/, false/*isSynthetic*/, + false/*isNative*/, false/* isDefault */, + /*isAnnotationElement*/ /*flatSignature*/ + /*overriddenMethod*/ cl.asTypeInfo(), + /*thrownExceptions*/ tokenizer.pos(), annotations); method.setDeprecated(isDeprecated); token = tokenizer.requireToken(); parseParameterList(api, tokenizer, method, /*new HashSet<String>(),*/ token); @@ -409,7 +410,7 @@ public class ApiFile { } private static void parseMethod(TextCodebase api, Tokenizer tokenizer, TextClassItem cl, String token) - throws ApiParseException { + throws ApiParseException { boolean isPublic = false; boolean isProtected = false; boolean isPrivate = false; @@ -514,10 +515,10 @@ public class ApiFile { assertIdent(tokenizer, token); name = token; method = new TextMethodItem( - api, name, /*signature*/ cl, - isPublic, isProtected, isPrivate, isInternal, isFinal, isStatic, isAbstract/*isAbstract*/, - isSynchronized, false/*isNative*/, isDefault/*isDefault*/, isInfix, isOperator, isInline, - returnType, tokenizer.pos(), annotations); + api, name, /*signature*/ cl, + isPublic, isProtected, isPrivate, isInternal, isFinal, isStatic, isAbstract/*isAbstract*/, + isSynchronized, false/*isNative*/, isDefault/*isDefault*/, isInfix, isOperator, isInline, + returnType, tokenizer.pos(), annotations); method.setDeprecated(isDeprecated); method.setTypeParameterList(typeParameterList); token = tokenizer.requireToken(); @@ -542,15 +543,15 @@ public class ApiFile { } // Reverse effect of TypeItem.shortenTypes(...) String qualifiedName = annotation.indexOf('.') == -1 - ? "@android.support.annotation" + annotation - : "@" + annotation; + ? "@androidx.annotation" + annotation + : "@" + annotation; annotations.add(qualifiedName); return annotations; } private static void parseField(TextCodebase api, Tokenizer tokenizer, TextClassItem cl, String token, boolean isEnum) - throws ApiParseException { + throws ApiParseException { boolean isPublic = false; boolean isProtected = false; boolean isPrivate = false; @@ -646,8 +647,8 @@ public class ApiFile { } field = new TextFieldItem(api, name, cl, isPublic, isProtected, isPrivate, isInternal, isFinal, isStatic, - isTransient, isVolatile, typeInfo, v, tokenizer.pos(), - annotations); + isTransient, isVolatile, typeInfo, v, tokenizer.pos(), + annotations); field.setDeprecated(isDeprecated); if (isEnum) { cl.addEnumConstant(field); @@ -788,10 +789,10 @@ public class ApiFile { } method.addParameter(new TextParameterItem(api, method, name, publicName, defaultValue, index, type, - typeInfo, - vararg || type.endsWith("..."), - tokenizer.pos(), - annotations)); + typeInfo, + vararg || type.endsWith("..."), + tokenizer.pos(), + annotations)); if (type.endsWith("...")) { method.setVarargs(true); } @@ -800,7 +801,7 @@ public class ApiFile { } private static String parseThrows(Tokenizer tokenizer, TextMethodItem method) - throws ApiParseException { + throws ApiParseException { String token = tokenizer.requireToken(); boolean comma = true; while (true) { @@ -967,7 +968,7 @@ public class ApiFile { } } } while (mPos < mBuf.length - && ((!isSpace(mBuf[mPos]) && !isSeparator(mBuf[mPos], parenIsSep)) || genericDepth != 0)); + && ((!isSpace(mBuf[mPos]) && !isSeparator(mBuf[mPos], parenIsSep)) || genericDepth != 0)); if (mPos >= mBuf.length) { throw new ApiParseException("Unexpected end of file for \" starting at " + line, mLine); } @@ -994,9 +995,6 @@ public class ApiFile { } private static boolean isIdent(char c) { - if (c == '"' || isSeparator(c, true)) { - return false; - } - return true; + return c != '"' && !isSeparator(c, true); } } diff --git a/src/main/java/com/android/tools/metalava/doclava1/ApiPredicate.kt b/src/main/java/com/android/tools/metalava/doclava1/ApiPredicate.kt index dfbcaeeda6ddba1153602f82f7d2269164fddd7f..bdace7245bfbc76e95c72fbfc978dd70d7ec289f 100644 --- a/src/main/java/com/android/tools/metalava/doclava1/ApiPredicate.kt +++ b/src/main/java/com/android/tools/metalava/doclava1/ApiPredicate.kt @@ -48,7 +48,10 @@ class ApiPredicate( private val matchRemoved: Boolean = false, /** Whether we allow matching items loaded from jar files instead of sources */ - private val allowFromJar: Boolean = true + private val allowFromJar: Boolean = true, + + /** Whether we should include doc-only items */ + private val includeDocOnly: Boolean = false ) : Predicate<Item> { override fun test(member: Item): Boolean { @@ -94,7 +97,7 @@ class ApiPredicate( removed = matchRemoved } - if (docOnly && options.includeDocOnly) { + if (docOnly && includeDocOnly) { docOnly = false } diff --git a/src/main/java/com/android/tools/metalava/doclava1/ElidingPredicate.kt b/src/main/java/com/android/tools/metalava/doclava1/ElidingPredicate.kt index 3f7c9d741da6cb1e150508c3c39d6e11c2787d74..6d091e24e5571b6d3c44ad87b5037af18589573e 100644 --- a/src/main/java/com/android/tools/metalava/doclava1/ElidingPredicate.kt +++ b/src/main/java/com/android/tools/metalava/doclava1/ElidingPredicate.kt @@ -20,8 +20,8 @@ class ElidingPredicate(private val wrapped: Predicate<Item>) : Predicate<Item> { val differentSuper = method.findPredicateSuperMethod(Predicate { test -> // We're looking for included and perfect signature wrapped.test(test) && - test is MethodItem && - MethodItem.sameSignature(method, test, false) + test is MethodItem && + MethodItem.sameSignature(method, test, false) }) differentSuper == null } else { diff --git a/src/main/java/com/android/tools/metalava/doclava1/Errors.java b/src/main/java/com/android/tools/metalava/doclava1/Errors.java index 625d865e8728156e0d25de96f4047cbd16931fbe..93ceee19c9cf534a4bc7046913c116e845048472 100644 --- a/src/main/java/com/android/tools/metalava/doclava1/Errors.java +++ b/src/main/java/com/android/tools/metalava/doclava1/Errors.java @@ -15,9 +15,9 @@ */ package com.android.tools.metalava.doclava1; -import com.android.annotations.Nullable; import com.android.tools.metalava.Severity; import com.google.common.base.Splitter; +import org.jetbrains.annotations.Nullable; import java.lang.reflect.Field; import java.util.ArrayList; @@ -179,7 +179,7 @@ public class Errors { // Metalava new warnings (not from doclava) - public static final Error TYPO = new Error(131, LINT); + public static final Error TYPO = new Error(131, WARNING); public static final Error MISSING_PERMISSION = new Error(132, LINT); public static final Error MULTIPLE_THREAD_ANNOTATIONS = new Error(133, LINT); public static final Error UNRESOLVED_CLASS = new Error(134, LINT); @@ -194,6 +194,9 @@ public class Errors { public static final Error MISSING_JVMSTATIC = new Error(143, WARNING); public static final Error DEFAULT_VALUE_CHANGE = new Error(144, ERROR); public static final Error DOCUMENT_EXCEPTIONS = new Error(145, ERROR); + public static final Error ANNOTATION_EXTRACTION = new Error(146, ERROR); + public static final Error SUPERFLUOUS_PREFIX = new Error(147, WARNING); + public static final Error HIDDEN_TYPEDEF_CONSTANT = new Error(148, ERROR); static { // Attempt to initialize error names based on the field names diff --git a/src/main/java/com/android/tools/metalava/doclava1/FilterPredicate.kt b/src/main/java/com/android/tools/metalava/doclava1/FilterPredicate.kt index 6fc75576ff75a21191507bdb2a76bb2b85921cd3..084c655832666489d3f55c4c84a3a4711c030f90 100644 --- a/src/main/java/com/android/tools/metalava/doclava1/FilterPredicate.kt +++ b/src/main/java/com/android/tools/metalava/doclava1/FilterPredicate.kt @@ -28,7 +28,7 @@ class FilterPredicate(private val wrapped: Predicate<Item>) : Predicate<Item> { return when { wrapped.test(method) -> true method is MethodItem -> !method.isConstructor() && - method.findPredicateSuperMethod(wrapped) != null + method.findPredicateSuperMethod(wrapped) != null else -> false } } diff --git a/src/main/java/com/android/tools/metalava/doclava1/SourcePositionInfo.java b/src/main/java/com/android/tools/metalava/doclava1/SourcePositionInfo.java index 015533ca054f5557a55d444753a31445f89a1ac7..a7f098e1eb9c1aeb2d64d58831783e8e0d9e3b06 100644 --- a/src/main/java/com/android/tools/metalava/doclava1/SourcePositionInfo.java +++ b/src/main/java/com/android/tools/metalava/doclava1/SourcePositionInfo.java @@ -16,7 +16,7 @@ package com.android.tools.metalava.doclava1; -import com.android.annotations.NonNull; +import org.jetbrains.annotations.NotNull; // Copied from doclava1 public class SourcePositionInfo implements Comparable { @@ -55,7 +55,7 @@ public class SourcePositionInfo implements Comparable { return file + ':' + line; } - public int compareTo(@NonNull Object o) { + public int compareTo(@NotNull Object o) { SourcePositionInfo that = (SourcePositionInfo) o; int r = this.file.compareTo(that.file); if (r != 0) return r; diff --git a/src/main/java/com/android/tools/metalava/doclava1/TextCodebase.kt b/src/main/java/com/android/tools/metalava/doclava1/TextCodebase.kt index 0e73a535f98bfb18ccb45c18544ac328dd0f98ec..2b66cf62cd5be635e168ec1403a0c78cec76f358 100644 --- a/src/main/java/com/android/tools/metalava/doclava1/TextCodebase.kt +++ b/src/main/java/com/android/tools/metalava/doclava1/TextCodebase.kt @@ -16,7 +16,6 @@ package com.android.tools.metalava.doclava1 -import com.android.annotations.NonNull import com.android.tools.metalava.CodebaseComparator import com.android.tools.metalava.ComparisonVisitor import com.android.tools.metalava.JAVA_LANG_ANNOTATION @@ -70,7 +69,7 @@ class TextCodebase : DefaultCodebase() { return mPackages.size } - override fun findClass(@NonNull className: String): TextClassItem? { + override fun findClass(className: String): TextClassItem? { return mAllClasses[className] } diff --git a/src/main/java/com/android/tools/metalava/model/AnnotationItem.kt b/src/main/java/com/android/tools/metalava/model/AnnotationItem.kt index a21f0f4b2c124599609a1676bdf8a702b0d7e469..f87833fa6ce1f19d9924e897c07bce92feb618ae 100644 --- a/src/main/java/com/android/tools/metalava/model/AnnotationItem.kt +++ b/src/main/java/com/android/tools/metalava/model/AnnotationItem.kt @@ -18,10 +18,15 @@ package com.android.tools.metalava.model import com.android.SdkConstants import com.android.SdkConstants.ATTR_VALUE +import com.android.SdkConstants.INT_DEF_ANNOTATION +import com.android.SdkConstants.LONG_DEF_ANNOTATION +import com.android.SdkConstants.STRING_DEF_ANNOTATION +import com.android.tools.lint.annotations.Extractor.ANDROID_INT_DEF +import com.android.tools.lint.annotations.Extractor.ANDROID_LONG_DEF +import com.android.tools.lint.annotations.Extractor.ANDROID_STRING_DEF +import com.android.tools.metalava.ANDROIDX_ANNOTATION_PREFIX import com.android.tools.metalava.ANDROID_SUPPORT_ANNOTATION_PREFIX import com.android.tools.metalava.JAVA_LANG_PREFIX -import com.android.tools.metalava.NEWLY_NONNULL -import com.android.tools.metalava.NEWLY_NULLABLE import com.android.tools.metalava.Options import com.android.tools.metalava.RECENTLY_NONNULL import com.android.tools.metalava.RECENTLY_NULLABLE @@ -34,8 +39,8 @@ fun isNullableAnnotation(qualifiedName: String): Boolean { fun isNonNullAnnotation(qualifiedName: String): Boolean { return qualifiedName.endsWith("NonNull") || - qualifiedName.endsWith("NotNull") || - qualifiedName.endsWith("Nonnull") + qualifiedName.endsWith("NotNull") || + qualifiedName.endsWith("Nonnull") } interface AnnotationItem { @@ -49,7 +54,7 @@ interface AnnotationItem { /** Whether this annotation is significant and should be included in signature files, stubs, etc */ fun isSignificant(): Boolean { - return isSignificantAnnotation(qualifiedName() ?: return false) + return includeInSignatures(qualifiedName() ?: return false) } /** Attributes of the annotation (may be empty) */ @@ -70,6 +75,17 @@ interface AnnotationItem { return isNonNullAnnotation(qualifiedName() ?: return false) } + /** True if this annotation represents @IntDef, @LongDef or @StringDef */ + fun isTypeDefAnnotation(): Boolean { + val name = qualifiedName() ?: return false + return (INT_DEF_ANNOTATION.isEquals(name) || + STRING_DEF_ANNOTATION.isEquals(name) || + LONG_DEF_ANNOTATION.isEquals(name) || + ANDROID_INT_DEF == name || + ANDROID_STRING_DEF == name || + ANDROID_LONG_DEF == name) + } + /** * True if this annotation represents a @ParameterName annotation (or some synonymous annotation). * The parameter name should be the default attribute or "value". @@ -99,8 +115,20 @@ interface AnnotationItem { companion object { /** Whether the given annotation name is "significant", e.g. should be included in signature files */ - fun isSignificantAnnotation(qualifiedName: String?): Boolean { - return qualifiedName?.startsWith(ANDROID_SUPPORT_ANNOTATION_PREFIX) ?: false + fun includeInSignatures(qualifiedName: String?): Boolean { + qualifiedName ?: return false + if (qualifiedName.startsWith(ANDROID_SUPPORT_ANNOTATION_PREFIX) || + qualifiedName.startsWith(ANDROIDX_ANNOTATION_PREFIX) + ) { + + // Don't include typedefs in the stub files. + if (qualifiedName.endsWith("IntDef") || qualifiedName.endsWith("StringDef")) { + return false + } + + return true + } + return false } /** The simple name of an annotation, which is the annotation name (not qualified name) prefixed by @ */ @@ -118,63 +146,110 @@ interface AnnotationItem { when (qualifiedName) { // Resource annotations - "android.annotation.AnimRes" -> return "android.support.annotation.AnimRes" - "android.annotation.AnimatorRes" -> return "android.support.annotation.AnimatorRes" - "android.annotation.AnyRes" -> return "android.support.annotation.AnyRes" - "android.annotation.ArrayRes" -> return "android.support.annotation.ArrayRes" - "android.annotation.AttrRes" -> return "android.support.annotation.AttrRes" - "android.annotation.BoolRes" -> return "android.support.annotation.BoolRes" - "android.annotation.ColorRes" -> return "android.support.annotation.ColorRes" - "android.annotation.DimenRes" -> return "android.support.annotation.DimenRes" - "android.annotation.DrawableRes" -> return "android.support.annotation.DrawableRes" - "android.annotation.FontRes" -> return "android.support.annotation.FontRes" - "android.annotation.FractionRes" -> return "android.support.annotation.FractionRes" - "android.annotation.IdRes" -> return "android.support.annotation.IdRes" - "android.annotation.IntegerRes" -> return "android.support.annotation.IntegerRes" - "android.annotation.InterpolatorRes" -> return "android.support.annotation.InterpolatorRes" - "android.annotation.LayoutRes" -> return "android.support.annotation.LayoutRes" - "android.annotation.MenuRes" -> return "android.support.annotation.MenuRes" - "android.annotation.PluralsRes" -> return "android.support.annotation.PluralsRes" - "android.annotation.RawRes" -> return "android.support.annotation.RawRes" - "android.annotation.StringRes" -> return "android.support.annotation.StringRes" - "android.annotation.StyleRes" -> return "android.support.annotation.StyleRes" - "android.annotation.StyleableRes" -> return "android.support.annotation.StyleableRes" - "android.annotation.TransitionRes" -> return "android.support.annotation.TransitionRes" - "android.annotation.XmlRes" -> return "android.support.annotation.XmlRes" + "android.support.annotation.AnimRes", + "android.annotation.AnimRes" -> return "androidx.annotation.AnimRes" + "android.support.annotation.AnimatorRes", + "android.annotation.AnimatorRes" -> return "androidx.annotation.AnimatorRes" + "android.support.annotation.AnyRes", + "android.annotation.AnyRes" -> return "androidx.annotation.AnyRes" + "android.support.annotation.ArrayRes", + "android.annotation.ArrayRes" -> return "androidx.annotation.ArrayRes" + "android.support.annotation.AttrRes", + "android.annotation.AttrRes" -> return "androidx.annotation.AttrRes" + "android.support.annotation.BoolRes", + "android.annotation.BoolRes" -> return "androidx.annotation.BoolRes" + "android.support.annotation.ColorRes", + "android.annotation.ColorRes" -> return "androidx.annotation.ColorRes" + "android.support.annotation.DimenRes", + "android.annotation.DimenRes" -> return "androidx.annotation.DimenRes" + "android.support.annotation.DrawableRes", + "android.annotation.DrawableRes" -> return "androidx.annotation.DrawableRes" + "android.support.annotation.FontRes", + "android.annotation.FontRes" -> return "androidx.annotation.FontRes" + "android.support.annotation.FractionRes", + "android.annotation.FractionRes" -> return "androidx.annotation.FractionRes" + "android.support.annotation.IdRes", + "android.annotation.IdRes" -> return "androidx.annotation.IdRes" + "android.support.annotation.IntegerRes", + "android.annotation.IntegerRes" -> return "androidx.annotation.IntegerRes" + "android.support.annotation.InterpolatorRes", + "android.annotation.InterpolatorRes" -> return "androidx.annotation.InterpolatorRes" + "android.support.annotation.LayoutRes", + "android.annotation.LayoutRes" -> return "androidx.annotation.LayoutRes" + "android.support.annotation.MenuRes", + "android.annotation.MenuRes" -> return "androidx.annotation.MenuRes" + "android.support.annotation.PluralsRes", + "android.annotation.PluralsRes" -> return "androidx.annotation.PluralsRes" + "android.support.annotation.RawRes", + "android.annotation.RawRes" -> return "androidx.annotation.RawRes" + "android.support.annotation.StringRes", + "android.annotation.StringRes" -> return "androidx.annotation.StringRes" + "android.support.annotation.StyleRes", + "android.annotation.StyleRes" -> return "androidx.annotation.StyleRes" + "android.support.annotation.StyleableRes", + "android.annotation.StyleableRes" -> return "androidx.annotation.StyleableRes" + "android.support.annotation.TransitionRes", + "android.annotation.TransitionRes" -> return "androidx.annotation.TransitionRes" + "android.support.annotation.XmlRes", + "android.annotation.XmlRes" -> return "androidx.annotation.XmlRes" // Threading - "android.annotation.AnyThread" -> return "android.support.annotation.AnyThread" - "android.annotation.BinderThread" -> return "android.support.annotation.BinderThread" - "android.annotation.MainThread" -> return "android.support.annotation.MainThread" - "android.annotation.UiThread" -> return "android.support.annotation.UiThread" - "android.annotation.WorkerThread" -> return "android.support.annotation.WorkerThread" + "android.support.annotation.AnyThread", + "android.annotation.AnyThread" -> return "androidx.annotation.AnyThread" + "android.support.annotation.BinderThread", + "android.annotation.BinderThread" -> return "androidx.annotation.BinderThread" + "android.support.annotation.MainThread", + "android.annotation.MainThread" -> return "androidx.annotation.MainThread" + "android.support.annotation.UiThread", + "android.annotation.UiThread" -> return "androidx.annotation.UiThread" + "android.support.annotation.WorkerThread", + "android.annotation.WorkerThread" -> return "androidx.annotation.WorkerThread" // Colors - "android.annotation.ColorInt" -> return "android.support.annotation.ColorInt" - "android.annotation.ColorLong" -> return "android.support.annotation.ColorLong" - "android.annotation.HalfFloat" -> return "android.support.annotation.HalfFloat" + "android.support.annotation.ColorInt", + "android.annotation.ColorInt" -> return "androidx.annotation.ColorInt" + "android.support.annotation.ColorLong", + "android.annotation.ColorLong" -> return "androidx.annotation.ColorLong" + "android.support.annotation.HalfFloat", + "android.annotation.HalfFloat" -> return "androidx.annotation.HalfFloat" // Ranges and sizes - "android.annotation.FloatRange" -> return "android.support.annotation.FloatRange" - "android.annotation.IntRange" -> return "android.support.annotation.IntRange" - "android.annotation.Size" -> return "android.support.annotation.Size" - "android.annotation.Px" -> return "android.support.annotation.Px" - "android.annotation.Dimension" -> return "android.support.annotation.Dimension" + "android.support.annotation.FloatRange", + "android.annotation.FloatRange" -> return "androidx.annotation.FloatRange" + "android.support.annotation.IntRange", + "android.annotation.IntRange" -> return "androidx.annotation.IntRange" + "android.support.annotation.Size", + "android.annotation.Size" -> return "androidx.annotation.Size" + "android.support.annotation.Px", + "android.annotation.Px" -> return "androidx.annotation.Px" + "android.support.annotation.Dimension", + "android.annotation.Dimension" -> return "androidx.annotation.Dimension" // Null - "android.annotation.NonNull" -> return "android.support.annotation.NonNull" - "android.annotation.Nullable" -> return "android.support.annotation.Nullable" - "libcore.util.NonNull" -> return "android.support.annotation.NonNull" - "libcore.util.Nullable" -> return "android.support.annotation.Nullable" + "android.support.annotation.NonNull", + "android.annotation.NonNull" -> return "androidx.annotation.NonNull" + "android.support.annotation.Nullable", + "android.annotation.Nullable" -> return "androidx.annotation.Nullable" + "libcore.util.NonNull" -> return "androidx.annotation.NonNull" + "libcore.util.Nullable" -> return "androidx.annotation.Nullable" + "org.jetbrains.annotations.NotNull" -> return "androidx.annotation.NonNull" + "org.jetbrains.annotations.Nullable" -> return "androidx.annotation.Nullable" // Typedefs - "android.annotation.IntDef" -> return "android.support.annotation.IntDef" - "android.annotation.StringDef" -> return "android.support.annotation.StringDef" + "android.support.annotation.IntDef", + "android.annotation.IntDef" -> return "androidx.annotation.IntDef" + "android.support.annotation.StringDef", + "android.annotation.StringDef" -> return "androidx.annotation.StringDef" + "android.support.annotation.LongDef", + "android.annotation.LongDef" -> return "androidx.annotation.LongDef" // Misc - "android.annotation.CallSuper" -> return "android.support.annotation.CallSuper" - "android.annotation.CheckResult" -> return "android.support.annotation.CheckResult" - "android.annotation.RequiresPermission" -> return "android.support.annotation.RequiresPermission" + "android.support.annotation.CallSuper", + "android.annotation.CallSuper" -> return "androidx.annotation.CallSuper" + "android.support.annotation.CheckResult", + "android.annotation.CheckResult" -> return "androidx.annotation.CheckResult" + "android.support.annotation.RequiresPermission", + "android.annotation.RequiresPermission" -> return "androidx.annotation.RequiresPermission" // These aren't support annotations, but could/should be: "android.annotation.CurrentTimeMillisLong", @@ -218,8 +293,8 @@ interface AnnotationItem { "android.annotation.SuppressLint" -> return qualifiedName // We only change recently/newly nullable annotation if the codebase supports it - NEWLY_NULLABLE, RECENTLY_NULLABLE -> return if (codebase.supportsStagedNullability) qualifiedName else "android.support.annotation.Nullable" - NEWLY_NONNULL, RECENTLY_NONNULL -> return if (codebase.supportsStagedNullability) qualifiedName else "android.support.annotation.NonNull" + RECENTLY_NULLABLE -> return if (codebase.supportsStagedNullability) qualifiedName else "androidx.annotation.Nullable" + RECENTLY_NONNULL -> return if (codebase.supportsStagedNullability) qualifiedName else "androidx.annotation.NonNull" else -> { // Some new annotations added to the platform: assume they are support annotations? @@ -229,11 +304,11 @@ interface AnnotationItem { "kotlin.annotations.jvm.internal${qualifiedName.substring(qualifiedName.lastIndexOf('.'))}" // Other third party nullness annotations? - isNullableAnnotation(qualifiedName) -> "android.support.annotation.Nullable" - isNonNullAnnotation(qualifiedName) -> "android.support.annotation.NonNull" + isNullableAnnotation(qualifiedName) -> "androidx.annotation.Nullable" + isNonNullAnnotation(qualifiedName) -> "androidx.annotation.NonNull" // Support library annotations are all included, as is the built-in stuff like @Retention - qualifiedName.startsWith(ANDROID_SUPPORT_ANNOTATION_PREFIX) -> return qualifiedName + qualifiedName.startsWith(ANDROIDX_ANNOTATION_PREFIX) -> return qualifiedName qualifiedName.startsWith(JAVA_LANG_PREFIX) -> return qualifiedName // Unknown Android platform annotations @@ -246,6 +321,14 @@ interface AnnotationItem { } } + qualifiedName.startsWith(ANDROID_SUPPORT_ANNOTATION_PREFIX) -> { + return mapName( + codebase, + ANDROIDX_ANNOTATION_PREFIX + qualifiedName.substring(ANDROID_SUPPORT_ANNOTATION_PREFIX.length), + filter + ) + } + else -> { // Remove, unless (a) public or (b) specifically included in --showAnnotations return if (options.showAnnotations.contains(qualifiedName)) { @@ -271,8 +354,8 @@ interface AnnotationItem { * This is intended to be used by the [Options.omitCommonPackages] flag * to reduce clutter in signature files. * - * For example, this method will convert `@android.support.annotation.Nullable` to just - * `@Nullable`, and `@android.support.annotation.IntRange(from=20)` to `IntRange(from=20)`. + * For example, this method will convert `@androidx.annotation.Nullable` to just + * `@Nullable`, and `@androidx.annotation.IntRange(from=20)` to `IntRange(from=20)`. */ fun shortenAnnotation(source: String): String { return when { @@ -282,6 +365,9 @@ interface AnnotationItem { source.startsWith(ANDROID_SUPPORT_ANNOTATION_PREFIX, 1) -> { "@" + source.substring("@android.support.annotation.".length) } + source.startsWith(ANDROIDX_ANNOTATION_PREFIX, 1) -> { + "@" + source.substring("@androidx.annotation.".length) + } else -> source } } @@ -294,11 +380,11 @@ interface AnnotationItem { return when { // These 3 annotations are in the android.annotation. package, not android.support.annotation source.startsWith("@SystemService") || - source.startsWith("@TargetApi") || - source.startsWith("@SuppressLint") -> + source.startsWith("@TargetApi") || + source.startsWith("@SuppressLint") -> "@android.annotation." + source.substring(1) else -> { - "@android.support.annotation." + source.substring(1) + "@androidx.annotation." + source.substring(1) } } } @@ -381,8 +467,9 @@ class DefaultAnnotationAttribute( fun createList(source: String): List<AnnotationAttribute> { val list = mutableListOf<AnnotationAttribute>() if (source.contains("{")) { - assert(source.indexOf('{', source.indexOf('{', source.indexOf('{') + 1) + 1) != -1, - { "Multiple arrays not supported: $source" }) + assert( + source.indexOf('{', source.indexOf('{', source.indexOf('{') + 1) + 1) != -1 + ) { "Multiple arrays not supported: $source" } val split = source.indexOf('=') val name: String val value: String @@ -456,7 +543,7 @@ class DefaultAnnotationSingleAttributeValue(override val valueSource: String) : class DefaultAnnotationArrayAttributeValue(val value: String) : DefaultAnnotationValue(), AnnotationArrayAttributeValue { init { - assert(value.startsWith("{") && value.endsWith("}"), { value }) + assert(value.startsWith("{") && value.endsWith("}")) { value } } override val values = value.substring(1, value.length - 1).split(",").map { diff --git a/src/main/java/com/android/tools/metalava/model/ClassItem.kt b/src/main/java/com/android/tools/metalava/model/ClassItem.kt index 328d5aff4d830a7cd1e317b8b5bc088ed659a841..de95759c2edb3ac256576d2fef62744e316742c2 100644 --- a/src/main/java/com/android/tools/metalava/model/ClassItem.kt +++ b/src/main/java/com/android/tools/metalava/model/ClassItem.kt @@ -83,7 +83,7 @@ interface ClassItem : Item { } return curr.containingPackage().qualifiedName().replace('.', '/') + "/" + - fullName().replace('.', '$') + fullName().replace('.', '$') } /** The super class of this class, if any */ @@ -216,6 +216,12 @@ interface ClassItem : Item { var hasPrivateConstructor: Boolean + /** + * Maven artifact of this class, if any. (Not used for the Android SDK, but used in + * for example support libraries. + */ + var artifact: String? + override fun accept(visitor: ItemVisitor) { if (visitor is ApiVisitor) { accept(visitor) @@ -381,6 +387,37 @@ interface ClassItem : Item { return null } + /** Finds a given method in this class matching the VM name signature */ + fun findMethodByDesc( + name: String, + desc: String, + includeSuperClasses: Boolean = false, + includeInterfaces: Boolean = false + ): MethodItem? { + if (desc.startsWith("<init>")) { + constructors().asSequence() + .filter { it.internalDesc() == desc } + .forEach { return it } + return null + } else { + methods().asSequence() + .filter { it.name() == name && it.internalDesc() == desc } + .forEach { return it } + } + + if (includeSuperClasses) { + superClass()?.findMethodByDesc(name, desc, true, includeInterfaces)?.let { return it } + } + + if (includeInterfaces) { + for (itf in interfaceTypes()) { + val cls = itf.asClass() ?: continue + cls.findMethodByDesc(name, desc, includeSuperClasses, true)?.let { return it } + } + } + return null + } + fun findConstructor(template: ConstructorItem): ConstructorItem? { constructors().asSequence() .filter { it.matches(template) } @@ -447,7 +484,6 @@ interface ClassItem : Item { return true } - /** Returns the corresponding compilation unit, if any */ fun getCompilationUnit(): CompilationUnit? = null @@ -497,8 +533,8 @@ interface ClassItem : Item { val methods = LinkedHashSet<MethodItem>() for (method in methods()) { if (predicate.test(method) || method.findPredicateSuperMethod(predicate) != null) { - //val duplicated = method.duplicate(this) - //methods.add(duplicated) + // val duplicated = method.duplicate(this) + // methods.add(duplicated) methods.remove(method) methods.add(method) } @@ -616,6 +652,10 @@ interface ClassItem : Item { } fun allInnerClasses(includeSelf: Boolean = false): Sequence<ClassItem> { + if (!includeSelf && innerClasses().isEmpty()) { + return emptySequence() + } + val list = ArrayList<ClassItem>() if (includeSelf) { list.add(this) @@ -689,13 +729,14 @@ class VisitCandidate(private val cls: ClassItem, private val visitor: ApiVisitor cls.filteredFields(filterEmit).asSequence() } else { cls.fields().asSequence() + .filter { filterEmit.test(it) } } if (cls.isEnum()) { fields = fieldSequence - .filter({ !it.isEnumConstant() }) + .filter { !it.isEnumConstant() } .sortedWith(FieldItem.comparator) enums = fieldSequence - .filter({ it.isEnumConstant() }) + .filter { it.isEnumConstant() } .filter { filterEmit.test(it) } .sortedWith(FieldItem.comparator) } else { @@ -744,8 +785,7 @@ class VisitCandidate(private val cls: ClassItem, private val visitor: ApiVisitor return } - val emitClass = emitClass() - + val emitClass = if (visitor.includeEmptyOuterClasses) emit() else emitClass() if (emitClass) { if (!visitor.visitingPackage) { visitor.visitingPackage = true @@ -789,7 +829,7 @@ class VisitCandidate(private val cls: ClassItem, private val visitor: ApiVisitor } } - if (visitor.nestInnerClasses) { // otherwise done below + if (visitor.nestInnerClasses) { // otherwise done below innerClasses.forEach { it.accept() } } diff --git a/src/main/java/com/android/tools/metalava/model/Codebase.kt b/src/main/java/com/android/tools/metalava/model/Codebase.kt index c18e047c470ff4dc7e43290673ffa901ea4fcc02..41ce549108afbf75be9267799c514b381b8254ef 100644 --- a/src/main/java/com/android/tools/metalava/model/Codebase.kt +++ b/src/main/java/com/android/tools/metalava/model/Codebase.kt @@ -44,6 +44,9 @@ interface Codebase { /** Description of what this codebase is (useful during debugging) */ var description: String + /** The API level of this codebase, or -1 if not known */ + var apiLevel: Int + /** The packages in the codebase (may include packages that are not included in the API) */ fun getPackages(): PackageList @@ -92,7 +95,8 @@ interface Codebase { * Creates an annotation item for the given (fully qualified) Java source */ fun createAnnotation( - @Language("JAVA") source: String, context: Item? = null, + @Language("JAVA") source: String, + context: Item? = null, mapName: Boolean = true ): AnnotationItem = TextBackedAnnotationItem( this, source, mapName @@ -153,11 +157,13 @@ abstract class DefaultCodebase : Codebase { override var original: Codebase? = null override var supportsStagedNullability: Boolean = false override var units: List<PsiFile> = emptyList() + override var apiLevel: Int = -1 override fun getPermissionLevel(name: String): String? { if (permissions == null) { - assert(manifest != null, - { "This method should only be called when a manifest has been configured on the codebase" }) + assert(manifest != null) { + "This method should only be called when a manifest has been configured on the codebase" + } try { val map = HashMap<String, String>(600) val doc = XmlUtils.parseDocument(manifest?.readText(UTF_8), true) diff --git a/src/main/java/com/android/tools/metalava/model/FieldItem.kt b/src/main/java/com/android/tools/metalava/model/FieldItem.kt index b0773e83718b28f744c8fad5a23784ee1aa3a4d6..f5128333991f2412ffabbb8ef9d1076b725feb61 100644 --- a/src/main/java/com/android/tools/metalava/model/FieldItem.kt +++ b/src/main/java/com/android/tools/metalava/model/FieldItem.kt @@ -110,6 +110,25 @@ interface FieldItem : MemberItem { return false } + override fun hasNullnessInfo(): Boolean { + if (!requiresNullnessInfo()) { + return true + } + + return modifiers.hasNullnessInfo() + } + + override fun requiresNullnessInfo(): Boolean { + if (type().primitive) { + return false + } + + if (modifiers.isFinal() && initialValue(true) != null) { + return false + } + + return true + } companion object { val comparator: java.util.Comparator<FieldItem> = Comparator { a, b -> a.name().compareTo(b.name()) } @@ -126,7 +145,7 @@ interface FieldItem : MemberItem { ) { val value = initialValue(!allowDefaultValue) - ?: if (allowDefaultValue && !containingClass().isClass()) type().defaultValue() else null + ?: if (allowDefaultValue && !containingClass().isClass()) type().defaultValue() else null if (value != null) { when (value) { is Int -> { @@ -310,8 +329,8 @@ fun javaUnescapeString(str: String): String { in 'a'..'f' -> (escaped.toInt() or (10 + (c - 'a'))).toChar() in 'A'..'F' -> (escaped.toInt() or (10 + (c - 'A'))).toChar() else -> throw IllegalArgumentException( - "bad escape sequence: '" + c + "' at pos " + i + " in: \"" - + str + "\"" + "bad escape sequence: '" + c + "' at pos " + i + " in: \"" + + str + "\"" ) } if (state == CHAR4) { @@ -324,7 +343,7 @@ fun javaUnescapeString(str: String): String { } } if (state != START) { - throw IllegalArgumentException("unfinished escape sequence: " + str) + throw IllegalArgumentException("unfinished escape sequence: $str") } return buf.toString() } diff --git a/src/main/java/com/android/tools/metalava/model/Item.kt b/src/main/java/com/android/tools/metalava/model/Item.kt index 028960f41c5cb13613a0cc15db94b93fe7af4cac..e6984765aebd9edf1df4975de3b8ad8d04b2cf26 100644 --- a/src/main/java/com/android/tools/metalava/model/Item.kt +++ b/src/main/java/com/android/tools/metalava/model/Item.kt @@ -121,15 +121,23 @@ interface Item { override fun hashCode(): Int + /** Whether this member was cloned in from a super class or interface */ + fun isCloned(): Boolean + /** * Returns true if this item requires nullness information (e.g. for a method * where either the return value or any of the parameters are non-primitives. * Note that it doesn't consider whether it already has nullness annotations; * for that see [hasNullnessInfo]. */ - fun requiresNullnessInfo(): Boolean { - return false - } + fun requiresNullnessInfo(): Boolean = false + + /** + * Returns true if this item requires nullness information and supplies it + * (for all items, e.g. if a method is partially annotated this method would + * still return false) + */ + fun hasNullnessInfo(): Boolean = false /** * Whether this item was loaded from the classpath (e.g. jar dependencies) @@ -137,41 +145,12 @@ interface Item { */ fun isFromClassPath(): Boolean = false - /** Is this element declared in Java (rather than Kotlin) ? */ fun isJava(): Boolean = true /** Is this element declared in Kotlin (rather than Java) ? */ fun isKotlin() = !isJava() - /** - * Returns true if this item requires nullness information and supplies it - * (for all items, e.g. if a method is partially annotated this method would - * still return false) - */ - fun hasNullnessInfo(): Boolean { - when (this) { - is ParameterItem -> { - return !type().primitive - } - - is MethodItem -> { - val returnType = returnType() - if (returnType != null && !returnType.primitive) { - return true - } - for (parameter in parameters()) { - if (!parameter.type().primitive) { - return true - } - } - return false - } - } - - return false - } - fun hasShowAnnotation(): Boolean = modifiers.hasShowAnnotation() fun hasHideAnnotation(): Boolean = modifiers.hasHideAnnotations() diff --git a/src/main/java/com/android/tools/metalava/model/MethodItem.kt b/src/main/java/com/android/tools/metalava/model/MethodItem.kt index 726f32e603602f87d6ce4e1660965dd1869957f0..03eb3878747e0a263a4b108aa8c867128c145ba9 100644 --- a/src/main/java/com/android/tools/metalava/model/MethodItem.kt +++ b/src/main/java/com/android/tools/metalava/model/MethodItem.kt @@ -37,6 +37,32 @@ interface MethodItem : MemberItem { /** Returns the super methods that this method is overriding */ fun superMethods(): List<MethodItem> + /** + * Like [internalName] but is the desc-portion of the internal signature, + * e.g. for the method "void create(int x, int y)" the internal name of + * the constructor is "create" and the desc is "(II)V" + */ + fun internalDesc(voidConstructorTypes: Boolean = false): String { + val sb = StringBuilder() + sb.append("(") + + // Non-static inner classes get an implicit constructor parameter for the + // outer type + if (isConstructor() && containingClass().containingClass() != null && + !containingClass().modifiers.isStatic() + ) { + sb.append(containingClass().containingClass()?.toType()?.internalName() ?: "") + } + + for (parameter in parameters()) { + sb.append(parameter.type().internalName()) + } + + sb.append(")") + sb.append(if (voidConstructorTypes && isConstructor()) "V" else returnType()?.internalName() ?: "V") + return sb.toString() + } + fun allSuperMethods(): Sequence<MethodItem> { val original = superMethods().firstOrNull() ?: return emptySequence() return generateSequence(original) { item -> @@ -72,6 +98,9 @@ interface MethodItem : MemberItem { } fun filteredThrowsTypes(predicate: Predicate<Item>): Collection<ClassItem> { + if (throwsTypes().isEmpty()) { + return emptyList() + } return filteredThrowsTypes(predicate, LinkedHashSet()) } @@ -87,7 +116,7 @@ interface MethodItem : MemberItem { // Excluded, but it may have super class throwables that are included; if so, include those var curr = cls.publicSuperClass() while (curr != null) { - if (predicate.test(cls)) { + if (predicate.test(curr)) { classes.add(curr) break } @@ -106,7 +135,7 @@ interface MethodItem : MemberItem { * may think the method required and not yet implemented, e.g. the class must be * abstract.) */ - var inheritedInterfaceMethod: Boolean + var inheritedMethod: Boolean /** * Duplicates this field item. Used when we need to insert inherited fields from @@ -284,7 +313,6 @@ interface MethodItem : MemberItem { if (pt1.toErasedTypeString() != pt2.toErasedTypeString()) { return false } - } else { if (pt1 != pt2) { return false @@ -350,6 +378,10 @@ interface MethodItem : MemberItem { } override fun hasNullnessInfo(): Boolean { + if (!requiresNullnessInfo()) { + return true + } + if (!isConstructor() && returnType()?.primitive != true) { if (!modifiers.hasNullnessInfo()) { return false diff --git a/src/main/java/com/android/tools/metalava/model/ModifierList.kt b/src/main/java/com/android/tools/metalava/model/ModifierList.kt index c05362b8b284989e9992834a8bb71a356a9e6b61..435baf8743c0034e655af76cb9d7ed8e4806c681 100644 --- a/src/main/java/com/android/tools/metalava/model/ModifierList.kt +++ b/src/main/java/com/android/tools/metalava/model/ModifierList.kt @@ -75,7 +75,7 @@ interface ModifierList { if (isVolatile() != other.isVolatile()) return false // Default does not require an override to "remove" it - //if (isDefault() != other.isDefault()) return false + // if (isDefault() != other.isDefault()) return false return true } @@ -183,7 +183,9 @@ interface ModifierList { omitCommonPackages: Boolean = false, removeAbstract: Boolean = false, removeFinal: Boolean = false, - addPublic: Boolean = false + addPublic: Boolean = false, + onlyIncludeSignatureAnnotations: Boolean = true + ) { val list = if (removeAbstract || removeFinal || addPublic) { @@ -211,10 +213,15 @@ interface ModifierList { skipNullnessAnnotations = skipNullnessAnnotations, omitCommonPackages = omitCommonPackages, separateLines = false, - writer = writer + writer = writer, + onlyIncludeSignatureAnnotations = onlyIncludeSignatureAnnotations ) } + if (compatibility.doubleSpaceForPackagePrivate && item.isPackagePrivate && item is MemberItem) { + writer.write(" ") + } + // Kotlin order: // https://kotlinlang.org/docs/reference/coding-conventions.html#modifiers @@ -262,15 +269,15 @@ interface ModifierList { writer.write("operator ") } - val isInterface = classItem?.isInterface() == true - || (methodItem?.containingClass()?.isInterface() == true && + val isInterface = classItem?.isInterface() == true || + (methodItem?.containingClass()?.isInterface() == true && !list.isDefault() && !list.isStatic()) - if ((compatibility.abstractInInterfaces && isInterface - || list.isAbstract() && - (classItem?.isEnum() != true && - (compatibility.abstractInAnnotations || classItem?.isAnnotationType() != true))) - && (!isInterface || compatibility.abstractInInterfaces) + if ((compatibility.abstractInInterfaces && isInterface || + list.isAbstract() && + (classItem?.isEnum() != true && + (compatibility.abstractInAnnotations || classItem?.isAnnotationType() != true))) && + (!isInterface || compatibility.abstractInInterfaces) ) { writer.write("abstract ") } @@ -310,15 +317,15 @@ interface ModifierList { list.isPrivate() -> writer.write("private ") } - val isInterface = classItem?.isInterface() == true - || (methodItem?.containingClass()?.isInterface() == true && + val isInterface = classItem?.isInterface() == true || + (methodItem?.containingClass()?.isInterface() == true && !list.isDefault() && !list.isStatic()) - if ((compatibility.abstractInInterfaces && isInterface - || list.isAbstract() && - (classItem?.isEnum() != true && - (compatibility.abstractInAnnotations || classItem?.isAnnotationType() != true))) - && (!isInterface || compatibility.abstractInInterfaces) + if ((compatibility.abstractInInterfaces && isInterface || + list.isAbstract() && + (classItem?.isEnum() != true && + (compatibility.abstractInAnnotations || classItem?.isAnnotationType() != true))) && + (!isInterface || compatibility.abstractInInterfaces) ) { writer.write("abstract ") } @@ -378,18 +385,39 @@ interface ModifierList { skipNullnessAnnotations: Boolean = false, omitCommonPackages: Boolean = false, separateLines: Boolean = false, - writer: Writer + filterDuplicates: Boolean = false, + writer: Writer, + onlyIncludeSignatureAnnotations: Boolean = true ) { - if (list.annotations().isNotEmpty()) { - for (annotation in list.annotations()) { + val annotations = list.annotations() + if (annotations.isNotEmpty()) { + var index = -1 + for (annotation in annotations) { + index++ if ((annotation.isNonNull() || annotation.isNullable())) { if (skipNullnessAnnotations) { continue } - } else if (!annotation.isSignificant()) { + } else if (onlyIncludeSignatureAnnotations && !annotation.isSignificant()) { continue } + // Optionally filter out duplicates + if (index > 0 && filterDuplicates) { + val qualifiedName = annotation.qualifiedName() + var found = false + for (i in 0 until index) { + val prev = annotations[i] + if (prev.qualifiedName() == qualifiedName) { + found = true + break + } + } + if (found) { + continue + } + } + val source = annotation.toSource() if (omitCommonPackages) { writer.write(AnnotationItem.shortenAnnotation(source)) @@ -406,4 +434,3 @@ interface ModifierList { } } } - diff --git a/src/main/java/com/android/tools/metalava/model/PackageItem.kt b/src/main/java/com/android/tools/metalava/model/PackageItem.kt index ed6e31cab2981d256acef824f3be50aac9da9afa..ad4190aae230e67698a7285c89b31690bd9584cd 100644 --- a/src/main/java/com/android/tools/metalava/model/PackageItem.kt +++ b/src/main/java/com/android/tools/metalava/model/PackageItem.kt @@ -79,7 +79,6 @@ interface PackageItem : Item { return } - if (visitor.skip(this)) { return } diff --git a/src/main/java/com/android/tools/metalava/model/TypeItem.kt b/src/main/java/com/android/tools/metalava/model/TypeItem.kt index fe2c4001e353f31f6e6d2e70aa22c0a9a386c40c..5d1eaa8d833fcd11fb6a5f90d9ab484b2b2f69c4 100644 --- a/src/main/java/com/android/tools/metalava/model/TypeItem.kt +++ b/src/main/java/com/android/tools/metalava/model/TypeItem.kt @@ -142,11 +142,20 @@ interface TypeItem { */ fun asTypeParameter(context: MemberItem? = null): TypeParameterItem? + /** + * Mark nullness annotations in the type as recent. + * TODO: This isn't very clean; we should model individual annotations. + */ + fun markRecent() + companion object { /** Shortens types, if configured */ fun shortenTypes(type: String): String { if (options.omitCommonPackages) { var cleaned = type + if (cleaned.contains("@androidx.annotation.")) { + cleaned = cleaned.replace("@androidx.annotation.", "@") + } if (cleaned.contains("@android.support.annotation.")) { cleaned = cleaned.replace("@android.support.annotation.", "@") } @@ -209,9 +218,8 @@ interface TypeItem { // <T extends java.lang.Object> is the same as <T> // but NOT for <T extends Object & java.lang.Comparable> -- you can't // shorten this to <T & java.lang.Comparable - //return type.replace(" extends java.lang.Object", "") + // return type.replace(" extends java.lang.Object", "") return signature.replace(" extends java.lang.Object>", ">") - } val comparator: Comparator<TypeItem> = Comparator { type1, type2 -> diff --git a/src/main/java/com/android/tools/metalava/model/psi/Javadoc.kt b/src/main/java/com/android/tools/metalava/model/psi/Javadoc.kt index 784c80a1c9b7d8101aa04566f0a99fe096185b32..2a6d70208d4acacea354ab5f6021bea60b3a9ead 100644 --- a/src/main/java/com/android/tools/metalava/model/psi/Javadoc.kt +++ b/src/main/java/com/android/tools/metalava/model/psi/Javadoc.kt @@ -72,7 +72,7 @@ fun mergeDocumentation( // we don't have to search for raw substrings like "@return" which // can incorrectly find matches in escaped code snippets etc. val factory = JavaPsiFacade.getElementFactory(psiElement.project) - ?: error("Invalid tool configuration; did not find JavaPsiFacade factory") + ?: error("Invalid tool configuration; did not find JavaPsiFacade factory") val docComment = factory.createDocCommentFromText(doc) if (tagSection == "@return") { @@ -91,15 +91,13 @@ fun mergeDocumentation( // Add text to the existing @return tag val offset = if (append) findTagEnd(returnTag) - else - returnTag.textRange.startOffset + returnTag.name.length + 1 + else returnTag.textRange.startOffset + returnTag.name.length + 1 return insertInto(doc, newText, offset) } } else if (tagSection != null) { val parameter = if (tagSection.startsWith("@")) docComment.findTagByName(tagSection.substring(1)) - else - findParamTag(docComment, tagSection) + else findParamTag(docComment, tagSection) if (parameter == null) { // Add new parameter or tag // TODO: Decide whether to place it alphabetically or place it by parameter order @@ -114,7 +112,7 @@ fun mergeDocumentation( val offset = when { returnTag != null -> returnTag.textRange.startOffset anchor != null -> findTagEnd(anchor) - else -> doc.length - 2 // "*/ + else -> doc.length - 2 // "*/ } val tagName = if (tagSection.startsWith("@")) tagSection else "@param $tagSection" return insertInto(doc, "$tagName $newText", offset) @@ -122,8 +120,7 @@ fun mergeDocumentation( // Add to existing tag/parameter val offset = if (append) findTagEnd(parameter) - else - parameter.textRange.startOffset + parameter.name.length + 1 + else parameter.textRange.startOffset + parameter.name.length + 1 return insertInto(doc, newText, offset) } } else { @@ -132,7 +129,7 @@ fun mergeDocumentation( val startOffset = if (!append) { 4 // "/** ".length - } else firstTag?.textRange?.startOffset ?: doc.length-2 + } else firstTag?.textRange?.startOffset ?: doc.length - 2 return insertInto(doc, newText, startOffset) } } @@ -171,13 +168,13 @@ fun trimDocIndent(existingDoc: String): String { } return existingDoc.substring(0, index + 1) + - existingDoc.substring(index + 1).trimIndent().split('\n').joinToString(separator = "\n") { - if (!it.startsWith(" ")) { - " ${it.trimEnd()}" - } else { - it.trimEnd() - } + existingDoc.substring(index + 1).trimIndent().split('\n').joinToString(separator = "\n") { + if (!it.startsWith(" ")) { + " ${it.trimEnd()}" + } else { + it.trimEnd() } + } } fun insertInto(existingDoc: String, newText: String, initialOffset: Int): String { @@ -190,7 +187,7 @@ fun insertInto(existingDoc: String, newText: String, initialOffset: Int): String } val index = existingDoc.indexOf('\n') val prefixWithStar = index == -1 || existingDoc[index + 1] == '*' || - existingDoc[index + 1] == ' ' && existingDoc[index + 2] == '*' + existingDoc[index + 1] == ' ' && existingDoc[index + 2] == '*' val prefix = existingDoc.substring(0, offset) val suffix = existingDoc.substring(offset) @@ -200,7 +197,7 @@ fun insertInto(existingDoc: String, newText: String, initialOffset: Int): String val middle = if (prefixWithStar) { startSeparator + newText.split('\n').joinToString(separator = "\n") { " * $it" } + - endSeparator + endSeparator } else { "$startSeparator$newText$endSeparator" } @@ -208,7 +205,7 @@ fun insertInto(existingDoc: String, newText: String, initialOffset: Int): String // Going from single-line to multi-line? return if (existingDoc.indexOf('\n') == -1 && existingDoc.startsWith("/** ")) { prefix.substring(0, 3) + "\n *" + prefix.substring(3) + middle + - if (suffix == "*/") " */" else suffix + if (suffix == "*/") " */" else suffix } else { prefix + middle + suffix } diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiAnnotationItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiAnnotationItem.kt index b8ebb4594eb3d2a8c689770a5a40a84d9440ce7a..4d78346df525bbc80b7dd3d2ecf9244c4906b106 100644 --- a/src/main/java/com/android/tools/metalava/model/psi/PsiAnnotationItem.kt +++ b/src/main/java/com/android/tools/metalava/model/psi/PsiAnnotationItem.kt @@ -55,7 +55,7 @@ class PsiAnnotationItem private constructor( val attributes = psiAnnotation.parameterList.attributes if (attributes.isEmpty()) { - return "@" + qualifiedName + return "@$qualifiedName" } val sb = StringBuilder(30) @@ -299,7 +299,8 @@ class PsiAnnotationItem private constructor( // TODO: Inline this such that instead of constructing XmlBackedAnnotationItem // and then producing source and parsing it, produce source directly fun create( - codebase: Codebase, xmlAnnotation: XmlBackedAnnotationItem, + codebase: Codebase, + xmlAnnotation: XmlBackedAnnotationItem, context: Item? = null ): PsiAnnotationItem { if (codebase is PsiBasedCodebase) { diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiBasedCodebase.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiBasedCodebase.kt index 94e9d164976bcab8ca1bbfc6e44c49973299645f..b8b2504387cf4d92e513925337b96f9808aab300 100644 --- a/src/main/java/com/android/tools/metalava/model/psi/PsiBasedCodebase.kt +++ b/src/main/java/com/android/tools/metalava/model/psi/PsiBasedCodebase.kt @@ -287,7 +287,7 @@ open class PsiBasedCodebase(override var description: String = "Unknown") : Defa val topLevelClasses = ArrayList<ClassItem>(CLASS_ESTIMATE) try { - ZipFile(jarFile).use({ jar -> + ZipFile(jarFile).use { jar -> val enumeration = jar.entries() while (enumeration.hasMoreElements()) { val entry = enumeration.nextElement() @@ -324,7 +324,7 @@ open class PsiBasedCodebase(override var description: String = "Unknown") : Defa } } } - }) + } } catch (e: IOException) { reporter.report(Errors.IO_ERROR, jarFile, e.message ?: e.toString()) } @@ -365,8 +365,8 @@ open class PsiBasedCodebase(override var description: String = "Unknown") : Defa fun dumpStats() { options.stdout.println( "INTERNAL STATS: Size of classMap=${classMap.size} and size of " + - "methodMap=${methodMap.size} and size of packageMap=${packageMap.size}, and the " + - "typemap size is ${typeMap.size}, and the packageClasses size is ${packageClasses.size} " + "methodMap=${methodMap.size} and size of packageMap=${packageMap.size}, and the " + + "typemap size is ${typeMap.size}, and the packageClasses size is ${packageClasses.size} " ) } @@ -451,9 +451,9 @@ open class PsiBasedCodebase(override var description: String = "Unknown") : Defa val pkgName = getPackageName(clz) val pkg = findPackage(pkgName) if (pkg == null) { - //val packageHtml: String? = packageDocs?.packageDocs!![pkgName] + // val packageHtml: String? = packageDocs?.packageDocs!![pkgName] // dynamically discovered packages should NOT be included - //val packageHtml = "/** @hide */" + // val packageHtml = "/** @hide */" val packageHtml = null val psiPackage = JavaPsiFacade.getInstance(project).findPackage(pkgName) if (psiPackage != null) { @@ -522,7 +522,6 @@ open class PsiBasedCodebase(override var description: String = "Unknown") : Defa inner!! // should be there now return inner } - } return existing ?: return createClass(psiClass) @@ -633,6 +632,9 @@ open class PsiBasedCodebase(override var description: String = "Unknown") : Defa fun createPsiMethod(s: String, parent: PsiElement? = null): PsiMethod = getFactory().createMethodFromText(s, parent) + fun createConstructor(s: String, parent: PsiElement? = null): PsiMethod = + getFactory().createConstructor(s, parent) + fun createPsiType(s: String, parent: PsiElement? = null): PsiType = getFactory().createTypeFromText(s, parent) @@ -644,7 +646,8 @@ open class PsiBasedCodebase(override var description: String = "Unknown") : Defa private fun getFactory() = JavaPsiFacade.getElementFactory(project) override fun createAnnotation( - @Language("JAVA") source: String, context: Item?, + @Language("JAVA") source: String, + context: Item?, mapName: Boolean ): PsiAnnotationItem { val psiAnnotation = createPsiAnnotation(source, context?.psi()) @@ -737,7 +740,7 @@ open class PsiBasedCodebase(override var description: String = "Unknown") : Defa reporter.report( Errors.HIDDEN_SUPERCLASS, originalClass.psiClass, "$cls has a super class " + - "that is excluded via filters: $superClassName" + "that is excluded via filters: $superClassName" ) null } @@ -807,8 +810,8 @@ open class PsiBasedCodebase(override var description: String = "Unknown") : Defa } else { reporter.report( Errors.HIDDEN_SUPERCLASS, psiClass, "$method has a super method " + - "in a class that is excluded via filters: " + - "${superConstructor.containingClass().qualifiedName()} " + "in a class that is excluded via filters: " + + "${superConstructor.containingClass().qualifiedName()} " ) } } else { @@ -817,8 +820,8 @@ open class PsiBasedCodebase(override var description: String = "Unknown") : Defa if (newSuperConstructor == null) { reporter.report( Errors.HIDDEN_SUPERCLASS, psiClass, "$method has a super method " + - "in a class that is not matched via filters: " + - "${superConstructor.containingClass().qualifiedName()} " + "in a class that is not matched via filters: " + + "${superConstructor.containingClass().qualifiedName()} " ) } else { val constructorItem = newSuperConstructor as PsiConstructorItem @@ -849,8 +852,8 @@ open class PsiBasedCodebase(override var description: String = "Unknown") : Defa } else { reporter.report( Errors.HIDDEN_SUPERCLASS, psiClass, "$method has a super method " + - "in a class that is excluded via filters: " + - "${superMethod.containingClass().qualifiedName()} " + "in a class that is excluded via filters: " + + "${superMethod.containingClass().qualifiedName()} " ) } } else { @@ -859,8 +862,8 @@ open class PsiBasedCodebase(override var description: String = "Unknown") : Defa if (newSuperMethod == null) { reporter.report( Errors.HIDDEN_SUPERCLASS, psiClass, "$method has a super method " + - "in a class that is not matched via filters: " + - "${superMethod.containingClass().qualifiedName()} " + "in a class that is not matched via filters: " + + "${superMethod.containingClass().qualifiedName()} " ) } else { list.add(newSuperMethod) @@ -889,7 +892,7 @@ open class PsiBasedCodebase(override var description: String = "Unknown") : Defa } else { reporter.report( Errors.HIDDEN_SUPERCLASS, psiClass, "$newCls has a throws class " + - "that is excluded via filters: ${it.qualifiedName()}" + "that is excluded via filters: ${it.qualifiedName()}" ) } } else { @@ -949,7 +952,7 @@ class FilteredClassView( init { constructors = cls.constructors().asSequence().filter { filterEmit.test(it) } methods = cls.methods().asSequence().filter { filterEmit.test(it) } - //fields = cls.fields().asSequence().filter { filterEmit.test(it) } + // fields = cls.fields().asSequence().filter { filterEmit.test(it) } fields = cls.filteredFields(filterEmit).asSequence() innerClasses = cls.innerClasses() @@ -987,8 +990,8 @@ class FilteredClassView( class LockedPsiBasedCodebase(description: String = "Unknown") : PsiBasedCodebase(description) { // Not yet locked - //override fun findClass(psiClass: PsiClass): PsiClassItem { + // override fun findClass(psiClass: PsiClass): PsiClassItem { // val qualifiedName: String = psiClass.qualifiedName ?: psiClass.name!! // return classMap[qualifiedName] ?: error("Attempted to register ${psiClass.name} in locked codebase") - //} + // } } \ No newline at end of file diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiClassItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiClassItem.kt index c5dc287930f10de04ba7b372012245c791d4407e..b1cfb489f03a9ec702bc32535f88ad4f10945b32 100644 --- a/src/main/java/com/android/tools/metalava/model/psi/PsiClassItem.kt +++ b/src/main/java/com/android/tools/metalava/model/psi/PsiClassItem.kt @@ -75,6 +75,7 @@ open class PsiClassItem( } override var defaultConstructor: ConstructorItem? = null + override var artifact: String? = null private var containingClass: PsiClassItem? = null override fun containingClass(): PsiClassItem? = containingClass @@ -165,7 +166,7 @@ open class PsiClassItem( if (psiClass.hasTypeParameters()) { return PsiTypeParameterList( codebase, psiClass.typeParameterList - ?: return TypeParameterList.NONE + ?: return TypeParameterList.NONE ) } else { return TypeParameterList.NONE @@ -315,7 +316,7 @@ open class PsiClassItem( val stub = method.toStub(replacementMap) val psiMethod = codebase.createPsiMethod(stub, psiClass) newMethod = PsiMethodItem.create(codebase, this, psiMethod) - newMethod.inheritedInterfaceMethod = method.inheritedInterfaceMethod + newMethod.inheritedMethod = method.inheritedMethod newMethod.documentation = method.documentation } @@ -409,8 +410,11 @@ open class PsiClassItem( if (classType == ClassType.INTERFACE) { // All members are implicitly public, fields are implicitly static, non-static methods are abstract + // (except in Java 1.9, where they can be private for (method in methods) { - method.mutableModifiers().setPublic(true) + if (!method.isPrivate) { + method.mutableModifiers().setPublic(true) + } } for (method in fields) { val m = method.mutableModifiers() @@ -467,14 +471,12 @@ open class PsiClassItem( PsiMethodItem.create(codebase, newClass, it as PsiMethodItem) }.toMutableList() - newClass.fields = classFilter.fields.asSequence() // Preserve sorting order for enums .sortedBy { it.sortingRank }.map { PsiFieldItem.create(codebase, newClass, it as PsiFieldItem) }.toMutableList() - newClass.innerClasses = classFilter.innerClasses.map { val newInnerClass = codebase.findClass(it.cls.qualifiedName) ?: it.create(codebase) newInnerClass.containingClass = newClass @@ -511,6 +513,9 @@ open class PsiClassItem( psiClass, result, "public static final ${psiClass.qualifiedName}[] values() { return null; }" ) + // Also add a private constructor; used when emitting the private API + val psiMethod = codebase.createConstructor("private ${psiClass.name}", psiClass) + result.add(PsiConstructorItem.create(codebase, classItem, psiMethod)) } } @@ -518,7 +523,8 @@ open class PsiClassItem( codebase: PsiBasedCodebase, classItem: PsiClassItem, psiClass: PsiClass, - result: MutableList<PsiMethodItem>, source: String + result: MutableList<PsiMethodItem>, + source: String ) { val psiMethod = codebase.createPsiMethod(source, psiClass) result.add(PsiMethodItem.create(codebase, classItem, psiMethod)) @@ -542,7 +548,6 @@ open class PsiClassItem( curr.containingClass } else { break - } } return list.asReversed().asSequence().joinToString(separator = ".") { it } @@ -684,5 +689,5 @@ fun PsiModifierListOwner.isPrivate(): Boolean = modifierList?.hasExplicitModifie fun PsiModifierListOwner.isPackagePrivate(): Boolean { val modifiers = modifierList ?: return false return !(modifiers.hasModifierProperty(PsiModifier.PUBLIC) || - modifiers.hasModifierProperty(PsiModifier.PROTECTED)) + modifiers.hasModifierProperty(PsiModifier.PROTECTED)) } \ No newline at end of file diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiCompilationUnit.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiCompilationUnit.kt index f70d21cb73b07134448b7fc2973ef306cfc3a50f..843d3a0a900264a07f2794f53444caf54926a21a 100644 --- a/src/main/java/com/android/tools/metalava/model/psi/PsiCompilationUnit.kt +++ b/src/main/java/com/android/tools/metalava/model/psi/PsiCompilationUnit.kt @@ -84,9 +84,7 @@ class PsiCompilationUnit(val codebase: PsiBasedCodebase, containingFile: PsiFile for (psiClass in file.classes) { val classItem = codebase.findClass(psiClass) ?: continue if (predicate.test(classItem)) { - } - } } else if (file is KtFile) { for (importDirective in file.importDirectives) { diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiConstructorItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiConstructorItem.kt index 4ee24552bd3eaffdc52376c8f296809b54c514ce..cc14c10b41347ad9ce18766c2e36d10d33eb6c08 100644 --- a/src/main/java/com/android/tools/metalava/model/psi/PsiConstructorItem.kt +++ b/src/main/java/com/android/tools/metalava/model/psi/PsiConstructorItem.kt @@ -60,6 +60,7 @@ class PsiConstructorItem( override fun isImplicitConstructor(): Boolean = implicitConstructor override fun isConstructor(): Boolean = true override var superConstructor: ConstructorItem? = null + override fun isCloned(): Boolean = false private var _superMethods: List<MethodItem>? = null override fun superMethods(): List<MethodItem> { @@ -133,7 +134,8 @@ class PsiConstructorItem( companion object { fun create( - codebase: PsiBasedCodebase, containingClass: PsiClassItem, + codebase: PsiBasedCodebase, + containingClass: PsiClassItem, psiMethod: PsiMethod ): PsiConstructorItem { assert(psiMethod.isConstructor) diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiFieldItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiFieldItem.kt index 1a6507926a1b9bff5163b4bf4c6900dab4f4e49b..f04647a48563253cb518e4404f1ecee88f742019 100644 --- a/src/main/java/com/android/tools/metalava/model/psi/PsiFieldItem.kt +++ b/src/main/java/com/android/tools/metalava/model/psi/PsiFieldItem.kt @@ -19,9 +19,11 @@ package com.android.tools.metalava.model.psi import com.android.tools.metalava.model.ClassItem import com.android.tools.metalava.model.FieldItem import com.android.tools.metalava.model.TypeItem +import com.intellij.psi.PsiClass import com.intellij.psi.PsiEnumConstant import com.intellij.psi.PsiField import com.intellij.psi.impl.JavaConstantExpressionEvaluator +import org.jetbrains.uast.UClass class PsiFieldItem( override val codebase: PsiBasedCodebase, @@ -63,6 +65,18 @@ class PsiFieldItem( override fun name(): String = name override fun containingClass(): ClassItem = containingClass + override fun isCloned(): Boolean { + val psiClass = run { + val p = containingClass().psi() as? PsiClass ?: return false + if (p is UClass) { + p.sourcePsi as? PsiClass ?: return false + } else { + p + } + } + return psiField.containingClass != psiClass + } + override fun duplicate(targetContainingClass: ClassItem): PsiFieldItem { val duplicated = create(codebase, targetContainingClass as PsiClassItem, psiField) diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiItem.kt index d8621a7a5ef3aa1b610f893d179664a771cd9161..591bcd755db8e233708527cb88f0865625db1751 100644 --- a/src/main/java/com/android/tools/metalava/model/psi/PsiItem.kt +++ b/src/main/java/com/android/tools/metalava/model/psi/PsiItem.kt @@ -49,20 +49,22 @@ abstract class PsiItem( @Suppress("LeakingThis") override var removed = documentation.contains("@removed") @Suppress("LeakingThis") - override var hidden = (documentation.contains("@hide") || documentation.contains("@pending") - || modifiers.hasHideAnnotations()) && !modifiers.hasShowAnnotation() + override var hidden = (documentation.contains("@hide") || documentation.contains("@pending") || + modifiers.hasHideAnnotations()) && !modifiers.hasShowAnnotation() override fun psi(): PsiElement? = element // TODO: Consider only doing this in tests! override fun isFromClassPath(): Boolean { return if (element is UElement) { - element.psi is PsiCompiledElement + (element.sourcePsi ?: element.javaPsi) is PsiCompiledElement } else { element is PsiCompiledElement } } + override fun isCloned(): Boolean = false + /** Get a mutable version of modifiers for this item */ override fun mutableModifiers(): MutableModifierList = modifiers @@ -137,8 +139,8 @@ abstract class PsiItem( } if (!(documentation.contains("@link") || // includes @linkplain - documentation.contains("@see") || - documentation.contains("@throws")) + documentation.contains("@see") || + documentation.contains("@throws")) ) { // No relevant tags that need to be expanded/rewritten return documentation @@ -317,7 +319,6 @@ abstract class PsiItem( if (first is KDoc) { return first.text } - } } diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiMethodItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiMethodItem.kt index c84ad8fe4b547b9d82dac5e185acaec650eac5b3..a54dc242e0a7f8c7bfb4a7932ab2f4e59dcf972f 100644 --- a/src/main/java/com/android/tools/metalava/model/psi/PsiMethodItem.kt +++ b/src/main/java/com/android/tools/metalava/model/psi/PsiMethodItem.kt @@ -23,12 +23,14 @@ import com.android.tools.metalava.model.ModifierList import com.android.tools.metalava.model.ParameterItem import com.android.tools.metalava.model.TypeItem import com.android.tools.metalava.model.TypeParameterList +import com.intellij.psi.PsiClass import com.intellij.psi.PsiMethod import com.intellij.psi.util.PsiTypesUtil import com.intellij.psi.util.TypeConversionUtil import org.intellij.lang.annotations.Language import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.psi.KtProperty +import org.jetbrains.uast.UClass import org.jetbrains.uast.UElement import org.jetbrains.uast.UMethod import org.jetbrains.uast.UThrowExpression @@ -70,7 +72,7 @@ open class PsiMethodItem( */ internal var source: PsiMethodItem? = null - override var inheritedInterfaceMethod: Boolean = false + override var inheritedMethod: Boolean = false override fun name(): String = name override fun containingClass(): PsiClassItem = containingClass @@ -118,7 +120,7 @@ open class PsiMethodItem( if (psiMethod.hasTypeParameters()) { return PsiTypeParameterList( codebase, psiMethod.typeParameterList - ?: return TypeParameterList.NONE + ?: return TypeParameterList.NONE ) } else { return TypeParameterList.NONE @@ -138,11 +140,23 @@ open class PsiMethodItem( override fun throwsTypes(): List<ClassItem> = throwsTypes + override fun isCloned(): Boolean { + val psiClass = run { + val p = containingClass().psi() as? PsiClass ?: return false + if (p is UClass) { + p.sourcePsi as? PsiClass ?: return false + } else { + p + } + } + return psiMethod.containingClass != psiClass + } + override fun isExtensionMethod(): Boolean { if (isKotlin()) { val ktParameters = ((psiMethod as? KotlinUMethod)?.sourcePsi as? KtNamedFunction)?.valueParameters - ?: return false + ?: return false return ktParameters.size < parameters.size } @@ -235,7 +249,8 @@ open class PsiMethodItem( val modifierString = StringWriter() ModifierList.write( modifierString, method.modifiers, method, removeAbstract = false, - removeFinal = false, addPublic = true + removeFinal = false, addPublic = true, + onlyIncludeSignatureAnnotations = true ) sb.append(modifierString.toString()) @@ -341,7 +356,7 @@ open class PsiMethodItem( ) method.modifiers.setOwner(method) method.source = original - method.inheritedInterfaceMethod = original.inheritedInterfaceMethod + method.inheritedMethod = original.inheritedMethod return method } diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiModifierItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiModifierItem.kt index d52a677d6754754a8a4e772bf09da40d4a7a0d41..324625edb88ed923b39aaf2e485a67b8ad759dd0 100644 --- a/src/main/java/com/android/tools/metalava/model/psi/PsiModifierItem.kt +++ b/src/main/java/com/android/tools/metalava/model/psi/PsiModifierItem.kt @@ -207,11 +207,11 @@ class PsiModifierItem( } override fun isPackagePrivate(): Boolean { - return flags and (PUBLIC or PROTECTED or PRIVATE) == 0 + return flags and (PUBLIC or PROTECTED or PRIVATE or INTERNAL) == 0 } fun getAccessFlags(): Int { - return flags and (PUBLIC or PROTECTED or PRIVATE) + return flags and (PUBLIC or PROTECTED or PRIVATE or INTERNAL) } // Rename? It's not a full equality, it's whether an override's modifier set is significant @@ -222,8 +222,8 @@ class PsiModifierItem( // Skipping the "default" flag // TODO: Compatibility: skipNativeModifier and skipStrictFpModifier modifier flags! - //if (!compatibility.skipNativeModifier && isNative() != other.isNative()) return false - //if (!compatibility.skipStrictFpModifier && isStrictFp() != other.isStrictFp()) return false + // if (!compatibility.skipNativeModifier && isNative() != other.isNative()) return false + // if (!compatibility.skipStrictFpModifier && isStrictFp() != other.isStrictFp()) return false return flags and mask == flags2 and mask } return false @@ -255,8 +255,8 @@ class PsiModifierItem( * to consider whether an override of a method is different from its super implementation */ private const val EQUIVALENCE_MASK = PUBLIC or PROTECTED or PRIVATE or STATIC or ABSTRACT or - FINAL or TRANSIENT or VOLATILE or SYNCHRONIZED or DEPRECATED or VARARG or - SEALED or INTERNAL or INFIX or OPERATOR + FINAL or TRANSIENT or VOLATILE or SYNCHRONIZED or DEPRECATED or VARARG or + SEALED or INTERNAL or INFIX or OPERATOR fun create(codebase: PsiBasedCodebase, element: PsiModifierListOwner, documentation: String?): PsiModifierItem { val modifiers = create( diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiParameterItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiParameterItem.kt index a6a4be3aaccbb5eb807cf58bb29e3fc0db9eb533..a15e0836bf7420eab4f502c4f0316b52479cb73f 100644 --- a/src/main/java/com/android/tools/metalava/model/psi/PsiParameterItem.kt +++ b/src/main/java/com/android/tools/metalava/model/psi/PsiParameterItem.kt @@ -72,7 +72,7 @@ class PsiParameterItem( private fun getKtParameter(): KtParameter? { val ktParameters = ((containingMethod.psiMethod as? KotlinUMethod)?.sourcePsi as? KtNamedFunction)?.valueParameters - ?: return null + ?: return null // Perform matching based on parameter names, because indices won't work in the // presence of @JvmOverloads where UAST generates multiple permutations of the diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiTypeItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiTypeItem.kt index 9a986e19e2c318c56a7e25d393e9cac6ef7f91c8..248d04778f78e79e0e1b6c1d922736111c3aeab0 100644 --- a/src/main/java/com/android/tools/metalava/model/psi/PsiTypeItem.kt +++ b/src/main/java/com/android/tools/metalava/model/psi/PsiTypeItem.kt @@ -16,7 +16,7 @@ package com.android.tools.metalava.model.psi -import com.android.tools.lint.detector.api.LintUtils +import com.android.tools.lint.detector.api.getInternalName import com.android.tools.metalava.compatibility import com.android.tools.metalava.doclava1.ApiPredicate import com.android.tools.metalava.model.AnnotationItem @@ -265,6 +265,11 @@ class PsiTypeItem private constructor(private val codebase: PsiBasedCodebase, pr override fun hasTypeArguments(): Boolean = psiType is PsiClassType && psiType.hasParameters() + override fun markRecent() { + toAnnotatedString = toTypeString(false, true, false).replace(".NonNull", ".RecentlyNonNull") + toInnerAnnotatedString = toTypeString(true, true, false).replace(".NonNull", ".RecentlyNonNull") + } + companion object { private fun getPrimitiveSignature(typeName: String): String? = when (typeName) { "boolean" -> "Z" @@ -310,7 +315,7 @@ class PsiTypeItem private constructor(private val codebase: PsiBasedCodebase, pr signature: StringBuilder, outerClass: PsiClass ): Boolean { - val className = LintUtils.getInternalName(outerClass) ?: return false + val className = getInternalName(outerClass) ?: return false signature.append('L').append(className).append(';') return true } @@ -341,7 +346,6 @@ class PsiTypeItem private constructor(private val codebase: PsiBasedCodebase, pr return TextTypeItem.eraseAnnotations(typeString, false, true) } return typeString - } else { return type.canonicalText } @@ -434,9 +438,9 @@ class PsiTypeItem private constructor(private val codebase: PsiBasedCodebase, pr ci-- } return typeString.substring(0, ci) + - annotation + " " + - typeString.substring(ci, index + 1) + - typeString.substring(end + 1) + annotation + " " + + typeString.substring(ci, index + 1) + + typeString.substring(end + 1) } else { return typeString.substring(0, index + 1) + typeString.substring(end + 1) } diff --git a/src/main/java/com/android/tools/metalava/model/text/TextBackedAnnotationItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextBackedAnnotationItem.kt index 4a8b13891534ffe0a8fe52b9f56b1af4919bac5c..4911c566049ed77727546263ec743b6645c90808 100644 --- a/src/main/java/com/android/tools/metalava/model/text/TextBackedAnnotationItem.kt +++ b/src/main/java/com/android/tools/metalava/model/text/TextBackedAnnotationItem.kt @@ -34,13 +34,12 @@ class TextBackedAnnotationItem( val index = source.indexOf("(") val annotationClass = if (index == -1) source.substring(1) // Strip @ - else - source.substring(1, index) + else source.substring(1, index) qualifiedName = if (mapName) AnnotationItem.mapName(codebase, annotationClass) else annotationClass full = when { qualifiedName == null -> "" - index == -1 -> "@" + qualifiedName + index == -1 -> "@$qualifiedName" else -> "@" + qualifiedName + source.substring(index) } diff --git a/src/main/java/com/android/tools/metalava/model/text/TextClassItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextClassItem.kt index 27a30c9e1625565ed02750011e5b51b9c88ce9dc..7fbfe59a74875ffb00c332e2b854fe922a16d651 100644 --- a/src/main/java/com/android/tools/metalava/model/text/TextClassItem.kt +++ b/src/main/java/com/android/tools/metalava/model/text/TextClassItem.kt @@ -64,6 +64,8 @@ open class TextClassItem( override val isTypeParameter: Boolean = false + override var artifact: String? = null + override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is ClassItem) return false @@ -119,8 +121,7 @@ open class TextClassItem( if (typeParameterList().toString().isNotEmpty()) // TODO: No, handle List<String>[], though this is highly unlikely in a class qualifiedName() + "<" + typeParameterList() + ">" - else - qualifiedName() + else qualifiedName() ) override fun hasTypeVariables(): Boolean { @@ -227,7 +228,8 @@ open class TextClassItem( } private fun addStubPackage( - name: String, codebase: TextCodebase, + name: String, + codebase: TextCodebase, textClassItem: TextClassItem ) { val endIndex = name.lastIndexOf('.') diff --git a/src/main/java/com/android/tools/metalava/model/text/TextConstructorItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextConstructorItem.kt index fe0114ff67ec79f4c4e08c2549e64c958c409eba..4262188358ea9a59db8bdf94eb62bf053e2cda0a 100644 --- a/src/main/java/com/android/tools/metalava/model/text/TextConstructorItem.kt +++ b/src/main/java/com/android/tools/metalava/model/text/TextConstructorItem.kt @@ -48,5 +48,3 @@ class TextConstructorItem( override fun isConstructor(): Boolean = true } - - diff --git a/src/main/java/com/android/tools/metalava/model/text/TextFieldItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextFieldItem.kt index 732c6c482c6147e59835c7f27d025d60ea0e44d3..50acde87ebb2c05f8886221344d6afe60c310aff 100644 --- a/src/main/java/com/android/tools/metalava/model/text/TextFieldItem.kt +++ b/src/main/java/com/android/tools/metalava/model/text/TextFieldItem.kt @@ -30,7 +30,8 @@ class TextFieldItem( isProtected: Boolean, isPrivate: Boolean, isInternal: Boolean, - isFinal: Boolean, isStatic: Boolean, + isFinal: Boolean, + isStatic: Boolean, isTransient: Boolean, isVolatile: Boolean, private val type: TextTypeItem, diff --git a/src/main/java/com/android/tools/metalava/model/text/TextItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextItem.kt index 0acb645cedf55b112e1bdf69a5f6e670aafd1f95..72968bd679e69a45c0a576f1afd31039838afe7e 100644 --- a/src/main/java/com/android/tools/metalava/model/text/TextItem.kt +++ b/src/main/java/com/android/tools/metalava/model/text/TextItem.kt @@ -43,6 +43,8 @@ abstract class TextItem( override val deprecated get() = mutableDeprecated + override fun isCloned(): Boolean = false + fun setDeprecated(deprecated: Boolean) { mutableDeprecated = deprecated } diff --git a/src/main/java/com/android/tools/metalava/model/text/TextMethodItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextMethodItem.kt index 8fb461ee558edab15edab3a86f3a89a8d3f01378..d187adcfe93088c0c0199617acd81e1f47d9a3e5 100644 --- a/src/main/java/com/android/tools/metalava/model/text/TextMethodItem.kt +++ b/src/main/java/com/android/tools/metalava/model/text/TextMethodItem.kt @@ -183,12 +183,10 @@ open class TextMethodItem( override fun isExtensionMethod(): Boolean = codebase.unsupported() - override var inheritedInterfaceMethod: Boolean = false + override var inheritedMethod: Boolean = false override fun toString(): String = "${if (isConstructor()) "Constructor" else "Method"} ${containingClass().qualifiedName()}.${name()}(${parameters().joinToString { it.type().toSimpleType() }})" } - - diff --git a/src/main/java/com/android/tools/metalava/model/text/TextModifiers.kt b/src/main/java/com/android/tools/metalava/model/text/TextModifiers.kt index 9ac8cebc1cc5b6e500086e22fab5bff1fce36a76..a6271999c8b80ba02ba7698a165edcf040a33ed2 100644 --- a/src/main/java/com/android/tools/metalava/model/text/TextModifiers.kt +++ b/src/main/java/com/android/tools/metalava/model/text/TextModifiers.kt @@ -144,8 +144,8 @@ class TextModifiers( override fun owner(): Item = owner!! // Must be set after construction override fun isEmpty(): Boolean { - return !(public || protected || private || static || abstract || final || native || synchronized - || strictfp || transient || volatile || default) + return !(public || protected || private || static || abstract || final || native || synchronized || + strictfp || transient || volatile || default) } override fun annotations(): List<AnnotationItem> { diff --git a/src/main/java/com/android/tools/metalava/model/text/TextTypeItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextTypeItem.kt index c4e9e8ca2d8d1507d88a9b9c752795e64398c6cb..306be6fde8369219043503bfc1fb3687ae86d74d 100644 --- a/src/main/java/com/android/tools/metalava/model/text/TextTypeItem.kt +++ b/src/main/java/com/android/tools/metalava/model/text/TextTypeItem.kt @@ -40,7 +40,7 @@ class TextTypeItem( innerAnnotations: Boolean, erased: Boolean ): String { - return Companion.toTypeString(type, outerAnnotations, innerAnnotations, erased) + return toTypeString(type, outerAnnotations, innerAnnotations, erased) } override fun asClass(): ClassItem? { @@ -129,7 +129,6 @@ class TextTypeItem( } typeParameter - } else { null } @@ -144,6 +143,8 @@ class TextTypeItem( return TextTypeItem(codebase, convertTypeString(replacementMap)) } + override fun markRecent() = codebase.unsupported() + companion object { // heuristic to guess if a given type parameter is a type variable fun isLikelyTypeParameter(typeString: String): Boolean { diff --git a/src/main/java/com/android/tools/metalava/model/visitors/ApiVisitor.kt b/src/main/java/com/android/tools/metalava/model/visitors/ApiVisitor.kt index 3ee9389b5c244529e5205f0f306917517fab2f64..56d8375bb0c44037c17f5f5ebc85d7b48715ed2c 100644 --- a/src/main/java/com/android/tools/metalava/model/visitors/ApiVisitor.kt +++ b/src/main/java/com/android/tools/metalava/model/visitors/ApiVisitor.kt @@ -52,8 +52,15 @@ open class ApiVisitor( /** The filter to use to determine if we should emit an item */ val filterEmit: Predicate<Item>, /** The filter to use to determine if we should emit a reference to an item */ - val filterReference: Predicate<Item> + val filterReference: Predicate<Item>, + /** + * Whether the visitor should include visiting top-level classes that have + * nothing other than non-empty inner classes within. + * Typically these are not included in signature files, but when generating + * stubs we need to include them. + */ + val includeEmptyOuterClasses: Boolean = false ) : ItemVisitor(visitConstructorsAsMethods, nestInnerClasses) { constructor( codebase: Codebase, @@ -71,6 +78,7 @@ open class ApiVisitor( nestInnerClasses: Boolean = false, /** Whether to ignore APIs with annotations in the --show-annotations list */ +// ignoreShown: Boolean = options.showUnannotated, ignoreShown: Boolean = true, /** Whether to match APIs marked for removal instead of the normal API */ @@ -89,11 +97,9 @@ open class ApiVisitor( ApiPredicate(codebase, ignoreShown = true, ignoreRemoved = remove) ) - // The API visitor lazily visits packages only when there's a match within at least one class; // this property keeps track of whether we've already visited the current package var visitingPackage = false open fun include(cls: ClassItem): Boolean = cls.emit } - diff --git a/src/main/resources/version.properties b/src/main/resources/version.properties index be11ad6ed39bfb1ae6d357c43a302d0fd5f25ec9..dd2d971e7b996caa2593b12e66d56683bd409669 100644 --- a/src/main/resources/version.properties +++ b/src/main/resources/version.properties @@ -1,4 +1,4 @@ # Version definition # This file is read by gradle build scripts, but also packaged with metalava # as a resource for the Version classes to read. -metalavaVersion=0.9.6 +metalavaVersion=0.9.12 diff --git a/src/test/java/com/android/tools/metalava/AnnotationStatisticsTest.kt b/src/test/java/com/android/tools/metalava/AnnotationStatisticsTest.kt index dc749c31bc2a75dfd25d5495dcff98687c8427dc..f62c805b30d434e99f2a742f3d105593a94d951b 100644 --- a/src/test/java/com/android/tools/metalava/AnnotationStatisticsTest.kt +++ b/src/test/java/com/android/tools/metalava/AnnotationStatisticsTest.kt @@ -22,14 +22,46 @@ import org.junit.Test class AnnotationStatisticsTest : DriverTest() { @Test - fun `Test emitting annotation statistics`() { check( extraArguments = arrayOf("--annotation-coverage-stats"), expectedOutput = """ Nullness Annotation Coverage Statistics: - 4 out of 6 methods were annotated (66%) - 0 out of 0 fields were annotated (0%) + 0 out of 0 methods were annotated (100%) + 0 out of 1 fields were annotated (0%) + 0 out of 0 parameters were annotated (100%) + """, + sourceFiles = *arrayOf( + java( + """ + package test.pkg; + + @SuppressWarnings("ALL") + public class Foo { + public final static String foo1 = "constant"; + public final static String foo2 = System.getProperty("not.constant"); + public void test() { + String foo1 = foo1(); + String foo2 = foo2(); + test(); + + } + } + """ + ) + ), + compatibilityMode = false + ) + } + + @Test + fun `Static final initialized fields are not nullable`() { + check( + extraArguments = arrayOf("--annotation-coverage-stats"), + expectedOutput = """ + Nullness Annotation Coverage Statistics: + 4 out of 5 methods were annotated (80%) + 0 out of 0 fields were annotated (100%) 4 out of 5 parameters were annotated (80%) """, compatibilityMode = false, @@ -38,10 +70,10 @@ class AnnotationStatisticsTest : DriverTest() { public class MyTest { ctor public MyTest(); method public java.lang.Double convert0(java.lang.Float); - method @android.support.annotation.Nullable public java.lang.Double convert1(@android.support.annotation.NonNull java.lang.Float); - method @android.support.annotation.Nullable public java.lang.Double convert2(@android.support.annotation.NonNull java.lang.Float); - method @android.support.annotation.Nullable public java.lang.Double convert3(@android.support.annotation.NonNull java.lang.Float); - method @android.support.annotation.Nullable public java.lang.Double convert4(@android.support.annotation.NonNull java.lang.Float); + method @androidx.annotation.Nullable public java.lang.Double convert1(@androidx.annotation.NonNull java.lang.Float); + method @androidx.annotation.Nullable public java.lang.Double convert2(@androidx.annotation.NonNull java.lang.Float); + method @androidx.annotation.Nullable public java.lang.Double convert3(@androidx.annotation.NonNull java.lang.Float); + method @androidx.annotation.Nullable public java.lang.Double convert4(@androidx.annotation.NonNull java.lang.Float); } } """ @@ -78,18 +110,18 @@ class AnnotationStatisticsTest : DriverTest() { "libs/api-usage.jar", base64gzip( "test/pkg/ApiUsage.class", "" + - "H4sIAAAAAAAAAH1Ta28SQRQ9Q4Fd1qW0VPD9KNZKWe0WaH3VmBiTRpKNfqiW" + - "+HGgIw4uu4RdTPzqPzJRSDTxB/ijjHcGWqqlZjNn5945c86du7O/fn//CaCO" + - "xxZyuG7ghoUEbmYIVjNYREmFt0ysqfdtBesK7igoK9hQUDHgGLjLYPQ+7Unh" + - "HzLkvS7/yF2fBx335bDXEoNdhvQTGcj4KcNCeeOAIfk8PBQMOU8GYsJ5zVu+" + - "UJvDNvcP+ECqeJpMxu9lxLDixSKK3f6HjvusL99EvCNIOTVUEwaL9+X+cPCO" + - "tyko/EWdpols7YfDQVvsSSWbPVLZVAXbyGOFTOZsVEv3bGxi2caSgjxcMn4h" + - "fD+0sYWqjRrqNraxY+M+Hth4qHKPUGVYPlUzw9KsQa9aXdGOGYpl79/kbkN1" + - "KtuTUSSDjm4u6RXmEBXP4kEQxjwWirQ+j3Q6xWBO1c8SbswotbOKPMGpK5nG" + - "f522Z9MdrNI9y9ElZDSosYQJGvQdKHOeZl2k9NpWZQxW+YHEW2aOsfAVyW9I" + - "6XCMdN4YwWweRWyETPOLVioQFkkBSNKTIcUUSkjDhUF5wJ5o4wIu6houHft+" + - "Jr6qpHZs6TkTG0frO8wcwWo6hOd0ym7q9ewJ5xJM7WEhSydbJBf6y+iUaxRV" + - "6IxVcivqCrXTtAoLZVzGFaqD4arWuvYH9nECI6kDAAA=" + "H4sIAAAAAAAAAH1Ta28SQRQ9Q4Fd1qW0VPD9KNZKWe0WaH3VmBiTRpKNfqiW" + + "+HGgIw4uu4RdTPzqPzJRSDTxB/ijjHcGWqqlZjNn5945c86du7O/fn//CaCO" + + "xxZyuG7ghoUEbmYIVjNYREmFt0ysqfdtBesK7igoK9hQUDHgGLjLYPQ+7Unh" + + "HzLkvS7/yF2fBx335bDXEoNdhvQTGcj4KcNCeeOAIfk8PBQMOU8GYsJ5zVu+" + + "UJvDNvcP+ECqeJpMxu9lxLDixSKK3f6HjvusL99EvCNIOTVUEwaL9+X+cPCO" + + "tyko/EWdpols7YfDQVvsSSWbPVLZVAXbyGOFTOZsVEv3bGxi2caSgjxcMn4h" + + "fD+0sYWqjRrqNraxY+M+Hth4qHKPUGVYPlUzw9KsQa9aXdGOGYpl79/kbkN1" + + "KtuTUSSDjm4u6RXmEBXP4kEQxjwWirQ+j3Q6xWBO1c8SbswotbOKPMGpK5nG" + + "f522Z9MdrNI9y9ElZDSosYQJGvQdKHOeZl2k9NpWZQxW+YHEW2aOsfAVyW9I" + + "6XCMdN4YwWweRWyETPOLVioQFkkBSNKTIcUUSkjDhUF5wJ5o4wIu6houHft+" + + "Jr6qpHZs6TkTG0frO8wcwWo6hOd0ym7q9ewJ5xJM7WEhSydbJBf6y+iUaxRV" + + "6IxVcivqCrXTtAoLZVzGFaqD4arWuvYH9nECI6kDAAA=" ) ) ), @@ -98,8 +130,8 @@ class AnnotationStatisticsTest : DriverTest() { """ package test.pkg; - import android.support.annotation.NonNull; - import android.support.annotation.Nullable; + import androidx.annotation.NonNull; + import androidx.annotation.Nullable; public class ApiSurface { ApiSurface(Object param) { @@ -135,8 +167,11 @@ class AnnotationStatisticsTest : DriverTest() { supportNullableSource ), expectedOutput = """ - 6 methods and fields were missing nullness annotations out of 7 total API references. - API nullness coverage is 14% + 6 methods and fields were missing nullness annotations out of 8 total API references. + API nullness coverage is 25% + + + Top referenced un-annotated classes: | Qualified Class Name | Usage Count | |--------------------------------------------------------------|-----------------:| diff --git a/src/test/java/com/android/tools/metalava/AnnotationsDifferTest.kt b/src/test/java/com/android/tools/metalava/AnnotationsDifferTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..0d367d114ec7c181df1d6d77d39b3df453c95b69 --- /dev/null +++ b/src/test/java/com/android/tools/metalava/AnnotationsDifferTest.kt @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tools.metalava + +import com.android.tools.metalava.doclava1.ApiFile +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder + +class AnnotationsDifferTest { + @get:Rule + var temporaryFolder = TemporaryFolder() + + @Test + fun `Write diff`() { + val codebase = ApiFile.parseApi("old.txt", """ + package test.pkg { + public interface Appendable { + method public test.pkg.Appendable append(java.lang.CharSequence?); + method public test.pkg.Appendable append2(java.lang.CharSequence?); + method public java.lang.String! reverse(java.lang.String!); + } + public interface RandomClass { + method public test.pkg.Appendable append(java.lang.CharSequence); + } + } + """.trimIndent(), true, true) + + val codebase2 = ApiFile.parseApi("new.txt", """ + package test.pkg { + public interface Appendable { + method @androidx.annotation.NonNull public test.pkg.Appendable append(@androidx.annotation.Nullable java.lang.CharSequence); + method public test.pkg.Appendable append2(java.lang.CharSequence); + } + } + """.trimIndent(), false, false) + + val apiFile = temporaryFolder.newFile("diff.txt") + AnnotationsDiffer(codebase, codebase2).writeDiffSignature(apiFile) + assertTrue(apiFile.exists()) + val actual = apiFile.readText(Charsets.UTF_8) + assertEquals(""" + package test.pkg { + public abstract interface Appendable { + method public abstract test.pkg.Appendable append2(java.lang.CharSequence); + } + } + """.trimIndent(), actual.trim()) + } +} \ No newline at end of file diff --git a/src/test/java/com/android/tools/metalava/AnnotationsMergerTest.kt b/src/test/java/com/android/tools/metalava/AnnotationsMergerTest.kt index 129f41e4ef02d91e844b158a4b4f4c80395c6284..654ed1a76f08595b42d0762b2a07dc1661646ca4 100644 --- a/src/test/java/com/android/tools/metalava/AnnotationsMergerTest.kt +++ b/src/test/java/com/android/tools/metalava/AnnotationsMergerTest.kt @@ -36,10 +36,10 @@ class AnnotationsMergerTest : DriverTest() { """ package test.pkg; - import android.support.annotation.NonNull; - import android.support.annotation.Nullable; + import androidx.annotation.NonNull; + import androidx.annotation.Nullable; import android.annotation.IntRange; - import android.support.annotation.UiThread; + import androidx.annotation.UiThread; @UiThread public class MyTest { @@ -56,15 +56,16 @@ class AnnotationsMergerTest : DriverTest() { // Skip the annotations themselves from the output extraArguments = arrayOf( "--hide-package", "android.annotation", + "--hide-package", "androidx.annotation", "--hide-package", "android.support.annotation" ), api = """ package test.pkg { - @android.support.annotation.UiThread public class MyTest { + @androidx.annotation.UiThread public class MyTest { ctor public MyTest(); - method @android.support.annotation.IntRange(from=10, to=20) public int clamp(int); - method @android.support.annotation.Nullable public java.lang.Double convert(@android.support.annotation.NonNull java.lang.Float); - field @android.support.annotation.Nullable public java.lang.Number myNumber; + method @androidx.annotation.IntRange(from=10, to=20) public int clamp(int); + method @androidx.annotation.Nullable public java.lang.Double convert(@androidx.annotation.NonNull java.lang.Float); + field @androidx.annotation.Nullable public java.lang.Number myNumber; } } """ @@ -90,7 +91,7 @@ class AnnotationsMergerTest : DriverTest() { compatibilityMode = false, outputKotlinStyleNulls = false, omitCommonPackages = false, - mergeAnnotations = """<?xml version="1.0" encoding="UTF-8"?> + mergeXmlAnnotations = """<?xml version="1.0" encoding="UTF-8"?> <root> <item name="test.pkg.MyTest"> <annotation name="android.support.annotation.UiThread" /> @@ -110,15 +111,101 @@ class AnnotationsMergerTest : DriverTest() { <val name="to" val="20" /> </annotation> </item> + <item name="test.pkg.MyTest int clamp(int) 0"> + <annotation name='org.jetbrains.annotations.Range'> + <val name="from" val="-1"/> + <val name="to" val="java.lang.Integer.MAX_VALUE"/> + </annotation> + </item> </root> """, api = """ package test.pkg { - @android.support.annotation.UiThread public class MyTest { + @androidx.annotation.UiThread public class MyTest { ctor public MyTest(); - method @android.support.annotation.IntRange(from=10, to=20) public int clamp(int); - method @android.support.annotation.Nullable public java.lang.Double convert(@android.support.annotation.NonNull java.lang.Float); - field @android.support.annotation.Nullable public java.lang.Number myNumber; + method @androidx.annotation.IntRange(from=10, to=20) public int clamp(@androidx.annotation.IntRange(from=-1L, to=java.lang.Integer.MAX_VALUE) int); + method @androidx.annotation.Nullable public java.lang.Double convert(@androidx.annotation.NonNull java.lang.Float); + field @androidx.annotation.Nullable public java.lang.Number myNumber; + } + } + """ + ) + } + + @Test + fun `Merge jaif files`() { + check( + sourceFiles = *arrayOf( + java( + """ + package test.pkg; + + public interface Appendable { + Appendable append(CharSequence csq) throws IOException; + String reverse(String s); + } + """ + ) + ), + compatibilityMode = false, + outputKotlinStyleNulls = false, + omitCommonPackages = false, + mergeJaifAnnotations = """ + // + // Copyright (C) 2017 The Android Open Source Project + // + package test.pkg: + class Appendable: + method append(Ljava/lang/CharSequence;)Ltest/pkg/Appendable;: + parameter #0: + type: @libcore.util.Nullable + // Is expected to return self + return: @libcore.util.NonNull + """, + api = """ + package test.pkg { + public interface Appendable { + method @androidx.annotation.NonNull public test.pkg.Appendable append(@androidx.annotation.Nullable java.lang.CharSequence); + method public java.lang.String reverse(java.lang.String); + } + } + """ + ) + } + + @Test + fun `Merge signature files`() { + check( + sourceFiles = *arrayOf( + java( + """ + package test.pkg; + + public interface Appendable { + Appendable append(CharSequence csq) throws IOException; + } + """ + ) + ), + compatibilityMode = false, + outputKotlinStyleNulls = false, + omitCommonPackages = false, + mergeSignatureAnnotations = """ + package test.pkg { + public interface Appendable { + method public test.pkg.Appendable append(java.lang.CharSequence?); + method public test.pkg.Appendable append2(java.lang.CharSequence?); + method public java.lang.String! reverse(java.lang.String!); + } + public interface RandomClass { + method public test.pkg.Appendable append(java.lang.CharSequence); + } + } + """, + api = """ + package test.pkg { + public interface Appendable { + method @androidx.annotation.NonNull public test.pkg.Appendable append(@androidx.annotation.Nullable java.lang.CharSequence); } } """ diff --git a/src/test/java/com/android/tools/metalava/ApiFileTest.kt b/src/test/java/com/android/tools/metalava/ApiFileTest.kt index 330a1e237e8bd7ff4998c9e8b49b8a63f91ce865..abd3c1df07bd97f8afbf62685dca2dd76ed8b5e4 100644 --- a/src/test/java/com/android/tools/metalava/ApiFileTest.kt +++ b/src/test/java/com/android/tools/metalava/ApiFileTest.kt @@ -18,8 +18,6 @@ package com.android.tools.metalava -import com.android.tools.lint.checks.infrastructure.TestFiles.kotlin -import org.junit.Ignore import org.junit.Test class ApiFileTest : DriverTest() { @@ -90,7 +88,7 @@ class ApiFileTest : DriverTest() { java( """ package test.pkg; - import android.support.annotation.ParameterName; + import androidx.annotation.ParameterName; public class Foo { public void foo(int javaParameter1, @ParameterName("publicParameterName") int javaParameter2) { @@ -108,7 +106,7 @@ class ApiFileTest : DriverTest() { } } """, - extraArguments = arrayOf("--hide-package", "android.support.annotation"), + extraArguments = arrayOf("--hide-package", "androidx.annotation"), checkDoclava1 = false /* doesn't support parameter names */ ) } @@ -122,7 +120,7 @@ class ApiFileTest : DriverTest() { java( """ package test.pkg; - import android.support.annotation.DefaultValue; + import androidx.annotation.DefaultValue; public class Foo { public void foo( @@ -143,7 +141,7 @@ class ApiFileTest : DriverTest() { } } """, - extraArguments = arrayOf("--hide-package", "android.support.annotation"), + extraArguments = arrayOf("--hide-package", "androidx.annotation"), checkDoclava1 = false /* doesn't support default Values */ ) } @@ -172,7 +170,7 @@ class ApiFileTest : DriverTest() { } } """, - extraArguments = arrayOf("--hide-package", "android.support.annotation"), + extraArguments = arrayOf("--hide-package", "androidx.annotation"), checkDoclava1 = false /* doesn't support default Values */ ) } @@ -248,7 +246,7 @@ class ApiFileTest : DriverTest() { public static final class Kotlin.Companion { ctor private Kotlin.Companion(); } - internal static final class Kotlin.myHiddenClass extends kotlin.Unit { + internal static final class Kotlin.myHiddenClass extends kotlin.Unit { ctor public Kotlin.myHiddenClass(); method internal test.pkg.Kotlin.myHiddenClass copy(); } @@ -258,7 +256,6 @@ class ApiFileTest : DriverTest() { ) } - @Ignore("Still broken: UAST is missing reified methods, and some missing symbol resolution") @Test fun `Kotlin Reified Methods`() { check( @@ -292,7 +289,7 @@ class ApiFileTest : DriverTest() { } public final class _java_Kt { ctor public _java_Kt(); - method public static final error.NonExistentClass systemService2(test.pkg.Context); + method public static java.lang.String systemService2(test.pkg.Context); } } """, @@ -342,8 +339,8 @@ class ApiFileTest : DriverTest() { """ // Platform nullability Pair in Java package androidx.util; - import android.support.annotation.NonNull; - import android.support.annotation.Nullable; + import androidx.annotation.NonNull; + import androidx.annotation.Nullable; @SuppressWarnings("WeakerAccess") public class NullableJavaPair<F, S> { @@ -362,7 +359,7 @@ class ApiFileTest : DriverTest() { // Platform nullability Pair in Java package androidx.util; - import android.support.annotation.NonNull; + import androidx.annotation.NonNull; @SuppressWarnings("WeakerAccess") public class NonNullableJavaPair<F, S> { @@ -420,7 +417,7 @@ class ApiFileTest : DriverTest() { } } """, - extraArguments = arrayOf("--hide-package", "android.support.annotation"), + extraArguments = arrayOf("--hide-package", "androidx.annotation"), checkDoclava1 = false /* doesn't support Kotlin... */ ) } @@ -472,7 +469,7 @@ class ApiFileTest : DriverTest() { } } """, - extraArguments = arrayOf("--hide-package", "android.support.annotation"), + extraArguments = arrayOf("--hide-package", "androidx.annotation"), checkDoclava1 = false /* doesn't support default Values */ ) } @@ -1535,7 +1532,7 @@ class ApiFileTest : DriverTest() { check( checkDoclava1 = false, // doclava1 does not include method2, which it should compatibilityMode = true, - extraArguments = arrayOf("--include-public-methods-from-hidden-super-classes=true"), + extraArguments = arrayOf("--skip-inherited-methods=false"), sourceFiles = *arrayOf( java( @@ -1891,7 +1888,16 @@ class ApiFileTest : DriverTest() { field public int removed; } } - """ + """, + removedDexApi = "" + + "Ltest/pkg/Bar;-><init>()V\n" + + "Ltest/pkg/Bar;->removedMethod()V\n" + + "Ltest/pkg/Bar;->removedField:I\n" + + "Ltest/pkg/Bar\$Inner;\n" + + "Ltest/pkg/Bar\$Inner;-><init>()V\n" + + "Ltest/pkg/Bar\$Inner2\$Inner3\$Inner4;\n" + + "Ltest/pkg/Bar\$Inner2\$Inner3\$Inner4;-><init>()V\n" + + "Ltest/pkg/Bar\$Inner5\$Inner6\$Inner7;->removed:I" ) } @@ -2021,7 +2027,15 @@ class ApiFileTest : DriverTest() { ctor public Parent(); } } - """ + """, + dexApi = """ + Ltest/pkg/Child; + Ltest/pkg/Child;-><init>()V + Ltest/pkg/Child;->toString()Ljava/lang/String; + Ltest/pkg/Parent; + Ltest/pkg/Parent;-><init>()V + Ltest/pkg/Parent;->toString()Ljava/lang/String; + """ ) } @@ -2036,8 +2050,9 @@ class ApiFileTest : DriverTest() { """ @file:JvmName("-Foo") - package test.pkg; + package test.pkg + @Suppress("unused") inline fun String.printHelloWorld() { println("Hello World") } """ ) @@ -2163,7 +2178,7 @@ class ApiFileTest : DriverTest() { java( """ package test.pkg; - public class Class1 { + public class Class1 implements MyInterface { Class1(int arg) { } /** @hide */ public void method1() { } @@ -2205,23 +2220,34 @@ class ApiFileTest : DriverTest() { public void method5() { } } """ + ), + + java( + """ + package test.pkg; + /** @hide */ + @SuppressWarnings("UnnecessaryInterfaceModifier") + public interface MyInterface { + public static final String MY_CONSTANT = "5"; + } + """ ) ), privateApi = """ package test.pkg { - public class Class1 { - ctor Class1(int); + public class Class1 implements test.pkg.MyInterface { + ctor Class1(int); method public void method1(); - method void method2(); + method void method2(); method private void method3(); - method void myVarargsMethod(int, java.lang.String...); - field int field3; - field float[][] field4; - field long[] field5; + method void myVarargsMethod(int, java.lang.String...); + field int field3; + field float[][] field4; + field long[] field5; field private int field6; } class Class2 { - ctor Class2(); + ctor Class2(); method public void method4(); } private class Class2.Class3 { @@ -2229,9 +2255,12 @@ class ApiFileTest : DriverTest() { method public void method5(); } class Class4 { - ctor Class4(); + ctor Class4(); method public void method5(); } + public abstract interface MyInterface { + field public static final java.lang.String MY_CONSTANT = "5"; + } } """, privateDexApi = """ @@ -2253,6 +2282,112 @@ class ApiFileTest : DriverTest() { Ltest/pkg/Class4; Ltest/pkg/Class4;-><init>()V Ltest/pkg/Class4;->method5()V + Ltest/pkg/MyInterface; + Ltest/pkg/MyInterface;->MY_CONSTANT:Ljava/lang/String; + """ + ) + } + + @Test + fun `Private API signature corner cases`() { + // Some corner case scenarios exposed by differences in output from doclava and metalava + check( + checkDoclava1 = false, + sourceFiles = *arrayOf( + java( + """ + package test.pkg; + import android.os.Parcel; + import android.os.Parcelable; + import java.util.concurrent.FutureTask; + + public class Class1 extends PrivateParent implements MyInterface { + Class1(int arg) { } + + @Override public String toString() { + return "Class1"; + } + + private abstract class AmsTask extends FutureTask<String> { + @Override + protected void set(String bundle) { + super.set(bundle); + } + } + + /** @hide */ + public abstract static class TouchPoint implements Parcelable { + } + } + """ + ), + + java( + """ + package test.pkg; + class PrivateParent { + final String getValue() { + return ""; + } + } + """ + ), + + java( + """ + package test.pkg; + /** @hide */ + public enum MyEnum { + FOO, BAR + } + """ + ), + + java( + """ + package test.pkg; + @SuppressWarnings("UnnecessaryInterfaceModifier") + public interface MyInterface { + public static final String MY_CONSTANT = "5"; + } + """ + ) + ), + privateApi = """ + package test.pkg { + public class Class1 extends test.pkg.PrivateParent implements test.pkg.MyInterface { + ctor Class1(int); + } + private abstract class Class1.AmsTask extends java.util.concurrent.FutureTask { + } + public static abstract class Class1.TouchPoint implements android.os.Parcelable { + ctor public Class1.TouchPoint(); + } + public final class MyEnum extends java.lang.Enum { + ctor private MyEnum(); + enum_constant public static final test.pkg.MyEnum BAR; + enum_constant public static final test.pkg.MyEnum FOO; + } + class PrivateParent { + ctor PrivateParent(); + method final java.lang.String getValue(); + } + } + """, + privateDexApi = """ + Ltest/pkg/Class1;-><init>(I)V + Ltest/pkg/Class1${"$"}AmsTask; + Ltest/pkg/Class1${"$"}TouchPoint; + Ltest/pkg/Class1${"$"}TouchPoint;-><init>()V + Ltest/pkg/MyEnum; + Ltest/pkg/MyEnum;-><init>()V + Ltest/pkg/MyEnum;->valueOf(Ljava/lang/String;)Ltest/pkg/MyEnum; + Ltest/pkg/MyEnum;->values()[Ltest/pkg/MyEnum; + Ltest/pkg/MyEnum;->BAR:Ltest/pkg/MyEnum; + Ltest/pkg/MyEnum;->FOO:Ltest/pkg/MyEnum; + Ltest/pkg/PrivateParent; + Ltest/pkg/PrivateParent;-><init>()V + Ltest/pkg/PrivateParent;->getValue()Ljava/lang/String; """ ) } diff --git a/src/test/java/com/android/tools/metalava/ApiFromTextTest.kt b/src/test/java/com/android/tools/metalava/ApiFromTextTest.kt index fe63f8b6c96e9751d7df1c5a8c024cec399cc29b..7e9dcbde34533e43d02eccd76d2a025279037d11 100644 --- a/src/test/java/com/android/tools/metalava/ApiFromTextTest.kt +++ b/src/test/java/com/android/tools/metalava/ApiFromTextTest.kt @@ -44,7 +44,6 @@ class ApiFromTextTest : DriverTest() { @Test fun `Infer fully qualified names from shorter names`() { - check( compatibilityMode = true, extraArguments = arrayOf("--annotations-in-signatures"), @@ -62,7 +61,7 @@ class ApiFromTextTest : DriverTest() { public class MyTest { ctor public MyTest(); method public int clamp(int); - method public double convert(@android.support.annotation.Nullable java.lang.Float, byte[], java.lang.Iterable<java.io.File>); + method public double convert(@androidx.annotation.Nullable java.lang.Float, byte[], java.lang.Iterable<java.io.File>); } } """ @@ -224,9 +223,9 @@ class ApiFromTextTest : DriverTest() { @Language("TEXT") val source = """ package test.pkg { - @android.support.annotation.UiThread public class MyTest { + @androidx.annotation.UiThread public class MyTest { ctor public MyTest(); - method @android.support.annotation.IntRange(from=10, to=20) public int clamp(int); + method @androidx.annotation.IntRange(from=10, to=20) public int clamp(int); method public java.lang.Double? convert(java.lang.Float myPublicName); field public java.lang.Number? myNumber; } @@ -355,5 +354,4 @@ class ApiFromTextTest : DriverTest() { api = source ) } - } \ No newline at end of file diff --git a/src/test/java/com/android/tools/metalava/ArtifactTaggerTest.kt b/src/test/java/com/android/tools/metalava/ArtifactTaggerTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..940447c674703a1022c5468780897955019bfc23 --- /dev/null +++ b/src/test/java/com/android/tools/metalava/ArtifactTaggerTest.kt @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tools.metalava + +import org.junit.Test + +class ArtifactTaggerTest : DriverTest() { + + @Test + fun `Tag API`() { + check( + checkDoclava1 = false, + sourceFiles = *arrayOf( + java( + """ + package test.pkg.foo; + /** My Foo class documentation. */ + public class Foo { // registered in both the foo and bar libraries: should get duplicate warnings + public class Inner { + } + } + """ + ), + java( + """ + package test.pkg.bar; + /** My Bar class documentation. */ + public class Bar { + public class Inner { + } + } + """ + ), + java( + """ + package test.pkg.baz; + /** Extra class not registered in artifact files: should be flagged */ + public class Missing { + } + """ + ) + ), + artifacts = mapOf( + "my.library.group:foo:1.0.0" to """ + package test.pkg.foo { + public class Foo { + ctor public Foo(); + } + public class Foo.Inner { + ctor public Foo.Inner(); + } + } + """, + "my.library.group:bar:3.1.4" to """ + package test.pkg.bar { + public class Bar { + ctor public Bar(); + } + public class Bar.Inner { + ctor public Bar.Inner(); + } + } + package test.pkg.foo { + public class Foo { // duplicate registration: should generate warning + } + } + """ + ), + extraArguments = arrayOf("--error", "NoArtifactData,BrokenArtifactFile"), + warnings = """ + src/test/pkg/foo/Foo.java:2: error: Class test.pkg.foo.Foo belongs to multiple artifacts: my.library.group:foo:1.0.0 and my.library.group:bar:3.1.4 [BrokenArtifactFile:130] + src/test/pkg/foo/Foo.java:4: error: Class test.pkg.foo.Foo.Inner belongs to multiple artifacts: my.library.group:foo:1.0.0 and my.library.group:bar:3.1.4 [BrokenArtifactFile:130] + src/test/pkg/baz/Missing.java:2: error: No registered artifact signature file referenced class test.pkg.baz.Missing [NoArtifactData:129] + """, + stubs = arrayOf( + """ + package test.pkg.foo; + /** + * My Foo class documentation. + * @artifactId my.library.group:foo:1.0.0 + */ + @SuppressWarnings({"unchecked", "deprecation", "all"}) + public class Foo { + public Foo() { throw new RuntimeException("Stub!"); } + /** + * @artifactId my.library.group:foo:1.0.0 + */ + @SuppressWarnings({"unchecked", "deprecation", "all"}) + public class Inner { + public Inner() { throw new RuntimeException("Stub!"); } + } + } + """ + ) + + ) + } +} diff --git a/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt b/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt index ecbb3193a9daaf9d60f1e1b0463a100ac04150a7..3b06bb99e59090c526cdab10541882c02e69a09e 100644 --- a/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt +++ b/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt @@ -16,6 +16,7 @@ package com.android.tools.metalava +import org.junit.Ignore import org.junit.Test import java.io.File @@ -243,7 +244,7 @@ CompatibilityCheckTest : DriverTest() { """ @Suppress("all") package test.pkg; - import android.support.annotation.ParameterName; + import androidx.annotation.ParameterName; public class JavaClass { public String method1(String newName) { return null; } @@ -253,7 +254,7 @@ CompatibilityCheckTest : DriverTest() { ), supportParameterName ), - extraArguments = arrayOf("--hide-package", "android.support.annotation") + extraArguments = arrayOf("--hide-package", "androidx.annotation") ) } @@ -1297,12 +1298,13 @@ CompatibilityCheckTest : DriverTest() { package androidx.content { public final class ContentValuesKt { ctor public ContentValuesKt(); - method public static error.NonExistentClass contentValuesOf(kotlin.Pair<String,?>... pairs); + method public static android.content.ContentValues contentValuesOf(kotlin.Pair<String,?>... pairs); } } """, sourceFiles = *arrayOf( - kotlin("src/androidx/content/ContentValues.kt", + kotlin( + "src/androidx/content/ContentValues.kt", """ package androidx.content @@ -1334,6 +1336,7 @@ CompatibilityCheckTest : DriverTest() { ) } + @Ignore("Not currently working: we're getting the wrong PSI results; I suspect caching across the two codebases") @Test fun `Test All Android API levels`() { // Checks API across Android SDK versions and makes sure the results are @@ -1450,8 +1453,8 @@ CompatibilityCheckTest : DriverTest() { "--omit-locations", "--hide", suppressLevels[apiLevel] - ?: "AddedPackage,AddedClass,AddedMethod,AddedInterface,AddedField,ChangedDeprecated,RemovedField,RemovedClass,RemovedDeprecatedClass" - +(if ((apiLevel == 19 || apiLevel == 20) && loadPrevAsSignature) ",ChangedType" else "") + ?: "AddedPackage,AddedClass,AddedMethod,AddedInterface,AddedField,ChangedDeprecated,RemovedField,RemovedClass,RemovedDeprecatedClass" + + (if ((apiLevel == 19 || apiLevel == 20) && loadPrevAsSignature) ",ChangedType" else "") ), warnings = expected[apiLevel]?.trimIndent() ?: "", @@ -1464,14 +1467,13 @@ CompatibilityCheckTest : DriverTest() { // Check signature file checks. We have .txt files for API level 14 and up, but there are a // BUNCH of problems in older signature files that make the comparisons not work -- // missing type variables in class declarations, missing generics in method signatures, etc. - val signatureFile = File("../../prebuilts/sdk/api/${apiLevel - 1}.txt") + val signatureFile = File("../../prebuilts/sdk/${apiLevel - 1}/public/api/android.txt") if (!(signatureFile.isFile)) { println("Couldn't find $signatureFile: Check that pwd for test is correct. Skipping this test.") return } val previousSignatureApi = signatureFile.readText(Charsets.UTF_8) - check( checkDoclava1 = false, checkCompatibility = true, @@ -1479,7 +1481,7 @@ CompatibilityCheckTest : DriverTest() { "--omit-locations", "--hide", suppressLevels[apiLevel] - ?: "AddedPackage,AddedClass,AddedMethod,AddedInterface,AddedField,ChangedDeprecated,RemovedField,RemovedClass,RemovedDeprecatedClass" + ?: "AddedPackage,AddedClass,AddedMethod,AddedInterface,AddedField,ChangedDeprecated,RemovedField,RemovedClass,RemovedDeprecatedClass" ), warnings = expected[apiLevel]?.trimIndent() ?: "", previousApi = previousSignatureApi, @@ -1489,8 +1491,7 @@ CompatibilityCheckTest : DriverTest() { } } - // TODO: Check method signatures changing incompatibly (look especially out for adding new overloaded // methods and comparator getting confused!) // ..equals on the method items should actually be very useful! -} \ No newline at end of file +} diff --git a/src/test/java/com/android/tools/metalava/DocAnalyzerTest.kt b/src/test/java/com/android/tools/metalava/DocAnalyzerTest.kt index 4f6572fc367706bb302255d37e3ae33353805068..f8ad6ec8d80801ae5ef44ea7b00b99d75bb5f7e5 100644 --- a/src/test/java/com/android/tools/metalava/DocAnalyzerTest.kt +++ b/src/test/java/com/android/tools/metalava/DocAnalyzerTest.kt @@ -35,7 +35,7 @@ class DocAnalyzerTest : DriverTest() { nonNullSource, nullableSource ), - checkCompilation = false, // needs android.support annotations in classpath + checkCompilation = false, // needs androidx.annotations in classpath checkDoclava1 = false, stubs = arrayOf( """ @@ -49,19 +49,19 @@ class DocAnalyzerTest : DriverTest() { * @param factor2 This value must never be {@code null}. * @return This value may be {@code null}. */ - @android.support.annotation.Nullable public java.lang.Double method1(@android.support.annotation.NonNull java.lang.Double factor1, @android.support.annotation.NonNull java.lang.Double factor2) { throw new RuntimeException("Stub!"); } + @androidx.annotation.Nullable public java.lang.Double method1(@androidx.annotation.NonNull java.lang.Double factor1, @androidx.annotation.NonNull java.lang.Double factor2) { throw new RuntimeException("Stub!"); } /** * These are the docs for method2. It can sometimes return null. * @param factor1 This value must never be {@code null}. * @param factor2 This value must never be {@code null}. */ - @android.support.annotation.Nullable public java.lang.Double method2(@android.support.annotation.NonNull java.lang.Double factor1, @android.support.annotation.NonNull java.lang.Double factor2) { throw new RuntimeException("Stub!"); } + @androidx.annotation.Nullable public java.lang.Double method2(@androidx.annotation.NonNull java.lang.Double factor1, @androidx.annotation.NonNull java.lang.Double factor2) { throw new RuntimeException("Stub!"); } /** * @param factor1 This value must never be {@code null}. * @param factor2 This value must never be {@code null}. * @return This value may be {@code null}. */ - @android.support.annotation.Nullable public java.lang.Double method3(@android.support.annotation.NonNull java.lang.Double factor1, @android.support.annotation.NonNull java.lang.Double factor2) { throw new RuntimeException("Stub!"); } + @androidx.annotation.Nullable public java.lang.Double method3(@androidx.annotation.NonNull java.lang.Double factor1, @androidx.annotation.NonNull java.lang.Double factor2) { throw new RuntimeException("Stub!"); } } """ ) @@ -124,7 +124,7 @@ class DocAnalyzerTest : DriverTest() { ), checkCompilation = true, checkDoclava1 = false, - warnings = "src/test/pkg/Foo.java:2: lint: Replaced Andriod with Android in documentation for class test.pkg.Foo [Typo:131]", + warnings = "src/test/pkg/Foo.java:2: warning: Replaced Andriod with Android in documentation for class test.pkg.Foo [Typo:131]", stubs = arrayOf( """ package test.pkg; @@ -165,6 +165,10 @@ class DocAnalyzerTest : DriverTest() { @RequiresPermission(allOf = {Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCOUNT_MANAGER}) public void test4() { } + + @RequiresPermission(value=Manifest.permission.WATCH_APPOPS, conditional=true) // b/73559440 + public void test5() { + } } """ ), @@ -177,13 +181,14 @@ class DocAnalyzerTest : DriverTest() { public static final String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION"; public static final String ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION"; public static final String ACCOUNT_MANAGER = "android.permission.ACCOUNT_MANAGER"; + public static final String WATCH_APPOPS = "android.permission.WATCH_APPOPS"; } } """ ), requiresPermissionSource ), - checkCompilation = false, // needs android.support annotations in classpath + checkCompilation = false, // needs androidx.annotations in classpath checkDoclava1 = false, stubs = arrayOf( """ @@ -195,19 +200,20 @@ class DocAnalyzerTest : DriverTest() { /** * Requires {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} */ - @android.support.annotation.RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void test1() { throw new RuntimeException("Stub!"); } + @androidx.annotation.RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void test1() { throw new RuntimeException("Stub!"); } /** * Requires {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} */ - @android.support.annotation.RequiresPermission(allOf=android.Manifest.permission.ACCESS_COARSE_LOCATION) public void test2() { throw new RuntimeException("Stub!"); } + @androidx.annotation.RequiresPermission(allOf=android.Manifest.permission.ACCESS_COARSE_LOCATION) public void test2() { throw new RuntimeException("Stub!"); } /** * Requires {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} or {@link android.Manifest.permission#ACCESS_FINE_LOCATION} */ - @android.support.annotation.RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void test3() { throw new RuntimeException("Stub!"); } + @androidx.annotation.RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void test3() { throw new RuntimeException("Stub!"); } /** * Requires {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} and {@link android.Manifest.permission#ACCOUNT_MANAGER} */ - @android.support.annotation.RequiresPermission(allOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCOUNT_MANAGER}) public void test4() { throw new RuntimeException("Stub!"); } + @androidx.annotation.RequiresPermission(allOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCOUNT_MANAGER}) public void test4() { throw new RuntimeException("Stub!"); } + @androidx.annotation.RequiresPermission(value=android.Manifest.permission.WATCH_APPOPS, conditional=true) public void test5() { throw new RuntimeException("Stub!"); } } """ ) @@ -251,15 +257,15 @@ class DocAnalyzerTest : DriverTest() { * @param range2 Value is 20 or greater * @return Value is 10 or greater */ - @android.support.annotation.IntRange(from=10) public int test1(@android.support.annotation.IntRange(from=20) int range2) { throw new RuntimeException("Stub!"); } + @androidx.annotation.IntRange(from=10) public int test1(@androidx.annotation.IntRange(from=20) int range2) { throw new RuntimeException("Stub!"); } /** * @return Value is between 10 and 20 inclusive */ - @android.support.annotation.IntRange(from=10, to=20) public int test2() { throw new RuntimeException("Stub!"); } + @androidx.annotation.IntRange(from=10, to=20) public int test2() { throw new RuntimeException("Stub!"); } /** * @return Value is 100 or less */ - @android.support.annotation.IntRange(to=100) public int test3() { throw new RuntimeException("Stub!"); } + @androidx.annotation.IntRange(to=100) public int test3() { throw new RuntimeException("Stub!"); } } """ ) @@ -273,8 +279,8 @@ class DocAnalyzerTest : DriverTest() { java( """ package test.pkg; - import android.support.annotation.UiThread; - import android.support.annotation.WorkerThread; + import androidx.annotation.UiThread; + import androidx.annotation.WorkerThread; @UiThread public class RangeTest { @WorkerThread @@ -294,11 +300,11 @@ class DocAnalyzerTest : DriverTest() { * this UI element, unless otherwise noted. This is typically the * main thread of your app. * */ @SuppressWarnings({"unchecked", "deprecation", "all"}) - @android.support.annotation.UiThread public class RangeTest { + @androidx.annotation.UiThread public class RangeTest { public RangeTest() { throw new RuntimeException("Stub!"); } /** This method may take several seconds to complete, so it should * only be called from a worker thread. */ - @android.support.annotation.WorkerThread public int test1() { throw new RuntimeException("Stub!"); } + @androidx.annotation.WorkerThread public int test1() { throw new RuntimeException("Stub!"); } } """ ) @@ -312,8 +318,8 @@ class DocAnalyzerTest : DriverTest() { java( """ package test.pkg; - import android.support.annotation.UiThread; - import android.support.annotation.WorkerThread; + import androidx.annotation.UiThread; + import androidx.annotation.WorkerThread; public class RangeTest { @UiThread @WorkerThread public int test1() { } @@ -337,7 +343,7 @@ class DocAnalyzerTest : DriverTest() { * This method may take several seconds to complete, so it should * * only be called from a worker thread. */ - @android.support.annotation.UiThread @android.support.annotation.WorkerThread public int test1() { throw new RuntimeException("Stub!"); } + @androidx.annotation.UiThread @androidx.annotation.WorkerThread public int test1() { throw new RuntimeException("Stub!"); } } """ ) @@ -498,7 +504,7 @@ class DocAnalyzerTest : DriverTest() { /** * Requires {@link test.pkg.RangeTest#ACCESS_COARSE_LOCATION} */ - @android.support.annotation.RequiresPermission(test.pkg.RangeTest.ACCESS_COARSE_LOCATION) public void test1() { throw new RuntimeException("Stub!"); } + @androidx.annotation.RequiresPermission(test.pkg.RangeTest.ACCESS_COARSE_LOCATION) public void test1() { throw new RuntimeException("Stub!"); } public static final java.lang.String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION"; } """ @@ -535,7 +541,7 @@ class DocAnalyzerTest : DriverTest() { /** * Requires "MyPermission" */ - @android.support.annotation.RequiresPermission("MyPermission") public void test1() { throw new RuntimeException("Stub!"); } + @androidx.annotation.RequiresPermission("MyPermission") public void test1() { throw new RuntimeException("Stub!"); } } """ ) @@ -573,7 +579,7 @@ class DocAnalyzerTest : DriverTest() { * This is the existing documentation. * Requires {@link test.pkg.RangeTest#ACCESS_COARSE_LOCATION} */ - @android.support.annotation.RequiresPermission(test.pkg.RangeTest.ACCESS_COARSE_LOCATION) public int test1() { throw new RuntimeException("Stub!"); } + @androidx.annotation.RequiresPermission(test.pkg.RangeTest.ACCESS_COARSE_LOCATION) public int test1() { throw new RuntimeException("Stub!"); } public static final java.lang.String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION"; } """ @@ -616,7 +622,7 @@ class DocAnalyzerTest : DriverTest() { * Multiple lines of it. * Requires {@link test.pkg.RangeTest#ACCESS_COARSE_LOCATION} */ - @android.support.annotation.RequiresPermission(test.pkg.RangeTest.ACCESS_COARSE_LOCATION) public int test1() { throw new RuntimeException("Stub!"); } + @androidx.annotation.RequiresPermission(test.pkg.RangeTest.ACCESS_COARSE_LOCATION) public int test1() { throw new RuntimeException("Stub!"); } public static final java.lang.String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION"; } """ @@ -650,7 +656,7 @@ class DocAnalyzerTest : DriverTest() { /** * @param parameter2 Value is 10 or greater */ - public int test1(int parameter1, @android.support.annotation.IntRange(from=10) int parameter2, int parameter3) { throw new RuntimeException("Stub!"); } + public int test1(int parameter1, @androidx.annotation.IntRange(from=10) int parameter2, int parameter3) { throw new RuntimeException("Stub!"); } } """ ) @@ -698,7 +704,7 @@ class DocAnalyzerTest : DriverTest() { * @param parameter3 docs for parameter2 * @return return value documented here */ - @android.support.annotation.RequiresPermission(test.pkg.RangeTest.ACCESS_COARSE_LOCATION) public int test1(int parameter1, int parameter2, int parameter3) { throw new RuntimeException("Stub!"); } + @androidx.annotation.RequiresPermission(test.pkg.RangeTest.ACCESS_COARSE_LOCATION) public int test1(int parameter1, int parameter2, int parameter3) { throw new RuntimeException("Stub!"); } public static final java.lang.String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION"; } """ @@ -738,7 +744,7 @@ class DocAnalyzerTest : DriverTest() { * @param parameter2 Value is 10 or greater * @return return value documented here */ - public int test1(int parameter1, @android.support.annotation.IntRange(from=10) int parameter2, int parameter3) { throw new RuntimeException("Stub!"); } + public int test1(int parameter1, @androidx.annotation.IntRange(from=10) int parameter2, int parameter3) { throw new RuntimeException("Stub!"); } } """ ) @@ -781,7 +787,7 @@ class DocAnalyzerTest : DriverTest() { * @param parameter2 Value is 10 or greater * @return return value documented here */ - public int test1(int parameter1, @android.support.annotation.IntRange(from=10) int parameter2, int parameter3) { throw new RuntimeException("Stub!"); } + public int test1(int parameter1, @androidx.annotation.IntRange(from=10) int parameter2, int parameter3) { throw new RuntimeException("Stub!"); } } """ ) @@ -826,7 +832,7 @@ class DocAnalyzerTest : DriverTest() { * @param parameter3 docs for parameter2 * @return return value documented here */ - public int test1(int parameter1, @android.support.annotation.IntRange(from=10) int parameter2, int parameter3) { throw new RuntimeException("Stub!"); } + public int test1(int parameter1, @androidx.annotation.IntRange(from=10) int parameter2, int parameter3) { throw new RuntimeException("Stub!"); } } """ ) @@ -860,7 +866,7 @@ class DocAnalyzerTest : DriverTest() { /** * @return Value is 10 or greater */ - @android.support.annotation.IntRange(from=10) public int test1(int parameter1, int parameter2, int parameter3) { throw new RuntimeException("Stub!"); } + @androidx.annotation.IntRange(from=10) public int test1(int parameter1, int parameter2, int parameter3) { throw new RuntimeException("Stub!"); } } """ ) @@ -900,7 +906,7 @@ class DocAnalyzerTest : DriverTest() { * @return return value documented here * Value is 10 or greater */ - @android.support.annotation.IntRange(from=10) public int test1(int parameter1, int parameter2, int parameter3) { throw new RuntimeException("Stub!"); } + @androidx.annotation.IntRange(from=10) public int test1(int parameter1, int parameter2, int parameter3) { throw new RuntimeException("Stub!"); } } """ ) @@ -1045,7 +1051,6 @@ class DocAnalyzerTest : DriverTest() { ) } - @Test fun `Generate overview html docs`() { // If a codebase provides overview.html files in the a public package, @@ -1123,6 +1128,40 @@ class DocAnalyzerTest : DriverTest() { ) } + @Test + fun `Check RequiresApi handling`() { + check( + sourceFiles = *arrayOf( + java( + """ + package test.pkg; + import androidx.annotation.RequiresApi; + @RequiresApi(value = 21) + public class MyClass1 { + } + """ + ), + + requiresApiSource + ), + checkCompilation = true, + checkDoclava1 = false, + stubs = arrayOf( + """ + package test.pkg; + /** + * Requires API level 21 + * @since 5.0 Lollipop (21) + */ + @SuppressWarnings({"unchecked", "deprecation", "all"}) + @androidx.annotation.RequiresApi(21) public class MyClass1 { + public MyClass1() { throw new RuntimeException("Stub!"); } + } + """ + ) + ) + } + @Test fun `Invoke external documentation tool`() { val jdkPath = getJdkPath() diff --git a/src/test/java/com/android/tools/metalava/DriverTest.kt b/src/test/java/com/android/tools/metalava/DriverTest.kt index 2cd3e751cf26fb467b09425669d5daab01b9fe85..13ad8cdae9a2fc038c1f9b80acc413bb85cea038 100644 --- a/src/test/java/com/android/tools/metalava/DriverTest.kt +++ b/src/test/java/com/android/tools/metalava/DriverTest.kt @@ -20,7 +20,6 @@ import com.android.SdkConstants import com.android.SdkConstants.DOT_JAVA import com.android.SdkConstants.DOT_KT import com.android.SdkConstants.VALUE_TRUE -import com.android.annotations.NonNull import com.android.ide.common.process.DefaultProcessExecutor import com.android.ide.common.process.LoggedProcessOutputHandler import com.android.ide.common.process.ProcessException @@ -33,8 +32,11 @@ import com.android.tools.lint.checks.infrastructure.TestFiles.java import com.android.tools.lint.checks.infrastructure.stripComments import com.android.tools.metalava.doclava1.Errors import com.android.utils.FileUtils +import com.android.utils.SdkUtils import com.android.utils.StdLogger import com.google.common.base.Charsets +import com.google.common.io.ByteStreams +import com.google.common.io.Closeables import com.google.common.io.Files import org.intellij.lang.annotations.Language import org.junit.Assert.assertEquals @@ -46,6 +48,7 @@ import org.junit.rules.TemporaryFolder import java.io.File import java.io.PrintWriter import java.io.StringWriter +import java.net.URL const val CHECK_OLD_DOCLAVA_TOO = false const val CHECK_STUB_COMPILATION = false @@ -124,14 +127,21 @@ abstract class DriverTest { exactApi: String? = null, /** The removed API (corresponds to --removed-api) */ removedApi: String? = null, + /** The removed dex API (corresponds to --removed-dex-api) */ + removedDexApi: String? = null, /** The private API (corresponds to --private-api) */ privateApi: String? = null, /** The private DEX API (corresponds to --private-dex-api) */ privateDexApi: String? = null, + /** The DEX API (corresponds to --dex-api) */ + dexApi: String? = null, /** Expected stubs (corresponds to --stubs) */ @Language("JAVA") stubs: Array<String> = emptyArray(), /** Stub source file list generated */ stubsSourceList: String? = null, + /** Whether the stubs should be written as documentation stubs instead of plain stubs. Decides + * whether the stubs include @doconly elements, uses rewritten/migration annotations, etc */ + docStubs: Boolean = false, /** Whether to run in doclava1 compat mode */ compatibilityMode: Boolean = true, /** Whether to trim the output (leading/trailing whitespace removal) */ @@ -143,8 +153,12 @@ abstract class DriverTest { /** Whether to run doclava1 on the test output and assert that the output is identical */ checkDoclava1: Boolean = compatibilityMode, checkCompilation: Boolean = false, - /** Annotations to merge in */ - @Language("XML") mergeAnnotations: String? = null, + /** Annotations to merge in (in .xml format) */ + @Language("XML") mergeXmlAnnotations: String? = null, + /** Annotations to merge in (in .jaif format) */ + @Language("TEXT") mergeJaifAnnotations: String? = null, + /** Annotations to merge in (in .txt/.signature format) */ + @Language("TEXT") mergeSignatureAnnotations: String? = null, /** An optional API signature file content to load **instead** of Java/Kotlin source files */ @Language("TEXT") signatureSource: String? = null, /** An optional API jar file content to load **instead** of Java/Kotlin source files */ @@ -201,17 +215,33 @@ abstract class DriverTest { /** Corresponds to SDK constants file features.txt */ sdk_features: String? = null, /** Corresponds to SDK constants file widgets.txt */ - sdk_widgets: String? = null + sdk_widgets: String? = null, + /** Map from artifact id to artifact descriptor */ + artifacts: Map<String, String>? = null, + /** Extract annotations and check that the given packages contain the given extracted XML files */ + extractAnnotations: Map<String, String>? = null ) { System.setProperty("METALAVA_TESTS_RUNNING", VALUE_TRUE) - if (compatibilityMode && mergeAnnotations != null) { + if (compatibilityMode && mergeXmlAnnotations != null) { fail( "Can't specify both compatibilityMode and mergeAnnotations: there were no " + - "annotations output in doclava1" + "annotations output in doclava1" + ) + } + if (compatibilityMode && mergeJaifAnnotations != null) { + fail( + "Can't specify both compatibilityMode and mergeJaifAnnotations: there were no " + + "annotations output in doclava1" ) } + if (compatibilityMode && mergeSignatureAnnotations != null) { + fail( + "Can't specify both compatibilityMode and mergeSignatureAnnotations: there were no " + + "annotations output in doclava1" + ) + } Errors.resetLevels() /** Expected output if exiting with an error code */ @@ -233,7 +263,7 @@ abstract class DriverTest { val sourceList = if (signatureSource != null) { sourcePathDir.mkdirs() - assert(sourceFiles.isEmpty(), { "Shouldn't combine sources with signature file loads" }) + assert(sourceFiles.isEmpty()) { "Shouldn't combine sources with signature file loads" } val signatureFile = File(project, "load-api.txt") Files.asCharSink(signatureFile, Charsets.UTF_8).write(signatureSource.trimIndent()) if (includeStrippedSuperclassWarnings) { @@ -247,7 +277,7 @@ abstract class DriverTest { } } else if (apiJar != null) { sourcePathDir.mkdirs() - assert(sourceFiles.isEmpty(), { "Shouldn't combine sources with API jar file loads" }) + assert(sourceFiles.isEmpty()) { "Shouldn't combine sources with API jar file loads" } arrayOf(apiJar.path) } else { sourceFiles.asSequence().map { File(project, it.targetPath).path }.toList().toTypedArray() @@ -260,9 +290,25 @@ abstract class DriverTest { } } - val mergeAnnotationsArgs = if (mergeAnnotations != null) { + val mergeAnnotationsArgs = if (mergeXmlAnnotations != null) { val merged = File(project, "merged-annotations.xml") - Files.asCharSink(merged, Charsets.UTF_8).write(mergeAnnotations.trimIndent()) + Files.asCharSink(merged, Charsets.UTF_8).write(mergeXmlAnnotations.trimIndent()) + arrayOf("--merge-annotations", merged.path) + } else { + emptyArray() + } + + val jaifAnnotationsArgs = if (mergeJaifAnnotations != null) { + val merged = File(project, "merged-annotations.jaif") + Files.asCharSink(merged, Charsets.UTF_8).write(mergeJaifAnnotations.trimIndent()) + arrayOf("--merge-annotations", merged.path) + } else { + emptyArray() + } + + val signatureAnnotationsArgs = if (mergeSignatureAnnotations != null) { + val merged = File(project, "merged-annotations.txt") + Files.asCharSink(merged, Charsets.UTF_8).write(mergeSignatureAnnotations.trimIndent()) arrayOf("--merge-annotations", merged.path) } else { emptyArray() @@ -348,6 +394,10 @@ abstract class DriverTest { args.add("--show-annotation") args.add("android.annotation.SystemApi") } + if (includeSystemApiAnnotations && !args.contains("android.annotation.SystemService")) { + args.add("--show-annotation") + args.add("android.annotation.SystemService") + } args.toTypedArray() } else { emptyArray() @@ -368,9 +418,17 @@ abstract class DriverTest { emptyArray() } + var removedDexApiFile: File? = null + val removedDexArgs = if (removedDexApi != null) { + removedDexApiFile = temporaryFolder.newFile("removed-dex.txt") + arrayOf("--removed-dex-api", removedDexApiFile.path) + } else { + emptyArray() + } + var apiFile: File? = null val apiArgs = if (api != null) { - apiFile = temporaryFolder.newFile("api.txt") + apiFile = temporaryFolder.newFile("public-api.txt") arrayOf("--api", apiFile.path) } else { emptyArray() @@ -392,6 +450,14 @@ abstract class DriverTest { emptyArray() } + var dexApiFile: File? = null + val dexApiArgs = if (dexApi != null) { + dexApiFile = temporaryFolder.newFile("public-dex.txt") + arrayOf("--dex-api", dexApiFile.path) + } else { + emptyArray() + } + var privateDexApiFile: File? = null val privateDexApiArgs = if (privateDexApi != null) { privateDexApiFile = temporaryFolder.newFile("private-dex.txt") @@ -403,7 +469,11 @@ abstract class DriverTest { var stubsDir: File? = null val stubsArgs = if (stubs.isNotEmpty()) { stubsDir = temporaryFolder.newFolder("stubs") - arrayOf("--stubs", stubsDir.path) + if (docStubs) { + arrayOf("--doc-stubs", stubsDir.path) + } else { + arrayOf("--stubs", stubsDir.path) + } } else { emptyArray() } @@ -465,6 +535,32 @@ abstract class DriverTest { sdkFilesDir = null } + val artifactArgs = if (artifacts != null) { + val args = mutableListOf<String>() + var index = 1 + for ((artifactId, signatures) in artifacts) { + val signatureFile = temporaryFolder.newFile("signature-file-$index.txt") + Files.asCharSink(signatureFile, Charsets.UTF_8).write(signatures.trimIndent()) + index++ + + args.add("--register-artifact") + args.add(signatureFile.path) + args.add(artifactId) + } + args.toTypedArray() + } else { + emptyArray() + } + + val extractedAnnotationsZip: File? + val extractAnnotationsArgs = if (extractAnnotations != null) { + extractedAnnotationsZip = temporaryFolder.newFile("extracted-annotations.zip") + arrayOf("--extract-annotations", extractedAnnotationsZip.path) + } else { + extractedAnnotationsZip = null + emptyArray() + } + val actualOutput = runDriver( "--no-color", "--no-banner", @@ -472,7 +568,7 @@ abstract class DriverTest { // For the tests we want to treat references to APIs like java.io.Closeable // as a class that is part of the API surface, not as a hidden class as would // be the case when analyzing a complete API surface - //"--unhide-classpath-classes", + // "--unhide-classpath-classes", "--allow-referencing-unknown-classes", // Annotation generation temporarily turned off by default while integrating with @@ -485,9 +581,11 @@ abstract class DriverTest { androidJar.path, *kotlinPathArgs, *removedArgs, + *removedDexArgs, *apiArgs, *exactApiArgs, *privateApiArgs, + *dexApiArgs, *privateDexApiArgs, *stubsArgs, *stubsSourceListArgs, @@ -498,6 +596,8 @@ abstract class DriverTest { *coverageStats, *quiet, *mergeAnnotationsArgs, + *jaifAnnotationsArgs, + *signatureAnnotationsArgs, *previousApiArgs, *migrateNullsArguments, *checkCompatibilityArguments, @@ -509,6 +609,8 @@ abstract class DriverTest { *sdkFilesArgs, *importedPackageArgs.toTypedArray(), *skipEmitPackagesArgs.toTypedArray(), + *artifactArgs, + *extractAnnotationsArgs, *sourceList, *extraArguments, expectedFail = expectedFail @@ -533,6 +635,15 @@ abstract class DriverTest { assertEquals(stripComments(removedApi, stripLineComments = false).trimIndent(), expectedText) } + if (removedDexApi != null && removedDexApiFile != null) { + assertTrue( + "${removedDexApiFile.path} does not exist even though --removed-dex-api was used", + removedDexApiFile.exists() + ) + val expectedText = readFile(removedDexApiFile, stripBlankLines, trim) + assertEquals(stripComments(removedDexApi, stripLineComments = false).trimIndent(), expectedText) + } + if (exactApi != null && exactApiFile != null) { assertTrue( "${exactApiFile.path} does not exist even though --exact-api was used", @@ -551,6 +662,15 @@ abstract class DriverTest { assertEquals(stripComments(privateApi, stripLineComments = false).trimIndent(), expectedText) } + if (dexApi != null && dexApiFile != null) { + assertTrue( + "${dexApiFile.path} does not exist even though --dex-api was used", + dexApiFile.exists() + ) + val expectedText = readFile(dexApiFile, stripBlankLines, trim) + assertEquals(stripComments(dexApi, stripLineComments = false).trimIndent(), expectedText) + } + if (privateDexApi != null && privateDexApiFile != null) { assertTrue( "${privateDexApiFile.path} does not exist even though --private-dex-api was used", @@ -609,6 +729,16 @@ abstract class DriverTest { ) } + if (extractAnnotations != null && extractedAnnotationsZip != null) { + assertTrue( + "Using --extract-annotations but $extractedAnnotationsZip was not created", + extractedAnnotationsZip.isFile + ) + for ((pkg, xml) in extractAnnotations) { + assertPackageXml(pkg, extractedAnnotationsZip, xml) + } + } + if (stubs.isNotEmpty() && stubsDir != null) { for (i in 0 until stubs.size) { val stub = stubs[i] @@ -638,13 +768,6 @@ abstract class DriverTest { val generated = gatherSources(listOf(stubsDir)).map { it.path }.toList().toTypedArray() // Also need to include on the compile path annotation classes referenced in the stubs - val supportAnnotationsDir = File("../../frameworks/support/annotations/src/main/java/") - if (!supportAnnotationsDir.isDirectory) { - fail("Couldn't find $supportAnnotationsDir: Is the pwd set to the root of the metalava source code?") - } - val supportAnnotations = - gatherSources(listOf(supportAnnotationsDir)).map { it.path }.toList().toTypedArray() - val extraAnnotationsDir = File("stub-annotations/src/main/java") if (!extraAnnotationsDir.isDirectory) { fail("Couldn't find $extraAnnotationsDir: Is the pwd set to the root of the metalava source code?") @@ -652,11 +775,9 @@ abstract class DriverTest { } val extraAnnotations = gatherSources(listOf(extraAnnotationsDir)).map { it.path }.toList().toTypedArray() - if (!runCommand( "${getJdkPath()}/bin/javac", arrayOf( - "-d", project.path, *generated, - *supportAnnotations, *extraAnnotations + "-d", project.path, *generated, *extraAnnotations ) ) ) { @@ -668,7 +789,7 @@ abstract class DriverTest { if (checkDoclava1 && !CHECK_OLD_DOCLAVA_TOO) { println( "This test requested diffing with doclava1, but doclava1 testing was disabled with the " + - "DriverTest#CHECK_OLD_DOCLAVA_TOO = false" + "DriverTest#CHECK_OLD_DOCLAVA_TOO = false" ) } @@ -714,8 +835,8 @@ abstract class DriverTest { ) } - if (CHECK_OLD_DOCLAVA_TOO && checkDoclava1 && signatureSource == null - && removedApi != null && removedApiFile != null + if (CHECK_OLD_DOCLAVA_TOO && checkDoclava1 && signatureSource == null && + removedApi != null && removedApiFile != null ) { removedApiFile.delete() checkSignaturesWithDoclava1( @@ -819,6 +940,48 @@ abstract class DriverTest { showUnannotated = showUnannotated ) } + + if (CHECK_OLD_DOCLAVA_TOO && checkDoclava1 && signatureSource == null && + dexApi != null && dexApiFile != null + ) { + dexApiFile.delete() + checkSignaturesWithDoclava1( + api = dexApi, + argument = "-dexApi", + output = dexApiFile, + expected = dexApiFile, + sourceList = sourceList, + sourcePath = sourcePath, + packages = packages, + androidJar = androidJar, + trim = trim, + stripBlankLines = stripBlankLines, + showAnnotationArgs = showAnnotationArguments, + stubImportPackages = importedPackages, + // Workaround: -dexApi is a no-op if you don't also provide -api + extraArguments = arrayOf("-api", File(dexApiFile.parentFile, "dummy-api.txt").path), + showUnannotated = showUnannotated + ) + } + } + + /** Checks that the given zip annotations file contains the given XML package contents */ + private fun assertPackageXml(pkg: String, output: File, @Language("XML") expected: String) { + assertNotNull(output) + assertTrue(output.exists()) + val url = URL( + "jar:" + SdkUtils.fileToUrlString(output) + "!/" + pkg.replace('.', '/') + + "/annotations.xml" + ) + val stream = url.openStream() + try { + val bytes = ByteStreams.toByteArray(stream) + assertNotNull(bytes) + val xml = String(bytes, Charsets.UTF_8).replace("\r\n", "\n") + assertEquals(expected.trimIndent().trim(), xml.trimIndent().trim()) + } finally { + Closeables.closeQuietly(stream) + } } private fun checkSignaturesWithDoclava1( @@ -931,11 +1094,9 @@ abstract class DriverTest { output.path ) - val message = "\n${args.joinToString(separator = "\n") { "\"$it\"," }}" println("Running doclava1 with the following args:\n$message") - if (!runCommand( "$jdkPath/bin/java", arrayOf( @@ -982,11 +1143,11 @@ abstract class DriverTest { private val sdk: File get() = File( System.getenv("ANDROID_HOME") - ?: error("You must set \$ANDROID_HOME before running tests") + ?: error("You must set \$ANDROID_HOME before running tests") ) fun getAndroidJar(apiLevel: Int): File? { - val localFile = File("../../prebuilts/sdk/$apiLevel/android.jar") + val localFile = File("../../prebuilts/sdk/$apiLevel/public/android.jar") if (localFile.exists()) { return localFile } @@ -1058,6 +1219,23 @@ val intDefAnnotationSource: TestFile = java( @Retention(SOURCE) @Target({ANNOTATION_TYPE}) public @interface IntDef { + int[] value() default {}; + boolean flag() default false; + } + """ +).indented() + +val longDefAnnotationSource: TestFile = java( + """ + package android.annotation; + import java.lang.annotation.Retention; + import java.lang.annotation.RetentionPolicy; + import java.lang.annotation.Target; + import static java.lang.annotation.ElementType.*; + import static java.lang.annotation.RetentionPolicy.SOURCE; + @Retention(SOURCE) + @Target({ANNOTATION_TYPE}) + public @interface LongDef { long[] value() default {}; boolean flag() default false; } @@ -1088,6 +1266,37 @@ val nonNullSource: TestFile = java( """ ).indented() +val libcoreNonNullSource: TestFile = DriverTest.java( + """ + package libcore.util; + import static java.lang.annotation.ElementType.*; + import static java.lang.annotation.RetentionPolicy.SOURCE; + import java.lang.annotation.*; + @Documented + @Retention(SOURCE) + @Target({TYPE_USE}) + public @interface NonNull { + int from() default Integer.MIN_VALUE; + int to() default Integer.MAX_VALUE; + } + """ +).indented() + +val libcoreNullableSource: TestFile = DriverTest.java( + """ + package libcore.util; + import static java.lang.annotation.ElementType.*; + import static java.lang.annotation.RetentionPolicy.SOURCE; + import java.lang.annotation.*; + @Documented + @Retention(SOURCE) + @Target({TYPE_USE}) + public @interface Nullable { + int from() default Integer.MIN_VALUE; + int to() default Integer.MAX_VALUE; + } + """ +).indented() val requiresPermissionSource: TestFile = java( """ package android.annotation; @@ -1119,6 +1328,21 @@ val requiresFeatureSource: TestFile = java( """ ).indented() +val requiresApiSource: TestFile = java( + """ + package androidx.annotation; + import java.lang.annotation.*; + import static java.lang.annotation.ElementType.*; + import static java.lang.annotation.RetentionPolicy.SOURCE; + @Retention(SOURCE) + @Target({TYPE,FIELD,METHOD,CONSTRUCTOR}) + public @interface RequiresApi { + int value() default 1; + int api() default 1; + } + """ +).indented() + val sdkConstantSource: TestFile = java( """ package android.annotation; @@ -1127,7 +1351,7 @@ val sdkConstantSource: TestFile = java( @Retention(RetentionPolicy.SOURCE) public @interface SdkConstant { enum SdkConstantType { - ACTIVITY_INTENT_ACTION, BROADCAST_INTENT_ACTION, SERVICE_ACTION, INTENT_CATEGORY, FEATURE; + ACTIVITY_INTENT_ACTION, BROADCAST_INTENT_ACTION, SERVICE_ACTION, INTENT_CATEGORY, FEATURE } SdkConstantType value(); } @@ -1172,7 +1396,7 @@ val nullableSource: TestFile = java( val supportNonNullSource: TestFile = java( """ - package android.support.annotation; + package androidx.annotation; import java.lang.annotation.*; import static java.lang.annotation.ElementType.*; import static java.lang.annotation.RetentionPolicy.SOURCE; @@ -1186,7 +1410,35 @@ val supportNonNullSource: TestFile = java( val supportNullableSource: TestFile = java( """ -package android.support.annotation; +package androidx.annotation; +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.SOURCE; +@SuppressWarnings("WeakerAccess") +@Retention(SOURCE) +@Target({METHOD, PARAMETER, FIELD, TYPE_USE}) +public @interface Nullable { +} + """ +) + +val androidxNonNullSource: TestFile = java( + """ + package androidx.annotation; + import java.lang.annotation.*; + import static java.lang.annotation.ElementType.*; + import static java.lang.annotation.RetentionPolicy.SOURCE; + @SuppressWarnings("WeakerAccess") + @Retention(SOURCE) + @Target({METHOD, PARAMETER, FIELD, TYPE_USE}) + public @interface NonNull { + } + """ +).indented() + +val androidxNullableSource: TestFile = java( + """ +package androidx.annotation; import java.lang.annotation.*; import static java.lang.annotation.ElementType.*; import static java.lang.annotation.RetentionPolicy.SOURCE; @@ -1200,7 +1452,7 @@ public @interface Nullable { val supportParameterName: TestFile = java( """ - package android.support.annotation; + package androidx.annotation; import java.lang.annotation.*; import static java.lang.annotation.ElementType.*; import static java.lang.annotation.RetentionPolicy.SOURCE; @@ -1215,7 +1467,7 @@ val supportParameterName: TestFile = java( val supportDefaultValue: TestFile = java( """ - package android.support.annotation; + package androidx.annotation; import java.lang.annotation.*; import static java.lang.annotation.ElementType.*; import static java.lang.annotation.RetentionPolicy.SOURCE; @@ -1230,7 +1482,7 @@ val supportDefaultValue: TestFile = java( val uiThreadSource: TestFile = java( """ - package android.support.annotation; + package androidx.annotation; import java.lang.annotation.*; import static java.lang.annotation.ElementType.*; import static java.lang.annotation.RetentionPolicy.SOURCE; @@ -1254,7 +1506,7 @@ val uiThreadSource: TestFile = java( val workerThreadSource: TestFile = java( """ - package android.support.annotation; + package androidx.annotation; import java.lang.annotation.*; import static java.lang.annotation.ElementType.*; import static java.lang.annotation.RetentionPolicy.SOURCE; @@ -1313,6 +1565,18 @@ val systemApiSource: TestFile = java( """ ).indented() +val testApiSource: TestFile = java( + """ + package android.annotation; + import static java.lang.annotation.ElementType.*; + import java.lang.annotation.*; + @Target({TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE}) + @Retention(RetentionPolicy.SOURCE) + public @interface TestApi { + } + """ +).indented() + val widgetSource: TestFile = java( """ package android.annotation; @@ -1323,4 +1587,3 @@ val widgetSource: TestFile = java( } """ ).indented() - diff --git a/src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt b/src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt index 517abd79b521c6a78c1a9b433877f2fc21fc95c1..427e41d9b513cb2b47484c49c830ebf2f85b8ff0 100644 --- a/src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt +++ b/src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt @@ -16,417 +16,206 @@ package com.android.tools.metalava -import com.android.utils.SdkUtils.fileToUrlString -import com.google.common.base.Charsets -import com.google.common.io.ByteStreams -import com.google.common.io.Closeables -import com.google.common.io.Files -import org.intellij.lang.annotations.Language -import org.junit.Assert.assertEquals -import org.junit.Assert.assertNotNull -import org.junit.Assert.assertTrue +import org.junit.Ignore import org.junit.Test -import java.io.File -import java.net.URL @SuppressWarnings("ALL") // Sample code class ExtractAnnotationsTest : DriverTest() { @Test - fun `Include class retention`() { - val androidJar = getPlatformFile("android.jar") + fun `Check java typedef extraction and warning about non-source retention of typedefs`() { + check( + sourceFiles = *arrayOf( + java( + """ + package test.pkg; - val project = createProject( - packageTest, - genericTest, - intDefTest, - permissionsTest, - manifest, - intDefAnnotation, - intRangeAnnotation, - permissionAnnotation, - nullableAnnotation - ) + import android.annotation.IntDef; + import android.annotation.IntRange; - val output = temporaryFolder.newFile("annotations.zip") + import java.lang.annotation.Retention; + import java.lang.annotation.RetentionPolicy; - runDriver( - "--sources", - File(project, "src").path, - "--classpath", - androidJar.path, - "--extract-annotations", - output.path - ) + @SuppressWarnings({"UnusedDeclaration", "WeakerAccess"}) + public class IntDefTest { + @IntDef({STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT}) + @IntRange(from = 20) + private @interface DialogStyle {} - // Check extracted annotations - checkPackageXml( - "test.pkg", output, """<?xml version="1.0" encoding="UTF-8"?> -<root> - <item name="test.pkg"> - <annotation name="android.support.annotation.IntRange"> - <val name="from" val="20" /> - </annotation> - </item> - <item name="test.pkg.IntDefTest void setFlags(java.lang.Object, int) 1"> - <annotation name="android.support.annotation.IntDef"> - <val name="value" val="{test.pkg.IntDefTest.STYLE_NORMAL, test.pkg.IntDefTest.STYLE_NO_TITLE, test.pkg.IntDefTest.STYLE_NO_FRAME, test.pkg.IntDefTest.STYLE_NO_INPUT, 3, 4}" /> - <val name="flag" val="true" /> - </annotation> - </item> - <item name="test.pkg.IntDefTest void setStyle(int, int) 0"> - <annotation name="android.support.annotation.IntDef"> - <val name="value" val="{test.pkg.IntDefTest.STYLE_NORMAL, test.pkg.IntDefTest.STYLE_NO_TITLE, test.pkg.IntDefTest.STYLE_NO_FRAME, test.pkg.IntDefTest.STYLE_NO_INPUT}" /> - </annotation> - <annotation name="android.support.annotation.IntRange"> - <val name="from" val="20" /> - </annotation> - </item> - <item name="test.pkg.IntDefTest.Inner void setInner(int) 0"> - <annotation name="android.support.annotation.IntDef"> - <val name="value" val="{test.pkg.IntDefTest.STYLE_NORMAL, test.pkg.IntDefTest.STYLE_NO_TITLE, test.pkg.IntDefTest.STYLE_NO_FRAME, test.pkg.IntDefTest.STYLE_NO_INPUT, 3, 4}" /> - <val name="flag" val="true" /> - </annotation> - </item> - <item name="test.pkg.MyEnhancedList"> - <annotation name="android.support.annotation.IntRange"> - <val name="from" val="0" /> - </annotation> - </item> - <item name="test.pkg.MyEnhancedList E getReversed(java.util.List<java.lang.String>, java.util.Comparator<? super E>)"> - <annotation name="android.support.annotation.IntRange"> - <val name="from" val="10" /> - </annotation> - </item> - <item name="test.pkg.MyEnhancedList java.lang.String getPrefix()"> - <annotation name="android.support.annotation.Nullable" /> - </item> - <item name="test.pkg.PermissionsTest CONTENT_URI"> - <annotation name="android.support.annotation.RequiresPermission.Read"> - <val name="value" val=""android.permission.MY_READ_PERMISSION_STRING"" /> - </annotation> - <annotation name="android.support.annotation.RequiresPermission.Write"> - <val name="value" val=""android.permission.MY_WRITE_PERMISSION_STRING"" /> - </annotation> - </item> - <item name="test.pkg.PermissionsTest void myMethod()"> - <annotation name="android.support.annotation.RequiresPermission"> - <val name="value" val=""android.permission.MY_PERMISSION_STRING"" /> - </annotation> - </item> - <item name="test.pkg.PermissionsTest void myMethod2()"> - <annotation name="android.support.annotation.RequiresPermission"> - <val name="anyOf" val="{"android.permission.MY_PERMISSION_STRING", "android.permission.MY_PERMISSION_STRING2"}" /> - </annotation> - </item> -</root> + public static final int STYLE_NORMAL = 0; + public static final int STYLE_NO_TITLE = 1; + public static final int STYLE_NO_FRAME = 2; + public static final int STYLE_NO_INPUT = 3; + public static final int UNRELATED = 3; -""" - ) - } - - @Test - fun `Skip class retention`() { - val androidJar = getPlatformFile("android.jar") - - val project = createProject( - intDefTest, - permissionsTest, - manifest, - intDefAnnotation, - intRangeAnnotation, - permissionAnnotation - ) + public void setStyle(@DialogStyle int style, int theme) { + } - val output = temporaryFolder.newFile("annotations.zip") + public void testIntDef(int arg) { + } + @IntDef(value = {STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT, 3, 3 + 1}, flag=true) + @Retention(RetentionPolicy.SOURCE) + private @interface DialogFlags {} - runDriver( - "--sources", - File(project, "src").path, - "--classpath", - androidJar.path, - "--skip-class-retention", - "--extract-annotations", - output.path - ) + public void setFlags(Object first, @DialogFlags int flags) { + } - // Check external annotations - checkPackageXml( - "test.pkg", output, - """<?xml version="1.0" encoding="UTF-8"?> -<root> - <item name="test.pkg.IntDefTest void setFlags(java.lang.Object, int) 1"> - <annotation name="android.support.annotation.IntDef"> - <val name="value" val="{test.pkg.IntDefTest.STYLE_NORMAL, test.pkg.IntDefTest.STYLE_NO_TITLE, test.pkg.IntDefTest.STYLE_NO_FRAME, test.pkg.IntDefTest.STYLE_NO_INPUT, 3, 4}" /> - <val name="flag" val="true" /> - </annotation> - </item> - <item name="test.pkg.IntDefTest void setStyle(int, int) 0"> - <annotation name="android.support.annotation.IntDef"> - <val name="value" val="{test.pkg.IntDefTest.STYLE_NORMAL, test.pkg.IntDefTest.STYLE_NO_TITLE, test.pkg.IntDefTest.STYLE_NO_FRAME, test.pkg.IntDefTest.STYLE_NO_INPUT}" /> - </annotation> - <annotation name="android.support.annotation.IntRange"> - <val name="from" val="20" /> - </annotation> - </item> - <item name="test.pkg.IntDefTest.Inner void setInner(int) 0"> - <annotation name="android.support.annotation.IntDef"> - <val name="value" val="{test.pkg.IntDefTest.STYLE_NORMAL, test.pkg.IntDefTest.STYLE_NO_TITLE, test.pkg.IntDefTest.STYLE_NO_FRAME, test.pkg.IntDefTest.STYLE_NO_INPUT, 3, 4}" /> - <val name="flag" val="true" /> - </annotation> - </item> -</root> + public static final String TYPE_1 = "type1"; + public static final String TYPE_2 = "type2"; + public static final String UNRELATED_TYPE = "other"; -""" + public static class Inner { + public void setInner(@DialogFlags int flags) { + } + } + } + """ + ).indented(), + intDefAnnotationSource, + intRangeAnnotationSource + ), + warnings = "src/test/pkg/IntDefTest.java:11: error: This typedef annotation class should have @Retention(RetentionPolicy.SOURCE) [AnnotationExtraction:146]", + extractAnnotations = mapOf("test.pkg" to """ + <?xml version="1.0" encoding="UTF-8"?> + <root> + <item name="test.pkg.IntDefTest void setFlags(java.lang.Object, int) 1"> + <annotation name="androidx.annotation.IntDef"> + <val name="value" val="{test.pkg.IntDefTest.STYLE_NORMAL, test.pkg.IntDefTest.STYLE_NO_TITLE, test.pkg.IntDefTest.STYLE_NO_FRAME, test.pkg.IntDefTest.STYLE_NO_INPUT, 3, 4}" /> + <val name="flag" val="true" /> + </annotation> + </item> + <item name="test.pkg.IntDefTest void setStyle(int, int) 0"> + <annotation name="androidx.annotation.IntDef"> + <val name="value" val="{test.pkg.IntDefTest.STYLE_NORMAL, test.pkg.IntDefTest.STYLE_NO_TITLE, test.pkg.IntDefTest.STYLE_NO_FRAME, test.pkg.IntDefTest.STYLE_NO_INPUT}" /> + </annotation> + </item> + <item name="test.pkg.IntDefTest.Inner void setInner(int) 0"> + <annotation name="androidx.annotation.IntDef"> + <val name="value" val="{test.pkg.IntDefTest.STYLE_NORMAL, test.pkg.IntDefTest.STYLE_NO_TITLE, test.pkg.IntDefTest.STYLE_NO_FRAME, test.pkg.IntDefTest.STYLE_NO_INPUT, 3, 4}" /> + <val name="flag" val="true" /> + </annotation> + </item> + </root> + """ + ) ) } @Test - fun `Test writing jar recipe file`() { - val androidJar = getPlatformFile("android.jar") - - val project = createProject( - intDefTest, - permissionsTest, - manifest, - intDefAnnotation, - intRangeAnnotation, - permissionAnnotation - ) - - val output = temporaryFolder.newFile("annotations.zip") - val typedefFile = temporaryFolder.newFile("typedefs.txt") - - runDriver( - "--sources", - File(project, "src").path, - "--classpath", - androidJar.path, - - "--extract-annotations", - output.path, - "--typedef-file", - typedefFile.path - ) - - // Check recipe - assertEquals( - """D test/pkg/IntDefTest${"$"}DialogFlags -D test/pkg/IntDefTest${"$"}DialogStyle -""", - Files.asCharSource(typedefFile, Charsets.UTF_8).read() - ) - } - - @SuppressWarnings("all") // sample code - private val intDefAnnotation = java( - """ - package android.support.annotation; - import java.lang.annotation.Retention; - import java.lang.annotation.RetentionPolicy; - import java.lang.annotation.Target; - import static java.lang.annotation.ElementType.*; - import static java.lang.annotation.RetentionPolicy.SOURCE; - @Retention(SOURCE) - @Target({ANNOTATION_TYPE}) - public @interface IntDef { - long[] value() default {}; - boolean flag() default false; - } - """ - ).indented() - - @SuppressWarnings("all") // sample code - private val intRangeAnnotation = java( - """ - package android.support.annotation; - - import java.lang.annotation.Retention; - import java.lang.annotation.Target; - - import static java.lang.annotation.ElementType.*; - import static java.lang.annotation.RetentionPolicy.CLASS; - - @Retention(CLASS) - @Target({CONSTRUCTOR,METHOD,PARAMETER,FIELD,LOCAL_VARIABLE,ANNOTATION_TYPE,PACKAGE}) - public @interface IntRange { - long from() default Long.MIN_VALUE; - long to() default Long.MAX_VALUE; - } - """ - ).indented() - - @SuppressWarnings("all") // sample code - private val permissionAnnotation = java( - """ - package android.support.annotation; - import java.lang.annotation.Retention; - import java.lang.annotation.RetentionPolicy; - import java.lang.annotation.Target; - import static java.lang.annotation.ElementType.*; - import static java.lang.annotation.RetentionPolicy.*; - @Retention(CLASS) - @Target({ANNOTATION_TYPE,METHOD,CONSTRUCTOR,FIELD}) - public @interface RequiresPermission { - String value() default ""; - String[] allOf() default {}; - String[] anyOf() default {}; - boolean conditional() default false; - @Target(FIELD) - @interface Read { - RequiresPermission value(); - } - @Target(FIELD) - @interface Write { - RequiresPermission value(); - } - }""" - ).indented() - - @SuppressWarnings("all") // sample code - private val nullableAnnotation = java( - """ - package android.support.annotation; - import java.lang.annotation.*; - import static java.lang.annotation.ElementType.*; - import static java.lang.annotation.RetentionPolicy.*; - @Retention(CLASS) - @Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE}) - public @interface Nullable { - }""" - ).indented() - - @SuppressWarnings("all") // sample code - private val packageTest = java( - """ - @IntRange(from = 20) - package test.pkg; - - import android.support.annotation.IntRange;""" - ).indented() - - @SuppressWarnings("all") // sample code - private val genericTest = java( - """ - package test.pkg; - - import android.support.annotation.IntRange; - import android.support.annotation.Nullable; - - import java.util.Comparator; - import java.util.List; - - @IntRange(from = 0) - public interface MyEnhancedList<E> extends List<E> { - @IntRange(from = 10) - E getReversed(List<String> filter, Comparator<? super E> comparator); - @Nullable String getPrefix(); - } - """ - ) - - @SuppressWarnings("all") // sample code - private val intDefTest = java( - """ - package test.pkg; - - import android.support.annotation.IntDef; - import android.support.annotation.IntRange; - import android.support.annotation.Keep; + fun `Check Kotlin and referencing hidden constants from typedef`() { + check( + sourceFiles = *arrayOf( + kotlin( + """ + @file:Suppress("unused", "UseExpressionBody") - import java.lang.annotation.Retention; - import java.lang.annotation.RetentionPolicy; + package test.pkg - @SuppressWarnings({"UnusedDeclaration", "WeakerAccess"}) - public class IntDefTest { - @IntDef({STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT}) - @IntRange(from = 20) - @Retention(RetentionPolicy.SOURCE) - private @interface DialogStyle {} + import android.annotation.LongDef - public static final int STYLE_NORMAL = 0; - public static final int STYLE_NO_TITLE = 1; - public static final int STYLE_NO_FRAME = 2; - public static final int STYLE_NO_INPUT = 3; - public static final int UNRELATED = 3; + const val STYLE_NORMAL = 0L + const val STYLE_NO_TITLE = 1L + const val STYLE_NO_FRAME = 2L + const val STYLE_NO_INPUT = 3L + const val UNRELATED = 3L + private const val HIDDEN = 4 - public void setStyle(@DialogStyle int style, int theme) { - } + const val TYPE_1 = "type1" + const val TYPE_2 = "type2" + const val UNRELATED_TYPE = "other" - @Keep public void testIntDef(int arg) { - } - @IntDef(value = {STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT, 3, 3 + 1}, flag=true) - @Retention(RetentionPolicy.SOURCE) - private @interface DialogFlags {} + class LongDefTest { - public void setFlags(Object first, @DialogFlags int flags) { - } + /** @hide */ + @LongDef(STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT, HIDDEN) + @Retention(AnnotationRetention.SOURCE) + private annotation class DialogStyle - public static final String TYPE_1 = "type1"; - public static final String TYPE_2 = "type2"; - public static final String UNRELATED_TYPE = "other"; + fun setStyle(@DialogStyle style: Int, theme: Int) {} - public static class Inner { - public void setInner(@DialogFlags int flags) { + fun testLongDef(arg: Int) { } - } - }""" - ).indented() - @SuppressWarnings("all") // sample code - private val permissionsTest = java( - """ - package test.pkg; + @LongDef(STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT, 3L, 3L + 1L, flag = true) + @Retention(AnnotationRetention.SOURCE) + private annotation class DialogFlags - import android.support.annotation.RequiresPermission; + fun setFlags(first: Any, @DialogFlags flags: Int) {} - public class PermissionsTest { - @RequiresPermission(Manifest.permission.MY_PERMISSION) - public void myMethod() { - } - @RequiresPermission(anyOf={Manifest.permission.MY_PERMISSION,Manifest.permission.MY_PERMISSION2}) - public void myMethod2() { + class Inner { + fun setInner(@DialogFlags flags: Int) {} + fun isNull(value: String?): Boolean } + }""" + ).indented(), + longDefAnnotationSource + ), + warnings = "src/test/pkg/LongDefTest.kt:12: error: Typedef class references hidden field field LongDefTestKt.HIDDEN: removed from typedef metadata [HiddenTypedefConstant:148]", + extractAnnotations = mapOf("test.pkg" to """ + <?xml version="1.0" encoding="UTF-8"?> + <root> + <item name="test.pkg.LongDefTest void setFlags(java.lang.Object, int) 1"> + <annotation name="androidx.annotation.LongDef"> + <val name="flag" val="true" /> + <val name="value" val="{test.pkg.LongDefTestKt.STYLE_NORMAL, test.pkg.LongDefTestKt.STYLE_NO_TITLE, test.pkg.LongDefTestKt.STYLE_NO_FRAME, test.pkg.LongDefTestKt.STYLE_NO_INPUT, 3, 4}" /> + </annotation> + </item> + <item name="test.pkg.LongDefTest void setStyle(int, int) 0"> + <annotation name="androidx.annotation.LongDef"> + <val name="value" val="{test.pkg.LongDefTestKt.STYLE_NORMAL, test.pkg.LongDefTestKt.STYLE_NO_TITLE, test.pkg.LongDefTestKt.STYLE_NO_FRAME, test.pkg.LongDefTestKt.STYLE_NO_INPUT}" /> + </annotation> + </item> + <item name="test.pkg.LongDefTest.Inner void setInner(int) 0"> + <annotation name="androidx.annotation.LongDef"> + <val name="flag" val="true" /> + <val name="value" val="{test.pkg.LongDefTestKt.STYLE_NORMAL, test.pkg.LongDefTestKt.STYLE_NO_TITLE, test.pkg.LongDefTestKt.STYLE_NO_FRAME, test.pkg.LongDefTestKt.STYLE_NO_INPUT, 3, 4}" /> + </annotation> + </item> + </root> + """ + ) + ) + } - - @RequiresPermission.Read(@RequiresPermission(Manifest.permission.MY_READ_PERMISSION)) - @RequiresPermission.Write(@RequiresPermission(Manifest.permission.MY_WRITE_PERMISSION)) - public static final String CONTENT_URI = ""; - } + @Ignore("Not working reliably") + @Test + fun `Include merged annotations in exported source annotations`() { + check( + compatibilityMode = false, + outputKotlinStyleNulls = false, + includeSystemApiAnnotations = false, + omitCommonPackages = false, + warnings = "error: Unexpected reference to Nonexistent.Field [AnnotationExtraction:146]", + sourceFiles = *arrayOf( + java( """ - ) - - @SuppressWarnings("all") // sample code - private val manifest = java( - """ package test.pkg; - public class Manifest { - public static final class permission { - public static final String MY_PERMISSION = "android.permission.MY_PERMISSION_STRING"; - public static final String MY_PERMISSION2 = "android.permission.MY_PERMISSION_STRING2"; - public static final String MY_READ_PERMISSION = "android.permission.MY_READ_PERMISSION_STRING"; - public static final String MY_WRITE_PERMISSION = "android.permission.MY_WRITE_PERMISSION_STRING"; - } - } - """ - ).indented() - - private fun checkPackageXml(pkg: String, output: File, @Language("XML") expected: String) { - assertNotNull(output) - assertTrue(output.exists()) - val url = URL( - "jar:" + fileToUrlString(output) + "!/" + pkg.replace('.', '/') + - "/annotations.xml" + public class MyTest { + public void test(int arg) { } + }""" + ) + ), + mergeXmlAnnotations = """<?xml version="1.0" encoding="UTF-8"?> + <root> + <item name="test.pkg.MyTest void test(int) 0"> + <annotation name="org.intellij.lang.annotations.MagicConstant"> + <val name="intValues" val="{java.util.Calendar.ERA, java.util.Calendar.YEAR, java.util.Calendar.MONTH, java.util.Calendar.WEEK_OF_YEAR, Nonexistent.Field}" /> + </annotation> + </item> + </root> + """, + extractAnnotations = mapOf("test.pkg" to """ + <?xml version="1.0" encoding="UTF-8"?> + <root> + <item name="test.pkg.MyTest void test(int) 0"> + <annotation name="androidx.annotation.IntDef"> + <val name="value" val="{java.util.Calendar.ERA, java.util.Calendar.YEAR, java.util.Calendar.MONTH, java.util.Calendar.WEEK_OF_YEAR}" /> + </annotation> + </item> + </root> + """ + ) ) - val stream = url.openStream() - try { - val bytes = ByteStreams.toByteArray(stream) - assertNotNull(bytes) - val xml = String(bytes, Charsets.UTF_8).replace("\r\n", "\n") - assertEquals(expected, xml) - } finally { - Closeables.closeQuietly(stream) - } } } \ No newline at end of file diff --git a/src/test/java/com/android/tools/metalava/Java9LanguageFeaturesTest.kt b/src/test/java/com/android/tools/metalava/Java9LanguageFeaturesTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..e5233faaf5b4bb31180dbc775ccde2b5d507569e --- /dev/null +++ b/src/test/java/com/android/tools/metalava/Java9LanguageFeaturesTest.kt @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Suppress("ALL") + +package com.android.tools.metalava + +import org.junit.Test + +class Java9LanguageFeaturesTest : DriverTest() { + @Test + fun `Private Interface Method`() { + // Basic class; also checks that default constructor is made explicit + check( + checkCompilation = false, // Not compiling with JDK 9 yet + checkDoclava1 = false, // Not handling JDK 9 + sourceFiles = *arrayOf( + java( + """ + package test.pkg; + + public interface Person { + String name(); + private String reverse(String s) { + return new StringBuilder(s).reverse().toString(); + } + } + """ + ) + ), + api = """ + package test.pkg { + public abstract interface Person { + method public abstract java.lang.String name(); + } + } + """, + extraArguments = arrayOf("--java-source", "1.9") + ) + } + + @Test + fun `Basic class signature extraction`() { + // Basic class; also checks that default constructor is made explicit + check( + checkCompilation = false, // Not compiling with JDK 9 yet + checkDoclava1 = false, // Not handling JDK 9 + sourceFiles = *arrayOf( + java( + """ + package libcore.internal; + + import java.io.ByteArrayInputStream; + import java.io.ByteArrayOutputStream; + import java.io.IOException; + import java.io.InputStream; + import java.util.ArrayList; + import java.util.Arrays; + import java.util.List; + import java.util.Objects; + import java.util.concurrent.atomic.AtomicReference; + + public class Java9LanguageFeatures { + + public interface Person { + String name(); + + default boolean isPalindrome() { + return name().equals(reverse(name())); + } + + default boolean isPalindromeIgnoreCase() { + return name().equalsIgnoreCase(reverse(name())); + } + + // Language feature: private interface method + private String reverse(String s) { + return new StringBuilder(s).reverse().toString(); + } + } + + @SafeVarargs + public static<T> String toListString(T... values) { + return toString(values).toString(); + } + + // Language feature: @SafeVarargs on private methods + @SafeVarargs + private static<T> List<String> toString(T... values) { + List<String> result = new ArrayList<>(); + for (T value : values) { + result.add(value.toString()); + } + return result; + } + + public <T> AtomicReference<T> createReference(T content) { + // Language feature: <> on anonymous class + //noinspection unchecked + return new AtomicReference<>(content) { }; + } + + public static byte[] copy(byte[] bytes) throws IOException { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + InputStream inputStream = new ByteArrayInputStream(bytes); + try (inputStream) { // Language feature: try on effectively-final variable + int value; + while ((value = inputStream.read()) != -1) { + byteArrayOutputStream.write(value); + } + } + return byteArrayOutputStream.toByteArray(); + } + } + """ + ) + ), + api = """ + package libcore.internal { + public class Java9LanguageFeatures { + ctor public Java9LanguageFeatures(); + method public static byte[] copy(byte[]) throws java.io.IOException; + method public <T> java.util.concurrent.atomic.AtomicReference<T> createReference(T); + method public static <T> java.lang.String toListString(T...); + } + public static abstract interface Java9LanguageFeatures.Person { + method public default boolean isPalindrome(); + method public default boolean isPalindromeIgnoreCase(); + method public abstract java.lang.String name(); + } + } + """, + extraArguments = arrayOf("--java-source", "1.9") + ) + } +} \ No newline at end of file diff --git a/src/test/java/com/android/tools/metalava/KotlinInteropChecksTest.kt b/src/test/java/com/android/tools/metalava/KotlinInteropChecksTest.kt index 575ad585f559230fc762e8b5d03cf2eee1d7d850..2360fb2ab2f72330d616a16266cff4e4a109be3e 100644 --- a/src/test/java/com/android/tools/metalava/KotlinInteropChecksTest.kt +++ b/src/test/java/com/android/tools/metalava/KotlinInteropChecksTest.kt @@ -32,7 +32,7 @@ class KotlinInteropChecksTest : DriverTest() { java( """ package test.pkg; - import android.support.annotation.ParameterName; + import androidx.annotation.ParameterName; public class Test { public void fun() { } @@ -183,14 +183,14 @@ class KotlinInteropChecksTest : DriverTest() { } @Throws(FileNotFoundException::class) - fun ok_hasThrows(x: Int) { + fun ok_hasThrows1(x: Int) { if (x < 0) { throw java.io.FileNotFoundException("Something") } } @Throws(UnsupportedOperationException::class, FileNotFoundException::class) - fun ok_hasThrows(x: Int) { + fun ok_hasThrows2(x: Int) { if (x < 0) { throw java.io.FileNotFoundException("Something") } diff --git a/src/test/java/com/android/tools/metalava/NullnessMigrationTest.kt b/src/test/java/com/android/tools/metalava/NullnessMigrationTest.kt index c4e25e25aba3a384270a7793b34c93766b063c33..0557168dc41181939867a846f4e634cd1712d02f 100644 --- a/src/test/java/com/android/tools/metalava/NullnessMigrationTest.kt +++ b/src/test/java/com/android/tools/metalava/NullnessMigrationTest.kt @@ -51,7 +51,7 @@ class NullnessMigrationTest : DriverTest() { } @Test - fun `Method which is now marked null should be marked as newly migrated null`() { + fun `Method which is now marked null should be marked as recently migrated null`() { check( migrateNulls = true, outputKotlinStyleNulls = false, @@ -73,7 +73,7 @@ class NullnessMigrationTest : DriverTest() { api = """ package test.pkg { public abstract class MyTest { - method @NewlyNullable public Double convert1(Float); + method @RecentlyNullable public Double convert1(Float); } } """ @@ -81,7 +81,7 @@ class NullnessMigrationTest : DriverTest() { } @Test - fun `Parameter which is now marked null should be marked as newly migrated null`() { + fun `Parameter which is now marked null should be marked as recently migrated null`() { check( migrateNulls = true, outputKotlinStyleNulls = false, @@ -103,7 +103,7 @@ class NullnessMigrationTest : DriverTest() { api = """ package test.pkg { public abstract class MyTest { - method public Double convert1(@NewlyNonNull Float); + method public Double convert1(@RecentlyNonNull Float); } } """ @@ -134,7 +134,7 @@ class NullnessMigrationTest : DriverTest() { ctor public MyTest(); method public Double convert0(Float); method public Double convert1(Float); - method @NewlyNullable public Double convert2(@NewlyNonNull Float); + method @RecentlyNullable public Double convert2(@RecentlyNonNull Float); method @RecentlyNullable public Double convert3(@RecentlyNonNull Float); method @Nullable public Double convert4(@NonNull Float); } @@ -145,8 +145,8 @@ class NullnessMigrationTest : DriverTest() { public class MyTest { ctor public MyTest(); method public Double convert0(Float); - method @NewlyNullable public Double convert1(@NewlyNonNull Float); - method @RecentlyNullable public Double convert2(@RecentlyNonNull Float); + method @RecentlyNullable public Double convert1(@RecentlyNonNull Float); + method @Nullable public Double convert2(@NonNull Float); method @Nullable public Double convert3(@NonNull Float); method @Nullable public Double convert4(@NonNull Float); } @@ -179,7 +179,7 @@ class NullnessMigrationTest : DriverTest() { ctor public MyTest(); method public Double convert0(Float); method public Double convert1(Float); - method @NewlyNullable public Double convert2(@NewlyNonNull Float); + method @RecentlyNullable public Double convert2(@RecentlyNonNull Float); method @RecentlyNullable public Double convert3(@RecentlyNonNull Float); method @Nullable public Double convert4(@NonNull Float); } @@ -260,7 +260,7 @@ class NullnessMigrationTest : DriverTest() { java( """ package test.pkg; - import android.support.annotation.Nullable; + import androidx.annotation.Nullable; import java.util.List; public class Test { public @Nullable Integer compute1(@Nullable java.util.List<@Nullable String> list) { @@ -276,7 +276,7 @@ class NullnessMigrationTest : DriverTest() { supportNonNullSource, supportNullableSource ), - extraArguments = arrayOf("--hide-package", "android.support.annotation"), + extraArguments = arrayOf("--hide-package", "androidx.annotation"), api = """ package test.pkg { public class Test { @@ -288,4 +288,142 @@ class NullnessMigrationTest : DriverTest() { """ ) } + + @Test + fun `Check androidx package annotation`() { + check( + outputKotlinStyleNulls = false, + compatibilityMode = false, + sourceFiles = *arrayOf( + java( + """ + package test.pkg; + import androidx.annotation.Nullable; + import androidx.annotation.NonNull; + import java.util.List; + public class Test { + public @Nullable Integer compute1(@Nullable java.util.List<@Nullable String> list) { + return 5; + } + public @Nullable Integer compute2(@NonNull java.util.List<@NonNull List<?>> list) { + return 5; + } + } + """ + ), + androidxNonNullSource, + androidxNullableSource + ), + extraArguments = arrayOf("--hide-package", "androidx.annotation"), + api = """ + package test.pkg { + public class Test { + ctor public Test(); + method @Nullable public Integer compute1(@Nullable java.util.List<@Nullable java.lang.String>); + method @Nullable public Integer compute2(@NonNull java.util.List<@NonNull java.util.List<?>>); + } + } + """ + ) + } + + @Test + fun `Migrate nullness for type-use annotations`() { + check( + outputKotlinStyleNulls = false, + compatibilityMode = false, + migrateNulls = true, + sourceFiles = *arrayOf( + java( + """ + package test.pkg; + import androidx.annotation.Nullable; + import androidx.annotation.NonNull; + public class Foo { + public static char @NonNull [] toChars(int codePoint) { return new char[0]; } + public static int codePointAt(char @NonNull [] a, int index) { throw new RuntimeException("Stub!"); } + public <T> T @NonNull [] toArray(T @NonNull [] a); + } + """ + ), + androidxNonNullSource, + androidxNullableSource + ), + extraArguments = arrayOf("--hide-package", "androidx.annotation"), + // TODO: Handle multiple nullness annotations + previousApi = + """ + package test.pkg { + public class Foo { + ctor public Foo(); + method public static int codePointAt(char[], int); + method public <T> T[] toArray(T[]); + method public static char[] toChars(int); + } + } + """, + stubs = arrayOf( + """ + package test.pkg; + @SuppressWarnings({"unchecked", "deprecation", "all"}) + public class Foo { + public Foo() { throw new RuntimeException("Stub!"); } + public static char @androidx.annotation.RecentlyNonNull [] toChars(int codePoint) { throw new RuntimeException("Stub!"); } + public static int codePointAt(char @androidx.annotation.RecentlyNonNull [] a, int index) { throw new RuntimeException("Stub!"); } + public <T> T @androidx.annotation.RecentlyNonNull [] toArray(T @androidx.annotation.RecentlyNonNull [] a) { throw new RuntimeException("Stub!"); } + } + """ + ) + ) + } + + @Test + fun `Do not migrate type-use annotations when not changed`() { + check( + outputKotlinStyleNulls = false, + compatibilityMode = false, + migrateNulls = true, + sourceFiles = *arrayOf( + java( + """ + package test.pkg; + import androidx.annotation.Nullable; + import androidx.annotation.NonNull; + public class Foo { + public static char @NonNull [] toChars(int codePoint) { return new char[0]; } + public static int codePointAt(char @NonNull [] a, int index) { throw new RuntimeException("Stub!"); } + public <T> T @NonNull [] toArray(T @NonNull [] a); + } + """ + ), + androidxNonNullSource, + androidxNullableSource + ), + extraArguments = arrayOf("--hide-package", "androidx.annotation"), + // TODO: Handle multiple nullness annotations + previousApi = + """ + package test.pkg { + public class Foo { + ctor public Foo(); + method public static int codePointAt(char[], int); + method public <T> T[] toArray(T[]); + method public static char[] toChars(int); + } + } + """, + stubs = arrayOf( + """ + package test.pkg; + @SuppressWarnings({"unchecked", "deprecation", "all"}) + public class Foo { + public Foo() { throw new RuntimeException("Stub!"); } + public static char @androidx.annotation.RecentlyNonNull [] toChars(int codePoint) { throw new RuntimeException("Stub!"); } + public static int codePointAt(char @androidx.annotation.RecentlyNonNull [] a, int index) { throw new RuntimeException("Stub!"); } + public <T> T @androidx.annotation.RecentlyNonNull [] toArray(T @androidx.annotation.RecentlyNonNull [] a) { throw new RuntimeException("Stub!"); } + } + """ + ) + ) + } } \ No newline at end of file diff --git a/src/test/java/com/android/tools/metalava/OptionsTest.kt b/src/test/java/com/android/tools/metalava/OptionsTest.kt index 8adf2afd2da8a7f665bcffd635d5ea3ee122eaa5..7c79caa658234d9b3850ecc86d57557e94271b4e 100644 --- a/src/test/java/com/android/tools/metalava/OptionsTest.kt +++ b/src/test/java/com/android/tools/metalava/OptionsTest.kt @@ -53,7 +53,8 @@ API sources: when parsing the source files --merge-annotations <file> An external annotations file (using IntelliJ's external annotations database format) to merge and - overlay the sources + overlay the sources. A subset of .jaif files is + also supported. --input-api-jar <file> A .jar file to read APIs from directly --manifest <file> A manifest file, used to for check permissions to cross check APIs @@ -62,6 +63,8 @@ API sources: --show-annotation <annotation class> Include the given annotation in the API analysis --show-unannotated Include un-annotated public APIs in the signature file as well +--java-source <level> Sets the source level for Java source files; + default is 1.8. Documentation: --public Only include elements that are public @@ -76,6 +79,8 @@ Extracting Signature Files: --api <file> Generate a signature descriptor file --private-api <file> Generate a signature descriptor file listing the exact private APIs +--dex-api <file> Generate a DEX signature descriptor file listing + the APIs --private-dex-api <file> Generate a DEX signature descriptor file listing the exact private APIs --removed-api <file> Generate a signature descriptor file for APIs that @@ -100,10 +105,31 @@ Extracting Signature Files: Generating Stubs: --stubs <dir> Generate stub source files for the API +--doc-stubs <dir> Generate documentation stub source files for the + API. Documentation stub files are similar to + regular stub files, but there are some differences. + For example, in the stub files, we'll use special + annotations like @RecentlyNonNull instead of + @NonNull to indicate that an element is recently + marked as non null, whereas in the documentation + stubs we'll just list this as @NonNull. Another + difference is that @doconly elements are included + in documentation stubs, but not regular stubs, + etc. --exclude-annotations Exclude annotations such as @Nullable from the stub files --write-stubs-source-list <file> Write the list of generated stub files into the + given source list file. If generating documentation + stubs and you haven't also specified + --write-doc-stubs-source-list, this list will refer + to the documentation stubs; otherwise it's the + non-documentation stubs. +--write-doc-stubs-source-list <file> Write the list of generated doc stub files into the given source list file +--register-artifact <api-file> <id> Registers the given id for the packages found in + the given signature file. metalava will inject an + @artifactId <id> tag into every top level stub + class in that API. Diffs and Checks: --previous-api <signature file> A signature file for the previous version of this @@ -116,6 +142,9 @@ Diffs and Checks: --check-compatibility Check compatibility with the previous API --check-kotlin-interop Check API intended to be used from both Kotlin and Java for interoperability issues +--current-api <signature file> A signature file for the current version of this + API to check compatibility with. If not specified, + --previous-api will be used instead. --migrate-nullness Compare nullness information with the previous API and mark newly annotated APIs as under migration. --warnings-as-errors Promote all warnings to errors @@ -139,20 +168,14 @@ Statistics: --skip-java-in-coverage-report In the coverage annotation report, skip java.** and kotlin.** to narrow the focus down to the Android framework APIs. +--write-class-coverage-to <path> Specifies a file to write the annotation coverage + report for classes to. +--write-member-coverage-to <path> Specifies a file to write the annotation coverage + report for members to. Extracting Annotations: ---extract-annotations <zipfile> Extracts annotations from the source files and - writes them into the given zip file ---api-filter <file> Applies the given signature file as a filter (which - means no classes,methods or fields not found in the - filter will be included.) ---hide-filtered Omit listing APIs that were skipped because of the - --api-filter ---skip-class-retention Do not extract annotations that have class file - retention ---rmtypedefs Delete all the typedef .class files ---typedef-file <file> Writes an typedef annotation class names into the - given file +--extract-annotations <zipfile> Extracts source annotations from the source files + and writes them into the given zip file Injecting API Levels: --apply-api-levels <api-versions.xml> Reads an XML file containing API level descriptions diff --git a/src/test/java/com/android/tools/metalava/ShowAnnotationTest.kt b/src/test/java/com/android/tools/metalava/ShowAnnotationTest.kt index 37e3c68f04d1dfd5567de79815cfbe86909672b8..e66c35d68e44ad6c16d1ef91499d4242a9e4f8be 100644 --- a/src/test/java/com/android/tools/metalava/ShowAnnotationTest.kt +++ b/src/test/java/com/android/tools/metalava/ShowAnnotationTest.kt @@ -124,4 +124,43 @@ class ShowAnnotationTest : DriverTest() { """ ) } + + @Test + fun `Check @TestApi handling`() { + check( + includeSystemApiAnnotations = true, + checkDoclava1 = true, + sourceFiles = *arrayOf( + java( + """ + package test.pkg; + import android.annotation.TestApi; + + /** + * Blah blah blah + * @hide + */ + @TestApi + public class Bar { + } + """ + ), + testApiSource + ), + + extraArguments = arrayOf( + "--show-annotation", "android.annotation.TestApi", + "--hide-package", "android.annotation", + "--hide-package", "android.support.annotation" + ), + + api = """ + package test.pkg { + public class Bar { + ctor public Bar(); + } + } + """ + ) + } } \ No newline at end of file diff --git a/src/test/java/com/android/tools/metalava/StubsTest.kt b/src/test/java/com/android/tools/metalava/StubsTest.kt index 71d35fbb473895b6d92560a15095ec209c17a2fb..5d7f7df3379b4b84435b1ffac31ad8bc81a8c797 100644 --- a/src/test/java/com/android/tools/metalava/StubsTest.kt +++ b/src/test/java/com/android/tools/metalava/StubsTest.kt @@ -34,17 +34,21 @@ class StubsTest : DriverTest() { checkDoclava1: Boolean = false, api: String? = null, extraArguments: Array<String> = emptyArray(), + docStubs: Boolean = false, + showAnnotations: Array<String> = emptyArray(), vararg sourceFiles: TestFile ) { check( *sourceFiles, + showAnnotations = showAnnotations, stubs = arrayOf(source), compatibilityMode = compatibilityMode, warnings = warnings, checkDoclava1 = checkDoclava1, checkCompilation = true, api = api, - extraArguments = extraArguments + extraArguments = extraArguments, + docStubs = docStubs ) } @@ -711,7 +715,7 @@ class StubsTest : DriverTest() { checkStubs( // Note that doclava1 includes fields here that it doesn't include in the // signature file. - //checkDoclava1 = true, + // checkDoclava1 = true, compatibilityMode = false, sourceFiles = *arrayOf( @@ -1041,7 +1045,7 @@ class StubsTest : DriverTest() { * @param size Value is between 0 and (1 << MeasureSpec.MODE_SHIFT) - 1 inclusive * @param mode Value is {@link android.view.View.View.MeasureSpec#UNSPECIFIED}, {@link android.view.View.View.MeasureSpec#EXACTLY}, or {@link android.view.View.View.MeasureSpec#AT_MOST} */ - public static int makeMeasureSpec(@android.support.annotation.IntRange(from=0, to=0x40000000 - 1) int size, int mode) { throw new RuntimeException("Stub!"); } + public static int makeMeasureSpec(@androidx.annotation.IntRange(from=0, to=0x40000000 - 1) int size, int mode) { throw new RuntimeException("Stub!"); } public static final int AT_MOST = -2147483648; // 0x80000000 public static final int EXACTLY = 1073741824; // 0x40000000 public static final int UNSPECIFIED = 0; // 0x0 @@ -1127,7 +1131,7 @@ class StubsTest : DriverTest() { /** * Requires {@link android.Manifest.permission#INTERACT_ACROSS_USERS} and {@link android.Manifest.permission#BROADCAST_STICKY} */ - @android.support.annotation.RequiresPermission(allOf={"android.permission.INTERACT_ACROSS_USERS", android.Manifest.permission.BROADCAST_STICKY}) public abstract void removeStickyBroadcast(@android.support.annotation.RequiresPermission java.lang.Object intent); + @androidx.annotation.RequiresPermission(allOf={"android.permission.INTERACT_ACROSS_USERS", android.Manifest.permission.BROADCAST_STICKY}) public abstract void removeStickyBroadcast(@androidx.annotation.RequiresPermission java.lang.Object intent); } """ ) @@ -1260,15 +1264,15 @@ class StubsTest : DriverTest() { /** My class doc */ @SuppressWarnings({"unchecked", "deprecation", "all"}) public final class Kotlin extends test.pkg.Parent { - public Kotlin(@android.support.annotation.NonNull java.lang.String property1, int arg2) { throw new RuntimeException("Stub!"); } - @android.support.annotation.NonNull public java.lang.String method() { throw new RuntimeException("Stub!"); } + public Kotlin(@androidx.annotation.NonNull java.lang.String property1, int arg2) { throw new RuntimeException("Stub!"); } + @androidx.annotation.NonNull public java.lang.String method() { throw new RuntimeException("Stub!"); } /** My method doc */ public void otherMethod(boolean ok, int times) { throw new RuntimeException("Stub!"); } /** property doc */ - @android.support.annotation.Nullable public java.lang.String getProperty2() { throw new RuntimeException("Stub!"); } + @androidx.annotation.Nullable public java.lang.String getProperty2() { throw new RuntimeException("Stub!"); } /** property doc */ - public void setProperty2(@android.support.annotation.Nullable java.lang.String p) { throw new RuntimeException("Stub!"); } - @android.support.annotation.NonNull public java.lang.String getProperty1() { throw new RuntimeException("Stub!"); } + public void setProperty2(@androidx.annotation.Nullable java.lang.String p) { throw new RuntimeException("Stub!"); } + @androidx.annotation.NonNull public java.lang.String getProperty1() { throw new RuntimeException("Stub!"); } public int someField2; } """, @@ -1285,7 +1289,7 @@ class StubsTest : DriverTest() { java( """ package test.pkg; - import android.support.annotation.ParameterName; + import androidx.annotation.ParameterName; public class Foo { public void foo(int javaParameter1, @ParameterName("publicParameterName") int javaParameter2) { @@ -1442,12 +1446,94 @@ class StubsTest : DriverTest() { ) } + @Test + fun `Arguments to super constructors with showAnnotations`() { + // When overriding constructors we have to supply arguments + checkStubs( + showAnnotations = arrayOf("android.annotation.SystemApi"), + sourceFiles = + *arrayOf( + java( + """ + package test.pkg; + + @SuppressWarnings("WeakerAccess") + public class Constructors { + public class Parent { + public Parent(String s, int i, long l, boolean b, short sh) { + } + } + + public class Child extends Parent { + public Child(String s, int i, long l, boolean b, short sh) { + super(s, i, l, b, sh); + } + + private Child(String s) { + super(s, 0, 0, false, 0); + } + } + + public class Child2 extends Parent { + Child2(String s) { + super(s, 0, 0, false, 0); + } + } + + public class Child3 extends Child2 { + private Child3(String s) { + super("something"); + } + } + + public class Child4 extends Parent { + Child4(String s, HiddenClass hidden) { + super(s, 0, 0, true, 0); + } + } + /** @hide */ + public class HiddenClass { + } + } + """ + ) + ), + source = """ + package test.pkg; + @SuppressWarnings({"unchecked", "deprecation", "all"}) + public class Constructors { + public Constructors() { throw new RuntimeException("Stub!"); } + @SuppressWarnings({"unchecked", "deprecation", "all"}) + public class Child extends test.pkg.Constructors.Parent { + public Child(java.lang.String s, int i, long l, boolean b, short sh) { super(null, 0, 0, false, (short)0); throw new RuntimeException("Stub!"); } + } + @SuppressWarnings({"unchecked", "deprecation", "all"}) + public class Child2 extends test.pkg.Constructors.Parent { + Child2(java.lang.String s) { super(null, 0, 0, false, (short)0); throw new RuntimeException("Stub!"); } + } + @SuppressWarnings({"unchecked", "deprecation", "all"}) + public class Child3 extends test.pkg.Constructors.Child2 { + Child3(java.lang.String s) { super(null); throw new RuntimeException("Stub!"); } + } + @SuppressWarnings({"unchecked", "deprecation", "all"}) + public class Child4 extends test.pkg.Constructors.Parent { + Child4() { super(null, 0, 0, false, (short)0); throw new RuntimeException("Stub!"); } + } + @SuppressWarnings({"unchecked", "deprecation", "all"}) + public class Parent { + public Parent(java.lang.String s, int i, long l, boolean b, short sh) { throw new RuntimeException("Stub!"); } + } + } + """ + ) + } + // TODO: Add test to see what happens if I have Child4 in a different package which can't access the package private constructor of child3? @Test fun `DocOnly members should be omitted`() { // When marked @doconly don't include in stubs or signature files - // unless specifically asked for (which we do when generating docs). + // unless specifically asked for (which we do when generating docs-stubs). checkStubs( sourceFiles = *arrayOf( @@ -1502,7 +1588,7 @@ class StubsTest : DriverTest() { // When marked @doconly don't include in stubs or signature files // unless specifically asked for (which we do when generating docs). checkStubs( - extraArguments = arrayOf("--include-doconly"), + docStubs = true, sourceFiles = *arrayOf( java( @@ -1665,9 +1751,9 @@ class StubsTest : DriverTest() { sourceFiles = *arrayOf( java( "package my.pkg;\n" + - "public class String {\n" + - "public String(char @libcore.util.NonNull [] value) { throw new RuntimeException(\"Stub!\"); }\n" + - "}\n" + "public class String {\n" + + "public String(char @libcore.util.NonNull [] value) { throw new RuntimeException(\"Stub!\"); }\n" + + "}\n" ) ), warnings = "", @@ -1683,7 +1769,7 @@ class StubsTest : DriverTest() { package my.pkg; @SuppressWarnings({"unchecked", "deprecation", "all"}) public class String { - public String(char @android.support.annotation.NonNull [] value) { throw new RuntimeException("Stub!"); } + public String(char @androidx.annotation.NonNull [] value) { throw new RuntimeException("Stub!"); } } """ ) @@ -1865,6 +1951,7 @@ class StubsTest : DriverTest() { @Test fun `Rewriting type parameters in interfaces from hidden super classes and in throws lists`() { checkStubs( + extraArguments = arrayOf("--skip-inherited-methods=false"), checkDoclava1 = false, sourceFiles = *arrayOf( @@ -1914,7 +2001,7 @@ class StubsTest : DriverTest() { } public class Generics.MyClass<X, Y extends java.lang.Number> extends test.pkg.Generics.PublicParent implements test.pkg.Generics.PublicInterface { ctor public Generics.MyClass(); - method public java.util.Map<X, java.util.Map<Y, java.lang.String>> createMap(java.util.List<X>); + method public java.util.Map<X, java.util.Map<Y, java.lang.String>> createMap(java.util.List<X>) throws test.pkg.Generics.MyThrowable; method public java.util.List<X> foo(); } public static abstract interface Generics.PublicInterface<A, B> { @@ -1935,7 +2022,98 @@ class StubsTest : DriverTest() { public class MyClass<X, Y extends java.lang.Number> extends test.pkg.Generics.PublicParent<X,Y> implements test.pkg.Generics.PublicInterface<X,Y> { public MyClass() { throw new RuntimeException("Stub!"); } public java.util.List<X> foo() { throw new RuntimeException("Stub!"); } - public java.util.Map<X,java.util.Map<Y,java.lang.String>> createMap(java.util.List<X> list) { throw new RuntimeException("Stub!"); } + public java.util.Map<X,java.util.Map<Y,java.lang.String>> createMap(java.util.List<X> list) throws java.io.IOException { throw new RuntimeException("Stub!"); } + } + @SuppressWarnings({"unchecked", "deprecation", "all"}) + public static interface PublicInterface<A, B> { + public java.util.Map<A,java.util.Map<B,java.lang.String>> createMap(java.util.List<A> list) throws java.io.IOException; + } + @SuppressWarnings({"unchecked", "deprecation", "all"}) + public abstract class PublicParent<A, B extends java.lang.Number> { + public PublicParent() { throw new RuntimeException("Stub!"); } + protected abstract java.util.List<A> foo(); + } + } + """ + ) + } + + @Test + fun `Picking super class throwables`() { + // Like previous test, but without compatibility mode: ensures that we + // use super classes of filtered throwables + checkStubs( + compatibilityMode = false, + sourceFiles = + *arrayOf( + java( + """ + package test.pkg; + + import java.io.IOException; + import java.util.List; + import java.util.Map; + + @SuppressWarnings({"RedundantThrows", "WeakerAccess"}) + public class Generics { + public class MyClass<X, Y extends Number> extends HiddenParent<X, Y> implements PublicInterface<X, Y> { + } + + class HiddenParent<M, N extends Number> extends PublicParent<M, N> { + public Map<M, Map<N, String>> createMap(List<M> list) throws MyThrowable { + return null; + } + + protected List<M> foo() { + return null; + } + + } + + class MyThrowable extends IOException { + } + + public abstract class PublicParent<A, B extends Number> { + protected abstract List<A> foo(); + } + + public interface PublicInterface<A, B> { + Map<A, Map<B, String>> createMap(List<A> list) throws IOException; + } + } + """ + ) + ), + warnings = "", + api = """ + package test.pkg { + public class Generics { + ctor public Generics(); + } + public class Generics.MyClass<X, Y extends java.lang.Number> extends test.pkg.Generics.PublicParent<X,Y> implements test.pkg.Generics.PublicInterface<X,Y> { + ctor public Generics.MyClass(); + method public java.util.Map<X,java.util.Map<Y,java.lang.String>>! createMap(java.util.List<X>!) throws java.io.IOException; + method public java.util.List<X>! foo(); + } + public static interface Generics.PublicInterface<A, B> { + method public java.util.Map<A,java.util.Map<B,java.lang.String>>! createMap(java.util.List<A>!) throws java.io.IOException; + } + public abstract class Generics.PublicParent<A, B extends java.lang.Number> { + ctor public Generics.PublicParent(); + method protected abstract java.util.List<A>! foo(); + } + } + """, + source = """ + package test.pkg; + @SuppressWarnings({"unchecked", "deprecation", "all"}) + public class Generics { + public Generics() { throw new RuntimeException("Stub!"); } + @SuppressWarnings({"unchecked", "deprecation", "all"}) + public class MyClass<X, Y extends java.lang.Number> extends test.pkg.Generics.PublicParent<X,Y> implements test.pkg.Generics.PublicInterface<X,Y> { + public MyClass() { throw new RuntimeException("Stub!"); } + public java.util.List<X> foo() { throw new RuntimeException("Stub!"); } + public java.util.Map<X,java.util.Map<Y,java.lang.String>> createMap(java.util.List<X> list) throws java.io.IOException { throw new RuntimeException("Stub!"); } } @SuppressWarnings({"unchecked", "deprecation", "all"}) public static interface PublicInterface<A, B> { @@ -1955,6 +2133,7 @@ class StubsTest : DriverTest() { fun `Rewriting implements class references`() { // Checks some more subtle bugs around generics type variable renaming checkStubs( + extraArguments = arrayOf("--skip-inherited-methods=false"), checkDoclava1 = false, sourceFiles = *arrayOf( @@ -2073,6 +2252,7 @@ class StubsTest : DriverTest() { import android.util.AttributeSet; import org.xmlpull.v1.XmlPullParser; + @SuppressWarnings("UnnecessaryInterfaceModifier") public interface XmlResourceParser extends XmlPullParser, AttributeSet, AutoCloseable { public void close(); } @@ -2170,9 +2350,11 @@ class StubsTest : DriverTest() { public abstract FileDescriptor getFileDescriptor(); } + @SuppressWarnings("UnnecessaryInterfaceModifier") public static interface Closeable extends AutoCloseable { } + @SuppressWarnings("UnnecessaryInterfaceModifier") public static interface AutoCloseable { } @@ -2783,7 +2965,7 @@ class StubsTest : DriverTest() { *arrayOf( java( """ - @android.support.annotation.Nullable + @androidx.annotation.Nullable package test.pkg; """ ), @@ -2808,10 +2990,10 @@ class StubsTest : DriverTest() { } """, // WRONG: I should include package annotations! source = """ - @android.support.annotation.Nullable + @androidx.annotation.Nullable package test.pkg; """, - extraArguments = arrayOf("--hide-package", "android.support.annotation") + extraArguments = arrayOf("--hide-package", "androidx.annotation") ) } diff --git a/src/test/java/com/android/tools/metalava/SystemServiceCheckTest.kt b/src/test/java/com/android/tools/metalava/SystemServiceCheckTest.kt index 2c8fb78315cbf0136abd82ae49890406f87ea07d..de29f8f2a1eefc8a94375c7426624dd658b6b853 100644 --- a/src/test/java/com/android/tools/metalava/SystemServiceCheckTest.kt +++ b/src/test/java/com/android/tools/metalava/SystemServiceCheckTest.kt @@ -221,8 +221,8 @@ class SystemServiceCheckTest : DriverTest() { fun `Check SystemService -- must be system permission, not normal`() { check( warnings = "src/test/pkg/MyTest2.java:6: lint: Method 'test' must be protected with a system " + - "permission; it currently allows non-system callers holding [foo.bar.PERMISSION1, " + - "foo.bar.PERMISSION2] [RequiresPermission:125]", + "permission; it currently allows non-system callers holding [foo.bar.PERMISSION1, " + + "foo.bar.PERMISSION2] [RequiresPermission:125]", compatibilityMode = false, includeSystemApiAnnotations = true, sourceFiles = *arrayOf( diff --git a/src/test/java/com/android/tools/metalava/apilevels/ApiGeneratorTest.kt b/src/test/java/com/android/tools/metalava/apilevels/ApiGeneratorTest.kt index 778408e4f14b12b13f67a32a967d618c1584d66d..8ea27507ce91662b138d8ce8957b04c2ca2ed493 100644 --- a/src/test/java/com/android/tools/metalava/apilevels/ApiGeneratorTest.kt +++ b/src/test/java/com/android/tools/metalava/apilevels/ApiGeneratorTest.kt @@ -51,7 +51,7 @@ class ApiGeneratorTest : DriverTest() { "--android-jar-pattern", "${oldSdkJars.path}/android-%/android.jar", "--android-jar-pattern", - "${platformJars.path}/%/android.jar" + "${platformJars.path}/%/public/android.jar" ), checkDoclava1 = false, signatureSource = """ @@ -75,7 +75,5 @@ class ApiGeneratorTest : DriverTest() { val document = XmlUtils.parseDocumentSilently(xml, false) assertNotNull(document) - } - -} \ No newline at end of file +} diff --git a/src/test/java/com/android/tools/metalava/model/TextBackedAnnotationItemTest.kt b/src/test/java/com/android/tools/metalava/model/TextBackedAnnotationItemTest.kt index cc4e121ab9f75212472af5738cbd440f5d1aad1f..f3c367aa36707e038a54ba0dca369eb957c21ca6 100644 --- a/src/test/java/com/android/tools/metalava/model/TextBackedAnnotationItemTest.kt +++ b/src/test/java/com/android/tools/metalava/model/TextBackedAnnotationItemTest.kt @@ -41,10 +41,10 @@ class TextBackedAnnotationItemTest { fun testSimple() { val annotation = TextBackedAnnotationItem( dummyCodebase, - "@android.support.annotation.Nullable" + "@androidx.annotation.Nullable" ) - assertEquals("@android.support.annotation.Nullable", annotation.toSource()) - assertEquals("android.support.annotation.Nullable", annotation.qualifiedName()) + assertEquals("@androidx.annotation.Nullable", annotation.toSource()) + assertEquals("androidx.annotation.Nullable", annotation.qualifiedName()) assertTrue(annotation.attributes().isEmpty()) } @@ -52,10 +52,10 @@ class TextBackedAnnotationItemTest { fun testIntRange() { val annotation = TextBackedAnnotationItem( dummyCodebase, - "@android.support.annotation.IntRange(from = 20, to = 40)" + "@androidx.annotation.IntRange(from = 20, to = 40)" ) - assertEquals("@android.support.annotation.IntRange(from = 20, to = 40)", annotation.toSource()) - assertEquals("android.support.annotation.IntRange", annotation.qualifiedName()) + assertEquals("@androidx.annotation.IntRange(from = 20, to = 40)", annotation.toSource()) + assertEquals("androidx.annotation.IntRange", annotation.qualifiedName()) assertEquals(2, annotation.attributes().size) assertEquals("from", annotation.findAttribute("from")?.name) assertEquals("20", annotation.findAttribute("from")?.value.toString()) @@ -67,13 +67,13 @@ class TextBackedAnnotationItemTest { fun testIntDef() { val annotation = TextBackedAnnotationItem( dummyCodebase, - "@android.support.annotation.IntDef({STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT})" + "@androidx.annotation.IntDef({STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT})" ) assertEquals( - "@android.support.annotation.IntDef({STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT})", + "@androidx.annotation.IntDef({STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT})", annotation.toSource() ) - assertEquals("android.support.annotation.IntDef", annotation.qualifiedName()) + assertEquals("androidx.annotation.IntDef", annotation.qualifiedName()) assertEquals(1, annotation.attributes().size) val attribute = annotation.findAttribute("value") assertNotNull(attribute) diff --git a/stub-annotations/.gitignore b/stub-annotations/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..dd06f843d5c837553e42f1110d7f123abbccfb40 --- /dev/null +++ b/stub-annotations/.gitignore @@ -0,0 +1,8 @@ +*.iml +.gradle +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/runConfigurations +/out +/build diff --git a/stub-annotations/README.md b/stub-annotations/README.md new file mode 100644 index 0000000000000000000000000000000000000000..29d6921a3cf179baf037454a1c1bbcb2af2b1fcc --- /dev/null +++ b/stub-annotations/README.md @@ -0,0 +1,39 @@ +Stub Annotations + +The annotations in these packages are used to compile +the stubs. They are mostly identical to the annotations +in the support library, but + +(1) There are some annotations here which are not in + the support library, such as @RecentlyNullable and + @RecentlyNonNull. These are used *only* in the stubs + to automatically mark code as recently annotated + with null/non-null. We do *not* want these annotations + in the source code; the recent-ness is computed at + build time and injected into the stubs in place + of the normal null annotations. + +(2) There are some annotations in the support library + that do not apply here, such as @Keep, + @VisibleForTesting, etc. + +(3) Only class retention annotations are interesting for + the stubs; that means for example that we don't + include @IntDef and @StringDef. + +(4) We've tweaked the retention of some of the support + library annotations; some of them were accidentally + source retention, and here they are class retention. + +(5) We've tweaked the nullness annotations to include + TYPE_PARAMETER and TYPE_USE targets, and removed + package, local variable, annotation type, etc. + +(6) There are some other differences; for example, the + @RequiresPermission annotation here has an extra + "apis" field used for merged historical annotations + to express the applicable API range. + +Essentially, this is a curated list of exactly the +set of annotations we allow injected into the stubs. + diff --git a/stub-annotations/src/main/java/androidx/annotation/AnimRes.java b/stub-annotations/src/main/java/androidx/annotation/AnimRes.java new file mode 100644 index 0000000000000000000000000000000000000000..952fcf47c6921ef915b64b840308094d816d0deb --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/AnimRes.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface AnimRes {} diff --git a/stub-annotations/src/main/java/androidx/annotation/AnimatorRes.java b/stub-annotations/src/main/java/androidx/annotation/AnimatorRes.java new file mode 100644 index 0000000000000000000000000000000000000000..6918e9a748279c444f3ff059fd732830976968ce --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/AnimatorRes.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface AnimatorRes {} diff --git a/stub-annotations/src/main/java/androidx/annotation/AnyRes.java b/stub-annotations/src/main/java/androidx/annotation/AnyRes.java new file mode 100644 index 0000000000000000000000000000000000000000..84d6177443ed0e73a9e8c7b48b8d213a4ad6a472 --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/AnyRes.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface AnyRes {} diff --git a/stub-annotations/src/main/java/androidx/annotation/AnyThread.java b/stub-annotations/src/main/java/androidx/annotation/AnyThread.java new file mode 100644 index 0000000000000000000000000000000000000000..ab4eddc0ec82f8ac0730f229ab285f4e38b599c1 --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/AnyThread.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({METHOD, CONSTRUCTOR, TYPE, PARAMETER}) +public @interface AnyThread {} diff --git a/stub-annotations/src/main/java/androidx/annotation/ArrayRes.java b/stub-annotations/src/main/java/androidx/annotation/ArrayRes.java new file mode 100644 index 0000000000000000000000000000000000000000..90a4c29b3e02e16afa83b05debb4fa6bc34baca2 --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/ArrayRes.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface ArrayRes {} diff --git a/stub-annotations/src/main/java/androidx/annotation/AttrRes.java b/stub-annotations/src/main/java/androidx/annotation/AttrRes.java new file mode 100644 index 0000000000000000000000000000000000000000..296e9e9d7693b73669062793b6d06d902d8f862b --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/AttrRes.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface AttrRes {} diff --git a/stub-annotations/src/main/java/androidx/annotation/BinderThread.java b/stub-annotations/src/main/java/androidx/annotation/BinderThread.java new file mode 100644 index 0000000000000000000000000000000000000000..246e092b4b9654e2478dea54cf5f5462bc069839 --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/BinderThread.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({METHOD, CONSTRUCTOR, TYPE, PARAMETER}) +public @interface BinderThread {} diff --git a/stub-annotations/src/main/java/androidx/annotation/BoolRes.java b/stub-annotations/src/main/java/androidx/annotation/BoolRes.java new file mode 100644 index 0000000000000000000000000000000000000000..ec44e6efa911b344894e686735f772679c6a5074 --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/BoolRes.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface BoolRes {} diff --git a/stub-annotations/src/main/java/android/support/annotation/Migrate.java b/stub-annotations/src/main/java/androidx/annotation/CallSuper.java similarity index 73% rename from stub-annotations/src/main/java/android/support/annotation/Migrate.java rename to stub-annotations/src/main/java/androidx/annotation/CallSuper.java index 233ce52c8620a7b77d81e19be1145c4c470dbfc1..d099272bf903771fd272f53a155f473d67e6b3e5 100644 --- a/stub-annotations/src/main/java/android/support/annotation/Migrate.java +++ b/stub-annotations/src/main/java/androidx/annotation/CallSuper.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,17 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.support.annotation; +package androidx.annotation; -import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.CLASS; -import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; -@Documented +/** Stub only annotation. Do not use directly. */ @Retention(CLASS) -@Target({ANNOTATION_TYPE}) -public @interface Migrate { -} +@Target({METHOD}) +public @interface CallSuper {} diff --git a/stub-annotations/src/main/java/androidx/annotation/CheckResult.java b/stub-annotations/src/main/java/androidx/annotation/CheckResult.java new file mode 100644 index 0000000000000000000000000000000000000000..f96c750b93cdfb65cd62554b74218c7a0e893e3f --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/CheckResult.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({METHOD}) +public @interface CheckResult { + /** + * Defines the name of the suggested method to use instead, if applicable (using the same + * signature format as javadoc.) If there is more than one possibility, list them all separated + * by commas. + * + * <p>For example, ProcessBuilder has a method named {@code redirectErrorStream()} which sounds + * like it might redirect the error stream. It does not. It's just a getter which returns + * whether the process builder will redirect the error stream, and to actually set it, you must + * call {@code redirectErrorStream(boolean)}. In that case, the method should be defined like + * this: + * + * <pre> + * @CheckResult(suggest="#redirectErrorStream(boolean)") + * public boolean redirectErrorStream() { ... } + * </pre> + */ + String suggest() default ""; +} diff --git a/stub-annotations/src/main/java/androidx/annotation/ColorInt.java b/stub-annotations/src/main/java/androidx/annotation/ColorInt.java new file mode 100644 index 0000000000000000000000000000000000000000..f6972dd1ed3b7a8218b429573c9701db015c3663 --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/ColorInt.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD}) +public @interface ColorInt {} diff --git a/stub-annotations/src/main/java/android/support/annotation/NewlyNullable.java b/stub-annotations/src/main/java/androidx/annotation/ColorLong.java similarity index 75% rename from stub-annotations/src/main/java/android/support/annotation/NewlyNullable.java rename to stub-annotations/src/main/java/androidx/annotation/ColorLong.java index e11fe4f79d91ae8bd8983fa19502fa982e19e9d0..46937da296e83ac84a2d662edfea92d58b3fc0f4 100644 --- a/stub-annotations/src/main/java/android/support/annotation/NewlyNullable.java +++ b/stub-annotations/src/main/java/androidx/annotation/ColorLong.java @@ -13,23 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.support.annotation; -import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +package androidx.annotation; + import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.LOCAL_VARIABLE; import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.ElementType.PACKAGE; import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.RetentionPolicy.CLASS; -import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; -@Documented +/** Stub only annotation. Do not use directly. */ @Retention(CLASS) -@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE}) -@Migrate -public @interface NewlyNullable { -} +@Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD}) +public @interface ColorLong {} diff --git a/stub-annotations/src/main/java/androidx/annotation/ColorRes.java b/stub-annotations/src/main/java/androidx/annotation/ColorRes.java new file mode 100644 index 0000000000000000000000000000000000000000..558f4efc0d68c51a5474ad131f251e1df72b6219 --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/ColorRes.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface ColorRes {} diff --git a/stub-annotations/src/main/java/androidx/annotation/DimenRes.java b/stub-annotations/src/main/java/androidx/annotation/DimenRes.java new file mode 100644 index 0000000000000000000000000000000000000000..95f9a7d85ba70fbc0e1dd03faa1e29d240915589 --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/DimenRes.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface DimenRes {} diff --git a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/Dimension.java similarity index 80% rename from stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java rename to stub-annotations/src/main/java/androidx/annotation/Dimension.java index 83f4e5a552c6b0071f0d226495f2294cc533a9ca..64defe8525216285eeb9d27f99a4f2f82f36c45f 100644 --- a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java +++ b/stub-annotations/src/main/java/androidx/annotation/Dimension.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,23 +13,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.support.annotation; + +package androidx.annotation; import static java.lang.annotation.ElementType.ANNOTATION_TYPE; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.LOCAL_VARIABLE; import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.ElementType.PACKAGE; import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.RetentionPolicy.CLASS; -import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; -@Documented +/** Stub only annotation. Do not use directly. */ @Retention(CLASS) -@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE}) -@Migrate -public @interface NewlyNonNull { +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE, ANNOTATION_TYPE}) +public @interface Dimension { + int unit() default PX; + + int DP = 0; + int PX = 1; + int SP = 2; } diff --git a/stub-annotations/src/main/java/androidx/annotation/DrawableRes.java b/stub-annotations/src/main/java/androidx/annotation/DrawableRes.java new file mode 100644 index 0000000000000000000000000000000000000000..92ac64041a1a40dee031d294a8c7c897089164a8 --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/DrawableRes.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface DrawableRes {} diff --git a/stub-annotations/src/main/java/androidx/annotation/FloatRange.java b/stub-annotations/src/main/java/androidx/annotation/FloatRange.java new file mode 100644 index 0000000000000000000000000000000000000000..7cb285609dffb1b69c5b731a4ea7fe98497446cb --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/FloatRange.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE, ANNOTATION_TYPE}) +public @interface FloatRange { + /** Smallest value. Whether it is inclusive or not is determined by {@link #fromInclusive} */ + double from() default Double.NEGATIVE_INFINITY; + /** Largest value. Whether it is inclusive or not is determined by {@link #toInclusive} */ + double to() default Double.POSITIVE_INFINITY; + + /** Whether the from value is included in the range */ + boolean fromInclusive() default true; + + /** Whether the to value is included in the range */ + boolean toInclusive() default true; +} diff --git a/stub-annotations/src/main/java/android/support/annotation/RecentlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/FontRes.java similarity index 75% rename from stub-annotations/src/main/java/android/support/annotation/RecentlyNonNull.java rename to stub-annotations/src/main/java/androidx/annotation/FontRes.java index d2309fceb7f1d32790680021eca1663921c1cff3..0ebca727e4fd6f3671000e754b28a5db17cd1117 100644 --- a/stub-annotations/src/main/java/android/support/annotation/RecentlyNonNull.java +++ b/stub-annotations/src/main/java/androidx/annotation/FontRes.java @@ -13,23 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.support.annotation; +package androidx.annotation; -import static java.lang.annotation.ElementType.ANNOTATION_TYPE; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.LOCAL_VARIABLE; import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.ElementType.PACKAGE; import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.RetentionPolicy.CLASS; -import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; -@Documented +/** Stub only annotation. Do not use directly. */ @Retention(CLASS) -@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE}) -@Migrate -public @interface RecentlyNonNull { -} +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface FontRes {} diff --git a/stub-annotations/src/main/java/androidx/annotation/FractionRes.java b/stub-annotations/src/main/java/androidx/annotation/FractionRes.java new file mode 100644 index 0000000000000000000000000000000000000000..98ec5345c3e7ed741edb8594e44032ff4891579b --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/FractionRes.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface FractionRes {} diff --git a/stub-annotations/src/main/java/androidx/annotation/HalfFloat.java b/stub-annotations/src/main/java/androidx/annotation/HalfFloat.java new file mode 100644 index 0000000000000000000000000000000000000000..a239a82bf75c10a68e10e651e57cbe8c8181fefc --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/HalfFloat.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD}) +public @interface HalfFloat {} diff --git a/stub-annotations/src/main/java/androidx/annotation/IdRes.java b/stub-annotations/src/main/java/androidx/annotation/IdRes.java new file mode 100644 index 0000000000000000000000000000000000000000..0ba207273d507f9d6bbb47a5105cc31b366363be --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/IdRes.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface IdRes {} diff --git a/stub-annotations/src/main/java/android/support/annotation/RecentlyNullable.java b/stub-annotations/src/main/java/androidx/annotation/IntRange.java similarity index 77% rename from stub-annotations/src/main/java/android/support/annotation/RecentlyNullable.java rename to stub-annotations/src/main/java/androidx/annotation/IntRange.java index d24bad08484532b11211e0ab45c48feff46f2c45..e37b5cca4eff329dd7e6cbbef5e477fa628707f2 100644 --- a/stub-annotations/src/main/java/android/support/annotation/RecentlyNullable.java +++ b/stub-annotations/src/main/java/androidx/annotation/IntRange.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,23 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.support.annotation; +package androidx.annotation; import static java.lang.annotation.ElementType.ANNOTATION_TYPE; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.LOCAL_VARIABLE; import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.ElementType.PACKAGE; import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.RetentionPolicy.CLASS; -import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; -@Documented +/** Stub only annotation. Do not use directly. */ @Retention(CLASS) -@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE}) -@Migrate -public @interface RecentlyNullable { +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE, ANNOTATION_TYPE}) +public @interface IntRange { + /** Smallest value, inclusive */ + long from() default Long.MIN_VALUE; + /** Largest value, inclusive */ + long to() default Long.MAX_VALUE; } diff --git a/stub-annotations/src/main/java/androidx/annotation/IntegerRes.java b/stub-annotations/src/main/java/androidx/annotation/IntegerRes.java new file mode 100644 index 0000000000000000000000000000000000000000..7ab05ea52572eb0013a8c360adfb1b264d3af864 --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/IntegerRes.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface IntegerRes {} diff --git a/stub-annotations/src/main/java/androidx/annotation/InterpolatorRes.java b/stub-annotations/src/main/java/androidx/annotation/InterpolatorRes.java new file mode 100644 index 0000000000000000000000000000000000000000..f8862f98c01f515cbf917cedf0a994f35acd1e0e --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/InterpolatorRes.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface InterpolatorRes {} diff --git a/stub-annotations/src/main/java/androidx/annotation/LayoutRes.java b/stub-annotations/src/main/java/androidx/annotation/LayoutRes.java new file mode 100644 index 0000000000000000000000000000000000000000..a3ed0c8199b2498be7e06264c0797b5de464bb97 --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/LayoutRes.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface LayoutRes {} diff --git a/stub-annotations/src/main/java/androidx/annotation/MainThread.java b/stub-annotations/src/main/java/androidx/annotation/MainThread.java new file mode 100644 index 0000000000000000000000000000000000000000..d8a5e60b5ba60ecf640885401025eee808309744 --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/MainThread.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({METHOD, CONSTRUCTOR, TYPE, PARAMETER}) +public @interface MainThread {} diff --git a/stub-annotations/src/main/java/androidx/annotation/MenuRes.java b/stub-annotations/src/main/java/androidx/annotation/MenuRes.java new file mode 100644 index 0000000000000000000000000000000000000000..b7914f2587d3a6ee96633fc722071d6f95a2f039 --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/MenuRes.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface MenuRes {} diff --git a/stub-annotations/src/main/java/androidx/annotation/NavigationRes.java b/stub-annotations/src/main/java/androidx/annotation/NavigationRes.java new file mode 100644 index 0000000000000000000000000000000000000000..b964de0a9813bae0a644aec63fc008dd0f8c1ff2 --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/NavigationRes.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface NavigationRes {} diff --git a/stub-annotations/src/main/java/androidx/annotation/NonNull.java b/stub-annotations/src/main/java/androidx/annotation/NonNull.java new file mode 100644 index 0000000000000000000000000000000000000000..3b41cc8a5b36231d071190377445619fcd9eadc5 --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/NonNull.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.PACKAGE; +import static java.lang.annotation.ElementType.TYPE_PARAMETER; +import static java.lang.annotation.ElementType.TYPE_USE; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, PACKAGE, TYPE_PARAMETER, TYPE_USE}) +public @interface NonNull {} diff --git a/stub-annotations/src/main/java/androidx/annotation/Nullable.java b/stub-annotations/src/main/java/androidx/annotation/Nullable.java new file mode 100644 index 0000000000000000000000000000000000000000..0e6abcec768694bc594e287e64615cf6838447f6 --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/Nullable.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PACKAGE; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE_PARAMETER; +import static java.lang.annotation.ElementType.TYPE_USE; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, PACKAGE, TYPE_PARAMETER, TYPE_USE}) +public @interface Nullable {} diff --git a/stub-annotations/src/main/java/androidx/annotation/PluralsRes.java b/stub-annotations/src/main/java/androidx/annotation/PluralsRes.java new file mode 100644 index 0000000000000000000000000000000000000000..f2741cbf231b4a63897d5e92c8d6d94e21d7321a --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/PluralsRes.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface PluralsRes {} diff --git a/stub-annotations/src/main/java/androidx/annotation/Px.java b/stub-annotations/src/main/java/androidx/annotation/Px.java new file mode 100644 index 0000000000000000000000000000000000000000..5f8f2ff1c7d7ac216db4d39b0bf084a626ae26e9 --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/Px.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +@Dimension(unit = Dimension.PX) +public @interface Px {} diff --git a/stub-annotations/src/main/java/androidx/annotation/RawRes.java b/stub-annotations/src/main/java/androidx/annotation/RawRes.java new file mode 100644 index 0000000000000000000000000000000000000000..af874df36a50dc05674d1b8d4c22fce56edee9ba --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/RawRes.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface RawRes {} diff --git a/stub-annotations/src/main/java/androidx/annotation/RecentlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/RecentlyNonNull.java new file mode 100644 index 0000000000000000000000000000000000000000..575b3ebb924042d1962ac3b1b71cb9c7ceec6b5c --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/RecentlyNonNull.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE_USE; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, TYPE_USE}) +public @interface RecentlyNonNull {} diff --git a/stub-annotations/src/main/java/androidx/annotation/RecentlyNullable.java b/stub-annotations/src/main/java/androidx/annotation/RecentlyNullable.java new file mode 100644 index 0000000000000000000000000000000000000000..8a9141e4e1a0f22c3c83a13613f70209c9e9f0a8 --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/RecentlyNullable.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE_USE; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, TYPE_USE}) +public @interface RecentlyNullable {} diff --git a/stub-annotations/src/main/java/androidx/annotation/RequiresApi.java b/stub-annotations/src/main/java/androidx/annotation/RequiresApi.java new file mode 100644 index 0000000000000000000000000000000000000000..f8824b3a7219720c25bff2660eff83674c29a419 --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/RequiresApi.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({TYPE, METHOD, CONSTRUCTOR, FIELD}) +public @interface RequiresApi { + /** + * The API level to require. Alias for {@link #api} which allows you to leave out the {@code + * api=} part. + */ + @IntRange(from = 1) + int value() default 1; + + /** The API level to require */ + @IntRange(from = 1) + int api() default 1; +} diff --git a/stub-annotations/src/main/java/androidx/annotation/RequiresFeature.java b/stub-annotations/src/main/java/androidx/annotation/RequiresFeature.java new file mode 100644 index 0000000000000000000000000000000000000000..8ca1352e167b48384ab151f0299ed0e1f758df67 --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/RequiresFeature.java @@ -0,0 +1,41 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({TYPE, FIELD, METHOD, CONSTRUCTOR}) +public @interface RequiresFeature { + /** The name of the feature that is required. */ + String name(); + + /** + * Defines the name of the method that should be called to check whether the feature is + * available, using the same signature format as javadoc. The feature checking method can have + * multiple parameters, but the feature name parameter must be of type String and must also be + * the first String-type parameter. + */ + String enforcement(); +} diff --git a/stub-annotations/src/main/java/androidx/annotation/RequiresPermission.java b/stub-annotations/src/main/java/androidx/annotation/RequiresPermission.java new file mode 100644 index 0000000000000000000000000000000000000000..74f37e3230a08a7a2a784e74d667dfc533f4551c --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/RequiresPermission.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({ANNOTATION_TYPE, METHOD, CONSTRUCTOR, FIELD, PARAMETER}) +public @interface RequiresPermission { + /** + * The name of the permission that is required, if precisely one permission is required. If more + * than one permission is required, specify either {@link #allOf()} or {@link #anyOf()} instead. + * + * <p>If specified, {@link #anyOf()} and {@link #allOf()} must both be null. + */ + String value() default ""; + + /** + * Specifies a list of permission names that are all required. + * + * <p>If specified, {@link #anyOf()} and {@link #value()} must both be null. + */ + String[] allOf() default {}; + + /** + * Specifies a list of permission names where at least one is required + * + * <p>If specified, {@link #allOf()} and {@link #value()} must both be null. + */ + String[] anyOf() default {}; + + /** + * If true, the permission may not be required in all cases (e.g. it may only be enforced on + * certain platforms, or for certain call parameters, etc. + */ + boolean conditional() default false; + + // STUBS ONLY: historical API range for when this permission applies. + // Used for merged annotations. + String apis() default ""; + + /** + * Specifies that the given permission is required for read operations. + * + * <p>When specified on a parameter, the annotation indicates that the method requires a + * permission which depends on the value of the parameter (and typically the corresponding field + * passed in will be one of a set of constants which have been annotated with a + * {@code @RequiresPermission} annotation.) + */ + @Target({FIELD, METHOD, PARAMETER}) + @interface Read { + RequiresPermission value() default @RequiresPermission; + } + + /** + * Specifies that the given permission is required for write operations. + * + * <p>When specified on a parameter, the annotation indicates that the method requires a + * permission which depends on the value of the parameter (and typically the corresponding field + * passed in will be one of a set of constants which have been annotated with a + * {@code @RequiresPermission} annotation.) + */ + @Target({FIELD, METHOD, PARAMETER}) + @interface Write { + RequiresPermission value() default @RequiresPermission; + } +} diff --git a/stub-annotations/src/main/java/androidx/annotation/RestrictTo.java b/stub-annotations/src/main/java/androidx/annotation/RestrictTo.java new file mode 100644 index 0000000000000000000000000000000000000000..51ebdbde90d3dc76ac189916a36cea370d83376f --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/RestrictTo.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PACKAGE; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({ANNOTATION_TYPE, TYPE, METHOD, CONSTRUCTOR, FIELD, PACKAGE}) +public @interface RestrictTo { + + /** The scope to which usage should be restricted. */ + Scope[] value(); + + enum Scope { + /** + * Restrict usage to code within the same library (e.g. the same gradle group ID and + * artifact ID). + */ + LIBRARY, + + /** + * Restrict usage to code within the same group of libraries. This corresponds to the gradle + * group ID. + */ + LIBRARY_GROUP, + + /** + * Restrict usage to code within the same group ID (based on gradle group ID). This is an + * alias for {@link #LIBRARY_GROUP}. + * + * @deprecated Use {@link #LIBRARY_GROUP} instead + */ + @Deprecated + GROUP_ID, + + /** Restrict usage to tests. */ + TESTS, + + /** + * Restrict usage to subclasses of the enclosing class. + * + * <p><strong>Note:</strong> This scope should not be used to annotate packages. + */ + SUBCLASSES, + } +} diff --git a/stub-annotations/src/main/java/androidx/annotation/Size.java b/stub-annotations/src/main/java/androidx/annotation/Size.java new file mode 100644 index 0000000000000000000000000000000000000000..412a9ca788d79e6cd0cad9e0ec3f15482b0d6404 --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/Size.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({PARAMETER, LOCAL_VARIABLE, METHOD, FIELD, ANNOTATION_TYPE}) +public @interface Size { + /** An exact size (or -1 if not specified) */ + long value() default -1; + /** A minimum size, inclusive */ + long min() default Long.MIN_VALUE; + /** A maximum size, inclusive */ + long max() default Long.MAX_VALUE; + /** The size must be a multiple of this factor */ + long multiple() default 1; + + // STUBS ONLY: historical API range for when this permission applies. + // Used for merged annotations. + String apis() default ""; +} diff --git a/stub-annotations/src/main/java/androidx/annotation/StringRes.java b/stub-annotations/src/main/java/androidx/annotation/StringRes.java new file mode 100644 index 0000000000000000000000000000000000000000..ecb78b74c6b42aef3aa3595c1ca91759b58c2aa7 --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/StringRes.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface StringRes {} diff --git a/stub-annotations/src/main/java/androidx/annotation/StyleRes.java b/stub-annotations/src/main/java/androidx/annotation/StyleRes.java new file mode 100644 index 0000000000000000000000000000000000000000..d257f99d04cfa973b690e6e6f16243e7d97f38a4 --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/StyleRes.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface StyleRes {} diff --git a/stub-annotations/src/main/java/androidx/annotation/StyleableRes.java b/stub-annotations/src/main/java/androidx/annotation/StyleableRes.java new file mode 100644 index 0000000000000000000000000000000000000000..4d780c084a4dcac1c9abd15c1ab12114e57c3f5b --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/StyleableRes.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface StyleableRes {} diff --git a/stub-annotations/src/main/java/androidx/annotation/TransitionRes.java b/stub-annotations/src/main/java/androidx/annotation/TransitionRes.java new file mode 100644 index 0000000000000000000000000000000000000000..f1c0e346d11890ecd37a4ab463715962e962bdc5 --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/TransitionRes.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD}) +public @interface TransitionRes {} diff --git a/stub-annotations/src/main/java/androidx/annotation/UiThread.java b/stub-annotations/src/main/java/androidx/annotation/UiThread.java new file mode 100644 index 0000000000000000000000000000000000000000..bdcf7213d149c9bbf250b25cab58b72dfb28dc63 --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/UiThread.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({METHOD, CONSTRUCTOR, TYPE, PARAMETER}) +public @interface UiThread {} diff --git a/stub-annotations/src/main/java/androidx/annotation/WorkerThread.java b/stub-annotations/src/main/java/androidx/annotation/WorkerThread.java new file mode 100644 index 0000000000000000000000000000000000000000..dbb11a5b6456310a8fcfabed67787401604f56da --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/WorkerThread.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({METHOD, CONSTRUCTOR, TYPE, PARAMETER}) +public @interface WorkerThread {} diff --git a/stub-annotations/src/main/java/androidx/annotation/XmlRes.java b/stub-annotations/src/main/java/androidx/annotation/XmlRes.java new file mode 100644 index 0000000000000000000000000000000000000000..cb383651cd04f59cbbda92a33f9194433f51b8b3 --- /dev/null +++ b/stub-annotations/src/main/java/androidx/annotation/XmlRes.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.annotation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** Stub only annotation. Do not use directly. */ +@Retention(CLASS) +@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) +public @interface XmlRes {}