Snap for 4829593 from 22e57c4b9f202219d0545e990cc00365cc931e17 to pi-release
Change-Id: Ie269e238ec921b99edc9d8e9e2e4d45637fd8bb2
diff --git a/.gitignore b/.gitignore
index 8ddfee5..2e527fd 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 0000000..7b2b7ad
--- /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 a55e7a1..79ee123 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 304a161..4eefbdf 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 bf8e898..6b14ab5 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 267d99c..8b90e76 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 a78c468..42f9f3a 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 84da703..f6d5d5f 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 f9e7119..4456034 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 @@
}
}
+repositories {
+ google()
+ jcenter()
+}
+
apply plugin: 'application'
apply plugin: 'java'
apply plugin: 'kotlin'
@@ -42,11 +47,6 @@
}
}
-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 @@
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 @@
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 0000000..2128bdd
--- /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 0000000..c4e8392
--- /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 0000000..4be43e7
--- /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 0000000..a6b79c5
--- /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 7006579..78ad9c7 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 @@
// found beginning
break
}
-
}
begin += tag.length
}
@@ -152,8 +151,8 @@
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 @@
}
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 @@
}
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 @@
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 @@
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 @@
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 @@
) {
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 @@
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 1f2e4aa..c229bca 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.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 @@
}
override fun visitParameter(parameter: ParameterItem) {
+ if (!parameter.requiresNullnessInfo()) {
+ return
+ }
allParameters++
if (parameter.modifiers.annotations().any { it.isNonNull() || it.isNullable() }) {
annotatedParameters++
@@ -81,6 +87,9 @@
}
override fun visitField(field: FieldItem) {
+ if (!field.requiresNullnessInfo()) {
+ return
+ }
allFields++
if (field.modifiers.annotations().any { it.isNonNull() || it.isNullable() }) {
annotatedFields++
@@ -88,6 +97,9 @@
}
override fun visitMethod(method: MethodItem) {
+ if (!method.requiresNullnessInfo()) {
+ return
+ }
allMethods++
if (method.modifiers.annotations().any { it.isNonNull() || it.isNullable() }) {
annotatedMethods++
@@ -111,7 +123,7 @@
private fun percent(numerator: Int, denominator: Int): Int {
return if (denominator == 0) {
- 0
+ 100
} else {
numerator * 100 / denominator
}
@@ -147,7 +159,7 @@
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 @@
}
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 @@
{ 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 @@
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 @@
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 0000000..8147ef8
--- /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 cea88d6..11ff5d5 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.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_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.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 @@
} 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 @@
}
}
- 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 @@
}
}
+ 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 @@
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 @@
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 @@
} 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 @@
}
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 @@
}
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 @@
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++
+ }
+ }
+
+ 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
+ }
}
- return count
+ // 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 @@
// 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 @@
}
}
- 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 @@
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 @@
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 @@
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 @@
}
}
- 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 @@
}
}
- 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
+ return name == IDEA_NULLABLE ||
+ name == ANDROID_NULLABLE ||
+ name == ANDROIDX_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)
- }
-
- @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 @@
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 810e7e1..6cf8127 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.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 @@
val superConstructor = constructor.superConstructor
if (superConstructor == null ||
(superConstructor.containingClass() != superClass &&
- superConstructor.containingClass() != cls)
+ superConstructor.containingClass() != cls)
) {
constructor.superConstructor = superDefaultConstructor
}
@@ -195,7 +195,7 @@
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 @@
}
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 @@
val nextParameterCount = next.parameters().size
if (currentParameterCount <= nextParameterCount) {
current
- } else
- next
+ } else next
}
}
@@ -321,7 +320,6 @@
addInheritedStubsFrom(cls, hiddenSuperClasses, superClasses, filterEmit, filterReference)
addInheritedInterfacesFrom(cls, hiddenSuperClasses, filterReference)
-
}
private fun addInheritedInterfacesFrom(
@@ -338,7 +336,7 @@
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 @@
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 @@
}
}
- 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 @@
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 @@
fun mergeExternalAnnotations() {
val mergeAnnotations = options.mergeAnnotations
if (!mergeAnnotations.isEmpty()) {
- AnnotationsMerger(codebase, options.apiFilter).merge(mergeAnnotations)
+ AnnotationsMerger(codebase).merge(mergeAnnotations)
}
}
@@ -531,15 +526,12 @@
*/
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 @@
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,74 +577,51 @@
}
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
+ } 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
+ }
+ 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
- }
- }
-
- override fun visitMethod(method: MethodItem) {
- if (!filter.hasMethod(
- method.containingClass().qualifiedName(), method.name(),
- method.formatParameters()
- )
- ) {
- method.included = false
- }
- }
-
- override fun visitField(field: FieldItem) {
- if (!filter.hasField(field.containingClass().qualifiedName(), field.name())) {
- field.included = false
- }
- }
- })
- }
- }
-
private fun checkHiddenTypes() {
packages.accept(object : ApiVisitor(codebase, visitConstructorsAsMethods = false) {
override fun visitMethod(method: MethodItem) {
@@ -769,8 +745,8 @@
)
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 @@
reporter.report(
Errors.REMOVED_FIELD, method,
"None of the permissions ${missing.joinToString()} are defined by manifest " +
- "${codebase.manifest}."
+ "${codebase.manifest}."
)
}
@@ -789,9 +765,9 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
} 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 @@
// 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 @@
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 @@
}
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 0000000..23c9be7
--- /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 ff1a430..1c6e2f6 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.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 @@
* 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 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 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(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 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 @@
}
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 @@
index2++
}
}
-
} else {
// All the remaining items in oldList have been deleted
while (index1 < length1) {
@@ -139,8 +148,20 @@
}
}
+ 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 564e4b9..5c21e38 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 @@
/** 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"
*/
@@ -89,6 +92,13 @@
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
* doclava1 format
@@ -150,8 +160,11 @@
* 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 @@
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 ba19c59..ab77657 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 @@
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 @@
}
}
-
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 @@
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 @@
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 @@
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 @@
// 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 7badba3..d863b9b 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_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 15b4573..27952a5 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 @@
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 @@
}
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 1e8d470..2c91eaa 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 @@
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 @@
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 @@
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 @@
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 @@
}
// 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 @@
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 @@
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 @@
}
// 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 @@
}
// 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 @@
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 @@
"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 @@
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 @@
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 @@
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 @@
/** Replacements to perform in documentation */
val typos = mapOf(
- //noinspection SpellCheckingInspection
"Andriod" to "Android",
"Kitkat" to "KitKat",
"LemonMeringuePie" to "Lollipop",
@@ -498,34 +529,34 @@
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 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 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 describeApiLevel(level: Int): String {
+ return "${SdkVersionInfo.getVersionString(level)} ${SdkVersionInfo.getCodeName(level)} ($level)"
+ }
}
fun ApiLookup.getClassVersion(cls: PsiClass): Int {
diff --git a/src/main/java/com/android/tools/metalava/Driver.kt b/src/main/java/com/android/tools/metalava/Driver.kt
index cdf2b70..c538126 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.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.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.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 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 @@
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 @@
// 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 @@
// 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 @@
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 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 @@
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 @@
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 @@
// 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 @@
// 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 @@
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 @@
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 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 @@
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)
+ 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)
+ }
}
- /*
- // 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"
- )
- */
-
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 tick() {
tick++
if (tick % 100 == 0) {
+ if (!options.verbose) {
+ return
+ }
options.stdout.print(".")
options.stdout.flush()
}
@@ -681,10 +730,8 @@
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 @@
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 @@
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 f97c112..dd3fb67 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()
- extractor.export(options.externalAnnotations, null)
+ private data class AnnotationHolder(
+ val annotationClass: ClassItem?,
+ val annotationItem: AnnotationItem,
+ val uAnnotation: UAnnotation?
+ )
- if (typedefFile != null) {
- extractor.writeTypedefFile(typedefFile)
- }
+ private val classToAnnotationHolder = mutableMapOf<String, AnnotationHolder>()
- if (rmTypeDefs.isNotEmpty()) {
- if (typedefFile != null) {
- Extractor.removeTypedefClasses(rmTypeDefs, typedefFile)
- } else {
- extractor.removeTypedefClasses()
+ 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
+ }
+ }
+ }
+
+ return false
+ }
+
+ 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")
+ }
+ }
+ }
+
+ 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 {
+ 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 ebda9ad..7af08d1 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 @@
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 @@
} 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 @@
}
/** 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 60c727f..8cb8d6b 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 @@
* {@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
+ markRecent(new)
+ }
+ }
+ override fun compare(old: MethodItem, new: MethodItem) {
+ val newType = new.returnType() ?: return
+ val oldType = old.returnType() ?: return
+ checkType(oldType, newType)
+ }
- val migration = findNullnessAnnotation(new) ?: return
- val modifiers = new.mutableModifiers()
- modifiers.removeAnnotation(migration)
+ override fun compare(old: FieldItem, new: FieldItem) {
+ val newType = new.type()
+ val oldType = old.type()
+ checkType(oldType, newType)
+ }
- // 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)
+ override fun compare(old: ParameterItem, new: ParameterItem) {
+ val newType = new.type()
+ val oldType = old.type()
+ checkType(oldType, newType)
+ }
- 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))
- }
+ 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 @@
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 8fc443b..b2c1c68 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 @@
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_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_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_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_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 @@
/** 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 @@
/** 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 @@
/** 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 @@
/** 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 @@
*/
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 @@
*/
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 @@
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 @@
"-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 @@
"--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 @@
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 @@
}
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 @@
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 @@
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 @@
// 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 @@
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 @@
}
}
- "-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 @@
// doclava1 flags with two arguments
"-federate",
"-federationapi",
- "-artifact",
"-htmldir2" -> {
javadoc(arg)
index += 2
@@ -739,8 +790,7 @@
} 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 @@
++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 @@
}
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 @@
/** 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 @@
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 @@
}
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 @@
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 @@
"", "\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_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",
+ "$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.",
+
+ "$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.",
"", "\nExtracting Annotations:",
- ARG_EXTRACT_ANNOTATIONS + " <zipfile>", "Extracts annotations from the source files and writes them " +
- "into the given zip file",
-
- 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",
+ "$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 c8d1f0c..846b9ae 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 @@
path
} else {
val lineNumber = getLineNumber(psiFile.text, range.startOffset) + 1
- path + ":" + lineNumber
+ "$path:$lineNumber"
}
}
@@ -214,7 +214,10 @@
}
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 4f88b62..0d68845 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 @@
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 6d651d1..7fca3d8 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.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 @@
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 @@
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 @@
return
}
- if (compatibility.skipInheritedInterfaceMethods && method.inheritedInterfaceMethod) {
+ if (compatibility.skipInheritedMethods && method.inheritedMethod) {
return
}
@@ -92,7 +93,7 @@
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 @@
includeDeprecated = true,
includeAnnotations = compatibility.annotationsInSignatures,
skipNullnessAnnotations = options.outputKotlinStyleNulls,
- omitCommonPackages = options.omitCommonPackages
+ omitCommonPackages = options.omitCommonPackages,
+ onlyIncludeSignatureAnnotations = true
)
}
@@ -164,8 +166,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
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 832ff92..40a6a5a 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 @@
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 @@
// 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 @@
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 @@
}
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 @@
ModifierList.write(
writer, modifiers, item, removeAbstract = removeAbstract, removeFinal = removeFinal,
- addPublic = addPublic, includeAnnotations = generateAnnotations
+ addPublic = addPublic, includeAnnotations = generateAnnotations,
+ onlyIncludeSignatureAnnotations = true
)
}
@@ -308,9 +314,7 @@
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 @@
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 @@
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 @@
writer.write(")")
}
writer.write("null")
-
} else {
if (typeString != "boolean" && typeString != "int" && typeString != "long") {
writer.write("(")
@@ -471,8 +472,8 @@
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 @@
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 8a32691..8ba7551 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 @@
}
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 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 0000000..f46ca3a
--- /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 378d536..c5a7d59 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 30dc5f7..f37e9c3 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 @@
* 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 90dd511..d215ec0 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 @@
* 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 @@
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 a5b38dd..ab5af66 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 @@
}
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 @@
}
@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 e3b3225..afcac41 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 @@
} 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 @@
}
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 @@
}
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 @@
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 @@
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 @@
* @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 88bfbbb..fca21ff 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.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 @@
}
private static void parsePackage(TextCodebase api, Tokenizer tokenizer)
- throws ApiParseException {
+ throws ApiParseException {
String token;
String name;
TextPackageItem pkg;
@@ -114,7 +115,7 @@
}
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 @@
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 @@
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 @@
}
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 @@
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 @@
}
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 @@
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 @@
}
// 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 @@
}
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 @@
}
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 @@
}
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 @@
}
}
} 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 @@
}
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 dfbcaee..bdace72 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 @@
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 @@
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 3f7c9d7..6d091e2 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 @@
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 625d865..93ceee1 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 @@
// 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 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 6fc7557..084c655 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 @@
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 015533c..a7f098e 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 @@
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 0e73a53..2b66cf6 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 @@
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 a21f0f4..f87833f 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 @@
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 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 @@
/** 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 @@
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 @@
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 @@
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 @@
"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 @@
"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 @@
}
}
+ 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 @@
* 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 @@
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 @@
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 @@
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 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 328d5af..de95759 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 @@
}
return curr.containingPackage().qualifiedName().replace('.', '/') + "/" +
- fullName().replace('.', '$')
+ fullName().replace('.', '$')
}
/** The super class of this class, if any */
@@ -216,6 +216,12 @@
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 @@
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 @@
return true
}
-
/** Returns the corresponding compilation unit, if any */
fun getCompilationUnit(): CompilationUnit? = null
@@ -497,8 +533,8 @@
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 @@
}
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 @@
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 @@
return
}
- val emitClass = emitClass()
-
+ val emitClass = if (visitor.includeEmptyOuterClasses) emit() else emitClass()
if (emitClass) {
if (!visitor.visitingPackage) {
visitor.visitingPackage = true
@@ -789,7 +829,7 @@
}
}
- 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 c18e047..41ce549 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 @@
/** 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 @@
* 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 @@
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 b0773e8..f512833 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 @@
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 @@
) {
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 @@
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 @@
}
}
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 028960f..e698476 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 @@
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 @@
*/
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 726f32e..03eb387 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 @@
/** 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 @@
}
fun filteredThrowsTypes(predicate: Predicate<Item>): Collection<ClassItem> {
+ if (throwsTypes().isEmpty()) {
+ return emptyList()
+ }
return filteredThrowsTypes(predicate, LinkedHashSet())
}
@@ -87,7 +116,7 @@
// 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 @@
* 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 @@
if (pt1.toErasedTypeString() != pt2.toErasedTypeString()) {
return false
}
-
} else {
if (pt1 != pt2) {
return false
@@ -350,6 +378,10 @@
}
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 c05362b..435baf8 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
}
}
-
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 ed6e31c..ad4190a 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 @@
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 fe2c400..5d1eaa8 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 @@
*/
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 @@
// <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 784c80a..2a6d702 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 @@
// 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 @@
// 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 @@
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 @@
// 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 @@
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 @@
}
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 @@
}
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 @@
val middle = if (prefixWithStar) {
startSeparator + newText.split('\n').joinToString(separator = "\n") { " * $it" } +
- endSeparator
+ endSeparator
} else {
"$startSeparator$newText$endSeparator"
}
@@ -208,7 +205,7 @@
// 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 b8ebb45..4d78346 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 @@
val attributes = psiAnnotation.parameterList.attributes
if (attributes.isEmpty()) {
- return "@" + qualifiedName
+ return "@$qualifiedName"
}
val sb = StringBuilder(30)
@@ -299,7 +299,8 @@
// 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 94e9d16..b8b2504 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 @@
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 @@
}
}
}
- })
+ }
} catch (e: IOException) {
reporter.report(Errors.IO_ERROR, jarFile, e.message ?: e.toString())
}
@@ -365,8 +365,8 @@
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 @@
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 @@
inner!! // should be there now
return inner
}
-
}
return existing ?: return createClass(psiClass)
@@ -633,6 +632,9 @@
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 @@
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 @@
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 @@
} 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 @@
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 @@
} 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 @@
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 @@
} 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 @@
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 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 c5dc287..b1cfb48 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 @@
}
override var defaultConstructor: ConstructorItem? = null
+ override var artifact: String? = null
private var containingClass: PsiClassItem? = null
override fun containingClass(): PsiClassItem? = containingClass
@@ -165,7 +166,7 @@
if (psiClass.hasTypeParameters()) {
return PsiTypeParameterList(
codebase, psiClass.typeParameterList
- ?: return TypeParameterList.NONE
+ ?: return TypeParameterList.NONE
)
} else {
return TypeParameterList.NONE
@@ -315,7 +316,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
curr.containingClass
} else {
break
-
}
}
return list.asReversed().asSequence().joinToString(separator = ".") { it }
@@ -684,5 +689,5 @@
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 f70d21c..843d3a0 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 @@
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 4ee2455..cc14c10 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 @@
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 @@
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 1a65079..f04647a 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 @@
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 @@
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 d8621a7..591bcd7 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 @@
@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 @@
}
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 @@
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 c84ad8f..a54dc24 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.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 @@
*/
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 @@
if (psiMethod.hasTypeParameters()) {
return PsiTypeParameterList(
codebase, psiMethod.typeParameterList
- ?: return TypeParameterList.NONE
+ ?: return TypeParameterList.NONE
)
} else {
return TypeParameterList.NONE
@@ -138,11 +140,23 @@
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 @@
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 @@
)
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 d52a677..324625e 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 @@
}
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 @@
// 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 @@
* 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 a6a4be3..a15e083 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 @@
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 9a986e1..248d047 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 @@
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 @@
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 @@
return TextTypeItem.eraseAnnotations(typeString, false, true)
}
return typeString
-
} else {
return type.canonicalText
}
@@ -434,9 +438,9 @@
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 4a8b138..4911c56 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 @@
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 27a30c9..7fbfe59 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 @@
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 @@
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 @@
}
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 fe0114f..4262188 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 @@
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 732c6c4..50acde8 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 @@
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 0acb645..72968bd 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 @@
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 8fb461e..d187adc 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 @@
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 9ac8ceb..a627199 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 @@
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 c4e9e8c..306be6f 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 @@
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 @@
}
typeParameter
-
} else {
null
}
@@ -144,6 +143,8 @@
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 3ee9389..56d8375 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 @@
/** 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 @@
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 @@
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 be11ad6..dd2d971 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 dc749c3..f62c805 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 @@
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 @@
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 @@
"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 @@
"""
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 @@
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 0000000..0d367d1
--- /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 129f41e..654ed1a 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 @@
"""
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 @@
// 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 @@
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 @@
<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 330a1e2..abd3c1d 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 @@
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 @@
}
}
""",
- extraArguments = arrayOf("--hide-package", "android.support.annotation"),
+ extraArguments = arrayOf("--hide-package", "androidx.annotation"),
checkDoclava1 = false /* doesn't support parameter names */
)
}
@@ -122,7 +120,7 @@
java(
"""
package test.pkg;
- import android.support.annotation.DefaultValue;
+ import androidx.annotation.DefaultValue;
public class Foo {
public void foo(
@@ -143,7 +141,7 @@
}
}
""",
- extraArguments = arrayOf("--hide-package", "android.support.annotation"),
+ extraArguments = arrayOf("--hide-package", "androidx.annotation"),
checkDoclava1 = false /* doesn't support default Values */
)
}
@@ -172,7 +170,7 @@
}
}
""",
- extraArguments = arrayOf("--hide-package", "android.support.annotation"),
+ extraArguments = arrayOf("--hide-package", "androidx.annotation"),
checkDoclava1 = false /* doesn't support default Values */
)
}
@@ -248,7 +246,7 @@
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 @@
)
}
- @Ignore("Still broken: UAST is missing reified methods, and some missing symbol resolution")
@Test
fun `Kotlin Reified Methods`() {
check(
@@ -292,7 +289,7 @@
}
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 @@
"""
// 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 @@
// 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 @@
}
}
""",
- extraArguments = arrayOf("--hide-package", "android.support.annotation"),
+ extraArguments = arrayOf("--hide-package", "androidx.annotation"),
checkDoclava1 = false /* doesn't support Kotlin... */
)
}
@@ -472,7 +469,7 @@
}
}
""",
- extraArguments = arrayOf("--hide-package", "android.support.annotation"),
+ extraArguments = arrayOf("--hide-package", "androidx.annotation"),
checkDoclava1 = false /* doesn't support default Values */
)
}
@@ -1535,7 +1532,7 @@
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 @@
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 @@
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 @@
"""
@file:JvmName("-Foo")
- package test.pkg;
+ package test.pkg
+ @Suppress("unused")
inline fun String.printHelloWorld() { println("Hello World") }
"""
)
@@ -2163,7 +2178,7 @@
java(
"""
package test.pkg;
- public class Class1 {
+ public class Class1 implements MyInterface {
Class1(int arg) { }
/** @hide */
public void method1() { }
@@ -2205,23 +2220,34 @@
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 @@
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 @@
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 fe63f8b..7e9dcbd 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 @@
@Test
fun `Infer fully qualified names from shorter names`() {
-
check(
compatibilityMode = true,
extraArguments = arrayOf("--annotations-in-signatures"),
@@ -62,7 +61,7 @@
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 @@
@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 @@
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 0000000..940447c
--- /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 ecbb319..3b06bb9 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 @@
"""
@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 @@
),
supportParameterName
),
- extraArguments = arrayOf("--hide-package", "android.support.annotation")
+ extraArguments = arrayOf("--hide-package", "androidx.annotation")
)
}
@@ -1297,12 +1298,13 @@
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 @@
)
}
+ @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 @@
"--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 @@
// 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 @@
"--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 @@
}
}
-
// 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 4f6572f..f8ad6ec 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 @@
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 @@
* @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 @@
),
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 @@
@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 @@
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 @@
/**
* 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 @@
* @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 @@
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 @@
* 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 @@
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 @@
* 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 @@
/**
* 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 @@
/**
* 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 @@
* 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 @@
* 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 @@
/**
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
/**
* @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 @@
* @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 @@
)
}
-
@Test
fun `Generate overview html docs`() {
// If a codebase provides overview.html files in the a public package,
@@ -1124,6 +1129,40 @@
}
@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()
if (jdkPath == null) {
diff --git a/src/test/java/com/android/tools/metalava/DriverTest.kt b/src/test/java/com/android/tools/metalava/DriverTest.kt
index 2cd3e75..13ad8cd 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.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.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 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 @@
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 @@
/** 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 @@
/** 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 @@
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 @@
}
} 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 @@
}
}
- 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
androidJar.path,
*kotlinPathArgs,
*removedArgs,
+ *removedDexArgs,
*apiArgs,
*exactApiArgs,
*privateApiArgs,
+ *dexApiArgs,
*privateDexApiArgs,
*stubsArgs,
*stubsSourceListArgs,
@@ -498,6 +596,8 @@
*coverageStats,
*quiet,
*mergeAnnotationsArgs,
+ *jaifAnnotationsArgs,
+ *signatureAnnotationsArgs,
*previousApiArgs,
*migrateNullsArguments,
*checkCompatibilityArguments,
@@ -509,6 +609,8 @@
*sdkFilesArgs,
*importedPackageArgs.toTypedArray(),
*skipEmitPackagesArgs.toTypedArray(),
+ *artifactArgs,
+ *extractAnnotationsArgs,
*sourceList,
*extraArguments,
expectedFail = expectedFail
@@ -533,6 +635,15 @@
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 @@
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 @@
)
}
+ 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 @@
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 @@
}
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 @@
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 @@
)
}
- 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 @@
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 @@
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 @@
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 @@
@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 @@
"""
).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 @@
"""
).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 @@
@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 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 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 @@
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 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 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 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 @@
"""
).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 @@
}
"""
).indented()
-
diff --git a/src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt b/src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt
index 517abd7..427e41d 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;
-"""
+ public void setStyle(@DialogStyle int style, int theme) {
+ }
+
+ 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 {}
+
+ public void setFlags(Object first, @DialogFlags int flags) {
+ }
+
+ 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 `Skip class retention`() {
- val androidJar = getPlatformFile("android.jar")
+ fun `Check Kotlin and referencing hidden constants from typedef`() {
+ check(
+ sourceFiles = *arrayOf(
+ kotlin(
+ """
+ @file:Suppress("unused", "UseExpressionBody")
- val project = createProject(
- intDefTest,
- permissionsTest,
- manifest,
- intDefAnnotation,
- intRangeAnnotation,
- permissionAnnotation
- )
+ package test.pkg
- val output = temporaryFolder.newFile("annotations.zip")
+ import android.annotation.LongDef
- runDriver(
- "--sources",
- File(project, "src").path,
- "--classpath",
- androidJar.path,
- "--skip-class-retention",
- "--extract-annotations",
- output.path
- )
+ 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
- // 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>
+ const val TYPE_1 = "type1"
+ const val TYPE_2 = "type2"
+ const val UNRELATED_TYPE = "other"
-"""
+ class LongDefTest {
+
+ /** @hide */
+ @LongDef(STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT, HIDDEN)
+ @Retention(AnnotationRetention.SOURCE)
+ private annotation class DialogStyle
+
+ fun setStyle(@DialogStyle style: Int, theme: Int) {}
+
+ fun testLongDef(arg: Int) {
+ }
+
+ @LongDef(STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT, 3L, 3L + 1L, flag = true)
+ @Retention(AnnotationRetention.SOURCE)
+ private annotation class DialogFlags
+
+ fun setFlags(first: Any, @DialogFlags flags: Int) {}
+
+ 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>
+ """
+ )
)
}
+ @Ignore("Not working reliably")
@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;
-
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
-
- @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 {}
-
- 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;
-
- public void setStyle(@DialogStyle int style, int theme) {
- }
-
- @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 {}
-
- public void setFlags(Object first, @DialogFlags int flags) {
- }
-
- 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()
-
- @SuppressWarnings("all") // sample code
- private val permissionsTest = java(
- """
+ 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(
+ """
package test.pkg;
- import android.support.annotation.RequiresPermission;
-
- public class PermissionsTest {
- @RequiresPermission(Manifest.permission.MY_PERMISSION)
- public void myMethod() {
- }
- @RequiresPermission(anyOf={Manifest.permission.MY_PERMISSION,Manifest.permission.MY_PERMISSION2})
- public void myMethod2() {
- }
-
-
- @RequiresPermission.Read(@RequiresPermission(Manifest.permission.MY_READ_PERMISSION))
- @RequiresPermission.Write(@RequiresPermission(Manifest.permission.MY_WRITE_PERMISSION))
- public static final String CONTENT_URI = "";
- }
- """
- )
-
- @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 0000000..e5233fa
--- /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 575ad58..2360fb2 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 @@
java(
"""
package test.pkg;
- import android.support.annotation.ParameterName;
+ import androidx.annotation.ParameterName;
public class Test {
public void fun() { }
@@ -183,14 +183,14 @@
}
@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 c4e25e2..0557168 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 @@
}
@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 @@
api = """
package test.pkg {
public abstract class MyTest {
- method @NewlyNullable public Double convert1(Float);
+ method @RecentlyNullable public Double convert1(Float);
}
}
"""
@@ -81,7 +81,7 @@
}
@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 @@
api = """
package test.pkg {
public abstract class MyTest {
- method public Double convert1(@NewlyNonNull Float);
+ method public Double convert1(@RecentlyNonNull Float);
}
}
"""
@@ -134,7 +134,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
"""
)
}
+
+ @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 8adf2af..7c79caa 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 @@
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 @@
--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 @@
--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 @@
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 @@
--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 @@
--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 37e3c68..e66c35d 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 @@
"""
)
}
+
+ @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 71d35fb..5d7f7df 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 @@
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 @@
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 @@
* @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 @@
/**
* 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 @@
/** 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 @@
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 @@
)
}
+ @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 @@
// 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 @@
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 @@
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 @@
@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 @@
}
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 @@
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 @@
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 @@
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 @@
public abstract FileDescriptor getFileDescriptor();
}
+ @SuppressWarnings("UnnecessaryInterfaceModifier")
public static interface Closeable extends AutoCloseable {
}
+ @SuppressWarnings("UnnecessaryInterfaceModifier")
public static interface AutoCloseable {
}
@@ -2783,7 +2965,7 @@
*arrayOf(
java(
"""
- @android.support.annotation.Nullable
+ @androidx.annotation.Nullable
package test.pkg;
"""
),
@@ -2808,10 +2990,10 @@
}
""", // 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 2c8fb78..de29f8f 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 @@
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 778408e..8ea2750 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 @@
"--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 @@
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 cc4e121..f3c367a 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 @@
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 @@
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 @@
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 0000000..dd06f84
--- /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 0000000..29d6921
--- /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/android/support/annotation/NewlyNullable.java b/stub-annotations/src/main/java/android/support/annotation/NewlyNullable.java
deleted file mode 100644
index e11fe4f..0000000
--- a/stub-annotations/src/main/java/android/support/annotation/NewlyNullable.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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 android.support.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
-@Retention(CLASS)
-@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE})
-@Migrate
-public @interface NewlyNullable {
-}
diff --git a/stub-annotations/src/main/java/android/support/annotation/RecentlyNonNull.java b/stub-annotations/src/main/java/android/support/annotation/RecentlyNonNull.java
deleted file mode 100644
index d2309fc..0000000
--- a/stub-annotations/src/main/java/android/support/annotation/RecentlyNonNull.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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 android.support.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
-@Retention(CLASS)
-@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE})
-@Migrate
-public @interface RecentlyNonNull {
-}
diff --git a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/AnimRes.java
similarity index 70%
copy from stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
copy to stub-annotations/src/main/java/androidx/annotation/AnimRes.java
index 83f4e5a..952fcf4 100644
--- a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/AnimRes.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -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 NewlyNonNull {
-}
+@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
+public @interface AnimRes {}
diff --git a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/AnimatorRes.java
similarity index 70%
copy from stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
copy to stub-annotations/src/main/java/androidx/annotation/AnimatorRes.java
index 83f4e5a..6918e9a 100644
--- a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/AnimatorRes.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -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 NewlyNonNull {
-}
+@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
+public @interface AnimatorRes {}
diff --git a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/AnyRes.java
similarity index 70%
copy from stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
copy to stub-annotations/src/main/java/androidx/annotation/AnyRes.java
index 83f4e5a..84d6177 100644
--- a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/AnyRes.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -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 NewlyNonNull {
-}
+@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
+public @interface AnyRes {}
diff --git a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/AnyThread.java
similarity index 62%
copy from stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
copy to stub-annotations/src/main/java/androidx/annotation/AnyThread.java
index 83f4e5a..ab4eddc 100644
--- a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/AnyThread.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,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.CONSTRUCTOR;
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;
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, CONSTRUCTOR, TYPE, PARAMETER})
+public @interface AnyThread {}
diff --git a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/ArrayRes.java
similarity index 70%
copy from stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
copy to stub-annotations/src/main/java/androidx/annotation/ArrayRes.java
index 83f4e5a..90a4c29 100644
--- a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/ArrayRes.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -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 NewlyNonNull {
-}
+@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
+public @interface ArrayRes {}
diff --git a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/AttrRes.java
similarity index 70%
copy from stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
copy to stub-annotations/src/main/java/androidx/annotation/AttrRes.java
index 83f4e5a..296e9e9 100644
--- a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/AttrRes.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -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 NewlyNonNull {
-}
+@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
+public @interface AttrRes {}
diff --git a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/BinderThread.java
similarity index 62%
copy from stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
copy to stub-annotations/src/main/java/androidx/annotation/BinderThread.java
index 83f4e5a..246e092 100644
--- a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/BinderThread.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 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,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.CONSTRUCTOR;
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;
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, CONSTRUCTOR, TYPE, PARAMETER})
+public @interface BinderThread {}
diff --git a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/BoolRes.java
similarity index 70%
copy from stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
copy to stub-annotations/src/main/java/androidx/annotation/BoolRes.java
index 83f4e5a..ec44e6e 100644
--- a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/BoolRes.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -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 NewlyNonNull {
-}
+@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 72%
rename from stub-annotations/src/main/java/android/support/annotation/Migrate.java
rename to stub-annotations/src/main/java/androidx/annotation/CallSuper.java
index 233ce52..d099272 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 0000000..f96c750
--- /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/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/ColorInt.java
similarity index 70%
copy from stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
copy to stub-annotations/src/main/java/androidx/annotation/ColorInt.java
index 83f4e5a..f6972dd 100644
--- a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/ColorInt.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 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,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 NewlyNonNull {
-}
+@Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD})
+public @interface ColorInt {}
diff --git a/stub-annotations/src/main/java/android/support/annotation/RecentlyNullable.java b/stub-annotations/src/main/java/androidx/annotation/ColorLong.java
similarity index 74%
copy from stub-annotations/src/main/java/android/support/annotation/RecentlyNullable.java
copy to stub-annotations/src/main/java/androidx/annotation/ColorLong.java
index d24bad0..46937da 100644
--- a/stub-annotations/src/main/java/android/support/annotation/RecentlyNullable.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 RecentlyNullable {
-}
+@Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD})
+public @interface ColorLong {}
diff --git a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/ColorRes.java
similarity index 70%
copy from stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
copy to stub-annotations/src/main/java/androidx/annotation/ColorRes.java
index 83f4e5a..558f4ef 100644
--- a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/ColorRes.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -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 NewlyNonNull {
-}
+@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
+public @interface ColorRes {}
diff --git a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/DimenRes.java
similarity index 70%
copy from stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
copy to stub-annotations/src/main/java/androidx/annotation/DimenRes.java
index 83f4e5a..95f9a7d 100644
--- a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/DimenRes.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -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 NewlyNonNull {
-}
+@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 83f4e5a..64defe8 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/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/DrawableRes.java
similarity index 70%
copy from stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
copy to stub-annotations/src/main/java/androidx/annotation/DrawableRes.java
index 83f4e5a..92ac640 100644
--- a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/DrawableRes.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -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 NewlyNonNull {
-}
+@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
+public @interface DrawableRes {}
diff --git a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/FloatRange.java
similarity index 61%
copy from stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
copy to stub-annotations/src/main/java/androidx/annotation/FloatRange.java
index 83f4e5a..7cb2856 100644
--- a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/FloatRange.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 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,30 @@
* 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 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/RecentlyNullable.java b/stub-annotations/src/main/java/androidx/annotation/FontRes.java
similarity index 74%
rename from stub-annotations/src/main/java/android/support/annotation/RecentlyNullable.java
rename to stub-annotations/src/main/java/androidx/annotation/FontRes.java
index d24bad0..0ebca72 100644
--- a/stub-annotations/src/main/java/android/support/annotation/RecentlyNullable.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 RecentlyNullable {
-}
+@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
+public @interface FontRes {}
diff --git a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/FractionRes.java
similarity index 70%
copy from stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
copy to stub-annotations/src/main/java/androidx/annotation/FractionRes.java
index 83f4e5a..98ec534 100644
--- a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/FractionRes.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -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 NewlyNonNull {
-}
+@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
+public @interface FractionRes {}
diff --git a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/HalfFloat.java
similarity index 70%
copy from stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
copy to stub-annotations/src/main/java/androidx/annotation/HalfFloat.java
index 83f4e5a..a239a82 100644
--- a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/HalfFloat.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,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 NewlyNonNull {
-}
+@Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD})
+public @interface HalfFloat {}
diff --git a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/IdRes.java
similarity index 70%
copy from stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
copy to stub-annotations/src/main/java/androidx/annotation/IdRes.java
index 83f4e5a..0ba2072 100644
--- a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/IdRes.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -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 NewlyNonNull {
-}
+@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
+public @interface IdRes {}
diff --git a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/IntRange.java
similarity index 76%
copy from stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
copy to stub-annotations/src/main/java/androidx/annotation/IntRange.java
index 83f4e5a..e37b5cc 100644
--- a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/IntRange.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 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 NewlyNonNull {
+@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/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/IntegerRes.java
similarity index 70%
copy from stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
copy to stub-annotations/src/main/java/androidx/annotation/IntegerRes.java
index 83f4e5a..7ab05ea 100644
--- a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/IntegerRes.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -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 NewlyNonNull {
-}
+@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
+public @interface IntegerRes {}
diff --git a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/InterpolatorRes.java
similarity index 70%
copy from stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
copy to stub-annotations/src/main/java/androidx/annotation/InterpolatorRes.java
index 83f4e5a..f8862f9 100644
--- a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/InterpolatorRes.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -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 NewlyNonNull {
-}
+@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
+public @interface InterpolatorRes {}
diff --git a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/LayoutRes.java
similarity index 70%
copy from stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
copy to stub-annotations/src/main/java/androidx/annotation/LayoutRes.java
index 83f4e5a..a3ed0c8 100644
--- a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/LayoutRes.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -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 NewlyNonNull {
-}
+@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
+public @interface LayoutRes {}
diff --git a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/MainThread.java
similarity index 62%
copy from stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
copy to stub-annotations/src/main/java/androidx/annotation/MainThread.java
index 83f4e5a..d8a5e60 100644
--- a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/MainThread.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 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,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.CONSTRUCTOR;
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;
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, CONSTRUCTOR, TYPE, PARAMETER})
+public @interface MainThread {}
diff --git a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/MenuRes.java
similarity index 70%
copy from stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
copy to stub-annotations/src/main/java/androidx/annotation/MenuRes.java
index 83f4e5a..b7914f2 100644
--- a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/MenuRes.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -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 NewlyNonNull {
-}
+@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
+public @interface MenuRes {}
diff --git a/stub-annotations/src/main/java/android/support/annotation/RecentlyNullable.java b/stub-annotations/src/main/java/androidx/annotation/NavigationRes.java
similarity index 74%
copy from stub-annotations/src/main/java/android/support/annotation/RecentlyNullable.java
copy to stub-annotations/src/main/java/androidx/annotation/NavigationRes.java
index d24bad0..b964de0 100644
--- a/stub-annotations/src/main/java/android/support/annotation/RecentlyNullable.java
+++ b/stub-annotations/src/main/java/androidx/annotation/NavigationRes.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 RecentlyNullable {
-}
+@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
+public @interface NavigationRes {}
diff --git a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/NonNull.java
similarity index 74%
copy from stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
copy to stub-annotations/src/main/java/androidx/annotation/NonNull.java
index 83f4e5a..3b41cc8 100644
--- a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/NonNull.java
@@ -13,23 +13,20 @@
* 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.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.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, PACKAGE, TYPE_PARAMETER, TYPE_USE})
+public @interface NonNull {}
diff --git a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/Nullable.java
similarity index 74%
copy from stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
copy to stub-annotations/src/main/java/androidx/annotation/Nullable.java
index 83f4e5a..0e6abce 100644
--- a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/Nullable.java
@@ -13,23 +13,20 @@
* 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.ElementType.TYPE_PARAMETER;
+import static java.lang.annotation.ElementType.TYPE_USE;
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, PACKAGE, TYPE_PARAMETER, TYPE_USE})
+public @interface Nullable {}
diff --git a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/PluralsRes.java
similarity index 70%
copy from stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
copy to stub-annotations/src/main/java/androidx/annotation/PluralsRes.java
index 83f4e5a..f2741cb 100644
--- a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/PluralsRes.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -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 NewlyNonNull {
-}
+@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
+public @interface PluralsRes {}
diff --git a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/Px.java
similarity index 70%
copy from stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
copy to stub-annotations/src/main/java/androidx/annotation/Px.java
index 83f4e5a..5f8f2ff 100644
--- a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/Px.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,19 @@
* 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})
+@Dimension(unit = Dimension.PX)
+public @interface Px {}
diff --git a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/RawRes.java
similarity index 70%
copy from stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
copy to stub-annotations/src/main/java/androidx/annotation/RawRes.java
index 83f4e5a..af874df 100644
--- a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/RawRes.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -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 NewlyNonNull {
-}
+@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
+public @interface RawRes {}
diff --git a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/RecentlyNonNull.java
similarity index 66%
copy from stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
copy to stub-annotations/src/main/java/androidx/annotation/RecentlyNonNull.java
index 83f4e5a..575b3eb 100644
--- a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/RecentlyNonNull.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -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.ElementType.TYPE_USE;
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, TYPE_USE})
+public @interface RecentlyNonNull {}
diff --git a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/RecentlyNullable.java
similarity index 66%
copy from stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
copy to stub-annotations/src/main/java/androidx/annotation/RecentlyNullable.java
index 83f4e5a..8a9141e 100644
--- a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/RecentlyNullable.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -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.ElementType.TYPE_USE;
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, 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 0000000..f8824b3
--- /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 0000000..8ca1352
--- /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 0000000..74f37e3
--- /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 0000000..51ebdbd
--- /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 0000000..412a9ca
--- /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/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/StringRes.java
similarity index 70%
copy from stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
copy to stub-annotations/src/main/java/androidx/annotation/StringRes.java
index 83f4e5a..ecb78b7 100644
--- a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/StringRes.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -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 NewlyNonNull {
-}
+@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
+public @interface StringRes {}
diff --git a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/StyleRes.java
similarity index 70%
copy from stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
copy to stub-annotations/src/main/java/androidx/annotation/StyleRes.java
index 83f4e5a..d257f99 100644
--- a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/StyleRes.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -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 NewlyNonNull {
-}
+@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
+public @interface StyleRes {}
diff --git a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/StyleableRes.java
similarity index 70%
copy from stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
copy to stub-annotations/src/main/java/androidx/annotation/StyleableRes.java
index 83f4e5a..4d780c0 100644
--- a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/StyleableRes.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -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 NewlyNonNull {
-}
+@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
+public @interface StyleableRes {}
diff --git a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/TransitionRes.java
similarity index 66%
copy from stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
copy to stub-annotations/src/main/java/androidx/annotation/TransitionRes.java
index 83f4e5a..f1c0e34 100644
--- a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/TransitionRes.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 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,17 @@
* 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})
+public @interface TransitionRes {}
diff --git a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/UiThread.java
similarity index 62%
copy from stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
copy to stub-annotations/src/main/java/androidx/annotation/UiThread.java
index 83f4e5a..bdcf721 100644
--- a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/UiThread.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 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,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.CONSTRUCTOR;
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;
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, CONSTRUCTOR, TYPE, PARAMETER})
+public @interface UiThread {}
diff --git a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/WorkerThread.java
similarity index 62%
copy from stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
copy to stub-annotations/src/main/java/androidx/annotation/WorkerThread.java
index 83f4e5a..dbb11a5 100644
--- a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/WorkerThread.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 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,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.CONSTRUCTOR;
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;
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, CONSTRUCTOR, TYPE, PARAMETER})
+public @interface WorkerThread {}
diff --git a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/XmlRes.java
similarity index 70%
copy from stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
copy to stub-annotations/src/main/java/androidx/annotation/XmlRes.java
index 83f4e5a..cb38365 100644
--- a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/XmlRes.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -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 NewlyNonNull {
-}
+@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
+public @interface XmlRes {}