From b108cb54f09537fee44332602f8a966c49a825be Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Wed, 28 Jun 2017 21:51:58 +0200 Subject: [PATCH 01/28] test: reproduce the problem --- .../spoon/test/template/TemplateTest.java | 36 +++++++++++++++++++ .../testclasses/FieldAccessTemplate.java | 22 ++++++++++++ .../testclasses/inheritance/SubTemplate.java | 14 ++++++-- 3 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 src/test/java/spoon/test/template/testclasses/FieldAccessTemplate.java diff --git a/src/test/java/spoon/test/template/TemplateTest.java b/src/test/java/spoon/test/template/TemplateTest.java index 3efb2a9535e..13184d2f0b4 100644 --- a/src/test/java/spoon/test/template/TemplateTest.java +++ b/src/test/java/spoon/test/template/TemplateTest.java @@ -30,6 +30,7 @@ import spoon.template.TemplateMatcher; import spoon.template.TemplateParameter; import spoon.test.template.testclasses.ArrayAccessTemplate; +import spoon.test.template.testclasses.FieldAccessTemplate; import spoon.test.template.testclasses.InnerClassTemplate; import spoon.test.template.testclasses.InvocationTemplate; import spoon.test.template.testclasses.LoggerModel; @@ -175,6 +176,23 @@ public void testTemplateInheritance() throws Exception { assertEquals("java.lang.System.out.println(1)", methodWithTemplatedParameters.getBody().getStatement(10).toString()); //contract: for each whose expression is not a template parameter is not inlined assertTrue(methodWithTemplatedParameters.getBody().getStatement(11) instanceof CtForEach); + + // contract: local variable write are replaced by local variable write with modified local variable name + assertEquals("newVarName = o", methodWithTemplatedParameters.getBody().getStatement(12).toString()); + + // contract: local variable read are replaced by local variable read with modified local variable name + assertEquals("l = ((java.util.LinkedList) (newVarName))", methodWithTemplatedParameters.getBody().getStatement(13).toString()); + + // contract; field access is handled same like local variable access + CtMethod methodWithFieldAccess = subc.getElements( + new NameFilter>("methodWithFieldAccess")).get(0); + + // contract: field write are replaced by field write with modified field name + assertEquals("newVarName = o", methodWithFieldAccess.getBody().getStatement(2).toString()); + + // contract: field read are replaced by field read with modified field name + assertEquals("l = ((java.util.LinkedList) (newVarName))", methodWithFieldAccess.getBody().getStatement(3).toString()); + } @Test @@ -811,4 +829,22 @@ public void substituteSubString() throws Exception { } } } + + @Test + public void testFieldAccessNameSubstitution() throws Exception { + //contract: the substitution of name of whole field is possible + Launcher spoon = new Launcher(); + spoon.addTemplateResource(new FileSystemFile("./src/test/java/spoon/test/template/testclasses/FieldAccessTemplate.java")); + + spoon.buildModel(); + Factory factory = spoon.getFactory(); + + { + //contract: String value is substituted in String literal + final CtClass result = (CtClass) new FieldAccessTemplate("value").apply(factory.Class().create("x.X")); + assertEquals("int value;", result.getField("value").toString()); + + assertEquals("value = 7", result.getMethodsByName("m").get(0).getBody().getStatement(0).toString()); + } + } } diff --git a/src/test/java/spoon/test/template/testclasses/FieldAccessTemplate.java b/src/test/java/spoon/test/template/testclasses/FieldAccessTemplate.java new file mode 100644 index 00000000000..faa9bb9d67c --- /dev/null +++ b/src/test/java/spoon/test/template/testclasses/FieldAccessTemplate.java @@ -0,0 +1,22 @@ +package spoon.test.template.testclasses; + +import spoon.template.ExtensionTemplate; +import spoon.template.Local; +import spoon.template.Parameter; + +public class FieldAccessTemplate extends ExtensionTemplate { + + int $field$; + + void m() { + $field$ = 7; + } + + @Local + public FieldAccessTemplate(String fieldName) { + this.fieldName = fieldName; + } + + @Parameter("$field$") + String fieldName; +} diff --git a/src/test/java/spoon/test/template/testclasses/inheritance/SubTemplate.java b/src/test/java/spoon/test/template/testclasses/inheritance/SubTemplate.java index c0216fbf5bb..b31fab1886a 100644 --- a/src/test/java/spoon/test/template/testclasses/inheritance/SubTemplate.java +++ b/src/test/java/spoon/test/template/testclasses/inheritance/SubTemplate.java @@ -39,6 +39,16 @@ public void methodWithTemplatedParameters(Object params) { for(Object x : o) { System.out.println(x); // will be NOT inlined } + var = o; //will be replaced by newVarName = o + l = (ArrayList) var; //will be replaced by l = (LinkedList) newVarName + } + + List var = null; + public void methodWithFieldAccess() { + List o = (ArrayList) new ArrayList(); // will be replaced by List o = (LinkedList) new LinkedList(); + ArrayList l = null; // will be replaced by LinkedList l = null; + var = o; + l = (ArrayList) var; //will be replaced by l = (LinkedList) newVarName } /** @@ -55,8 +65,8 @@ void var() {} public List params; // name template "var" -> "newVarName" - @Parameter - public String var = "newVarName"; + @Parameter("var") + public String param_var = "newVarName"; // type reference template @Parameter From 7566c3ef9f9c1e8534ced5b0ccb28cdd791a8aa7 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Wed, 28 Jun 2017 21:52:14 +0200 Subject: [PATCH 02/28] fix SubstitutionVisitor --- .../support/template/SubstitutionVisitor.java | 52 ++++++++++++++++--- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/src/main/java/spoon/support/template/SubstitutionVisitor.java b/src/main/java/spoon/support/template/SubstitutionVisitor.java index 22aa43053aa..07cf95505a4 100644 --- a/src/main/java/spoon/support/template/SubstitutionVisitor.java +++ b/src/main/java/spoon/support/template/SubstitutionVisitor.java @@ -238,11 +238,45 @@ private void visitFieldAccess(final CtFieldAccess fieldAccess) { throw context.replace(toReplace, enumValueAccess); } else if ((value != null) && value.getClass().isArray()) { throw context.replace(toReplace, factory.Code().createLiteralArray((Object[]) value)); + } else if (fieldAccess == toReplace && value instanceof String) { + /* + * If the value is type String, then it is ambiguous request, because: + * A) sometime client wants to replace parameter field access by String literal + * + * @Parameter + * String field = "x" + * + * System.printLn(field) //is substitutes as: System.printLn("x") + * + * but in the case of local variables it already behaves like this + * { + * int field; + * System.printLn(field) //is substitutes as: System.printLn(x) + * } + * + * B) sometime client wants to keep field access and just substitute field name + * + * @Parameter("field") + * String fieldName = "x" + * + * System.printLn(field) //is substitutes as: System.printLn(x) + * + * ---------------------- + * + * The case B is more clear and is compatible with substitution of name of local variable, method name, etc. + * And case A can be easily modeled using this clear code + * + * @Parameter + * String field = "x" + * System.printLn("field") //is substitutes as: System.printLn("x") + */ + //New implementation always replaces the name of the accessed field + //so do nothing here. The string substitution is handled by #scanCtReference } else { throw context.replace(toReplace, factory.Code().createLiteral(value)); } } else { - throw context.replace(toReplace, toReplace.clone()); + throw context.replace(toReplace, (CtElement) value); } } } @@ -370,14 +404,14 @@ public List substitute(E element) { * @return list where each item is assured to be of type itemClass */ @SuppressWarnings("unchecked") - private static List getParameterValueAsListOfClones(Class itemClass, Object parameterValue) { + private List getParameterValueAsListOfClones(Class itemClass, Object parameterValue) { List list = getParameterValueAsNewList(parameterValue); for (int i = 0; i < list.size(); i++) { list.set(i, getParameterValueAsClass(itemClass, list.get(i))); } return (List) list; } - private static List getParameterValueAsNewList(Object parameterValue) { + private List getParameterValueAsNewList(Object parameterValue) { List list = new ArrayList<>(); if (parameterValue != null) { if (parameterValue instanceof Object[]) { @@ -405,7 +439,7 @@ private static List getParameterValueAsNewList(Object parameterValue) { * @return parameterValue cast (in future potentially converted) to itemClass */ @SuppressWarnings("unchecked") - private static T getParameterValueAsClass(Class itemClass, Object parameterValue) { + private T getParameterValueAsClass(Class itemClass, Object parameterValue) { if (parameterValue == null || parameterValue == NULL_VALUE) { return null; } @@ -425,7 +459,11 @@ private static T getParameterValueAsClass(Class itemClass, Object paramet if (parameterValue instanceof CtTypeReference) { //convert type reference into code element as class access CtTypeReference tr = (CtTypeReference) parameterValue; - return (T) tr.getFactory().Code().createClassAccess(tr); + return (T) factory.Code().createClassAccess(tr); + } + if (parameterValue instanceof String) { + //convert String to code element as Literal + return (T) factory.Code().createLiteral((String) parameterValue); } } throw new SpoonException("Parameter value has unexpected class: " + parameterValue.getClass().getName() + ". Expected class is: " + itemClass.getName()); @@ -434,7 +472,7 @@ private static T getParameterValueAsClass(Class itemClass, Object paramet * @param parameterValue a value of an template parameter * @return parameter value converted to String */ - private static String getParameterValueAsString(Object parameterValue) { + private String getParameterValueAsString(Object parameterValue) { if (parameterValue == null) { return null; } @@ -503,7 +541,7 @@ private static CtTypeReference getParameterValueAsTypeReference(Factory f * @param index index of item from the list, or null if item is not expected to be a list * @return parameterValue (optionally item from the list) cast (in future potentially converted) to itemClass */ - private static T getParameterValueAtIndex(Class itemClass, Object parameterValue, Integer index) { + private T getParameterValueAtIndex(Class itemClass, Object parameterValue, Integer index) { if (index != null) { //convert to list, but do not clone List list = getParameterValueAsNewList(parameterValue); From 2069e7543af7f042a2e14dd3f623f34bde77d277 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Wed, 28 Jun 2017 21:52:29 +0200 Subject: [PATCH 03/28] fix old tests - behavior changed! --- .../java/spoon/test/template/testclasses/LoggerModel.java | 4 ++-- .../constructors/TemplateWithFieldsAndMethods.java | 2 +- .../test/template/testclasses/logger/LoggerTemplate.java | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/java/spoon/test/template/testclasses/LoggerModel.java b/src/test/java/spoon/test/template/testclasses/LoggerModel.java index 02a7399224e..21752e30194 100644 --- a/src/test/java/spoon/test/template/testclasses/LoggerModel.java +++ b/src/test/java/spoon/test/template/testclasses/LoggerModel.java @@ -27,10 +27,10 @@ public class LoggerModel { public void block() throws Throwable { try { - Logger.enter(_classname_, _methodName_); + Logger.enter("_classname_", "_methodName_"); _block_.S(); } finally { - Logger.exit(_methodName_); + Logger.exit("_methodName_"); } } } diff --git a/src/test/java/spoon/test/template/testclasses/constructors/TemplateWithFieldsAndMethods.java b/src/test/java/spoon/test/template/testclasses/constructors/TemplateWithFieldsAndMethods.java index 239543519da..c78169fedc5 100644 --- a/src/test/java/spoon/test/template/testclasses/constructors/TemplateWithFieldsAndMethods.java +++ b/src/test/java/spoon/test/template/testclasses/constructors/TemplateWithFieldsAndMethods.java @@ -20,7 +20,7 @@ public TemplateWithFieldsAndMethods(String PARAM, } public String methodToBeInserted() { - return PARAM; + return "PARAM"; } public String fieldToBeInserted; diff --git a/src/test/java/spoon/test/template/testclasses/logger/LoggerTemplate.java b/src/test/java/spoon/test/template/testclasses/logger/LoggerTemplate.java index d0451b96bcd..d1780fead35 100644 --- a/src/test/java/spoon/test/template/testclasses/logger/LoggerTemplate.java +++ b/src/test/java/spoon/test/template/testclasses/logger/LoggerTemplate.java @@ -40,10 +40,10 @@ public LoggerTemplate(String _classname_, String _methodName_, CtBlock _block @Override public void block() throws Throwable { try { - Logger.enter(_classname_, _methodName_); + Logger.enter("_classname_", "_methodName_"); _block_.S(); } finally { - Logger.exit(_methodName_); + Logger.exit("_methodName_"); } } } From 9a561ba1f71483a4d3d7b46694958cb621b9aede Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Wed, 28 Jun 2017 22:11:21 +0200 Subject: [PATCH 04/28] minor change to force Travis run again --- src/test/java/spoon/test/template/testclasses/LoggerModel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/spoon/test/template/testclasses/LoggerModel.java b/src/test/java/spoon/test/template/testclasses/LoggerModel.java index 21752e30194..fb1cd55c4b2 100644 --- a/src/test/java/spoon/test/template/testclasses/LoggerModel.java +++ b/src/test/java/spoon/test/template/testclasses/LoggerModel.java @@ -27,7 +27,7 @@ public class LoggerModel { public void block() throws Throwable { try { - Logger.enter("_classname_", "_methodName_"); + Logger.enter("_classname_", "_methodName_"); _block_.S(); } finally { Logger.exit("_methodName_"); From e2840120f48de62ab52f370cccde4d985c176003 Mon Sep 17 00:00:00 2001 From: Simon Urli Date: Thu, 6 Jul 2017 14:49:55 +0200 Subject: [PATCH 05/28] Revert "fix old tests - behavior changed!" This reverts commit 7005aef70acb84e98590e7db44022a98587c6939. --- .../java/spoon/test/template/testclasses/LoggerModel.java | 4 ++-- .../constructors/TemplateWithFieldsAndMethods.java | 2 +- .../test/template/testclasses/logger/LoggerTemplate.java | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/java/spoon/test/template/testclasses/LoggerModel.java b/src/test/java/spoon/test/template/testclasses/LoggerModel.java index fb1cd55c4b2..02a7399224e 100644 --- a/src/test/java/spoon/test/template/testclasses/LoggerModel.java +++ b/src/test/java/spoon/test/template/testclasses/LoggerModel.java @@ -27,10 +27,10 @@ public class LoggerModel { public void block() throws Throwable { try { - Logger.enter("_classname_", "_methodName_"); + Logger.enter(_classname_, _methodName_); _block_.S(); } finally { - Logger.exit("_methodName_"); + Logger.exit(_methodName_); } } } diff --git a/src/test/java/spoon/test/template/testclasses/constructors/TemplateWithFieldsAndMethods.java b/src/test/java/spoon/test/template/testclasses/constructors/TemplateWithFieldsAndMethods.java index c78169fedc5..239543519da 100644 --- a/src/test/java/spoon/test/template/testclasses/constructors/TemplateWithFieldsAndMethods.java +++ b/src/test/java/spoon/test/template/testclasses/constructors/TemplateWithFieldsAndMethods.java @@ -20,7 +20,7 @@ public TemplateWithFieldsAndMethods(String PARAM, } public String methodToBeInserted() { - return "PARAM"; + return PARAM; } public String fieldToBeInserted; diff --git a/src/test/java/spoon/test/template/testclasses/logger/LoggerTemplate.java b/src/test/java/spoon/test/template/testclasses/logger/LoggerTemplate.java index d1780fead35..d0451b96bcd 100644 --- a/src/test/java/spoon/test/template/testclasses/logger/LoggerTemplate.java +++ b/src/test/java/spoon/test/template/testclasses/logger/LoggerTemplate.java @@ -40,10 +40,10 @@ public LoggerTemplate(String _classname_, String _methodName_, CtBlock _block @Override public void block() throws Throwable { try { - Logger.enter("_classname_", "_methodName_"); + Logger.enter(_classname_, _methodName_); _block_.S(); } finally { - Logger.exit("_methodName_"); + Logger.exit(_methodName_); } } } From a5e1ca57fc73de506dde4a27b5c7ce59c397e59a Mon Sep 17 00:00:00 2001 From: Simon Urli Date: Thu, 6 Jul 2017 16:49:20 +0200 Subject: [PATCH 06/28] Add a new test corresponding to the example of #1444 --- .../spoon/test/template/TemplateTest.java | 20 +++++++++++++++++++ .../AnotherFieldAccessTemplate.java | 7 +++++++ 2 files changed, 27 insertions(+) create mode 100644 src/test/java/spoon/test/template/testclasses/AnotherFieldAccessTemplate.java diff --git a/src/test/java/spoon/test/template/TemplateTest.java b/src/test/java/spoon/test/template/TemplateTest.java index 13184d2f0b4..e5ca9ca2dbe 100644 --- a/src/test/java/spoon/test/template/TemplateTest.java +++ b/src/test/java/spoon/test/template/TemplateTest.java @@ -29,6 +29,7 @@ import spoon.template.Substitution; import spoon.template.TemplateMatcher; import spoon.template.TemplateParameter; +import spoon.test.template.testclasses.AnotherFieldAccessTemplate; import spoon.test.template.testclasses.ArrayAccessTemplate; import spoon.test.template.testclasses.FieldAccessTemplate; import spoon.test.template.testclasses.InnerClassTemplate; @@ -847,4 +848,23 @@ public void testFieldAccessNameSubstitution() throws Exception { assertEquals("value = 7", result.getMethodsByName("m").get(0).getBody().getStatement(0).toString()); } } + + @Test + public void testAnotherFieldAccessNameSubstitution() throws Exception { + //contract: the substitution of name of whole field is possible + Launcher spoon = new Launcher(); + spoon.addTemplateResource(new FileSystemFile("./src/test/java/spoon/test/template/testclasses/AnotherFieldAccessTemplate.java")); + + spoon.buildModel(); + Factory factory = spoon.getFactory(); + + { + //contract: String value is substituted in String literal + final CtClass result = (CtClass) new AnotherFieldAccessTemplate().apply(factory.Class().create("x.X")); + assertEquals("int x;", result.getField("x").toString()); + assertEquals("int m_x;", result.getField("m_x").toString()); + + assertEquals("java.lang.System.out.println(((x) + (m_x)))", result.getAnonymousExecutables().get(0).getBody().getStatement(0).toString()); + } + } } diff --git a/src/test/java/spoon/test/template/testclasses/AnotherFieldAccessTemplate.java b/src/test/java/spoon/test/template/testclasses/AnotherFieldAccessTemplate.java new file mode 100644 index 00000000000..7056617efb8 --- /dev/null +++ b/src/test/java/spoon/test/template/testclasses/AnotherFieldAccessTemplate.java @@ -0,0 +1,7 @@ +package spoon.test.template.testclasses; + +/** + * Created by urli on 06/07/2017. + */ +public class AnotherFieldAccessTemplate { +} From 20c97a3c9884c6a3c4e65d577464e5674f38618e Mon Sep 17 00:00:00 2001 From: Simon Urli Date: Thu, 6 Jul 2017 16:49:54 +0200 Subject: [PATCH 07/28] Only change the error comment when the contract is not respected with parameter value --- src/main/java/spoon/template/Substitution.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/spoon/template/Substitution.java b/src/main/java/spoon/template/Substitution.java index d413e1b8ccc..da41bbe55b9 100644 --- a/src/main/java/spoon/template/Substitution.java +++ b/src/main/java/spoon/template/Substitution.java @@ -691,7 +691,7 @@ private static void checkTemplateContracts(CtClass c) { } } if (!found) { - throw new TemplateException("if a proxy parameter is declared and named \"" + proxyName + "\", then a type member named \"\" + proxyName + \"\" must exist."); + throw new TemplateException("if a proxy parameter is declared and named \"" + proxyName + "\", then a type member named \"" + proxyName + "\" must exist."); } } From d906bfd04afa9ceb7696513d4acd07291cd6aae8 Mon Sep 17 00:00:00 2001 From: Simon Urli Date: Thu, 6 Jul 2017 16:50:36 +0200 Subject: [PATCH 08/28] Add the new template test class --- .../testclasses/AnotherFieldAccessTemplate.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/test/java/spoon/test/template/testclasses/AnotherFieldAccessTemplate.java b/src/test/java/spoon/test/template/testclasses/AnotherFieldAccessTemplate.java index 7056617efb8..00e7a74aa25 100644 --- a/src/test/java/spoon/test/template/testclasses/AnotherFieldAccessTemplate.java +++ b/src/test/java/spoon/test/template/testclasses/AnotherFieldAccessTemplate.java @@ -1,7 +1,19 @@ package spoon.test.template.testclasses; +import spoon.template.ExtensionTemplate; +import spoon.template.Parameter; + /** * Created by urli on 06/07/2017. */ -public class AnotherFieldAccessTemplate { +public class AnotherFieldAccessTemplate extends ExtensionTemplate { + + @Parameter("$name$") + String name = "x"; + + int $name$; + int m_$name$; + { + System.out.println($name$+m_$name$); + } } From b1a3aba6db7bf495ca923e85ef628b5f33feb789 Mon Sep 17 00:00:00 2001 From: Simon Urli Date: Thu, 6 Jul 2017 16:50:56 +0200 Subject: [PATCH 09/28] Change SubstitutionVisitor to treat differently case A and case B --- .../support/template/SubstitutionVisitor.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/main/java/spoon/support/template/SubstitutionVisitor.java b/src/main/java/spoon/support/template/SubstitutionVisitor.java index 07cf95505a4..880664b1d28 100644 --- a/src/main/java/spoon/support/template/SubstitutionVisitor.java +++ b/src/main/java/spoon/support/template/SubstitutionVisitor.java @@ -38,8 +38,10 @@ import spoon.reflect.code.CtReturn; import spoon.reflect.code.CtStatement; import spoon.reflect.code.CtThisAccess; +import spoon.reflect.declaration.CtClass; import spoon.reflect.declaration.CtElement; import spoon.reflect.declaration.CtExecutable; +import spoon.reflect.declaration.CtField; import spoon.reflect.declaration.CtNamedElement; import spoon.reflect.declaration.CtType; import spoon.reflect.factory.Factory; @@ -49,6 +51,7 @@ import spoon.reflect.reference.CtTypeReference; import spoon.reflect.visitor.CtInheritanceScanner; import spoon.reflect.visitor.CtScanner; +import spoon.reflect.visitor.filter.TypeFilter; import spoon.template.Parameter; import spoon.template.Template; import spoon.template.TemplateParameter; @@ -270,8 +273,19 @@ private void visitFieldAccess(final CtFieldAccess fieldAccess) { * String field = "x" * System.printLn("field") //is substitutes as: System.printLn("x") */ - //New implementation always replaces the name of the accessed field - //so do nothing here. The string substitution is handled by #scanCtReference + // if parameter value is not the same name as field name + // then we substitute the value + CtType declaringClass = ref.getDeclaringType().getDeclaration(); + List fields = declaringClass.getElements(new TypeFilter<>(CtField.class)); + + for (CtField field : fields) { + Parameter param = field.getAnnotation(Parameter.class); + if (param != null && param.value().equals(ref.getSimpleName())) { + return; // case B do nothing + } + } + + throw context.replace(toReplace, factory.Code().createLiteral(value)); // case A } else { throw context.replace(toReplace, factory.Code().createLiteral(value)); } From 435bf93bd874aeae8ba45feac5348119542126ba Mon Sep 17 00:00:00 2001 From: Simon Urli Date: Thu, 6 Jul 2017 16:55:31 +0200 Subject: [PATCH 10/28] Fix checkstyle --- src/main/java/spoon/support/template/SubstitutionVisitor.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/spoon/support/template/SubstitutionVisitor.java b/src/main/java/spoon/support/template/SubstitutionVisitor.java index 880664b1d28..971c4923a91 100644 --- a/src/main/java/spoon/support/template/SubstitutionVisitor.java +++ b/src/main/java/spoon/support/template/SubstitutionVisitor.java @@ -38,7 +38,6 @@ import spoon.reflect.code.CtReturn; import spoon.reflect.code.CtStatement; import spoon.reflect.code.CtThisAccess; -import spoon.reflect.declaration.CtClass; import spoon.reflect.declaration.CtElement; import spoon.reflect.declaration.CtExecutable; import spoon.reflect.declaration.CtField; From 853e8a396f1427013062f87b327d80ab43b0d200 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Sat, 15 Jul 2017 11:32:39 +0200 Subject: [PATCH 11/28] test: field access in inner class --- .../spoon/test/template/TemplateTest.java | 20 ++++++++++++++++ .../FieldAccessOfInnerClassTemplate.java | 24 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 src/test/java/spoon/test/template/testclasses/FieldAccessOfInnerClassTemplate.java diff --git a/src/test/java/spoon/test/template/TemplateTest.java b/src/test/java/spoon/test/template/TemplateTest.java index e5ca9ca2dbe..0860e23e443 100644 --- a/src/test/java/spoon/test/template/TemplateTest.java +++ b/src/test/java/spoon/test/template/TemplateTest.java @@ -31,6 +31,7 @@ import spoon.template.TemplateParameter; import spoon.test.template.testclasses.AnotherFieldAccessTemplate; import spoon.test.template.testclasses.ArrayAccessTemplate; +import spoon.test.template.testclasses.FieldAccessOfInnerClassTemplate; import spoon.test.template.testclasses.FieldAccessTemplate; import spoon.test.template.testclasses.InnerClassTemplate; import spoon.test.template.testclasses.InvocationTemplate; @@ -849,6 +850,25 @@ public void testFieldAccessNameSubstitution() throws Exception { } } + @Test + public void testFieldAccessNameSubstitutionInInnerClass() throws Exception { + //contract: the substitution of name of whole field is possible in inner class too + Launcher spoon = new Launcher(); + spoon.addTemplateResource(new FileSystemFile("./src/test/java/spoon/test/template/testclasses/FieldAccessOfInnerClassTemplate.java")); + + spoon.buildModel(); + Factory factory = spoon.getFactory(); + + { + //contract: String value is substituted in String literal + final CtClass result = (CtClass) new FieldAccessOfInnerClassTemplate("value").apply(factory.Class().create("x.X")); + final CtClass innerClass = result.getNestedType("Inner"); + assertEquals("int value;", innerClass.getField("value").toString()); + + assertEquals("value = 7", innerClass.getMethodsByName("m").get(0).getBody().getStatement(0).toString()); + } + } + @Test public void testAnotherFieldAccessNameSubstitution() throws Exception { //contract: the substitution of name of whole field is possible diff --git a/src/test/java/spoon/test/template/testclasses/FieldAccessOfInnerClassTemplate.java b/src/test/java/spoon/test/template/testclasses/FieldAccessOfInnerClassTemplate.java new file mode 100644 index 00000000000..43a2ededc30 --- /dev/null +++ b/src/test/java/spoon/test/template/testclasses/FieldAccessOfInnerClassTemplate.java @@ -0,0 +1,24 @@ +package spoon.test.template.testclasses; + +import spoon.template.ExtensionTemplate; +import spoon.template.Local; +import spoon.template.Parameter; + +public class FieldAccessOfInnerClassTemplate extends ExtensionTemplate { + + class Inner { + int $field$; + + void m() { + $field$ = 7; + } + } + + @Local + public FieldAccessOfInnerClassTemplate(String fieldName) { + this.$field$ = fieldName; + } + + @Parameter + String $field$; +} From e1a3332b584310c19569b894252d539e22543913 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Sat, 15 Jul 2017 11:33:59 +0200 Subject: [PATCH 12/28] rollback SubstitutionVisitor treat differently case A and case B --- .../support/template/SubstitutionVisitor.java | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/src/main/java/spoon/support/template/SubstitutionVisitor.java b/src/main/java/spoon/support/template/SubstitutionVisitor.java index 971c4923a91..ab0656d3c0b 100644 --- a/src/main/java/spoon/support/template/SubstitutionVisitor.java +++ b/src/main/java/spoon/support/template/SubstitutionVisitor.java @@ -40,7 +40,6 @@ import spoon.reflect.code.CtThisAccess; import spoon.reflect.declaration.CtElement; import spoon.reflect.declaration.CtExecutable; -import spoon.reflect.declaration.CtField; import spoon.reflect.declaration.CtNamedElement; import spoon.reflect.declaration.CtType; import spoon.reflect.factory.Factory; @@ -50,7 +49,6 @@ import spoon.reflect.reference.CtTypeReference; import spoon.reflect.visitor.CtInheritanceScanner; import spoon.reflect.visitor.CtScanner; -import spoon.reflect.visitor.filter.TypeFilter; import spoon.template.Parameter; import spoon.template.Template; import spoon.template.TemplateParameter; @@ -271,20 +269,10 @@ private void visitFieldAccess(final CtFieldAccess fieldAccess) { * @Parameter * String field = "x" * System.printLn("field") //is substitutes as: System.printLn("x") + * System.printLn(field) //is substitutes as: System.printLn("x") because the parameter `field` is constructed with literal value */ - // if parameter value is not the same name as field name - // then we substitute the value - CtType declaringClass = ref.getDeclaringType().getDeclaration(); - List fields = declaringClass.getElements(new TypeFilter<>(CtField.class)); - - for (CtField field : fields) { - Parameter param = field.getAnnotation(Parameter.class); - if (param != null && param.value().equals(ref.getSimpleName())) { - return; // case B do nothing - } - } - - throw context.replace(toReplace, factory.Code().createLiteral(value)); // case A + //New implementation always replaces the name of the accessed field + //so do nothing here. The string substitution is handled by #scanCtReference } else { throw context.replace(toReplace, factory.Code().createLiteral(value)); } From aa422baef4fdcddd786afdbf7d5fe2fb609d012a Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Sat, 15 Jul 2017 11:34:48 +0200 Subject: [PATCH 13/28] convert String to Literal parameter value automatically --- .../spoon/support/template/Parameters.java | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/main/java/spoon/support/template/Parameters.java b/src/main/java/spoon/support/template/Parameters.java index ed93b9ef6c5..3e9ee5dd94f 100644 --- a/src/main/java/spoon/support/template/Parameters.java +++ b/src/main/java/spoon/support/template/Parameters.java @@ -212,11 +212,28 @@ public static List getNames(CtClass> templateType) public static Map getNamesToValues(Template template, CtClass> templateType) { //use linked hash map to assure same order of parameter names. There are cases during substitution of parameters when substitution order matters. E.g. SubstitutionVisitor#substituteName(...) Map params = new LinkedHashMap<>(); + final Factory factory = templateType.getFactory(); try { for (CtFieldReference f : templateType.getAllFields()) { if (isParameterSource(f)) { String parameterName = getParameterName(f); - params.put(parameterName, getValue(template, parameterName, (Field) f.getActualField())); + Field field = (Field) f.getActualField(); + Object value = getValue(template, parameterName, field); + if (parameterName.equals(f.getSimpleName()) && field.getType().equals(String.class)) { + /* + * it is not a proxy parameter but the value type is String. + * In this case the String must be always understood as String literal + * otherwise the SubstitutionVisitor would not know whether + * A) field reference name should be replaced by field reference with the new name equal to value of string + * B) field reference should be replaced by String literal + */ + value = factory.Code().createLiteral((String) value); + } + /* + * ... else ... it is a proxy parameter whose value type is always a String - assured by Substitution#checkTemplateContracts. + * And which is used to replace one field reference to another - keep String value there + */ + params.put(parameterName, value); } } } catch (Exception e) { From 949ed27f34e409f530cf09f89a42cb94d4e3b0a0 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Sat, 15 Jul 2017 12:54:40 +0200 Subject: [PATCH 14/28] disable one Substitution#checkTemplateContracts contract --- .../java/spoon/template/Substitution.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/main/java/spoon/template/Substitution.java b/src/main/java/spoon/template/Substitution.java index da41bbe55b9..399fffe2022 100644 --- a/src/main/java/spoon/template/Substitution.java +++ b/src/main/java/spoon/template/Substitution.java @@ -684,15 +684,16 @@ private static void checkTemplateContracts(CtClass c) { // } // contract: if a proxy parameter is declared and named "x" (@Parameter("x")), then a type member named "x" must exist. - boolean found = false; - for (CtTypeMember member: c.getTypeMembers()) { - if (member.getSimpleName().equals(proxyName)) { - found = true; - } - } - if (!found) { - throw new TemplateException("if a proxy parameter is declared and named \"" + proxyName + "\", then a type member named \"" + proxyName + "\" must exist."); - } + // it is not good contract, because fields of inner classes and method invocations cannot be handled then +// boolean found = false; +// for (CtTypeMember member: c.getTypeMembers()) { +// if (member.getSimpleName().equals(proxyName)) { +// found = true; +// } +// } +// if (!found) { +// throw new TemplateException("if a proxy parameter is declared and named \"" + proxyName + "\", then a type member named \"" + proxyName + "\" must exist."); +// } } } From 9da7b1affe348847e1a49c0fbc350057b2f4e1c8 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Sat, 15 Jul 2017 11:36:02 +0200 Subject: [PATCH 15/28] adapt test to pass SubstitutionVisitor changes --- src/test/java/spoon/test/template/TemplateTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/spoon/test/template/TemplateTest.java b/src/test/java/spoon/test/template/TemplateTest.java index 0860e23e443..c60a4d2f92b 100644 --- a/src/test/java/spoon/test/template/TemplateTest.java +++ b/src/test/java/spoon/test/template/TemplateTest.java @@ -441,8 +441,8 @@ public void testExtensionDecoupledSubstitutionVisitor() throws Exception { Map params = new HashMap<>(); - params.put("_classname_", aTargetType.getSimpleName()) ; - params.put("_methodName_", toBeLoggedMethod.getSimpleName()); + params.put("_classname_", factory.Code().createLiteral(aTargetType.getSimpleName())); + params.put("_methodName_", factory.Code().createLiteral(toBeLoggedMethod.getSimpleName())); params.put("_block_", toBeLoggedMethod.getBody()); final List> aMethods = new SubstitutionVisitor(factory, params).substitute(aTemplateModel.clone()); assertEquals(1, aMethods.size()); From 8ee34001108a310ed04ee0070417fee95e752df2 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Sat, 15 Jul 2017 12:55:46 +0200 Subject: [PATCH 16/28] adapt test InvocationTemplate --- .../test/template/testclasses/InvocationTemplate.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/test/java/spoon/test/template/testclasses/InvocationTemplate.java b/src/test/java/spoon/test/template/testclasses/InvocationTemplate.java index 7f14dc48ab8..a0ab1d3b98a 100644 --- a/src/test/java/spoon/test/template/testclasses/InvocationTemplate.java +++ b/src/test/java/spoon/test/template/testclasses/InvocationTemplate.java @@ -16,15 +16,14 @@ void invoke() { @Local public InvocationTemplate(CtTypeReference ifaceType, String methodName) { this._IFace = ifaceType.getSimpleName(); - this.$method$ = methodName; + this.methodName = methodName; } @Parameter("IFace") String _IFace; - @Parameter - String $method$; - + @Parameter("$method$") + String methodName; @Local From 8aaeccbad3195b7d2f12cd1c5bad0ca8dcba4d4d Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Sat, 15 Jul 2017 12:56:02 +0200 Subject: [PATCH 17/28] adapt test FieldAccessOfInnerClassTemplate --- .../testclasses/FieldAccessOfInnerClassTemplate.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/java/spoon/test/template/testclasses/FieldAccessOfInnerClassTemplate.java b/src/test/java/spoon/test/template/testclasses/FieldAccessOfInnerClassTemplate.java index 43a2ededc30..28947128e20 100644 --- a/src/test/java/spoon/test/template/testclasses/FieldAccessOfInnerClassTemplate.java +++ b/src/test/java/spoon/test/template/testclasses/FieldAccessOfInnerClassTemplate.java @@ -8,7 +8,7 @@ public class FieldAccessOfInnerClassTemplate extends ExtensionTemplate { class Inner { int $field$; - + void m() { $field$ = 7; } @@ -16,9 +16,9 @@ void m() { @Local public FieldAccessOfInnerClassTemplate(String fieldName) { - this.$field$ = fieldName; + this.fieldName = fieldName; } - @Parameter - String $field$; + @Parameter("$field$") + String fieldName; } From 7f2431ef05bceb8399ac485d01a3b171bd3ec48d Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Mon, 24 Jul 2017 17:25:01 +0200 Subject: [PATCH 18/28] add GeneratedByMember required by new feature --- src/test/java/spoon/test/template/TemplateTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/java/spoon/test/template/TemplateTest.java b/src/test/java/spoon/test/template/TemplateTest.java index 28345601ee6..5af113434b1 100644 --- a/src/test/java/spoon/test/template/TemplateTest.java +++ b/src/test/java/spoon/test/template/TemplateTest.java @@ -201,6 +201,8 @@ public void testTemplateInheritance() throws Exception { // contract; field access is handled same like local variable access CtMethod methodWithFieldAccess = subc.getElements( new NameFilter>("methodWithFieldAccess")).get(0); + elementToGeneratedByMember.put(methodWithFieldAccess, "#methodWithFieldAccess"); + elementToGeneratedByMember.put(subc.getField("newVarName"), "#var"); // contract: field write are replaced by field write with modified field name assertEquals("newVarName = o", methodWithFieldAccess.getBody().getStatement(2).toString()); From be4679f90779a914bb986347ffb29c978fd015e7 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Fri, 4 Aug 2017 21:23:36 +0200 Subject: [PATCH 19/28] do not create CtLiteral automatically --- .../spoon/support/template/Parameters.java | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/src/main/java/spoon/support/template/Parameters.java b/src/main/java/spoon/support/template/Parameters.java index cf43cc68ed1..48ced1d6985 100644 --- a/src/main/java/spoon/support/template/Parameters.java +++ b/src/main/java/spoon/support/template/Parameters.java @@ -212,28 +212,11 @@ public static List getNames(CtClass> templateType) public static Map getNamesToValues(Template template, CtClass> templateType) { //use linked hash map to assure same order of parameter names. There are cases during substitution of parameters when substitution order matters. E.g. SubstitutionVisitor#substituteName(...) Map params = new LinkedHashMap<>(); - final Factory factory = templateType.getFactory(); try { for (CtFieldReference f : templateType.getAllFields()) { if (isParameterSource(f)) { String parameterName = getParameterName(f); - Field field = (Field) f.getActualField(); - Object value = getValue(template, parameterName, field); - if (parameterName.equals(f.getSimpleName()) && field.getType().equals(String.class)) { - /* - * it is not a proxy parameter but the value type is String. - * In this case the String must be always understood as String literal - * otherwise the SubstitutionVisitor would not know whether - * A) field reference name should be replaced by field reference with the new name equal to value of string - * B) field reference should be replaced by String literal - */ - value = factory.Code().createLiteral((String) value); - } - /* - * ... else ... it is a proxy parameter whose value type is always a String - assured by Substitution#checkTemplateContracts. - * And which is used to replace one field reference to another - keep String value there - */ - params.put(parameterName, value); + params.put(parameterName, getValue(template, parameterName, (Field) f.getActualField())); } } } catch (Exception e) { From b854d07718613885543bd5500d8274114d851bad Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Fri, 4 Aug 2017 21:24:00 +0200 Subject: [PATCH 20/28] restore the Template Parameter constraint --- .../java/spoon/template/Substitution.java | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/main/java/spoon/template/Substitution.java b/src/main/java/spoon/template/Substitution.java index 399fffe2022..d413e1b8ccc 100644 --- a/src/main/java/spoon/template/Substitution.java +++ b/src/main/java/spoon/template/Substitution.java @@ -684,16 +684,15 @@ private static void checkTemplateContracts(CtClass c) { // } // contract: if a proxy parameter is declared and named "x" (@Parameter("x")), then a type member named "x" must exist. - // it is not good contract, because fields of inner classes and method invocations cannot be handled then -// boolean found = false; -// for (CtTypeMember member: c.getTypeMembers()) { -// if (member.getSimpleName().equals(proxyName)) { -// found = true; -// } -// } -// if (!found) { -// throw new TemplateException("if a proxy parameter is declared and named \"" + proxyName + "\", then a type member named \"" + proxyName + "\" must exist."); -// } + boolean found = false; + for (CtTypeMember member: c.getTypeMembers()) { + if (member.getSimpleName().equals(proxyName)) { + found = true; + } + } + if (!found) { + throw new TemplateException("if a proxy parameter is declared and named \"" + proxyName + "\", then a type member named \"\" + proxyName + \"\" must exist."); + } } } From 5e3a2ff3d01ff6c805331062c096aac7f89b82a1 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Fri, 4 Aug 2017 21:32:22 +0200 Subject: [PATCH 21/28] parameter of type String is simply substituted in method name --- .../testclasses/FieldAccessOfInnerClassTemplate.java | 6 +++--- .../test/template/testclasses/InvocationTemplate.java | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/test/java/spoon/test/template/testclasses/FieldAccessOfInnerClassTemplate.java b/src/test/java/spoon/test/template/testclasses/FieldAccessOfInnerClassTemplate.java index 28947128e20..59c02c7f35f 100644 --- a/src/test/java/spoon/test/template/testclasses/FieldAccessOfInnerClassTemplate.java +++ b/src/test/java/spoon/test/template/testclasses/FieldAccessOfInnerClassTemplate.java @@ -16,9 +16,9 @@ void m() { @Local public FieldAccessOfInnerClassTemplate(String fieldName) { - this.fieldName = fieldName; + this.$field$ = fieldName; } - @Parameter("$field$") - String fieldName; + @Parameter + String $field$; } diff --git a/src/test/java/spoon/test/template/testclasses/InvocationTemplate.java b/src/test/java/spoon/test/template/testclasses/InvocationTemplate.java index a0ab1d3b98a..7f14dc48ab8 100644 --- a/src/test/java/spoon/test/template/testclasses/InvocationTemplate.java +++ b/src/test/java/spoon/test/template/testclasses/InvocationTemplate.java @@ -16,14 +16,15 @@ void invoke() { @Local public InvocationTemplate(CtTypeReference ifaceType, String methodName) { this._IFace = ifaceType.getSimpleName(); - this.methodName = methodName; + this.$method$ = methodName; } @Parameter("IFace") String _IFace; - @Parameter("$method$") - String methodName; + @Parameter + String $method$; + @Local From ecf69be083cc602328667de38f513cdd9bf6eaf5 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Fri, 4 Aug 2017 21:33:14 +0200 Subject: [PATCH 22/28] If String literal is needed then template must already contain it --- .../constructors/TemplateWithFieldsAndMethods.java | 2 +- .../test/template/testclasses/logger/LoggerTemplate.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/spoon/test/template/testclasses/constructors/TemplateWithFieldsAndMethods.java b/src/test/java/spoon/test/template/testclasses/constructors/TemplateWithFieldsAndMethods.java index 239543519da..c78169fedc5 100644 --- a/src/test/java/spoon/test/template/testclasses/constructors/TemplateWithFieldsAndMethods.java +++ b/src/test/java/spoon/test/template/testclasses/constructors/TemplateWithFieldsAndMethods.java @@ -20,7 +20,7 @@ public TemplateWithFieldsAndMethods(String PARAM, } public String methodToBeInserted() { - return PARAM; + return "PARAM"; } public String fieldToBeInserted; diff --git a/src/test/java/spoon/test/template/testclasses/logger/LoggerTemplate.java b/src/test/java/spoon/test/template/testclasses/logger/LoggerTemplate.java index d0451b96bcd..d1780fead35 100644 --- a/src/test/java/spoon/test/template/testclasses/logger/LoggerTemplate.java +++ b/src/test/java/spoon/test/template/testclasses/logger/LoggerTemplate.java @@ -40,10 +40,10 @@ public LoggerTemplate(String _classname_, String _methodName_, CtBlock _block @Override public void block() throws Throwable { try { - Logger.enter(_classname_, _methodName_); + Logger.enter("_classname_", "_methodName_"); _block_.S(); } finally { - Logger.exit(_methodName_); + Logger.exit("_methodName_"); } } } From 2b88af7e795bcbe1a8849e590006d14e6df4775e Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Sun, 13 Aug 2017 10:55:24 +0200 Subject: [PATCH 23/28] report invalid field reference --- .../visitor/DefaultJavaPrettyPrinter.java | 3 ++ .../spoon/test/template/TemplateTest.java | 28 +++++++++++++++++++ .../TemplateWithFieldsAndMethods_Wrong.java | 20 +++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 src/test/java/spoon/test/template/testclasses/constructors/TemplateWithFieldsAndMethods_Wrong.java diff --git a/src/main/java/spoon/reflect/visitor/DefaultJavaPrettyPrinter.java b/src/main/java/spoon/reflect/visitor/DefaultJavaPrettyPrinter.java index b7bb8ad401b..929ea79dc90 100644 --- a/src/main/java/spoon/reflect/visitor/DefaultJavaPrettyPrinter.java +++ b/src/main/java/spoon/reflect/visitor/DefaultJavaPrettyPrinter.java @@ -787,6 +787,9 @@ private void printCtFieldAccess(CtFieldAccess f) { * Search for potential variable declaration until we found a class which declares or inherits this field */ final CtField field = f.getVariable().getFieldDeclaration(); + if (field == null) { + throw new SpoonException("The reference to field named \"" + f.getVariable().getSimpleName() + "\" is invalid, because there is no field with such name."); + } final String fieldName = field.getSimpleName(); CtVariable var = f.getVariable().map(new PotentialVariableDeclarationFunction(fieldName)).first(); if (var != field) { diff --git a/src/test/java/spoon/test/template/TemplateTest.java b/src/test/java/spoon/test/template/TemplateTest.java index a505495f85f..fb4dc72b749 100644 --- a/src/test/java/spoon/test/template/TemplateTest.java +++ b/src/test/java/spoon/test/template/TemplateTest.java @@ -51,6 +51,7 @@ import spoon.test.template.testclasses.constructors.C1; import spoon.test.template.testclasses.constructors.TemplateWithConstructor; import spoon.test.template.testclasses.constructors.TemplateWithFieldsAndMethods; +import spoon.test.template.testclasses.constructors.TemplateWithFieldsAndMethods_Wrong; import spoon.test.template.testclasses.inheritance.InterfaceTemplate; import spoon.test.template.testclasses.inheritance.SubClass; import spoon.test.template.testclasses.inheritance.SubTemplate; @@ -340,7 +341,34 @@ public void testTemplateC1() throws Exception { assertEquals(0, factory.getEnvironment().getErrorCount()); assertEquals(0, factory.getEnvironment().getWarningCount()); + } + @Test + public void testTemplateWithWrongUsedStringParam() throws Exception { + Launcher spoon = new Launcher(); + Factory factory = spoon.createFactory(); + spoon.createCompiler( + factory, + SpoonResourceHelper + .resources("./src/test/java/spoon/test/template/testclasses/constructors/C1.java"), + SpoonResourceHelper + .resources( + "./src/test/java/spoon/test/template/testclasses/constructors/TemplateWithFieldsAndMethods_Wrong.java")) + .build(); + + CtClass c1 = factory.Class().get(C1.class); + + new TemplateWithFieldsAndMethods_Wrong( + "testparam").apply(c1); + + CtMethod m = c1.getMethod("methodToBeInserted"); + assertNotNull(m); + //contract: printing of code which contains invalid field reference, fails with nice exception + try { + m.getBody().getStatement(0).toString(); + } catch (SpoonException e) { + assertTrue("The error description doesn't contain name of invalid field. There is:\n" + e.getMessage(), e.getMessage().indexOf("testparam") >= 0); + } } @Test diff --git a/src/test/java/spoon/test/template/testclasses/constructors/TemplateWithFieldsAndMethods_Wrong.java b/src/test/java/spoon/test/template/testclasses/constructors/TemplateWithFieldsAndMethods_Wrong.java new file mode 100644 index 00000000000..a02612e3722 --- /dev/null +++ b/src/test/java/spoon/test/template/testclasses/constructors/TemplateWithFieldsAndMethods_Wrong.java @@ -0,0 +1,20 @@ +package spoon.test.template.testclasses.constructors; + +import spoon.template.ExtensionTemplate; +import spoon.template.Local; +import spoon.template.Parameter; + +public class TemplateWithFieldsAndMethods_Wrong extends ExtensionTemplate { + + @Parameter + public String PARAM; + + @Local + public TemplateWithFieldsAndMethods_Wrong(String PARAM) { + this.PARAM = PARAM; + } + + public String methodToBeInserted() { + return PARAM; + } +} From d14196092e75cb41d856d4875ea97534431cf128 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Sun, 13 Aug 2017 11:08:23 +0200 Subject: [PATCH 24/28] add path the the failing node into exception --- .../java/spoon/reflect/visitor/DefaultJavaPrettyPrinter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/spoon/reflect/visitor/DefaultJavaPrettyPrinter.java b/src/main/java/spoon/reflect/visitor/DefaultJavaPrettyPrinter.java index 929ea79dc90..23f9beadb05 100644 --- a/src/main/java/spoon/reflect/visitor/DefaultJavaPrettyPrinter.java +++ b/src/main/java/spoon/reflect/visitor/DefaultJavaPrettyPrinter.java @@ -788,7 +788,7 @@ private void printCtFieldAccess(CtFieldAccess f) { */ final CtField field = f.getVariable().getFieldDeclaration(); if (field == null) { - throw new SpoonException("The reference to field named \"" + f.getVariable().getSimpleName() + "\" is invalid, because there is no field with such name."); + throw new SpoonException("The reference to field named \"" + f.getVariable().getSimpleName() + "\" is invalid, because there is no field with such name on path:" + getPath(f)); } final String fieldName = field.getSimpleName(); CtVariable var = f.getVariable().map(new PotentialVariableDeclarationFunction(fieldName)).first(); From d0ae0912eb8d3d516dfe13806aba1622e036a923 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Fri, 25 Aug 2017 18:56:57 +0200 Subject: [PATCH 25/28] update Template documentation --- doc/template_definition.md | 46 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/doc/template_definition.md b/doc/template_definition.md index 184579e3515..2c4c6ba5c12 100644 --- a/doc/template_definition.md +++ b/doc/template_definition.md @@ -209,7 +209,7 @@ is transformed into: #### Literal template Parameters For literals, Spoon provides developers with *literal template parameters*. When the parameter is known to -be a literal (primitive types, `String`, `Class` or a one-dimensional array of +be a literal (primitive types, `Class` or a one-dimensional array of these types), a template parameter, annotated with `@Parameter` enables one to have concise template code. ```java @@ -222,6 +222,48 @@ val = 5; if (list.size()>val) {...} ``` +String parameters are not working like other primitive type parameters, since we're using String parameters only to rename elements of the code like fields and methods. + +```java +// with String template parameter, which is used to substitute method name. +@Parameter +String methodName; +... +methodName = "generatedMethod"; +... +void methodName() { + //a body of generated method +} +``` + +To use a parameter with a type String like other primitive types, use CtLiteral. + +```java +// with CtLiteral template parameter, which is used to substitute String literal +@Parameter +CtLiteral val; +... +val = factory.Code().createLiteral("Some string"); +... +String someMethod() { + return val.S(); //is substituted as return "Some string"; +} +``` + +or String literal can be optionally generated like this + +```java +// with CtLiteral template parameter, which is used to substitute String literal +@Parameter +String val; +... +val = "Some string"; +... +String someMethod() { + return "val"; //is substituted as return "Some string"; +} +``` + Note that AST elements can also be given as parameter using `@Parameter` ([javadoc](http://spoon.gforge.inria.fr/mvnsites/spoon-core/apidocs/spoon/template/Parameter.html)) annotation. @@ -230,6 +272,6 @@ class ATemplate extends BlockTemplate { @Parameter CtExpression exp; ... -if ("er".equals(val)) {...} +if ("er".equals(exp.S())) {...} } ``` From 90bf6b9cb3b6ec1c9b9fececa5bb029dbb68c7b5 Mon Sep 17 00:00:00 2001 From: Simon Urli Date: Thu, 31 Aug 2017 11:36:09 +0200 Subject: [PATCH 26/28] Fix PrinterTest --- src/test/java/spoon/test/prettyprinter/PrinterTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/spoon/test/prettyprinter/PrinterTest.java b/src/test/java/spoon/test/prettyprinter/PrinterTest.java index 8db79aa121a..084dbb6008a 100644 --- a/src/test/java/spoon/test/prettyprinter/PrinterTest.java +++ b/src/test/java/spoon/test/prettyprinter/PrinterTest.java @@ -157,7 +157,6 @@ public void testPrintingOfOrphanFieldReference() throws Exception { type.getMethodsByName("failingMethod").get(0).getBody().getStatement(0).toString(); fail(); } catch (SpoonException e) { - assertTrue(e.getCause() instanceof NullPointerException); //the name of the missing field declaration is part of exception assertTrue(e.getMessage().indexOf("testedField")>=0); //the name of the method where field declaration is missing is part of exception From 0bc9676280e9b2e3d5a23a105d2d18f2ccb20d2d Mon Sep 17 00:00:00 2001 From: Simon Urli Date: Thu, 31 Aug 2017 13:42:53 +0200 Subject: [PATCH 27/28] Change documentation for Template --- src/main/java/spoon/template/Template.java | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/main/java/spoon/template/Template.java b/src/main/java/spoon/template/Template.java index ea3f0363fa9..d4e417d91d7 100644 --- a/src/main/java/spoon/template/Template.java +++ b/src/main/java/spoon/template/Template.java @@ -26,9 +26,10 @@ * {@link TemplateParameter#S()} method. * *

- * When the template parameter is a String or a primitive type (or a boxing + * When the template parameter is a String it is used to rename element of the code such as fields or methods. + * When it is another primitive type (or a boxing * type) representing a literal, or a Class, the template parameter can be - * directly accessed. + * directly accessed. To use a standard parameter containing a String type, use a CtLiteral<String> * *

  *       import spoon.template.Template;
@@ -38,15 +39,19 @@
  *           // template parameter fields
  *            \@Parameter String _parameter_;
  *
+ *            \@Parameter CtLiteral<String> _anotherParameter;
+ *
+ *
  *           // parameters binding
  *            \@Local
- *           public SimpleTemplate(String parameter) {
+ *           public SimpleTemplate(String parameter, CtLiteral<String> anotherParameter) {
  *               _parameter_ = parameter;
+ *               _anotherParameter = anotherParameter;
  *           }
  *
  *           // template method
- *           public void simpleTemplateMethod() {
- *               System.out.println(_parameter_);
+ *           public void methodwith_parameter_() {
+ *               System.out.println(_anotherParameter);
  *           }
  *       }
  * 
@@ -60,7 +65,10 @@ * *
  *       spoon.reflect.CtClass target=...;
- *       Template template=new SimpleTemplate("hello templated world");
+ *       CtLiteral<String> anotherParameter = factory.createLiteral();
+ *       anotherParameter.setValue("hello templated world");
+ *
+ *       Template template=new SimpleTemplate("ParameterizedName", anotherParameter);
  *       Substitution.insertAll(target,template);
  * 
* @@ -70,7 +78,7 @@ * *
  * public class A {
- * 	public void insertedMethod() {
+ * 	public void methodwithpPrameterizedName() {
  * 		System.out.println("hello templated world");
  *    }
  * }

From 5b39127be420d11f5aa8f5bb7f9a9fc3289ff99b Mon Sep 17 00:00:00 2001
From: Simon Urli 
Date: Thu, 31 Aug 2017 14:15:57 +0200
Subject: [PATCH 28/28] Fix typo

---
 src/main/java/spoon/template/Template.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/spoon/template/Template.java b/src/main/java/spoon/template/Template.java
index d4e417d91d7..14aecdbe6c3 100644
--- a/src/main/java/spoon/template/Template.java
+++ b/src/main/java/spoon/template/Template.java
@@ -78,7 +78,7 @@
  *
  * 
  * public class A {
- * 	public void methodwithpPrameterizedName() {
+ * 	public void methodwithParameterizedName() {
  * 		System.out.println("hello templated world");
  *    }
  * }