/* * Copyright 2016-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.assertEquals; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import com.facebook.buck.jvm.java.testutil.compiler.CompilerTreeApiParameterized; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableMap; import java.io.IOException; import java.util.ArrayList; import java.util.List; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.NestingKind; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.NoType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; import javax.lang.model.util.ElementFilter; import javax.lang.model.util.SimpleElementVisitor8; import org.hamcrest.Matchers; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(CompilerTreeApiParameterized.class) public class TreeBackedTypeElementTest extends CompilerTreeApiParameterizedTest { @Test public void testGetSimpleName() throws IOException { compile("public class Foo {}"); TypeElement element = elements.getTypeElement("Foo"); assertNameEquals("Foo", element.getSimpleName()); } @Test public void testGetQualifiedNameUnnamedPackage() throws IOException { compile("public class Foo {}"); TypeElement element = elements.getTypeElement("Foo"); assertNameEquals("Foo", element.getQualifiedName()); } @Test public void testGetQualifiedNameNamedPackage() throws IOException { compile(Joiner.on('\n').join("package com.facebook.buck;", "public class Foo {}")); TypeElement element = elements.getTypeElement("com.facebook.buck.Foo"); assertNameEquals("com.facebook.buck.Foo", element.getQualifiedName()); } @Test public void testGetKindAnnotation() throws IOException { compile("@interface Foo { }"); assertSame(ElementKind.ANNOTATION_TYPE, elements.getTypeElement("Foo").getKind()); } @Test public void testGetKindClass() throws IOException { compile("class Foo { }"); assertSame(ElementKind.CLASS, elements.getTypeElement("Foo").getKind()); } @Test public void testGetKindEnum() throws IOException { compile("enum Foo { }"); assertSame(ElementKind.ENUM, elements.getTypeElement("Foo").getKind()); } @Test public void testGetKindInterface() throws IOException { compile("interface Foo { }"); assertSame(ElementKind.INTERFACE, elements.getTypeElement("Foo").getKind()); } @Test public void testAccept() throws IOException { compile("class Foo { }"); TypeElement expectedType = elements.getTypeElement("Foo"); Object expectedResult = new Object(); Object actualResult = expectedType.accept( new SimpleElementVisitor8<Object, Object>() { @Override protected Object defaultAction(Element e, Object o) { return null; } @Override public Object visitType(TypeElement actualType, Object o) { assertSame(expectedType, actualType); return o; } }, expectedResult); assertSame(expectedResult, actualResult); } @Test public void testToString() throws IOException { compile(Joiner.on('\n').join("package com.facebook.buck;", "public class Foo<T> {}")); TypeElement element = elements.getTypeElement("com.facebook.buck.Foo"); assertEquals("com.facebook.buck.Foo", element.toString()); } @Test public void testGetQualifiedNameInnerClass() throws IOException { compile( Joiner.on('\n') .join( "package com.facebook.buck;", "public class Foo {", " public class Bar { }", "}")); TypeElement element = elements.getTypeElement("com.facebook.buck.Foo.Bar"); assertNameEquals("com.facebook.buck.Foo.Bar", element.getQualifiedName()); } @Test public void testGetQualifiedNameEnumAnonymousMember() throws IOException { compile(Joiner.on('\n').join("public enum Foo {", " BAR,", " BAZ() {", " }", "}")); // Expect no crash. Enum anonymous members don't have qualified names, but at one point during // development we would crash trying to make one for them. } @Test public void testGetNestingKindTopLevel() throws IOException { compile("public class Foo { }"); assertSame(NestingKind.TOP_LEVEL, elements.getTypeElement("Foo").getNestingKind()); } @Test public void testGetNestingKindMember() throws IOException { compile(Joiner.on('\n').join("public class Foo {", " class Bar { }", "}")); assertSame(NestingKind.MEMBER, elements.getTypeElement("Foo.Bar").getNestingKind()); } @Test public void testAsType() throws IOException { compile("class Foo { }"); TypeElement fooElement = elements.getTypeElement("Foo"); TypeMirror fooTypeMirror = fooElement.asType(); assertEquals(TypeKind.DECLARED, fooTypeMirror.getKind()); DeclaredType fooDeclaredType = (DeclaredType) fooTypeMirror; assertSame(fooElement, fooDeclaredType.asElement()); assertEquals(0, fooDeclaredType.getTypeArguments().size()); TypeMirror enclosingType = fooDeclaredType.getEnclosingType(); assertEquals(TypeKind.NONE, enclosingType.getKind()); assertTrue(enclosingType instanceof NoType); } @Test public void testAsTypeGeneric() throws IOException { compile("class Foo<T> { }"); TypeElement fooElement = elements.getTypeElement("Foo"); TypeMirror fooTypeMirror = fooElement.asType(); assertEquals(TypeKind.DECLARED, fooTypeMirror.getKind()); DeclaredType fooDeclaredType = (DeclaredType) fooTypeMirror; assertSame(fooElement, fooDeclaredType.asElement()); List<? extends TypeMirror> typeArguments = fooDeclaredType.getTypeArguments(); assertEquals("T", ((TypeVariable) typeArguments.get(0)).asElement().getSimpleName().toString()); assertEquals(1, typeArguments.size()); TypeMirror enclosingType = fooDeclaredType.getEnclosingType(); assertEquals(TypeKind.NONE, enclosingType.getKind()); assertTrue(enclosingType instanceof NoType); } @Test public void testGetSuperclassNoSuperclassIsObject() throws IOException { compile("class Foo { }"); TypeElement fooElement = elements.getTypeElement("Foo"); DeclaredType superclass = (DeclaredType) fooElement.getSuperclass(); TypeElement objectElement = elements.getTypeElement("java.lang.Object"); assertSame(objectElement, superclass.asElement()); } @Test public void testGetSuperclassObjectSuperclassIsObject() throws IOException { compile("class Foo extends java.lang.Object { }"); TypeElement fooElement = elements.getTypeElement("Foo"); DeclaredType superclass = (DeclaredType) fooElement.getSuperclass(); TypeElement objectElement = elements.getTypeElement("java.lang.Object"); assertSame(objectElement, superclass.asElement()); } @Test public void testGetSuperclassOfInterfaceIsNoneType() throws IOException { compile("interface Foo extends java.lang.Runnable { }"); TypeElement fooElement = elements.getTypeElement("Foo"); assertSame(TypeKind.NONE, fooElement.getSuperclass().getKind()); } @Test public void testGetSuperclassOfEnumIsEnumWithArgs() throws IOException { compile("enum Foo { }"); TypeElement fooElement = elements.getTypeElement("Foo"); DeclaredType superclass = (DeclaredType) fooElement.getSuperclass(); TypeElement enumElement = elements.getTypeElement("java.lang.Enum"); TypeMirror expectedSuperclass = types.getDeclaredType(enumElement, fooElement.asType()); assertSameType(expectedSuperclass, superclass); } @Test public void testGetSuperclassOtherSuperclass() throws IOException { compile( ImmutableMap.of( "Foo.java", Joiner.on('\n') .join( "package com.facebook.foo;", "public class Foo extends com.facebook.bar.Bar { }"), "Bar.java", Joiner.on('\n').join("package com.facebook.bar;", "public class Bar { }"))); TypeElement fooElement = elements.getTypeElement("com.facebook.foo.Foo"); TypeElement barElement = elements.getTypeElement("com.facebook.bar.Bar"); DeclaredType superclass = (DeclaredType) fooElement.getSuperclass(); assertSame(barElement, superclass.asElement()); } @Test public void testGetInterfacesOnClass() throws IOException { compile( Joiner.on('\n') .join( "package com.facebook.foo;", "public abstract class Foo implements Runnable, java.io.Closeable { }")); TypeElement fooElement = elements.getTypeElement("com.facebook.foo.Foo"); TypeMirror runnableType = elements.getTypeElement("java.lang.Runnable").asType(); TypeMirror closeableType = elements.getTypeElement("java.io.Closeable").asType(); List<? extends TypeMirror> interfaces = fooElement.getInterfaces(); assertSameType(runnableType, interfaces.get(0)); assertSameType(closeableType, interfaces.get(1)); assertEquals(2, interfaces.size()); } @Test public void testGetInterfacesOnInterface() throws IOException { compile( Joiner.on('\n') .join( "package com.facebook.foo;", "public interface Foo extends Runnable, java.io.Closeable { }")); TypeElement fooElement = elements.getTypeElement("com.facebook.foo.Foo"); TypeMirror runnableType = elements.getTypeElement("java.lang.Runnable").asType(); TypeMirror closeableType = elements.getTypeElement("java.io.Closeable").asType(); List<? extends TypeMirror> interfaces = fooElement.getInterfaces(); assertSameType(runnableType, interfaces.get(0)); assertSameType(closeableType, interfaces.get(1)); assertEquals(2, interfaces.size()); } @Test public void testGetInterfacesDefaultsEmptyForClass() throws IOException { compile(Joiner.on('\n').join("package com.facebook.foo;", "public class Foo { }")); TypeElement fooElement = elements.getTypeElement("com.facebook.foo.Foo"); assertThat(fooElement.getInterfaces(), Matchers.empty()); } @Test public void testGetInterfacesDefaultsEmptyForInterface() throws IOException { compile(Joiner.on('\n').join("package com.facebook.foo;", "public interface Foo { }")); TypeElement fooElement = elements.getTypeElement("com.facebook.foo.Foo"); assertThat(fooElement.getInterfaces(), Matchers.empty()); } @Test public void testGetInterfacesDefaultsEmptyForEnum() throws IOException { compile(Joiner.on('\n').join("package com.facebook.foo;", "public enum Foo { }")); TypeElement fooElement = elements.getTypeElement("com.facebook.foo.Foo"); assertThat(fooElement.getInterfaces(), Matchers.empty()); } @Test public void testGetInterfacesDefaultsAnnotationForAnnotation() throws IOException { compile(Joiner.on('\n').join("package com.facebook.foo;", "public @interface Foo { }")); TypeElement fooElement = elements.getTypeElement("com.facebook.foo.Foo"); TypeMirror annotationType = elements.getTypeElement("java.lang.annotation.Annotation").asType(); List<? extends TypeMirror> interfaces = fooElement.getInterfaces(); assertSameType(annotationType, interfaces.get(0)); assertEquals(1, interfaces.size()); } @Test public void testEnclosedClasses() throws IOException { compile(Joiner.on('\n').join("class Foo {", " class Bar { }", "}")); TypeElement fooElement = elements.getTypeElement("Foo"); TypeElement barElement = elements.getTypeElement("Foo.Bar"); List<Element> enclosedElements = new ArrayList<>(fooElement.getEnclosedElements()); assertThat(enclosedElements, Matchers.hasItems(barElement)); assertSame(fooElement, barElement.getEnclosingElement()); } @Test public void testGetEnclosingElementForTopLevelClasses() throws IOException { compile("class Foo { }"); TypeElement fooElement = elements.getTypeElement("Foo"); PackageElement unnamedPackage = elements.getPackageElement(""); assertSame(unnamedPackage, fooElement.getEnclosingElement()); assertTrue(unnamedPackage.getEnclosedElements().contains(fooElement)); } @Ignore( "TODO(jkeljo): Need to wrap elements coming out of javac to ensure callers always get" + "our PackageElement impl instead of javac's.") @Test public void testGetEnclosingElementForBuiltInTopLevelClasses() throws IOException { initCompiler(); TypeElement stringElement = elements.getTypeElement("java.lang.String"); PackageElement javaLangElement = elements.getPackageElement("java.lang"); assertSame(javaLangElement, stringElement.getEnclosingElement()); } @Test public void testIncludesGeneratedDefaultConstructor() throws IOException { compile("class Foo { }"); TypeElement fooElement = elements.getTypeElement("Foo"); ExecutableElement constructorElement = (ExecutableElement) fooElement.getEnclosedElements().get(0); assertEquals("<init>", constructorElement.getSimpleName().toString()); assertSameType(types.getNoType(TypeKind.VOID), constructorElement.getReturnType()); assertThat(constructorElement.getParameters(), Matchers.empty()); assertThat(constructorElement.getTypeParameters(), Matchers.empty()); assertThat(constructorElement.getThrownTypes(), Matchers.empty()); assertEquals(1, fooElement.getEnclosedElements().size()); } @Test public void testIncludesGeneratedEnumMembers() throws IOException { compile("enum Foo { }"); TypeElement fooElement = elements.getTypeElement("Foo"); List<ExecutableElement> methods = ElementFilter.methodsIn(fooElement.getEnclosedElements()); ExecutableElement valuesMethod = methods.get(0); assertEquals("values", valuesMethod.getSimpleName().toString()); assertSameType(types.getArrayType(fooElement.asType()), valuesMethod.getReturnType()); assertThat(valuesMethod.getParameters(), Matchers.empty()); assertThat(valuesMethod.getTypeParameters(), Matchers.empty()); assertThat(valuesMethod.getThrownTypes(), Matchers.empty()); ExecutableElement valueOfMethod = methods.get(1); assertEquals("valueOf", valueOfMethod.getSimpleName().toString()); assertSameType(fooElement.asType(), valueOfMethod.getReturnType()); List<? extends VariableElement> parameters = valueOfMethod.getParameters(); assertSameType( elements.getTypeElement("java.lang.String").asType(), parameters.get(0).asType()); assertEquals(1, parameters.size()); assertThat(valuesMethod.getTypeParameters(), Matchers.empty()); assertThat(valuesMethod.getThrownTypes(), Matchers.empty()); assertEquals(2, methods.size()); } }