From ebfbba90a3430ada66b892b29f3da598b79bc1f5 Mon Sep 17 00:00:00 2001
From: Pavel Vojtechovsky
Date: Tue, 6 Jun 2017 23:45:55 +0200
Subject: [PATCH] refactor SubstitutionVisitor
---
.../replace/ReplacementVisitor.java | 8 +-
.../spoon/reflect/factory/CodeFactory.java | 43 +++
.../support/template/SubstitutionVisitor.java | 246 ++++++++++++------
.../spoon/template/StatementTemplate.java | 13 +-
.../java/spoon/template/Substitution.java | 14 +-
.../spoon/test/template/TemplateTest.java | 9 +-
6 files changed, 242 insertions(+), 91 deletions(-)
diff --git a/src/main/java/spoon/generating/replace/ReplacementVisitor.java b/src/main/java/spoon/generating/replace/ReplacementVisitor.java
index c5615276fa7..41282c4aa77 100644
--- a/src/main/java/spoon/generating/replace/ReplacementVisitor.java
+++ b/src/main/java/spoon/generating/replace/ReplacementVisitor.java
@@ -126,13 +126,7 @@ private void replaceInListIfExist(List listProtected, R
private void replaceElementIfExist(CtElement candidate, ReplaceListener listener) {
if (candidate == original) {
- if (replace.size() > 1) {
- throw new SpoonException("Cannot replace single value by multiple values in " + listener.getClass().getSimpleName());
- }
- CtElement val = null;
- if (replace.size() == 1) {
- val = replace.iterator().next();
- }
+ CtElement val = candidate.getFactory().Code().convertToSingle(replace);
listener.set(val);
if (val != null) {
val.setParent(candidate.getParent());
diff --git a/src/main/java/spoon/reflect/factory/CodeFactory.java b/src/main/java/spoon/reflect/factory/CodeFactory.java
index 48f5a836962..3d548ab9826 100644
--- a/src/main/java/spoon/reflect/factory/CodeFactory.java
+++ b/src/main/java/spoon/reflect/factory/CodeFactory.java
@@ -16,6 +16,7 @@
*/
package spoon.reflect.factory;
+import spoon.SpoonException;
import spoon.reflect.code.BinaryOperatorKind;
import spoon.reflect.code.CtAssignment;
import spoon.reflect.code.CtBinaryOperator;
@@ -43,6 +44,7 @@
import spoon.reflect.code.CtVariableAccess;
import spoon.reflect.declaration.CtAnnotation;
import spoon.reflect.declaration.CtClass;
+import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtField;
import spoon.reflect.declaration.CtNamedElement;
import spoon.reflect.declaration.CtVariable;
@@ -60,6 +62,7 @@
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
@@ -714,4 +717,44 @@ public CtJavaDocTag createJavaDocTag(String content, CtJavaDocTag.TagType type)
return docTag.setContent(content.trim()).setType(type);
}
+ /**
+ * Converts collection of elements to single element.
+ * It actually supports conversion of {@link CtStatement}s to {@link CtBlock}
+ *
+ * @param elements a to be converted collection of elements
+ * @return single element which represents input `elements`
+ * @throws SpoonException if conversion is not possible
+ */
+ @SuppressWarnings("unchecked")
+ public T convertToSingle(Collection elements) {
+ if (elements.isEmpty()) {
+ return null;
+ }
+ if (elements.size() == 1) {
+ return (T) elements.iterator().next();
+ }
+ if (isAllSubtypeOf(elements, CtStatement.class)) {
+ CtBlock block = factory.Core().createBlock();
+ block.setStatements(toList((Collection) elements));
+ return (T) block;
+ }
+ throw new SpoonException("Cannot convert collection to single");
+ }
+
+ private List toList(Collection col) {
+ if (col instanceof List) {
+ return (List) col;
+ }
+ return new ArrayList<>(col);
+ }
+
+ private boolean isAllSubtypeOf(Collection> elements, Class clazz) {
+ for (Object ele : elements) {
+ if (clazz.isInstance(ele) == false) {
+ return false;
+ }
+ }
+ return true;
+ }
+
}
diff --git a/src/main/java/spoon/support/template/SubstitutionVisitor.java b/src/main/java/spoon/support/template/SubstitutionVisitor.java
index ae34653b26d..50789e05db1 100644
--- a/src/main/java/spoon/support/template/SubstitutionVisitor.java
+++ b/src/main/java/spoon/support/template/SubstitutionVisitor.java
@@ -18,6 +18,8 @@
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -31,10 +33,10 @@
import spoon.reflect.code.CtFieldWrite;
import spoon.reflect.code.CtForEach;
import spoon.reflect.code.CtInvocation;
+import spoon.reflect.code.CtLiteral;
import spoon.reflect.code.CtReturn;
import spoon.reflect.code.CtStatement;
import spoon.reflect.code.CtThisAccess;
-import spoon.reflect.code.CtVariableAccess;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtExecutable;
import spoon.reflect.declaration.CtNamedElement;
@@ -47,8 +49,6 @@
import spoon.reflect.reference.CtTypeReference;
import spoon.reflect.visitor.CtInheritanceScanner;
import spoon.reflect.visitor.CtScanner;
-import spoon.reflect.visitor.Query;
-import spoon.reflect.visitor.filter.VariableAccessFilter;
import spoon.template.Parameter;
import spoon.template.Template;
import spoon.template.TemplateParameter;
@@ -86,12 +86,11 @@ class DoNotFurtherTemplateThisElement extends SpoonException {
*/
public class SubstitutionVisitor extends CtScanner {
- public class InheritanceSustitutionScanner extends CtInheritanceScanner {
+ private Context context;
- SubstitutionVisitor parent = null;
+ public class InheritanceSustitutionScanner extends CtInheritanceScanner {
- public InheritanceSustitutionScanner(SubstitutionVisitor parent) {
- this.parent = parent;
+ public InheritanceSustitutionScanner() {
}
/**
@@ -105,7 +104,7 @@ public void scanCtExecutable(CtExecutable e) {
boolean wasChanged = false;
for (CtParameter> parameter : e.getParameters()) {
@SuppressWarnings({ "rawtypes", "unchecked" })
- List> list = (List) getParameterValueAsList(CtParameter.class, getParameterValue(parameter.getSimpleName()));
+ List> list = (List) getParameterValueAsList(CtParameter.class, context.getParameterValue(parameter.getSimpleName()));
if (list == null) {
//it is normal parameter, keep it.
substitutedParams.add(parameter);
@@ -128,28 +127,27 @@ public void scanCtExecutable(CtExecutable e) {
@Override
public void scanCtNamedElement(CtNamedElement element) {
if (element.getDocComment() != null) {
- element.setDocComment(substituteName(element.getDocComment()));
+ element.setDocComment(context.substituteName(element.getDocComment()));
}
// replace parameters in names
- element.setSimpleName(substituteName(element.getSimpleName()));
+ element.setSimpleName(context.substituteName(element.getSimpleName()));
super.scanCtNamedElement(element);
}
@Override
public void scanCtReference(CtReference reference) {
- reference.setSimpleName(substituteName(reference.getSimpleName()));
- super.scanCtReference(reference);
- }
-
- private String substituteName(String name) {
- for (Map.Entry e : namesToValues.entrySet()) {
- String pname = e.getKey();
- if (name.contains(pname)) {
- String value = getParameterValueAsString(e.getValue());
- name = name.replace(pname, value);
+ Object value = context.getParameterValue(reference.getSimpleName());
+ if (value != null) {
+ if (value instanceof CtLiteral) {
+ //replace reference with literal
+ CtLiteral literal = (CtLiteral) value;
+ CtExpression expr = reference.getParent(CtExpression.class);
+ context.replace(expr, literal);
+ throw new DoNotFurtherTemplateThisElement(expr);
}
}
- return name;
+ reference.setSimpleName(context.substituteName(reference.getSimpleName()));
+ super.scanCtReference(reference);
}
/** statically inline foreach */
@@ -158,22 +156,26 @@ private String substituteName(String name) {
public void visitCtForEach(CtForEach foreach) {
if (foreach.getExpression() instanceof CtFieldAccess) {
CtFieldAccess> fa = (CtFieldAccess>) foreach.getExpression();
- Object value = getParameterValue(fa.getVariable().getSimpleName());
+ Object value = context.getParameterValue(fa.getVariable().getSimpleName());
if (value != null) {
+ //create local context which holds local substitution parameter
+ Context localContext = createContext();
List list = getParameterValueAsList(CtExpression.class, value);
- CtBlock> l = foreach.getFactory().Core().createBlock();
CtStatement body = foreach.getBody();
+ String newParamName = foreach.getVariable().getSimpleName();
+ List newStatements = new ArrayList<>();
for (CtExpression element : list) {
- CtStatement b = body.clone();
- for (CtVariableAccess> va : Query.getElements(b, new VariableAccessFilter<>(foreach.getVariable().getReference()))) {
- va.replace(element);
- }
- if (b instanceof CtBlock && ((CtBlock) b).getStatements().size() == 1) {
- b = ((CtBlock) b).getStatement(0);
+ localContext.putParameter(newParamName, element);
+ if (body instanceof CtBlock) {
+ CtBlock foreachBlock = (CtBlock) body;
+ for (CtStatement st : foreachBlock.getStatements()) {
+ newStatements.addAll(localContext.substitute(st.clone()));
+ }
+ } else {
+ newStatements.addAll(localContext.substitute(body.clone()));
}
- l.addStatement(b);
}
- replace(foreach, l);
+ context.replace(foreach, newStatements);
throw new DoNotFurtherTemplateThisElement(foreach);
}
}
@@ -196,16 +198,16 @@ private void visitFieldAccess(final CtFieldAccess fieldAccess) {
if ("length".equals(ref.getSimpleName())) {
if (fieldAccess.getTarget() instanceof CtFieldAccess) {
ref = ((CtFieldAccess>) fieldAccess.getTarget()).getVariable();
- Object value = getParameterValue(ref.getSimpleName());
+ Object value = context.getParameterValue(ref.getSimpleName());
if (value != null) {
//the items of this list are not cloned
List