Merge pi-qpr1-release PQ1A.181105.017.A1 to pi-platform-release
Change-Id: Idaf2f036566669baa1500525d3beae2efdacd531
diff --git a/manual/master.txt b/manual/master.txt
new file mode 100644
index 0000000..b239c51
--- /dev/null
+++ b/manual/master.txt
@@ -0,0 +1,142 @@
+// APIs annotated in master that haven't been annotated in pi-dev
+package android.content.pm {
+ public abstract class PackageManager {
+ method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setApplicationEnabledSetting(String, int, int);
+ method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setComponentEnabledSetting(android.content.ComponentName, int, int);
+ }
+}
+package android.graphics {
+ public class Path {
+ method @NonNull public android.graphics.Path.FillType getFillType();
+ }
+ public final class Rect implements android.os.Parcelable {
+ method @NonNull public String flattenToString();
+ method @NonNull public String toShortString();
+ method @Nullable public static android.graphics.Rect unflattenFromString(@Nullable String);
+ }
+ public class RectF implements android.os.Parcelable {
+ method @NonNull public String toShortString();
+ }
+}
+package android.os {
+ public class DropBoxManager {
+ method @RequiresPermission(allOf={android.Manifest.permission.READ_LOGS, android.Manifest.permission.PACKAGE_USAGE_STATS}) @Nullable public android.os.DropBoxManager.Entry getNextEntry(String, long);
+ }
+ public final class Parcel {
+ method @Nullable public android.os.IBinder[] createBinderArray();
+ method @Nullable public java.util.ArrayList<android.os.IBinder> createBinderArrayList();
+ method @Nullable public boolean[] createBooleanArray();
+ method @Nullable public byte[] createByteArray();
+ method @Nullable public char[] createCharArray();
+ method @Nullable public double[] createDoubleArray();
+ method @Nullable public float[] createFloatArray();
+ method @Nullable public int[] createIntArray();
+ method @Nullable public long[] createLongArray();
+ method @Nullable public String[] createStringArray();
+ method @Nullable public java.util.ArrayList<java.lang.String> createStringArrayList();
+ method @Nullable public <T> T[] createTypedArray(@NonNull android.os.Parcelable.Creator<T>);
+ method @Nullable public <T> java.util.ArrayList<T> createTypedArrayList(@NonNull android.os.Parcelable.Creator<T>);
+ method @NonNull public static android.os.Parcel obtain();
+ method @Nullable public Object[] readArray(@Nullable ClassLoader);
+ method @Nullable public java.util.ArrayList readArrayList(@Nullable ClassLoader);
+ method @Nullable public android.os.Bundle readBundle();
+ method @Nullable public android.os.Bundle readBundle(@Nullable ClassLoader);
+ method @Nullable public java.util.HashMap readHashMap(@Nullable ClassLoader);
+ method @Nullable public <T extends android.os.Parcelable> T readParcelable(@Nullable ClassLoader);
+ method @Nullable public android.os.Parcelable[] readParcelableArray(@Nullable ClassLoader);
+ method @Nullable public android.os.PersistableBundle readPersistableBundle();
+ method @Nullable public android.os.PersistableBundle readPersistableBundle(@Nullable ClassLoader);
+ method @Nullable public java.io.Serializable readSerializable();
+ method @NonNull public android.util.Size readSize();
+ method @NonNull public android.util.SizeF readSizeF();
+ method @Nullable public android.util.SparseArray readSparseArray(@Nullable ClassLoader);
+ method @Nullable public android.util.SparseBooleanArray readSparseBooleanArray();
+ method @Nullable public String readString();
+ method @Nullable public <T> T readTypedObject(@NonNull android.os.Parcelable.Creator<T>);
+ method @Nullable public Object readValue(@Nullable ClassLoader);
+ }
+}
+package android.util {
+ public final class Log {
+ method @NonNull public static String getStackTraceString(@Nullable Throwable);
+ }
+}
+package android.view {
+ public abstract class Window {
+ method @NonNull public abstract android.view.View getDecorView();
+ }
+}
+package java.lang {
+ public final class Character implements java.lang.Comparable<java.lang.Character> java.io.Serializable {
+ method @NonNull public static char @NonNull [] toChars(int);
+ }
+ public final class Class<T> implements java.lang.reflect.AnnotatedElement java.lang.reflect.GenericDeclaration java.io.Serializable java.lang.reflect.Type {
+ method @Nullable public T cast(@Nullable Object);
+ method @NonNull public static Class<?> forName(@NonNull String) throws java.lang.ClassNotFoundException;
+ method @NonNull public static Class<?> forName(@NonNull String, boolean, @Nullable ClassLoader) throws java.lang.ClassNotFoundException;
+ method @Nullable public <A extends java.lang.annotation.Annotation> A getAnnotation(@NonNull Class<A>);
+ method @NonNull public java.lang.annotation.Annotation @NonNull [] getAnnotations();
+ method @NonNull public <A extends java.lang.annotation.Annotation> A @NonNull [] getAnnotationsByType(@NonNull Class<A>);
+ method @Nullable public String getCanonicalName();
+ method @Nullable public ClassLoader getClassLoader();
+ method @NonNull public Class<?> @NonNull [] getClasses();
+ method @Nullable public Class<?> getComponentType();
+ method @NonNull public java.lang.reflect.Constructor<?> @NonNull [] getConstructors() throws java.lang.SecurityException;
+ method @Nullable public native <A extends java.lang.annotation.Annotation> A getDeclaredAnnotation(@NonNull Class<A>);
+ method @NonNull public native java.lang.annotation.Annotation @NonNull [] getDeclaredAnnotations();
+ method @NonNull public native Class<?> @NonNull [] getDeclaredClasses();
+ method @NonNull public java.lang.reflect.Constructor<?> @NonNull [] getDeclaredConstructors() throws java.lang.SecurityException;
+ method @NonNull public native java.lang.reflect.Field getDeclaredField(@NonNull String) throws java.lang.NoSuchFieldException;
+ method @NonNull public native java.lang.reflect.Field @NonNull [] getDeclaredFields();
+ method @NonNull public java.lang.reflect.Method @NonNull [] getDeclaredMethods() throws java.lang.SecurityException;
+ method @Nullable public native Class<?> getDeclaringClass();
+ method @Nullable public native Class<?> getEnclosingClass();
+ method @Nullable public java.lang.reflect.Constructor<?> getEnclosingConstructor();
+ method @Nullable public java.lang.reflect.Method getEnclosingMethod();
+ method @NonNull public T @Nullable [] getEnumConstants();
+ method @NonNull public java.lang.reflect.Field getField(@NonNull String) throws java.lang.NoSuchFieldException;
+ method @NonNull public java.lang.reflect.Field @NonNull [] getFields() throws java.lang.SecurityException;
+ method @NonNull public java.lang.reflect.Type @NonNull [] getGenericInterfaces();
+ method @Nullable public java.lang.reflect.Type getGenericSuperclass();
+ method @NonNull public Class<?> @NonNull [] getInterfaces();
+ method @NonNull public java.lang.reflect.Method @NonNull [] getMethods() throws java.lang.SecurityException;
+ method @NonNull public String getName();
+ method @Nullable public Package getPackage();
+ method @Nullable public java.security.ProtectionDomain getProtectionDomain();
+ method @Nullable public java.net.URL getResource(@NonNull String);
+ method @Nullable public java.io.InputStream getResourceAsStream(@NonNull String);
+ method @NonNull public Object @Nullable [] getSigners();
+ method @NonNull public String getSimpleName();
+ method @Nullable public Class<? super T> getSuperclass();
+ method @NonNull public synchronized java.lang.reflect.TypeVariable<java.lang.@NonNull Class<T>> @NonNull [] getTypeParameters();
+ method @NonNull public native T newInstance() throws java.lang.IllegalAccessException, java.lang.InstantiationException;
+ method @NonNull public String toGenericString();
+ }
+ public class Object {
+ method @NonNull public final Class<?> getClass();
+ method @NonNull public String toString();
+ }
+ public final class System {
+ method @NonNull public static java.util.Map<java.lang.String,java.lang.String> getenv();
+ }
+ public class ThreadLocal<T> {
+ method @NonNull public static <S> ThreadLocal<S> withInitial(@NonNull java.util.function.Supplier<? extends S>);
+ }
+}
+package java.util {
+ public class ArrayList<E> extends java.util.AbstractList<E> implements java.lang.Cloneable java.util.List<E> java.util.RandomAccess java.io.Serializable {
+ method @NonNull public Object clone();
+ }
+ public class HashMap<K, V> extends java.util.AbstractMap<K,V> implements java.lang.Cloneable java.util.Map<K,V> java.io.Serializable {
+ method @NonNull public Object clone();
+ method @NonNull public java.util.Set<java.util.Map.Entry<K,V>> entrySet();
+ }
+ public interface Map<K, V> {
+ method @Nullable public V get(@Nullable Object);
+ method @Nullable public default V getOrDefault(@Nullable Object, @Nullable V);
+ method @Nullable public V put(K, V);
+ method @Nullable public default V putIfAbsent(K, V);
+ method @Nullable public V remove(@Nullable Object);
+ method @Nullable public default V replace(K, V);
+ }
+}
diff --git a/src/main/java/com/android/tools/metalava/AnnotationsDiffer.kt b/src/main/java/com/android/tools/metalava/AnnotationsDiffer.kt
index 8147ef8..0bd7d00 100644
--- a/src/main/java/com/android/tools/metalava/AnnotationsDiffer.kt
+++ b/src/main/java/com/android/tools/metalava/AnnotationsDiffer.kt
@@ -19,6 +19,7 @@
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.doclava1.TextCodebase
import com.android.tools.metalava.model.Codebase
import com.android.tools.metalava.model.Item
import java.io.File
@@ -57,7 +58,7 @@
* file.
*/
class AnnotationsDiffer(
- superset: Codebase,
+ private val superset: Codebase,
private val codebase: Codebase
) {
private val relevant = HashSet<Item>(1000)
@@ -100,10 +101,17 @@
}
}
}
- CodebaseComparator().compare(visitor, superset, codebase, ApiPredicate(codebase))
+ val filter =
+ if (codebase.supportsDocumentation()) {
+ ApiPredicate(codebase)
+ } else {
+ Predicate<Item> { true }
+ }
+ CodebaseComparator().compare(visitor, superset, codebase, filter)
}
fun writeDiffSignature(apiFile: File) {
+ val codebase = superset
val apiFilter = FilterPredicate(ApiPredicate(codebase))
val apiReference = ApiPredicate(codebase, ignoreShown = true)
val apiEmit = apiFilter.and(predicate)
@@ -113,13 +121,13 @@
val stringWriter = StringWriter()
val writer = PrintWriter(stringWriter)
writer.use { printWriter ->
- val preFiltered = codebase.original != null
+ val preFiltered = codebase.original != null || codebase is TextCodebase
val apiWriter = SignatureWriter(printWriter, apiEmit, apiReference, preFiltered)
codebase.accept(apiWriter)
}
// Clean up blank lines
- var prev: Char = ' '
+ var prev = ' '
val cleanedUp = stringWriter.toString().filter {
if (it == '\n' && prev == '\n')
false
diff --git a/src/main/java/com/android/tools/metalava/AnnotationsMerger.kt b/src/main/java/com/android/tools/metalava/AnnotationsMerger.kt
index 11ff5d5..c32c7df 100644
--- a/src/main/java/com/android/tools/metalava/AnnotationsMerger.kt
+++ b/src/main/java/com/android/tools/metalava/AnnotationsMerger.kt
@@ -57,6 +57,7 @@
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.SUPPORT_TYPE_USE_ANNOTATIONS
import com.android.tools.metalava.model.psi.PsiAnnotationItem
import com.android.tools.metalava.model.visitors.ApiVisitor
import com.android.utils.XmlUtils
@@ -267,7 +268,7 @@
null
}
curr = parameterItem
- } else if (line.startsWith("type: ")) {
+ } else if (line.startsWith("type: ") && SUPPORT_TYPE_USE_ANNOTATIONS) {
val typeAnnotation = line.substring("type: ".length)
if (curr != null) {
mergeJaifAnnotation(path, curr, typeAnnotation)
@@ -277,7 +278,7 @@
if (methodItem != null) {
mergeJaifAnnotation(path, methodItem, annotation)
}
- } else if (line.startsWith("inner-type")) {
+ } else if (line.startsWith("inner-type") && SUPPORT_TYPE_USE_ANNOTATIONS) {
warning("$path: Skipping inner-type annotations for now ($line)")
} else if (line.startsWith("int ")) {
// warning("Skipping int attribute definitions for annotations now ($line)")
diff --git a/src/main/java/com/android/tools/metalava/ApiAnalyzer.kt b/src/main/java/com/android/tools/metalava/ApiAnalyzer.kt
index 6cf8127..25f1f75 100644
--- a/src/main/java/com/android/tools/metalava/ApiAnalyzer.kt
+++ b/src/main/java/com/android/tools/metalava/ApiAnalyzer.kt
@@ -803,6 +803,22 @@
// TODO: Check opposite (doc tag but no annotation)
// TODO: Other checks
}
+
+ override fun visitMethod(method: MethodItem) {
+ // Make sure we don't annotate findViewById & getSystemService as @Nullable.
+ // See for example 68914170.
+ val name = method.name()
+ if ((name == "findViewById" || name == "getSystemService") && method.parameters().size == 1 &&
+ method.modifiers.hasNullnessInfo()
+ ) {
+ // In master we generate a warning here; in this branch we won't go in
+ // and remove the assertions
+ val annotation = method.modifiers.annotations().find { it.isNullable() }
+ annotation?.let {
+ method.mutableModifiers().removeAnnotation(it)
+ }
+ }
+ }
})
}
diff --git a/src/main/java/com/android/tools/metalava/Compatibility.kt b/src/main/java/com/android/tools/metalava/Compatibility.kt
index 5c21e38..536f5f5 100644
--- a/src/main/java/com/android/tools/metalava/Compatibility.kt
+++ b/src/main/java/com/android/tools/metalava/Compatibility.kt
@@ -45,6 +45,10 @@
/** Add in explicit `valueOf` and `values` methods into annotation classes */
var defaultAnnotationMethods: Boolean = compat
+ /** Whether signature files should contain annotation default values (as is already
+ * done for field default values) */
+ var includeAnnotationDefaults: Boolean = !compat
+
/** In signature files, refer to enums as "class" instead of "enum" */
var classForEnums: Boolean = compat
diff --git a/src/main/java/com/android/tools/metalava/DocAnalyzer.kt b/src/main/java/com/android/tools/metalava/DocAnalyzer.kt
index 2c91eaa..f040027 100644
--- a/src/main/java/com/android/tools/metalava/DocAnalyzer.kt
+++ b/src/main/java/com/android/tools/metalava/DocAnalyzer.kt
@@ -1,6 +1,7 @@
package com.android.tools.metalava
import com.android.sdklib.SdkVersionInfo
+import com.android.sdklib.repository.AndroidSdkHandler
import com.android.tools.lint.LintCliClient
import com.android.tools.lint.checks.ApiLookup
import com.android.tools.lint.helpers.DefaultJavaEvaluator
@@ -326,7 +327,18 @@
val field = value.resolve()
if (field is FieldItem)
- sb.append("{@link ${field.containingClass().qualifiedName()}#${field.name()}}")
+ if (filterReference.test(field)) {
+ sb.append("{@link ${field.containingClass().qualifiedName()}#${field.name()}}")
+ } else {
+ // Typdef annotation references field which isn't part of the API: don't
+ // try to link to it.
+ reporter.report(
+ Errors.MISSING_TYPEDEF_CONSTANT, item,
+ "Typedef references constant which isn't part of the API, skipping in documentation: " +
+ "${field.containingClass().qualifiedName()}#${field.name()}"
+ )
+ sb.append(field.containingClass().qualifiedName() + "." + field.name())
+ }
else {
sb.append(value.toSource())
}
@@ -500,6 +512,10 @@
return super.findResource(relativePath)
}
+ override fun getSdk(): AndroidSdkHandler? {
+ return null
+ }
+
override fun getCacheDir(name: String?, create: Boolean): File? {
val dir = File(System.getProperty("java.io.tmpdir"))
if (create) {
diff --git a/src/main/java/com/android/tools/metalava/Driver.kt b/src/main/java/com/android/tools/metalava/Driver.kt
index c538126..ed27efb 100644
--- a/src/main/java/com/android/tools/metalava/Driver.kt
+++ b/src/main/java/com/android/tools/metalava/Driver.kt
@@ -144,6 +144,20 @@
private fun processFlags() {
val stopwatch = Stopwatch.createStarted()
+ // --copy-annotations?
+ val privateAnnotationsSource = options.privateAnnotationsSource
+ val privateAnnotationsTarget = options.privateAnnotationsTarget
+ if (privateAnnotationsSource != null && privateAnnotationsTarget != null) {
+ val rewrite = RewriteAnnotations()
+ // Support pointing to both stub-annotations and stub-annotations/src/main/java
+ val src = File(privateAnnotationsSource, "src${File.separator}main${File.separator}java")
+ val source = if (src.isDirectory) src else privateAnnotationsSource
+ rewrite.modifyAnnotationSources(source, privateAnnotationsTarget)
+ }
+
+ // --rewrite-annotations?
+ options.rewriteAnnotations?.let { RewriteAnnotations().rewriteAnnotations(it) }
+
val codebase =
if (options.sources.size == 1 && options.sources[0].path.endsWith(SdkConstants.DOT_TXT)) {
loadFromSignatureFiles(
@@ -154,8 +168,10 @@
loadFromJarFile(options.apiJar!!)
} else if (options.sources.size == 1 && options.sources[0].path.endsWith(SdkConstants.DOT_JAR)) {
loadFromJarFile(options.sources[0])
- } else {
+ } else if (options.sources.isNotEmpty() || options.sourcePath.isNotEmpty()) {
loadFromSources()
+ } else {
+ return
}
options.manifest?.let { codebase.manifest = it }
@@ -323,7 +339,18 @@
it, codebase, docStubs = false,
writeStubList = options.stubsSourceList != null
)
+
+ val stubAnnotations = options.copyStubAnnotationsFrom
+ if (stubAnnotations != null) {
+ // Support pointing to both stub-annotations and stub-annotations/src/main/java
+ val src = File(stubAnnotations, "src${File.separator}main${File.separator}java")
+ val source = if (src.isDirectory) src else stubAnnotations
+ source.listFiles()?.forEach { file ->
+ RewriteAnnotations().copyAnnotations(file, File(it, file.name))
+ }
+ }
}
+
if (options.docStubsDir == null && options.stubsDir == null) {
val writeStubsFile: (File) -> Unit = { file ->
val root = File("").absoluteFile
@@ -375,8 +402,8 @@
if (args.isNotEmpty()) {
if (!options.quiet) {
options.stdout.println(
- "Invoking external documentation tool ${args[0]} with arguments ${
- args.slice(1 until args.size).joinToString { it }}"
+ "Invoking external documentation tool ${args[0]} with arguments\n\"${
+ args.slice(1 until args.size).joinToString(separator = "\",\n\"") { it }}\""
)
options.stdout.flush()
}
diff --git a/src/main/java/com/android/tools/metalava/ExtractAnnotations.kt b/src/main/java/com/android/tools/metalava/ExtractAnnotations.kt
index dd3fb67..e065f58 100644
--- a/src/main/java/com/android/tools/metalava/ExtractAnnotations.kt
+++ b/src/main/java/com/android/tools/metalava/ExtractAnnotations.kt
@@ -30,6 +30,8 @@
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.canonicalizeFloatingPointString
+import com.android.tools.metalava.model.javaEscapeString
import com.android.tools.metalava.model.psi.PsiAnnotationItem
import com.android.tools.metalava.model.psi.PsiClassItem
import com.android.tools.metalava.model.visitors.ApiVisitor
@@ -37,10 +39,16 @@
import com.google.common.base.Charsets
import com.google.common.xml.XmlEscapers
import com.intellij.psi.PsiAnnotation
+import com.intellij.psi.PsiAnnotationMemberValue
+import com.intellij.psi.PsiArrayInitializerMemberValue
import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiClassObjectAccessExpression
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiField
+import com.intellij.psi.PsiLiteral
import com.intellij.psi.PsiNameValuePair
+import com.intellij.psi.PsiReference
+import com.intellij.psi.PsiTypeCastExpression
import org.jetbrains.uast.UAnnotation
import org.jetbrains.uast.UBinaryExpressionWithType
import org.jetbrains.uast.UCallExpression
@@ -101,25 +109,35 @@
val pairs = packageToAnnotationPairs[pkg] ?: continue
+ // Ensure stable output
+ if (pairs.size > 1) {
+ pairs.sortBy { it.first.getExternalAnnotationSignature() }
+ }
+
StringPrintWriter.create().use { writer ->
writer.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<root>")
+ var open = false
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
+ if (item != prev) {
+ if (open) {
+ writer.print(" </item>")
+ writer.println()
+ }
+ writer.print(" <item name=\"")
+ writer.print(item.getExternalAnnotationSignature())
+ writer.println("\">")
+ open = true
+ }
prev = item
- writer.print(" <item name=\"")
- writer.print(item.getExternalAnnotationSignature())
- writer.println("\">")
-
writeAnnotation(writer, item, annotation)
-
+ }
+ if (open) {
writer.print(" </item>")
writer.println()
}
-
writer.println("</root>\n")
writer.close()
val bytes = writer.contents.toByteArray(Charsets.UTF_8)
@@ -131,15 +149,7 @@
}
}
- /** 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
-
+ private fun addItem(item: Item, annotation: AnnotationHolder) {
val pkg = when (item) {
is MemberItem -> item.containingClass().containingPackage()
is ParameterItem -> item.containingMethod().containingClass().containingPackage()
@@ -152,7 +162,7 @@
packageToAnnotationPairs[pkg] = new
new
}
- list.add(Pair(item, typedef))
+ list.add(Pair(item, annotation))
}
override fun visitField(field: FieldItem) {
@@ -167,7 +177,8 @@
checkItem(parameter)
}
- private fun findTypeDef(item: Item): AnnotationHolder? {
+ /** For a given item, extract the relevant annotations for that item */
+ private fun checkItem(item: Item) {
for (annotation in item.modifiers.annotations()) {
val qualifiedName = annotation.qualifiedName() ?: continue
if (qualifiedName.startsWith(JAVA_LANG_PREFIX) ||
@@ -177,7 +188,11 @@
) {
if (annotation.isTypeDefAnnotation()) {
// Imported typedef
- return AnnotationHolder(null, annotation, null)
+ addItem(item, AnnotationHolder(null, annotation, null))
+ } else if (annotation.isSignificantInStubs() && !annotation.hasClassRetention() &&
+ !options.includeSourceRetentionAnnotations
+ ) {
+ addItem(item, AnnotationHolder(null, annotation, null))
}
continue
@@ -188,7 +203,8 @@
if (typeDefClass.isAnnotationType()) {
val cached = classToAnnotationHolder[className]
if (cached != null) {
- return cached
+ addItem(item, cached)
+ continue
}
val typeDefAnnotation = typeDefClass.modifiers.annotations().firstOrNull {
@@ -220,12 +236,12 @@
)
)
classToAnnotationHolder[className] = result
- return result
+ addItem(item, result)
+ continue
}
}
}
}
- return null
}
private fun hasSourceRetention(annotationClass: ClassItem): Boolean {
@@ -374,112 +390,115 @@
annotationHolder: AnnotationHolder
) {
val annotationItem = annotationHolder.annotationItem
+ val uAnnotation = annotationHolder.uAnnotation
+ ?: if (annotationItem is PsiAnnotationItem) {
+ // Imported annotation
+ JavaUAnnotation.wrap(annotationItem.psiAnnotation)
+ } else {
+ return
+ }
val qualifiedName = annotationItem.qualifiedName()
writer.mark()
writer.print(" <annotation name=\"")
writer.print(qualifiedName)
+ var attributes = uAnnotation.attributeValues
+ if (attributes.isEmpty()) {
+ writer.print("\"/>")
+ writer.println()
+ return
+ }
+
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)
- // 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
+ }
- // 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
+ }
- 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))
+ }
+ })
+ }
- 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
+ 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
- } 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("\" />")
+ 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
}
- if (empty) {
- // All items were filtered out: don't write the annotation at all
- writer.reset()
- return
+ // 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 && attributes.isNotEmpty()) {
+ // All items were filtered out: don't write the annotation at all
+ writer.reset()
+ return
}
writer.println(" </annotation>")
@@ -621,4 +640,133 @@
private fun error(string: String) {
reporter.report(Severity.WARNING, null as PsiElement?, string, Errors.ANNOTATION_EXTRACTION)
}
+
+ companion object {
+ /** Given an annotation member value, returns the corresponding Java source expression */
+ fun toSourceExpression(value: PsiAnnotationMemberValue, owner: Item): String {
+ val sb = StringBuilder()
+ appendSourceExpression(value, sb, owner)
+ return sb.toString()
+ }
+
+ private fun appendSourceExpression(value: PsiAnnotationMemberValue, sb: StringBuilder, owner: Item): Boolean {
+ if (value is PsiReference) {
+ val resolved = value.resolve()
+ if (resolved is PsiField) {
+ sb.append(resolved.containingClass?.qualifiedName).append('.').append(resolved.name)
+ return true
+ }
+ } else if (value is PsiLiteral) {
+ return appendSourceLiteral(value.value, sb, owner)
+ } else if (value is PsiClassObjectAccessExpression) {
+ sb.append(value.operand.type.canonicalText).append(".class")
+ return true
+ } else if (value is PsiArrayInitializerMemberValue) {
+ sb.append('{')
+ var first = true
+ val initialLength = sb.length
+ for (e in value.initializers) {
+ val length = sb.length
+ if (first) {
+ first = false
+ } else {
+ sb.append(", ")
+ }
+ val appended = appendSourceExpression(e, sb, owner)
+ 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 true
+ } else if (value is PsiAnnotation) {
+ sb.append('@').append(value.qualifiedName)
+ return true
+ } else {
+ if (value is PsiTypeCastExpression) {
+ val type = value.castType?.type
+ val operand = value.operand
+ if (type != null && operand is PsiAnnotationMemberValue) {
+ sb.append('(')
+ sb.append(type.canonicalText)
+ sb.append(')')
+ return appendSourceExpression(operand, sb, owner)
+ }
+ }
+ val constant = ConstantEvaluator.evaluate(null, value)
+ if (constant != null) {
+ return appendSourceLiteral(constant, sb, owner)
+ }
+ }
+ reporter.report(Errors.INTERNAL_ERROR, owner, "Unexpected annotation default value $value")
+ return false
+ }
+
+ private fun appendSourceLiteral(v: Any?, sb: StringBuilder, owner: Item): Boolean {
+ if (v == null) {
+ sb.append("null")
+ return true
+ }
+ when (v) {
+ is Int, is Long, is Boolean, is Byte, is Short -> {
+ sb.append(v.toString())
+ return true
+ }
+ is String -> {
+ sb.append('"').append(javaEscapeString(v)).append('"')
+ return true
+ }
+ is Float -> {
+ return when (v) {
+ Float.POSITIVE_INFINITY -> {
+ // This convention (displaying fractions) is inherited from doclava
+ sb.append("(1.0f/0.0f)"); true
+ }
+ Float.NEGATIVE_INFINITY -> {
+ sb.append("(-1.0f/0.0f)"); true
+ }
+ Float.NaN -> {
+ sb.append("(0.0f/0.0f)"); true
+ }
+ else -> {
+ sb.append(canonicalizeFloatingPointString(v.toString()) + "f")
+ true
+ }
+ }
+ }
+ is Double -> {
+ return when (v) {
+ Double.POSITIVE_INFINITY -> {
+ // This convention (displaying fractions) is inherited from doclava
+ sb.append("(1.0/0.0)"); true
+ }
+ Double.NEGATIVE_INFINITY -> {
+ sb.append("(-1.0/0.0)"); true
+ }
+ Double.NaN -> {
+ sb.append("(0.0/0.0)"); true
+ }
+ else -> {
+ sb.append(canonicalizeFloatingPointString(v.toString()))
+ true
+ }
+ }
+ }
+ is Char -> {
+ sb.append('\'').append(javaEscapeString(v.toString())).append('\'')
+ return true
+ }
+ else -> {
+ reporter.report(Errors.INTERNAL_ERROR, owner, "Unexpected literal value $v")
+ }
+ }
+
+ return false
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/metalava/Options.kt b/src/main/java/com/android/tools/metalava/Options.kt
index b2c1c68..5bd8415 100644
--- a/src/main/java/com/android/tools/metalava/Options.kt
+++ b/src/main/java/com/android/tools/metalava/Options.kt
@@ -107,6 +107,10 @@
private const val ARG_GENERATE_DOCUMENTATION = "--generate-documentation"
private const val ARG_JAVA_SOURCE = "--java-source"
private const val ARG_REGISTER_ARTIFACT = "--register-artifact"
+private const val ARG_COPY_ANNOTATIONS = "--copy-annotations"
+private const val ARG_INCLUDE_ANNOTATION_CLASSES = "--include-annotation-classes"
+private const val ARG_REWRITE_ANNOTATIONS = "--rewrite-annotations"
+private const val ARG_INCLUDE_SOURCE_RETENTION = "--include-source-retention"
class Options(
args: Array<String>,
@@ -258,6 +262,28 @@
/** If set, a file to write extracted annotations to. Corresponds to the --extract-annotations flag. */
var externalAnnotations: File? = null
+ /** For [ARG_COPY_ANNOTATIONS], the source directory to read stub annotations from */
+ var privateAnnotationsSource: File? = null
+
+ /** For [ARG_COPY_ANNOTATIONS], the target directory to write converted stub annotations from */
+ var privateAnnotationsTarget: File? = null
+
+ /**
+ * For [ARG_INCLUDE_ANNOTATION_CLASSES], the directory to copy stub annotation source files into the
+ * stubs folder from
+ */
+ var copyStubAnnotationsFrom: File? = null
+
+ /**
+ * For [ARG_INCLUDE_SOURCE_RETENTION], true if we want to include source-retention annotations
+ * both in the set of files emitted by [ARG_INCLUDE_ANNOTATION_CLASSES] and into the stubs
+ * themselves
+ */
+ var includeSourceRetentionAnnotations = false
+
+ /** For [ARG_REWRITE_ANNOTATIONS], the jar or bytecode folder to rewrite annotations in */
+ var rewriteAnnotations: List<File>? = null
+
/** A manifest file to read to for example look up available permissions */
var manifest: File? = null
@@ -523,6 +549,13 @@
ARG_INPUT_API_JAR -> apiJar = stringToExistingFile(getValue(args, ++index))
ARG_EXTRACT_ANNOTATIONS -> externalAnnotations = stringToNewFile(getValue(args, ++index))
+ ARG_COPY_ANNOTATIONS -> {
+ privateAnnotationsSource = stringToExistingDir(getValue(args, ++index))
+ privateAnnotationsTarget = stringToNewDir(getValue(args, ++index))
+ }
+ ARG_REWRITE_ANNOTATIONS -> rewriteAnnotations = stringToExistingDirsOrJars(getValue(args, ++index))
+ ARG_INCLUDE_ANNOTATION_CLASSES -> copyStubAnnotationsFrom = stringToExistingDir(getValue(args, ++index))
+ ARG_INCLUDE_SOURCE_RETENTION -> includeSourceRetentionAnnotations = true
ARG_PREVIOUS_API -> previousApi = stringToExistingFile(getValue(args, ++index))
ARG_CURRENT_API -> currentApi = stringToExistingFile(getValue(args, ++index))
@@ -556,12 +589,12 @@
"-werror" -> {
// Temporarily disabled; this is used in various builds but is pretty much
// never what we want.
- //warningsAreErrors = true
+ // warningsAreErrors = true
}
"-lerror" -> {
// Temporarily disabled; this is used in various builds but is pretty much
// never what we want.
- //lintsAreErrors = true
+ // lintsAreErrors = true
}
ARG_CHECK_KOTLIN_INTEROP -> checkKotlinInterop = true
@@ -620,19 +653,33 @@
ARG_GENERATE_DOCUMENTATION -> {
// Digest all the remaining arguments.
// Allow "STUBS_DIR" to reference the stubs directory.
+ var prev = ""
invokeDocumentationToolArguments = args.slice(++index until args.size).mapNotNull {
- if (it == "STUBS_DIR" && docStubsDir != null) {
- docStubsDir?.path
- } else if (it == "STUBS_DIR" && stubsDir != null) {
+ var argument = it
+ // When generating documentation, use the doc stubs directory rather than the
+ // original source path
+ val docStubsDir = docStubsDir
+ if (docStubsDir != null && (prev == ARG_SOURCE_PATH || prev == "-sourcepath") &&
+ !argument.contains(docStubsDir.path)) {
+ // Insert the doc stubs as the default place to look for sources
+ argument = docStubsDir.path // + File.pathSeparatorChar + argument
+ }
+ prev = it
+
+ if (argument == "STUBS_DIR" && docStubsDir != null) {
+ docStubsDir.path
+ } else if (argument == "STUBS_DIR" && stubsDir != null) {
stubsDir?.path
- } else if (it == "DOC_STUBS_SOURCE_LIST" && docStubsSourceList != null) {
+ } else if (argument == "DOCS_STUBS_DIR" && docStubsDir != null) {
+ docStubsDir.path
+ } else if (argument == "DOC_STUBS_SOURCE_LIST" && docStubsSourceList != null) {
"@${docStubsSourceList?.path}"
- } else if (it == "STUBS_SOURCE_LIST" && stubsSourceList != null) {
+ } else if (argument == "STUBS_SOURCE_LIST" && stubsSourceList != null) {
"@${stubsSourceList?.path}"
- } else if (it == "STUBS_SOURCE_LIST" && docStubsSourceList != null) {
+ } else if (argument == "STUBS_SOURCE_LIST" && docStubsSourceList != null) {
"@${docStubsSourceList?.path}"
} else {
- it
+ argument
}
}.toTypedArray()
@@ -1271,7 +1318,17 @@
"", "\nExtracting Annotations:",
"$ARG_EXTRACT_ANNOTATIONS <zipfile>", "Extracts source annotations from the source files and writes " +
"them into the given zip file",
+ "$ARG_INCLUDE_ANNOTATION_CLASSES <dir>", "Copies the given stub annotation source files into the " +
+ "generated stub sources; <dir> is typically $PROGRAM_NAME/stub-annotations/src/main/java/.",
+// Soon to be removed; not documented:
+// "$ARG_COPY_ANNOTATIONS <source> <dest>", "For a source folder full of annotation " +
+// "sources, generates corresponding package private versions of the same annotations.",
+ "$ARG_REWRITE_ANNOTATIONS <dir/jar>", "For a bytecode folder or output jar, rewrites the " +
+ "androidx annotations to be package private",
+ ARG_INCLUDE_SOURCE_RETENTION, "If true, include source-retention annotations in the stub files. Does " +
+ "not apply to signature files. Source retention annotations are extracted into the external " +
+ "annotations files instead.",
"", "\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",
diff --git a/src/main/java/com/android/tools/metalava/RewriteAnnotations.kt b/src/main/java/com/android/tools/metalava/RewriteAnnotations.kt
new file mode 100644
index 0000000..78117c1
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/RewriteAnnotations.kt
@@ -0,0 +1,230 @@
+/*
+ * 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.SdkConstants
+import com.android.SdkConstants.DOT_CLASS
+import com.android.SdkConstants.DOT_JAR
+import com.android.tools.metalava.model.AnnotationItem
+import com.google.common.io.Closer
+import org.objectweb.asm.ClassReader
+import org.objectweb.asm.ClassVisitor
+import org.objectweb.asm.ClassWriter
+import org.objectweb.asm.Opcodes
+import org.objectweb.asm.Opcodes.ASM6
+import java.io.BufferedInputStream
+import java.io.BufferedOutputStream
+import java.io.File
+import java.io.FileInputStream
+import java.io.FileOutputStream
+import java.io.IOException
+import java.nio.file.attribute.FileTime
+import java.util.jar.JarEntry
+import java.util.zip.ZipInputStream
+import java.util.zip.ZipOutputStream
+
+/**
+ * Converts public stub annotation sources into package private annotation sources.
+ * This is needed for the stub sources, where we want to reference annotations that aren't
+ * public, but (a) they need to be public during compilation, and (b) they need to be
+ * package private when compiled and packaged on their own such that annotation processors
+ * can find them. See b/110532131 for details.
+ */
+
+class RewriteAnnotations {
+ /** Copies annotation source files from [source] to [target] */
+ fun copyAnnotations(source: File, target: File, pkg: String = "") {
+ val fileName = source.name
+ if (fileName.endsWith(SdkConstants.DOT_JAVA)) {
+ if (!options.includeSourceRetentionAnnotations) {
+ // Only copy non-source retention annotation classes
+ val qualifiedName = pkg + "." + fileName.substring(0, fileName.indexOf('.'))
+ if (!AnnotationItem.hasClassRetention(qualifiedName)) {
+ return
+ }
+ }
+
+ // Copy and convert
+ target.parentFile.mkdirs()
+ source.copyTo(target)
+ } else if (source.isDirectory) {
+ val newPackage = if (pkg.isEmpty()) fileName else "$pkg.$fileName"
+ source.listFiles()?.forEach {
+ copyAnnotations(it, File(target, it.name), newPackage)
+ }
+ }
+ }
+
+ /** Modifies annotation source files such that they are package private */
+ fun modifyAnnotationSources(source: File, target: File) {
+ if (source.name.endsWith(SdkConstants.DOT_JAVA)) {
+ // Copy and convert
+ target.parentFile.mkdirs()
+ target.writeText(
+ source.readText(Charsets.UTF_8).replace(
+ "\npublic @interface",
+ "\n@interface"
+ )
+ )
+ } else if (source.isDirectory) {
+ source.listFiles()?.forEach {
+ modifyAnnotationSources(it, File(target, it.name))
+ }
+ }
+ }
+
+ /** Writes the bytecode for the compiled annotations in the given file list such that they are package private */
+ fun rewriteAnnotations(files: List<File>) {
+ for (file in files) {
+ // Jump directly into androidx/annotation if it appears we were invoked at the top level
+ if (file.isDirectory) {
+ val annotations = File(file, "androidx${File.separator}annotation/")
+ if (annotations.isDirectory) {
+ rewriteAnnotations(annotations)
+ continue
+ }
+ }
+
+ rewriteAnnotations(file)
+ }
+ }
+
+ /** Writes the bytecode for the compiled annotations in the given file such that they are package private */
+ private fun rewriteAnnotations(file: File) {
+ when {
+ file.isDirectory -> file.listFiles()?.forEach { rewriteAnnotations(it) }
+ file.path.endsWith(DOT_CLASS) -> rewriteClassFile(file)
+ file.path.endsWith(DOT_JAR) -> rewriteJar(file)
+ }
+ }
+
+ private fun rewriteClassFile(file: File) {
+ if (file.name.contains("$")) {
+ return // Not worrying about inner classes
+ }
+ val bytes = file.readBytes()
+ val rewritten = rewriteClass(bytes, file.path) ?: return
+ file.writeBytes(rewritten)
+ }
+
+ private fun rewriteClass(bytes: ByteArray, path: String): ByteArray? {
+ return try {
+ val reader = ClassReader(bytes)
+ rewriteOuterClass(reader)
+ } catch (ioe: IOException) {
+ error("Could not process " + path + ": " + ioe.localizedMessage)
+ }
+ }
+
+ private fun rewriteOuterClass(reader: ClassReader): ByteArray? {
+ val classWriter = ClassWriter(ASM6)
+ var skip = true
+ val classVisitor = object : ClassVisitor(ASM6, classWriter) {
+ override fun visit(
+ version: Int,
+ access: Int,
+ name: String,
+ signature: String?,
+ superName: String?,
+ interfaces: Array<out String>?
+ ) {
+ // Only process public annotations in androidx.annotation
+ if (access and Opcodes.ACC_PUBLIC != 0 &&
+ access and Opcodes.ACC_ANNOTATION != 0 &&
+ name.startsWith("androidx/annotation/")
+ ) {
+ skip = false
+ val flagsWithoutPublic = access and Opcodes.ACC_PUBLIC.inv()
+ super.visit(version, flagsWithoutPublic, name, signature, superName, interfaces)
+ }
+ }
+ }
+
+ reader.accept(classVisitor, 0)
+ return if (skip) {
+ null
+ } else {
+ classWriter.toByteArray()
+ }
+ }
+
+ private fun rewriteJar(file: File) {
+ val temp = File(file.name + ".temp-$PROGRAM_NAME")
+ rewriteJar(file, temp)
+ file.delete()
+ temp.renameTo(file)
+ }
+
+ private val zeroTime = FileTime.fromMillis(0)
+
+ private fun rewriteJar(from: File, to: File/*, filter: Predicate<String>?*/) {
+ Closer.create().use { closer ->
+ val fos = closer.register(FileOutputStream(to))
+ val bos = closer.register(BufferedOutputStream(fos))
+ val zos = closer.register(ZipOutputStream(bos))
+
+ val fis = closer.register(FileInputStream(from))
+ val bis = closer.register(BufferedInputStream(fis))
+ val zis = closer.register(ZipInputStream(bis))
+
+ while (true) {
+ val entry = zis.nextEntry ?: break
+ val name = entry.name
+ val newEntry: JarEntry
+
+ // Preserve the STORED method of the input entry.
+ newEntry = if (entry.method == JarEntry.STORED) {
+ val jarEntry = JarEntry(entry)
+ jarEntry.size = entry.size
+ jarEntry.compressedSize = entry.compressedSize
+ jarEntry.crc = entry.crc
+ jarEntry
+ } else {
+ // Create a new entry so that the compressed len is recomputed.
+ JarEntry(name)
+ }
+
+ newEntry.lastAccessTime = zeroTime
+ newEntry.creationTime = zeroTime
+ newEntry.lastModifiedTime = entry.lastModifiedTime
+
+ // add the entry to the jar archive
+ zos.putNextEntry(newEntry)
+
+ // read the content of the entry from the input stream, and write it into the archive.
+ if (name.endsWith(DOT_CLASS) &&
+ name.startsWith("androidx/annotation/") &&
+ name.indexOf("$") == -1 &&
+ !entry.isDirectory
+ ) {
+ val bytes = zis.readBytes(entry.size.toInt())
+ val rewritten = rewriteClass(bytes, name)
+ if (rewritten != null) {
+ zos.write(rewritten)
+ } else {
+ zos.write(bytes)
+ }
+ } else {
+ zis.copyTo(zos)
+ }
+
+ zos.closeEntry()
+ zis.closeEntry()
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/metalava/SignatureWriter.kt b/src/main/java/com/android/tools/metalava/SignatureWriter.kt
index 7fca3d8..5d7be95 100644
--- a/src/main/java/com/android/tools/metalava/SignatureWriter.kt
+++ b/src/main/java/com/android/tools/metalava/SignatureWriter.kt
@@ -98,6 +98,17 @@
writer.print(method.name())
writeParameterList(method)
writeThrowsList(method)
+
+ if (compatibility.includeAnnotationDefaults) {
+ if (method.containingClass().isAnnotationType()) {
+ val default = method.defaultValue()
+ if (default.isNotEmpty()) {
+ writer.print(" default ")
+ writer.print(default)
+ }
+ }
+ }
+
writer.print(";\n")
}
@@ -150,7 +161,9 @@
includeAnnotations = compatibility.annotationsInSignatures,
skipNullnessAnnotations = options.outputKotlinStyleNulls,
omitCommonPackages = options.omitCommonPackages,
- onlyIncludeSignatureAnnotations = true
+ onlyIncludeSignatureAnnotations = true,
+ onlyIncludeStubAnnotations = false,
+ onlyIncludeClassRetentionAnnotations = false
)
}
@@ -199,9 +212,20 @@
}
if (all.any()) {
- val label = if (isInterface && !compatibility.extendsForInterfaceSuperClass) " extends" else " implements"
+ val label =
+ if (isInterface && !compatibility.extendsForInterfaceSuperClass) {
+ val superInterface = cls.filteredSuperclass(filterReference)
+ if (superInterface != null && !superInterface.isJavaLangObject()) {
+ // For interfaces we've already listed "extends <super interface>"; we don't
+ // want to repeat "extends " here
+ ""
+ } else {
+ " extends"
+ }
+ } else {
+ " implements"
+ }
writer.print(label)
-
all.sortedWith(TypeItem.comparator).forEach { item ->
writer.print(" ")
writer.print(item.toTypeString(erased = compatibility.omitTypeParametersInInterfaces))
diff --git a/src/main/java/com/android/tools/metalava/StubWriter.kt b/src/main/java/com/android/tools/metalava/StubWriter.kt
index 40a6a5a..de41742 100644
--- a/src/main/java/com/android/tools/metalava/StubWriter.kt
+++ b/src/main/java/com/android/tools/metalava/StubWriter.kt
@@ -140,7 +140,9 @@
// Some bug in UAST triggers duplicate nullability annotations
// here; make sure the are filtered out
filterDuplicates = true,
- onlyIncludeSignatureAnnotations = true,
+ onlyIncludeSignatureAnnotations = false,
+ onlyIncludeStubAnnotations = true,
+ onlyIncludeClassRetentionAnnotations = true,
writer = writer
)
writer.println("package ${pkg.qualifiedName()};")
@@ -302,7 +304,9 @@
ModifierList.write(
writer, modifiers, item, removeAbstract = removeAbstract, removeFinal = removeFinal,
addPublic = addPublic, includeAnnotations = generateAnnotations,
- onlyIncludeSignatureAnnotations = true
+ onlyIncludeSignatureAnnotations = false,
+ onlyIncludeStubAnnotations = true,
+ onlyIncludeClassRetentionAnnotations = true
)
}
@@ -499,6 +503,14 @@
generateParameterList(method)
generateThrowsList(method)
+ if (isAnnotation) {
+ val default = method.defaultValue()
+ if (default.isNotEmpty()) {
+ writer.print(" default ")
+ writer.print(default)
+ }
+ }
+
if (modifiers.isAbstract() && !removeAbstract && !isEnum || isAnnotation || modifiers.isNative()) {
writer.println(";")
} else {
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 fca21ff..5456d75 100644
--- a/src/main/java/com/android/tools/metalava/doclava1/ApiFile.java
+++ b/src/main/java/com/android/tools/metalava/doclava1/ApiFile.java
@@ -31,6 +31,7 @@
import com.google.common.base.Charsets;
import com.google.common.io.Files;
import kotlin.Pair;
+import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
@@ -80,7 +81,7 @@
if ("package".equals(token)) {
parsePackage(api, tokenizer);
} else {
- throw new ApiParseException("expected package got " + token, tokenizer.getLine());
+ throw new ApiParseException("expected package got " + token, tokenizer);
}
}
@@ -101,7 +102,7 @@
pkg = new TextPackageItem(api, name, tokenizer.pos());
token = tokenizer.requireToken();
if (!"{".equals(token)) {
- throw new ApiParseException("expected '{' got " + token, tokenizer.getLine());
+ throw new ApiParseException("expected '{' got " + token, tokenizer);
}
while (true) {
token = tokenizer.requireToken();
@@ -201,7 +202,7 @@
ext = JAVA_LANG_ENUM;
token = tokenizer.requireToken();
} else {
- throw new ApiParseException("missing class or interface. got: " + token, tokenizer.getLine());
+ throw new ApiParseException("missing class or interface. got: " + token, tokenizer);
}
assertIdent(tokenizer, token);
name = token;
@@ -232,7 +233,11 @@
}
// Resolve superclass after done parsing
api.mapClassToSuper(cl, ext);
- if ("implements".equals(token)) {
+ if ("implements".equals(token) || "extends".equals(token) ||
+ isInterface && ext != null && !token.equals("{")) {
+ if (!token.equals("implements") && !token.equals("extends")) {
+ api.mapClassToInterface(cl, token);
+ }
while (true) {
token = tokenizer.requireToken();
if ("{".equals(token)) {
@@ -253,7 +258,7 @@
cl.setIsAnnotationType(true);
}
if (!"{".equals(token)) {
- throw new ApiParseException("expected {", tokenizer.getLine());
+ throw new ApiParseException("expected {, was " + token, tokenizer);
}
token = tokenizer.requireToken();
while (true) {
@@ -272,31 +277,31 @@
token = tokenizer.requireToken();
parseField(api, tokenizer, cl, token, true);
} else {
- throw new ApiParseException("expected ctor, enum_constant, field or method", tokenizer.getLine());
+ throw new ApiParseException("expected ctor, enum_constant, field or method", tokenizer);
}
token = tokenizer.requireToken();
}
pkg.addClass(cl);
}
- private static Pair<String, List<String>> processKotlinTypeSuffix(TextCodebase api, String token, List<String> annotations) throws ApiParseException {
+ private static Pair<String, List<String>> processKotlinTypeSuffix(TextCodebase api, String type, List<String> annotations) throws ApiParseException {
if (api.getKotlinStyleNulls()) {
- if (token.endsWith("?")) {
- token = token.substring(0, token.length() - 1);
+ if (type.endsWith("?")) {
+ type = type.substring(0, type.length() - 1);
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
+ } else if (type.endsWith("!")) {
+ type = type.substring(0, type.length() - 1);
+ } else if (!type.endsWith("!")) {
+ if (!TextTypeItem.Companion.isPrimitive(type)) { // Don't add nullness on primitive types like void
annotations = mergeAnnotations(annotations, ANDROIDX_NOTNULL);
}
}
- } else if (token.endsWith("?") || token.endsWith("!")) {
+ } else if (type.endsWith("?") || type.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: " + type);
}
//noinspection unchecked
- return new Pair<>(token, annotations);
+ return new Pair<>(type, annotations);
}
private static Pair<String, List<String>> getAnnotations(Tokenizer tokenizer, String token) throws ApiParseException {
@@ -387,7 +392,7 @@
name = token.substring(token.lastIndexOf('.') + 1); // For inner classes, strip outer classes from name
token = tokenizer.requireToken();
if (!"(".equals(token)) {
- throw new ApiParseException("expected (", tokenizer.getLine());
+ throw new ApiParseException("expected (", tokenizer);
}
method = new TextConstructorItem(api, /*typeParameters*/
name, /*signature*/ cl, isPublic, isProtected, isPrivate, isInternal, false/*isFinal*/,
@@ -397,14 +402,13 @@
/*overriddenMethod*/ cl.asTypeInfo(),
/*thrownExceptions*/ tokenizer.pos(), annotations);
method.setDeprecated(isDeprecated);
- token = tokenizer.requireToken();
- parseParameterList(api, tokenizer, method, /*new HashSet<String>(),*/ token);
+ parseParameterList(api, tokenizer, method);
token = tokenizer.requireToken();
if ("throws".equals(token)) {
token = parseThrows(tokenizer, method);
}
if (!";".equals(token)) {
- throw new ApiParseException("expected ; found " + token, tokenizer.getLine());
+ throw new ApiParseException("expected ; found " + token, tokenizer);
}
cl.addConstructor(method);
}
@@ -424,6 +428,8 @@
boolean isInfix = false;
boolean isOperator = false;
boolean isInline = false;
+ boolean isNative = false;
+ boolean isStrictFp = false;
TextTypeItem returnType;
String name;
TextMethodItem method;
@@ -483,6 +489,14 @@
isSynchronized = true;
token = tokenizer.requireToken();
break;
+ case "native":
+ isNative = true;
+ token = tokenizer.requireToken();
+ break;
+ case "strictfp":
+ isStrictFp = true;
+ token = tokenizer.requireToken();
+ break;
case "infix":
isInfix = true;
token = tokenizer.requireToken();
@@ -509,30 +523,56 @@
Pair<String, List<String>> kotlinTypeSuffix = processKotlinTypeSuffix(api, token, annotations);
token = kotlinTypeSuffix.getFirst();
annotations = kotlinTypeSuffix.getSecond();
- returnType = api.obtainTypeFromString(token, cl, typeParameterList);
+ String returnTypeString = token;
token = tokenizer.requireToken();
+
+ if (returnTypeString.contains("@") && (returnTypeString.indexOf('<') == -1 ||
+ returnTypeString.indexOf('@') < returnTypeString.indexOf('<'))) {
+ //noinspection StringConcatenationInLoop
+ returnTypeString += " " + token;
+ token = tokenizer.requireToken();
+ }
+ while (true) {
+ if (token.contains("@") && (token.indexOf('<') == -1 ||
+ token.indexOf('@') < token.indexOf('<'))) {
+ // Type-use annotations in type; keep accumulating
+ returnTypeString += " " + token;
+ token = tokenizer.requireToken();
+ if (token.startsWith("[")) { // TODO: This isn't general purpose; make requireToken smarter!
+ returnTypeString += " " + token;
+ token = tokenizer.requireToken();
+ }
+ } else {
+ break;
+ }
+ }
+
+ returnType = api.obtainTypeFromString(returnTypeString, cl, typeParameterList);
+
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,
+ isPublic, isProtected, isPrivate, isInternal, isFinal, isStatic, isAbstract,
+ isSynchronized, isNative, isDefault, isStrictFp, isInfix, isOperator, isInline,
returnType, tokenizer.pos(), annotations);
method.setDeprecated(isDeprecated);
method.setTypeParameterList(typeParameterList);
token = tokenizer.requireToken();
if (!"(".equals(token)) {
- throw new ApiParseException("expected (", tokenizer.getLine());
+ throw new ApiParseException("expected (, was " + token, tokenizer);
}
- token = tokenizer.requireToken();
- parseParameterList(api, tokenizer, method, /*typeVariableNames,*/ token);
+ parseParameterList(api, tokenizer, method);
token = tokenizer.requireToken();
if ("throws".equals(token)) {
token = parseThrows(tokenizer, method);
}
+ if ("default".equals(token)) {
+ token = parseDefault(tokenizer, method);
+ }
if (!";".equals(token)) {
- throw new ApiParseException("expected ; found " + token, tokenizer.getLine());
+ throw new ApiParseException("expected ; found " + token, tokenizer);
}
cl.addMethod(method);
}
@@ -637,7 +677,7 @@
token = tokenizer.requireToken();
}
if (!";".equals(token)) {
- throw new ApiParseException("expected ; found " + token, tokenizer.getLine());
+ throw new ApiParseException("expected ; found " + token, tokenizer);
}
try {
v = parseValue(type, val);
@@ -728,14 +768,18 @@
}
}
- private static void parseParameterList(TextCodebase api, Tokenizer tokenizer, TextMethodItem method,
- String token) throws ApiParseException {
+ private static void parseParameterList(TextCodebase api, Tokenizer tokenizer, TextMethodItem method)
+ throws ApiParseException {
+ String token = tokenizer.requireToken();
int index = 0;
while (true) {
if (")".equals(token)) {
return;
}
+ // Each item can be
+ // annotations optional-modifiers type-with-use-annotations-and-generics optional-name optional-equals-default-value
+
// Metalava: including annotations in file now
List<String> annotations = null;
Pair<String, List<String>> result = getAnnotations(tokenizer, token);
@@ -744,20 +788,31 @@
annotations = result.component2();
}
- Pair<String, List<String>> kotlinTypeSuffix = processKotlinTypeSuffix(api, token, annotations);
- token = kotlinTypeSuffix.getFirst();
- annotations = kotlinTypeSuffix.getSecond();
- String type = token;
- TextTypeItem typeInfo = api.obtainTypeFromString(token);
-
boolean vararg = false;
- token = tokenizer.requireToken();
-
if ("vararg".equals(token)) {
vararg = true;
token = tokenizer.requireToken();
}
+ // Token should now represent the type
+ String type = token;
+ token = tokenizer.requireToken();
+ if (token.startsWith("@")) {
+ // Type use annotations within the type, which broke up the tokenizer;
+ // put it back together
+ type += " " + token;
+ token = tokenizer.requireToken();
+ if (token.startsWith("[")) { // TODO: This isn't general purpose; make requireToken smarter!
+ type += " " + token;
+ token = tokenizer.requireToken();
+ }
+ }
+
+ Pair<String, List<String>> kotlinTypeSuffix = processKotlinTypeSuffix(api, type, annotations);
+ String typeString = kotlinTypeSuffix.getFirst();
+ annotations = kotlinTypeSuffix.getSecond();
+ TextTypeItem typeInfo = api.obtainTypeFromString(typeString);
+
String name;
String publicName;
if (isIdent(token) && !token.equals("=")) {
@@ -785,21 +840,35 @@
token = tokenizer.requireToken();
} else if (")".equals(token)) {
} else {
- throw new ApiParseException("expected , found " + token, tokenizer.getLine());
+ throw new ApiParseException("expected , or ), found " + token, tokenizer);
}
- method.addParameter(new TextParameterItem(api, method, name, publicName, defaultValue, index, type,
+ method.addParameter(new TextParameterItem(api, method, name, publicName, defaultValue, index, typeString,
typeInfo,
- vararg || type.endsWith("..."),
+ vararg || typeString.endsWith("..."),
tokenizer.pos(),
annotations));
- if (type.endsWith("...")) {
+ if (typeString.endsWith("...")) {
method.setVarargs(true);
}
index++;
}
}
+ private static String parseDefault(Tokenizer tokenizer, TextMethodItem method)
+ throws ApiParseException {
+ StringBuilder sb = new StringBuilder();
+ while (true) {
+ String token = tokenizer.requireToken();
+ if (";".equals(token)) {
+ method.setAnnotationDefault(sb.toString());
+ return token;
+ } else {
+ sb.append(token);
+ }
+ }
+ }
+
private static String parseThrows(Tokenizer tokenizer, TextMethodItem method)
throws ApiParseException {
String token = tokenizer.requireToken();
@@ -809,12 +878,12 @@
return token;
} else if (",".equals(token)) {
if (comma) {
- throw new ApiParseException("Expected exception, got ','", tokenizer.getLine());
+ throw new ApiParseException("Expected exception, got ','", tokenizer);
}
comma = true;
} else {
if (!comma) {
- throw new ApiParseException("Expected ',' or ';' got " + token, tokenizer.getLine());
+ throw new ApiParseException("Expected ',' or ';' got " + token, tokenizer);
}
comma = false;
method.addException(token);
@@ -833,7 +902,7 @@
private static void assertIdent(Tokenizer tokenizer, String token) throws ApiParseException {
if (!isIdent(token.charAt(0))) {
- throw new ApiParseException("Expected identifier: " + token, tokenizer.getLine());
+ throw new ApiParseException("Expected identifier: " + token, tokenizer);
}
}
@@ -930,7 +999,7 @@
}
final char k = mBuf[mPos];
if (k == '\n' || k == '\r') {
- throw new ApiParseException("Unexpected newline for \" starting at " + line, mLine);
+ throw new ApiParseException("Unexpected newline for \" starting at " + line + " in " + mFilename, mLine);
}
mPos++;
switch (state) {
@@ -953,7 +1022,24 @@
} else {
int genericDepth = 0;
do {
- while (mPos < mBuf.length && !isSpace(mBuf[mPos]) && !isSeparator(mBuf[mPos], parenIsSep)) {
+ while (mPos < mBuf.length) {
+ char d = mBuf[mPos];
+ if (isSpace(d) || isSeparator(d, parenIsSep)) {
+ break;
+ } else if (d == '"') {
+ // String literal in token: skip the full thing
+ mPos++;
+ while (mPos < mBuf.length) {
+ if (mBuf[mPos] == '"') {
+ mPos++;
+ break;
+ } else if (mBuf[mPos] == '\\') {
+ mPos++;
+ }
+ mPos++;
+ }
+ continue;
+ }
mPos++;
}
if (mPos < mBuf.length) {
@@ -975,6 +1061,11 @@
return new String(mBuf, start, mPos - start);
}
}
+
+ @Nullable
+ public String getFileName() {
+ return mFilename;
+ }
}
static boolean isSpace(char c) {
diff --git a/src/main/java/com/android/tools/metalava/doclava1/ApiParseException.java b/src/main/java/com/android/tools/metalava/doclava1/ApiParseException.java
index 6711f8c..23bd38e 100644
--- a/src/main/java/com/android/tools/metalava/doclava1/ApiParseException.java
+++ b/src/main/java/com/android/tools/metalava/doclava1/ApiParseException.java
@@ -16,6 +16,9 @@
package com.android.tools.metalava.doclava1;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
//
// Copied from doclava1, but adapted to metalava's code model
//
@@ -23,27 +26,44 @@
public String file;
public int line;
- public ApiParseException(String message) {
+ ApiParseException(@NotNull String message) {
super(message);
}
- public ApiParseException(String message, Exception cause) {
+ ApiParseException(@NotNull String message, Exception cause) {
super(message, cause);
if (cause instanceof ApiParseException) {
+ this.file = ((ApiParseException) cause).file;
this.line = ((ApiParseException) cause).line;
}
}
- public ApiParseException(String message, int line) {
+ ApiParseException(@NotNull String message, @NotNull ApiFile.Tokenizer tokenizer) {
+ this(message, tokenizer.getFileName(), tokenizer.getLine());
+ }
+
+ private ApiParseException(@NotNull String message, @Nullable String file, int line) {
super(message);
+ this.file = file;
this.line = line;
}
+ ApiParseException(@NotNull String message, int line) {
+ this(message, null, line);
+ }
+
public String getMessage() {
- if (line > 0) {
- return super.getMessage() + " line " + line;
- } else {
- return super.getMessage();
+ StringBuilder sb = new StringBuilder();
+ if (file != null) {
+ sb.append(file).append(':');
}
+ if (line > 0) {
+ sb.append(Integer.toString(line)).append(':');
+ }
+ if (sb.length() > 0) {
+ sb.append(' ');
+ }
+ sb.append(super.getMessage());
+ return sb.toString();
}
}
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 93ceee1..4f48174 100644
--- a/src/main/java/com/android/tools/metalava/doclava1/Errors.java
+++ b/src/main/java/com/android/tools/metalava/doclava1/Errors.java
@@ -197,6 +197,8 @@
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);
+ public static final Error MISSING_TYPEDEF_CONSTANT = new Error(149, LINT);
+ public static final Error INTERNAL_ERROR = new Error(150, ERROR);
static {
// Attempt to initialize error names based on the field names
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 f87833f..a3d9647 100644
--- a/src/main/java/com/android/tools/metalava/model/AnnotationItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/AnnotationItem.kt
@@ -52,11 +52,24 @@
/** Generates source code for this annotation (using fully qualified names) */
fun toSource(): String
- /** Whether this annotation is significant and should be included in signature files, stubs, etc */
- fun isSignificant(): Boolean {
+ /** Whether this annotation is significant and should be included in signature files */
+ fun isSignificantInSignatures(): Boolean {
return includeInSignatures(qualifiedName() ?: return false)
}
+ /** Whether this annotation is significant and should be included in stub files etc */
+ fun isSignificantInStubs(): Boolean {
+ return includeInStubs(qualifiedName() ?: return false)
+ }
+
+ /**
+ * Whether this annotation has class retention. Only class retention annotations are
+ * inserted into the stubs, the rest are extracted into the separate external annotations file.
+ */
+ fun hasClassRetention(): Boolean {
+ return hasClassRetention(qualifiedName())
+ }
+
/** Attributes of the annotation (may be empty) */
fun attributes(): List<AnnotationAttribute>
@@ -131,6 +144,33 @@
return false
}
+ /** Whether the given annotation name is "significant", e.g. should be included in signature files */
+ fun includeInStubs(qualifiedName: String?): Boolean {
+ qualifiedName ?: return false
+ if (includeInSignatures(qualifiedName)) {
+ return true
+ }
+
+ // These are the significant annotations that should be included in the stubs.
+ // This is a hardcoded list here to minimize risk in the P release branch;
+ // in master the check is more general (we keep only runtime retention annotations
+ // that match the API filter, plus the retention one)
+ return when (qualifiedName) {
+ "android.view.ViewDebug.ExportedProperty",
+ "android.widget.RemoteViews.RemoteView",
+ "android.view.ViewDebug.CapturedViewProperty",
+
+ "java.lang.FunctionalInterface",
+ "java.lang.SafeVarargs",
+ "java.lang.annotation.Documented",
+ "java.lang.annotation.Inherited",
+ "java.lang.annotation.Repeatable",
+ "java.lang.annotation.Retention",
+ "java.lang.annotation.Target" -> true
+ else -> false
+ }
+ }
+
/** The simple name of an annotation, which is the annotation name (not qualified name) prefixed by @ */
fun simpleName(item: AnnotationItem): String {
val qualifiedName = item.qualifiedName() ?: return ""
@@ -250,6 +290,8 @@
"android.annotation.CheckResult" -> return "androidx.annotation.CheckResult"
"android.support.annotation.RequiresPermission",
"android.annotation.RequiresPermission" -> return "androidx.annotation.RequiresPermission"
+ "android.annotation.RequiresPermission.Read" -> return "androidx.annotation.RequiresPermission.Read"
+ "android.annotation.RequiresPermission.Write" -> return "androidx.annotation.RequiresPermission.Write"
// These aren't support annotations, but could/should be:
"android.annotation.CurrentTimeMillisLong",
@@ -388,6 +430,30 @@
}
}
}
+
+ fun hasClassRetention(qualifiedName: String?): Boolean {
+ // For now, we treat everything except the recently nullable annotations
+ // as source retention; this works around the bug that we don't want to
+ // reference (from the .class files) annotations that aren't part of the SDK
+ // except for those that we include with the stubs
+
+ qualifiedName ?: return false
+
+ return when (qualifiedName) {
+ // Hardcoded list for now; in master, this is generalized
+ "android.view.ViewDebug.ExportedProperty",
+ "android.widget.RemoteViews.RemoteView",
+ "android.view.ViewDebug.CapturedViewProperty",
+
+ "androidx.annotation.RecentlyNullable",
+ "androidx.annotation.RecentlyNonNull" -> return true
+
+ else -> qualifiedName.startsWith("java.") ||
+ qualifiedName.startsWith("javax.") ||
+ qualifiedName.startsWith("kotlin.") ||
+ qualifiedName.startsWith("kotlinx.")
+ }
+ }
}
}
@@ -465,40 +531,74 @@
}
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" }
- val split = source.indexOf('=')
- val name: String
- val value: String
- if (split == -1) {
- name = "value"
- value = source.substring(source.indexOf('{'))
- } else {
- name = source.substring(0, split).trim()
- value = source.substring(split + 1).trim()
+ val list = mutableListOf<AnnotationAttribute>() // TODO: default size = 2
+ var begin = 0
+ var index = 0
+ val length = source.length
+ while (index < length) {
+ val c = source[index]
+ if (c == '{') {
+ index = findEnd(source, index + 1, length, '}')
+ } else if (c == '"') {
+ index = findEnd(source, index + 1, length, '"')
+ } else if (c == ',') {
+ addAttribute(list, source, begin, index)
+ index++
+ begin = index
+ continue
+ } else if (c == ' ' && index == begin) {
+ begin++
}
- list.add(DefaultAnnotationAttribute.create(name, value))
- return list
+
+ index++
}
- source.split(",").forEach { declaration ->
- val split = declaration.indexOf('=')
- val name: String
- val value: String
- if (split == -1) {
- name = "value"
- value = declaration.trim()
- } else {
- name = declaration.substring(0, split).trim()
- value = declaration.substring(split + 1).trim()
- }
- list.add(DefaultAnnotationAttribute.create(name, value))
+ if (begin < length) {
+ addAttribute(list, source, begin, length)
}
+
return list
}
+
+ private fun findEnd(source: String, from: Int, to: Int, sentinel: Char): Int {
+ var i = from
+ while (i < to) {
+ val c = source[i]
+ if (c == '\\') {
+ i++
+ } else if (c == sentinel) {
+ return i
+ }
+ i++
+ }
+ return to
+ }
+
+ private fun addAttribute(list: MutableList<AnnotationAttribute>, source: String, from: Int, to: Int) {
+ var split = source.indexOf('=', from)
+ if (split >= to) {
+ split = -1
+ }
+ val name: String
+ val value: String
+ val valueBegin: Int
+ val valueEnd: Int
+ if (split == -1) {
+ valueBegin = split + 1
+ valueEnd = to
+ name = "value"
+ } else {
+ name = source.substring(from, split).trim()
+ valueBegin = split + 1
+ valueEnd = to
+ }
+ value = source.substring(valueBegin, valueEnd).trim()
+ list.add(DefaultAnnotationAttribute.create(name, value))
+ }
+ }
+
+ override fun toString(): String {
+ return "DefaultAnnotationAttribute(name='$name', value=$value)"
}
}
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 41ce549..3c86e61 100644
--- a/src/main/java/com/android/tools/metalava/model/Codebase.kt
+++ b/src/main/java/com/android/tools/metalava/model/Codebase.kt
@@ -155,7 +155,7 @@
override var manifest: File? = null
private var permissions: Map<String, String>? = null
override var original: Codebase? = null
- override var supportsStagedNullability: Boolean = false
+ override var supportsStagedNullability: Boolean = true
override var units: List<PsiFile> = emptyList()
override var apiLevel: Int = -1
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 03eb387..a3836c0 100644
--- a/src/main/java/com/android/tools/metalava/model/MethodItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/MethodItem.kt
@@ -406,6 +406,9 @@
* declared in the signature) */
fun findThrownExceptions(): Set<ClassItem> = codebase.unsupported()
+ /** If annotation method, returns the default value as a source expression */
+ fun defaultValue(): String = ""
+
/**
* Returns true if this method is a signature match for the given method (e.g. can
* be overriding). This checks that the name and parameter lists match, but ignores
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 435baf8..a6c1b9f 100644
--- a/src/main/java/com/android/tools/metalava/model/ModifierList.kt
+++ b/src/main/java/com/android/tools/metalava/model/ModifierList.kt
@@ -184,8 +184,9 @@
removeAbstract: Boolean = false,
removeFinal: Boolean = false,
addPublic: Boolean = false,
- onlyIncludeSignatureAnnotations: Boolean = true
-
+ onlyIncludeSignatureAnnotations: Boolean = true,
+ onlyIncludeStubAnnotations: Boolean = false,
+ onlyIncludeClassRetentionAnnotations: Boolean = false
) {
val list = if (removeAbstract || removeFinal || addPublic) {
@@ -214,7 +215,9 @@
omitCommonPackages = omitCommonPackages,
separateLines = false,
writer = writer,
- onlyIncludeSignatureAnnotations = onlyIncludeSignatureAnnotations
+ onlyIncludeSignatureAnnotations = onlyIncludeSignatureAnnotations,
+ onlyIncludeStubAnnotations = onlyIncludeStubAnnotations,
+ onlyIncludeClassRetentionAnnotations = onlyIncludeClassRetentionAnnotations
)
}
@@ -387,19 +390,27 @@
separateLines: Boolean = false,
filterDuplicates: Boolean = false,
writer: Writer,
- onlyIncludeSignatureAnnotations: Boolean = true
+ onlyIncludeSignatureAnnotations: Boolean = true,
+ onlyIncludeStubAnnotations: Boolean = true,
+ onlyIncludeClassRetentionAnnotations: Boolean = false
) {
val annotations = list.annotations()
if (annotations.isNotEmpty()) {
var index = -1
for (annotation in annotations) {
index++
- if ((annotation.isNonNull() || annotation.isNullable())) {
+ if (onlyIncludeSignatureAnnotations && !annotation.isSignificantInSignatures()) {
+ continue
+ } else if (onlyIncludeStubAnnotations && !annotation.isSignificantInStubs()) {
+ continue
+ } else if (onlyIncludeClassRetentionAnnotations && !annotation.hasClassRetention() &&
+ !options.includeSourceRetentionAnnotations
+ ) {
+ continue
+ } else if ((annotation.isNonNull() || annotation.isNullable())) {
if (skipNullnessAnnotations) {
continue
}
- } else if (onlyIncludeSignatureAnnotations && !annotation.isSignificant()) {
- continue
}
// Optionally filter out duplicates
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 5d1eaa8..b1ac351 100644
--- a/src/main/java/com/android/tools/metalava/model/TypeItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/TypeItem.kt
@@ -23,6 +23,14 @@
import com.android.tools.metalava.options
import java.util.function.Predicate
+/**
+ * Whether metalava supports type use annotations.
+ * Note that you can't just turn this flag back on; you have to
+ * also add TYPE_USE back to the handful of nullness
+ * annotations in stub-annotations/src/main/java/.
+ */
+const val SUPPORT_TYPE_USE_ANNOTATIONS = false
+
/** Represents a type */
interface TypeItem {
/**
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 4d78346..1aaca8e 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
@@ -51,14 +51,20 @@
override fun toString(): String = toSource()
override fun toSource(): String {
- val qualifiedName = qualifiedName() ?: return ""
+ val sb = StringBuilder(60)
+ appendAnnotation(sb, psiAnnotation)
+ return sb.toString()
+ }
+
+ private fun appendAnnotation(sb: StringBuilder, psiAnnotation: PsiAnnotation) {
+ val qualifiedName = AnnotationItem.mapName(codebase, psiAnnotation.qualifiedName) ?: return
val attributes = psiAnnotation.parameterList.attributes
if (attributes.isEmpty()) {
- return "@$qualifiedName"
+ sb.append("@$qualifiedName")
+ return
}
- val sb = StringBuilder(30)
sb.append("@")
sb.append(qualifiedName)
sb.append("(")
@@ -79,8 +85,6 @@
}
}
sb.append(")")
-
- return sb.toString()
}
override fun resolve(): ClassItem? {
@@ -150,6 +154,9 @@
}
sb.append('}')
}
+ is PsiAnnotation -> {
+ appendAnnotation(sb, value)
+ }
else -> {
if (value is PsiExpression) {
val source = getConstantSource(value)
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 a54dc24..2f557be 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
@@ -16,6 +16,7 @@
package com.android.tools.metalava.model.psi
+import com.android.tools.metalava.ExtractAnnotations
import com.android.tools.metalava.compatibility
import com.android.tools.metalava.model.ClassItem
import com.android.tools.metalava.model.MethodItem
@@ -23,6 +24,7 @@
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.PsiAnnotationMethod
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiMethod
import com.intellij.psi.util.PsiTypesUtil
@@ -211,6 +213,17 @@
return exceptions
}
+ override fun defaultValue(): String {
+ if (psiMethod is PsiAnnotationMethod) {
+ val value = psiMethod.defaultValue
+ if (value != null) {
+ return ExtractAnnotations.toSourceExpression(value, this)
+ }
+ }
+
+ return super.defaultValue()
+ }
+
override fun duplicate(targetContainingClass: ClassItem): PsiMethodItem {
val duplicated = create(codebase, targetContainingClass as PsiClassItem, psiMethod)
@@ -250,7 +263,9 @@
ModifierList.write(
modifierString, method.modifiers, method, removeAbstract = false,
removeFinal = false, addPublic = true,
- onlyIncludeSignatureAnnotations = true
+ onlyIncludeSignatureAnnotations = false,
+ onlyIncludeStubAnnotations = true,
+ onlyIncludeClassRetentionAnnotations = true
)
sb.append(modifierString.toString())
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 248d047..be0d36c 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
@@ -24,6 +24,7 @@
import com.android.tools.metalava.model.Codebase
import com.android.tools.metalava.model.Item
import com.android.tools.metalava.model.MemberItem
+import com.android.tools.metalava.model.SUPPORT_TYPE_USE_ANNOTATIONS
import com.android.tools.metalava.model.TypeItem
import com.android.tools.metalava.model.TypeParameterItem
import com.android.tools.metalava.model.text.TextTypeItem
@@ -73,7 +74,7 @@
assert(innerAnnotations || !outerAnnotations) // Can't supply outer=true,inner=false
return if (erased) {
- if (innerAnnotations || outerAnnotations) {
+ if (SUPPORT_TYPE_USE_ANNOTATIONS && (innerAnnotations || outerAnnotations)) {
// Not cached: Not common
toTypeString(codebase, psiType, outerAnnotations, innerAnnotations, erased)
} else {
@@ -84,7 +85,7 @@
}
} else {
when {
- outerAnnotations -> {
+ SUPPORT_TYPE_USE_ANNOTATIONS && outerAnnotations -> {
if (toAnnotatedString == null) {
toAnnotatedString = TypeItem.formatType(
toTypeString(
@@ -98,7 +99,7 @@
}
toAnnotatedString!!
}
- innerAnnotations -> {
+ SUPPORT_TYPE_USE_ANNOTATIONS && innerAnnotations -> {
if (toInnerAnnotatedString == null) {
toInnerAnnotatedString = TypeItem.formatType(
toTypeString(
@@ -339,7 +340,7 @@
)
}
- if (outerAnnotations || innerAnnotations) {
+ if (SUPPORT_TYPE_USE_ANNOTATIONS && (innerAnnotations || outerAnnotations)) {
val typeString = mapAnnotations(codebase, getCanonicalText(type, true))
if (!outerAnnotations && typeString.contains("@")) {
// Temporary hack: should use PSI type visitor instead
@@ -404,8 +405,8 @@
}
private fun getCanonicalText(type: PsiType, annotated: Boolean): String {
- val typeString = type.getCanonicalText(annotated)
- if (!annotated) {
+ val typeString = type.getCanonicalText(annotated && SUPPORT_TYPE_USE_ANNOTATIONS)
+ if (!annotated || !SUPPORT_TYPE_USE_ANNOTATIONS) {
return typeString
}
@@ -764,4 +765,4 @@
}
}
}
-}
\ No newline at end of file
+}
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 4262188..2b1e1d7 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
@@ -39,7 +39,7 @@
annotations: List<String>?
) : TextMethodItem(
codebase, name, containingClass, isPublic, isProtected, isPrivate, isInternal,
- isFinal, isStatic, isAbstract, isSynchronized, isNative, isDefault, false, false, false,
+ isFinal, isStatic, isAbstract, isSynchronized, isNative, isDefault, false, false, false, false,
returnType, position, annotations
),
ConstructorItem {
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 50acde8..fba5169 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
@@ -26,30 +26,41 @@
codebase: TextCodebase,
name: String,
containingClass: TextClassItem,
- isPublic: Boolean,
- isProtected: Boolean,
- isPrivate: Boolean,
- isInternal: Boolean,
- isFinal: Boolean,
- isStatic: Boolean,
- isTransient: Boolean,
- isVolatile: Boolean,
+ modifiers: TextModifiers,
private val type: TextTypeItem,
private val constantValue: Any?,
- position: SourcePositionInfo,
- annotations: List<String>?
-) : TextMemberItem(
- codebase, name, containingClass, position,
- TextModifiers(
- codebase = codebase,
- annotationStrings = annotations,
- public = isPublic, protected = isProtected, private = isPrivate, internal = isInternal,
- static = isStatic, final = isFinal, transient = isTransient, volatile = isVolatile
- )
-), FieldItem {
+ position: SourcePositionInfo
+) : TextMemberItem(codebase, name, containingClass, position, modifiers), FieldItem {
+ constructor(
+ codebase: TextCodebase,
+ name: String,
+ containingClass: TextClassItem,
+ isPublic: Boolean,
+ isProtected: Boolean,
+ isPrivate: Boolean,
+ isInternal: Boolean,
+ isFinal: Boolean,
+ isStatic: Boolean,
+ isTransient: Boolean,
+ isVolatile: Boolean,
+ type: TextTypeItem,
+ constantValue: Any?,
+ position: SourcePositionInfo,
+ annotations: List<String>?
+ ) :
+ this(
+ codebase, name, containingClass,
+ TextModifiers(
+ codebase = codebase,
+ annotationStrings = annotations,
+ public = isPublic, protected = isProtected, private = isPrivate, internal = isInternal,
+ static = isStatic, final = isFinal, transient = isTransient, volatile = isVolatile
+ ),
+ type, constantValue, position
+ )
init {
- (modifiers as TextModifiers).owner = this
+ modifiers.owner = this
}
override fun equals(other: Any?): Boolean {
@@ -71,7 +82,26 @@
override fun toString(): String = "Field ${containingClass().fullName()}.${name()}"
- override fun duplicate(targetContainingClass: ClassItem): FieldItem = codebase.unsupported()
+ override fun duplicate(targetContainingClass: ClassItem): TextFieldItem {
+ val m = modifiers as TextModifiers
+ val duplicated = TextFieldItem(
+ codebase, name(), targetContainingClass as TextClassItem,
+ m.duplicate(), type, constantValue, position
+ )
+
+ // Preserve flags that may have been inherited (propagated) fro surrounding packages
+ if (targetContainingClass.hidden) {
+ duplicated.hidden = true
+ }
+ if (targetContainingClass.removed) {
+ duplicated.removed = true
+ }
+ if (targetContainingClass.docOnly) {
+ duplicated.docOnly = true
+ }
+
+ return duplicated
+ }
private var isEnumConstant = false
override fun isEnumConstant(): Boolean = isEnumConstant
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 d187adc..ec76fff 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
@@ -40,6 +40,7 @@
isSynchronized: Boolean,
isNative: Boolean,
isDefault: Boolean,
+ isStrictFp: Boolean,
isInfix: Boolean,
isOperator: Boolean,
isInline: Boolean,
@@ -54,7 +55,7 @@
codebase = codebase,
annotationStrings = annotations, public = isPublic, protected = isProtected, internal = isInternal,
private = isPrivate, static = isStatic, final = isFinal, abstract = isAbstract,
- synchronized = isSynchronized, native = isNative, default = isDefault,
+ synchronized = isSynchronized, native = isNative, default = isDefault, strictfp = isStrictFp,
infix = isInfix, operator = isOperator, inline = isInline
)
), MethodItem {
@@ -189,4 +190,14 @@
"${if (isConstructor()) "Constructor" else "Method"} ${containingClass().qualifiedName()}.${name()}(${parameters().joinToString {
it.type().toSimpleType()
}})"
+
+ private var annotationDefault = ""
+
+ fun setAnnotationDefault(default: String) {
+ annotationDefault = default
+ }
+
+ override fun defaultValue(): String {
+ return annotationDefault
+ }
}
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 a627199..5038b3b 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
@@ -27,7 +27,7 @@
class TextModifiers(
override val codebase: Codebase,
- annotationStrings: List<String>? = null,
+ private val annotationStrings: List<String>? = null,
private var public: Boolean = false,
private var protected: Boolean = false,
private var private: Boolean = false,
@@ -49,6 +49,33 @@
) : MutableModifierList {
private var annotations: MutableList<AnnotationItem> = mutableListOf()
+ fun duplicate(): TextModifiers {
+ val new = TextModifiers(
+ codebase,
+ null,
+ public,
+ protected,
+ private,
+ internal,
+ static,
+ abstract,
+ final,
+ native,
+ synchronized,
+ strictfp,
+ transient,
+ volatile,
+ default,
+ infix,
+ operator,
+ inline,
+ sealed,
+ vararg
+ )
+ new.annotations.addAll(annotations) // these are immutable; sharing copies is fine
+ return new
+ }
+
init {
annotationStrings?.forEach { source ->
val index = source.indexOf('(')
@@ -89,6 +116,7 @@
override fun isDefault(): Boolean = default
override fun isSealed(): Boolean = sealed
override fun isInfix(): Boolean = infix
+ override fun isInline(): Boolean = inline
override fun isOperator(): Boolean = operator
override fun isVarArg(): Boolean = vararg
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 56d8375..ce054c4 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
@@ -78,7 +78,6 @@
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 */
diff --git a/src/main/resources/version.properties b/src/main/resources/version.properties
index dd2d971..ab0e374 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.12
+metalavaVersion=1.0.0
diff --git a/src/test/java/com/android/tools/metalava/AnnotationStatisticsTest.kt b/src/test/java/com/android/tools/metalava/AnnotationStatisticsTest.kt
index f62c805..4a1cf39 100644
--- a/src/test/java/com/android/tools/metalava/AnnotationStatisticsTest.kt
+++ b/src/test/java/com/android/tools/metalava/AnnotationStatisticsTest.kt
@@ -163,8 +163,8 @@
}
"""
),
- supportNonNullSource,
- supportNullableSource
+ androidxNonNullSource,
+ androidxNullableSource
),
expectedOutput = """
6 methods and fields were missing nullness annotations out of 8 total API references.
diff --git a/src/test/java/com/android/tools/metalava/AnnotationsDifferTest.kt b/src/test/java/com/android/tools/metalava/AnnotationsDifferTest.kt
index 0d367d1..daf8c20 100644
--- a/src/test/java/com/android/tools/metalava/AnnotationsDifferTest.kt
+++ b/src/test/java/com/android/tools/metalava/AnnotationsDifferTest.kt
@@ -52,6 +52,8 @@
""".trimIndent(), false, false)
val apiFile = temporaryFolder.newFile("diff.txt")
+ compatibility = Compatibility(true)
+ options = Options(emptyArray())
AnnotationsDiffer(codebase, codebase2).writeDiffSignature(apiFile)
assertTrue(apiFile.exists())
val actual = apiFile.readText(Charsets.UTF_8)
diff --git a/src/test/java/com/android/tools/metalava/AnnotationsMergerTest.kt b/src/test/java/com/android/tools/metalava/AnnotationsMergerTest.kt
index 654ed1a..003e7ba 100644
--- a/src/test/java/com/android/tools/metalava/AnnotationsMergerTest.kt
+++ b/src/test/java/com/android/tools/metalava/AnnotationsMergerTest.kt
@@ -16,6 +16,7 @@
package com.android.tools.metalava
+import com.android.tools.metalava.model.SUPPORT_TYPE_USE_ANNOTATIONS
import org.junit.Test
class AnnotationsMergerTest : DriverTest() {
@@ -50,8 +51,8 @@
),
uiThreadSource,
intRangeAnnotationSource,
- supportNonNullSource,
- supportNullableSource
+ androidxNonNullSource,
+ androidxNullableSource
),
// Skip the annotations themselves from the output
extraArguments = arrayOf(
@@ -162,14 +163,25 @@
// 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);
- }
- }
+ api = if (SUPPORT_TYPE_USE_ANNOTATIONS) {
"""
+ 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);
+ }
+ }
+ """
+ } else {
+ """
+ package test.pkg {
+ public interface Appendable {
+ method @androidx.annotation.NonNull public test.pkg.Appendable append(java.lang.CharSequence);
+ method public java.lang.String reverse(java.lang.String);
+ }
+ }
+ """
+ }
)
}
diff --git a/src/test/java/com/android/tools/metalava/ApiFileTest.kt b/src/test/java/com/android/tools/metalava/ApiFileTest.kt
index abd3c1d..387c24e 100644
--- a/src/test/java/com/android/tools/metalava/ApiFileTest.kt
+++ b/src/test/java/com/android/tools/metalava/ApiFileTest.kt
@@ -157,7 +157,7 @@
package test.pkg
class Foo {
- fun error(int: Int = 42, int2: Int? = null, byte: Int = 42) { }
+ fun error(int: Int = 42, int2: Int? = null, byte: Int = 42, vararg args: String) { }
}
"""
)
@@ -166,7 +166,7 @@
package test.pkg {
public final class Foo {
ctor public Foo();
- method public void error(int p = "42", Integer? int2 = "null", int p1 = "42");
+ method public void error(int p = "42", Integer? int2 = "null", int p1 = "42", java.lang.String... args);
}
}
""",
@@ -381,8 +381,8 @@
inline operator fun <F, S> PlatformJavaPair<F, S>.component1() = first
"""
),
- supportNonNullSource,
- supportNullableSource
+ androidxNonNullSource,
+ androidxNullableSource
),
api = """
package androidx.util {
@@ -971,6 +971,42 @@
}
@Test
+ fun `Warn about findViewById`() {
+ // Include as many modifiers as possible to see which ones are included
+ // in the signature files, and the expected sorting order.
+ // Note that the signature files treat "deprecated" as a fake modifier.
+ // Note also how the "protected" modifier on the interface method gets
+ // promoted to public.
+ check(
+ checkDoclava1 = true,
+ compatibilityMode = false,
+ outputKotlinStyleNulls = false,
+ sourceFiles = *arrayOf(
+ java(
+ """
+ package test.pkg;
+ import android.annotation.Nullable;
+
+ @SuppressWarnings("ALL")
+ public abstract class Foo {
+ @Nullable public String findViewById(int id) { return ""; }
+ }
+ """
+ ),
+ nullableSource
+ ),
+ api = """
+ package test.pkg {
+ public abstract class Foo {
+ ctor public Foo();
+ method public String findViewById(int);
+ }
+ }
+ """
+ )
+ }
+
+ @Test
fun `Check all modifiers`() {
// Include as many modifiers as possible to see which ones are included
// in the signature files, and the expected sorting order.
diff --git a/src/test/java/com/android/tools/metalava/ApiFromTextTest.kt b/src/test/java/com/android/tools/metalava/ApiFromTextTest.kt
index 7e9dcbd..70d8020 100644
--- a/src/test/java/com/android/tools/metalava/ApiFromTextTest.kt
+++ b/src/test/java/com/android/tools/metalava/ApiFromTextTest.kt
@@ -43,6 +43,111 @@
}
@Test
+ fun `Annotation signatures requiring more complicated token matching`() {
+ val source = """
+ package test {
+ public class MyTest {
+ method @RequiresPermission(value="android.permission.AUTHENTICATE_ACCOUNTS", apis="..22") public boolean addAccountExplicitly(android.accounts.Account, String, android.os.Bundle);
+ method @CheckResult(suggest="#enforceCallingOrSelfPermission(String,\"foo\",String)") public abstract int checkCallingOrSelfPermission(@NonNull String);
+ method @RequiresPermission(anyOf={"android.permission.MANAGE_ACCOUNTS", "android.permission.USE_CREDENTIALS"}, apis="..22") public void invalidateAuthToken(String, String);
+ }
+ }
+ """
+ check(
+ compatibilityMode = false,
+ outputKotlinStyleNulls = false,
+ signatureSource = source,
+ api = source
+ )
+ }
+
+ @Test
+ fun `Multiple extends`() {
+ val source = """
+ package test {
+ public static interface PickConstructors extends test.pkg.PickConstructors.AutoCloseable {
+ }
+ public interface XmlResourceParser extends org.xmlpull.v1.XmlPullParser android.util.AttributeSet java.lang.AutoCloseable {
+ method public void close();
+ }
+ }
+ """
+ check(
+ compatibilityMode = false,
+ outputKotlinStyleNulls = false,
+ signatureSource = source,
+ api = source
+ )
+ }
+
+ @Test
+ fun `Native and strictfp keywords`() {
+ val source = """
+ package test.pkg {
+ public class MyTest {
+ method public native float dotWithNormal(float, float, float);
+ method public static strictfp double toDegrees(double);
+ }
+ }
+ """
+ check(
+ compatibilityMode = false,
+ outputKotlinStyleNulls = false,
+ signatureSource = source,
+ api = source
+ )
+ }
+
+ @Test
+ fun `Type use annotations`() {
+ check(
+ compatibilityMode = false,
+ outputKotlinStyleNulls = false,
+ signatureSource = """
+ package test.pkg {
+ public class MyTest {
+ method public static int codePointAt(char @NonNull [], int);
+ method @NonNull public java.util.Set<java.util.Map.@NonNull Entry<K,V>> entrySet();
+ method @NonNull public java.lang.annotation.@NonNull Annotation @NonNull [] getAnnotations();
+ method @NonNull public abstract java.lang.annotation.@NonNull Annotation @NonNull [] @NonNull [] getParameterAnnotations();
+ method @NonNull public @NonNull String @NonNull [] split(@NonNull String, int);
+ method public static char @NonNull [] toChars(int);
+ }
+ }
+ """,
+ api = """
+ package test.pkg {
+ public class MyTest {
+ method public static int codePointAt(char @NonNull [], int);
+ method @NonNull public java.util.Set<java.util.Map.@NonNull Entry<K,V>> entrySet();
+ method @NonNull public java.lang.annotation.Annotation @NonNull [] getAnnotations();
+ method @NonNull public abstract java.lang.annotation.Annotation @NonNull [] @NonNull [] getParameterAnnotations();
+ method @NonNull public String @NonNull [] split(@NonNull String, int);
+ method public static char @NonNull [] toChars(int);
+ }
+ }
+ """
+ )
+ }
+
+ @Test
+ fun `Vararg modifier`() {
+ val source = """
+ package test.pkg {
+ public final class Foo {
+ ctor public Foo();
+ method public void error(int p = "42", Integer int2 = "null", int p1 = "42", vararg String args);
+ }
+ }
+ """
+ check(
+ compatibilityMode = false,
+ outputKotlinStyleNulls = false,
+ signatureSource = source
+ )
+ }
+
+ @Test
fun `Infer fully qualified names from shorter names`() {
check(
compatibilityMode = true,
@@ -354,4 +459,25 @@
api = source
)
}
+
+ @Test
+ fun `Signatures with default annotation method values`() {
+ val source = """
+ package libcore.util {
+ public @interface NonNull {
+ method public abstract int from() default java.lang.Integer.MIN_VALUE;
+ method public abstract double fromWithCast() default (double)java.lang.Float.NEGATIVE_INFINITY;
+ method public abstract String! myString() default "This is a \"string\"";
+ method public abstract int to() default java.lang.Integer.MAX_VALUE;
+ }
+ }
+ """
+
+ check(
+ inputKotlinStyleNulls = true,
+ compatibilityMode = false,
+ signatureSource = source,
+ api = source
+ )
+ }
}
\ 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 f8ad6ec..0b5b239 100644
--- a/src/test/java/com/android/tools/metalava/DocAnalyzerTest.kt
+++ b/src/test/java/com/android/tools/metalava/DocAnalyzerTest.kt
@@ -102,7 +102,7 @@
* to be a String resource reference (e.g. {@code android.R.string.ok}).
*/
@SuppressWarnings({"unchecked", "deprecation", "all"})
- public @interface StringRes {
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface StringRes {
}
"""
)
diff --git a/src/test/java/com/android/tools/metalava/DriverTest.kt b/src/test/java/com/android/tools/metalava/DriverTest.kt
index 13ad8cd..1449791 100644
--- a/src/test/java/com/android/tools/metalava/DriverTest.kt
+++ b/src/test/java/com/android/tools/metalava/DriverTest.kt
@@ -219,7 +219,12 @@
/** 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
+ extractAnnotations: Map<String, String>? = null,
+ /**
+ * Whether to include source retention annotations in the stubs (in that case they do not
+ * go into the extracted annotations zip file)
+ */
+ includeSourceRetentionAnnotations: Boolean = true
) {
System.setProperty("METALAVA_TESTS_RUNNING", VALUE_TRUE)
@@ -407,7 +412,14 @@
if (showUnannotated) {
arrayOf("--show-unannotated")
} else {
- emptyArray<String>()
+ emptyArray()
+ }
+
+ val includeSourceRetentionAnnotationArgs =
+ if (includeSourceRetentionAnnotations) {
+ arrayOf("--include-source-retention")
+ } else {
+ emptyArray()
}
var removedApiFile: File? = null
@@ -606,6 +618,7 @@
*applyApiLevelsXmlArgs,
*showAnnotationArguments,
*showUnannotatedArgs,
+ *includeSourceRetentionAnnotationArgs,
*sdkFilesArgs,
*importedPackageArgs.toTypedArray(),
*skipEmitPackagesArgs.toTypedArray(),
@@ -1310,8 +1323,16 @@
String[] allOf() default {};
String[] anyOf() default {};
boolean conditional() default false;
+ @Target({FIELD, METHOD, PARAMETER})
+ @interface Read {
+ RequiresPermission value() default @RequiresPermission;
+ }
+ @Target({FIELD, METHOD, PARAMETER})
+ @interface Write {
+ RequiresPermission value() default @RequiresPermission;
+ }
}
- """
+ """
).indented()
val requiresFeatureSource: TestFile = java(
@@ -1325,7 +1346,7 @@
public @interface RequiresFeature {
String value();
}
- """
+ """
).indented()
val requiresApiSource: TestFile = java(
@@ -1340,7 +1361,7 @@
int value() default 1;
int api() default 1;
}
- """
+ """
).indented()
val sdkConstantSource: TestFile = java(
@@ -1355,23 +1376,23 @@
}
SdkConstantType value();
}
- """
+ """
).indented()
val broadcastBehaviorSource: TestFile = java(
"""
- package android.annotation;
- import java.lang.annotation.*;
- /** @hide */
- @Target({ ElementType.FIELD })
- @Retention(RetentionPolicy.SOURCE)
- public @interface BroadcastBehavior {
- boolean explicitOnly() default false;
- boolean registeredOnly() default false;
- boolean includeBackground() default false;
- boolean protectedBroadcast() default false;
- }
- """
+ package android.annotation;
+ import java.lang.annotation.*;
+ /** @hide */
+ @Target({ ElementType.FIELD })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface BroadcastBehavior {
+ boolean explicitOnly() default false;
+ boolean registeredOnly() default false;
+ boolean includeBackground() default false;
+ boolean protectedBroadcast() default false;
+ }
+ """
).indented()
val nullableSource: TestFile = java(
@@ -1396,7 +1417,7 @@
val supportNonNullSource: TestFile = java(
"""
- package androidx.annotation;
+ package android.support.annotation;
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -1410,17 +1431,17 @@
val supportNullableSource: 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 Nullable {
-}
- """
-)
+ package android.support.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 {
+ }
+ """
+).indented()
val androidxNonNullSource: TestFile = java(
"""
@@ -1438,17 +1459,45 @@
val androidxNullableSource: 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 Nullable {
-}
- """
-)
+ 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 {
+ }
+ """
+).indented()
+
+val recentlyNonNullSource: 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 RecentlyNonNull {
+ }
+ """
+).indented()
+
+val recentlyNullableSource: 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 RecentlyNullable {
+ }
+ """
+).indented()
val supportParameterName: TestFile = java(
"""
@@ -1527,17 +1576,17 @@
val suppressLintSource: TestFile = java(
"""
-package android.annotation;
+ package android.annotation;
-import static java.lang.annotation.ElementType.*;
-import java.lang.annotation.*;
-@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
-@Retention(RetentionPolicy.CLASS)
-public @interface SuppressLint {
- String[] value();
-}
- """
-)
+ import static java.lang.annotation.ElementType.*;
+ import java.lang.annotation.*;
+ @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
+ @Retention(RetentionPolicy.CLASS)
+ public @interface SuppressLint {
+ String[] value();
+ }
+ """
+).indented()
val systemServiceSource: TestFile = java(
"""
diff --git a/src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt b/src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt
index 427e41d..5a8ee6f 100644
--- a/src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt
+++ b/src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt
@@ -25,9 +25,10 @@
@Test
fun `Check java typedef extraction and warning about non-source retention of typedefs`() {
check(
+ includeSourceRetentionAnnotations = false,
sourceFiles = *arrayOf(
java(
- """
+ """
package test.pkg;
import android.annotation.IntDef;
@@ -75,7 +76,8 @@
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 """
+ 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">
@@ -104,6 +106,7 @@
@Test
fun `Check Kotlin and referencing hidden constants from typedef`() {
check(
+ includeSourceRetentionAnnotations = false,
sourceFiles = *arrayOf(
kotlin(
"""
@@ -151,24 +154,187 @@
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 """
+ extractAnnotations = mapOf(
+ "test.pkg" to """
+ <?xml version="1.0" encoding="UTF-8"?>
+ <root>
+ <item name="test.pkg.LongDefTest void setFlags(java.lang.Object, int) 0">
+ <annotation name="androidx.annotation.NonNull"/>
+ </item>
+ <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 boolean isNull(java.lang.String) 0">
+ <annotation name="androidx.annotation.Nullable"/>
+ </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>
+ <item name="test.pkg.LongDefTestKt TYPE_1">
+ <annotation name="androidx.annotation.NonNull"/>
+ </item>
+ <item name="test.pkg.LongDefTestKt TYPE_2">
+ <annotation name="androidx.annotation.NonNull"/>
+ </item>
+ <item name="test.pkg.LongDefTestKt UNRELATED_TYPE">
+ <annotation name="androidx.annotation.NonNull"/>
+ </item>
+ </root>
+ """
+ )
+ )
+ }
+
+ @Test
+ fun `Check including only class retention annotations other than typedefs`() {
+ check(
+ includeSourceRetentionAnnotations = true,
+ sourceFiles = *arrayOf(
+ kotlin(
+ """
+ @file:Suppress("unused", "UseExpressionBody")
+
+ package test.pkg
+
+ import android.annotation.LongDef
+
+ 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
+
+ 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>
+ """
+ )
+ )
+ }
+
+ @Test
+ fun `Extract permission annotations`() {
+ check(
+ includeSourceRetentionAnnotations = false,
+ sourceFiles = *arrayOf(
+ java(
+ """
+ package test.pkg;
+
+ import android.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 = "";
+ }
+ """
+ ).indented(),
+ 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(),
+ requiresPermissionSource
+ ),
+ 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}" />
+ <item name="test.pkg.PermissionsTest CONTENT_URI">
+ <annotation name="androidx.annotation.RequiresPermission.Read">
+ <val name="value" val=""android.permission.MY_READ_PERMISSION_STRING"" />
+ </annotation>
+ <annotation name="androidx.annotation.RequiresPermission.Write">
+ <val name="value" val=""android.permission.MY_WRITE_PERMISSION_STRING"" />
</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}" />
+ <item name="test.pkg.PermissionsTest void myMethod()">
+ <annotation name="androidx.annotation.RequiresPermission">
+ <val name="value" val=""android.permission.MY_PERMISSION_STRING"" />
</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}" />
+ <item name="test.pkg.PermissionsTest void myMethod2()">
+ <annotation name="androidx.annotation.RequiresPermission">
+ <val name="anyOf" val="{"android.permission.MY_PERMISSION_STRING", "android.permission.MY_PERMISSION_STRING2"}" />
</annotation>
</item>
</root>
@@ -181,6 +347,7 @@
@Test
fun `Include merged annotations in exported source annotations`() {
check(
+ includeSourceRetentionAnnotations = true,
compatibilityMode = false,
outputKotlinStyleNulls = false,
includeSystemApiAnnotations = false,
@@ -205,7 +372,8 @@
</item>
</root>
""",
- extractAnnotations = mapOf("test.pkg" to """
+ extractAnnotations = mapOf(
+ "test.pkg" to """
<?xml version="1.0" encoding="UTF-8"?>
<root>
<item name="test.pkg.MyTest void test(int) 0">
@@ -218,4 +386,43 @@
)
)
}
+
+ @Test
+ fun `Only including class retention annotations in stubs`() {
+ check(
+ includeSourceRetentionAnnotations = false,
+ compatibilityMode = false,
+ outputKotlinStyleNulls = false,
+ includeSystemApiAnnotations = false,
+ omitCommonPackages = false,
+ sourceFiles =
+ *arrayOf(
+ java(
+ """
+ package test.pkg;
+ import android.annotation.IntRange;
+ import androidx.annotation.RecentlyNullable;
+ public class Test {
+ @RecentlyNullable
+ public static String sayHello(@IntRange(from = 10) int value) { return "hello " + value; }
+ }
+ """
+ ),
+ intRangeAnnotationSource,
+ recentlyNullableSource
+ ),
+ extractAnnotations = mapOf(
+ "test.pkg" to """
+ <?xml version="1.0" encoding="UTF-8"?>
+ <root>
+ <item name="test.pkg.Test java.lang.String sayHello(int) 0">
+ <annotation name="androidx.annotation.IntRange">
+ <val name="from" val="10" />
+ </annotation>
+ </item>
+ </root>
+ """
+ )
+ )
+ }
}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/NullnessMigrationTest.kt b/src/test/java/com/android/tools/metalava/NullnessMigrationTest.kt
index 0557168..6c1e2ef 100644
--- a/src/test/java/com/android/tools/metalava/NullnessMigrationTest.kt
+++ b/src/test/java/com/android/tools/metalava/NullnessMigrationTest.kt
@@ -16,6 +16,7 @@
package com.android.tools.metalava
+import com.android.tools.metalava.model.SUPPORT_TYPE_USE_ANNOTATIONS
import org.junit.Test
class NullnessMigrationTest : DriverTest() {
@@ -237,8 +238,8 @@
api = """
package libcore.util {
public @interface NonNull {
- method public abstract int from();
- method public abstract int to();
+ method public abstract int from() default java.lang.Integer.MIN_VALUE;
+ method public abstract int to() default java.lang.Integer.MAX_VALUE;
}
}
package test.pkg {
@@ -273,11 +274,12 @@
}
"""
),
- supportNonNullSource,
- supportNullableSource
+ androidxNonNullSource,
+ androidxNullableSource
),
extraArguments = arrayOf("--hide-package", "androidx.annotation"),
- api = """
+ api = if (SUPPORT_TYPE_USE_ANNOTATIONS) {
+ """
package test.pkg {
public class Test {
ctor public Test();
@@ -286,6 +288,17 @@
}
}
"""
+ } else {
+ """
+ package test.pkg {
+ public class Test {
+ ctor public Test();
+ method @Nullable public Integer compute1(@Nullable java.util.List<java.lang.String>);
+ method @Nullable public Integer compute2(@Nullable java.util.List<java.util.List<?>>);
+ }
+ }
+ """
+ }
)
}
@@ -315,7 +328,8 @@
androidxNullableSource
),
extraArguments = arrayOf("--hide-package", "androidx.annotation"),
- api = """
+ api = if (SUPPORT_TYPE_USE_ANNOTATIONS) {
+ """
package test.pkg {
public class Test {
ctor public Test();
@@ -324,6 +338,17 @@
}
}
"""
+ } else {
+ """
+ package test.pkg {
+ public class Test {
+ ctor public Test();
+ method @Nullable public Integer compute1(@Nullable java.util.List<java.lang.String>);
+ method @Nullable public Integer compute2(@NonNull java.util.List<java.util.List<?>>);
+ }
+ }
+ """
+ }
)
}
@@ -362,18 +387,33 @@
}
}
""",
- 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!"); }
- }
- """
- )
+ stubs = if (SUPPORT_TYPE_USE_ANNOTATIONS) {
+ 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!"); }
+ }
+ """
+ )
+ } else {
+ arrayOf(
+ """
+ package test.pkg;
+ @SuppressWarnings({"unchecked", "deprecation", "all"})
+ public class Foo {
+ public Foo() { throw new RuntimeException("Stub!"); }
+ public static char[] toChars(int codePoint) { throw new RuntimeException("Stub!"); }
+ public static int codePointAt(char[] a, int index) { throw new RuntimeException("Stub!"); }
+ public <T> T[] toArray(T[] a) { throw new RuntimeException("Stub!"); }
+ }
+ """
+ )
+ }
)
}
@@ -412,18 +452,33 @@
}
}
""",
- 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!"); }
- }
- """
- )
+ stubs = if (SUPPORT_TYPE_USE_ANNOTATIONS) {
+ 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!"); }
+ }
+ """
+ )
+ } else {
+ arrayOf(
+ """
+ package test.pkg;
+ @SuppressWarnings({"unchecked", "deprecation", "all"})
+ public class Foo {
+ public Foo() { throw new RuntimeException("Stub!"); }
+ public static char[] toChars(int codePoint) { throw new RuntimeException("Stub!"); }
+ public static int codePointAt(char[] a, int index) { throw new RuntimeException("Stub!"); }
+ public <T> T[] toArray(T[] 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 7c79caa..5497e8a 100644
--- a/src/test/java/com/android/tools/metalava/OptionsTest.kt
+++ b/src/test/java/com/android/tools/metalava/OptionsTest.kt
@@ -176,6 +176,15 @@
Extracting Annotations:
--extract-annotations <zipfile> Extracts source annotations from the source files
and writes them into the given zip file
+--include-annotation-classes <dir> Copies the given stub annotation source files into
+ the generated stub sources; <dir> is typically
+ metalava/stub-annotations/src/main/java/.
+--rewrite-annotations <dir/jar> For a bytecode folder or output jar, rewrites the
+ androidx annotations to be package private
+--include-source-retention If true, include source-retention annotations in
+ the stub files. Does not apply to signature files.
+ Source retention annotations are extracted into the
+ external annotations files instead.
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/RewriteAnnotationsTest.kt b/src/test/java/com/android/tools/metalava/RewriteAnnotationsTest.kt
new file mode 100644
index 0000000..1f96736
--- /dev/null
+++ b/src/test/java/com/android/tools/metalava/RewriteAnnotationsTest.kt
@@ -0,0 +1,147 @@
+/*
+ * 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.lint.checks.infrastructure.TestFiles.base64gzip
+import com.android.tools.lint.checks.infrastructure.TestFiles.jar
+import com.android.tools.lint.checks.infrastructure.TestFiles.xml
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import java.io.File
+import java.lang.reflect.Modifier
+import java.net.URLClassLoader
+
+class RewriteAnnotationsTest : DriverTest() {
+ @Test
+ fun `Test copying private annotations from one of the stubs`() {
+ val source = File("stub-annotations/src/main/java/androidx/annotation".replace('/', File.separatorChar))
+ assertTrue(source.path, source.isDirectory)
+ val target = temporaryFolder.newFolder()
+ runDriver(
+ "--no-color",
+ "--no-banner",
+
+ "--copy-annotations",
+ source.path,
+ target.path
+ )
+
+ val converted = File(target, "CallSuper.java")
+ assertTrue("${converted.path} doesn't exist", converted.isFile)
+ assertEquals(
+ """
+ /*
+ * 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})
+ @interface CallSuper {}
+ """.trimIndent().trim(), converted.readText(Charsets.UTF_8).trim().replace("\r\n", "\n")
+ )
+ }
+
+ @Test
+ fun `Test rewriting the bytecode for one of the public annotations`() {
+ val bytecode = base64gzip(
+ "androidx/annotation/CallSuper.class", "" +
+ "H4sIAAAAAAAAAIWPsU4CQRRF70NhEQWxJMZoLCjdxs6KIMYCA2E3NlbD8kKG" +
+ "DDNkmSXwaxZ+gB9FfGMBFps4yczc5J53kve9//wC8IirCK0IlxHahEbiijzj" +
+ "F22Y0OorY5JixfnDQm0UoTMprNdLftdrPTXcs9Z55bWza8LdMDCxUXYeq0MR" +
+ "T9izDemJUN0oU4i3+w86dkZnuzDQH/aShHBTPpCqfM5euPvyfmB4KcZ0t2KB" +
+ "am+D9HX0LDZlZ7nTs+1f9rAqoX2UjaYLzjzhttR/3L9LIFTkniCcCk5/3ypq" +
+ "8l9LiqSrM87QwHmIHyDGBZo/ObYRQoUBAAA="
+ )
+
+ val compiledStubs = temporaryFolder.newFolder("compiled-stubs")
+ bytecode.createFile(compiledStubs)
+
+ runDriver(
+ "--no-color",
+ "--no-banner",
+
+ "--rewrite-annotations",
+ compiledStubs.path
+ )
+
+ // Load the class to make sure it's legit
+ val url = compiledStubs.toURI().toURL()
+ val loader = URLClassLoader(arrayOf(url), null)
+ val annotationClass = loader.loadClass("androidx.annotation.CallSuper")
+ val modifiers = annotationClass.modifiers
+ assertEquals(0, modifiers and Modifier.PUBLIC)
+ assertTrue(annotationClass.isAnnotation)
+ }
+
+ @Test
+ fun `Test rewriting the bytecode for one of the public annotations in a jar file`() {
+ val bytecode = base64gzip(
+ "androidx/annotation/CallSuper.class", "" +
+ "H4sIAAAAAAAAAIWPsU4CQRRF70NhEQWxJMZoLCjdxs6KIMYCA2E3NlbD8kKG" +
+ "DDNkmSXwaxZ+gB9FfGMBFps4yczc5J53kve9//wC8IirCK0IlxHahEbiijzj" +
+ "F22Y0OorY5JixfnDQm0UoTMprNdLftdrPTXcs9Z55bWza8LdMDCxUXYeq0MR" +
+ "T9izDemJUN0oU4i3+w86dkZnuzDQH/aShHBTPpCqfM5euPvyfmB4KcZ0t2KB" +
+ "am+D9HX0LDZlZ7nTs+1f9rAqoX2UjaYLzjzhttR/3L9LIFTkniCcCk5/3ypq" +
+ "8l9LiqSrM87QwHmIHyDGBZo/ObYRQoUBAAA="
+ )
+
+ val jarDesc = jar(
+ "myjar.jar",
+ bytecode,
+ xml("foo/bar/baz.xml", "<hello-world/>")
+ )
+
+ val jarFile = jarDesc.createFile(temporaryFolder.root)
+
+ runDriver(
+ "--no-color",
+ "--no-banner",
+
+ "--rewrite-annotations",
+ jarFile.path
+ )
+
+ // Load the class to make sure it's legit
+ val url = jarFile.toURI().toURL()
+ val loader = URLClassLoader(arrayOf(url), null)
+ val annotationClass = loader.loadClass("androidx.annotation.CallSuper")
+ val modifiers = annotationClass.modifiers
+ assertEquals(0, modifiers and Modifier.PUBLIC)
+ assertTrue(annotationClass.isAnnotation)
+ }
+}
\ 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 5d7f7df..1edcbbf 100644
--- a/src/test/java/com/android/tools/metalava/StubsTest.kt
+++ b/src/test/java/com/android/tools/metalava/StubsTest.kt
@@ -19,6 +19,7 @@
package com.android.tools.metalava
import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.metalava.model.SUPPORT_TYPE_USE_ANNOTATIONS
import org.intellij.lang.annotations.Language
import org.junit.Test
@@ -36,6 +37,8 @@
extraArguments: Array<String> = emptyArray(),
docStubs: Boolean = false,
showAnnotations: Array<String> = emptyArray(),
+ includeSourceRetentionAnnotations: Boolean = true,
+ skipEmitPackages: List<String> = listOf("java.lang", "java.util", "java.io"),
vararg sourceFiles: TestFile
) {
check(
@@ -48,7 +51,9 @@
checkCompilation = true,
api = api,
extraArguments = extraArguments,
- docStubs = docStubs
+ docStubs = docStubs,
+ includeSourceRetentionAnnotations = includeSourceRetentionAnnotations,
+ skipEmitPackages = skipEmitPackages
)
}
@@ -304,7 +309,7 @@
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
- public @interface Foo {
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.LOCAL_VARIABLE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Foo {
public java.lang.String value();
}
"""
@@ -1345,16 +1350,28 @@
"""
)
),
- api = """
+ api = if (SUPPORT_TYPE_USE_ANNOTATIONS) {
+ """
package test.pkg {
public class Foo {
ctor public Foo();
method public void foo(int, java.util.Map<java.lang.String, java.lang.String>!);
}
}
- """,
+ """
+ } else {
+ """
+ package test.pkg {
+ public class Foo {
+ ctor public Foo();
+ method public void foo(int, java.util.Map<java.lang.String,java.lang.String>!);
+ }
+ }
+ """
+ },
- source = """
+ source = if (SUPPORT_TYPE_USE_ANNOTATIONS) {
+ """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Foo {
@@ -1362,6 +1379,16 @@
public void foo(int p1, java.util.Map<java.lang.String, java.lang.String> p2) { throw new RuntimeException("Stub!"); }
}
"""
+ } else {
+ """
+ package test.pkg;
+ @SuppressWarnings({"unchecked", "deprecation", "all"})
+ public class Foo {
+ public Foo() { throw new RuntimeException("Stub!"); }
+ public void foo(int p1, java.util.Map<java.lang.String,java.lang.String> p2) { throw new RuntimeException("Stub!"); }
+ }
+ """
+ }
)
}
@@ -1764,15 +1791,27 @@
}
}
""",
- stubs = arrayOf(
- """
- package my.pkg;
- @SuppressWarnings({"unchecked", "deprecation", "all"})
- public class String {
- public String(char @androidx.annotation.NonNull [] value) { throw new RuntimeException("Stub!"); }
- }
- """
- )
+ stubs = if (SUPPORT_TYPE_USE_ANNOTATIONS) {
+ arrayOf(
+ """
+ package my.pkg;
+ @SuppressWarnings({"unchecked", "deprecation", "all"})
+ public class String {
+ public String(char @androidx.annotation.NonNull [] value) { throw new RuntimeException("Stub!"); }
+ }
+ """
+ )
+ } else {
+ arrayOf(
+ """
+ package my.pkg;
+ @SuppressWarnings({"unchecked", "deprecation", "all"})
+ public class String {
+ public String(char[] value) { throw new RuntimeException("Stub!"); }
+ }
+ """
+ )
+ }
)
}
@@ -2013,28 +2052,53 @@
}
}
""",
- 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> {
- 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();
- }
- }
- """
+ source = if (SUPPORT_TYPE_USE_ANNOTATIONS) {
+ """
+ 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> {
+ 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();
+ }
+ }
+ """
+ } else {
+ """
+ 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> {
+ 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();
+ }
+ }
+ """
+ }
)
}
@@ -2292,8 +2356,8 @@
)
}
- // TODO: Add a protected constructor too to make sure my code to make non-public constructors package private
- // don't accidentally demote protected constructors to package private!
+// TODO: Add a protected constructor too to make sure my code to make non-public constructors package private
+// don't accidentally demote protected constructors to package private!
@Test
fun `Picking Super Constructors`() {
@@ -2959,6 +3023,208 @@
}
@Test
+ fun `Annotation default values`() {
+ checkStubs(
+ compatibilityMode = false,
+ sourceFiles =
+ *arrayOf(
+ java(
+ """
+ package test.pkg;
+
+ import java.lang.annotation.ElementType;
+ import java.lang.annotation.Retention;
+ import java.lang.annotation.RetentionPolicy;
+ import java.lang.annotation.Target;
+
+ import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+ /**
+ * This annotation can be used to mark fields and methods to be dumped by
+ * the view server. Only non-void methods with no arguments can be annotated
+ * by this annotation.
+ */
+ @Target({ElementType.FIELD, ElementType.METHOD})
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface ExportedProperty {
+ /**
+ * When resolveId is true, and if the annotated field/method return value
+ * is an int, the value is converted to an Android's resource name.
+ *
+ * @return true if the property's value must be transformed into an Android
+ * resource name, false otherwise
+ */
+ boolean resolveId() default false;
+ String prefix() default "";
+ String category() default "";
+ boolean formatToHexString() default false;
+ boolean hasAdjacentMapping() default false;
+ Class<? extends Number> myCls() default Integer.class;
+ char[] letters1() default {};
+ char[] letters2() default {'a', 'b', 'c'};
+ double from() default Double.NEGATIVE_INFINITY;
+ double fromWithCast() default (double)Float.NEGATIVE_INFINITY;
+ InnerAnnotation value() default @InnerAnnotation;
+ char letter() default 'a';
+ int integer() default 1;
+ long large_integer() default 1L;
+ float floating() default 1.0f;
+ double large_floating() default 1.0;
+ byte small() default 1;
+ short medium() default 1;
+ int math() default 1+2*3;
+ @InnerAnnotation
+ int unit() default PX;
+ int DP = 0;
+ int PX = 1;
+ int SP = 2;
+ @Retention(SOURCE)
+ @interface InnerAnnotation {
+ }
+ }
+ """
+ )
+ ),
+ warnings = "",
+ api = """
+ package test.pkg {
+ public @interface ExportedProperty {
+ method public abstract String! category() default "";
+ method public abstract float floating() default 1.0f;
+ method public abstract boolean formatToHexString() default false;
+ method public abstract double from() default java.lang.Double.NEGATIVE_INFINITY;
+ method public abstract double fromWithCast() default (double)java.lang.Float.NEGATIVE_INFINITY;
+ method public abstract boolean hasAdjacentMapping() default false;
+ method public abstract int integer() default 1;
+ method public abstract double large_floating() default 1.0;
+ method public abstract long large_integer() default 1;
+ method public abstract char letter() default 'a';
+ method public abstract char[]! letters1() default {};
+ method public abstract char[]! letters2() default {'a', 'b', 'c'};
+ method public abstract int math() default 7;
+ method public abstract short medium() default 1;
+ method public abstract Class<? extends java.lang.Number>! myCls() default java.lang.Integer.class;
+ method public abstract String! prefix() default "";
+ method public abstract boolean resolveId() default false;
+ method public abstract byte small() default 1;
+ method public abstract int unit() default test.pkg.ExportedProperty.PX;
+ method public abstract test.pkg.ExportedProperty.InnerAnnotation! value() default @test.pkg.ExportedProperty.InnerAnnotation;
+ field public static final int DP = 0; // 0x0
+ field public static final int PX = 1; // 0x1
+ field public static final int SP = 2; // 0x2
+ }
+ public static @interface ExportedProperty.InnerAnnotation {
+ }
+ }
+ """,
+ source = """
+ package test.pkg;
+ /**
+ * This annotation can be used to mark fields and methods to be dumped by
+ * the view server. Only non-void methods with no arguments can be annotated
+ * by this annotation.
+ */
+ @SuppressWarnings({"unchecked", "deprecation", "all"})
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) public @interface ExportedProperty {
+ /**
+ * When resolveId is true, and if the annotated field/method return value
+ * is an int, the value is converted to an Android's resource name.
+ *
+ * @return true if the property's value must be transformed into an Android
+ * resource name, false otherwise
+ */
+ public boolean resolveId() default false;
+ public java.lang.String prefix() default "";
+ public java.lang.String category() default "";
+ public boolean formatToHexString() default false;
+ public boolean hasAdjacentMapping() default false;
+ public java.lang.Class<? extends java.lang.Number> myCls() default java.lang.Integer.class;
+ public char[] letters1() default {};
+ public char[] letters2() default {'a', 'b', 'c'};
+ public double from() default java.lang.Double.NEGATIVE_INFINITY;
+ public double fromWithCast() default (double)java.lang.Float.NEGATIVE_INFINITY;
+ public test.pkg.ExportedProperty.InnerAnnotation value() default @test.pkg.ExportedProperty.InnerAnnotation;
+ public char letter() default 'a';
+ public int integer() default 1;
+ public long large_integer() default 1;
+ public float floating() default 1.0f;
+ public double large_floating() default 1.0;
+ public byte small() default 1;
+ public short medium() default 1;
+ public int math() default 7;
+ public int unit() default test.pkg.ExportedProperty.PX;
+ public static final int DP = 0; // 0x0
+ public static final int PX = 1; // 0x1
+ public static final int SP = 2; // 0x2
+ @SuppressWarnings({"unchecked", "deprecation", "all"})
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface InnerAnnotation {
+ }
+ }
+ """
+ )
+ }
+
+ @Test
+ fun `Annotation metadata in stubs`() {
+ checkStubs(
+ compatibilityMode = false,
+ includeSourceRetentionAnnotations = false,
+ skipEmitPackages = emptyList(),
+ sourceFiles =
+ *arrayOf(
+ java(
+ """
+ package java.lang;
+
+ import java.lang.annotation.*;
+
+ @Target(ElementType.METHOD)
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface MyAnnotation {
+ }
+ """
+ )
+ ),
+ warnings = "",
+ source = """
+ package java.lang;
+ @SuppressWarnings({"unchecked", "deprecation", "all"})
+ @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public @interface MyAnnotation {
+ }
+ """
+ )
+ }
+
+ @Test
+ fun `Functional Interfaces`() {
+ checkStubs(
+ compatibilityMode = false,
+ skipEmitPackages = emptyList(),
+ sourceFiles =
+ *arrayOf(
+ java(
+ """
+ package java.lang;
+
+ @SuppressWarnings("something") @FunctionalInterface
+ public interface MyInterface {
+ void run();
+ }
+ """
+ )
+ ),
+ warnings = "",
+ source = """
+ package java.lang;
+ @SuppressWarnings({"unchecked", "deprecation", "all"})
+ @java.lang.FunctionalInterface public interface MyInterface {
+ public void run();
+ }
+ """
+ )
+ }
+
+ @Test
fun `Check writing package info file`() {
checkStubs(
sourceFiles =
@@ -2979,7 +3245,7 @@
"""
),
- supportNullableSource
+ androidxNullableSource
),
warnings = "",
api = """
@@ -2997,10 +3263,10 @@
)
}
- // TODO: Add in some type variables in method signatures and constructors!
- // TODO: Test what happens when a class extends a hidden extends a public in separate packages,
- // and the hidden has a @hide constructor so the stub in the leaf class doesn't compile -- I should
- // check for this and fail build.
+// TODO: Add in some type variables in method signatures and constructors!
+// TODO: Test what happens when a class extends a hidden extends a public in separate packages,
+// and the hidden has a @hide constructor so the stub in the leaf class doesn't compile -- I should
+// check for this and fail build.
- // TODO: Test -stubPackages
+// TODO: Test -stubPackages
}
\ No newline at end of file
diff --git a/stub-annotations/src/main/java/androidx/annotation/NonNull.java b/stub-annotations/src/main/java/androidx/annotation/NonNull.java
index 3b41cc8..647ee60 100644
--- a/stub-annotations/src/main/java/androidx/annotation/NonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/NonNull.java
@@ -28,5 +28,5 @@
/** Stub only annotation. Do not use directly. */
@Retention(CLASS)
-@Target({METHOD, PARAMETER, FIELD, PACKAGE, TYPE_PARAMETER, TYPE_USE})
+@Target({METHOD, PARAMETER, FIELD, PACKAGE, TYPE_PARAMETER})
public @interface NonNull {}
diff --git a/stub-annotations/src/main/java/androidx/annotation/Nullable.java b/stub-annotations/src/main/java/androidx/annotation/Nullable.java
index 0e6abce..b9c630f 100644
--- a/stub-annotations/src/main/java/androidx/annotation/Nullable.java
+++ b/stub-annotations/src/main/java/androidx/annotation/Nullable.java
@@ -28,5 +28,5 @@
/** Stub only annotation. Do not use directly. */
@Retention(CLASS)
-@Target({METHOD, PARAMETER, FIELD, PACKAGE, TYPE_PARAMETER, TYPE_USE})
+@Target({METHOD, PARAMETER, FIELD, PACKAGE, TYPE_PARAMETER})
public @interface Nullable {}
diff --git a/stub-annotations/src/main/java/androidx/annotation/RecentlyNonNull.java b/stub-annotations/src/main/java/androidx/annotation/RecentlyNonNull.java
index 575b3eb..9ad4f5e 100644
--- a/stub-annotations/src/main/java/androidx/annotation/RecentlyNonNull.java
+++ b/stub-annotations/src/main/java/androidx/annotation/RecentlyNonNull.java
@@ -26,5 +26,5 @@
/** Stub only annotation. Do not use directly. */
@Retention(CLASS)
-@Target({METHOD, PARAMETER, FIELD, TYPE_USE})
+@Target({METHOD, PARAMETER, FIELD})
public @interface RecentlyNonNull {}
diff --git a/stub-annotations/src/main/java/androidx/annotation/RecentlyNullable.java b/stub-annotations/src/main/java/androidx/annotation/RecentlyNullable.java
index 8a9141e..8ad2a2d 100644
--- a/stub-annotations/src/main/java/androidx/annotation/RecentlyNullable.java
+++ b/stub-annotations/src/main/java/androidx/annotation/RecentlyNullable.java
@@ -26,5 +26,5 @@
/** Stub only annotation. Do not use directly. */
@Retention(CLASS)
-@Target({METHOD, PARAMETER, FIELD, TYPE_USE})
+@Target({METHOD, PARAMETER, FIELD})
public @interface RecentlyNullable {}