/*
* 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());
}
}