diff --git a/src/test/java/spoon/test/template/TemplateInvocationSubstitutionTest.java b/src/test/java/spoon/test/template/TemplateInvocationSubstitutionTest.java new file mode 100644 index 00000000000..5c72549cd58 --- /dev/null +++ b/src/test/java/spoon/test/template/TemplateInvocationSubstitutionTest.java @@ -0,0 +1,48 @@ +package spoon.test.template; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import spoon.Launcher; +import spoon.reflect.code.CtBlock; +import spoon.reflect.code.CtStatement; +import spoon.reflect.declaration.CtClass; +import spoon.reflect.factory.Factory; +import spoon.support.compiler.FileSystemFile; +import spoon.test.template.testclasses.InvocationSubstitutionByExpressionTemplate; +import spoon.test.template.testclasses.InvocationSubstitutionByStatementTemplate; + +public class TemplateInvocationSubstitutionTest { + + @Test + public void testInvocationSubstitutionByStatement() throws Exception { + //contract: the template engine supports substitution of any method invocation + Launcher spoon = new Launcher(); + spoon.addTemplateResource(new FileSystemFile("./src/test/java/spoon/test/template/testclasses/InvocationSubstitutionByStatementTemplate.java")); + + spoon.buildModel(); + Factory factory = spoon.getFactory(); + + CtBlock model = factory.Class().get(InvocationSubstitutionByStatementTemplate.class).getMethod("sample").getBody(); + + CtClass resultKlass = factory.Class().create("Result"); + CtStatement result = new InvocationSubstitutionByStatementTemplate(model).apply(resultKlass); + assertEquals("throw new java.lang.RuntimeException(\"Failed\")", result.toString()); + } + + @Test + public void testInvocationSubstitutionByExpression() throws Exception { + //contract: the template engine supports substitution of any method invocation + Launcher spoon = new Launcher(); + spoon.addTemplateResource(new FileSystemFile("./src/test/java/spoon/test/template/testclasses/InvocationSubstitutionByExpressionTemplate.java")); + + spoon.buildModel(); + Factory factory = spoon.getFactory(); + + CtClass resultKlass = factory.Class().create("Result"); + CtBlock result = new InvocationSubstitutionByExpressionTemplate(factory.createLiteral("abc")).apply(resultKlass); + assertEquals("java.lang.System.out.println(\"abc\".substring(1))", result.getStatement(0).toString()); + assertEquals("java.lang.System.out.println(\"abc\".substring(1))", result.getStatement(1).toString()); + } +} diff --git a/src/test/java/spoon/test/template/TemplateReplaceReturnTest.java b/src/test/java/spoon/test/template/TemplateReplaceReturnTest.java index fadcaf31214..40936e4eae0 100644 --- a/src/test/java/spoon/test/template/TemplateReplaceReturnTest.java +++ b/src/test/java/spoon/test/template/TemplateReplaceReturnTest.java @@ -30,7 +30,7 @@ public void testReturnReplaceTemplate() throws Exception { CtClass resultKlass = factory.Class().create(factory.Package().getOrCreate("spoon.test.template"), "ReturnReplaceResult"); new ReturnReplaceTemplate(model).apply(resultKlass); - assertEquals("{ { if (((java.lang.System.currentTimeMillis()) % 2L) == 0) { return \"Panna\"; }else { return \"Orel\"; } }}", resultKlass.getMethod("method").getBody().toString().replaceAll("[\\r\\n\\t]+", "").replaceAll("\\s{2,}", " ")); + assertEquals("{ if (((java.lang.System.currentTimeMillis()) % 2L) == 0) { return \"Panna\"; }else { return \"Orel\"; }}", resultKlass.getMethod("method").getBody().toString().replaceAll("[\\r\\n\\t]+", "").replaceAll("\\s{2,}", " ")); launcher.setSourceOutputDirectory(new File("./target/spooned/")); launcher.getModelBuilder().generateProcessedSourceFiles(OutputType.CLASSES); ModelUtils.canBeBuilt(new File("./target/spooned/spoon/test/template/ReturnReplaceResult.java"), 8); diff --git a/src/test/java/spoon/test/template/TemplateTest.java b/src/test/java/spoon/test/template/TemplateTest.java index 15b6b87dab1..4595b15f9bc 100644 --- a/src/test/java/spoon/test/template/TemplateTest.java +++ b/src/test/java/spoon/test/template/TemplateTest.java @@ -11,21 +11,29 @@ import spoon.reflect.code.CtTry; import spoon.reflect.declaration.CtClass; import spoon.reflect.declaration.CtElement; +import spoon.reflect.declaration.CtEnum; import spoon.reflect.declaration.CtField; import spoon.reflect.declaration.CtMethod; +import spoon.reflect.declaration.CtPackage; import spoon.reflect.declaration.CtParameter; +import spoon.reflect.declaration.CtType; import spoon.reflect.factory.Factory; import spoon.reflect.visitor.ModelConsistencyChecker; import spoon.reflect.visitor.filter.NameFilter; import spoon.support.compiler.FileSystemFile; +import spoon.support.compiler.FileSystemFolder; import spoon.support.template.Parameters; +import spoon.support.template.SubstitutionVisitor; +import spoon.template.Substitution; import spoon.template.TemplateMatcher; import spoon.template.TemplateParameter; import spoon.test.template.testclasses.ArrayAccessTemplate; import spoon.test.template.testclasses.InvocationTemplate; +import spoon.test.template.testclasses.LoggerModel; import spoon.test.template.testclasses.NtonCodeTemplate; import spoon.test.template.testclasses.SecurityCheckerTemplate; import spoon.test.template.testclasses.SimpleTemplate; +import spoon.test.template.testclasses.SubstituteRootTemplate; import spoon.test.template.testclasses.bounds.CheckBound; import spoon.test.template.testclasses.bounds.CheckBoundMatcher; import spoon.test.template.testclasses.bounds.CheckBoundTemplate; @@ -40,18 +48,26 @@ import spoon.test.template.testclasses.inheritance.SuperTemplate; import spoon.test.template.testclasses.logger.Logger; import spoon.test.template.testclasses.logger.LoggerTemplateProcessor; +import spoon.test.template.testclasses.types.AClassModel; +import spoon.test.template.testclasses.types.AnEnumModel; +import spoon.test.template.testclasses.types.AnIfaceModel; import java.io.File; import java.io.Serializable; import java.rmi.Remote; +import java.security.GeneralSecurityException; import java.util.ArrayList; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; @@ -130,10 +146,9 @@ public void testTemplateInheritance() throws Exception { // contract: invocations are replaced by actual invocations assertEquals("toBeOverriden()", methodWithTemplatedParameters.getBody().getStatement(3).toString()); - // contract: foreach are inlined - CtBlock templatedForEach = methodWithTemplatedParameters.getBody().getStatement(4); - assertEquals("java.lang.System.out.println(0)", templatedForEach.getStatement(0).toString()); - assertEquals("java.lang.System.out.println(1)", templatedForEach.getStatement(1).toString()); + // contract: foreach are inlined without extra block + assertEquals("java.lang.System.out.println(0)", methodWithTemplatedParameters.getBody().getStatement(4).toString()); + assertEquals("java.lang.System.out.println(1)", methodWithTemplatedParameters.getBody().getStatement(5).toString()); } @Test @@ -362,6 +377,39 @@ public void testExtensionBlock() throws Exception { assertTrue(aTry.getBody().getStatements().size() > 1); } + @Test + public void testExtensionDecoupledSubstitutionVisitor() throws Exception { + //contract: substitution can be done on model, which is not based on Template + final Launcher launcher = new Launcher(); + launcher.setArgs(new String[] {"--output-type", "nooutput" }); + launcher.addInputResource("./src/test/java/spoon/test/template/testclasses/logger/Logger.java"); + launcher.addTemplateResource(new FileSystemFile("./src/test/java/spoon/test/template/testclasses/LoggerModel.java")); + + launcher.buildModel(); + Factory factory = launcher.getFactory(); + + final CtClass aTemplateModelType = launcher.getFactory().Class().get(LoggerModel.class); + final CtMethod aTemplateModel = aTemplateModelType.getMethod("block"); + final CtClass aTargetType = launcher.getFactory().Class().get(Logger.class); + final CtMethod toBeLoggedMethod = aTargetType.getMethodsByName("enter").get(0); + + + Map params = new HashMap<>(); + params.put("_classname_", aTargetType.getSimpleName()) ; + params.put("_methodName_", toBeLoggedMethod.getSimpleName()); + params.put("_block_", toBeLoggedMethod.getBody()); + final List> aMethods = new SubstitutionVisitor(factory, params).substitute(aTemplateModel.clone()); + assertEquals(1, aMethods.size()); + final CtMethod aMethod = aMethods.get(0); + assertTrue(aMethod.getBody().getStatement(0) instanceof CtTry); + final CtTry aTry = (CtTry) aMethod.getBody().getStatement(0); + assertTrue(aTry.getFinalizer().getStatement(0) instanceof CtInvocation); + assertEquals("spoon.test.template.testclasses.logger.Logger.exit(\"enter\")", aTry.getFinalizer().getStatement(0).toString()); + assertTrue(aTry.getBody().getStatement(0) instanceof CtInvocation); + assertEquals("spoon.test.template.testclasses.logger.Logger.enter(\"Logger\", \"enter\")", aTry.getBody().getStatement(0).toString()); + assertTrue(aTry.getBody().getStatements().size() > 1); + } + @Test public void testTemplateInterfaces() throws Exception { Launcher spoon = new Launcher(); @@ -540,4 +588,72 @@ public void testTemplateArrayAccess() throws Exception { CtMethod m2 = resultKlass.getMethod("method2"); assertEquals("java.lang.System.out.println(\"second\")", m2.getBody().getStatement(0).toString()); } + + @Test + public void testStatementTemplateRootSubstitution() throws Exception { + //contract: the template engine supports substitution of root element + Launcher spoon = new Launcher(); + spoon.addTemplateResource(new FileSystemFile("./src/test/java/spoon/test/template/testclasses/SubstituteRootTemplate.java")); + + spoon.buildModel(); + Factory factory = spoon.getFactory(); + + CtClass templateClass = factory.Class().get(SubstituteRootTemplate.class); + CtBlock templateParam = (CtBlock) templateClass.getMethod("sampleBlock").getBody(); + + CtClass resultKlass = factory.Class().create("Result"); + CtStatement result = new SubstituteRootTemplate(templateParam).apply(resultKlass); + assertEquals("java.lang.String s = \"Spoon is cool!\"", ((CtBlock)result).getStatement(0).toString()); + } + + @Test + public void testInsertType() throws Exception { + final Launcher launcher = new Launcher(); + launcher.setArgs(new String[] {"--output-type", "nooutput" }); + launcher.addTemplateResource(new FileSystemFolder("./src/test/java/spoon/test/template/testclasses/types")); + + launcher.buildModel(); + Factory factory = launcher.getFactory(); + + CtPackage targetPackage = factory.Package().create(factory.getModel().getRootPackage(), "generated"); + factory.getModel().getRootPackage().addPackage(targetPackage); + + + Map parameters = new HashMap<>(); + //replace someMethod with genMethod + parameters.put("someMethod", "genMethod"); + + //contract: we can generate interface + final CtType aIfaceModel = launcher.getFactory().Interface().get(AnIfaceModel.class); + CtType genIface = Substitution.insertType(targetPackage, "GenIface", aIfaceModel, parameters); + assertNotNull(genIface); + assertSame(genIface, factory.Type().get("generated.GenIface")); + CtMethod generatedIfaceMethod = genIface.getMethod("genMethod"); + assertNotNull(generatedIfaceMethod); + assertNull(genIface.getMethod("someMethod")); + + //add new substitution request - replace AnIfaceModel by GenIface + parameters.put("AnIfaceModel", genIface.getReference()); + //contract: we can generate class + final CtType aClassModel = launcher.getFactory().Class().get(AClassModel.class); + CtType genClass = Substitution.insertType(targetPackage, "GenClass", aClassModel, parameters); + assertNotNull(genClass); + assertSame(genClass, factory.Type().get("generated.GenClass")); + CtMethod generatedClassMethod = genClass.getMethod("genMethod"); + assertNotNull(generatedClassMethod); + assertNull(genClass.getMethod("someMethod")); + assertTrue(generatedIfaceMethod!=generatedClassMethod); + assertTrue(generatedClassMethod.isOverriding(generatedIfaceMethod)); + + //contract: we can generate enum + parameters.put("case1", "GOOD"); + parameters.put("case2", "BETTER"); + final CtType aEnumModel = launcher.getFactory().Type().get(AnEnumModel.class); + CtEnum genEnum = (CtEnum) Substitution.insertType(targetPackage, "GenEnum", aEnumModel, parameters); + assertNotNull(genEnum); + assertSame(genEnum, factory.Type().get("generated.GenEnum")); + assertEquals(2, genEnum.getEnumValues().size()); + assertEquals("GOOD", genEnum.getEnumValues().get(0).getSimpleName()); + assertEquals("BETTER", genEnum.getEnumValues().get(1).getSimpleName()); + } } diff --git a/src/test/java/spoon/test/template/testclasses/InvocationSubstitutionByExpressionTemplate.java b/src/test/java/spoon/test/template/testclasses/InvocationSubstitutionByExpressionTemplate.java new file mode 100644 index 00000000000..7542c2ac04d --- /dev/null +++ b/src/test/java/spoon/test/template/testclasses/InvocationSubstitutionByExpressionTemplate.java @@ -0,0 +1,28 @@ +package spoon.test.template.testclasses; + +import spoon.reflect.code.CtExpression; +import spoon.template.BlockTemplate; +import spoon.template.Local; +import spoon.template.Parameter; + +public class InvocationSubstitutionByExpressionTemplate extends BlockTemplate { + + @Override + public void block() throws Throwable { + System.out.println(_expression_().substring(1)); + System.out.println(_expression_.S().substring(1)); + } + + @Parameter + CtExpression _expression_; + + @Local + public InvocationSubstitutionByExpressionTemplate(CtExpression expr) { + this._expression_ = expr; + } + + @Local + String _expression_() { + return null; + } +} \ No newline at end of file diff --git a/src/test/java/spoon/test/template/testclasses/InvocationSubstitutionByStatementTemplate.java b/src/test/java/spoon/test/template/testclasses/InvocationSubstitutionByStatementTemplate.java new file mode 100644 index 00000000000..43e75933fad --- /dev/null +++ b/src/test/java/spoon/test/template/testclasses/InvocationSubstitutionByStatementTemplate.java @@ -0,0 +1,31 @@ +package spoon.test.template.testclasses; + +import spoon.reflect.code.CtStatement; +import spoon.template.Local; +import spoon.template.Parameter; +import spoon.template.StatementTemplate; + +public class InvocationSubstitutionByStatementTemplate extends StatementTemplate { + + @Override + public void statement() throws Throwable { + _statement_(); + } + + @Parameter("_statement_") + CtStatement statement; + + @Local + public InvocationSubstitutionByStatementTemplate(CtStatement statement) { + this.statement = statement; + } + + @Local + void _statement_() { + } + + @Local + void sample() { + throw new RuntimeException("Failed"); + } +} \ No newline at end of file diff --git a/src/test/java/spoon/test/template/testclasses/LoggerModel.java b/src/test/java/spoon/test/template/testclasses/LoggerModel.java new file mode 100644 index 00000000000..02a7399224e --- /dev/null +++ b/src/test/java/spoon/test/template/testclasses/LoggerModel.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2006-2015 INRIA and contributors + * Spoon - http://spoon.gforge.inria.fr/ + * + * This software is governed by the CeCILL-C License under French law and + * abiding by the rules of distribution of free software. You can use, modify + * and/or redistribute the software under the terms of the CeCILL-C license as + * circulated by CEA, CNRS and INRIA at http://www.cecill.info. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL-C license and that you accept its terms. + */ + +package spoon.test.template.testclasses; + +import spoon.reflect.code.CtBlock; +import spoon.test.template.testclasses.logger.Logger; + +public class LoggerModel { + private String _classname_; + private String _methodName_; + private CtBlock _block_; + + public void block() throws Throwable { + try { + Logger.enter(_classname_, _methodName_); + _block_.S(); + } finally { + Logger.exit(_methodName_); + } + } +} diff --git a/src/test/java/spoon/test/template/testclasses/SubstituteRootTemplate.java b/src/test/java/spoon/test/template/testclasses/SubstituteRootTemplate.java new file mode 100644 index 00000000000..8c9d8018eb5 --- /dev/null +++ b/src/test/java/spoon/test/template/testclasses/SubstituteRootTemplate.java @@ -0,0 +1,28 @@ +package spoon.test.template.testclasses; + +import spoon.reflect.code.CtBlock; +import spoon.template.Local; +import spoon.template.Parameter; +import spoon.template.StatementTemplate; +import spoon.template.TemplateParameter; + +public class SubstituteRootTemplate extends StatementTemplate { + + @Override + public void statement() throws Throwable { + block.S(); + } + + @Parameter + TemplateParameter block; + + @Local + public SubstituteRootTemplate(CtBlock block) { + this.block = block; + } + + @Local + void sampleBlock() { + String s="Spoon is cool!"; + } +} \ No newline at end of file diff --git a/src/test/java/spoon/test/template/testclasses/types/AClassModel.java b/src/test/java/spoon/test/template/testclasses/types/AClassModel.java new file mode 100644 index 00000000000..897c2d35efe --- /dev/null +++ b/src/test/java/spoon/test/template/testclasses/types/AClassModel.java @@ -0,0 +1,23 @@ +package spoon.test.template.testclasses.types; + +import java.util.AbstractList; + +public class AClassModel extends AbstractList implements AnIfaceModel { + + public AClassModel() { + } + + @Override + public E get(int index) { + throw new IndexOutOfBoundsException(); + } + + @Override + public int size() { + return 0; + } + + @Override + public void someMethod() { + } +} diff --git a/src/test/java/spoon/test/template/testclasses/types/AnEnumModel.java b/src/test/java/spoon/test/template/testclasses/types/AnEnumModel.java new file mode 100644 index 00000000000..795506ea42b --- /dev/null +++ b/src/test/java/spoon/test/template/testclasses/types/AnEnumModel.java @@ -0,0 +1,5 @@ +package spoon.test.template.testclasses.types; + +public enum AnEnumModel { + case1, case2; +} diff --git a/src/test/java/spoon/test/template/testclasses/types/AnIfaceModel.java b/src/test/java/spoon/test/template/testclasses/types/AnIfaceModel.java new file mode 100644 index 00000000000..90a23086afd --- /dev/null +++ b/src/test/java/spoon/test/template/testclasses/types/AnIfaceModel.java @@ -0,0 +1,7 @@ +package spoon.test.template.testclasses.types; + +import java.io.Serializable; + +public interface AnIfaceModel extends Serializable { + void someMethod(); +}