package spoon.test.prettyprinter;
import org.apache.commons.io.IOUtils;
import org.junit.Test;
import spoon.Launcher;
import spoon.SpoonModelBuilder;
import spoon.compiler.Environment;
import spoon.compiler.SpoonResource;
import spoon.compiler.SpoonResourceHelper;
import spoon.reflect.code.CtCodeSnippetStatement;
import spoon.reflect.code.CtConstructorCall;
import spoon.reflect.code.CtInvocation;
import spoon.reflect.code.CtStatement;
import spoon.reflect.declaration.CtClass;
import spoon.reflect.declaration.CtType;
import spoon.reflect.factory.Factory;
import spoon.reflect.reference.CtTypeReference;
import spoon.reflect.visitor.DefaultJavaPrettyPrinter;
import spoon.reflect.visitor.PrettyPrinter;
import spoon.reflect.visitor.Query;
import spoon.reflect.visitor.filter.TypeFilter;
import spoon.support.JavaOutputProcessor;
import spoon.test.prettyprinter.testclasses.AClass;
import java.io.File;
import java.io.FileInputStream;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static spoon.testing.utils.ModelUtils.build;
public class DefaultPrettyPrinterTest {
private static final String nl = System.lineSeparator();
@Test
public void printerCanPrintInvocationWithoutException() throws Exception {
String packageName = "spoon.test.subclass.prettyprinter";
String className = "DefaultPrettyPrinterExample";
String qualifiedName = packageName + "." + className;
SpoonModelBuilder comp = new Launcher().createCompiler();
List<SpoonResource> fileToBeSpooned = SpoonResourceHelper.resources("./src/test/resources/printer-test/" + qualifiedName.replace('.', '/') + ".java");
assertEquals(1, fileToBeSpooned.size());
comp.addInputSources(fileToBeSpooned);
List<SpoonResource> classpath = SpoonResourceHelper.resources("./src/test/resources/printer-test/DefaultPrettyPrinterDependency.jar");
assertEquals(1, classpath.size());
comp.setSourceClasspath(classpath.get(0).getPath());
comp.build();
Factory factory = comp.getFactory();
CtType<?> theClass = factory.Type().get(qualifiedName);
List<CtInvocation<?>> elements = Query.getElements(theClass, new TypeFilter<CtInvocation<?>>(CtInvocation.class));
assertEquals(3, elements.size());
CtInvocation<?> mathAbsInvocation = elements.get(1);
assertEquals("java.lang.Math.abs(message.length())", mathAbsInvocation.toString());
}
@Test
public void superInvocationWithEnclosingInstance() throws Exception {
/**
* To extend a nested class an enclosing instance must be provided
* to call the super constructor.
*/
String sourcePath = "./src/test/resources/spoon/test/prettyprinter/NestedSuperCall.java";
List<SpoonResource> files = SpoonResourceHelper.resources(sourcePath);
assertEquals(1, files.size());
SpoonModelBuilder comp = new Launcher().createCompiler();
comp.addInputSources(files);
comp.build();
Factory factory = comp.getFactory();
CtType<?> theClass = factory.Type().get("spoon.test.prettyprinter.NestedSuperCall");
assertTrue(theClass.toString().contains("nc.super(\"a\")"));
}
@Test
public void testPrintAClassWithImports() throws Exception {
final Launcher launcher = new Launcher();
final Factory factory = launcher.getFactory();
factory.getEnvironment().setAutoImports(true);
final SpoonModelBuilder compiler = launcher.createCompiler();
compiler.addInputSource(new File("./src/test/java/spoon/test/prettyprinter/testclasses/"));
compiler.build();
final String expected =
"public class AClass {" +nl+
" public List<?> aMethod() {" +nl+
" return new ArrayList<>();" +nl+
" }" +nl+
"" +nl+
" public List<? extends ArrayList> aMethodWithGeneric() {" +nl+
" return new ArrayList<>();" +nl+
" }" +nl+
"}";
final CtClass<?> aClass = (CtClass<?>) factory.Type().get(AClass.class);
assertEquals(expected, aClass.toString());
final CtConstructorCall<?> constructorCall = aClass.getElements(new TypeFilter<CtConstructorCall<?>>(CtConstructorCall.class)).get(0);
final CtTypeReference<?> ctTypeReference = constructorCall.getType()
.getActualTypeArguments()
.get(0);
assertTrue(ctTypeReference.isImplicit());
assertEquals("Object", ctTypeReference.getSimpleName());
}
@Test
public void testPrintAMethodWithImports() throws Exception {
final Launcher launcher = new Launcher();
final Factory factory = launcher.getFactory();
factory.getEnvironment().setAutoImports(true);
final SpoonModelBuilder compiler = launcher.createCompiler();
compiler.addInputSource(new File("./src/test/java/spoon/test/prettyprinter/testclasses/"));
compiler.build();
final String expected =
"public List<?> aMethod() {" +nl+
" return new ArrayList<>();" +nl+
"}";
final CtClass<?> aClass = (CtClass<?>) factory.Type().get(AClass.class);
assertEquals(expected, aClass.getMethodsByName("aMethod").get(0).toString());
final CtConstructorCall<?> constructorCall =
aClass.getElements(new TypeFilter<CtConstructorCall<?>>(CtConstructorCall.class))
.get(0);
final CtTypeReference<?> ctTypeReference = constructorCall.getType()
.getActualTypeArguments()
.get(0);
assertTrue(ctTypeReference.isImplicit());
assertEquals("Object", ctTypeReference.getSimpleName());
}
@Test
public void testPrintAMethodWithGeneric() throws Exception {
final Launcher launcher = new Launcher();
final Factory factory = launcher.getFactory();
factory.getEnvironment().setAutoImports(true);
final SpoonModelBuilder compiler = launcher.createCompiler();
compiler.addInputSource(new File("./src/test/java/spoon/test/prettyprinter/testclasses/"));
compiler.build();
final CtClass<?> aClass = (CtClass<?>) factory.Type().get(AClass.class);
final String expected = "public List<? extends ArrayList> aMethodWithGeneric() {" + System.lineSeparator()
+ " return new ArrayList<>();" + System.lineSeparator()
+ "}";
assertEquals(expected, aClass.getMethodsByName("aMethodWithGeneric").get(0).toString());
final CtConstructorCall<?> constructorCall =
aClass.getElements(new TypeFilter<CtConstructorCall<?>>(CtConstructorCall.class))
.get(0);
final CtTypeReference<?> ctTypeReference = constructorCall.getType()
.getActualTypeArguments()
.get(0);
assertTrue(ctTypeReference.isImplicit());
assertEquals("Object", ctTypeReference.getSimpleName());
}
@Test
public void autoImportUsesFullyQualifiedNameWhenImportedNameAlreadyPresent() throws Exception {
final Launcher launcher = new Launcher();
final Factory factory = launcher.getFactory();
factory.getEnvironment().setAutoImports(true);
final SpoonModelBuilder compiler = launcher.createCompiler();
compiler.addInputSource(new File("./src/test/java/spoon/test/prettyprinter/testclasses/sub/TypeIdentifierCollision.java"));
compiler.addInputSource(new File("./src/test/java/spoon/test/prettyprinter/testclasses/TypeIdentifierCollision.java"));
compiler.build();
final CtClass<?> aClass = (CtClass<?>) factory.Type().get( spoon.test.prettyprinter.testclasses.TypeIdentifierCollision.class );
String expected =
"public void setFieldUsingExternallyDefinedEnumWithSameNameAsLocal() {" +nl+
" localField = E1.ordinal();" +nl+
"}"
;
String computed = aClass.getMethodsByName("setFieldUsingExternallyDefinedEnumWithSameNameAsLocal").get(0).toString();
assertEquals( "E1 is statically imported then we can call it directly", expected, computed );
expected = //This is correct however it could be more concise.
"public void setFieldUsingLocallyDefinedEnum() {" +nl+
" localField = TypeIdentifierCollision.ENUM.E1.ordinal();" +nl+
"}"
;
computed = aClass.getMethodsByName("setFieldUsingLocallyDefinedEnum").get(0).toString();
assertEquals( expected, computed );
expected =
"public void setFieldOfClassWithSameNameAsTheCompilationUnitClass() {" +nl+
" globalField = localField;" +nl+
"}"
;
computed = aClass.getMethodsByName("setFieldOfClassWithSameNameAsTheCompilationUnitClass").get(0).toString();
assertEquals( "The static field of an external type with the same identifier as the compilation unit is statically imported", expected, computed );
expected = //This is correct however it could be more concise.
"public void referToTwoInnerClassesWithTheSameName() {" +nl+
" TypeIdentifierCollision.Class0.ClassA.VAR0 = TypeIdentifierCollision.Class0.ClassA.getNum();" +nl+
" TypeIdentifierCollision.Class1.ClassA.VAR1 = TypeIdentifierCollision.Class1.ClassA.getNum();" +nl+
"}"
;
//Ensure the ClassA of Class0 takes precedence over an import statement for ClassA in Class1, and it's identifier can be the short version.
computed = aClass.getMethodsByName("referToTwoInnerClassesWithTheSameName").get(0).toString();
assertEquals( "where inner types have the same identifier only one may be shortened and the other should be fully qualified", expected, computed );
expected =
"public enum ENUM {" +nl+
"E1(globalField,E1);" +nl+
" final int NUM;" +nl+nl+
" final Enum<?> e;" +nl+nl+
" private ENUM(int num, Enum<?> e) {" +nl+
" NUM = num;" +nl+
" this.e = e;" +nl+
" }" +nl+
"}"
;
computed = aClass.getNestedType("ENUM").toString();
assertEquals( "Parameters in an enum constructor should be statically imported when they refer to externally defined static field of a class with the same identifier as another locally defined type", expected, computed );
}
@Test
public void useFullyQualifiedNamesInCtElementImpl_toString() throws Exception {
Factory factory = build( AClass.class );
factory.getEnvironment().setAutoImports(false);
final CtClass<?> aClass = (CtClass<?>) factory.Type().get( AClass.class );
String computed = aClass.getMethodsByName("aMethod").get(0).toString();
final String expected =
"public java.util.List<?> aMethod() {" +nl+
" return new java.util.ArrayList<>();" +nl+
"}"
;
assertEquals( "the toString method of CtElementImpl should not shorten type names as it has no context or import statements", expected, computed );
}
@Test
public void printClassCreatedWithSpoon() throws Exception {
/* test that spoon is able to print a class that it created without setting manually the output (default configuration) */
final String nl = System.getProperty("line.separator");
Launcher launcher = new Launcher();
launcher.getEnvironment().setNoClasspath(true);
launcher.buildModel();
Factory factory = launcher.getFactory();
CtClass<Object> ctClass = factory.Class().create("foo.Bar");
PrettyPrinter pp = new DefaultJavaPrettyPrinter(factory.getEnvironment());
JavaOutputProcessor jop =
new JavaOutputProcessor(File.createTempFile("foo","").getParentFile(),pp);
jop.setFactory(factory);
jop.createJavaFile(ctClass);//JavaOutputProcessor is able to create the file even if we do not set the cu manually
String pathname = System.getProperty("java.io.tmpdir") + "/foo/Bar.java";
File javaFile = new File(pathname);
assertTrue(javaFile.exists());
assertEquals(nl + nl + "package foo;" + nl + nl + nl + "class Bar {}" + nl + nl,
IOUtils.toString(new FileInputStream(javaFile), "UTF-8"));
}
@Test
public void importsFromMultipleTypesSupported() {
final Launcher launcher = new Launcher();
launcher.addInputResource("./src/test/java/spoon/test/prettyprinter/testclasses/A.java");
launcher.run();
Environment env = launcher.getEnvironment();
env.setAutoImports(true);
DefaultJavaPrettyPrinter printer = new DefaultJavaPrettyPrinter(env);
printer.calculate(null, Arrays.asList(
launcher.getFactory().Class().get("spoon.test.prettyprinter.testclasses.A"),
launcher.getFactory().Class().get("spoon.test.prettyprinter.testclasses.B")
));
assertTrue(printer.getResult().contains("import java.util.ArrayList;"));
}
@Test
public void testTernaryParenthesesOnLocalVariable() {
// Spooning the code snippet
Launcher launcher = new Launcher();
CtCodeSnippetStatement snippet = launcher.getFactory().Code().createCodeSnippetStatement(
"final int foo = (new Object() instanceof Object ? new Object().equals(null) : new Object().equals(new Object())) ? 0 : new Object().hashCode();");
CtStatement compile = snippet.compile();
// Pretty-printing the Spooned code snippet and compiling the resulting code.
snippet = launcher.getFactory().Code().createCodeSnippetStatement(compile.toString());
assertEquals(compile, snippet.compile());
}
}