From 1d24addf5ff6a6fcafc65b5a1094fde6be821a77 Mon Sep 17 00:00:00 2001
From: Pavel Vojtechovsky
Date: Thu, 1 Jun 2017 21:36:33 +0200
Subject: [PATCH 1/4] test substitution of root element
---
.../spoon/test/template/TemplateTest.java | 18 ++++++++++++
.../testclasses/SubstituteRootTemplate.java | 28 +++++++++++++++++++
2 files changed, 46 insertions(+)
create mode 100644 src/test/java/spoon/test/template/testclasses/SubstituteRootTemplate.java
diff --git a/src/test/java/spoon/test/template/TemplateTest.java b/src/test/java/spoon/test/template/TemplateTest.java
index 49b89e73f3c..fa7f31fec82 100644
--- a/src/test/java/spoon/test/template/TemplateTest.java
+++ b/src/test/java/spoon/test/template/TemplateTest.java
@@ -24,6 +24,7 @@
import spoon.test.template.testclasses.ArrayAccessTemplate;
import spoon.test.template.testclasses.InvocationTemplate;
import spoon.test.template.testclasses.SecurityCheckerTemplate;
+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;
@@ -503,4 +504,21 @@ 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 param = (CtBlock) templateClass.getMethod("sampleBlock").getBody();
+
+ CtClass> resultKlass = factory.Class().create("Result");
+ CtStatement result = new SubstituteRootTemplate(param).apply(resultKlass);
+ assertEquals("java.lang.String s = \"Spoon is cool!\"", ((CtBlock)result).getStatement(0).toString());
+ }
}
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
From d0b3e9d76c7e12f1c126571b2b641cfc87a992f1 Mon Sep 17 00:00:00 2001
From: Pavel Vojtechovsky
Date: Thu, 1 Jun 2017 21:57:28 +0200
Subject: [PATCH 2/4] fix substitution of root element
---
.../support/template/SubstitutionVisitor.java | 38 ++++++++++++++-----
.../spoon/template/StatementTemplate.java | 3 +-
2 files changed, 30 insertions(+), 11 deletions(-)
diff --git a/src/main/java/spoon/support/template/SubstitutionVisitor.java b/src/main/java/spoon/support/template/SubstitutionVisitor.java
index 60f0da47dcc..f45b7ce7e9f 100644
--- a/src/main/java/spoon/support/template/SubstitutionVisitor.java
+++ b/src/main/java/spoon/support/template/SubstitutionVisitor.java
@@ -188,7 +188,7 @@ public void visitCtForEach(CtForEach foreach) {
}
l.addStatement(b);
}
- foreach.replace(l);
+ replace(foreach, l);
throw new DoNotFurtherTemplateThisElement(foreach);
}
}
@@ -212,7 +212,7 @@ private void visitFieldAccess(CtFieldAccess fieldAccess) {
ref = ((CtFieldAccess>) fieldAccess.getTarget()).getVariable();
if (Parameters.isParameterSource(ref)) {
Object[] value = (Object[]) Parameters.getValue(template, ref.getSimpleName(), null);
- fieldAccess.replace((CtExpression) fieldAccess.getFactory().Code().createLiteral(value
+ replace(fieldAccess, (CtExpression) fieldAccess.getFactory().Code().createLiteral(value
.length));
throw new DoNotFurtherTemplateThisElement(fieldAccess);
}
@@ -227,11 +227,11 @@ private void visitFieldAccess(CtFieldAccess fieldAccess) {
}
if (!(value instanceof TemplateParameter)) {
if (value instanceof Class) {
- toReplace.replace(factory.Code()
+ replace(toReplace, factory.Code()
.createClassAccess(factory.Type().createReference(((Class>) value).getName())));
} else if (value instanceof Enum) {
CtTypeReference> enumType = factory.Type().createReference(value.getClass());
- toReplace.replace(factory.Code().createVariableRead(
+ replace(toReplace, factory.Code().createVariableRead(
factory.Field().createReference(enumType, enumType, ((Enum>) value).name()), true));
} else if (value instanceof List) {
// replace list of CtParameter for generic access to the
@@ -249,12 +249,12 @@ private void visitFieldAccess(CtFieldAccess fieldAccess) {
i++;
}
} else if ((value != null) && value.getClass().isArray()) {
- toReplace.replace(factory.Code().createLiteralArray((Object[]) value));
+ replace(toReplace, factory.Code().createLiteralArray((Object[]) value));
} else {
- toReplace.replace(factory.Code().createLiteral(value));
+ replace(toReplace, factory.Code().createLiteral(value));
}
} else {
- toReplace.clone();
+ replace(toReplace, toReplace.clone());
}
// do not visit if replaced
throw new DoNotFurtherTemplateThisElement(fieldAccess);
@@ -289,13 +289,13 @@ public void visitCtInvocation(CtInvocation invocation) {
// for recursive substitution)
r.accept(parent);
}
- if ((invocation.getParent() instanceof CtReturn) && (r instanceof CtBlock)) {
+ if (invocation.isParentInitialized() && (invocation.getParent() instanceof CtReturn) && (r instanceof CtBlock)) {
// block template parameters in returns should
// replace
// the return
((CtReturn>) invocation.getParent()).replace((CtStatement) r);
} else {
- invocation.replace(r);
+ replace(invocation, r);
}
}
// do not visit the invocation if replaced
@@ -433,6 +433,12 @@ public void visitCtTypeReference(CtTypeReference reference) {
Collection parameterNames;
+ /**
+ * represents root element, which is target of the substitution.
+ * It can be substituted too.
+ */
+ CtElement result;
+
/**
* Creates a new substitution visitor.
*
@@ -482,4 +488,18 @@ public void scan(CtElement element) {
} catch (DoNotFurtherTemplateThisElement ignore) {
}
}
+
+ public CtElement substitute(CtElement element) {
+ result = element;
+ scan(element);
+ return result;
+ }
+
+ private void replace(CtElement toBeReplaced, CtElement replacement) {
+ if (result == toBeReplaced) {
+ result = replacement;
+ } else {
+ toBeReplaced.replace(replacement);
+ }
+ }
}
diff --git a/src/main/java/spoon/template/StatementTemplate.java b/src/main/java/spoon/template/StatementTemplate.java
index 8835bb7bc02..b32bd887100 100644
--- a/src/main/java/spoon/template/StatementTemplate.java
+++ b/src/main/java/spoon/template/StatementTemplate.java
@@ -43,8 +43,7 @@ public CtStatement apply(CtType> targetType) {
CtClass> c = Substitution.getTemplateCtClass(targetType, this);
// we substitute the first statement of method statement
CtStatement result = c.getMethod("statement").getBody().getStatements().get(0).clone();
- new SubstitutionVisitor(c.getFactory(), targetType, this).scan(result);
- return result;
+ return (CtStatement) new SubstitutionVisitor(c.getFactory(), targetType, this).substitute(result);
}
public Void S() {
From bb0e05729a64b8eac2cf43e04ec9cec8f16af117 Mon Sep 17 00:00:00 2001
From: Pavel Vojtechovsky
Date: Fri, 2 Jun 2017 20:26:21 +0200
Subject: [PATCH 3/4] javadoc added, Substitution uses new method now
---
.../spoon/support/template/SubstitutionVisitor.java | 10 ++++++++--
src/main/java/spoon/template/StatementTemplate.java | 2 +-
src/main/java/spoon/template/Substitution.java | 6 ++----
3 files changed, 11 insertions(+), 7 deletions(-)
diff --git a/src/main/java/spoon/support/template/SubstitutionVisitor.java b/src/main/java/spoon/support/template/SubstitutionVisitor.java
index f45b7ce7e9f..a9c244f1f42 100644
--- a/src/main/java/spoon/support/template/SubstitutionVisitor.java
+++ b/src/main/java/spoon/support/template/SubstitutionVisitor.java
@@ -489,10 +489,16 @@ public void scan(CtElement element) {
}
}
- public CtElement substitute(CtElement element) {
+ /**
+ * Substitutes all template parameters of element and returns substituted element.
+ *
+ * @param element to be substituted model
+ * @return substituted model
+ */
+ public E substitute(E element) {
result = element;
scan(element);
- return result;
+ return (E) result;
}
private void replace(CtElement toBeReplaced, CtElement replacement) {
diff --git a/src/main/java/spoon/template/StatementTemplate.java b/src/main/java/spoon/template/StatementTemplate.java
index b32bd887100..906565d1136 100644
--- a/src/main/java/spoon/template/StatementTemplate.java
+++ b/src/main/java/spoon/template/StatementTemplate.java
@@ -43,7 +43,7 @@ public CtStatement apply(CtType> targetType) {
CtClass> c = Substitution.getTemplateCtClass(targetType, this);
// we substitute the first statement of method statement
CtStatement result = c.getMethod("statement").getBody().getStatements().get(0).clone();
- return (CtStatement) new SubstitutionVisitor(c.getFactory(), targetType, this).substitute(result);
+ return new SubstitutionVisitor(c.getFactory(), targetType, this).substitute(result);
}
public Void S() {
diff --git a/src/main/java/spoon/template/Substitution.java b/src/main/java/spoon/template/Substitution.java
index 966fbd43de9..61ae75882dd 100644
--- a/src/main/java/spoon/template/Substitution.java
+++ b/src/main/java/spoon/template/Substitution.java
@@ -484,8 +484,7 @@ public static E substitute(CtType> targetType, Template<
throw new RuntimeException("target is null in substitution");
}
E result = (E) code.clone();
- new SubstitutionVisitor(targetType.getFactory(), targetType, template).scan(result);
- return result;
+ return new SubstitutionVisitor(targetType.getFactory(), targetType, template).substitute(result);
}
/**
@@ -532,8 +531,7 @@ public static > T substitute(Template> template, T templat
T result = (T) templateType.clone();
result.setPositions(null);
// result.setParent(templateType.getParent());
- new SubstitutionVisitor(templateType.getFactory(), result, template).scan(result);
- return result;
+ return new SubstitutionVisitor(templateType.getFactory(), result, template).substitute(result);
}
/**
From 8eb0beab836d4111e58573800f06b346e86f9460 Mon Sep 17 00:00:00 2001
From: Pavel Vojtechovsky
Date: Wed, 31 May 2017 21:55:23 +0200
Subject: [PATCH 4/4] refactor SubstitutionVisitor. Unify conversion of
template parameters
---
.../spoon/support/template/Parameters.java | 45 ++-
.../support/template/SubstitutionVisitor.java | 291 ++++++++++++------
2 files changed, 235 insertions(+), 101 deletions(-)
diff --git a/src/main/java/spoon/support/template/Parameters.java b/src/main/java/spoon/support/template/Parameters.java
index 06da96599ae..8534e10906b 100644
--- a/src/main/java/spoon/support/template/Parameters.java
+++ b/src/main/java/spoon/support/template/Parameters.java
@@ -36,6 +36,7 @@
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -69,9 +70,8 @@ public static Integer getIndex(CtExpression> e) {
* Gets a template field parameter value.
*/
public static Object getValue(Template> template, String parameterName, Integer index) {
- Object tparamValue = null;
+ Field rtField = null;
try {
- Field rtField = null;
for (Field f : RtHelper.getAllFields(template.getClass())) {
if (isParameterSource(f)) {
if (parameterName.equals(getParameterName(f))) {
@@ -80,6 +80,20 @@ public static Object getValue(Template> template, String parameterName, Intege
}
}
}
+ } catch (Exception e) {
+ throw new UndefinedParameterException(e);
+ }
+ Object tparamValue = getValue(template, parameterName, rtField);
+ if (rtField.getType().isArray() && (index != null)) {
+ tparamValue = ((Object[]) tparamValue)[index];
+ }
+ return tparamValue;
+ }
+ private static Object getValue(Template> template, String parameterName, Field rtField) {
+ if (rtField == null) {
+ throw new UndefinedParameterException();
+ }
+ try {
if (Modifier.isFinal(rtField.getModifiers())) {
Map m = finals.get(template);
if (m == null) {
@@ -88,14 +102,10 @@ public static Object getValue(Template> template, String parameterName, Intege
return m.get(parameterName);
}
rtField.setAccessible(true);
- tparamValue = rtField.get(template);
- if (rtField.getType().isArray() && (index != null)) {
- tparamValue = ((Object[]) tparamValue)[index];
- }
+ return rtField.get(template);
} catch (Exception e) {
- throw new UndefinedParameterException();
+ throw new UndefinedParameterException(e);
}
- return tparamValue;
}
static Map, Map> finals = new HashMap<>();
@@ -194,6 +204,25 @@ public static List getNames(CtClass extends Template>> templateType)
}
return params;
}
+ /**
+ * Gets the Map of names to template parameter value for all the template parameters of a given template type
+ * (including the ones defined by the super types).
+ */
+ public static Map getNamesToValues(Template> template, CtClass extends Template>> 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<>();
+ try {
+ for (CtFieldReference> f : templateType.getAllFields()) {
+ if (isParameterSource(f)) {
+ String parameterName = getParameterName(f);
+ params.put(parameterName, getValue(template, parameterName, (Field) f.getActualField()));
+ }
+ }
+ } catch (Exception e) {
+ throw new SpoonException("Getting of template parameters failed", e);
+ }
+ return params;
+ }
/**
* Tells if a given field is a template parameter.
diff --git a/src/main/java/spoon/support/template/SubstitutionVisitor.java b/src/main/java/spoon/support/template/SubstitutionVisitor.java
index a9c244f1f42..004a116698e 100644
--- a/src/main/java/spoon/support/template/SubstitutionVisitor.java
+++ b/src/main/java/spoon/support/template/SubstitutionVisitor.java
@@ -19,6 +19,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.Map;
import spoon.SpoonException;
import spoon.reflect.code.CtAbstractInvocation;
@@ -89,24 +90,23 @@ public InheritanceSustitutionScanner(SubstitutionVisitor parent) {
@Override
public void scanCtExecutable(CtExecutable e) {
// replace method parameters
- for (CtParameter> parameter : new ArrayList<>(e.getParameters())) {
- String name = parameter.getSimpleName();
- for (String pname : parameterNames) {
- if (name.equals(pname)) {
- Object value = Parameters.getValue(template, pname, null);
- int i = parameter.getParent().getParameters().indexOf(parameter);
- if (value instanceof List) {
- List> l = (List>) value;
- for (Object p : l) {
- CtParameter> p2 = ((CtParameter>) p).clone();
- p2.setParent(parameter.getParent());
- parameter.getParent().getParameters().add(i++, p2);
- }
- parameter.getParent().getParameters().remove(parameter);
- }
- }
+ List> substitutedParams = new ArrayList<>(e.getParameters().size());
+ boolean wasChanged = false;
+ for (CtParameter> parameter : e.getParameters()) {
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ List> list = (List) getParameterValueAsList(CtParameter.class, getParameterValue(parameter.getSimpleName()));
+ if (list == null) {
+ //it is normal parameter, keep it.
+ substitutedParams.add(parameter);
+ } else {
+ wasChanged = true;
+ //current parameter has to be substituted by a template parameter value
+ substitutedParams.addAll(list);
}
}
+ if (wasChanged) {
+ e.setParameters(substitutedParams);
+ }
super.scanCtExecutable(e);
}
@@ -129,59 +129,45 @@ public void scanCtElement(CtElement e) {
@Override
public void scanCtNamedElement(CtNamedElement element) {
if (element.getDocComment() != null) {
- element.setDocComment(substituteInDocComment(element.getDocComment()));
+ element.setDocComment(substituteName(element.getDocComment()));
}
// replace parameters in names
- element.setSimpleName(substituteName(element.getSimpleName(), element));
+ element.setSimpleName(substituteName(element.getSimpleName()));
super.scanCtNamedElement(element);
}
@Override
public void scanCtReference(CtReference reference) {
- reference.setSimpleName(substituteName(reference.getSimpleName(), reference));
+ reference.setSimpleName(substituteName(reference.getSimpleName()));
super.scanCtReference(reference);
}
- private String substituteName(String name, CtElement element) {
- for (String pname : parameterNames) {
+ private String substituteName(String name) {
+ for (Map.Entry e : namesToValues.entrySet()) {
+ String pname = e.getKey();
if (name.contains(pname)) {
- Object value = Parameters.getValue(template, pname, null);
- if (value instanceof String) {
- // replace with the string value
- name = name.replace(pname, (String) value);
- } else if ((value instanceof CtTypeReference) && (element instanceof CtType)) {
- // replace with the type reference's name
- name = name.replace(pname, ((CtTypeReference>) value).getSimpleName());
- }
+ String value = getParameterValueAsString(e.getValue());
+ name = name.replace(pname, value);
}
}
return name;
}
- private String substituteInDocComment(String docComment) {
- String result = docComment;
- for (String pname : parameterNames) {
- Object value = Parameters.getValue(template, pname, null);
- if (value instanceof String) {
- result = result.replace(pname, (String) value);
- }
- }
- return result;
- }
-
/** statically inline foreach */
+ @SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public void visitCtForEach(CtForEach foreach) {
if (foreach.getExpression() instanceof CtFieldAccess) {
CtFieldAccess> fa = (CtFieldAccess>) foreach.getExpression();
- if (Parameters.isParameterSource(fa.getVariable())) {
- Object[] value = (Object[]) Parameters.getValue(template, fa.getVariable().getSimpleName(), null);
+ Object value = getParameterValue(fa.getVariable().getSimpleName());
+ if (value != null && Parameters.isParameterSource(fa.getVariable())) {
+ List list = getParameterValueAsList(CtExpression.class, value);
CtBlock> l = foreach.getFactory().Core().createBlock();
CtStatement body = foreach.getBody();
- for (Object element : value) {
+ for (CtExpression element : list) {
CtStatement b = body.clone();
for (CtVariableAccess> va : Query.getElements(b, new VariableAccessFilter<>(foreach.getVariable().getReference()))) {
- va.replace((CtExpression) element);
+ va.replace(element);
}
if (b instanceof CtBlock && ((CtBlock) b).getStatements().size() == 1) {
b = ((CtBlock) b).getStatement(0);
@@ -205,22 +191,25 @@ public void visitCtFieldWrite(CtFieldWrite fieldWrite) {
visitFieldAccess(fieldWrite);
}
- private void visitFieldAccess(CtFieldAccess fieldAccess) {
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ private void visitFieldAccess(final CtFieldAccess fieldAccess) {
CtFieldReference> ref = fieldAccess.getVariable();
if ("length".equals(ref.getSimpleName())) {
if (fieldAccess.getTarget() instanceof CtFieldAccess) {
ref = ((CtFieldAccess>) fieldAccess.getTarget()).getVariable();
- if (Parameters.isParameterSource(ref)) {
- Object[] value = (Object[]) Parameters.getValue(template, ref.getSimpleName(), null);
- replace(fieldAccess, (CtExpression) fieldAccess.getFactory().Code().createLiteral(value
- .length));
+ Object value = getParameterValue(ref.getSimpleName());
+ if (value != null && Parameters.isParameterSource(ref)) {
+ //the items of this list are not cloned
+ List