/*
* Copyright 2014-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.facebook.buck.jvm.java.abi;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeThat;
import com.facebook.buck.event.DefaultBuckEventBus;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.jvm.java.JarDumper;
import com.facebook.buck.jvm.java.testutil.compiler.CompilerTreeApiParameterized;
import com.facebook.buck.jvm.java.testutil.compiler.TestCompiler;
import com.facebook.buck.model.BuildId;
import com.facebook.buck.timing.FakeClock;
import com.facebook.buck.util.sha1.Sha1HashCode;
import com.facebook.buck.zip.Unzip;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSortedSet;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.objectweb.asm.ClassReader;
// The stub source is easier to read as long lines, so...
// CHECKSTYLE.OFF: LineLengthCheck
@RunWith(CompilerTreeApiParameterized.class)
public class StubJarTest {
// Test a stub generated by stripping a full jar
private static final String MODE_JAR_BASED = "JAR_BASED";
// Test a stub generated from source
private static final String MODE_SOURCE_BASED = "SOURCE_BASED";
// Test a stub generated from source, with dependencies missing
private static final String MODE_SOURCE_BASED_MISSING_DEPS = "SOURCE_BASED_MISSING_DEPS";
@Parameterized.Parameter public String testingMode;
@Parameterized.Parameters(name = "{0}")
public static Object[] getParameters() {
return new Object[] {MODE_JAR_BASED, MODE_SOURCE_BASED, MODE_SOURCE_BASED_MISSING_DEPS};
}
private static final ImmutableSortedSet<Path> EMPTY_CLASSPATH = ImmutableSortedSet.of();
@Rule public TemporaryFolder temp = new TemporaryFolder();
private Tester tester = new Tester();
private ProjectFilesystem filesystem;
@Before
public void createTempFilesystem() throws InterruptedException, IOException {
File out = temp.newFolder();
filesystem = new ProjectFilesystem(out.toPath());
}
@Test
public void emptyClass() throws IOException {
tester
.setSourceFile("A.java", "package com.example.buck; public class A {}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
"",
" // access flags 0x1",
" public <init>()V",
"}")
.createAndCheckStubJar()
.addStubJarToClasspath()
.setSourceFile("B.java", "package com.example.buck; public class B extends A {}")
.testCanCompile();
}
@Test
public void emptyClassWithAnnotation() throws IOException {
tester
.setSourceFile("A.java", "package com.example.buck; @Deprecated public class A {}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// DEPRECATED",
"// access flags 0x20021",
"public class com/example/buck/A {",
"",
"",
" @Ljava/lang/Deprecated;()",
"",
" // access flags 0x1",
" public <init>()V",
"}")
.createAndCheckStubJar();
}
@Test
public void classWithTwoMethods() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A {",
" public String toString() { return null; }",
" public void eatCake() {}",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
"",
" // access flags 0x1",
" public <init>()V",
"",
" // access flags 0x1",
" public toString()Ljava/lang/String;",
"",
" // access flags 0x1",
" public eatCake()V",
"}")
.createAndCheckStubJar();
}
@Test
public void preservesThrowsClauses() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A {",
" public void throwSomeStuff() throws Exception, Throwable {}",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
"",
" // access flags 0x1",
" public <init>()V",
"",
" // access flags 0x1",
" public throwSomeStuff()V throws java/lang/Exception java/lang/Throwable ",
"}")
.createAndCheckStubJar();
}
@Test
public void preservesThrowsClausesWithTypeVars() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"import java.io.IOException;",
"public class A {",
" public <E extends IOException> void throwSomeStuff() throws E {}",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
"",
" // access flags 0x1",
" public <init>()V",
"",
" // access flags 0x1",
" // signature <E:Ljava/io/IOException;>()V^TE;",
" // declaration: void throwSomeStuff<E extends java.io.IOException>() throws E",
" public throwSomeStuff()V throws java/io/IOException ",
"}")
.createAndCheckStubJar();
}
@Test
public void genericClassSignaturesShouldBePreserved() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A<T> {",
" public T get(String key) { return null; }",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"// signature <T:Ljava/lang/Object;>Ljava/lang/Object;",
"// declaration: com/example/buck/A<T>",
"public class com/example/buck/A {",
"",
"",
" // access flags 0x1",
" public <init>()V",
"",
" // access flags 0x1",
" // signature (Ljava/lang/String;)TT;",
" // declaration: T get(java.lang.String)",
" public get(Ljava/lang/String;)Ljava/lang/Object;",
"}")
.createAndCheckStubJar();
}
@Test
public void elementsInStubCorrectlyInOrder() throws IOException {
// Fields and methods should stub in order
// Inner classes should stub in reverse
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A {",
" boolean first;",
" float second;",
" public void foo() { }",
" public class B { }",
" public class C { }",
" public void bar() { }",
" public class D { }",
" int between;",
" public class E {",
" public void hello() { }",
" public void test() { }",
" }",
"}")
.addExpectedStub(
"com/example/buck/A$B",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A$B {",
"",
" // access flags 0x1",
" public INNERCLASS com/example/buck/A$B com/example/buck/A B",
"",
" // access flags 0x1",
" public <init>(Lcom/example/buck/A;)V",
"}")
.addExpectedStub(
"com/example/buck/A$C",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A$C {",
"",
" // access flags 0x1",
" public INNERCLASS com/example/buck/A$C com/example/buck/A C",
"",
" // access flags 0x1",
" public <init>(Lcom/example/buck/A;)V",
"}")
.addExpectedStub(
"com/example/buck/A$D",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A$D {",
"",
" // access flags 0x1",
" public INNERCLASS com/example/buck/A$D com/example/buck/A D",
"",
" // access flags 0x1",
" public <init>(Lcom/example/buck/A;)V",
"}")
.addExpectedStub(
"com/example/buck/A$E",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A$E {",
"",
" // access flags 0x1",
" public INNERCLASS com/example/buck/A$E com/example/buck/A E",
"",
" // access flags 0x1",
" public <init>(Lcom/example/buck/A;)V",
"",
" // access flags 0x1",
" public hello()V",
"",
" // access flags 0x1",
" public test()V",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
" // access flags 0x1",
" public INNERCLASS com/example/buck/A$E com/example/buck/A E",
" // access flags 0x1",
" public INNERCLASS com/example/buck/A$D com/example/buck/A D",
" // access flags 0x1",
" public INNERCLASS com/example/buck/A$C com/example/buck/A C",
" // access flags 0x1",
" public INNERCLASS com/example/buck/A$B com/example/buck/A B",
"",
" // access flags 0x0",
" Z first",
"",
" // access flags 0x0",
" F second",
"",
" // access flags 0x0",
" I between",
"",
" // access flags 0x1",
" public <init>()V",
"",
" // access flags 0x1",
" public foo()V",
"",
" // access flags 0x1",
" public bar()V",
"}")
.createAndCheckStubJar();
}
@Test
public void genericInterfaceSignaturesShouldBePreserved() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"public interface A<T> {",
" T get(String key);",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x601",
"// signature <T:Ljava/lang/Object;>Ljava/lang/Object;",
"// declaration: com/example/buck/A<T>",
"public abstract interface com/example/buck/A {",
"",
"",
" // access flags 0x401",
" // signature (Ljava/lang/String;)TT;",
" // declaration: T get(java.lang.String)",
" public abstract get(Ljava/lang/String;)Ljava/lang/Object;",
"}")
.createAndCheckStubJar();
}
@Test
public void shouldIgnorePrivateMethods() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A {",
" private void privateMethod() {}",
" void packageMethod() {}",
" protected void protectedMethod() {}",
" public void publicMethod() {}",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
"",
" // access flags 0x1",
" public <init>()V",
"",
" // access flags 0x0",
" packageMethod()V",
"",
" // access flags 0x4",
" protected protectedMethod()V",
"",
" // access flags 0x1",
" public publicMethod()V",
"}")
.createAndCheckStubJar();
}
@Test
public void shouldGenerateConstructorForClassWithSinglePrivateConstructor() throws IOException {
tester
.setSourceFile(
"A.java", "package com.example.buck;", "public class A {", " private A() { }", "}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
"",
" // access flags 0x2",
" private <init>()V",
"}")
.createAndCheckStubJar();
}
@Test
public void shouldGenerateConstructorForClassWithPrivateConstructorsOnly() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A {",
" private A() { }",
" private A(int test) { }",
" private A(String test) { }",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
"",
" // access flags 0x2",
" private <init>()V",
"}")
.createAndCheckStubJar();
}
@Test
public void shouldGeneratePrivateInnerClassDefaultConstructor() throws IOException {
tester
.setSourceFile(
"Outer.java",
"package com.example.buck;",
"public class Outer {",
" public class Inner {",
" private Inner() { }",
" }",
"}")
.addExpectedStub(
"com/example/buck/Outer$Inner",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/Outer$Inner {",
"",
" // access flags 0x1",
" public INNERCLASS com/example/buck/Outer$Inner com/example/buck/Outer Inner",
"",
" // access flags 0x2",
" private <init>(Lcom/example/buck/Outer;)V",
"}")
.addExpectedStub(
"com/example/buck/Outer",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/Outer {",
"",
" // access flags 0x1",
" public INNERCLASS com/example/buck/Outer$Inner com/example/buck/Outer Inner",
"",
" // access flags 0x1",
" public <init>()V",
"}")
.createAndCheckStubJar();
}
@Test
public void shouldGeneratePrivateNestedClassDefaultConstructor() throws IOException {
notYetImplementedForSource();
tester
.setSourceFile(
"Outer.java",
"package com.example.buck;",
"public class Outer {",
" public class Inner {",
" public class Nested {",
" private Nested() { }",
" }",
" }",
"}")
.addExpectedStub(
"com/example/buck/Outer$Inner$Nested",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/Outer$Inner$Nested {",
"",
" // access flags 0x1",
" public INNERCLASS com/example/buck/Outer$Inner com/example/buck/Outer Inner",
" // access flags 0x1",
" public INNERCLASS com/example/buck/Outer$Inner$Nested com/example/buck/Outer$Inner Nested",
"",
" // access flags 0x2",
" private <init>(Lcom/example/buck/Outer$Inner;)V",
"}")
.addExpectedStub(
"com/example/buck/Outer$Inner",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/Outer$Inner {",
"",
" // access flags 0x1",
" public INNERCLASS com/example/buck/Outer$Inner com/example/buck/Outer Inner",
" // access flags 0x1",
" public INNERCLASS com/example/buck/Outer$Inner$Nested com/example/buck/Outer$Inner Nested",
"",
" // access flags 0x1",
" public <init>(Lcom/example/buck/Outer;)V",
"}")
.addExpectedStub(
"com/example/buck/Outer",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/Outer {",
"",
" // access flags 0x1",
" public INNERCLASS com/example/buck/Outer$Inner com/example/buck/Outer Inner",
"",
" // access flags 0x1",
" public <init>()V",
"}")
.createAndCheckStubJar();
}
@Test
public void shouldPreserveAField() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A {",
" protected String protectedField;",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
"",
" // access flags 0x4",
" protected Ljava/lang/String; protectedField",
"",
" // access flags 0x1",
" public <init>()V",
"}")
.createAndCheckStubJar();
}
@Test
public void shouldIgnorePrivateFields() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A {",
" private String privateField;",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
"",
" // access flags 0x1",
" public <init>()V",
"}")
.createAndCheckStubJar();
}
@Test
public void shouldPreserveGenericTypesOnFields() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A<T> {",
" public T theField;",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"// signature <T:Ljava/lang/Object;>Ljava/lang/Object;",
"// declaration: com/example/buck/A<T>",
"public class com/example/buck/A {",
"",
"",
" // access flags 0x1",
" // signature TT;",
" // declaration: T",
" public Ljava/lang/Object; theField",
"",
" // access flags 0x1",
" public <init>()V",
"}")
.createAndCheckStubJar();
}
@Test
public void shouldPreserveGenericTypesOnMethods() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A<T> {",
" public T get(String key) { return null; }",
" public <X extends Comparable<T>> X compareWith(T other) { return null; }",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"// signature <T:Ljava/lang/Object;>Ljava/lang/Object;",
"// declaration: com/example/buck/A<T>",
"public class com/example/buck/A {",
"",
"",
" // access flags 0x1",
" public <init>()V",
"",
" // access flags 0x1",
" // signature (Ljava/lang/String;)TT;",
" // declaration: T get(java.lang.String)",
" public get(Ljava/lang/String;)Ljava/lang/Object;",
"",
" // access flags 0x1",
" // signature <X::Ljava/lang/Comparable<TT;>;>(TT;)TX;",
" // declaration: X compareWith<X extends java.lang.Comparable<T>>(T)",
" public compareWith(Ljava/lang/Object;)Ljava/lang/Comparable;",
"}")
.createAndCheckStubJar();
}
@Test
public void preservesAnnotationsOnMethods() throws IOException {
notYetImplementedForMissingClasspath();
createAnnotationFullJar()
.addFullJarToClasspath()
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A {",
" @Foo",
" public void cheese(String key) {}",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
"",
" // access flags 0x1",
" public <init>()V",
"",
" // access flags 0x1",
" public cheese(Ljava/lang/String;)V",
" @Lcom/example/buck/Foo;()",
"}")
.createAndCheckStubJar();
}
@Test
public void preservesAnnotationsOnFields() throws IOException {
notYetImplementedForMissingClasspath();
createAnnotationFullJar()
.addFullJarToClasspath()
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A {",
" @Foo",
" public String name;",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
"",
" // access flags 0x1",
" public Ljava/lang/String; name",
" @Lcom/example/buck/Foo;()",
"",
" // access flags 0x1",
" public <init>()V",
"}")
.createAndCheckStubJar();
}
@Test
public void preservesAnnotationsOnParameters() throws IOException {
notYetImplementedForMissingClasspath();
createAnnotationFullJar()
.addFullJarToClasspath()
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A {",
" public void peynir(@Foo String very, int tasty) {}",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
"",
" // access flags 0x1",
" public <init>()V",
"",
" // access flags 0x1",
" public peynir(Ljava/lang/String;I)V",
" @Lcom/example/buck/Foo;() // parameter 0",
"}")
.createAndCheckStubJar();
}
@Test
public void preservesTypeAnnotationsInClasses() throws IOException {
// TODO(jkeljo): It looks like annotated types are not accessible via Elements. The annotated
// type gets put on the Tree object but doesn't make it to the corresponding Element. We can
// work around this using Trees.getTypeMirror, but that brings in a lot of classpath challenges
// that I don't want to deal with right now.
notYetImplementedForSource();
createAnnotationFullJar()
.addFullJarToClasspath()
.setSourceFile(
"A.java", "package com.example.buck;", "public class A<@Foo.TypeAnnotation T> { }")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"// signature <T:Ljava/lang/Object;>Ljava/lang/Object;",
"// declaration: com/example/buck/A<T>",
"public class com/example/buck/A {",
"",
"",
" @Lcom/example/buck/Foo$TypeAnnotation;() : CLASS_TYPE_PARAMETER 0, null // invisible",
" // access flags 0x2609",
" public static abstract INNERCLASS com/example/buck/Foo$TypeAnnotation com/example/buck/Foo TypeAnnotation",
"",
" // access flags 0x1",
" public <init>()V",
"}")
.createAndCheckStubJar();
}
@Test
public void preservesTypeAnnotationsInMethods() throws IOException {
// TODO(jkeljo): It looks like annotated types are not accessible via Elements. The annotated
// type gets put on the Tree object but doesn't make it to the corresponding Element. We can
// work around this using Trees.getTypeMirror, but that brings in a lot of classpath challenges
// that I don't want to deal with right now.
notYetImplementedForSource();
createAnnotationFullJar()
.addFullJarToClasspath()
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A {",
" <@Foo.TypeAnnotation T> void foo(@Foo.TypeAnnotation String s) { }",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
" // access flags 0x2609",
" public static abstract INNERCLASS com/example/buck/Foo$TypeAnnotation com/example/buck/Foo TypeAnnotation",
"",
" // access flags 0x1",
" public <init>()V",
"",
" // access flags 0x0",
" // signature <T:Ljava/lang/Object;>(Ljava/lang/String;)V",
" // declaration: void foo<T>(java.lang.String)",
" foo(Ljava/lang/String;)V",
" @Lcom/example/buck/Foo$TypeAnnotation;() : METHOD_TYPE_PARAMETER 0, null // invisible",
" @Lcom/example/buck/Foo$TypeAnnotation;() : METHOD_FORMAL_PARAMETER 0, null // invisible",
"}")
.createAndCheckStubJar();
}
@Test
public void preservesTypeAnnotationsInFields() throws IOException {
// TODO(jkeljo): It looks like annotated types are not accessible via Elements. The annotated
// type gets put on the Tree object but doesn't make it to the corresponding Element. We can
// work around this using Trees.getTypeMirror, but that brings in a lot of classpath challenges
// that I don't want to deal with right now.
notYetImplementedForSource();
createAnnotationFullJar()
.addFullJarToClasspath()
.setSourceFile(
"A.java",
"package com.example.buck;",
"import java.util.List;",
"public class A {",
" List<@Foo.TypeAnnotation String> list;",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
" // access flags 0x2609",
" public static abstract INNERCLASS com/example/buck/Foo$TypeAnnotation com/example/buck/Foo TypeAnnotation",
"",
" // access flags 0x0",
" // signature Ljava/util/List<Ljava/lang/String;>;",
" // declaration: java.util.List<java.lang.String>",
" Ljava/util/List; list",
" @Lcom/example/buck/Foo$TypeAnnotation;() : FIELD, 0 // invisible",
"",
" // access flags 0x1",
" public <init>()V",
"}")
.createAndCheckStubJar();
}
@Test
public void omitsAnnotationsWithSourceRetention() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"import java.lang.annotation.*;",
"@SourceRetentionAnno()",
"public class A { }",
"@Retention(RetentionPolicy.SOURCE)",
"@interface SourceRetentionAnno { }")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
"",
" // access flags 0x1",
" public <init>()V",
"}")
.addExpectedStub(
"com/example/buck/SourceRetentionAnno",
"// class version 52.0 (52)",
"// access flags 0x2600",
"abstract @interface com/example/buck/SourceRetentionAnno implements java/lang/annotation/Annotation {",
"",
"",
" @Ljava/lang/annotation/Retention;(value=Ljava/lang/annotation/RetentionPolicy;.SOURCE)",
"}")
.createAndCheckStubJar();
}
@Test
public void preservesAnnotationsWithClassRetention() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"import java.lang.annotation.*;",
"@ClassRetentionAnno()",
"public class A { }",
"@Retention(RetentionPolicy.CLASS)",
"@interface ClassRetentionAnno { }")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
"",
" @Lcom/example/buck/ClassRetentionAnno;() // invisible",
"",
" // access flags 0x1",
" public <init>()V",
"}")
.addExpectedStub(
"com/example/buck/ClassRetentionAnno",
"// class version 52.0 (52)",
"// access flags 0x2600",
"abstract @interface com/example/buck/ClassRetentionAnno implements java/lang/annotation/Annotation {",
"",
"",
" @Ljava/lang/annotation/Retention;(value=Ljava/lang/annotation/RetentionPolicy;.CLASS)",
"}")
.createAndCheckStubJar();
}
@Test
public void preservesAnnotationsWithRuntimeRetention() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"import java.lang.annotation.*;",
"@RuntimeRetentionAnno()",
"public class A { }",
"@Retention(RetentionPolicy.RUNTIME)",
"@interface RuntimeRetentionAnno { }")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
"",
" @Lcom/example/buck/RuntimeRetentionAnno;()",
"",
" // access flags 0x1",
" public <init>()V",
"}")
.addExpectedStub(
"com/example/buck/RuntimeRetentionAnno",
"// class version 52.0 (52)",
"// access flags 0x2600",
"abstract @interface com/example/buck/RuntimeRetentionAnno implements java/lang/annotation/Annotation {",
"",
"",
" @Ljava/lang/annotation/Retention;(value=Ljava/lang/annotation/RetentionPolicy;.RUNTIME)",
"}")
.createAndCheckStubJar();
}
@Test
public void preservesAnnotationsWithPrimitiveValues() throws IOException {
notYetImplementedForMissingClasspath();
createAnnotationFullJar()
.addFullJarToClasspath()
.setSourceFile(
"A.java",
"package com.example.buck;",
"@Foo(primitiveValue=1)",
"public @interface A {}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x2601",
"public abstract @interface com/example/buck/A implements java/lang/annotation/Annotation {",
"",
"",
" @Lcom/example/buck/Foo;(primitiveValue=1)",
"}")
.createAndCheckStubJar();
}
@Test
public void preservesAnnotationsWithStringArrayValues() throws IOException {
notYetImplementedForMissingClasspath();
createAnnotationFullJar()
.addFullJarToClasspath()
.setSourceFile(
"A.java",
"package com.example.buck;",
"@Foo(stringArrayValue={\"1\", \"2\"})",
"public @interface A {}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x2601",
"public abstract @interface com/example/buck/A implements java/lang/annotation/Annotation {",
"",
"",
" @Lcom/example/buck/Foo;(stringArrayValue={\"1\", \"2\"})",
"}")
.createAndCheckStubJar();
}
@Test
public void preservesAnnotationsWithEnumValues() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"import java.lang.annotation.*;",
"@Retention(RetentionPolicy.RUNTIME)",
"public @interface A {}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x2601",
"public abstract @interface com/example/buck/A implements java/lang/annotation/Annotation {",
"",
"",
" @Ljava/lang/annotation/Retention;(value=Ljava/lang/annotation/RetentionPolicy;.RUNTIME)",
"}")
.createAndCheckStubJar();
}
@Test
public void preservesAnnotationsWithTypeValues() throws IOException {
notYetImplementedForMissingClasspath();
createAnnotationFullJar()
.addFullJarToClasspath()
.setSourceFile(
"A.java",
"package com.example.buck;",
"@Foo(typeValue=String.class)",
"public @interface A {}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x2601",
"public abstract @interface com/example/buck/A implements java/lang/annotation/Annotation {",
"",
"",
" @Lcom/example/buck/Foo;(typeValue=java.lang.String.class)",
"}")
.createAndCheckStubJar();
}
@Test
public void preservesAnnotationsWithEnumArrayValues() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"import java.lang.annotation.*;",
"@Target({ElementType.CONSTRUCTOR, ElementType.FIELD})",
"public @interface A {}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x2601",
"public abstract @interface com/example/buck/A implements java/lang/annotation/Annotation {",
"",
"",
" @Ljava/lang/annotation/Target;(value={Ljava/lang/annotation/ElementType;.CONSTRUCTOR, Ljava/lang/annotation/ElementType;.FIELD})",
"}")
.createAndCheckStubJar();
}
@Test
public void preservesAnnotationsWithAnnotationValues() throws IOException {
notYetImplementedForMissingClasspath();
createAnnotationFullJar()
.addFullJarToClasspath()
.setSourceFile(
"A.java",
"package com.example.buck;",
"import java.lang.annotation.*;",
"@Foo(annotationValue=@Retention(RetentionPolicy.RUNTIME))",
"public @interface A {}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x2601",
"public abstract @interface com/example/buck/A implements java/lang/annotation/Annotation {",
"",
"",
" @Lcom/example/buck/Foo;(annotationValue=@Ljava/lang/annotation/Retention;(value=Ljava/lang/annotation/RetentionPolicy;.RUNTIME))",
"}")
.createAndCheckStubJar();
}
@Test
public void preservesAnnotationsWithAnnotationArrayValues() throws IOException {
notYetImplementedForMissingClasspath();
createAnnotationFullJar()
.addFullJarToClasspath()
.setSourceFile(
"A.java",
"package com.example.buck;",
"import java.lang.annotation.*;",
"@Foo(annotationArrayValue=@Retention(RetentionPolicy.RUNTIME))",
"public @interface A {}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x2601",
"public abstract @interface com/example/buck/A implements java/lang/annotation/Annotation {",
"",
"",
" @Lcom/example/buck/Foo;(annotationArrayValue={@Ljava/lang/annotation/Retention;(value=Ljava/lang/annotation/RetentionPolicy;.RUNTIME)})",
"}")
.createAndCheckStubJar();
}
@Test
public void preservesAnnotationDefaultValues() throws IOException {
tester
.setSourceFile(
"Foo.java",
"package com.example.buck;",
"import java.lang.annotation.*;",
"public @interface Foo {",
" int primitiveValue() default 42;",
" String[] stringArrayValue() default {\"Hello\", \"World\", \"!\"};",
" Retention annotationValue() default @Retention(RetentionPolicy.SOURCE);",
" Retention[] annotationArrayValue() default {@Retention(RetentionPolicy.SOURCE), @Retention(RetentionPolicy.CLASS), @Retention(RetentionPolicy.RUNTIME)};",
" RetentionPolicy enumValue () default RetentionPolicy.CLASS;",
" Class typeValue() default Foo.class;",
"}")
.addExpectedStub(
"com/example/buck/Foo",
"// class version 52.0 (52)",
"// access flags 0x2601",
"public abstract @interface com/example/buck/Foo implements java/lang/annotation/Annotation {",
"",
"",
" // access flags 0x401",
" public abstract primitiveValue()I",
" default=42",
"",
" // access flags 0x401",
" public abstract stringArrayValue()[Ljava/lang/String;",
" default={\"Hello\", \"World\", \"!\"}",
"",
" // access flags 0x401",
" public abstract annotationValue()Ljava/lang/annotation/Retention;",
" default=@Ljava/lang/annotation/Retention;(value=Ljava/lang/annotation/RetentionPolicy;.SOURCE)",
"",
" // access flags 0x401",
" public abstract annotationArrayValue()[Ljava/lang/annotation/Retention;",
" default={@Ljava/lang/annotation/Retention;(value=Ljava/lang/annotation/RetentionPolicy;.SOURCE), @Ljava/lang/annotation/Retention;(value=Ljava/lang/annotation/RetentionPolicy;.CLASS), @Ljava/lang/annotation/Retention;(value=Ljava/lang/annotation/RetentionPolicy;.RUNTIME)}",
"",
" // access flags 0x401",
" public abstract enumValue()Ljava/lang/annotation/RetentionPolicy;",
" default=Ljava/lang/annotation/RetentionPolicy;.CLASS",
"",
" // access flags 0x401",
" public abstract typeValue()Ljava/lang/Class;",
" default=com.example.buck.Foo.class",
"}")
.createAndCheckStubJar();
}
@Test
public void stubsEnums() throws IOException {
tester
.setSourceFile(
"A.java", "package com.example.buck;", "public enum A {", " Value1,", " Value2", "}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x4031",
"// signature Ljava/lang/Enum<Lcom/example/buck/A;>;",
"// declaration: com/example/buck/A extends java.lang.Enum<com.example.buck.A>",
"public final enum com/example/buck/A extends java/lang/Enum {",
"",
"",
" // access flags 0x4019",
" public final static enum Lcom/example/buck/A; Value1",
"",
" // access flags 0x4019",
" public final static enum Lcom/example/buck/A; Value2",
"",
" // access flags 0x9",
" public static values()[Lcom/example/buck/A;",
"",
" // access flags 0x9",
" public static valueOf(Ljava/lang/String;)Lcom/example/buck/A;",
"",
" // access flags 0x2",
" private <init>(Ljava/lang/String;I)V",
"}")
.createAndCheckStubJar();
}
@Test
public void stubsExplicitlyAbstractEnums() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"public enum A {",
" Value1 {",
" @Override",
" public void run() { }",
" };",
" public abstract void run();",
"}")
.addExpectedFullAbi(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x4421",
"// signature Ljava/lang/Enum<Lcom/example/buck/A;>;",
"// declaration: com/example/buck/A extends java.lang.Enum<com.example.buck.A>",
// abstract flag is removed in the stub:
"public abstract enum com/example/buck/A extends java/lang/Enum {",
"",
" // access flags 0x4008",
" static enum INNERCLASS com/example/buck/A$1 null null",
"",
" // access flags 0x4019",
" public final static enum Lcom/example/buck/A; Value1",
"",
" // access flags 0x101A",
" private final static synthetic [Lcom/example/buck/A; $VALUES",
"",
" // access flags 0x9",
" public static values()[Lcom/example/buck/A;",
"",
" // access flags 0x9",
" public static valueOf(Ljava/lang/String;)Lcom/example/buck/A;",
"",
" // access flags 0x2",
" // signature ()V",
" // declaration: void <init>()",
" private <init>(Ljava/lang/String;I)V",
"",
" // access flags 0x401",
" public abstract run()V",
"",
" // access flags 0x1000",
" synthetic <init>(Ljava/lang/String;ILcom/example/buck/A$1;)V",
"",
" // access flags 0x8",
" static <clinit>()V",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x4021",
"// signature Ljava/lang/Enum<Lcom/example/buck/A;>;",
"// declaration: com/example/buck/A extends java.lang.Enum<com.example.buck.A>",
"public enum com/example/buck/A extends java/lang/Enum {",
"",
"",
" // access flags 0x4019",
" public final static enum Lcom/example/buck/A; Value1",
"",
" // access flags 0x9",
" public static values()[Lcom/example/buck/A;",
"",
" // access flags 0x9",
" public static valueOf(Ljava/lang/String;)Lcom/example/buck/A;",
"",
" // access flags 0x401",
" public abstract run()V",
"",
" // access flags 0x2",
" private <init>(Ljava/lang/String;I)V",
"}")
.createAndCheckStubJar();
}
@Test
public void stubsImplicitlyAbstractEnums() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"public enum A implements Runnable {",
" Value1 {",
" @Override",
" public void run() { }",
" };",
"}")
.addExpectedFullAbi(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x4421",
"// signature Ljava/lang/Enum<Lcom/example/buck/A;>;Ljava/lang/Runnable;",
"// declaration: com/example/buck/A extends java.lang.Enum<com.example.buck.A> implements java.lang.Runnable",
// abstract flag is removed in the stub:
"public abstract enum com/example/buck/A extends java/lang/Enum implements java/lang/Runnable {",
"",
" // access flags 0x4008",
" static enum INNERCLASS com/example/buck/A$1 null null",
"",
" // access flags 0x4019",
" public final static enum Lcom/example/buck/A; Value1",
"",
" // access flags 0x101A",
" private final static synthetic [Lcom/example/buck/A; $VALUES",
"",
" // access flags 0x9",
" public static values()[Lcom/example/buck/A;",
"",
" // access flags 0x9",
" public static valueOf(Ljava/lang/String;)Lcom/example/buck/A;",
"",
" // access flags 0x2",
" // signature ()V",
" // declaration: void <init>()",
" private <init>(Ljava/lang/String;I)V",
"",
" // access flags 0x1000",
" synthetic <init>(Ljava/lang/String;ILcom/example/buck/A$1;)V",
"",
" // access flags 0x8",
" static <clinit>()V",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x4021",
"// signature Ljava/lang/Enum<Lcom/example/buck/A;>;Ljava/lang/Runnable;",
"// declaration: com/example/buck/A extends java.lang.Enum<com.example.buck.A> implements java.lang.Runnable",
"public enum com/example/buck/A extends java/lang/Enum implements java/lang/Runnable {",
"",
"",
" // access flags 0x4019",
" public final static enum Lcom/example/buck/A; Value1",
"",
" // access flags 0x9",
" public static values()[Lcom/example/buck/A;",
"",
" // access flags 0x9",
" public static valueOf(Ljava/lang/String;)Lcom/example/buck/A;",
"",
" // access flags 0x2",
" private <init>(Ljava/lang/String;I)V",
"}")
.createAndCheckStubJar();
}
@Test
public void stubsInnerClasses() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A {",
" public class B {",
" public int count;",
" public void foo() {}",
" }",
"}")
.addExpectedStub(
"com/example/buck/A$B",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A$B {",
"",
" // access flags 0x1",
" public INNERCLASS com/example/buck/A$B com/example/buck/A B",
"",
" // access flags 0x1",
" public I count",
"",
" // access flags 0x1",
" public <init>(Lcom/example/buck/A;)V",
"",
" // access flags 0x1",
" public foo()V",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
" // access flags 0x1",
" public INNERCLASS com/example/buck/A$B com/example/buck/A B",
"",
" // access flags 0x1",
" public <init>()V",
"}")
.createAndCheckStubJar();
}
@Test
public void stubsProtectedInnerClasses() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A {",
" protected class B {",
" public int count;",
" public void foo() {}",
" }",
"}")
.addExpectedStub(
"com/example/buck/A$B",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A$B {",
"",
" // access flags 0x4",
" protected INNERCLASS com/example/buck/A$B com/example/buck/A B",
"",
" // access flags 0x1",
" public I count",
"",
" // access flags 0x4",
" protected <init>(Lcom/example/buck/A;)V",
"",
" // access flags 0x1",
" public foo()V",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
" // access flags 0x4",
" protected INNERCLASS com/example/buck/A$B com/example/buck/A B",
"",
" // access flags 0x1",
" public <init>()V",
"}")
.createAndCheckStubJar();
}
@Test
public void stubsDefaultInnerClasses() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A {",
" class B {",
" public int count;",
" public void foo() {}",
" }",
"}")
.addExpectedStub(
"com/example/buck/A$B",
"// class version 52.0 (52)",
"// access flags 0x20",
"class com/example/buck/A$B {",
"",
" // access flags 0x0",
" INNERCLASS com/example/buck/A$B com/example/buck/A B",
"",
" // access flags 0x1",
" public I count",
"",
" // access flags 0x0",
" <init>(Lcom/example/buck/A;)V",
"",
" // access flags 0x1",
" public foo()V",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
" // access flags 0x0",
" INNERCLASS com/example/buck/A$B com/example/buck/A B",
"",
" // access flags 0x1",
" public <init>()V",
"}")
.createAndCheckStubJar();
}
// TODO(jkeljo): We should only be stubbing private inner classes when they are referenced in
// the ABI. That's a more involved change which I'll make soon, but for now I want to document
// the existing behavior and ensure it is consistent across class and source-based ABIs.
@Test
public void stubsPrivateInnerClasses() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A {",
" private class B {",
" public int count;",
" public void foo() {}",
" }",
"}")
.addExpectedStub(
"com/example/buck/A$B",
"// class version 52.0 (52)",
"// access flags 0x20",
"class com/example/buck/A$B {",
"",
" // access flags 0x2",
" private INNERCLASS com/example/buck/A$B com/example/buck/A B",
"",
" // access flags 0x1",
" public I count",
"",
" // access flags 0x1",
" public foo()V",
"",
" // access flags 0x2",
" private <init>(Lcom/example/buck/A;)V",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
"", // TODO(jkeljo): If the private inner is stubbed, there should be an entry here too
" // access flags 0x1",
" public <init>()V",
"}")
.createAndCheckStubJar();
}
@Test
public void stubsInnerEnums() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A {",
" public enum B { Value }",
"}")
.addExpectedStub(
"com/example/buck/A$B",
"// class version 52.0 (52)",
"// access flags 0x4031",
"// signature Ljava/lang/Enum<Lcom/example/buck/A$B;>;",
"// declaration: com/example/buck/A$B extends java.lang.Enum<com.example.buck.A$B>",
"public final enum com/example/buck/A$B extends java/lang/Enum {",
"",
" // access flags 0x4019",
" public final static enum INNERCLASS com/example/buck/A$B com/example/buck/A B",
"",
" // access flags 0x4019",
" public final static enum Lcom/example/buck/A$B; Value",
"",
" // access flags 0x9",
" public static values()[Lcom/example/buck/A$B;",
"",
" // access flags 0x9",
" public static valueOf(Ljava/lang/String;)Lcom/example/buck/A$B;",
"",
" // access flags 0x2",
" private <init>(Ljava/lang/String;I)V",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
" // access flags 0x4019",
" public final static enum INNERCLASS com/example/buck/A$B com/example/buck/A B",
"",
" // access flags 0x1",
" public <init>()V",
"}")
.createAndCheckStubJar();
}
@Test
public void stubsAbstractInnerEnums() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A {",
" public enum B implements Runnable {",
" Value {",
" @Override",
" public void run() {}",
" }",
" }",
"}")
.addExpectedFullAbi(
"com/example/buck/A$B",
"// class version 52.0 (52)",
"// access flags 0x4421",
"// signature Ljava/lang/Enum<Lcom/example/buck/A$B;>;Ljava/lang/Runnable;",
"// declaration: com/example/buck/A$B extends java.lang.Enum<com.example.buck.A$B> implements java.lang.Runnable",
// abstract flag is removed in the stub:
"public abstract enum com/example/buck/A$B extends java/lang/Enum implements java/lang/Runnable {",
"",
" // access flags 0x4409",
// abstract flag is removed in the stub:
" public static abstract enum INNERCLASS com/example/buck/A$B com/example/buck/A B",
" // access flags 0x4008",
" static enum INNERCLASS com/example/buck/A$B$1 null null",
" // access flags 0x1008",
" static synthetic INNERCLASS com/example/buck/A$1 null null",
"",
" // access flags 0x4019",
" public final static enum Lcom/example/buck/A$B; Value",
"",
" // access flags 0x101A",
" private final static synthetic [Lcom/example/buck/A$B; $VALUES",
"",
" // access flags 0x9",
" public static values()[Lcom/example/buck/A$B;",
"",
" // access flags 0x9",
" public static valueOf(Ljava/lang/String;)Lcom/example/buck/A$B;",
"",
" // access flags 0x2",
" // signature ()V",
" // declaration: void <init>()",
" private <init>(Ljava/lang/String;I)V",
"",
" // access flags 0x1000",
" synthetic <init>(Ljava/lang/String;ILcom/example/buck/A$1;)V",
"",
" // access flags 0x8",
" static <clinit>()V",
"}")
.addExpectedStub(
"com/example/buck/A$B",
"// class version 52.0 (52)",
"// access flags 0x4021",
"// signature Ljava/lang/Enum<Lcom/example/buck/A$B;>;Ljava/lang/Runnable;",
"// declaration: com/example/buck/A$B extends java.lang.Enum<com.example.buck.A$B> implements java.lang.Runnable",
"public enum com/example/buck/A$B extends java/lang/Enum implements java/lang/Runnable {",
"",
" // access flags 0x4009",
" public static enum INNERCLASS com/example/buck/A$B com/example/buck/A B",
"",
" // access flags 0x4019",
" public final static enum Lcom/example/buck/A$B; Value",
"",
" // access flags 0x9",
" public static values()[Lcom/example/buck/A$B;",
"",
" // access flags 0x9",
" public static valueOf(Ljava/lang/String;)Lcom/example/buck/A$B;",
"",
" // access flags 0x2",
" private <init>(Ljava/lang/String;I)V",
"}")
.addExpectedFullAbi(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
" // access flags 0x1008",
" static synthetic INNERCLASS com/example/buck/A$1 null null",
" // access flags 0x4409",
// abstract flag is removed in the stub:
" public static abstract enum INNERCLASS com/example/buck/A$B com/example/buck/A B",
"",
" // access flags 0x1",
" public <init>()V",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
" // access flags 0x4009",
" public static enum INNERCLASS com/example/buck/A$B com/example/buck/A B",
"",
" // access flags 0x1",
" public <init>()V",
"}")
.createAndCheckStubJar();
}
@Test
public void stubsNestedInnerClasses() throws IOException {
notYetImplementedForSource();
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A {",
" public class B {",
" public class C {",
" public int count;",
" public void foo() {}",
" }",
" }",
"}")
.addExpectedStub(
"com/example/buck/A$B$C",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A$B$C {",
"",
" // access flags 0x1",
" public INNERCLASS com/example/buck/A$B com/example/buck/A B",
" // access flags 0x1",
" public INNERCLASS com/example/buck/A$B$C com/example/buck/A$B C",
"",
" // access flags 0x1",
" public I count",
"",
" // access flags 0x1",
" public <init>(Lcom/example/buck/A$B;)V",
"",
" // access flags 0x1",
" public foo()V",
"}")
.addExpectedStub(
"com/example/buck/A$B",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A$B {",
"",
" // access flags 0x1",
" public INNERCLASS com/example/buck/A$B com/example/buck/A B",
" // access flags 0x1",
" public INNERCLASS com/example/buck/A$B$C com/example/buck/A$B C",
"",
" // access flags 0x1",
" public <init>(Lcom/example/buck/A;)V",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
" // access flags 0x1",
" public INNERCLASS com/example/buck/A$B com/example/buck/A B",
"",
" // access flags 0x1",
" public <init>()V",
"}")
.createAndCheckStubJar();
}
@Test
public void stubsReferencesToInnerClassesOfOtherTypes() throws IOException {
notYetImplementedForSource();
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A {",
" B.C field;",
"}",
"class B {",
" public class C { }",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
// An innerclass entry is present for B$C even though it's not an inner class of A, so
// that the compiler and runtime know how to interpret the name B$C:
" // access flags 0x1",
" public INNERCLASS com/example/buck/B$C com/example/buck/B C",
"",
" // access flags 0x0",
" Lcom/example/buck/B$C; field",
"",
" // access flags 0x1",
" public <init>()V",
"}")
.addExpectedStub(
"com/example/buck/B$C",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/B$C {",
"",
" // access flags 0x1",
" public INNERCLASS com/example/buck/B$C com/example/buck/B C",
"",
" // access flags 0x1",
" public <init>(Lcom/example/buck/B;)V",
"}")
.addExpectedStub(
"com/example/buck/B",
"// class version 52.0 (52)",
"// access flags 0x20",
"class com/example/buck/B {",
"",
" // access flags 0x1",
" public INNERCLASS com/example/buck/B$C com/example/buck/B C",
"",
" // access flags 0x0",
" <init>()V",
"}")
.createAndCheckStubJar();
}
@Test
public void stubsStaticMemberClasses() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A {",
" public static class B {",
" public int count;",
" public void foo() {}",
" }",
"}")
.addExpectedStub(
"com/example/buck/A$B",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A$B {",
"",
" // access flags 0x9",
" public static INNERCLASS com/example/buck/A$B com/example/buck/A B",
"",
" // access flags 0x1",
" public I count",
"",
" // access flags 0x1",
" public <init>()V",
"",
" // access flags 0x1",
" public foo()V",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
" // access flags 0x9",
" public static INNERCLASS com/example/buck/A$B com/example/buck/A B",
"",
" // access flags 0x1",
" public <init>()V",
"}")
.createAndCheckStubJar();
}
@Test
public void ignoresAnonymousClasses() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A {",
" public Runnable r = new Runnable() {",
" public void run() { }",
" };",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
"",
" // access flags 0x1",
" public Ljava/lang/Runnable; r",
"",
" // access flags 0x1",
" public <init>()V",
"}")
.createAndCheckStubJar();
}
@Test
public void ignoresLocalClasses() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A {",
" public void method() {",
" class Local { };",
" }",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
"",
" // access flags 0x1",
" public <init>()V",
"",
" // access flags 0x1",
" public method()V",
"}")
.createAndCheckStubJar();
}
@Test
public void preservesThrowsClausesOnInnerClassConstructors() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A {",
" public class B {",
" B() throws Exception, Throwable {",
" }",
" }",
"}")
.addExpectedStub(
"com/example/buck/A$B",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A$B {",
"",
" // access flags 0x1",
" public INNERCLASS com/example/buck/A$B com/example/buck/A B",
"",
" // access flags 0x0",
" <init>(Lcom/example/buck/A;)V throws java/lang/Exception java/lang/Throwable ",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
" // access flags 0x1",
" public INNERCLASS com/example/buck/A$B com/example/buck/A B",
"",
" // access flags 0x1",
" public <init>()V",
"}")
.createAndCheckStubJar();
}
@Test
public void abiSafeChangesResultInTheSameOutputJar() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A {",
" protected final static int count = 42;",
" public String getGreeting() { return \"hello\"; }",
" Class<?> clazz;",
" public int other;",
"}")
.createStubJar()
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A {",
" protected final static int count = 42;",
" public String getGreeting() { return \"merhaba\"; }",
" Class<?> clazz = String.class;",
" public int other = 32;",
"}")
.assertStubJarIsIdentical();
}
@Test
public void shouldIncludeInnerClassTypeParameterReferenceInMethodParameter() throws IOException {
notYetImplementedForSource();
tester
.setSourceFile(
"Outer.java",
"package com.example.buck;",
"public class Outer {",
" public enum Inner { }",
"}")
.createStubJar()
.addStubJarToClasspath()
.setSourceFile(
"A.java",
"package com.example.buck;",
"import java.util.Set;",
"import com.example.buck.Outer;",
"public interface A {",
" void foo(Set<Outer.Inner> test);",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x601",
"public abstract interface com/example/buck/A {",
"",
" // access flags 0x4019",
" public final static enum INNERCLASS com/example/buck/Outer$Inner com/example/buck/Outer Inner",
"",
" // access flags 0x401",
" // signature (Ljava/util/Set<Lcom/example/buck/Outer$Inner;>;)V",
" // declaration: void foo(java.util.Set<com.example.buck.Outer$Inner>)",
" public abstract foo(Ljava/util/Set;)V",
"}")
.createAndCheckStubJar();
}
@Test
public void ordersChangesResultInADifferentOutputJar() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A {",
" protected final static int count = 42;",
" public String getGreeting() { return \"hello\"; }",
" Class<?> clazz;",
" public int other;",
"}")
.createStubJar()
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A {",
" Class<?> clazz;",
" public String getGreeting() { return \"hello\"; }",
" protected final static int count = 42;",
" public int other;",
"}")
.assertStubJarIsDifferent();
}
@Test
public void shouldIncludeStaticFields() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A {",
" public static String foo;",
" public final static int count = 42;",
" protected static void method() {}",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
"",
" // access flags 0x9",
" public static Ljava/lang/String; foo",
"",
" // access flags 0x19",
" public final static I count = 42",
"",
" // access flags 0x1",
" public <init>()V",
"",
" // access flags 0xC",
" protected static method()V",
"}")
.createAndCheckStubJar();
}
@Test
public void innerClassesInStubsCanBeCompiledAgainst() throws IOException {
tester
.setSourceFile(
"Outer.java",
"package com.example.buck;",
"public class Outer {",
" public class Inner {",
" public String getGreeting() { return \"hola\"; }",
" }",
"}")
.createStubJar()
.addStubJarToClasspath()
.setSourceFile(
"A.java",
"package com.example.buck2;", // Note: different package
"import com.example.buck.Outer;", // Inner class becomes available
"public class A {",
" private Outer.Inner field;", // Reference the inner class
"}")
.testCanCompile();
}
@Test
public void staticMethodOfInnerClassInStubsCanBeCompiledAgainst() throws IOException {
tester
.setSourceFile(
"Outer.java",
"package com.example.buck;",
"public class Outer {",
" public static class Inner {",
" public static int testStatic() { return 0; }",
" }",
"}")
.createStubJar()
.addStubJarToClasspath()
.setSourceFile(
"A.java",
"package com.example.buck2;",
"import com.example.buck.Outer;",
"public class A {",
" public void testMethod() {",
" int test = Outer.Inner.testStatic();",
" }",
"}")
.testCanCompile();
}
@Test
public void privateFieldInInnerClassInStubsCanBeCompiledAgainst() throws IOException {
tester
.setSourceFile(
"Outer.java",
"package com.example.buck;",
"public class Outer {",
" public String getInnerString() {",
" return new Outer.Inner().innerPrivateString;", // Relies on synthetic method
" }",
" private static final class Inner {",
" private final String innerPrivateString = \"hi!\";",
" }",
"}")
.createStubJar()
.addStubJarToClasspath()
.setSourceFile(
"A.java",
"package com.example.buck2;",
"import com.example.buck.Outer;",
"public class A {",
" public String testMethod() {",
" Outer test = new Outer();",
" return test.getInnerString();",
" }",
"}")
.testCanCompile();
}
@Test
public void nestedClassInStubsCanBeCompiledAgainst() throws IOException {
tester
.setSourceFile(
"Outer.java",
"package com.example.buck;",
"public class Outer {",
" public class Inner {",
" public class Nested { }",
" }",
"}")
.createStubJar()
.addStubJarToClasspath()
.setSourceFile(
"A.java",
"package com.example.buck2;",
"import com.example.buck.Outer;",
"public class A {",
" private Outer.Inner.Nested field;",
"}")
.testCanCompile();
}
@Test
public void methodReturningPrivateInnerClassInStubsCanBeCompiledAgainst() throws IOException {
tester
.setSourceFile(
"Test.java",
"package com.example.buck;",
"public class Test {",
" public static PrivateInner getPrivateInner() {",
" return new PrivateInner();",
" }",
" private static class PrivateInner { }",
"}")
.createStubJar()
.addStubJarToClasspath()
.setSourceFile(
"A.java",
"package com.example.buck2;",
"import com.example.buck.Test;",
"public class A {",
" public void test() {",
" Object privateInnerObject = Test.getPrivateInner();",
" }",
"}")
.testCanCompile();
}
@Test
public void methodOfPrivateSuperClassOfInnerPublicClassInStubsCanBeCompiledAgainst()
throws IOException {
tester
.setSourceFile(
"Test.java",
"package com.example.buck;",
"public class Test {",
" private class PrivateInner { ",
" public void foo() { }",
" }",
" public class PublicInner extends PrivateInner { }",
"}")
.createStubJar()
.addStubJarToClasspath()
.setSourceFile(
"A.java",
"package com.example.buck2;",
"import com.example.buck.Test;",
"public class A {",
" public void test() {",
" Test test = new Test();",
" Test.PublicInner inner = test.new PublicInner();",
" inner.foo();",
" }",
"}")
.testCanCompile();
}
@Test
public void bridgeMethodInStubsCanBeCompiledAgainst() throws IOException {
notYetImplementedForMissingClasspath();
tester
.setSourceFile(
"TestGenericInterface.java",
"package com.example.buck;",
"public interface TestGenericInterface<T> {",
" public int compare(T a, T b);",
"}")
.createStubJar()
.addStubJarToClasspath()
.setSourceFile(
"TestComparator.java",
"package com.example.buck;",
"import com.example.buck.TestGenericInterface;",
"public class TestComparator implements TestGenericInterface<Integer> {",
" public int compare(Integer a, Integer b) { return 1; }",
"}")
.createStubJar()
.addStubJarToClasspath()
.setSourceFile(
"A.java",
"package com.example.buck2;",
"import com.example.buck.TestComparator;",
"import com.example.buck.TestGenericInterface;",
"public class A {",
" public void test() {",
" Object first = 1;",
" Object second = 2;",
" TestGenericInterface com = new TestComparator();",
" int result = com.compare(first, second);", // Relies on bridge method
" }",
"}")
.testCanCompile();
}
@Test
public void enumInStubsCanBeCompiledAgainst() throws IOException {
tester
.setSourceFile(
"TestEnum.java",
"package com.example.buck;",
"public enum TestEnum {",
" Value1,",
" Value2",
"}")
.createStubJar()
.addStubJarToClasspath()
.setSourceFile(
"A.java",
"package com.example.buck2;",
"import com.example.buck.TestEnum;",
"public class A {",
" TestEnum testEnum = TestEnum.Value1;",
"}")
.testCanCompile();
}
@Test
public void genericClassInStubsCanBeCompiledAgainst() throws IOException {
tester
.setSourceFile(
"GenericClass.java",
"package com.example.buck;",
"public class GenericClass<T> {",
" public T theField;",
"}")
.createStubJar()
.addStubJarToClasspath()
.setSourceFile(
"A.java",
"package com.example.buck2;",
"import com.example.buck.GenericClass;",
"public class A {",
" public void test() {",
" GenericClass<String> field = new GenericClass<>();",
" field.theField = \"facebook\";",
" }",
"}")
.testCanCompile();
}
@Test
public void classExtendingGenericClassInStubsCanBeCompiledAgainst() throws IOException {
tester
.setSourceFile(
"GenericClass.java",
"package com.example.buck;",
"public class GenericClass<T> {",
" public T theField;",
"}")
.createStubJar()
.addStubJarToClasspath()
.setSourceFile(
"A.java",
"package com.example.buck2;",
"import com.example.buck.GenericClass;",
"public class A<T, M> extends GenericClass<T> {",
" public M otherField;",
" public A(T t, M m) {",
" this.theField = t;",
" this.otherField = m;",
" }",
"}")
.testCanCompile();
}
@Test
public void privateConstructorResultsInCorrectCompileError() throws IOException {
tester
.setSourceFile(
"PrivateTest.java",
"package com.example.buck;",
"public class PrivateTest {",
" private PrivateTest() { }",
"}")
.createStubJar()
.addStubJarToClasspath()
.setSourceFile(
"A.java",
"package com.example.buck2;",
"import com.example.buck.PrivateTest;",
"public class A {",
" public void foo() {",
" PrivateTest test = new PrivateTest();",
" }",
"}")
.addExpectedCompileError(
Joiner.on('\n')
.join(
"A.java:5: error: PrivateTest() has private access in com.example.buck.PrivateTest",
" PrivateTest test = new PrivateTest();",
" ^"))
.testCanCompile();
}
@Test
public void shouldPreserveSynchronizedKeywordOnMethods() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A {",
" public synchronized void doMagic() {}",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
"",
" // access flags 0x1",
" public <init>()V",
"",
" // access flags 0x21",
" public synchronized doMagic()V",
"}")
.createAndCheckStubJar();
}
@Test
public void shouldKeepMultipleFieldsWithSameDescValue() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A {",
" public static final A SEVERE = new A();",
" public static final A NOT_SEVERE = new A();",
" public static final A QUITE_MILD = new A();",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
"",
" // access flags 0x19",
" public final static Lcom/example/buck/A; SEVERE",
"",
" // access flags 0x19",
" public final static Lcom/example/buck/A; NOT_SEVERE",
"",
" // access flags 0x19",
" public final static Lcom/example/buck/A; QUITE_MILD",
"",
" // access flags 0x1",
" public <init>()V",
"}")
.createAndCheckStubJar();
}
@Test
public void shouldNotStubClinit() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A {",
" public static int i = 3;",
"}")
.addExpectedFullAbi(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
"",
" // access flags 0x9",
" public static I i",
"",
" // access flags 0x1",
" public <init>()V",
"",
" // access flags 0x8",
" static <clinit>()V", // Should not stub this, even though it's default access
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
"",
" // access flags 0x9",
" public static I i",
"",
" // access flags 0x1",
" public <init>()V",
"}")
.createAndCheckStubJar();
}
@Test
public void stubJarIsEquallyAtHomeWalkingADirectoryOfClassFiles()
throws InterruptedException, IOException {
Path fullJarPath =
compileToJar(
EMPTY_CLASSPATH,
Collections.emptyList(),
"A.java",
Joiner.on("\n")
.join(
ImmutableList.of(
"package com.example.buck;",
"public class A {",
" public String toString() { return null; }",
" public void eatCake() {}",
"}")),
temp.newFolder());
Path classDir = temp.newFolder().toPath();
Unzip.extractZipFile(fullJarPath, classDir, Unzip.ExistingFileMode.OVERWRITE);
Path stubJarPath = createStubJar(classDir);
tester
.setStubJar(stubJarPath)
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
"",
" // access flags 0x1",
" public <init>()V",
"",
" // access flags 0x1",
" public toString()Ljava/lang/String;",
"",
" // access flags 0x1",
" public eatCake()V",
"}")
.checkStubJar();
}
@Test
public void shouldNotIncludeSyntheticFields() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A {",
" public void method() {",
" assert false;",
" }",
"}")
.addExpectedFullAbi(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
"",
" // access flags 0x1018",
" final static synthetic Z $assertionsDisabled", // Should remove this field
"",
" // access flags 0x1",
" public <init>()V",
"",
" // access flags 0x1",
" public method()V",
"",
" // access flags 0x8",
" static <clinit>()V",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
"",
" // access flags 0x1",
" public <init>()V",
"",
" // access flags 0x1",
" public method()V",
"}")
.createAndCheckStubJar();
}
@Test
public void shouldNotIncludeSyntheticClasses() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A {",
" enum E { Value };",
" public void method(E e) {",
" switch (e) {",
" case Value: break;",
" }",
" }",
"}")
.addExpectedStub(
"com/example/buck/A$E",
"// class version 52.0 (52)",
"// access flags 0x4030",
"// signature Ljava/lang/Enum<Lcom/example/buck/A$E;>;",
"// declaration: com/example/buck/A$E extends java.lang.Enum<com.example.buck.A$E>",
"final enum com/example/buck/A$E extends java/lang/Enum {",
"",
" // access flags 0x4018",
" final static enum INNERCLASS com/example/buck/A$E com/example/buck/A E",
"",
" // access flags 0x4019",
" public final static enum Lcom/example/buck/A$E; Value",
"",
" // access flags 0x9",
" public static values()[Lcom/example/buck/A$E;",
"",
" // access flags 0x9",
" public static valueOf(Ljava/lang/String;)Lcom/example/buck/A$E;",
"",
" // access flags 0x2",
" private <init>(Ljava/lang/String;I)V",
"}")
.addExpectedFullAbi(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
" // access flags 0x1008",
" static synthetic INNERCLASS com/example/buck/A$1 null null",
// Should not include this class
" // access flags 0x4018",
" final static enum INNERCLASS com/example/buck/A$E com/example/buck/A E",
"",
" // access flags 0x1",
" public <init>()V",
"",
" // access flags 0x1",
" public method(Lcom/example/buck/A$E;)V",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A {",
"",
" // access flags 0x4018",
" final static enum INNERCLASS com/example/buck/A$E com/example/buck/A E",
"",
" // access flags 0x1",
" public <init>()V",
"",
" // access flags 0x1",
" public method(Lcom/example/buck/A$E;)V",
"}")
.createAndCheckStubJar();
}
@Test
public void shouldNotIncludeSyntheticMethods() throws IOException {
tester
.setSourceFile(
"A.java", "package com.example.buck;", "public enum A {", " Value1 { }", "}")
.addExpectedFullAbi(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x4021",
"// signature Ljava/lang/Enum<Lcom/example/buck/A;>;",
"// declaration: com/example/buck/A extends java.lang.Enum<com.example.buck.A>",
"public enum com/example/buck/A extends java/lang/Enum {",
"",
" // access flags 0x4008",
" static enum INNERCLASS com/example/buck/A$1 null null",
"",
" // access flags 0x4019",
" public final static enum Lcom/example/buck/A; Value1",
"",
" // access flags 0x101A",
" private final static synthetic [Lcom/example/buck/A; $VALUES",
"",
" // access flags 0x9",
" public static values()[Lcom/example/buck/A;",
"",
" // access flags 0x9",
" public static valueOf(Ljava/lang/String;)Lcom/example/buck/A;",
"",
" // access flags 0x2",
" // signature ()V",
" // declaration: void <init>()",
" private <init>(Ljava/lang/String;I)V",
"",
" // access flags 0x1000",
" synthetic <init>(Ljava/lang/String;ILcom/example/buck/A$1;)V",
// Should not include this method
"",
" // access flags 0x8",
" static <clinit>()V",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x4021",
"// signature Ljava/lang/Enum<Lcom/example/buck/A;>;",
"// declaration: com/example/buck/A extends java.lang.Enum<com.example.buck.A>",
"public enum com/example/buck/A extends java/lang/Enum {",
"",
"",
" // access flags 0x4019",
" public final static enum Lcom/example/buck/A; Value1",
"",
" // access flags 0x9",
" public static values()[Lcom/example/buck/A;",
"",
" // access flags 0x9",
" public static valueOf(Ljava/lang/String;)Lcom/example/buck/A;",
"",
" // access flags 0x2",
" private <init>(Ljava/lang/String;I)V",
"}")
.createAndCheckStubJar();
}
@Test
public void shouldNotIncludeGenericBridgeMethods() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A implements Comparable<A> {",
" public int compareTo(A other) {",
" return 0;",
" }",
"}")
.addExpectedFullAbi(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"// signature Ljava/lang/Object;Ljava/lang/Comparable<Lcom/example/buck/A;>;",
"// declaration: com/example/buck/A implements java.lang.Comparable<com.example.buck.A>",
"public class com/example/buck/A implements java/lang/Comparable {",
"",
"",
" // access flags 0x1",
" public <init>()V",
"",
" // access flags 0x1",
" public compareTo(Lcom/example/buck/A;)I",
"",
" // access flags 0x1041",
" public synthetic bridge compareTo(Ljava/lang/Object;)I",
// Should include this method
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"// signature Ljava/lang/Object;Ljava/lang/Comparable<Lcom/example/buck/A;>;",
"// declaration: com/example/buck/A implements java.lang.Comparable<com.example.buck.A>",
"public class com/example/buck/A implements java/lang/Comparable {",
"",
"",
" // access flags 0x1",
" public <init>()V",
"",
" // access flags 0x1",
" public compareTo(Lcom/example/buck/A;)I",
"}")
.createAndCheckStubJar();
}
/**
* There's this fun case in javac where if a public class has a non-public superclass, all public
* methods on the superclass get bridges in the subclass. Let's make sure
*/
@Test
public void shouldNotIncludeNonPublicBaseClassBridgeMethods() throws IOException {
tester
.setSourceFile(
"A.java",
"package com.example.buck;",
"public class A extends B {",
"}",
"class B {",
" public void foo() {};",
"}")
.addExpectedFullAbi(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A extends com/example/buck/B {",
"",
"",
" // access flags 0x1",
" public <init>()V",
"",
" // access flags 0x1041",
" public synthetic bridge foo()V",
"}")
.addExpectedStub(
"com/example/buck/A",
"// class version 52.0 (52)",
"// access flags 0x21",
"public class com/example/buck/A extends com/example/buck/B {",
"",
"",
" // access flags 0x1",
" public <init>()V",
"}")
.addExpectedStub(
"com/example/buck/B",
"// class version 52.0 (52)",
"// access flags 0x20",
"class com/example/buck/B {",
"",
"",
" // access flags 0x0",
" <init>()V",
"",
" // access flags 0x1",
" public foo()V",
"}")
.createAndCheckStubJar();
}
private Path createStubJar(
SortedSet<Path> classpath, String fileName, String source, Path outputDir)
throws IOException {
Path stubJar = outputDir.resolve("stub.jar");
try (TestCompiler testCompiler = new TestCompiler()) {
testCompiler.init();
testCompiler.useFrontendOnlyJavacTask();
testCompiler.addSourceFileContents(fileName, source);
testCompiler.addClasspath(classpath);
testCompiler.setProcessors(
ImmutableList.of(
new AbstractProcessor() {
@Override
public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton("*");
}
@Override
public boolean process(
Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
return false;
}
}));
StubGenerator generator =
new StubGenerator(
SourceVersion.RELEASE_8,
testCompiler.getElements(),
testCompiler.getFileManager(),
new DefaultBuckEventBus(new FakeClock(0), new BuildId()));
testCompiler.addPostEnterCallback(generator::generate);
testCompiler.compile();
testCompiler.getClasses().createJar(stubJar, true);
}
return stubJar;
}
private Path createStubJar(Path fullJar) throws IOException {
Path stubJar = fullJar.getParent().resolve("stub.jar");
new StubJar(fullJar).setSourceAbiCompatible(true).writeTo(filesystem, stubJar);
return stubJar;
}
private Path compileToJar(
SortedSet<Path> classpath,
List<Processor> processors,
String fileName,
String source,
File outputDir)
throws IOException {
try (TestCompiler compiler = new TestCompiler()) {
compiler.init();
compiler.addSourceFileContents(fileName, source);
compiler.addClasspath(classpath);
compiler.setProcessors(processors);
compiler.compile();
Path jarPath = outputDir.toPath().resolve("output.jar");
compiler.getClasses().createJar(jarPath, false);
return jarPath;
}
}
private Tester createAnnotationFullJar() throws IOException {
return tester
.setSourceFile(
"Foo.java",
"package com.example.buck;",
"import java.lang.annotation.*;",
"import static java.lang.annotation.ElementType.*;",
"@Retention(RetentionPolicy.RUNTIME)",
"@Target(value={CONSTRUCTOR, FIELD, METHOD, PARAMETER, TYPE})",
"public @interface Foo {",
" int primitiveValue() default 0;",
" String[] stringArrayValue() default {\"Hello\"};",
" Retention annotationValue() default @Retention(RetentionPolicy.SOURCE);",
" Retention[] annotationArrayValue() default {};",
" RetentionPolicy enumValue () default RetentionPolicy.CLASS;",
" Class typeValue() default Foo.class;",
" @Target({TYPE_PARAMETER, TYPE_USE})",
" @interface TypeAnnotation { }",
"}")
.compileFullJar();
}
private void notYetImplementedForMissingClasspath() {
assumeThat(testingMode, Matchers.not(Matchers.equalTo(MODE_SOURCE_BASED_MISSING_DEPS)));
}
private void notYetImplementedForSource() {
assumeThat(testingMode, Matchers.equalTo(MODE_JAR_BASED));
}
private final class Tester {
private final List<String> expectedDirectory = new ArrayList<>();
private final List<String> actualDirectory = new ArrayList<>();
private final Map<String, List<String>> expectedFullAbis = new HashMap<>();
private final Map<String, List<String>> actualFullAbis = new HashMap<>();
private final Map<String, List<String>> expectedStubs = new HashMap<>();
private final Map<String, List<String>> actualStubs = new HashMap<>();
private final List<String> expectedCompileErrors = new ArrayList<>();
private String sourceFileName = "";
private String sourceFileContents = "";
private ImmutableSortedSet<Path> classpath = EMPTY_CLASSPATH;
private Path stubJarPath;
private Path fullJarPath;
public Tester setSourceFile(String fileName, String... lines) {
sourceFileName = fileName;
sourceFileContents = Joiner.on('\n').join(lines);
return this;
}
public Tester addExpectedFullAbi(String classBinaryName, String... abiLines) {
String filePath = classBinaryName + ".class";
expectedFullAbis.put(filePath, Arrays.asList(abiLines));
return this;
}
public Tester addExpectedStub(String classBinaryName, String... stubLines) {
String filePath = classBinaryName + ".class";
expectedDirectory.add(filePath);
expectedStubs.put(filePath, Arrays.asList(stubLines));
return this;
}
public Tester addExpectedCompileError(String compileError) {
expectedCompileErrors.add(compileError);
return this;
}
public Tester setStubJar(Path stubJarPath) {
this.stubJarPath = stubJarPath;
return this;
}
public Tester createAndCheckStubJar() throws IOException {
if (!expectedFullAbis.isEmpty()) {
compileFullJar();
dumpFullJarAbi();
for (String entryName : expectedDirectory) {
if (!expectedFullAbis.containsKey(entryName)) {
// You don't have to put expectations for all full ABIs, only those that you feel really
// need to be a certain way for the test to be valid.
continue;
}
assertEquals(
"Full ABI for " + entryName + " is not what was expected.",
Joiner.on('\n').join(expectedFullAbis.get(entryName)),
Joiner.on('\n').join(actualFullAbis.get(entryName)));
}
}
createStubJar();
return checkStubJar();
}
public Tester checkStubJar() throws IOException {
dumpStubJar();
assertEquals("File list is not correct.", expectedDirectory, actualDirectory);
for (String entryName : expectedDirectory) {
assertEquals(
"Stub for " + entryName + " is not correct",
Joiner.on('\n').join(expectedStubs.get(entryName)),
Joiner.on('\n').join(actualStubs.get(entryName)));
}
return this;
}
public Tester createStubJar() throws IOException {
File outputDir = temp.newFolder();
if (testingMode != MODE_JAR_BASED) {
stubJarPath =
StubJarTest.this.createStubJar(
testingMode == MODE_SOURCE_BASED ? classpath : Collections.emptySortedSet(),
sourceFileName,
sourceFileContents,
outputDir.toPath());
} else {
compileFullJar();
stubJarPath = StubJarTest.this.createStubJar(fullJarPath);
}
return this;
}
public Tester compileFullJar() throws IOException {
File outputDir = temp.newFolder();
fullJarPath =
compileToJar(
classpath, Collections.emptyList(), sourceFileName, sourceFileContents, outputDir);
return this;
}
public Tester addStubJarToClasspath() throws IOException {
classpath =
ImmutableSortedSet.<Path>naturalOrder().addAll(classpath).add(stubJarPath).build();
return this;
}
public Tester addFullJarToClasspath() throws IOException {
classpath =
ImmutableSortedSet.<Path>naturalOrder().addAll(classpath).add(fullJarPath).build();
return this;
}
public void testCanCompile() throws IOException {
File outputDir = temp.newFolder();
try (TestCompiler compiler = new TestCompiler()) {
compiler.init();
compiler.addSourceFileContents(sourceFileName, sourceFileContents);
compiler.addClasspath(classpath);
compiler.setProcessors(Collections.emptyList());
compiler.setAllowCompilationErrors(!expectedCompileErrors.isEmpty());
compiler.compile();
if (!expectedCompileErrors.isEmpty()) {
List<String> actualCompileErrors =
compiler
.getDiagnosticMessages()
.stream()
.map(
diagnostic ->
diagnostic.substring(diagnostic.lastIndexOf(File.separatorChar) + 1))
.collect(Collectors.toList());
assertEquals(expectedCompileErrors, actualCompileErrors);
}
fullJarPath = outputDir.toPath().resolve("output.jar");
compiler.getClasses().createJar(fullJarPath, false);
}
}
public void assertStubJarIsIdentical() throws IOException {
Sha1HashCode originalHash = filesystem.computeSha1(stubJarPath);
createStubJar();
assertEquals(originalHash, filesystem.computeSha1(stubJarPath));
}
public void assertStubJarIsDifferent() throws IOException {
Sha1HashCode originalHash = filesystem.computeSha1(stubJarPath);
createStubJar();
assertNotEquals(originalHash, filesystem.computeSha1(stubJarPath));
}
@SuppressWarnings("unused")
public Tester dumpTestCode(boolean includeFullAbi) throws IOException {
if (includeFullAbi) {
compileFullJar();
dumpFullJarAbi();
}
createStubJar();
dumpStubJar();
String indent = " ";
StringBuilder result = new StringBuilder();
result.append("Test lines:\n");
result.append(" tester\n");
result.append(" .setSourceFile(\n");
result.append(indent);
result.append('"');
result.append(sourceFileName);
for (String sourceLine : sourceFileContents.split("\n")) {
result.append("\",\n");
result.append(indent);
result.append('"');
result.append(sourceLine.replace("\"", "\\\""));
}
result.append("\")\n");
for (String fileName : actualDirectory) {
if (includeFullAbi) {
result.append(" .addExpectedFullAbi(\n");
result.append(indent);
result.append('"');
result.append(fileName.substring(0, fileName.length() - ".class".length()));
for (String abiLine : actualFullAbis.get(fileName)) {
result.append("\",\n");
result.append(indent);
result.append('"');
result.append(abiLine.replace("\"", "\\\""));
}
result.append("\")\n");
}
result.append(" .addExpectedStub(\n");
result.append(indent);
result.append('"');
result.append(fileName.substring(0, fileName.length() - ".class".length()));
for (String stubLine : actualStubs.get(fileName)) {
result.append("\",\n");
result.append(indent);
result.append('"');
result.append(stubLine.replace("\"", "\\\""));
}
result.append("\")\n");
}
result.append(" .createAndCheckStubJar();\n");
fail(result.toString());
return this;
}
protected void dumpStubJar() throws IOException {
try (JarFile file = new JarFile(stubJarPath.toFile())) {
Iterable<JarEntry> entries = file.stream()::iterator;
for (JarEntry entry : entries) {
String name = entry.getName();
if (JarFile.MANIFEST_NAME.equals(name)) {
continue;
}
actualDirectory.add(name);
actualStubs.put(
name, new JarDumper().dumpEntry(file, entry).collect(Collectors.toList()));
}
}
}
protected void dumpFullJarAbi() throws IOException {
try (JarFile file = new JarFile(fullJarPath.toFile())) {
Iterable<JarEntry> entries = file.stream()::iterator;
for (JarEntry entry : entries) {
String name = entry.getName();
if (JarFile.MANIFEST_NAME.equals(name)) {
continue;
}
actualFullAbis.put(
name,
new JarDumper()
.setAsmFlags(
ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES)
.dumpEntry(file, entry)
.collect(Collectors.toList()));
}
}
}
}
}