package spoon.test.methodreference;
import org.junit.Before;
import org.junit.Test;
import spoon.Launcher;
import spoon.OutputType;
import spoon.SpoonModelBuilder;
import spoon.reflect.CtModel;
import spoon.reflect.code.CtExecutableReferenceExpression;
import spoon.reflect.code.CtExpression;
import spoon.reflect.code.CtFieldRead;
import spoon.reflect.code.CtInvocation;
import spoon.reflect.code.CtTypeAccess;
import spoon.reflect.code.CtVariableRead;
import spoon.reflect.declaration.CtClass;
import spoon.reflect.declaration.CtMethod;
import spoon.reflect.declaration.CtType;
import spoon.reflect.factory.Factory;
import spoon.reflect.reference.CtExecutableReference;
import spoon.reflect.reference.CtTypeReference;
import spoon.reflect.visitor.Query;
import spoon.reflect.visitor.filter.AbstractFilter;
import spoon.reflect.visitor.filter.NameFilter;
import spoon.reflect.visitor.filter.TypeFilter;
import spoon.test.methodreference.testclasses.Cloud;
import spoon.test.methodreference.testclasses.Foo;
import spoon.testing.utils.ModelUtils;
import java.io.File;
import java.lang.reflect.Method;
import java.util.Comparator;
import java.util.function.Supplier;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static spoon.testing.utils.ModelUtils.canBeBuilt;
public class MethodReferenceTest {
private static final String TEST_CLASS = "spoon.test.methodreference.testclasses.Foo.";
private CtClass<?> foo;
@Before
public void setUp() throws Exception {
final Launcher launcher = new Launcher();
final Factory factory = launcher.createFactory();
factory.getEnvironment().setComplianceLevel(8);
final File sourceOutputDir = new File("./target/spooned/");
factory.getEnvironment().setDefaultFileGenerator(launcher.createOutputWriter(sourceOutputDir, factory.getEnvironment()));
final SpoonModelBuilder compiler = launcher.createCompiler(factory);
compiler.setSourceOutputDirectory(sourceOutputDir);
compiler.addInputSource(new File("./src/test/java/spoon/test/methodreference/testclasses/"));
compiler.build();
compiler.generateProcessedSourceFiles(OutputType.CLASSES);
foo = (CtClass<?>) factory.Type().get(Foo.class);
}
@Test
public void testReferenceToAStaticMethod() throws Exception {
final String methodReference = TEST_CLASS + "Person::compareByAge";
final CtExecutableReferenceExpression<?,?> reference = getCtExecutableReferenceExpression(methodReference);
assertTypedBy(Comparator.class, reference.getType());
assertTargetedBy(TEST_CLASS + "Person", reference.getTarget());
assertTrue(reference.getTarget() instanceof CtTypeAccess);
assertExecutableNamedBy("compareByAge", reference.getExecutable());
assertIsWellPrinted(methodReference, reference);
}
@Test
public void testReferenceToAnInstanceMethodOfAParticularObject() throws Exception {
final String methodReference = "myComparisonProvider::compareByName";
final CtExecutableReferenceExpression<?,?> reference = getCtExecutableReferenceExpression(methodReference);
assertTypedBy(Comparator.class, reference.getType());
assertTargetedBy("myComparisonProvider", reference.getTarget());
assertTrue(reference.getTarget() instanceof CtVariableRead);
assertExecutableNamedBy("compareByName", reference.getExecutable());
assertIsWellPrinted(methodReference, reference);
}
@Test
public void testReferenceToAnInstanceMethodOfMultiParticularObject() throws Exception {
final String methodReference = "tarzan.phone::compareByNumbers";
final CtExecutableReferenceExpression<?,?> reference = getCtExecutableReferenceExpression(methodReference);
assertTypedBy(Comparator.class, reference.getType());
assertTargetedBy("tarzan.phone", reference.getTarget());
assertTrue(reference.getTarget() instanceof CtFieldRead);
assertExecutableNamedBy("compareByNumbers", reference.getExecutable());
assertIsWellPrinted(methodReference, reference);
}
@Test
public void testReferenceToAnInstanceMethodOfAnArbitraryObjectOfAParticularType() throws Exception {
final String methodReference = "java.lang.String::compareToIgnoreCase";
final CtExecutableReferenceExpression<?,?> reference = getCtExecutableReferenceExpression(methodReference);
assertTypedBy(Comparator.class, reference.getType());
assertTargetedBy("java.lang.String", reference.getTarget());
assertTrue(reference.getTarget() instanceof CtTypeAccess);
assertExecutableNamedBy("compareToIgnoreCase", reference.getExecutable());
assertIsWellPrinted(methodReference, reference);
}
@Test
public void testReferenceToAConstructor() throws Exception {
final String methodReference = TEST_CLASS + "Person::new";
final CtExecutableReferenceExpression<?,?> reference = getCtExecutableReferenceExpression(methodReference);
assertTypedBy(Supplier.class, reference.getType());
assertTargetedBy(TEST_CLASS + "Person", reference.getTarget());
assertTrue(reference.getTarget() instanceof CtTypeAccess);
assertIsConstructorReference(reference.getExecutable());
assertIsWellPrinted(methodReference, reference);
}
@Test
public void testReferenceToAClassParametrizedConstructor() throws Exception {
final String methodReference = TEST_CLASS + "Type<java.lang.String>::new";
final CtExecutableReferenceExpression<?,?> reference = getCtExecutableReferenceExpression(methodReference);
assertTypedBy(Supplier.class, reference.getType());
assertTargetedBy(TEST_CLASS + "Type<java.lang.String>", reference.getTarget());
assertTrue(reference.getTarget() instanceof CtTypeAccess);
assertIsConstructorReference(reference.getExecutable());
assertIsWellPrinted(methodReference, reference);
}
@Test
public void testReferenceToAJavaUtilClassConstructor() throws Exception {
final String methodReference = "java.util.HashSet<" + TEST_CLASS + "Person>::new";
final CtExecutableReferenceExpression<?,?> reference = getCtExecutableReferenceExpression(methodReference);
assertTypedBy(Supplier.class, reference.getType());
assertTargetedBy("java.util.HashSet<" + TEST_CLASS + "Person>", reference.getTarget());
assertTrue(reference.getTarget() instanceof CtTypeAccess);
assertIsConstructorReference(reference.getExecutable());
assertIsWellPrinted(methodReference, reference);
}
@Test
public void testCompileMethodReferenceGeneratedBySpoon() throws Exception {
canBeBuilt(new File("./target/spooned/spoon/test/methodreference/testclasses/"), 8);
}
@Test
public void testNoClasspathExecutableReferenceExpression() throws Exception {
final Launcher launcher = new Launcher();
launcher.run(new String[] {
"-i", "./src/test/resources/executable-reference-expression/Bar.java", "-o", "./target/spooned", "--noclasspath"
});
final CtExecutableReferenceExpression<?, ?> element = Query
.getElements(launcher.getFactory(), new TypeFilter<CtExecutableReferenceExpression<?, ?>>(CtExecutableReferenceExpression.class)).get(0);
assertEquals("isInstance", element.getExecutable().getSimpleName());
assertNotNull(element.getExecutable().getDeclaringType());
assertEquals("Tacos", element.getExecutable().getDeclaringType().getSimpleName());
assertEquals("elemType::isInstance", element.toString());
}
@Test
public void testNoClasspathSuperExecutable() {
final Launcher launcher = new Launcher();
launcher.getEnvironment().setNoClasspath(true);
launcher.addInputResource("src/test/resources/noclasspath/superclass/UnknownSuperClass.java");
launcher.buildModel();
final CtModel model = launcher.getModel();
final CtTypeReference overrideRef = launcher.getFactory().
Annotation().createReference(Override.class);
// call `getSuperClass()` indirectly using `getOverridingExecutable()`
// some consistency checks...
assertEquals(1, model.getElements(
new NameFilter<CtMethod>("a")).size());
assertEquals(1, model.getElements(
new NameFilter<CtMethod>("b")).size());
assertEquals(1, model.getElements(
new NameFilter<CtMethod>("toString")).size());
// get super method of a class not available in classpath
final CtMethod bMethod = model.getElements(
new NameFilter<CtMethod>("b")).get(0);
assertNotNull(bMethod.getAnnotation(overrideRef));
assertNull(bMethod.getReference().getOverridingExecutable());
// get super method of a class available in classpath (Object)
final CtMethod toStringMethod = model.getElements(
new NameFilter<CtMethod>("toString")).get(0);
assertNotNull(toStringMethod.getAnnotation(overrideRef));
assertNotNull(toStringMethod.getReference().getOverridingExecutable());
}
@Test
public void testGetGenericMethodFromReferene() throws Exception {
CtType<?> classCloud = ModelUtils.buildClass(Cloud.class);
CtMethod<?> ctMethod = classCloud.getMethodsByName("method").get(0);
CtExecutableReference<?> execRef = ctMethod.getReference();
Method method = execRef.getActualMethod();
assertNotNull(method);
assertEquals("method", method.getName());
CtClass<?> classSun = classCloud.getFactory().Class().get("spoon.test.methodreference.testclasses.Sun");
// CtExecutableReference<?> execRef2 = classSun.filterChildren(new TypeFilter<>(CtExecutableReference.class)).select(new NameFilter<>("method")).first();
CtExecutableReference<?> execRef2 = classSun.filterChildren(new TypeFilter<>(CtInvocation.class))
.select(((CtInvocation i)->i.getExecutable().getSimpleName().equals("method")))
.map((CtInvocation i)->i.getExecutable())
.first();
assertNotNull(execRef2);
Method method2 = execRef2.getActualMethod();
assertNotNull(method2);
assertEquals("method", method2.getName());
}
private void assertTypedBy(Class<?> expected, CtTypeReference<?> type) {
assertEquals("Method reference must be typed.", expected, type.getActualClass());
}
private void assertTargetedBy(String expected, CtExpression<?> target) {
assertNotNull("Method reference must have a target expression.", target);
assertEquals("Target reference correspond to the enclosing class.", expected, target.toString());
}
private void assertIsConstructorReference(CtExecutableReference<?> executable) {
assertExecutableNamedBy("<init>", executable);
}
private void assertExecutableNamedBy(String expected, CtExecutableReference<?> executable) {
assertNotNull("Method reference must reference an executable.", executable);
assertEquals("Method reference must reference the right executable.", expected, executable.getSimpleName());
}
private void assertIsWellPrinted(String methodReference, CtExecutableReferenceExpression<?,?> reference) {
assertEquals("Method reference must be well printed", methodReference, reference.toString());
}
private CtExecutableReferenceExpression<?,?> getCtExecutableReferenceExpression(final String methodReference) {
return foo.getElements(new AbstractFilter<CtExecutableReferenceExpression<?,?>>(CtExecutableReferenceExpression.class) {
@Override
public boolean matches(CtExecutableReferenceExpression<?,?> element) {
return (methodReference).equals(element.toString());
}
}).get(0);
}
}