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> 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> 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 list = getParameterValueAsList(Object.class, value); + replace(fieldAccess, (CtExpression) fieldAccess.getFactory().Code().createLiteral(list.size())); throw new DoNotFurtherTemplateThisElement(fieldAccess); } } } - if (Parameters.isParameterSource(ref)) { + Object v = getParameterValue(ref.getSimpleName()); + if (v != null && Parameters.isParameterSource(ref)) { // replace direct field parameter accesses - Object value = Parameters.getValue(template, ref.getSimpleName(), Parameters.getIndex(fieldAccess)); + Object value = getParameterValueAtIndex(Object.class, v, Parameters.getIndex(fieldAccess)); CtExpression toReplace = fieldAccess; if (fieldAccess.getParent() instanceof CtArrayAccess) { toReplace = (CtExpression) fieldAccess.getParent(); @@ -278,11 +267,9 @@ public void visitCtInvocation(CtInvocation invocation) { fa = (CtFieldAccess) ((CtArrayAccess>) invocation.getTarget()).getTarget(); } if ((fa != null) && (fa.getTarget() == null || fa.getTarget() instanceof CtThisAccess)) { - TemplateParameter tparamValue = (TemplateParameter) Parameters - .getValue(template, fa.getVariable().getSimpleName(), Parameters.getIndex(fa)); - CtCodeElement r = null; - if (tparamValue != null) { - r = ((CtCodeElement) tparamValue).clone(); + CtCodeElement r = getParameterValueAtIndex(CtCodeElement.class, + getParameterValue(fa.getVariable().getSimpleName()), Parameters.getIndex(fa)); + if (r != null) { // substitute in the replacement (for fixing type // references // and @@ -309,18 +296,11 @@ public void visitCtInvocation(CtInvocation invocation) { public void scanCtExpression(CtExpression expression) { for (int i = 0; i < expression.getTypeCasts().size(); i++) { CtTypeReference t = (CtTypeReference) expression.getTypeCasts().get(i); - if (parameterNames.contains(t.getSimpleName())) { + CtTypeReference value = getParameterValueAsTypeReference(factory, getParameterValue(t.getSimpleName())); + if (value != null) { // replace type parameters // TODO: this would probably not work with inner classes!!! - Object o = Parameters.getValue(template, t.getSimpleName(), null); - if (o instanceof Class) { - t = factory.Type().createReference(((Class) o)); - } else if (o instanceof CtTypeReference) { - t = (CtTypeReference) o; - expression.getTypeCasts().set(i, t); - } else { - throw new RuntimeException("unsupported reference substitution"); - } + expression.getTypeCasts().set(i, value); } } super.scanCtExpression(expression); @@ -329,25 +309,21 @@ public void scanCtExpression(CtExpression expression) { @SuppressWarnings("unchecked") @Override public void scanCtTypedElement(CtTypedElement e) { - if ((e.getType() != null) && parameterNames.contains(e.getType().getSimpleName())) { - // replace type parameters - // TODO: this would probably not work with inner classes!!! - CtTypeReference t; - Object o = Parameters.getValue(template, e.getType().getSimpleName(), null); - if (o instanceof Class) { - o = factory.Type().createReference(((Class) o)); - } - if (o instanceof CtTypeReference) { - if ((e.getType() instanceof CtArrayTypeReference) && !(o instanceof CtArrayTypeReference)) { + final CtTypeReference typeOfE = e.getType(); + if (typeOfE != null) { + CtTypeReference o = getParameterValueAsTypeReference(factory, getParameterValue(typeOfE.getSimpleName())); + if (o != null) { + // replace type parameters + // TODO: this would probably not work with inner classes!!! + CtTypeReference t; + if ((typeOfE instanceof CtArrayTypeReference) && !(o instanceof CtArrayTypeReference)) { t = (CtArrayTypeReference) e.getFactory().Type().createArrayReference( - (CtTypeReference) o, - ((CtArrayTypeReference) e.getType()).getDimensionCount()); + o, + ((CtArrayTypeReference) typeOfE).getDimensionCount()); } else { t = (CtTypeReference) o; } e.setType(t); - } else { - throw new RuntimeException("unsupported reference substitution"); } } super.scanCtTypedElement(e); @@ -379,24 +355,20 @@ public void visitCtTypeReference(CtTypeReference reference) { reference.setPackage(targetRef.getPackage()); reference.setSimpleName(targetRef.getSimpleName()); } - if (parameterNames.contains(reference.getSimpleName())) { + Object o = getParameterValue(reference.getSimpleName()); + if (o != null) { // replace type parameters // TODO: this would probably not work with inner classes!!! - CtTypeReference t; - Object o = Parameters.getValue(template, reference.getSimpleName(), null); - if (o instanceof Class) { - t = factory.Type().createReference(((Class) o)); - } else if (o instanceof CtTypeReference) { - t = ((CtTypeReference) o).clone(); + boolean paramHasActualTypeArguments = o instanceof CtTypeReference; + CtTypeReference t = getParameterValueAsTypeReference(factory, o); + if (paramHasActualTypeArguments) { + //the origin parameter has actual type arguments, apply them reference.setActualTypeArguments(t.getActualTypeArguments()); - } else { - throw new RuntimeException( - "unsupported reference substitution: " + reference.getSimpleName() + " with value " + o); } reference.setPackage(t.getPackage()); reference.setSimpleName(t.getSimpleName()); reference.setDeclaringType(t.getDeclaringType()); - } else if (templateTypeRef.isSubtypeOf(reference)) { + } else if (templateTypeRef.equals(reference)) { // this can only be a template inheritance case (to be verified) CtTypeReference sc = targetRef.getSuperclass(); if (sc != null) { @@ -431,7 +403,7 @@ public void visitCtTypeReference(CtTypeReference reference) { CtClass> templateType; - Collection parameterNames; + Map namesToValues; /** * represents root element, which is target of the substitution. @@ -458,7 +430,7 @@ public SubstitutionVisitor(Factory f, CtType targetType, Template template f.Type().createTypeParameterReference("T"), "S"); templateRef = f.Type().createReference(template.getClass()); templateType = f.Class().get(templateRef.getQualifiedName()); - parameterNames = Parameters.getNames(templateType); + namesToValues = Parameters.getNamesToValues(this.template, templateType); templateTypeRef = f.Type().createReference(Template.class); if (targetType != null) { targetRef = f.Type().createReference(targetType); @@ -508,4 +480,137 @@ private void replace(CtElement toBeReplaced, CtElement replacement) { toBeReplaced.replace(replacement); } } + + private Object getParameterValue(String parameterName) { + return namesToValues.get(parameterName); + } + /** + * 1) Converts `parameterValue` to List using these rules + *
    + *
  • Array is converted to List . + *
  • {@link Iterable} is converted to List . + *
  • Single item is add to list. + *
+ * 2) assures that each list item has expected type `itemClass` + * 3) if itemClass is sub type of CtElement then clones it + * + * @param itemClass the type of the items of resulting list. + * If some item cannot be converted to the itemClass then {@link SpoonException} is thrown + * @param parameterValue a value of an template parameter + * @return list where each item is assured to be of type itemClass + */ + @SuppressWarnings("unchecked") + private static List getParameterValueAsList(Class itemClass, Object parameterValue) { + if (parameterValue == null) { + return null; + } + List list = new ArrayList<>(); + if (parameterValue instanceof Object[]) { + for (T item : (T[]) parameterValue) { + list.add(getParameterValueAsClass(itemClass, item)); + } + } else if (parameterValue instanceof Iterable) { + for (T item : (Iterable) parameterValue) { + list.add(getParameterValueAsClass(itemClass, item)); + } + } else { + list.add(getParameterValueAsClass(itemClass, parameterValue)); + } + return list; + } + /** + * 1) Assures that parameterValue has expected type `itemClass` + * 2) if itemClass is sub type of CtElement then clones parameterValue + * + * @param itemClass required return class + * @param parameterValue a value of an template parameter + * @return parameterValue cast (in future potentially converted) to itemClass + */ + @SuppressWarnings("unchecked") + private static T getParameterValueAsClass(Class itemClass, Object parameterValue) { + if (parameterValue == null) { + return null; + } + + if (itemClass.isInstance(parameterValue)) { + if (CtElement.class.isAssignableFrom(itemClass)) { + /* + * the cloning is defined by itemClass and not by parameterValue, + * because there are cases when we do not want to clone parameterValue. + * In this case itemClass == Object.class + */ + parameterValue = ((CtElement) parameterValue).clone(); + } + return (T) parameterValue; + } + throw new SpoonException("Parameter value has unexpected class: " + parameterValue.getClass().getName() + ". Expected class is: " + itemClass.getName()); + } + /** + * @param parameterValue a value of an template parameter + * @return parameter value converted to String + */ + private static String getParameterValueAsString(Object parameterValue) { + if (parameterValue == null) { + return null; + } + if (parameterValue instanceof String) { + return (String) parameterValue; + } else if (parameterValue instanceof CtNamedElement) { + return ((CtNamedElement) parameterValue).getSimpleName(); + } else if (parameterValue instanceof CtReference) { + return ((CtReference) parameterValue).getSimpleName(); + } + throw new SpoonException("Parameter value has unexpected class: " + parameterValue.getClass().getName() + ", whose conversion to String is not supported"); + } + + /** + * Converts `parameterValue` to {@link CtTypeReference}. + * It assures that new reference is returned. + * If parameterValue is already a {@link CtTypeReference}, then it is cloned. + * + * @param factory a Spoon factory used to create CtTypeReference instance - if needed + * @param parameterValue a value of an template parameter + * @return parameter value converted to {@link CtTypeReference} + */ + @SuppressWarnings("unchecked") + private static CtTypeReference getParameterValueAsTypeReference(Factory factory, Object parameterValue) { + if (parameterValue == null) { + return null; + } + if (parameterValue instanceof Class) { + return factory.Type().createReference((Class) parameterValue); + } else if (parameterValue instanceof CtTypeReference) { + return ((CtTypeReference) parameterValue).clone(); + } else if (parameterValue instanceof CtType) { + return ((CtType) parameterValue).getReference(); + } else if (parameterValue instanceof String) { + return factory.Type().createReference((String) parameterValue); + } else { + throw new RuntimeException("unsupported reference substitution"); + } + } + + /** + * 1a) If index is null, then parameterValue must be a single item, which will be converted to itemClass + * 1b) If index is a number, then parameterValue is converted to List, the index-th item is converted to itemClass + * 2) if itemClass is sub type of CtElement then returned element is a clone + * + * @param itemClass required return class + * @param parameterValue a value of an template parameter + * @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) { + if (index != null) { + //convert to list, but do not clone + List list = getParameterValueAsList(Object.class, parameterValue); + if (list.size() > index) { + //convert and clone the returned item + return getParameterValueAsClass(itemClass, list.get(index)); + } + return null; + } + //convert and clone the returned item + return getParameterValueAsClass(itemClass, parameterValue); + } }