/*
* Copyright 2017-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.assertThat;
import com.facebook.buck.jvm.java.testutil.compiler.Classes;
import com.facebook.buck.jvm.java.testutil.compiler.CompilerTreeApiTestRunner;
import com.facebook.buck.jvm.java.testutil.compiler.TestCompiler;
import java.io.IOException;
import java.util.SortedSet;
import org.hamcrest.Matchers;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.objectweb.asm.ClassReader;
@RunWith(CompilerTreeApiTestRunner.class)
public class ClassReferenceTrackerTest {
@Rule public TestCompiler testCompiler = new TestCompiler();
@Rule public TemporaryFolder temp = new TemporaryFolder();
@Test
public void testRecordsSuperclassesAndInterfaces() throws IOException {
assertThat(
getReferencedClassNames(
"abstract class Foo extends java.util.ArrayList implements CharSequence, Runnable {",
"}"),
Matchers.containsInAnyOrder(
"java/util/ArrayList", "java/lang/Runnable", "java/lang/CharSequence"));
}
@Test
public void testRecordsMethodReturnTypes() throws IOException {
assertThat(
getReferencedClassNames("class Foo {", " String foo() { return null; }", "}"),
Matchers.containsInAnyOrder("java/lang/String", "java/lang/Object"));
}
@Test
public void testRecordsMethodParameterTypes() throws IOException {
assertThat(
getReferencedClassNames("class Foo {", " void foo(String s) { }", "}"),
Matchers.containsInAnyOrder("java/lang/String", "java/lang/Object"));
}
@Test
public void testRecordsMethodThrowsTypes() throws IOException {
assertThat(
getReferencedClassNames("class Foo {", " void foo() throws Exception { }", "}"),
Matchers.containsInAnyOrder("java/lang/Exception", "java/lang/Object"));
}
@Test
public void testRecordsFieldTypes() throws IOException {
assertThat(
getReferencedClassNames("class Foo {", " String s;", "}"),
Matchers.containsInAnyOrder("java/lang/String", "java/lang/Object"));
}
@Test
public void testRecordsAnnotationsOnClasses() throws IOException {
assertThat(
getReferencedClassNames("@Deprecated", "class Foo { }"),
Matchers.containsInAnyOrder("java/lang/Deprecated", "java/lang/Object"));
}
@Test
public void testRecordsTypeAnnotationsOnClasses() throws IOException {
assertThat(
getReferencedClassNames(
"import java.lang.annotation.*;",
"abstract class Foo implements @Bar CharSequence { }",
"@Target(ElementType.TYPE_USE)",
"@interface Bar { }"),
Matchers.containsInAnyOrder("Bar", "java/lang/CharSequence", "java/lang/Object"));
}
@Test
public void testRecordsAnnotationsOnMethods() throws IOException {
assertThat(
getReferencedClassNames("class Foo {", " @Deprecated", " void foo() { }", "}"),
Matchers.containsInAnyOrder("java/lang/Deprecated", "java/lang/Object"));
}
@Test
public void testRecordsAnnotationsOnMethodParameters() throws IOException {
assertThat(
getReferencedClassNames("class Foo {", " void foo(@Deprecated Object parameter) { }", "}"),
Matchers.containsInAnyOrder("java/lang/Deprecated", "java/lang/Object"));
}
@Test
public void testRecordsTypeAnnotationsOnMethodParameters() throws IOException {
assertThat(
getReferencedClassNames(
"import java.lang.annotation.*;",
"class Foo {",
" void foo(@Bar CharSequence c) { }",
"}",
"@Target(ElementType.TYPE_USE)",
"@interface Bar { }"),
Matchers.containsInAnyOrder("Bar", "java/lang/CharSequence", "java/lang/Object"));
}
@Test
public void testRecordsAnnotationsOnFields() throws IOException {
assertThat(
getReferencedClassNames("class Foo {", " @Deprecated", " Object field;", "}"),
Matchers.containsInAnyOrder("java/lang/Deprecated", "java/lang/Object"));
}
@Test
public void testRecordsTypeAnnotationsOnFields() throws IOException {
assertThat(
getReferencedClassNames(
"import java.lang.annotation.*;",
"class Foo {",
" @Bar CharSequence field;",
"}",
"@Target(ElementType.TYPE_USE)",
"@interface Bar { }"),
Matchers.containsInAnyOrder("Bar", "java/lang/CharSequence", "java/lang/Object"));
}
@Test
public void testRecordsGenericSuperclassTypeArguments() throws IOException {
assertThat(
getReferencedClassNames("class Foo extends java.util.HashMap<String, Integer> {", "}"),
Matchers.containsInAnyOrder("java/util/HashMap", "java/lang/String", "java/lang/Integer"));
}
@Test
public void testRecordsGenericInterfaceTypeArguments() throws IOException {
assertThat(
getReferencedClassNames(
"abstract class Foo implements java.util.Map<String, Integer> {", "}"),
Matchers.containsInAnyOrder(
"java/util/Map", "java/lang/String", "java/lang/Integer", "java/lang/Object"));
}
@Test
public void testRecordsGenericFieldTypeArguments() throws IOException {
assertThat(
getReferencedClassNames("class Foo {", " java.util.HashMap<String, Integer> field;", "}"),
Matchers.containsInAnyOrder(
"java/util/HashMap", "java/lang/String", "java/lang/Integer", "java/lang/Object"));
}
@Test
public void testRecordsGenericReturnTypeTypeArguments() throws IOException {
assertThat(
getReferencedClassNames(
"class Foo {" + " java.util.HashMap<String, Integer> foo() { return null; }", "}"),
Matchers.containsInAnyOrder(
"java/util/HashMap", "java/lang/String", "java/lang/Integer", "java/lang/Object"));
}
@Test
public void testRecordsGenericParameterTypeArguments() throws IOException {
assertThat(
getReferencedClassNames(
"class Foo {" + " void foo(java.util.HashMap<String, Integer> m) { }", "}"),
Matchers.containsInAnyOrder(
"java/util/HashMap", "java/lang/String", "java/lang/Integer", "java/lang/Object"));
}
@Test
public void testRecordsArrayElementTypes() throws IOException {
assertThat(
getReferencedClassNames("class Foo {", " String[] s;", "}"),
Matchers.containsInAnyOrder("java/lang/String", "java/lang/Object"));
}
@Test
public void testRecordsGenericArrayElementTypeArgumentTypes() throws IOException {
assertThat(
getReferencedClassNames("class Foo {", " java.util.List<String>[] s;", "}"),
Matchers.containsInAnyOrder("java/util/List", "java/lang/String", "java/lang/Object"));
}
@Test
public void testRecordsClassTypeBoundTypes() throws IOException {
assertThat(
getReferencedClassNames("class Foo<T extends java.util.ArrayList> {", "}"),
Matchers.containsInAnyOrder("java/util/ArrayList", "java/lang/Object"));
}
@Test
public void testRecordsInterfaceTypeBoundTypes() throws IOException {
assertThat(
getReferencedClassNames("abstract class Foo<T extends CharSequence> {", "}"),
Matchers.containsInAnyOrder("java/lang/CharSequence", "java/lang/Object"));
}
@Test
public void testRecordsWildcardBoundTypes() throws IOException {
assertThat(
getReferencedClassNames(
"class Foo {" + " java.util.HashMap<? extends CharSequence, ? super Integer> field;",
"}"),
Matchers.containsInAnyOrder(
"java/util/HashMap",
"java/lang/CharSequence",
"java/lang/Integer",
"java/lang/Object"));
}
@Test
public void testRecordsAnnotationEnumValues() throws IOException {
assertThat(
getReferencedClassNames(
"import java.lang.annotation.*;",
"@Retention(RetentionPolicy.RUNTIME)",
"@interface Foo {",
"}"),
Matchers.containsInAnyOrder(
"java/lang/annotation/Retention",
"java/lang/annotation/RetentionPolicy",
"java/lang/annotation/Annotation",
"java/lang/Object"));
}
@Test
public void testRecordsAnnotationArrayValues() throws IOException {
assertThat(
getReferencedClassNames(
"import java.lang.annotation.*;",
"@Target({ElementType.FIELD, ElementType.METHOD})",
"@interface Foo {",
"}"),
Matchers.containsInAnyOrder(
"java/lang/annotation/Target",
"java/lang/annotation/ElementType",
"java/lang/annotation/Annotation",
"java/lang/Object"));
}
@Test
public void testRecordsAnnotationAnnotationValues() throws IOException {
assertThat(
getReferencedClassNames(
"@Bar(@Baz)",
"class Foo {",
"}",
"@interface Bar {",
" Baz value();",
"}",
"@interface Baz {",
"}"),
Matchers.containsInAnyOrder("Bar", "Baz", "java/lang/Object"));
}
@Test
public void testDoesNotRecordTypeVariables() throws IOException {
assertThat(
getReferencedClassNames(
"class Foo {", " <T extends Exception, U> void foo() throws T { }", "}"),
Matchers.containsInAnyOrder("java/lang/Exception", "java/lang/Object"));
}
private SortedSet<String> getReferencedClassNames(String... sourceLines) throws IOException {
testCompiler.addSourceFileLines("Foo.java", sourceLines);
testCompiler.compile();
Classes classes = testCompiler.getClasses();
ClassReferenceTracker tracker = new ClassReferenceTracker();
classes.acceptClassVisitor(
"Foo", ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES, tracker);
return tracker.getReferencedClassNames();
}
}