/*
* 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.source;
import static org.junit.Assert.assertThat;
import com.facebook.buck.jvm.java.plugin.adapter.BuckJavacTask;
import com.facebook.buck.jvm.java.testutil.compiler.CompilerTreeApiTest;
import com.facebook.buck.jvm.java.testutil.compiler.CompilerTreeApiTestRunner;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.util.TaskListener;
import com.sun.source.util.TreePath;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(CompilerTreeApiTestRunner.class)
public class InterfaceTypeAndConstantReferenceFinderTest extends CompilerTreeApiTest {
private List<String> typeReferences;
private List<String> constantReferences;
private List<TypeElement> importedTypes;
@Test
public void testFindsVariableTypeReference() throws IOException {
findTypeReferences("class Foo {", " String s;", "}");
assertThat(
typeReferences,
Matchers.containsInAnyOrder(createSymbolicReference("java.lang.String", 2, 3)));
}
@Test
public void testFindsTypeReferencesInNestedTypes() throws IOException {
findTypeReferences("class Foo {", " class Bar {", " String s;", " }", "}");
assertThat(
typeReferences,
Matchers.containsInAnyOrder(createSymbolicReference("java.lang.String", 3, 5)));
}
@Test
public void testIgnoresPrivateNestedTypes() throws IOException {
findTypeReferences("class Foo {", " private class Bar {", " String s;", " }", "}");
assertThat(typeReferences, Matchers.empty());
}
@Test
public void testFindsTypeArgTypeReference() throws IOException {
findTypeReferences("import java.util.Map;", "class Foo {", " Map<String, Integer> s;", "}");
assertThat(
typeReferences,
Matchers.contains(
createSymbolicReference("java.util.Map", 3, 3),
createSymbolicReference("java.lang.String", 3, 7),
createSymbolicReference("java.lang.Integer", 3, 15)));
}
@Test
public void testFindsWildcardBoundsTypeReference() throws IOException {
findTypeReferences(
"import java.util.Map;",
"class Foo {",
" Map<? extends CharSequence, ? super Integer> s;",
"}");
assertThat(
typeReferences,
Matchers.containsInAnyOrder(
createSymbolicReference("java.util.Map", 3, 3),
createSymbolicReference("java.lang.CharSequence", 3, 17),
createSymbolicReference("java.lang.Integer", 3, 39)));
}
@Test
public void testFindsArrayElementTypeReference() throws IOException {
findTypeReferences("class Foo {", " String[][] s;", "}");
assertThat(
typeReferences,
Matchers.containsInAnyOrder(createSymbolicReference("java.lang.String", 2, 3)));
}
@Test
public void testFindsFullyQualifiedTypeReference() throws IOException {
findTypeReferences("class Foo {", " java.lang.String s;", "}");
assertThat(
typeReferences,
Matchers.containsInAnyOrder(createSymbolicReference("java.lang.String", 2, 3)));
}
@Test
public void testFindsTypeParameterBoundTypeReferences() throws IOException {
findTypeReferences("class Foo<T extends Runnable & CharSequence> { }");
assertThat(
typeReferences,
Matchers.containsInAnyOrder(
createSymbolicReference("java.lang.Runnable", 1, 21),
createSymbolicReference("java.lang.CharSequence", 1, 32)));
}
@Test
public void testIgnoresTypeVariableTypeReferences() throws IOException {
findTypeReferences("class Foo<T extends Runnable & CharSequence> {", " T t;", "}");
assertThat(
typeReferences,
Matchers.containsInAnyOrder(
createSymbolicReference("java.lang.Runnable", 1, 21),
createSymbolicReference("java.lang.CharSequence", 1, 32)));
}
@Test
public void testIgnoresPrimitiveTypeReferences() throws IOException {
findTypeReferences("class Foo {", " int i;", "}");
assertThat(typeReferences, Matchers.empty());
}
@Test
public void testIgnoresNullTypeReferences() throws IOException {
findTypeReferences("class Foo<T> { }");
assertThat(typeReferences, Matchers.empty());
}
@Test
public void testIgnoresVoidTypeReferences() throws IOException {
findTypeReferences("class Foo {", " void method() { };", "}");
assertThat(typeReferences, Matchers.empty());
}
@Test
public void testFindsReturnTypeReference() throws IOException {
findTypeReferences("class Foo {", " String method() { return null; }", "}");
assertThat(
typeReferences,
Matchers.containsInAnyOrder(createSymbolicReference("java.lang.String", 2, 3)));
}
@Test
public void testFindsMethodParameterTypeReferences() throws IOException {
findTypeReferences("class Foo {", " void method(String s, Integer i) { }", "}");
assertThat(
typeReferences,
Matchers.containsInAnyOrder(
createSymbolicReference("java.lang.String", 2, 15),
createSymbolicReference("java.lang.Integer", 2, 25)));
}
@Test
public void testFindsMethodTypeParameterTypeReferences() throws IOException {
findTypeReferences("class Foo {", " <T extends CharSequence> void method() { }", "}");
assertThat(
typeReferences,
Matchers.containsInAnyOrder(createSymbolicReference("java.lang.CharSequence", 2, 14)));
}
@Test
public void testFindsSuperclassTypeReferences() throws IOException {
findTypeReferences("abstract class Foo extends java.util.ArrayList { } ");
assertThat(
typeReferences,
Matchers.containsInAnyOrder(createSymbolicReference("java.util.ArrayList", 1, 28)));
}
@Test
public void testFindsInterfaceTypeReferences() throws IOException {
findTypeReferences("abstract class Foo implements Runnable, CharSequence { } ");
assertThat(
typeReferences,
Matchers.containsInAnyOrder(
createSymbolicReference("java.lang.Runnable", 1, 31),
createSymbolicReference("java.lang.CharSequence", 1, 41)));
}
@Test
public void testFindsAnnotationTypeReferencesOnMethods() throws IOException {
findTypeReferences(
"class Foo implements Runnable {", " @Override", " public void run() { }", "}");
assertThat(
typeReferences,
Matchers.containsInAnyOrder(
createSymbolicReference("java.lang.Runnable", 1, 22),
createSymbolicReference("java.lang.Override", 2, 4)));
}
@Test
public void testFindsAnnotationTypeReferencesOnFields() throws IOException {
findTypeReferences("class Foo {", " @SuppressWarnings(\"foo\")", " String s;", "}");
assertThat(
typeReferences,
Matchers.containsInAnyOrder(
createSymbolicReference("java.lang.SuppressWarnings", 2, 4),
createSymbolicReference("java.lang.String", 3, 3)));
}
@Test
public void testFindsAnnotationTypeReferencesOnTypeParameters() throws IOException {
findTypeReferences(
"import java.lang.annotation.*;",
"class Foo<@Anno T> { }",
"@Target(ElementType.TYPE_PARAMETER)",
"@interface Anno { }");
assertThat(
typeReferences,
Matchers.containsInAnyOrder(
createSymbolicReference("Anno", 2, 12),
createSymbolicReference("java.lang.annotation.Target", 3, 2),
createSymbolicReference("java.lang.annotation.ElementType", 3, 9)));
}
@Test
public void testFindsAnnotationTypeReferencesOnTypes() throws IOException {
findTypeReferences(
"import java.lang.annotation.*;",
"abstract class Foo implements @Anno CharSequence { }",
"@Target(ElementType.TYPE_USE)",
"@interface Anno { }");
assertThat(
typeReferences,
Matchers.containsInAnyOrder(
createSymbolicReference("Anno", 2, 32),
createSymbolicReference("java.lang.CharSequence", 2, 37),
createSymbolicReference("java.lang.annotation.Target", 3, 2),
createSymbolicReference("java.lang.annotation.ElementType", 3, 9)));
}
@Test
public void testFindsAnnotationTypeReferencesOnClasses() throws IOException {
findTypeReferences("import java.lang.annotation.*;", "@Documented", "@interface Foo { }");
assertThat(
typeReferences,
Matchers.containsInAnyOrder(
createSymbolicReference("java.lang.annotation.Documented", 2, 2)));
}
@Test
public void testIgnoresMethodBodies() throws IOException {
findTypeReferences("class Foo {", " void method() {", " String s;", " }", "}");
assertThat(typeReferences, Matchers.empty());
}
@Test
public void testIgnoresAnonymousClasses() throws IOException {
findTypeReferences(
"class Foo {",
" Runnable r = new Runnable() {",
" String s;",
" public void run() { }",
" };",
"}");
assertThat(
typeReferences,
Matchers.containsInAnyOrder(createSymbolicReference("java.lang.Runnable", 2, 3)));
}
@Test
public void testIgnoresPrivateMethods() throws IOException {
findTypeReferences("class Foo {", " private String method() { return null; }", "}");
assertThat(typeReferences, Matchers.empty());
}
@Test
public void testIgnoresPrivateFields() throws IOException {
findTypeReferences("class Foo {", " private String s;", "}");
assertThat(typeReferences, Matchers.empty());
}
@Test
public void testIgnoresPrivateTypes() throws IOException {
findTypeReferences("class Foo {", " private class Bar {", " public String s;", " }", "}");
assertThat(typeReferences, Matchers.empty());
}
@Test
public void testFindsReferencesInConstants() throws IOException {
findTypeReferences(
"class Foo {",
" public static final String s = Constants.CONSTANT;",
"}",
"class Constants {",
" public static final String CONSTANT = \"Hello\";",
"}");
assertThat(
typeReferences,
Matchers.containsInAnyOrder(
createSymbolicReference("java.lang.String", 2, 23),
createSymbolicReference("Constants", 2, 34),
createSymbolicReference("java.lang.String", 5, 23)));
}
@Test
public void testIgnoresReferencesToOtherConstantMembers() throws IOException {
findTypeReferences(
"class Foo {",
" public static final String CONSTANT = \"Hello\";",
" public static final String s = CONSTANT;",
"}");
assertThat(
typeReferences,
Matchers.containsInAnyOrder(
createSymbolicReference("java.lang.String", 3, 23),
createSymbolicReference("java.lang.String", 2, 23)));
}
@Test
public void testIgnoresReferencesInNonStaticConstants() throws IOException {
findTypeReferences(
"class Foo {",
" public final String s = Constants.CONSTANT;",
"}",
"class Constants {",
" public static final String CONSTANT = \"Hello\";",
"}");
assertThat(
typeReferences,
Matchers.containsInAnyOrder(
createSymbolicReference("java.lang.String", 2, 16),
createSymbolicReference("java.lang.String", 5, 23)));
}
@Test
public void testFindsReferencesInMethodDefaultValues() throws IOException {
findTypeReferences(
"@interface Foo {",
" String value() default Constants.CONSTANT;",
"}",
"class Constants {",
" public static final String CONSTANT = \"Hello\";",
"}");
assertThat(
typeReferences,
Matchers.containsInAnyOrder(
createSymbolicReference("java.lang.String", 2, 3),
createSymbolicReference("Constants", 2, 26),
createSymbolicReference("java.lang.String", 5, 23)));
}
@Test
public void testFindsReferencesInAnnotationValues() throws IOException {
findTypeReferences(
"@SuppressWarnings(Constants.CONSTANT)",
"class Foo {",
"}",
"class Constants {",
" public static final String CONSTANT = \"Hello\";",
"}");
assertThat(
typeReferences,
Matchers.containsInAnyOrder(
createSymbolicReference("java.lang.SuppressWarnings", 1, 2),
createSymbolicReference("Constants", 1, 19),
createSymbolicReference("java.lang.String", 5, 23)));
}
@Test
public void testFindsReferencesInAnnotationValuesThatAreAnnotations() throws IOException {
findTypeReferences(
"@Bar(@Baz)",
"class Foo {",
"}",
"@interface Bar {",
" Baz value();",
"}",
"@interface Baz {}");
assertThat(
typeReferences,
Matchers.containsInAnyOrder(
createSymbolicReference("Bar", 1, 2),
createSymbolicReference("Baz", 1, 7),
createSymbolicReference("Baz", 5, 3)));
}
@Test
public void testFindsReferencesInAnnotationValuesThatAreAnnotationArrays() throws IOException {
findTypeReferences(
"@Bar({@Baz, @Baz})",
"class Foo {",
"}",
"@interface Bar {",
" Baz[] value();",
"}",
"@interface Baz {}");
assertThat(
typeReferences,
Matchers.containsInAnyOrder(
createSymbolicReference("Bar", 1, 2),
createSymbolicReference("Baz", 1, 8),
createSymbolicReference("Baz", 1, 14),
createSymbolicReference("Baz", 5, 3)));
}
@Test
public void testFindsImportedTypes() throws IOException {
findTypeReferences("import java.util.List;", "class Foo { }");
assertThat(
importedTypes, Matchers.containsInAnyOrder(elements.getTypeElement("java.util.List")));
}
@Test
public void testIgnoresStaticImports() throws IOException {
findTypeReferences("import static java.util.Collections.*;", "class Foo { }");
assertThat(importedTypes, Matchers.empty());
}
@Test
public void testDoesNotCrashOnSimpleNamedPackage() throws IOException {
findTypeReferencesErrorsOK("package example;", "class Foo { }");
}
@Test
public void testFindsSimpleNameConstants() throws IOException {
withClasspath(
ImmutableMap.of(
"com/facebook/foo/Constants.java",
Joiner.on('\n')
.join(
"package com.facebook.foo;",
"class Constants {",
" protected static final int CONSTANT = 3;",
"}")));
findTypeReferences(
"package com.facebook.foo;",
"class Foo extends Constants {",
" public static final int CONSTANT2 = CONSTANT;",
"}");
assertThat(
constantReferences, Matchers.containsInAnyOrder(createSymbolicReference("3", 3, 39)));
}
@Test
public void testFindsQualifiedNameConstants() throws IOException {
withClasspath(
ImmutableMap.of(
"com/facebook/foo/Constants.java",
Joiner.on('\n')
.join(
"package com.facebook.foo;",
"public class Constants {",
" public static final int CONSTANT = 3;",
"}")));
findTypeReferences(
"class Foo {",
" public static final int CONSTANT2 = com.facebook.foo.Constants.CONSTANT;",
"}");
assertThat(
constantReferences, Matchers.containsInAnyOrder(createSymbolicReference("3", 2, 39)));
}
@Test
public void testIgnoresReferencesToNonexistentTypes() throws IOException {
findTypeReferencesErrorsOK(
"package com.facebook.foo;",
"class Foo {",
" protected Bar getBar() { return null; };",
"}");
assertThat(typeReferences, Matchers.empty());
}
private void findTypeReferences(String... sourceLines) throws IOException {
findTypeReferences(sourceLines, false);
}
private void findTypeReferencesErrorsOK(String... sourceLines) throws IOException {
findTypeReferences(sourceLines, true);
}
private void findTypeReferences(String[] sourceLines, boolean errorsOK) throws IOException {
constantReferences = new ArrayList<>();
typeReferences = new ArrayList<>();
importedTypes = new ArrayList<>();
compile(
ImmutableMap.of("Foo.java", Joiner.on('\n').join(sourceLines)),
// ErrorType's get nulled out when the task returns, so we have to get a call back during
// the run so that we can still look at them.
new TaskListenerFactory() {
@Override
public TaskListener newTaskListener(BuckJavacTask task) {
return new PostEnterCallback() {
@Override
protected void enterComplete(List<CompilationUnitTree> compilationUnits) {
InterfaceTypeAndConstantReferenceFinder finder =
new InterfaceTypeAndConstantReferenceFinder(trees, new FinderListener());
finder.findReferences(compilationUnits);
}
};
}
});
if (!errorsOK) {
assertNoErrors();
}
}
private String createSymbolicReference(String referent, TreePath treePath) {
try {
long position =
trees
.getSourcePositions()
.getStartPosition(treePath.getCompilationUnit(), treePath.getLeaf());
CharSequence content = treePath.getCompilationUnit().getSourceFile().getCharContent(true);
int line = 1;
int column = 1;
for (long i = 0; i < position; i++, column++) {
if (content.charAt((int) i) == '\n') {
line += 1;
column = 0;
}
}
return createSymbolicReference(referent, line, column);
} catch (IOException e) {
throw new AssertionError(e);
}
}
private String createSymbolicReference(String referent, int line, int column) {
return String.format("%d, %d: %s", line, column, referent);
}
private class FinderListener implements InterfaceTypeAndConstantReferenceFinder.Listener {
@Override
public void onTypeImported(TypeElement type) {
importedTypes.add(type);
}
@Override
public void onTypeReferenceFound(TypeElement type, TreePath path, Element enclosingElement) {
typeReferences.add(createSymbolicReference(type.getQualifiedName().toString(), path));
}
@Override
public void onConstantReferenceFound(
VariableElement constant, TreePath path, Element enclosingElement) {
constantReferences.add(createSymbolicReference(constant.getConstantValue().toString(), path));
}
}
}