blob: 390a2fc29045f32a85acbaa5366ebaf86175bc24 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("ALL")
package com.android.tools.metalava.stub
import com.android.tools.lint.checks.infrastructure.LintDetectorTest.source
import com.android.tools.lint.checks.infrastructure.TestFile
import com.android.tools.metalava.ARG_CHECK_API
import com.android.tools.metalava.ARG_EXCLUDE_ALL_ANNOTATIONS
import com.android.tools.metalava.ARG_EXCLUDE_ANNOTATION
import com.android.tools.metalava.ARG_EXCLUDE_DOCUMENTATION_FROM_STUBS
import com.android.tools.metalava.ARG_HIDE_PACKAGE
import com.android.tools.metalava.ARG_KOTLIN_STUBS
import com.android.tools.metalava.ARG_PASS_THROUGH_ANNOTATION
import com.android.tools.metalava.ARG_UPDATE_API
import com.android.tools.metalava.DriverTest
import com.android.tools.metalava.FileFormat
import com.android.tools.metalava.androidxNullableSource
import com.android.tools.metalava.extractRoots
import com.android.tools.metalava.gatherSources
import com.android.tools.metalava.intDefAnnotationSource
import com.android.tools.metalava.intRangeAnnotationSource
import com.android.tools.metalava.libcoreNonNullSource
import com.android.tools.metalava.model.SUPPORT_TYPE_USE_ANNOTATIONS
import com.android.tools.metalava.requiresApiSource
import com.android.tools.metalava.requiresPermissionSource
import com.android.tools.metalava.restrictToSource
import com.android.tools.metalava.supportParameterName
import org.intellij.lang.annotations.Language
import org.junit.Test
import java.io.File
import java.io.FileNotFoundException
import kotlin.test.assertEquals
@SuppressWarnings("ALL")
class StubsTest : DriverTest() {
// TODO: test fields that need initialization
// TODO: test @DocOnly handling
private fun checkStubs(
@Language("JAVA") source: String,
compatibilityMode: Boolean = false,
warnings: String? = "",
api: String? = null,
extraArguments: Array<String> = emptyArray(),
docStubs: Boolean = false,
showAnnotations: Array<String> = emptyArray(),
includeSourceRetentionAnnotations: Boolean = true,
skipEmitPackages: List<String> = listOf("java.lang", "java.util", "java.io"),
format: FileFormat? = null,
sourceFiles: Array<TestFile>
) {
check(
sourceFiles = sourceFiles,
showAnnotations = showAnnotations,
stubFiles = arrayOf(java(source)),
compatibilityMode = compatibilityMode,
expectedIssues = warnings,
checkCompilation = true,
api = api,
extraArguments = extraArguments,
docStubs = docStubs,
includeSourceRetentionAnnotations = includeSourceRetentionAnnotations,
skipEmitPackages = skipEmitPackages,
format = format
)
}
@Test
fun `Generate stubs for basic class`() {
checkStubs(
sourceFiles = arrayOf(
java(
"""
/*
* This is the copyright header.
*/
package test.pkg;
/** This is the documentation for the class */
@SuppressWarnings("ALL")
public class Foo {
private int hidden;
/** My field doc */
protected static final String field = "a\nb\n\"test\"";
/**
* Method documentation.
* Maybe it spans
* multiple lines.
*/
protected static void onCreate(String parameter1) {
// This is not in the stub
System.out.println(parameter1);
}
static {
System.out.println("Not included in stub");
}
}
"""
)
),
source = """
/*
* This is the copyright header.
*/
package test.pkg;
/** This is the documentation for the class */
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Foo {
public Foo() { throw new RuntimeException("Stub!"); }
/**
* Method documentation.
* Maybe it spans
* multiple lines.
*/
protected static void onCreate(java.lang.String parameter1) { throw new RuntimeException("Stub!"); }
/** My field doc */
protected static final java.lang.String field = "a\nb\n\"test\"";
}
"""
)
}
@Test
fun `Generate stubs for generics`() {
// Basic interface with generics; makes sure <T extends Object> is written as just <T>
// Also include some more complex generics expressions to make sure they're serialized
// correctly (in particular, using fully qualified names instead of what appears in
// the source code.)
check(
checkCompilation = true,
sourceFiles = arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings("ALL")
public interface MyInterface2<T extends Number>
extends MyBaseInterface {
class TtsSpan<C extends MyInterface<?>> { }
abstract class Range<T extends Comparable<? super T>> { }
}
"""
),
java(
"""
package test.pkg;
@SuppressWarnings("ALL")
public interface MyInterface<T extends Object>
extends MyBaseInterface {
}
"""
),
java(
"""
package test.pkg;
public interface MyBaseInterface {
}
"""
)
),
expectedIssues = "",
stubFiles = arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public interface MyInterface2<T extends java.lang.Number> extends test.pkg.MyBaseInterface {
@SuppressWarnings({"unchecked", "deprecation", "all"})
public abstract static class Range<T extends java.lang.Comparable<? super T>> {
public Range() { throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public static class TtsSpan<C extends test.pkg.MyInterface<?>> {
public TtsSpan() { throw new RuntimeException("Stub!"); }
}
}
"""
),
java(
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public interface MyInterface<T> extends test.pkg.MyBaseInterface {
}
"""
),
java(
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public interface MyBaseInterface {
}
"""
)
)
)
}
@Test
fun `Generate stubs for class that should not get default constructor (has other constructors)`() {
// Class without explicit constructors (shouldn't insert default constructor)
checkStubs(
sourceFiles = arrayOf(
java(
"""
package test.pkg;
public class Foo {
public Foo(int i) {
}
public Foo(int i, int j) {
}
}
"""
)
),
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Foo {
public Foo(int i) { throw new RuntimeException("Stub!"); }
public Foo(int i, int j) { throw new RuntimeException("Stub!"); }
}
"""
)
}
@Test
fun `Generate stubs for class that already has a private constructor`() {
// Class without private constructor; no default constructor should be inserted
checkStubs(
sourceFiles = arrayOf(
java(
"""
package test.pkg;
public class Foo {
private Foo() {
}
}
"""
)
),
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Foo {
private Foo() { throw new RuntimeException("Stub!"); }
}
"""
)
}
@Test
fun `Generate stubs for interface class`() {
// Interface: makes sure the right modifiers etc are shown (and that "package private" methods
// in the interface are taken to be public etc)
checkStubs(
sourceFiles = arrayOf(
java(
"""
package test.pkg;
public interface Foo {
void foo();
}
"""
)
),
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public interface Foo {
public void foo();
}
"""
)
}
@Test
fun `Generate stubs for enum`() {
// Interface: makes sure the right modifiers etc are shown (and that "package private" methods
// in the interface are taken to be public etc)
checkStubs(
sourceFiles = arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings("ALL")
public enum Foo {
A, /** @deprecated */ @Deprecated B;
}
"""
)
),
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public enum Foo {
A,
/** @deprecated */
@Deprecated
B;
}
"""
)
}
@Test
fun `Generate stubs for annotation type`() {
// Interface: makes sure the right modifiers etc are shown (and that "package private" methods
// in the interface are taken to be public etc)
checkStubs(
sourceFiles = arrayOf(
java(
"""
package test.pkg;
import static java.lang.annotation.ElementType.*;
import java.lang.annotation.*;
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.CLASS)
public @interface Foo {
String value();
}
"""
)
),
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS)
@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})
public @interface Foo {
public java.lang.String value();
}
"""
)
}
@Test
fun `Generate stubs for class with superclass`() {
// Make sure superclass statement is correct; unlike signature files, inherited method from parent
// that has same signature should be included in the child
checkStubs(
sourceFiles = arrayOf(
java(
"""
package test.pkg;
public class Foo extends Super {
@Override public void base() { }
public void child() { }
}
"""
), java(
"""
package test.pkg;
public class Super {
public void base() { }
}
"""
)
),
source =
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Foo extends test.pkg.Super {
public Foo() { throw new RuntimeException("Stub!"); }
public void base() { throw new RuntimeException("Stub!"); }
public void child() { throw new RuntimeException("Stub!"); }
}
"""
)
}
@Test
fun `Generate stubs for fields with initial values`() {
checkStubs(
sourceFiles = arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings("ALL")
public class Foo {
private int hidden = 1;
int hidden2 = 2;
/** @hide */
int hidden3 = 3;
protected int field00; // No value
public static final boolean field01 = true;
public static final int field02 = 42;
public static final long field03 = 42L;
public static final short field04 = 5;
public static final byte field05 = 5;
public static final char field06 = 'c';
public static final float field07 = 98.5f;
public static final double field08 = 98.5;
public static final String field09 = "String with \"escapes\" and \u00a9...";
public static final double field10 = Double.NaN;
public static final double field11 = Double.POSITIVE_INFINITY;
public static final String GOOD_IRI_CHAR = "a-zA-Z0-9\u00a0-\ud7ff\uf900-\ufdcf\ufdf0-\uffef";
public static final char HEX_INPUT = 61184;
}
"""
)
),
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Foo {
public Foo() { throw new RuntimeException("Stub!"); }
public static final java.lang.String GOOD_IRI_CHAR = "a-zA-Z0-9\u00a0-\ud7ff\uf900-\ufdcf\ufdf0-\uffef";
public static final char HEX_INPUT = 61184; // 0xef00 '\uef00'
protected int field00;
public static final boolean field01 = true;
public static final int field02 = 42; // 0x2a
public static final long field03 = 42L; // 0x2aL
public static final short field04 = 5; // 0x5
public static final byte field05 = 5; // 0x5
public static final char field06 = 99; // 0x0063 'c'
public static final float field07 = 98.5f;
public static final double field08 = 98.5;
public static final java.lang.String field09 = "String with \"escapes\" and \u00a9...";
public static final double field10 = (0.0/0.0);
public static final double field11 = (1.0/0.0);
}
"""
)
}
@Test
fun `Generate stubs for various modifier scenarios`() {
// 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.
checkStubs(
compatibilityMode = true,
warnings = null,
sourceFiles = arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings("ALL")
public abstract class Foo {
/** @deprecated */ @Deprecated private static final long field1 = 5;
/** @deprecated */ @Deprecated private static volatile long field2 = 5;
/** @deprecated */ @Deprecated public static strictfp final synchronized void method1() { }
/** @deprecated */ @Deprecated public static final synchronized native void method2();
/** @deprecated */ @Deprecated protected static final class Inner1 { }
/** @deprecated */ @Deprecated protected static abstract class Inner2 { }
/** @deprecated */ @Deprecated protected interface Inner3 {
protected default void method3() { }
static void method4() { }
}
}
"""
)
),
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public abstract class Foo {
public Foo() { throw new RuntimeException("Stub!"); }
/** @deprecated */
@Deprecated
public static final synchronized void method1() { throw new RuntimeException("Stub!"); }
/** @deprecated */
@Deprecated
public static final synchronized native void method2();
/** @deprecated */
@SuppressWarnings({"unchecked", "deprecation", "all"})
@Deprecated
protected static final class Inner1 {
@Deprecated
protected Inner1() { throw new RuntimeException("Stub!"); }
}
/** @deprecated */
@SuppressWarnings({"unchecked", "deprecation", "all"})
@Deprecated
protected abstract static class Inner2 {
@Deprecated
protected Inner2() { throw new RuntimeException("Stub!"); }
}
/** @deprecated */
@SuppressWarnings({"unchecked", "deprecation", "all"})
@Deprecated
protected static interface Inner3 {
@Deprecated
public default void method3() { throw new RuntimeException("Stub!"); }
@Deprecated
public static void method4() { throw new RuntimeException("Stub!"); }
}
}
"""
)
}
@Test
fun `Generate stubs for class with abstract enum methods`() {
// As per https://bugs.openjdk.java.net/browse/JDK-6287639
// abstract methods in enums should not be listed as abstract,
// but doclava1 does, so replicate this.
// Also checks that we handle both enum fields and regular fields
// and that they are listed separately.
checkStubs(
sourceFiles = arrayOf(
java(
"""
package test.pkg;
public enum FooBar {
/** My 1st documentation */
ABC {
@Override
protected void foo() { }
},
/** My 2nd documentation */
DEF {
@Override
protected void foo() { }
};
protected abstract void foo();
public static int field1 = 1;
public int field2 = 2;
}
"""
)
),
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public enum FooBar {
/** My 1st documentation */
ABC,
/** My 2nd documentation */
DEF;
protected void foo() { throw new RuntimeException("Stub!"); }
public static int field1 = 1; // 0x1
public int field2 = 2; // 0x2
}
"""
)
}
@Test
fun `Skip hidden enum constants in stubs`() {
checkStubs(
compatibilityMode = true,
sourceFiles = arrayOf(
java(
"""
package test.pkg;
public enum Alignment {
ALIGN_NORMAL,
ALIGN_OPPOSITE,
ALIGN_CENTER,
/** @hide */
ALIGN_LEFT,
/** @hide */
ALIGN_RIGHT
}
"""
)
),
api = """
package test.pkg {
public enum Alignment {
enum_constant public static final test.pkg.Alignment ALIGN_CENTER;
enum_constant public static final test.pkg.Alignment ALIGN_NORMAL;
enum_constant public static final test.pkg.Alignment ALIGN_OPPOSITE;
}
}
""",
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public enum Alignment {
ALIGN_NORMAL,
ALIGN_OPPOSITE,
ALIGN_CENTER;
}
"""
)
}
@Test
fun `Check correct throws list for generics`() {
checkStubs(
sourceFiles = arrayOf(
java(
"""
package test.pkg;
import java.util.function.Supplier;
@SuppressWarnings("RedundantThrows")
public final class Test<T> {
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
return null;
}
}
"""
)
),
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public final class Test<T> {
public Test() { throw new RuntimeException("Stub!"); }
public <X extends java.lang.Throwable> T orElseThrow(java.util.function.Supplier<? extends X> exceptionSupplier) throws X { throw new RuntimeException("Stub!"); }
}
"""
)
}
@Test
fun `Generate stubs for additional generics scenarios`() {
// Some additional declarations where PSI default type handling diffs from doclava1
checkStubs(
sourceFiles = arrayOf(
java(
"""
package test.pkg;
public abstract class Collections {
public static <T extends java.lang.Object & java.lang.Comparable<? super T>> T max(java.util.Collection<? extends T> collection) {
return null;
}
public abstract <T extends java.util.Collection<java.lang.String>> T addAllTo(T t);
public final class Range<T extends java.lang.Comparable<? super T>> { }
}
"""
)
),
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public abstract class Collections {
public Collections() { throw new RuntimeException("Stub!"); }
public static <T extends java.lang.Object & java.lang.Comparable<? super T>> T max(java.util.Collection<? extends T> collection) { throw new RuntimeException("Stub!"); }
public abstract <T extends java.util.Collection<java.lang.String>> T addAllTo(T t);
@SuppressWarnings({"unchecked", "deprecation", "all"})
public final class Range<T extends java.lang.Comparable<? super T>> {
public Range() { throw new RuntimeException("Stub!"); }
}
}
"""
)
}
@Test
fun `Generate stubs for even more generics scenarios`() {
// Some additional declarations where PSI default type handling diffs from doclava1
checkStubs(
sourceFiles = arrayOf(
java(
"""
package test.pkg;
import java.util.Set;
@SuppressWarnings("ALL")
public class MoreAsserts {
public static void assertEquals(String arg0, Set<? extends Object> arg1, Set<? extends Object> arg2) { }
public static void assertEquals(Set<? extends Object> arg1, Set<? extends Object> arg2) { }
}
"""
)
),
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class MoreAsserts {
public MoreAsserts() { throw new RuntimeException("Stub!"); }
public static void assertEquals(java.lang.String arg0, java.util.Set<?> arg1, java.util.Set<?> arg2) { throw new RuntimeException("Stub!"); }
public static void assertEquals(java.util.Set<?> arg1, java.util.Set<?> arg2) { throw new RuntimeException("Stub!"); }
}
"""
)
}
@Test
fun `Generate stubs enum instance methods`() {
checkStubs(
sourceFiles = arrayOf(
java(
"""
package test.pkg;
public enum ChronUnit implements TempUnit {
C(1), B(2), A(3);
ChronUnit(int y) {
}
public String valueOf(int x) {
return Integer.toString(x + 5);
}
public String values(String separator) {
return null;
}
@Override
public String toString() {
return name();
}
}
"""
), java(
"""
package test.pkg;
public interface TempUnit {
@Override
String toString();
}
"""
)
),
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public enum ChronUnit implements test.pkg.TempUnit {
C,
B,
A;
public java.lang.String valueOf(int x) { throw new RuntimeException("Stub!"); }
public java.lang.String values(java.lang.String separator) { throw new RuntimeException("Stub!"); }
public java.lang.String toString() { throw new RuntimeException("Stub!"); }
}
"""
)
}
@Test
fun `Generate stubs with superclass filtering`() {
checkStubs(
sourceFiles = arrayOf(
java(
"""
package test.pkg;
public class MyClass extends HiddenParent {
public void method4() { }
}
"""
), java(
"""
package test.pkg;
/** @hide */
@SuppressWarnings("ALL")
public class HiddenParent extends HiddenParent2 {
public static final String CONSTANT = "MyConstant";
protected int mContext;
public void method3() { }
// Static: should be included
public static void method3b() { }
// References hidden type: don't inherit
public void method3c(HiddenParent p) { }
// References hidden type: don't inherit
public void method3d(java.util.List<HiddenParent> p) { }
}
"""
), java(
"""
package test.pkg;
/** @hide */
@SuppressWarnings("ALL")
public class HiddenParent2 extends PublicParent {
public void method2() { }
}
"""
), java(
"""
package test.pkg;
public class PublicParent {
public void method1() { }
}
"""
)
),
// Notice how the intermediate methods (method2, method3) have been removed
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class MyClass extends test.pkg.PublicParent {
public MyClass() { throw new RuntimeException("Stub!"); }
public void method4() { throw new RuntimeException("Stub!"); }
public static void method3b() { throw new RuntimeException("Stub!"); }
public void method2() { throw new RuntimeException("Stub!"); }
public void method3() { throw new RuntimeException("Stub!"); }
public static final java.lang.String CONSTANT = "MyConstant";
}
""",
warnings = """
src/test/pkg/MyClass.java:2: warning: Public class test.pkg.MyClass stripped of unavailable superclass test.pkg.HiddenParent [HiddenSuperclass]
"""
)
}
@Test
fun `Check inheriting from package private class`() {
checkStubs(
// Note that doclava1 includes fields here that it doesn't include in the
// signature file.
// checkDoclava1 = true,
compatibilityMode = false,
sourceFiles = arrayOf(
java(
"""
package test.pkg;
public class MyClass extends HiddenParent {
public void method1() { }
}
"""
), java(
"""
package test.pkg;
class HiddenParent {
public static final String CONSTANT = "MyConstant";
protected int mContext;
public void method2() { }
}
"""
)
),
warnings = "",
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class MyClass {
public MyClass() { throw new RuntimeException("Stub!"); }
public void method1() { throw new RuntimeException("Stub!"); }
public void method2() { throw new RuntimeException("Stub!"); }
public static final java.lang.String CONSTANT = "MyConstant";
}
"""
)
}
@Test
fun `Check implementing a package private interface`() {
// If you implement a package private interface, we just remove it and inline the members into
// the subclass
// BUG: Note that we need to implement the parent
checkStubs(
compatibilityMode = true,
sourceFiles = arrayOf(
java(
"""
package test.pkg;
public class MyClass implements HiddenInterface {
@Override public void method() { }
@Override public void other() { }
}
"""
), java(
"""
package test.pkg;
public interface OtherInterface {
void other();
}
"""
), java(
"""
package test.pkg;
interface HiddenInterface extends OtherInterface {
void method() { }
String CONSTANT = "MyConstant";
}
"""
)
),
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class MyClass implements test.pkg.OtherInterface {
public MyClass() { throw new RuntimeException("Stub!"); }
public void method() { throw new RuntimeException("Stub!"); }
public void other() { throw new RuntimeException("Stub!"); }
public static final java.lang.String CONSTANT = "MyConstant";
}
"""
)
}
@Test
fun `Check throws list`() {
// Make sure we format a throws list
checkStubs(
sourceFiles = arrayOf(
java(
"""
package test.pkg;
import java.io.IOException;
@SuppressWarnings("RedundantThrows")
public abstract class AbstractCursor {
@Override protected void finalize1() throws Throwable { }
@Override protected void finalize2() throws IOException, IllegalArgumentException { }
}
"""
)
),
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public abstract class AbstractCursor {
public AbstractCursor() { throw new RuntimeException("Stub!"); }
protected void finalize1() throws java.lang.Throwable { throw new RuntimeException("Stub!"); }
protected void finalize2() throws java.io.IOException, java.lang.IllegalArgumentException { throw new RuntimeException("Stub!"); }
}
"""
)
}
@Test
fun `Check generating constants in interface without inline-able initializers`() {
checkStubs(
sourceFiles = arrayOf(
java(
"""
package test.pkg;
public interface MyClass {
String[] CONSTANT1 = {"MyConstant","MyConstant2"};
boolean CONSTANT2 = Boolean.getBoolean(System.getenv("VAR1"));
int CONSTANT3 = Integer.parseInt(System.getenv("VAR2"));
String CONSTANT4 = null;
}
"""
)
),
warnings = "",
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public interface MyClass {
public static final java.lang.String[] CONSTANT1 = null;
public static final boolean CONSTANT2 = false;
public static final int CONSTANT3 = 0; // 0x0
public static final java.lang.String CONSTANT4 = null;
}
"""
)
}
@Test
fun `Handle non-constant fields in final classes`() {
checkStubs(
sourceFiles = arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings("all")
public class FinalFieldTest {
public interface TemporalField {
String getBaseUnit();
}
public static final class IsoFields {
public static final TemporalField DAY_OF_QUARTER = Field.DAY_OF_QUARTER;
private IsoFields() {
throw new AssertionError("Not instantiable");
}
private static enum Field implements TemporalField {
DAY_OF_QUARTER {
@Override
public String getBaseUnit() {
return "days";
}
}
};
}
}
"""
)
),
warnings = "",
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class FinalFieldTest {
public FinalFieldTest() { throw new RuntimeException("Stub!"); }
@SuppressWarnings({"unchecked", "deprecation", "all"})
public static final class IsoFields {
private IsoFields() { throw new RuntimeException("Stub!"); }
public static final test.pkg.FinalFieldTest.TemporalField DAY_OF_QUARTER;
static { DAY_OF_QUARTER = null; }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public static interface TemporalField {
public java.lang.String getBaseUnit();
}
}
"""
)
}
@Test
fun `Test final instance fields`() {
// Instance fields in a class must be initialized
checkStubs(
sourceFiles = arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings("all")
public class InstanceFieldTest {
public static final class WindowLayout {
public WindowLayout(int width, int height, int gravity) {
this.width = width;
this.height = height;
this.gravity = gravity;
}
public final int width;
public final int height;
public final int gravity;
}
}
"""
)
),
warnings = "",
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class InstanceFieldTest {
public InstanceFieldTest() { throw new RuntimeException("Stub!"); }
@SuppressWarnings({"unchecked", "deprecation", "all"})
public static final class WindowLayout {
public WindowLayout(int width, int height, int gravity) { throw new RuntimeException("Stub!"); }
public final int gravity;
{ gravity = 0; }
public final int height;
{ height = 0; }
public final int width;
{ width = 0; }
}
}
"""
)
}
@Test
fun `Check generating constants in class without inline-able initializers`() {
checkStubs(
sourceFiles = arrayOf(
java(
"""
package test.pkg;
public class MyClass {
public static String[] CONSTANT1 = {"MyConstant","MyConstant2"};
public static boolean CONSTANT2 = Boolean.getBoolean(System.getenv("VAR1"));
public static int CONSTANT3 = Integer.parseInt(System.getenv("VAR2"));
public static String CONSTANT4 = null;
}
"""
)
),
warnings = "",
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class MyClass {
public MyClass() { throw new RuntimeException("Stub!"); }
public static java.lang.String[] CONSTANT1;
public static boolean CONSTANT2;
public static int CONSTANT3;
public static java.lang.String CONSTANT4;
}
"""
)
}
@Test
fun `Check generating annotation source`() {
checkStubs(
sourceFiles = arrayOf(
java(
"""
package android.view.View;
import android.annotation.IntDef;
import android.annotation.IntRange;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
public class View {
@SuppressWarnings("all")
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/** @hide */
@SuppressWarnings("all")
@IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
@Retention(RetentionPolicy.SOURCE)
public @interface MeasureSpecMode {}
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
return 0;
}
}
}
"""
),
intDefAnnotationSource,
intRangeAnnotationSource
),
warnings = "",
source = """
package android.view.View;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class View {
public View() { throw new RuntimeException("Stub!"); }
@SuppressWarnings({"unchecked", "deprecation", "all"})
public static class MeasureSpec {
public MeasureSpec() { throw new RuntimeException("Stub!"); }
public static int makeMeasureSpec(@androidx.annotation.IntRange(from=0, to=0x40000000 - 1) int size, int mode) { throw new RuntimeException("Stub!"); }
public static final int AT_MOST = -2147483648; // 0x80000000
public static final int EXACTLY = 1073741824; // 0x40000000
public static final int UNSPECIFIED = 0; // 0x0
}
}
"""
)
}
@Test
fun `Check generating classes with generics`() {
checkStubs(
sourceFiles = arrayOf(
java(
"""
package test.pkg;
public class Generics {
public <T> Generics(int surfaceSize, Class<T> klass) {
}
}
"""
)
),
warnings = "",
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Generics {
public <T> Generics(int surfaceSize, java.lang.Class<T> klass) { throw new RuntimeException("Stub!"); }
}
"""
)
}
@Test
fun `Check generating annotation for hidden constants`() {
checkStubs(
sourceFiles = arrayOf(
java(
"""
package test.pkg;
import android.content.Intent;
import android.annotation.RequiresPermission;
public abstract class HiddenPermission {
@RequiresPermission(allOf = {
android.Manifest.permission.INTERACT_ACROSS_USERS,
android.Manifest.permission.BROADCAST_STICKY
})
public abstract void removeStickyBroadcast(@RequiresPermission Object intent);
}
"""
),
java(
"""
package android;
public final class Manifest {
@SuppressWarnings("JavaDoc")
public static final class permission {
public static final String BROADCAST_STICKY = "android.permission.BROADCAST_STICKY";
/** @SystemApi @hide Allows an application to call APIs that allow it to do interactions
across the users on the device, using singleton services and
user-targeted broadcasts. This permission is not available to
third party applications. */
public static final String INTERACT_ACROSS_USERS = "android.permission.INTERACT_ACROSS_USERS";
}
}
"""
),
requiresPermissionSource
),
warnings = "",
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public abstract class HiddenPermission {
public HiddenPermission() { throw new RuntimeException("Stub!"); }
@androidx.annotation.RequiresPermission(allOf={"android.permission.INTERACT_ACROSS_USERS", android.Manifest.permission.BROADCAST_STICKY})
public abstract void removeStickyBroadcast(@androidx.annotation.RequiresPermission java.lang.Object intent);
}
"""
)
}
@Test
fun `Check generating type parameters in interface list`() {
checkStubs(
sourceFiles = arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings("NullableProblems")
public class GenericsInInterfaces<T> implements Comparable<GenericsInInterfaces> {
@Override
public int compareTo(GenericsInInterfaces o) {
return 0;
}
void foo(T bar) {
}
}
"""
)
),
api = """
package test.pkg {
public class GenericsInInterfaces<T> implements java.lang.Comparable<test.pkg.GenericsInInterfaces> {
ctor public GenericsInInterfaces();
method public int compareTo(test.pkg.GenericsInInterfaces);
}
}
""",
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class GenericsInInterfaces<T> implements java.lang.Comparable<test.pkg.GenericsInInterfaces> {
public GenericsInInterfaces() { throw new RuntimeException("Stub!"); }
public int compareTo(test.pkg.GenericsInInterfaces o) { throw new RuntimeException("Stub!"); }
}
"""
)
}
@Test
fun `Preserve file header comments`() {
checkStubs(
sourceFiles = arrayOf(
java(
"""
/*
My header 1
*/
/*
My header 2
*/
// My third comment
package test.pkg;
public class HeaderComments {
}
"""
)
),
source = """
/*
My header 1
*/
/*
My header 2
*/
// My third comment
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class HeaderComments {
public HeaderComments() { throw new RuntimeException("Stub!"); }
}
"""
)
}
@Test
fun `Basic Kotlin class`() {
checkStubs(
sourceFiles = arrayOf(
kotlin(
"""
/* My file header */
// Another comment
@file:JvmName("Driver")
package test.pkg
/** My class doc */
class Kotlin(val property1: String = "Default Value", arg2: Int) : Parent() {
override fun method() = "Hello World"
/** My method doc */
fun otherMethod(ok: Boolean, times: Int) {
}
/** property doc */
var property2: String? = null
/** @hide */
var hiddenProperty: String? = "hidden"
private var someField = 42
@JvmField
var someField2 = 42
}
open class Parent {
open fun method(): String? = null
open fun method2(value1: Boolean, value2: Boolean?): String? = null
open fun method3(value1: Int?, value2: Int): Int = null
}
"""
)
),
source = """
/* My file header */
// Another comment
package test.pkg;
/** My class doc */
@SuppressWarnings({"unchecked", "deprecation", "all"})
public final class Kotlin extends test.pkg.Parent {
public Kotlin(@android.annotation.NonNull java.lang.String property1, int arg2) { throw new RuntimeException("Stub!"); }
@android.annotation.NonNull
public java.lang.String method() { throw new RuntimeException("Stub!"); }
/** My method doc */
public void otherMethod(boolean ok, int times) { throw new RuntimeException("Stub!"); }
/** property doc */
@android.annotation.Nullable
public java.lang.String getProperty2() { throw new RuntimeException("Stub!"); }
/** property doc */
public void setProperty2(@android.annotation.Nullable java.lang.String property2) { throw new RuntimeException("Stub!"); }
@android.annotation.NonNull
public java.lang.String getProperty1() { throw new RuntimeException("Stub!"); }
public int someField2;
}
"""
)
}
@Test
fun `Parameter Names in Java`() {
// Java code which explicitly specifies parameter names: make sure stub uses
// parameter name
checkStubs(
sourceFiles = arrayOf(
java(
"""
package test.pkg;
import androidx.annotation.ParameterName;
public class Foo {
public void foo(int javaParameter1, @ParameterName("publicParameterName") int javaParameter2) {
}
}
"""
),
supportParameterName
),
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Foo {
public Foo() { throw new RuntimeException("Stub!"); }
public void foo(int javaParameter1, int publicParameterName) { throw new RuntimeException("Stub!"); }
}
"""
)
}
@Test
fun `Remove Hidden Annotations`() {
// When APIs reference annotations that are hidden, make sure the're excluded from the stubs and
// signature files
checkStubs(
compatibilityMode = false,
sourceFiles = arrayOf(
java(
"""
package test.pkg;
public class Foo {
public void foo(int p1, @MyAnnotation("publicParameterName") java.util.Map<java.lang.String, @MyAnnotation("Something") String> p2) {
}
}
"""
),
java(
"""
package test.pkg;
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.SOURCE;
/** @hide */
@SuppressWarnings("WeakerAccess")
@Retention(SOURCE)
@Target({METHOD, PARAMETER, FIELD, TYPE_USE})
public @interface MyAnnotation {
String value();
}
"""
)
),
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 = if (SUPPORT_TYPE_USE_ANNOTATIONS) {
"""
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!"); }
}
"""
} 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!"); }
}
"""
}
)
}
@Test
fun `Arguments to super constructors`() {
// When overriding constructors we have to supply arguments
checkStubs(
sourceFiles = arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings("WeakerAccess")
public class Constructors {
public class Parent {
public Parent(String s, int i, long l, boolean b, short sh) {
}
}
public class Child extends Parent {
public Child(String s, int i, long l, boolean b, short sh) {
super(s, i, l, b, sh);
}
private Child(String s) {
super(s, 0, 0, false, 0);
}
}
public class Child2 extends Parent {
Child2(String s) {
super(s, 0, 0, false, 0);
}
}
public class Child3 extends Child2 {
private Child3(String s) {
super("something");
}
}
public class Child4 extends Parent {
Child4(String s, HiddenClass hidden) {
super(s, 0, 0, true, 0);
}
}
/** @hide */
public class HiddenClass {
}
}
"""
)
),
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Constructors {
public Constructors() { throw new RuntimeException("Stub!"); }
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Child extends test.pkg.Constructors.Parent {
public Child(java.lang.String s, int i, long l, boolean b, short sh) { super(null, 0, 0, false, (short)0); throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Child2 extends test.pkg.Constructors.Parent {
Child2() { super(null, 0, 0, false, (short)0); throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Child3 extends test.pkg.Constructors.Child2 {
private Child3() { throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Child4 extends test.pkg.Constructors.Parent {
Child4() { super(null, 0, 0, false, (short)0); throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Parent {
public Parent(java.lang.String s, int i, long l, boolean b, short sh) { throw new RuntimeException("Stub!"); }
}
}
"""
)
}
@Test
fun `Arguments to super constructors with showAnnotations`() {
// When overriding constructors we have to supply arguments
checkStubs(
showAnnotations = arrayOf("android.annotation.SystemApi"),
sourceFiles = arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings("WeakerAccess")
public class Constructors {
public class Parent {
public Parent(String s, int i, long l, boolean b, short sh) {
}
}
public class Child extends Parent {
public Child(String s, int i, long l, boolean b, short sh) {
super(s, i, l, b, sh);
}
private Child(String s) {
super(s, 0, 0, false, 0);
}
}
public class Child2 extends Parent {
Child2(String s) {
super(s, 0, 0, false, 0);
}
}
public class Child3 extends Child2 {
private Child3(String s) {
super("something");
}
}
public class Child4 extends Parent {
Child4(String s, HiddenClass hidden) {
super(s, 0, 0, true, 0);
}
}
/** @hide */
public class HiddenClass {
}
}
"""
)
),
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Constructors {
public Constructors() { throw new RuntimeException("Stub!"); }
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Child extends test.pkg.Constructors.Parent {
public Child(java.lang.String s, int i, long l, boolean b, short sh) { super(null, 0, 0, false, (short)0); throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Child2 extends test.pkg.Constructors.Parent {
Child2() { super(null, 0, 0, false, (short)0); throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Child3 extends test.pkg.Constructors.Child2 {
private Child3() { throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Child4 extends test.pkg.Constructors.Parent {
Child4() { super(null, 0, 0, false, (short)0); throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Parent {
public Parent(java.lang.String s, int i, long l, boolean b, short sh) { throw new RuntimeException("Stub!"); }
}
}
"""
)
}
// TODO: Add test to see what happens if I have Child4 in a different package which can't access the package private constructor of child3?
@Test
fun `DocOnly members should be omitted`() {
// When marked @doconly don't include in stubs or signature files
// unless specifically asked for (which we do when generating docs-stubs).
checkStubs(
sourceFiles = arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings("JavaDoc")
public class Outer {
/** @doconly Some docs here */
public class MyClass1 {
public int myField;
}
public class MyClass2 {
/** @doconly Some docs here */
public int myField;
/** @doconly Some docs here */
public int myMethod() { return 0; }
}
}
"""
)
),
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Outer {
public Outer() { throw new RuntimeException("Stub!"); }
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class MyClass2 {
public MyClass2() { throw new RuntimeException("Stub!"); }
}
}
""",
api = """
package test.pkg {
public class Outer {
ctor public Outer();
}
public class Outer.MyClass2 {
ctor public Outer.MyClass2();
}
}
"""
)
}
@Test
fun `DocOnly members should be included when requested`() {
// When marked @doconly don't include in stubs or signature files
// unless specifically asked for (which we do when generating docs).
checkStubs(
docStubs = true,
sourceFiles = arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings("JavaDoc")
public class Outer {
/** @doconly Some docs here */
public class MyClass1 {
public int myField;
}
public class MyClass2 {
/** @doconly Some docs here */
public int myField;
/** @doconly Some docs here */
public int myMethod() { return 0; }
}
}
"""
)
),
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Outer {
public Outer() { throw new RuntimeException("Stub!"); }
/** @doconly Some docs here */
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class MyClass1 {
public MyClass1() { throw new RuntimeException("Stub!"); }
public int myField;
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class MyClass2 {
public MyClass2() { throw new RuntimeException("Stub!"); }
/** @doconly Some docs here */
public int myMethod() { throw new RuntimeException("Stub!"); }
/** @doconly Some docs here */
public int myField;
}
}
"""
)
}
@Test
fun `Check generating required stubs from hidden super classes and interfaces`() {
checkStubs(
compatibilityMode = false,
sourceFiles = arrayOf(
java(
"""
package test.pkg;
public class MyClass extends HiddenSuperClass implements HiddenInterface, PublicInterface2 {
public void myMethod() { }
@Override public void publicInterfaceMethod2() { }
}
"""
),
java(
"""
package test.pkg;
class HiddenSuperClass extends PublicSuperParent {
@Override public void inheritedMethod2() { }
@Override public void publicInterfaceMethod() { }
@Override public void publicMethod() {}
@Override public void publicMethod2() {}
}
"""
),
java(
"""
package test.pkg;
public abstract class PublicSuperParent {
public void inheritedMethod1() {}
public void inheritedMethod2() {}
public abstract void publicMethod() {}
}
"""
),
java(
"""
package test.pkg;
interface HiddenInterface extends PublicInterface {
int MY_CONSTANT = 5;
void hiddenInterfaceMethod();
}
"""
),
java(
"""
package test.pkg;
public interface PublicInterface {
void publicInterfaceMethod();
}
"""
),
java(
"""
package test.pkg;
public interface PublicInterface2 {
void publicInterfaceMethod2();
}
"""
)
),
warnings = "",
api = """
package test.pkg {
public class MyClass extends test.pkg.PublicSuperParent implements test.pkg.PublicInterface test.pkg.PublicInterface2 {
ctor public MyClass();
method public void myMethod();
method public void publicInterfaceMethod();
method public void publicInterfaceMethod2();
method public void publicMethod();
method public void publicMethod2();
field public static final int MY_CONSTANT = 5; // 0x5
}
public interface PublicInterface {
method public void publicInterfaceMethod();
}
public interface PublicInterface2 {
method public void publicInterfaceMethod2();
}
public abstract class PublicSuperParent {
ctor public PublicSuperParent();
method public void inheritedMethod1();
method public void inheritedMethod2();
method public abstract void publicMethod();
}
}
""",
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class MyClass extends test.pkg.PublicSuperParent implements test.pkg.PublicInterface, test.pkg.PublicInterface2 {
public MyClass() { throw new RuntimeException("Stub!"); }
public void myMethod() { throw new RuntimeException("Stub!"); }
public void publicInterfaceMethod2() { throw new RuntimeException("Stub!"); }
public void publicMethod() { throw new RuntimeException("Stub!"); }
public void publicMethod2() { throw new RuntimeException("Stub!"); }
public void publicInterfaceMethod() { throw new RuntimeException("Stub!"); }
public void inheritedMethod2() { throw new RuntimeException("Stub!"); }
public static final int MY_CONSTANT = 5; // 0x5
}
"""
)
}
@Test
fun `Rewrite unknown nullability annotations as sdk stubs`() {
check(
compatibilityMode = true,
checkCompilation = true,
sourceFiles = arrayOf(
java(
"package my.pkg;\n" +
"public class String {\n" +
"public String(@other.NonNull char[] value) { throw new RuntimeException(\"Stub!\"); }\n" +
"}\n"
)
),
expectedIssues = "",
api = """
package my.pkg {
public class String {
ctor public String(@NonNull char[]);
}
}
""",
stubFiles = arrayOf(
java(
"""
package my.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class String {
public String(@android.annotation.NonNull char[] value) { throw new RuntimeException("Stub!"); }
}
"""
)
)
)
}
@Test
fun `Rewrite unknown nullability annotations as doc stubs`() {
check(
compatibilityMode = true,
checkCompilation = true,
sourceFiles = arrayOf(
java(
"package my.pkg;\n" +
"public class String {\n" +
"public String(@other.NonNull char[] value) { throw new RuntimeException(\"Stub!\"); }\n" +
"}\n"
)
),
expectedIssues = "",
api = """
package my.pkg {
public class String {
ctor public String(@NonNull char[]);
}
}
""",
docStubs = true,
stubFiles = arrayOf(
java(
"""
package my.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class String {
public String(@androidx.annotation.NonNull char[] value) { throw new RuntimeException("Stub!"); }
}
"""
)
)
)
}
@Test
fun `Rewrite libcore annotations`() {
check(
checkCompilation = true,
sourceFiles = arrayOf(
java(
"package my.pkg;\n" +
"public class String {\n" +
"public String(char @libcore.util.NonNull [] value) { throw new RuntimeException(\"Stub!\"); }\n" +
"}\n"
)
),
expectedIssues = "",
api = """
package my.pkg {
public class String {
ctor public String(char[]);
}
}
""",
stubFiles = if (SUPPORT_TYPE_USE_ANNOTATIONS) {
arrayOf(
java(
"""
package my.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class String {
public String(char @androidx.annotation.NonNull [] value) { throw new RuntimeException("Stub!"); }
}
"""
)
)
} else {
arrayOf(
java(
"""
package my.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class String {
public String(char[] value) { throw new RuntimeException("Stub!"); }
}
"""
)
)
}
)
}
@Test
fun `Pass through libcore annotations`() {
check(
compatibilityMode = true,
checkCompilation = true,
extraArguments = arrayOf(
ARG_PASS_THROUGH_ANNOTATION, "libcore.util.NonNull"
),
sourceFiles = arrayOf(
java(
"""
package my.pkg;
public class String {
public String(@libcore.util.NonNull char[] value) { throw new RuntimeException("Stub!"); }
}
"""
),
libcoreNonNullSource
),
expectedIssues = "",
api = """
package libcore.util {
@java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE_USE}) public @interface NonNull {
}
}
package my.pkg {
public class String {
ctor public String(@libcore.util.NonNull char[]);
}
}
""",
stubFiles = arrayOf(
java(
"""
package my.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class String {
public String(@libcore.util.NonNull char[] value) { throw new RuntimeException("Stub!"); }
}
"""
)
)
)
}
@Test
fun `Pass through multiple annotations`() {
checkStubs(
extraArguments = arrayOf(
ARG_PASS_THROUGH_ANNOTATION, "androidx.annotation.RequiresApi,androidx.annotation.Nullable",
ARG_HIDE_PACKAGE, "androidx.annotation"
),
sourceFiles = arrayOf(
java(
"""
package my.pkg;
public class MyClass {
@androidx.annotation.RequiresApi(21)
public void testMethod() {}
@androidx.annotation.Nullable
public String anotherTestMethod() { return null; }
}
"""
),
supportParameterName,
requiresApiSource,
androidxNullableSource
),
source = """
package my.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class MyClass {
public MyClass() { throw new RuntimeException("Stub!"); }
@androidx.annotation.RequiresApi(21)
public void testMethod() { throw new RuntimeException("Stub!"); }
@androidx.annotation.Nullable
public java.lang.String anotherTestMethod() { throw new RuntimeException("Stub!"); }
}
"""
)
}
@Test
fun `Skip RequiresApi annotation`() {
check(
compatibilityMode = true,
extraArguments = arrayOf(
ARG_EXCLUDE_ANNOTATION, "androidx.annotation.RequiresApi"
),
sourceFiles = arrayOf(
java(
"""
package my.pkg;
public class MyClass {
@androidx.annotation.RequiresApi(21)
public void testMethod() {}
}
"""
),
requiresApiSource
),
expectedIssues = "",
api = """
package androidx.annotation {
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR}) public @interface RequiresApi {
method public abstract int api() default 1;
method public abstract int value() default 1;
}
}
package my.pkg {
public class MyClass {
ctor public MyClass();
method public void testMethod();
}
}
""",
stubFiles = arrayOf(
java(
"""
package my.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class MyClass {
public MyClass() { throw new RuntimeException("Stub!"); }
public void testMethod() { throw new RuntimeException("Stub!"); }
}
"""
)
)
)
}
@Test
fun `Test inaccessible constructors`() {
// If the constructors of a class are not visible, and the class has subclasses,
// those subclass stubs will need to reference these inaccessible constructors.
// This generally only happens when the constructors are package private (and
// therefore hidden) but the subclass using it is also in the same package.
check(
checkCompilation = true,
sourceFiles = arrayOf(
java(
"""
package test.pkg;
public class MyClass1 {
MyClass1(int myVar) { }
}
"""
),
java(
"""
package test.pkg;
import java.io.IOException;
@SuppressWarnings("RedundantThrows")
public class MySubClass1 extends MyClass1 {
MySubClass1(int myVar) throws IOException { super(myVar); }
}
"""
),
java(
"""
package test.pkg;
public class MyClass2 {
/** @hide */
public MyClass2(int myVar) { }
}
"""
),
java(
"""
package test.pkg;
public class MySubClass2 extends MyClass2 {
public MySubClass2() { super(5); }
}
"""
)
),
expectedIssues = "",
api = """
package test.pkg {
public class MyClass1 {
}
public class MyClass2 {
}
public class MySubClass1 extends test.pkg.MyClass1 {
}
public class MySubClass2 extends test.pkg.MyClass2 {
ctor public MySubClass2();
}
}
""",
stubFiles = arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class MyClass1 {
MyClass1() { throw new RuntimeException("Stub!"); }
}
"""
),
java(
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class MySubClass1 extends test.pkg.MyClass1 {
MySubClass1() { throw new RuntimeException("Stub!"); }
}
"""
),
java(
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class MyClass2 {
MyClass2() { throw new RuntimeException("Stub!"); }
}
"""
),
java(
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class MySubClass2 extends test.pkg.MyClass2 {
public MySubClass2() { throw new RuntimeException("Stub!"); }
}
"""
)
),
stubsSourceList = """
TESTROOT/stubs/test/pkg/MyClass1.java
TESTROOT/stubs/test/pkg/MyClass2.java
TESTROOT/stubs/test/pkg/MySubClass1.java
TESTROOT/stubs/test/pkg/MySubClass2.java
"""
)
}
@Test
fun `Generics Variable Rewriting`() {
// When we move methods from hidden superclasses into the subclass since they
// provide the implementation for a required method, it's possible that the
// method we copied in is referencing generics with a different variable than
// in the current class, so we need to handle this
checkStubs(
sourceFiles = arrayOf(
// TODO: Try using prefixes like "A", and "AA" to make sure my generics
// variable renaming doesn't do something really unexpected
java(
"""
package test.pkg;
import java.util.List;
import java.util.Map;
public class Generics {
public class MyClass<X extends Number,Y> extends HiddenParent<X,Y> implements PublicParent<X,Y> {
}
public class MyClass2<W> extends HiddenParent<Float,W> implements PublicParent<Float, W> {
}
public class MyClass3 extends HiddenParent<Float,Double> implements PublicParent<Float,Double> {
}
class HiddenParent<M, N> extends HiddenParent2<M, N> {
}
class HiddenParent2<T, TT> {
public Map<T,Map<TT, String>> createMap(List<T> list) {
return null;
}
}
public interface PublicParent<A extends Number,B> {
Map<A,Map<B, String>> createMap(List<A> list);
}
}
"""
)
),
warnings = "",
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 extends java.lang.Number, Y> implements test.pkg.Generics.PublicParent<X,Y> {
public MyClass() { throw new RuntimeException("Stub!"); }
public java.util.Map<X,java.util.Map<Y,java.lang.String>> createMap(java.util.List<X> list) { throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class MyClass2<W> implements test.pkg.Generics.PublicParent<java.lang.Float,W> {
public MyClass2() { throw new RuntimeException("Stub!"); }
public java.util.Map<java.lang.Float,java.util.Map<W,java.lang.String>> createMap(java.util.List<java.lang.Float> list) { throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class MyClass3 implements test.pkg.Generics.PublicParent<java.lang.Float,java.lang.Double> {
public MyClass3() { throw new RuntimeException("Stub!"); }
public java.util.Map<java.lang.Float,java.util.Map<java.lang.Double,java.lang.String>> createMap(java.util.List<java.lang.Float> list) { throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public static interface PublicParent<A extends java.lang.Number, B> {
public java.util.Map<A,java.util.Map<B,java.lang.String>> createMap(java.util.List<A> list);
}
}
"""
)
}
@Test
fun `Rewriting type parameters in interfaces from hidden super classes and in throws lists`() {
checkStubs(
format = FileFormat.V1,
sourceFiles = arrayOf(
java(
"""
package test.pkg;
import java.io.IOException;
import java.util.List;
import java.util.Map;
@SuppressWarnings({"RedundantThrows", "WeakerAccess"})
public class Generics {
public class MyClass<X, Y extends Number> extends HiddenParent<X, Y> implements PublicInterface<X, Y> {
}
class HiddenParent<M, N extends Number> extends PublicParent<M, N> {
public Map<M, Map<N, String>> createMap(List<M> list) throws MyThrowable {
return null;
}
protected List<M> foo() {
return null;
}
}
class MyThrowable extends IOException {
}
public abstract class PublicParent<A, B extends Number> {
protected abstract List<A> foo();
}
public interface PublicInterface<A, B> {
Map<A, Map<B, String>> createMap(List<A> list) throws IOException;
}
}
"""
)
),
warnings = "",
api = """
package test.pkg {
public class Generics {
ctor public Generics();
}
public class Generics.MyClass<X, Y extends java.lang.Number> extends test.pkg.Generics.PublicParent<X,Y> implements test.pkg.Generics.PublicInterface<X,Y> {
ctor public Generics.MyClass();
method public java.util.Map<X,java.util.Map<Y,java.lang.String>> createMap(java.util.List<X>) throws java.io.IOException;
method public java.util.List<X> foo();
}
public static interface Generics.PublicInterface<A, B> {
method public java.util.Map<A,java.util.Map<B,java.lang.String>> createMap(java.util.List<A>) throws java.io.IOException;
}
public abstract class Generics.PublicParent<A, B extends java.lang.Number> {
ctor public Generics.PublicParent();
method protected abstract java.util.List<A> foo();
}
}
""",
source = 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();
}
}
"""
}
)
}
@Test
fun `Picking super class throwables`() {
// Like previous test, but without compatibility mode: ensures that we
// use super classes of filtered throwables
checkStubs(
format = FileFormat.V3,
sourceFiles = arrayOf(
java(
"""
package test.pkg;
import java.io.IOException;
import java.util.List;
import java.util.Map;
@SuppressWarnings({"RedundantThrows", "WeakerAccess"})
public class Generics {
public class MyClass<X, Y extends Number> extends HiddenParent<X, Y> implements PublicInterface<X, Y> {
}
class HiddenParent<M, N extends Number> extends PublicParent<M, N> {
public Map<M, Map<N, String>> createMap(List<M> list) throws MyThrowable {
return null;
}
protected List<M> foo() {
return null;
}
}
class MyThrowable extends IOException {
}
public abstract class PublicParent<A, B extends Number> {
protected abstract List<A> foo();
}
public interface PublicInterface<A, B> {
Map<A, Map<B, String>> createMap(List<A> list) throws IOException;
}
}
"""
)
),
warnings = "",
api = """
// Signature format: 3.0
package test.pkg {
public class Generics {
ctor public Generics();
}
public class Generics.MyClass<X, Y extends java.lang.Number> extends test.pkg.Generics.PublicParent<X,Y> implements test.pkg.Generics.PublicInterface<X,Y> {
ctor public Generics.MyClass();
method public java.util.Map<X!,java.util.Map<Y!,java.lang.String!>!>! createMap(java.util.List<X!>!) throws java.io.IOException;
method public java.util.List<X!>! foo();
}
public static interface Generics.PublicInterface<A, B> {
method public java.util.Map<A!,java.util.Map<B!,java.lang.String!>!>! createMap(java.util.List<A!>!) throws java.io.IOException;
}
public abstract class Generics.PublicParent<A, B extends java.lang.Number> {
ctor public Generics.PublicParent();
method protected abstract java.util.List<A!>! foo();
}
}
""",
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Generics {
public Generics() { throw new RuntimeException("Stub!"); }
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class MyClass<X, Y extends java.lang.Number> extends test.pkg.Generics.PublicParent<X,Y> implements test.pkg.Generics.PublicInterface<X,Y> {
public MyClass() { throw new RuntimeException("Stub!"); }
public java.util.List<X> foo() { throw new RuntimeException("Stub!"); }
public java.util.Map<X,java.util.Map<Y,java.lang.String>> createMap(java.util.List<X> list) throws java.io.IOException { throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public static interface PublicInterface<A, B> {
public java.util.Map<A,java.util.Map<B,java.lang.String>> createMap(java.util.List<A> list) throws java.io.IOException;
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public abstract class PublicParent<A, B extends java.lang.Number> {
public PublicParent() { throw new RuntimeException("Stub!"); }
protected abstract java.util.List<A> foo();
}
}
"""
)
}
@Test
fun `Rewriting implements class references`() {
// Checks some more subtle bugs around generics type variable renaming
checkStubs(
compatibilityMode = true,
sourceFiles = arrayOf(
java(
"""
package test.pkg;
import java.util.Collection;
import java.util.Set;
@SuppressWarnings("all")
public class ConcurrentHashMap<K, V> {
public abstract static class KeySetView<K, V> extends CollectionView<K, V, K>
implements Set<K>, java.io.Serializable {
}
abstract static class CollectionView<K, V, E>
implements Collection<E>, java.io.Serializable {
public final Object[] toArray() { return null; }
public final <T> T[] toArray(T[] a) {
return null;
}
@Override
public int size() {
return 0;
}
}
}
"""
)
),
warnings = "",
api = """
package test.pkg {
public class ConcurrentHashMap<K, V> {
ctor public ConcurrentHashMap();
}
public abstract static class ConcurrentHashMap.KeySetView<K, V> implements java.util.Collection<K> java.io.Serializable java.util.Set<K> {
ctor public ConcurrentHashMap.KeySetView();
method public int size();
method public final Object[] toArray();
method public final <T> T[] toArray(T[]);
}
}
""",
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class ConcurrentHashMap<K, V> {
public ConcurrentHashMap() { throw new RuntimeException("Stub!"); }
@SuppressWarnings({"unchecked", "deprecation", "all"})
public abstract static class KeySetView<K, V> implements java.util.Collection<K>, java.io.Serializable, java.util.Set<K> {
public KeySetView() { throw new RuntimeException("Stub!"); }
public int size() { throw new RuntimeException("Stub!"); }
public final java.lang.Object[] toArray() { throw new RuntimeException("Stub!"); }
public final <T> T[] toArray(T[] a) { throw new RuntimeException("Stub!"); }
}
}
"""
)
}
@Test
fun `Arrays in type arguments`() {
checkStubs(
sourceFiles = arrayOf(
java(
"""
package test.pkg;
public class Generics2 {
public class FloatArrayEvaluator implements TypeEvaluator<float[]> {
}
@SuppressWarnings("WeakerAccess")
public interface TypeEvaluator<T> {
}
}
"""
)
),
warnings = "",
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Generics2 {
public Generics2() { throw new RuntimeException("Stub!"); }
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class FloatArrayEvaluator implements test.pkg.Generics2.TypeEvaluator<float[]> {
public FloatArrayEvaluator() { throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public static interface TypeEvaluator<T> {
}
}
"""
)
}
@Test
fun `Interface extending multiple interfaces`() {
// Ensure that we handle sorting correctly where we're mixing super classes and implementing
// interfaces
// Real-world example: XmlResourceParser
check(
checkCompilation = true,
sourceFiles = arrayOf(
java(
"""
package android.content.res;
import android.util.AttributeSet;
import org.xmlpull.v1.XmlPullParser;
@SuppressWarnings("UnnecessaryInterfaceModifier")
public interface XmlResourceParser extends XmlPullParser, AttributeSet, AutoCloseable {
public void close();
}
"""
),
java(
"""
package android.util;
public interface AttributeSet {
}
"""
),
java(
"""
package java.lang;
public interface AutoCloseable {
}
"""
),
java(
"""
package org.xmlpull.v1;
public interface XmlPullParser {
}
"""
)
),
stubFiles = arrayOf(
java(
"""
package android.content.res;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public interface XmlResourceParser extends org.xmlpull.v1.XmlPullParser, android.util.AttributeSet, java.lang.AutoCloseable {
public void close();
}
"""
)
)
)
}
// 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`() {
checkStubs(
compatibilityMode = true,
sourceFiles = arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings({"RedundantThrows", "JavaDoc", "WeakerAccess"})
public class PickConstructors {
public abstract static class FileInputStream extends InputStream {
public FileInputStream(String name) throws FileNotFoundException {
}
public FileInputStream(File file) throws FileNotFoundException {
}
public FileInputStream(FileDescriptor fdObj) {
this(fdObj, false /* isFdOwner */);
}
/**
* @hide
*/
public FileInputStream(FileDescriptor fdObj, boolean isFdOwner) {
}
}
public abstract static class AutoCloseInputStream extends FileInputStream {
public AutoCloseInputStream(ParcelFileDescriptor pfd) {
super(pfd.getFileDescriptor());
}
}
abstract static class HiddenParentStream extends FileInputStream {
public HiddenParentStream(FileDescriptor pfd) {
super(pfd);
}
}
public abstract static class AutoCloseInputStream2 extends HiddenParentStream {
public AutoCloseInputStream2(ParcelFileDescriptor pfd) {
super(pfd.getFileDescriptor());
}
}
public abstract class ParcelFileDescriptor implements Closeable {
public abstract FileDescriptor getFileDescriptor();
}
@SuppressWarnings("UnnecessaryInterfaceModifier")
public static interface Closeable extends AutoCloseable {
}
@SuppressWarnings("UnnecessaryInterfaceModifier")
public static interface AutoCloseable {
}
public static abstract class InputStream implements Closeable {
}
public static class File {
}
public static final class FileDescriptor {
}
public static class FileNotFoundException extends IOException {
}
public static class IOException extends Exception {
}
}
"""
)
),
warnings = "",
api = """
package test.pkg {
public class PickConstructors {
ctor public PickConstructors();
}
public abstract static class PickConstructors.AutoCloseInputStream extends test.pkg.PickConstructors.FileInputStream {
ctor public PickConstructors.AutoCloseInputStream(test.pkg.PickConstructors.ParcelFileDescriptor);
}
public abstract static class PickConstructors.AutoCloseInputStream2 extends test.pkg.PickConstructors.FileInputStream {
ctor public PickConstructors.AutoCloseInputStream2(test.pkg.PickConstructors.ParcelFileDescriptor);
}
public static interface PickConstructors.AutoCloseable {
}
public static interface PickConstructors.Closeable extends test.pkg.PickConstructors.AutoCloseable {
}
public static class PickConstructors.File {
ctor public PickConstructors.File();
}
public static final class PickConstructors.FileDescriptor {
ctor public PickConstructors.FileDescriptor();
}
public abstract static class PickConstructors.FileInputStream extends test.pkg.PickConstructors.InputStream {
ctor public PickConstructors.FileInputStream(String) throws test.pkg.PickConstructors.FileNotFoundException;
ctor public PickConstructors.FileInputStream(test.pkg.PickConstructors.File) throws test.pkg.PickConstructors.FileNotFoundException;
ctor public PickConstructors.FileInputStream(test.pkg.PickConstructors.FileDescriptor);
}
public static class PickConstructors.FileNotFoundException extends test.pkg.PickConstructors.IOException {
ctor public PickConstructors.FileNotFoundException();
}
public static class PickConstructors.IOException extends java.lang.Exception {
ctor public PickConstructors.IOException();
}
public abstract static class PickConstructors.InputStream implements test.pkg.PickConstructors.Closeable {
ctor public PickConstructors.InputStream();
}
public abstract class PickConstructors.ParcelFileDescriptor implements test.pkg.PickConstructors.Closeable {
ctor public PickConstructors.ParcelFileDescriptor();
method public abstract test.pkg.PickConstructors.FileDescriptor getFileDescriptor();
}
}
""",
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class PickConstructors {
public PickConstructors() { throw new RuntimeException("Stub!"); }
@SuppressWarnings({"unchecked", "deprecation", "all"})
public abstract static class AutoCloseInputStream extends test.pkg.PickConstructors.FileInputStream {
public AutoCloseInputStream(test.pkg.PickConstructors.ParcelFileDescriptor pfd) { super((test.pkg.PickConstructors.FileDescriptor)null); throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public abstract static class AutoCloseInputStream2 extends test.pkg.PickConstructors.FileInputStream {
public AutoCloseInputStream2(test.pkg.PickConstructors.ParcelFileDescriptor pfd) { super((test.pkg.PickConstructors.FileDescriptor)null); throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public static interface AutoCloseable {
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public static interface Closeable extends test.pkg.PickConstructors.AutoCloseable {
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public static class File {
public File() { throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public static final class FileDescriptor {
public FileDescriptor() { throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public abstract static class FileInputStream extends test.pkg.PickConstructors.InputStream {
public FileInputStream(java.lang.String name) throws test.pkg.PickConstructors.FileNotFoundException { throw new RuntimeException("Stub!"); }
public FileInputStream(test.pkg.PickConstructors.File file) throws test.pkg.PickConstructors.FileNotFoundException { throw new RuntimeException("Stub!"); }
public FileInputStream(test.pkg.PickConstructors.FileDescriptor fdObj) { throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public static class FileNotFoundException extends test.pkg.PickConstructors.IOException {
public FileNotFoundException() { throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public static class IOException extends java.lang.Exception {
public IOException() { throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public abstract static class InputStream implements test.pkg.PickConstructors.Closeable {
public InputStream() { throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public abstract class ParcelFileDescriptor implements test.pkg.PickConstructors.Closeable {
public ParcelFileDescriptor() { throw new RuntimeException("Stub!"); }
public abstract test.pkg.PickConstructors.FileDescriptor getFileDescriptor();
}
}
"""
)
}
@Test
fun `Picking Constructors`() {
checkStubs(
sourceFiles = arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings({"WeakerAccess", "unused"})
public class Constructors2 {
public class TestSuite implements Test {
public TestSuite() {
}
public TestSuite(final Class<?> theClass) {
}
public TestSuite(Class<? extends TestCase> theClass, String name) {
this(theClass);
}
public TestSuite(String name) {
}
public TestSuite(Class<?>... classes) {
}
public TestSuite(Class<? extends TestCase>[] classes, String name) {
this(classes);
}
}
public class TestCase {
}
public interface Test {
}
public class Parent {
public Parent(int x) throws IOException {
}
}
class Intermediate extends Parent {
Intermediate(int x) throws IOException { super(x); }
}
public class Child extends Intermediate {
public Child() throws IOException { super(5); }
public Child(float x) throws IOException { this(); }
}
// ----------------------------------------------------
public abstract class DrawableWrapper {
public DrawableWrapper(Drawable dr) {
}
DrawableWrapper(Clipstate state, Object resources) {
}
}
public class ClipDrawable extends DrawableWrapper {
ClipDrawable() {
this(null);
}
public ClipDrawable(Drawable drawable, int gravity, int orientation) { this(null); }
private ClipDrawable(Clipstate clipstate) {
super(clipstate, null);
}
}
public class Drawable {
}
class Clipstate {
}
}
"""
)
),
warnings = "",
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Constructors2 {
public Constructors2() { throw new RuntimeException("Stub!"); }
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Child extends test.pkg.Constructors2.Parent {
public Child() { super(0); throw new RuntimeException("Stub!"); }
public Child(float x) { super(0); throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class ClipDrawable extends test.pkg.Constructors2.DrawableWrapper {
public ClipDrawable(test.pkg.Constructors2.Drawable drawable, int gravity, int orientation) { super(null); throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Drawable {
public Drawable() { throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public abstract class DrawableWrapper {
public DrawableWrapper(test.pkg.Constructors2.Drawable dr) { throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Parent {
public Parent(int x) { throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public static interface Test {
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class TestCase {
public TestCase() { throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class TestSuite implements test.pkg.Constructors2.Test {
public TestSuite() { throw new RuntimeException("Stub!"); }
public TestSuite(java.lang.Class<?> theClass) { throw new RuntimeException("Stub!"); }
public TestSuite(java.lang.Class<? extends test.pkg.Constructors2.TestCase> theClass, java.lang.String name) { throw new RuntimeException("Stub!"); }
public TestSuite(java.lang.String name) { throw new RuntimeException("Stub!"); }
public TestSuite(java.lang.Class<?>... classes) { throw new RuntimeException("Stub!"); }
public TestSuite(java.lang.Class<? extends test.pkg.Constructors2.TestCase>[] classes, java.lang.String name) { throw new RuntimeException("Stub!"); }
}
}
"""
)
}
@Test
fun `Another Constructor Test`() {
// A specific scenario triggered in the API where the right super class detector was not chosen
checkStubs(
sourceFiles = arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings({"RedundantThrows", "JavaDoc", "WeakerAccess"})
public class PickConstructors2 {
public interface EventListener {
}
public interface PropertyChangeListener extends EventListener {
}
public static abstract class EventListenerProxy<T extends EventListener>
implements EventListener {
public EventListenerProxy(T listener) {
}
}
public static class PropertyChangeListenerProxy
extends EventListenerProxy<PropertyChangeListener>
implements PropertyChangeListener {
public PropertyChangeListenerProxy(String propertyName, PropertyChangeListener listener) {
super(listener);
}
}
}
"""
)
),
warnings = "",
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class PickConstructors2 {
public PickConstructors2() { throw new RuntimeException("Stub!"); }
@SuppressWarnings({"unchecked", "deprecation", "all"})
public static interface EventListener {
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public abstract static class EventListenerProxy<T extends test.pkg.PickConstructors2.EventListener> implements test.pkg.PickConstructors2.EventListener {
public EventListenerProxy(T listener) { throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public static interface PropertyChangeListener extends test.pkg.PickConstructors2.EventListener {
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public static class PropertyChangeListenerProxy extends test.pkg.PickConstructors2.EventListenerProxy<test.pkg.PickConstructors2.PropertyChangeListener> implements test.pkg.PickConstructors2.PropertyChangeListener {
public PropertyChangeListenerProxy(java.lang.String propertyName, test.pkg.PickConstructors2.PropertyChangeListener listener) { super(null); throw new RuntimeException("Stub!"); }
}
}
"""
)
}
@Test
fun `Overriding protected methods`() {
// Checks a scenario where the stubs were missing overrides
checkStubs(
compatibilityMode = true,
sourceFiles = arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings("all")
public class Layouts {
public static class View {
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
}
public static abstract class ViewGroup extends View {
@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
}
public static class Toolbar extends ViewGroup {
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
}
}
}
"""
)
),
warnings = "",
api = """
package test.pkg {
public class Layouts {
ctor public Layouts();
}
public static class Layouts.Toolbar extends test.pkg.Layouts.ViewGroup {
ctor public Layouts.Toolbar();
}
public static class Layouts.View {
ctor public Layouts.View();
method protected void onLayout(boolean, int, int, int, int);
}
public abstract static class Layouts.ViewGroup extends test.pkg.Layouts.View {
ctor public Layouts.ViewGroup();
method protected abstract void onLayout(boolean, int, int, int, int);
}
}
""",
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Layouts {
public Layouts() { throw new RuntimeException("Stub!"); }
@SuppressWarnings({"unchecked", "deprecation", "all"})
public static class Toolbar extends test.pkg.Layouts.ViewGroup {
public Toolbar() { throw new RuntimeException("Stub!"); }
protected void onLayout(boolean changed, int l, int t, int r, int b) { throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public static class View {
public View() { throw new RuntimeException("Stub!"); }
protected void onLayout(boolean changed, int left, int top, int right, int bottom) { throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public abstract static class ViewGroup extends test.pkg.Layouts.View {
public ViewGroup() { throw new RuntimeException("Stub!"); }
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);
}
}
"""
)
}
@Test
fun `Missing overridden method`() {
// Another special case where overridden methods were missing
checkStubs(
sourceFiles = arrayOf(
java(
"""
package test.pkg;
import java.util.Collection;
import java.util.Set;
@SuppressWarnings("all")
public class SpanTest {
public interface CharSequence {
}
public interface Spanned extends CharSequence {
public int nextSpanTransition(int start, int limit, Class type);
}
public interface Spannable extends Spanned {
}
public class SpannableString extends SpannableStringInternal implements CharSequence, Spannable {
}
/* package */ abstract class SpannableStringInternal {
public int nextSpanTransition(int start, int limit, Class kind) {
return 0;
}
}
}
"""
)
),
warnings = "",
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class SpanTest {
public SpanTest() { throw new RuntimeException("Stub!"); }
@SuppressWarnings({"unchecked", "deprecation", "all"})
public static interface CharSequence {
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public static interface Spannable extends test.pkg.SpanTest.Spanned {
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class SpannableString implements test.pkg.SpanTest.CharSequence, test.pkg.SpanTest.Spannable {
public SpannableString() { throw new RuntimeException("Stub!"); }
public int nextSpanTransition(int start, int limit, java.lang.Class kind) { throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public static interface Spanned extends test.pkg.SpanTest.CharSequence {
public int nextSpanTransition(int start, int limit, java.lang.Class type);
}
}
"""
)
}
@Test
fun `Skip type variables in casts`() {
// When generating casts in super constructor calls, use raw types
checkStubs(
sourceFiles = arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings("all")
public class Properties {
public abstract class Property<T, V> {
public Property(Class<V> type, String name) {
}
public Property(Class<V> type, String name, String name2) { // force casts in super
}
}
public abstract class IntProperty<T> extends Property<T, Integer> {
public IntProperty(String name) {
super(Integer.class, name);
}
}
}
"""
)
),
warnings = "",
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Properties {
public Properties() { throw new RuntimeException("Stub!"); }
@SuppressWarnings({"unchecked", "deprecation", "all"})
public abstract class IntProperty<T> extends test.pkg.Properties.Property<T,java.lang.Integer> {
public IntProperty(java.lang.String name) { super((java.lang.Class)null, (java.lang.String)null); throw new RuntimeException("Stub!"); }
}
@SuppressWarnings({"unchecked", "deprecation", "all"})
public abstract class Property<T, V> {
public Property(java.lang.Class<V> type, java.lang.String name) { throw new RuntimeException("Stub!"); }
public Property(java.lang.Class<V> type, java.lang.String name, java.lang.String name2) { throw new RuntimeException("Stub!"); }
}
}
"""
)
}
@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 {
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) 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 1L;
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 @test.pkg.ExportedProperty.InnerAnnotation 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
}
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) 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.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD})
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 1L;
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 com.android.metalava.test;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface MyAnnotation {
}
"""
)
),
warnings = "",
source = """
package com.android.metalava.test;
@SuppressWarnings({"unchecked", "deprecation", "all"})
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
@java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD)
public @interface MyAnnotation {
}
"""
)
}
@Test
fun `Functional Interfaces`() {
checkStubs(
compatibilityMode = false,
skipEmitPackages = emptyList(),
sourceFiles = arrayOf(
java(
"""
package com.android.metalava.test;
@SuppressWarnings("something") @FunctionalInterface
public interface MyInterface {
void run();
}
"""
)
),
warnings = "",
source = """
package com.android.metalava.test;
@SuppressWarnings({"unchecked", "deprecation", "all"})
@java.lang.FunctionalInterface
public interface MyInterface {
public void run();
}
"""
)
}
@Test
fun `Check writing package info file`() {
checkStubs(
compatibilityMode = true,
sourceFiles = arrayOf(
java(
"""
@androidx.annotation.Nullable
package test.pkg;
"""
),
java(
"""
package test.pkg;
@SuppressWarnings("all")
public class Test {
}
"""
),
androidxNullableSource
),
warnings = "",
api = """
package @Nullable test.pkg {
public class Test {
ctor public Test();
}
}
""", // WRONG: I should include package annotations in the signature file!
source = """
@androidx.annotation.Nullable
package test.pkg;
""",
extraArguments = arrayOf(
ARG_HIDE_PACKAGE, "androidx.annotation",
// By default metalava rewrites androidx.annotation.Nullable to
// android.annotation.Nullable, but the latter does not have target PACKAGE thus
// fails to compile. This forces stubs keep the androidx annotation.
ARG_PASS_THROUGH_ANNOTATION, "androidx.annotation.Nullable")
)
}
@Test
fun `Test package-info documentation`() {
check(
sourceFiles = arrayOf(
java(
"""
/** My package docs */
package test.pkg;
"""
).indented(),
java("""package test.pkg; public abstract class Class1 { }""")
),
api = """
package test.pkg {
public abstract class Class1 {
ctor public Class1();
}
}
""",
stubFiles = arrayOf(
java(
"""
/** My package docs */
package test.pkg;
"""
),
java(
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public abstract class Class1 {
public Class1() { throw new RuntimeException("Stub!"); }
}
"""
)
)
)
}
@Test
fun `Test package-info annotations`() {
check(
compatibilityMode = false,
sourceFiles = arrayOf(
java(
"""
@RestrictTo(RestrictTo.Scope.SUBCLASSES)
package test.pkg;
import androidx.annotation.RestrictTo;
"""
).indented(),
java("""package test.pkg; public abstract class Class1 { }"""),
restrictToSource
),
api = """
package @RestrictTo(androidx.annotation.RestrictTo.Scope.SUBCLASSES) test.pkg {
public abstract class Class1 {
ctor public Class1();
}
}
""",
stubFiles = arrayOf(
java(
"""
@androidx.annotation.RestrictTo(androidx.annotation.RestrictTo.Scope.SUBCLASSES)
package test.pkg;
"""
),
java(
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public abstract class Class1 {
public Class1() { throw new RuntimeException("Stub!"); }
}
"""
)
),
extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation")
)
}
@Test
fun `Ensure we emit both deprecated javadoc and annotation with exclude-all-annotations`() {
check(
extraArguments = arrayOf(ARG_EXCLUDE_ALL_ANNOTATIONS),
compatibilityMode = false,
sourceFiles = arrayOf(
java(
"""
package test.pkg;
public class Foo {
/**
* @deprecated Use checkPermission instead.
*/
@Deprecated
protected boolean inClass(String name) {
return false;
}
}
"""
)
),
stubFiles = arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Foo {
public Foo() { throw new RuntimeException("Stub!"); }
/**
* @deprecated Use checkPermission instead.
*/
@Deprecated
protected boolean inClass(java.lang.String name) { throw new RuntimeException("Stub!"); }
}
"""
)
)
)
}
@Test
fun `Ensure we emit runtime and deprecated annotations in stubs with exclude-annotations`() {
check(
extraArguments = arrayOf(ARG_EXCLUDE_ALL_ANNOTATIONS),
compatibilityMode = false,
sourceFiles = arrayOf(
java(
"""
package test.pkg;
/** @deprecated */
@MySourceRetentionAnnotation
@MyClassRetentionAnnotation
@MyRuntimeRetentionAnnotation
@Deprecated
public class Foo {
private Foo() {}
}
"""
),
java(
"""
package test.pkg;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@Retention(SOURCE)
public @interface MySourceRetentionAnnotation {
}
"""
),
java(
"""
package test.pkg;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.CLASS;
@Retention(CLASS)
public @interface MyClassRetentionAnnotation {
}
"""
),
java(
"""
package test.pkg;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Retention(RUNTIME)
public @interface MyRuntimeRetentionAnnotation {
}
"""
)
),
stubFiles = arrayOf(
java(
"""
package test.pkg;
/** @deprecated */
@SuppressWarnings({"unchecked", "deprecation", "all"})
@Deprecated
@test.pkg.MyRuntimeRetentionAnnotation
public class Foo {
private Foo() { throw new RuntimeException("Stub!"); }
}
"""
)
)
)
}
@Test
fun `Ensure we include class and runtime and not source annotations in stubs with include-annotations`() {
check(
extraArguments = arrayOf("--include-annotations"),
compatibilityMode = false,
sourceFiles = arrayOf(
java(
"""
package test.pkg;
/** @deprecated */
@MySourceRetentionAnnotation
@MyClassRetentionAnnotation
@MyRuntimeRetentionAnnotation
@Deprecated
public class Foo {
private Foo() {}
protected int foo;
public void bar();
}
"""
),
java(
"""
package test.pkg;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@Retention(SOURCE)
public @interface MySourceRetentionAnnotation {
}
"""
),
java(
"""
package test.pkg;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.CLASS;
@Retention(CLASS)
public @interface MyClassRetentionAnnotation {
}
"""
),
java(
"""
package test.pkg;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Retention(RUNTIME)
public @interface MyRuntimeRetentionAnnotation {
}
"""
)
),
stubFiles = arrayOf(
java(
"""
package test.pkg;
/** @deprecated */
@SuppressWarnings({"unchecked", "deprecation", "all"})
@Deprecated
@test.pkg.MyClassRetentionAnnotation
@test.pkg.MyRuntimeRetentionAnnotation
public class Foo {
private Foo() { throw new RuntimeException("Stub!"); }
@Deprecated
public void bar() { throw new RuntimeException("Stub!"); }
@Deprecated protected int foo;
}
"""
)
)
)
}
@Test
fun `Generate stubs with --exclude-documentation-from-stubs`() {
checkStubs(
extraArguments = arrayOf(ARG_EXCLUDE_DOCUMENTATION_FROM_STUBS),
compatibilityMode = false,
sourceFiles = arrayOf(
java(
"""
/*
* This is the copyright header.
*/
package test.pkg;
/** This is the documentation for the class */
public class Foo {
/** My field doc */
protected static final String field = "a\nb\n\"test\"";
/**
* Method documentation.
*/
protected static void onCreate(String parameter1) {
// This is not in the stub
System.out.println(parameter1);
}
}
"""
)
),
// Excludes javadoc because of ARG_EXCLUDE_DOCUMENTATION_FROM_STUBS:
source = """
/*
* This is the copyright header.
*/
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Foo {
public Foo() { throw new RuntimeException("Stub!"); }
protected static void onCreate(java.lang.String parameter1) { throw new RuntimeException("Stub!"); }
protected static final java.lang.String field = "a\nb\n\"test\"";
}
"""
)
}
@Test
fun `Generate documentation stubs with --exclude-documentation-from-stubs`() {
checkStubs(
extraArguments = arrayOf(ARG_EXCLUDE_DOCUMENTATION_FROM_STUBS),
compatibilityMode = false,
sourceFiles = arrayOf(
java(
"""
/*
* This is the copyright header.
*/
package test.pkg;
/** This is the documentation for the class */
public class Foo {
/** My field doc */
protected static final String field = "a\nb\n\"test\"";
/**
* Method documentation.
*/
protected static void onCreate(String parameter1) {
// This is not in the stub
System.out.println(parameter1);
}
}
"""
)
),
docStubs = true,
// Includes javadoc despite ARG_EXCLUDE_DOCUMENTATION_FROM_STUBS, because of docStubs:
source = """
/*
* This is the copyright header.
*/
package test.pkg;
/** This is the documentation for the class */
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Foo {
public Foo() { throw new RuntimeException("Stub!"); }
/**
* Method documentation.
*/
protected static void onCreate(java.lang.String parameter1) { throw new RuntimeException("Stub!"); }
/** My field doc */
protected static final java.lang.String field = "a\nb\n\"test\"";
}
"""
)
}
@Test
fun `Annotation nested rewriting`() {
checkStubs(
sourceFiles = arrayOf(
java(
"""
package test.pkg;
import android.view.Gravity;
public class ActionBar {
@ViewDebug.ExportedProperty(category = "layout", mapping = {
@ViewDebug.IntToString(from = -1, to = "NONE"),
@ViewDebug.IntToString(from = Gravity.NO_GRAVITY, to = "NONE"),
@ViewDebug.IntToString(from = Gravity.TOP, to = "TOP"),
@ViewDebug.IntToString(from = Gravity.BOTTOM, to = "BOTTOM"),
})
public int gravity = Gravity.NO_GRAVITY;
}
"""
),
java(
"""
package android.view;
public class Gravity {
public static final int NO_GRAVITY = 0;
public static final int TOP = 1;
public static final int BOTTOM = 2;
}
"""
),
java(
"""
package test.pkg;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public class ViewDebug {
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExportedProperty {
boolean resolveId() default false;
IntToString[] mapping() default {};
IntToString[] indexMapping() default {};
boolean deepExport() default false;
String prefix() default "";
String category() default "";
boolean formatToHexString() default false;
boolean hasAdjacentMapping() default false;
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface IntToString {
int from();
String to();
}
}
"""
)
),
source = """
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class ActionBar {
public ActionBar() { throw new RuntimeException("Stub!"); }
@test.pkg.ViewDebug.ExportedProperty(category="layout", mapping={@test.pkg.ViewDebug.IntToString(from=0xffffffff, to="NONE"), @test.pkg.ViewDebug.IntToString(from=android.view.Gravity.NO_GRAVITY, to="NONE"), @test.pkg.ViewDebug.IntToString(from=android.view.Gravity.TOP, to="TOP"), @test.pkg.ViewDebug.IntToString(from=android.view.Gravity.BOTTOM, to="BOTTOM")}) public int gravity = 0; // 0x0
}
"""
)
}
@Test(expected = FileNotFoundException::class)
fun `Test update-api should not generate stubs`() {
check(
extraArguments = arrayOf(
ARG_UPDATE_API,
ARG_EXCLUDE_ALL_ANNOTATIONS
),
compatibilityMode = false,
sourceFiles = arrayOf(
java(
"""
package test.pkg;
public class Foo {
/**
* @deprecated Use checkPermission instead.
*/
@Deprecated
protected boolean inClass(String name) {
return false;
}
}
"""
)
),
api = """
package test.pkg {
public class Foo {
ctor public Foo();
method @Deprecated protected boolean inClass(String);
}
}
""",
stubFiles = arrayOf(
source(
"test/pkg/Foo.java",
"This file should not be generated since --update-api is supplied."
)
)
)
}
@Test(expected = AssertionError::class)
fun `Test check-api should not generate stubs or API files`() {
check(
extraArguments = arrayOf(
ARG_CHECK_API,
ARG_EXCLUDE_ALL_ANNOTATIONS
),
compatibilityMode = false,
sourceFiles = arrayOf(
java(
"""
package test.pkg;
public class Foo {
/**
* @deprecated Use checkPermission instead.
*/
@Deprecated
protected boolean inClass(String name) {
return false;
}
}
"""
)
),
api = """
package test.pkg {
public class Foo {
ctor public Foo();
method @Deprecated protected boolean inClass(String);
}
}
"""
)
}
@Test
fun `Include package private classes referenced from public API`() {
// Real world example: android.net.http.Connection in apache-http referenced from RequestHandle
check(
compatibilityMode = false,
expectedIssues = """
src/test/pkg/PublicApi.java:4: error: Class test.pkg.HiddenType is not public but was referenced (as return type) from public method test.pkg.PublicApi.getHiddenType() [ReferencesHidden]
src/test/pkg/PublicApi.java:5: error: Class test.pkg.HiddenType4 is hidden but was referenced (as return type) from public method test.pkg.PublicApi.getHiddenType4() [ReferencesHidden]
src/test/pkg/PublicApi.java:5: warning: Method test.pkg.PublicApi.getHiddenType4 returns unavailable type HiddenType4 [UnavailableSymbol]
src/test/pkg/PublicApi.java:4: warning: Method test.pkg.PublicApi.getHiddenType() references hidden type test.pkg.HiddenType. [HiddenTypeParameter]
src/test/pkg/PublicApi.java:5: warning: Method test.pkg.PublicApi.getHiddenType4() references hidden type test.pkg.HiddenType4. [HiddenTypeParameter]
""",
sourceFiles = arrayOf(
java(
"""
package test.pkg;
public class PublicApi {
public HiddenType getHiddenType() { return null; }
public HiddenType4 getHiddenType4() { return null; }
}
"""
),
java(
"""
package test.pkg;
public class PublicInterface {
}
"""
),
java(
"""
package test.pkg;
// Class exposed via public api above
final class HiddenType extends HiddenType2 implements HiddenType3, PublicInterface {
HiddenType(int i1, int i2) { }
public HiddenType2 getHiddenType2() { return null; }
public int field;
@Override public String toString() { return "hello"; }
}
"""
),
java(
"""
package test.pkg;
/** @hide */
public class HiddenType4 {
void foo();
}
"""
),
java(
"""
package test.pkg;
// Class not exposed; only referenced from HiddenType
class HiddenType2 {
HiddenType2(float f) { }
}
"""
),
java(
"""
package test.pkg;
// Class not exposed; only referenced from HiddenType
interface HiddenType3 {
}
"""
)
),
api = """
package test.pkg {
public class PublicApi {
ctor public PublicApi();
method public test.pkg.HiddenType getHiddenType();
method public test.pkg.HiddenType4 getHiddenType4();
}
public class PublicInterface {
ctor public PublicInterface();
}
}
""",
stubFiles = arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class PublicApi {
public PublicApi() { throw new RuntimeException("Stub!"); }
public test.pkg.HiddenType getHiddenType() { throw new RuntimeException("Stub!"); }
public test.pkg.HiddenType4 getHiddenType4() { throw new RuntimeException("Stub!"); }
}
"""
),
java(
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class PublicInterface {
public PublicInterface() { throw new RuntimeException("Stub!"); }
}
"""
)
)
)
}
@Test
fun `Include hidden inner classes referenced from public API`() {
// Real world example: hidden android.car.vms.VmsOperationRecorder.Writer in android.car-system-stubs
// referenced from outer class constructor
check(
compatibilityMode = false,
expectedIssues = """
src/test/pkg/PublicApi.java:4: error: Class test.pkg.PublicApi.HiddenInner is hidden but was referenced (as parameter type) from public parameter inner in test.pkg.PublicApi(test.pkg.PublicApi.HiddenInner inner) [ReferencesHidden]
src/test/pkg/PublicApi.java:4: warning: Parameter inner references hidden type test.pkg.PublicApi.HiddenInner. [HiddenTypeParameter]
""",
sourceFiles = arrayOf(
java(
"""
package test.pkg;
public class PublicApi {
public PublicApi(HiddenInner inner) { }
/** @hide */
public static class HiddenInner {
public void someHiddenMethod(); // should not be in stub
}
}
"""
)
),
api = """
package test.pkg {
public class PublicApi {
ctor public PublicApi(test.pkg.PublicApi.HiddenInner);
}
}
""",
stubFiles = arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class PublicApi {
public PublicApi(test.pkg.PublicApi.HiddenInner inner) { throw new RuntimeException("Stub!"); }
}
"""
)
)
)
}
@Test
fun `Use type argument in constructor cast`() {
check(
compatibilityMode = false,
sourceFiles = arrayOf(
java(
"""
package test.pkg;
/** @deprecated */
@Deprecated
public class BasicPoolEntryRef extends WeakRef<BasicPoolEntry> {
public BasicPoolEntryRef(BasicPoolEntry entry) {
super(entry);
}
}
"""
),
java(
"""
package test.pkg;
public class WeakRef<T> {
public WeakRef(T foo) {
}
// need to have more than one constructor to trigger casts in stubs
public WeakRef(T foo, int size) {
}
}
"""
),
java(
"""
package test.pkg;
public class BasicPoolEntry {
}
"""
)
),
api = """
package test.pkg {
public class BasicPoolEntry {
ctor public BasicPoolEntry();
}
@Deprecated public class BasicPoolEntryRef extends test.pkg.WeakRef<test.pkg.BasicPoolEntry> {
ctor @Deprecated public BasicPoolEntryRef(test.pkg.BasicPoolEntry);
}
public class WeakRef<T> {
ctor public WeakRef(T);
ctor public WeakRef(T, int);
}
}
""",
stubFiles = arrayOf(
java(
"""
package test.pkg;
/** @deprecated */
@SuppressWarnings({"unchecked", "deprecation", "all"})
@Deprecated
public class BasicPoolEntryRef extends test.pkg.WeakRef<test.pkg.BasicPoolEntry> {
@Deprecated
public BasicPoolEntryRef(test.pkg.BasicPoolEntry entry) { super((test.pkg.BasicPoolEntry)null); throw new RuntimeException("Stub!"); }
}
"""
)
)
)
}
@Test
fun `Regression test for 116777737`() {
// Regression test for 116777737: Stub generation broken for Bouncycastle
// """
// It appears as though metalava does not handle the case where:
// 1) class Alpha extends Beta<Orange>.
// 2) class Beta<T> extends Charlie<T>.
// 3) class Beta is hidden.
//
// It should result in a stub where Alpha extends Charlie<Orange> but
// instead results in a stub where Alpha extends Charlie<T>, so the
// type substitution of Orange for T is lost.
// """
check(
compatibilityMode = false,
expectedIssues = "src/test/pkg/Alpha.java:2: warning: Public class test.pkg.Alpha stripped of unavailable superclass test.pkg.Beta [HiddenSuperclass]",
sourceFiles = arrayOf(
java(
"""
package test.pkg;
public class Orange {
private Orange() { }
}
"""
),
java(
"""
package test.pkg;
public class Alpha extends Beta<Orange> {
private Alpha() { }
}
"""
),
java(
"""
package test.pkg;
/** @hide */
public class Beta<T> extends Charlie<T> {
private Beta() { }
}
"""
),
java(
"""
package test.pkg;
public class Charlie<T> {
private Charlie() { }
}
"""
)
),
api = """
package test.pkg {
public class Alpha extends test.pkg.Charlie<test.pkg.Orange> {
}
public class Charlie<T> {
}
public class Orange {
}
}
""",
stubFiles = arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Orange {
private Orange() { throw new RuntimeException("Stub!"); }
}
"""
),
java(
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class Alpha extends test.pkg.Charlie<test.pkg.Orange> {
private Alpha() { throw new RuntimeException("Stub!"); }
}
"""
)
)
)
}
@Test
fun `Regression test for 124333557`() {
// Regression test for 124333557: Handle empty java files
check(
compatibilityMode = false,
expectedIssues = """
TESTROOT/src/test/Something2.java: error: metalava was unable to determine the package name. This usually means that a source file was where the directory does not seem to match the package declaration; we expected the path TESTROOT/src/test/Something2.java to end with /test/wrong/Something2.java [IoError]
TESTROOT/src/test/Something2.java: error: metalava was unable to determine the package name. This usually means that a source file was where the directory does not seem to match the package declaration; we expected the path TESTROOT/src/test/Something2.java to end with /test/wrong/Something2.java [IoError]
""",
sourceFiles = arrayOf(
java(
"src/test/pkg/Something.java",
"""
/** Nothing much here */
"""
),
java(
"src/test/pkg/Something2.java",
"""
/** Nothing much here */
package test.pkg;
"""
),
java(
"src/test/Something2.java",
"""
/** Wrong package */
package test.wrong;
"""
),
java(
"""
package test.pkg;
public class Test {
private Test() { }
}
"""
)
),
api = """
package test.pkg {
public class Test {
}
}
""",
projectSetup = { dir ->
// Make sure we handle blank/doc-only java doc files in root extraction
val src = listOf(File(dir, "src"))
val files = gatherSources(src)
val roots = extractRoots(files)
assertEquals(1, roots.size)
assertEquals(src[0].path, roots[0].path)
}
)
}
@Test
fun `Basic Kotlin stubs`() {
check(
extraArguments = arrayOf(
ARG_KOTLIN_STUBS
),
sourceFiles = arrayOf(
kotlin(
"""
/* My file header */
// Another comment
@file:JvmName("Driver")
package test.pkg
/** My class doc */
class Kotlin(
val property1: String = "Default Value",
arg2: Int
) : Parent() {
override fun method() = "Hello World"
/** My method doc */
fun otherMethod(ok: Boolean, times: Int) {
}
/** property doc */
var property2: String? = null
/** @hide */
var hiddenProperty: String? = "hidden"
private var someField = 42
@JvmField
var someField2 = 42
}
/** Parent class doc */
open class Parent {
open fun method(): String? = null
open fun method2(value1: Boolean, value2: Boolean?): String? = null
open fun method3(value1: Int?, value2: Int): Int = null
}
"""
),
kotlin("""
package test.pkg
open class ExtendableClass<T>
"""
)
),
stubFiles = arrayOf(
kotlin(
"""
/* My file header */
// Another comment
package test.pkg
/** My class doc */
@file:Suppress("ALL")
class Kotlin : test.pkg.Parent() {
open fun Kotlin(open property1: java.lang.String!, open arg2: int): test.pkg.Kotlin! = error("Stub!")
open fun method(): java.lang.String = error("Stub!")
/** My method doc */
open fun otherMethod(open ok: boolean, open times: int): void = error("Stub!")
}
"""
),
kotlin(
"""
package test.pkg
@file:Suppress("ALL")
open class ExtendableClass<T> {
open fun ExtendableClass(): test.pkg.ExtendableClass<T!>! = error("Stub!")
}
"""
)
)
)
}
@Test
fun `Extends and implements multiple interfaces in Kotlin Stubs`() {
check(
extraArguments = arrayOf(
ARG_KOTLIN_STUBS
),
sourceFiles = arrayOf(
kotlin("""
package test.pkg
class MainClass: MyParentClass(), MyInterface1, MyInterface2
open class MyParentClass
interface MyInterface1
interface MyInterface2
""")
),
stubFiles = arrayOf(
kotlin(
"""
package test.pkg
@file:Suppress("ALL")
class MainClass : test.pkg.MyParentClass(), test.pkg.MyInterface1, test.pkg.MyInterface2 {
open fun MainClass(): test.pkg.MainClass! = error("Stub!")
}
"""
)
)
)
}
@Test
fun `Extends and implements multiple interfaces`() {
check(
checkCompilation = true,
sourceFiles = arrayOf(
java(
"""
package test.pkg;
public class MainClass extends MyParentClass implements MyInterface1, MyInterface2 {
}
"""
),
java(
"""
package test.pkg;
public interface MyInterface1 { }
"""
),
java(
"""
package test.pkg;
public interface MyInterface2 { }
"""
),
java(
"""
package test.pkg;
public class MyParentClass { }
"""
)
),
stubFiles = arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class MainClass extends test.pkg.MyParentClass implements test.pkg.MyInterface1, test.pkg.MyInterface2 {
public MainClass() { throw new RuntimeException("Stub!"); }
}
"""
)
)
)
}
@Test
fun `NaN constants`() {
check(
checkCompilation = true,
sourceFiles = arrayOf(
java(
"""
package test.pkg;
public class MyClass {
public static final float floatNaN = 0.0f / 0.0f;
public static final double doubleNaN = 0.0d / 0.0;
}
"""
)
),
stubFiles = arrayOf(
java(
"""
package test.pkg;
@SuppressWarnings({"unchecked", "deprecation", "all"})
public class MyClass {
public MyClass() { throw new RuntimeException("Stub!"); }
public static final double doubleNaN = (0.0/0.0);
public static final float floatNaN = (0.0f/0.0f);
}
"""
)
)
)
}
// 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.
}