package spoon.test.compilation;
import org.eclipse.jdt.internal.compiler.batch.CompilationUnit;
import org.junit.Assert;
import org.junit.Test;
import spoon.Launcher;
import spoon.SpoonModelBuilder;
import spoon.reflect.code.BinaryOperatorKind;
import spoon.reflect.code.CtBinaryOperator;
import spoon.reflect.code.CtBlock;
import spoon.reflect.code.CtReturn;
import spoon.reflect.declaration.CtClass;
import spoon.reflect.declaration.CtMethod;
import spoon.reflect.declaration.CtType;
import spoon.reflect.declaration.ModifierKind;
import spoon.reflect.factory.CodeFactory;
import spoon.reflect.factory.CoreFactory;
import spoon.reflect.factory.Factory;
import spoon.reflect.reference.CtTypeReference;
import spoon.reflect.visitor.CtScanner;
import spoon.reflect.visitor.filter.TypeFilter;
import spoon.support.compiler.FileSystemFolder;
import spoon.support.compiler.jdt.JDTBasedSpoonCompiler;
import spoon.support.compiler.jdt.JDTBatchCompiler;
import spoon.support.SpoonClassNotFoundException;
import spoon.test.compilation.testclasses.Bar;
import spoon.test.compilation.testclasses.IBar;
import spoon.testing.utils.ModelUtils;
import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
public class CompilationTest {
@Test
public void compileCommandLineTest() throws Exception {
// the --compile option works, shouldCompile is set
String sourceFile = "./src/test/resources/noclasspath/Simple.java";
String compiledFile = "./spooned-classes/Simple.class";
// ensuring clean state
new File(compiledFile).delete();
Launcher launcher = new Launcher();
launcher.run(new String[]{
"-i", sourceFile,
"-o", "target/spooned",
"--compile",
"--compliance", "7",
"--level", "OFF"
});
assertEquals(true, launcher.getEnvironment().shouldCompile());
assertEquals(true, new File(compiledFile).exists());
}
@Test
public void compileTest() throws Exception {
// contract: the modified version of classes is the one that is compiled to binary code
final Launcher launcher = new Launcher();
launcher.addInputResource("./src/test/resources/noclasspath/Simple.java");
File outputBinDirectory = new File("./target/class-simple");
if (!outputBinDirectory.exists()) {
outputBinDirectory.mkdirs();
}
launcher.setBinaryOutputDirectory(outputBinDirectory);
launcher.getEnvironment().setShouldCompile(true);
launcher.buildModel();
Factory factory = launcher.getFactory();
CoreFactory core = factory.Core();
CodeFactory code = factory.Code();
CtClass simple = factory.Class().get("Simple");
CtMethod method = core.createMethod();
method.addModifier(ModifierKind.PUBLIC);
method.setType(factory.Type().integerPrimitiveType());
method.setSimpleName("m");
CtBlock block = core.createBlock();
CtReturn aReturn = core.createReturn();
CtBinaryOperator binaryOperator = code.createBinaryOperator(
code.createLiteral(10),
code.createLiteral(32),
BinaryOperatorKind.PLUS);
aReturn.setReturnedExpression(binaryOperator);
// return 10 + 32;
block.addStatement(aReturn);
method.setBody(block);
simple.addMethod(method);
launcher.getModelBuilder().compile();
final URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{outputBinDirectory.toURL()});
Class<?> aClass = urlClassLoader.loadClass("Simple");
Method m = aClass.getMethod("m");
Assert.assertEquals(42, m.invoke(aClass.newInstance()));
}
@Test
public void testNewInstanceFromExistingClass() throws Exception {
CtClass<Bar> barCtType = (CtClass<Bar>) ModelUtils.buildClass(Bar.class);
CtReturn<Integer> m = barCtType.getMethod("m").getBody().getStatement(0);
// we cannot use Bar because it causes a runtime cast exception (2 different Bar from different classloader)
IBar bar = barCtType.newInstance();
int value = bar.m();
assertEquals(1, value);
// change the return value
m.setReturnedExpression(m.getFactory().Code().createLiteral(2));
bar = barCtType.newInstance();
value = bar.m();
assertEquals(2, value);
m.replace(m.getFactory().Code().createCodeSnippetStatement("throw new FooEx()"));
try {
bar = barCtType.newInstance();
value = bar.m();
fail();
} catch (Exception ignore) {}
}
@Test
public void testNewInstance() throws Exception {
// contract: a ctclass can be instantiated, and each modification results in a new valid object
Factory factory = new Launcher().getFactory();
CtClass<Ifoo> c = factory.Code().createCodeSnippetStatement(
"class X implements spoon.test.compilation.Ifoo { public int foo() {int i=0; return i;} }").compile();
c.addModifier(ModifierKind.PUBLIC); // required otherwise java.lang.IllegalAccessException at runtime when instantiating
CtBlock body = c.getElements(new TypeFilter<>(CtBlock.class)).get(1);
Ifoo o = c.newInstance();
assertEquals(0, o.foo());
for (int i = 1; i <= 10; i++) {
body.getStatement(0).replace(factory.Code().createCodeSnippetStatement("int i = " + i + ";"));
o = c.newInstance();
// each time this is a new class
// each time the behavior has changed!
assertEquals(i, o.foo());
}
}
@Test
public void testFilterResourcesFile() throws Exception {
// shows how to filter input java files, for https://github.com/INRIA/spoon/issues/877
Launcher launcher = new Launcher() {
@Override
public SpoonModelBuilder createCompiler() {
return new JDTBasedSpoonCompiler(getFactory()) {
@Override
protected JDTBatchCompiler createBatchCompiler() {
return new JDTBatchCompiler(this) {
@Override
public CompilationUnit[] getCompilationUnits() {
List<CompilationUnit> units = new ArrayList<>();
for (CompilationUnit u : super.getCompilationUnits()) {
if (new String(u.getMainTypeName()).contains("Foo")) {
units.add(u);
}
}
return units.toArray(new CompilationUnit[0]);
}
};
}
};
}
};
launcher.addInputResource("./src/test/java");
launcher.buildModel();
// we indeed only have types declared in a file called *Foo*
for (CtType<?> t : launcher.getFactory().getModel().getAllTypes()) {
assertTrue(t.getPosition().getFile().getAbsolutePath().contains("Foo"));
}
}
@Test
public void testFilterResourcesDir() throws Exception {
// shows how to filter input java dir
// only in package called "reference"
Launcher launcher = new Launcher() {
@Override
public SpoonModelBuilder createCompiler() {
return new JDTBasedSpoonCompiler(getFactory()) {
@Override
protected JDTBatchCompiler createBatchCompiler() {
return new JDTBatchCompiler(this) {
@Override
public CompilationUnit[] getCompilationUnits() {
List<CompilationUnit> units = new ArrayList<>();
for (CompilationUnit u : super.getCompilationUnits()) {
if (new String(u.getFileName()).contains("/reference/")) {
units.add(u);
}
}
return units.toArray(new CompilationUnit[0]);
}
};
}
};
}
};
launcher.addInputResource("./src/test/java");
launcher.buildModel();
// we indeed only have types declared in a file in package reference
for (CtType<?> t : launcher.getFactory().getModel().getAllTypes()) {
assertTrue(t.getQualifiedName().contains("reference"));
}
}
@Test
public void testPrecompile() {
// without precompile
Launcher l = new Launcher();
l.setArgs(new String[] {"--noclasspath", "-i", "src/test/resources/compilation/"});
l.buildModel();
CtClass klass = l.getFactory().Class().get("compilation.Bar");
// without precompile, actualClass does not exist (an exception is thrown)
try {
klass.getSuperInterfaces().toArray(new CtTypeReference[0])[0].getActualClass();
fail();
} catch (SpoonClassNotFoundException ignore) {}
// with precompile
Launcher l2 = new Launcher();
l2.setArgs(new String[] {"--precompile", "--noclasspath", "-i", "src/test/resources/compilation/"});
l2.buildModel();
CtClass klass2 = l2.getFactory().Class().get("compilation.Bar");
// with precompile, actualClass is not null
Class actualClass = klass2.getSuperInterfaces().toArray(new CtTypeReference[0])[0].getActualClass();
assertNotNull(actualClass);
assertEquals("IBar", actualClass.getSimpleName());
// precompile can be used to compile processors on the fly
Launcher l3 = new Launcher();
l3.setArgs(new String[] {"--precompile", "--noclasspath", "-i", "src/test/resources/compilation/", "-p", "compilation.SimpleProcessor"});
l3.run();
}
@Test
public void testClassLoader() throws Exception {
// contract: the environment exposes a classloader configured by the spoonclass path
Launcher launcher = new Launcher();
// not in the classpath
try {
Class.forName("spoontest.a.ClassA");
fail();
} catch (ClassNotFoundException expected) {
}
// not in the spoon classpath before setting it
try {
launcher.getEnvironment().getInputClassLoader().loadClass("spoontest.a.ClassA");
fail();
} catch (ClassNotFoundException expected) {
}
launcher.getEnvironment().setSourceClasspath(new String[]{"src/test/resources/reference-test-2/ReferenceTest2.jar"});
Class c = launcher.getEnvironment().getInputClassLoader().loadClass("spoontest.a.ClassA");
assertEquals("spoontest.a.ClassA", c.getName());
}
@Test
public void testSingleClassLoader() throws Exception {
/*
* contract: the environment exposes a classloader configured by the spoonclass path,
* there is one class loader, so the loaded classes are compatible
*/
Launcher launcher = new Launcher();
launcher.addInputResource(new FileSystemFolder("./src/test/resources/classloader-test"));
File outputBinDirectory = new File("./target/classloader-test");
if (!outputBinDirectory.exists()) {
outputBinDirectory.mkdirs();
}
launcher.setBinaryOutputDirectory(outputBinDirectory);
launcher.getModelBuilder().build();
CtTypeReference<?> mIFoo = launcher.getFactory().Type().createReference("spoontest.IFoo");
CtTypeReference<?> mFoo = launcher.getFactory().Type().createReference("spoontest.Foo");
assertTrue("Foo subtype of IFoo", mFoo.isSubtypeOf(mIFoo));
launcher.getModelBuilder().compile(SpoonModelBuilder.InputType.FILES);
//Create new launcher which uses classes compiled by previous launcher.
//It simulates the classes without sources, which has to be accessed using reflection
launcher = new Launcher();
// not in the classpath
try {
Class.forName("spoontest.IFoo");
fail();
} catch (ClassNotFoundException expected) {
}
// not in the spoon classpath before setting it
try {
launcher.getEnvironment().getInputClassLoader().loadClass("spoontest.IFoo");
fail();
} catch (ClassNotFoundException expected) {
}
launcher.getEnvironment().setSourceClasspath(new String[]{outputBinDirectory.getAbsolutePath()});
mIFoo = launcher.getFactory().Type().createReference("spoontest.IFoo");
mFoo = launcher.getFactory().Type().createReference("spoontest.Foo");
//if it fails then it is because each class is loaded by different class loader
assertTrue("Foo subtype of IFoo", mFoo.isSubtypeOf(mIFoo));
// not in the spoon classpath before setting it
Class<?> ifoo = launcher.getEnvironment().getInputClassLoader().loadClass("spoontest.IFoo");
Class<?> foo = launcher.getEnvironment().getInputClassLoader().loadClass("spoontest.Foo");
assertTrue(ifoo.isAssignableFrom(foo));
assertTrue(ifoo.getClassLoader()==foo.getClassLoader());
}
@Test
public void testExoticClassLoader() throws Exception {
// contract: Spoon uses the exotic class loader
final List<String> l = new ArrayList<>();
class MyClassLoader extends ClassLoader {
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
l.add(name);
return super.loadClass(name, resolve);
}
}
Launcher launcher = new Launcher();
launcher.getEnvironment().setInputClassLoader(new MyClassLoader());
launcher.getEnvironment().setNoClasspath(true);
launcher.addInputResource("src/test/resources/reference-test/Foo.java");
launcher.buildModel();
launcher.getModel().getRootPackage().accept(new CtScanner() {
@Override
public <T> void visitCtTypeReference(CtTypeReference<T> reference) {
try {
// forcing loading it
reference.getTypeDeclaration();
} catch (SpoonClassNotFoundException ignore) {}
}
});
assertEquals(3, l.size());
assertTrue(l.contains("KJHKY"));
}
@Test
public void testCompilationInEmptyDir() throws Exception {
// Contract: Spoon can be launched in an empty folder as a working directory
// See: https://github.com/INRIA/spoon/pull/1208 and https://github.com/INRIA/spoon/issues/1246
// This test does not fail (it's not enough to change user.dir we should launch process inside that dir) but it explains the problem
String userDir = System.getProperty("user.dir");
File testFile = new File("src/test/resources/compilation/compilation-tests/IBar.java");
String absoluteTestPath = testFile.getAbsolutePath();
Path tempDirPath = Files.createTempDirectory("test_compilation");
System.setProperty("user.dir", tempDirPath.toFile().getAbsolutePath());
SpoonModelBuilder compiler = new Launcher().createCompiler();
compiler.addInputSource(new File(absoluteTestPath));
compiler.setBinaryOutputDirectory(tempDirPath.toFile());
compiler.compile(SpoonModelBuilder.InputType.FILES);
System.setProperty("user.dir", userDir);
assertThat(tempDirPath.toFile().listFiles().length, not(0));
}
}