diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index 80ab7c3a4e7..bd470726d5a 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -29,6 +29,7 @@ import spoon.SpoonException; import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtInvocation; +import spoon.reflect.code.CtLiteral; import spoon.reflect.code.CtReturn; import spoon.reflect.code.CtStatement; import spoon.reflect.code.CtVariableAccess; @@ -75,7 +76,13 @@ public static PatternBuilder createPattern(CtElement element) { * @return {@link PatternBuilder} */ public static PatternBuilder createTypePattern(CtType type, Consumer modelSelector) { - TypeView tv = new TypeView(type.clone()); + CtType clonedType = type.clone(); + //I am not sure yet, whether template model will need to know it's context + //- e.g. to resolve variables (which are not part of template model) from references (in template model) +// if (type.isParentInitialized()) { +// clonedType.setParent(type.getParent()); +// } + TypeView tv = new TypeView(clonedType); modelSelector.accept(tv); return createPattern(tv.template); } @@ -176,12 +183,6 @@ public static PatternBuilder createExpressionPattern(CtType type, Filter parameters /** * adds all standard Template parameters based on {@link TemplateParameter} and {@link Parameter} annotation * @param templateType the CtType which contains template parameters + * @param templateParameters parameters, which will be used in substitution. It is needed here, + * because parameter value types influences which AST nodes will be the target of substitution * @return this to support fluent API */ - public PatternBuilder configureTemplateParameters(CtType templateType) { + public PatternBuilder configureTemplateParameters(CtType templateType, Map templateParameters) { @SuppressWarnings("rawtypes") CtTypeReference templateParamRef = templateType.getFactory().Type().createReference(TemplateParameter.class); CtTypeReference typeReferenceRef = templateType.getFactory().Type().createReference(CtTypeReference.class); @@ -322,12 +325,10 @@ public PatternBuilder configureTemplateParameters(CtType templateType) { * Use it as Pattern parameter */ String parameterName = typeMember.getSimpleName(); - String stringMarker = parameterName; - if (param.value() != null && param.value().length() > 0) { - stringMarker = param.value(); - //for the compatibility reasons with Parameters.getNamesToValues(), use the proxy name as parameter name - parameterName = stringMarker; - } + String stringMarker = (param.value() != null && param.value().length() > 0) ? param.value() : parameterName; + //for the compatibility reasons with Parameters.getNamesToValues(), use the proxy name as parameter name + parameterName = stringMarker; + CtTypeReference paramType = paramField.getType(); if (paramType.isSubtypeOf(typeReferenceRef) || paramType.getQualifiedName().equals(Class.class.getName())) { /* @@ -354,6 +355,24 @@ public PatternBuilder configureTemplateParameters(CtType templateType) { //all occurrences of parameter name in pattern model are subject of substitution .byVariableReference(paramField); } + if (paramType.getQualifiedName().equals(Object.class.getName()) && templateParameters != null) { + //if the parameter type is Object, then detect the real parameter type from the parameter value + Object value = templateParameters.get(parameterName); + if (value instanceof CtLiteral || value instanceof CtTypeReference) { + /* + * the real parameter value is CtLiteral or CtTypeReference + * We should replace all method invocations whose name equals to stringMarker + * by that CtLiteral or qualified name of CtTypeReference + */ + ParameterInfo pi = params.parameter(parameterName).getCurrentParameter(); + pattern.getModel().filterChildren((CtInvocation inv) -> { + return inv.getExecutable().getSimpleName().equals(stringMarker); + }).forEach((CtInvocation inv) -> { + pattern.addSubstitutionRequest(pi, inv); + }); + } + } + //any value can be converted to String. Substitute content of all string attributes params.parameter(parameterName) .bySubstring(stringMarker); diff --git a/src/main/java/spoon/pattern/TemplateBuilder.java b/src/main/java/spoon/pattern/TemplateBuilder.java index 46efc727b86..c6aa673a89e 100644 --- a/src/main/java/spoon/pattern/TemplateBuilder.java +++ b/src/main/java/spoon/pattern/TemplateBuilder.java @@ -17,6 +17,7 @@ package spoon.pattern; +import java.util.List; import java.util.Map; import spoon.SpoonException; @@ -37,8 +38,14 @@ */ public class TemplateBuilder { - @SuppressWarnings("unchecked") - public static TemplateBuilder createPattern(CtElement templateRoot) { + /** + * Creates a {@link Pattern} from {@link Template} + * @param templateRoot the root element of {@link Template} model + * @param template a instance of the {@link Template}. It is needed here, + * because parameter value types influences which AST nodes will be the target of substitution + * @return {@link TemplateBuilder} + */ + public static TemplateBuilder createPattern(CtElement templateRoot, Template template) { Factory f = templateRoot.getFactory(); CtClass> templateType = getTemplateType(templateRoot); @@ -46,6 +53,12 @@ public static TemplateBuilder createPattern(CtElement templateRoot) { if (templateType == null) { throw new SpoonException("Cannot create a Pattern based on Template from the AST tree which is not part of Template"); } + if (template == null) { + throw new SpoonException("Cannot create a Pattern based on Template without knowing actual Template parameters"); + } + if (templateType.getQualifiedName().equals(template.getClass().getName()) == false) { + throw new SpoonException("Unexpected template instance " + template.getClass().getName() + ". Expects " + templateType.getQualifiedName()); + } PatternBuilder pb; @@ -76,8 +89,9 @@ public static TemplateBuilder createPattern(CtElement templateRoot) { } else { pb = PatternBuilder.createPattern(templateRoot); } - pb.configureTemplateParameters(templateType); - return new TemplateBuilder(templateType, pb); + Map templateParameters = template == null ? null : Parameters.getTemplateParametersAsMap(f, null, template); + pb.configureTemplateParameters(templateType, templateParameters); + return new TemplateBuilder(templateType, pb, template); } private static CtClass> getTemplateType(CtElement element) { @@ -93,10 +107,12 @@ private static CtClass> getTemplateType(CtElement element) } } + private Template template; private PatternBuilder patternBuilder; private CtClass> templateType; - private TemplateBuilder(CtClass> templateType, PatternBuilder patternBuilder) { + private TemplateBuilder(CtClass> templateType, PatternBuilder patternBuilder, Template template) { + this.template = template; this.patternBuilder = patternBuilder; this.templateType = templateType; } @@ -109,19 +125,25 @@ public Pattern build() { * @param template the instance of the Template of this TemplateBuilder * @return Map of template parameters from `template` */ - public Map getTemplateParameters(Template template) { - return getTemplateParameters(template, null); + public Map getTemplateParameters() { + return getTemplateParameters(null); } /** * @param template the instance of the Template of this TemplateBuilder * @param targetType the type which will receive the model generated using returned parameters * @return Map of template parameters from `template` */ - public Map getTemplateParameters(Template template, CtType targetType) { + public Map getTemplateParameters(CtType targetType) { Factory f = templateType.getFactory(); - if (templateType.getQualifiedName().equals(template.getClass().getName()) == false) { - throw new SpoonException("Unexpected template instance " + template.getClass().getName() + ". Expects " + templateType.getQualifiedName()); - } return Parameters.getTemplateParametersAsMap(f, targetType, template); } + + /** + * generates a new AST made by cloning of `patternModel` and by substitution of parameters by their values + * @param targetType the CtType, which will receive the result of substitution + * @return + */ + public List substitute(CtType targetType) { + return build().substitute(getTemplateParameters(targetType)); + } } diff --git a/src/main/java/spoon/template/BlockTemplate.java b/src/main/java/spoon/template/BlockTemplate.java index ebf4907a00a..44772827757 100644 --- a/src/main/java/spoon/template/BlockTemplate.java +++ b/src/main/java/spoon/template/BlockTemplate.java @@ -52,8 +52,7 @@ public BlockTemplate() { public CtBlock apply(CtType targetType) { CtClass c = Substitution.getTemplateCtClass(targetType, this); - TemplateBuilder tb = TemplateBuilder.createPattern(getBlock(c)); - List> blocks = tb.build().substitute(tb.getTemplateParameters(this, targetType)); + List> blocks = TemplateBuilder.createPattern(getBlock(c), this).substitute(targetType); if (blocks.size() > 1) { throw new SpoonException("BlockTemplate cannot return more then one block"); } diff --git a/src/main/java/spoon/template/ExpressionTemplate.java b/src/main/java/spoon/template/ExpressionTemplate.java index 62ffe536d4d..8bc04a2eaef 100644 --- a/src/main/java/spoon/template/ExpressionTemplate.java +++ b/src/main/java/spoon/template/ExpressionTemplate.java @@ -68,8 +68,7 @@ public ExpressionTemplate() { @SuppressWarnings("unchecked") public CtExpression apply(CtType targetType) { CtClass> c = Substitution.getTemplateCtClass(targetType, this); - TemplateBuilder tb = TemplateBuilder.createPattern(getExpressionBlock(c)); - List> blocks = tb.build().substitute(tb.getTemplateParameters(this, targetType)); + List> blocks = TemplateBuilder.createPattern(getExpressionBlock(c), this).substitute(targetType); if (blocks.size() > 1) { throw new SpoonException("BlockTemplate cannot return more then one block"); } diff --git a/src/main/java/spoon/template/StatementTemplate.java b/src/main/java/spoon/template/StatementTemplate.java index b7478243e8a..0a657ac28c3 100644 --- a/src/main/java/spoon/template/StatementTemplate.java +++ b/src/main/java/spoon/template/StatementTemplate.java @@ -46,8 +46,7 @@ public CtStatement apply(CtType targetType) { CtClass c = Substitution.getTemplateCtClass(targetType, this); // we substitute the first statement of method statement CtStatement patternModel = c.getMethod("statement").getBody().getStatements().get(0); - TemplateBuilder tb = TemplateBuilder.createPattern(patternModel); - List statements = tb.build().substitute(tb.getTemplateParameters(this, targetType)); + List statements = TemplateBuilder.createPattern(patternModel, this).substitute(targetType); if (statements.size() > 1) { throw new SpoonException("StatementTemplate cannot return more then one statement"); } diff --git a/src/main/java/spoon/template/Substitution.java b/src/main/java/spoon/template/Substitution.java index 90e6f9a5c11..1f8fa3c690b 100644 --- a/src/main/java/spoon/template/Substitution.java +++ b/src/main/java/spoon/template/Substitution.java @@ -114,7 +114,7 @@ public static > T createTypeFromTemplate(String qualifiedTyp CtPackage targetPackage = f.Package().getOrCreate(typeRef.getPackage().getSimpleName()); final Map extendedParams = new HashMap(templateParameters); extendedParams.put(templateOfType.getSimpleName(), typeRef); - List> generated = PatternBuilder.createPattern(templateOfType).configureTemplateParameters(templateOfType).build().substitute(extendedParams); + List> generated = PatternBuilder.createPattern(templateOfType).configureTemplateParameters(templateOfType, extendedParams).build().substitute(extendedParams); for (CtType ctType : generated) { targetPackage.addType(ctType); } @@ -533,8 +533,7 @@ public static E substitute(CtType targetType, Template< if (targetType == null) { throw new RuntimeException("target is null in substitution"); } - TemplateBuilder tb = TemplateBuilder.createPattern(code); - List results = tb.build().substitute(tb.getTemplateParameters(template, targetType)); + List results = TemplateBuilder.createPattern(code, template).substitute(targetType); if (results.size() > 1) { throw new SpoonException("StatementTemplate cannot return more then one statement"); } @@ -583,8 +582,7 @@ public static E substitute(CtType targetType, Template< */ public static > T substitute(Template template, T templateType) { // result.setParent(templateType.getParent()); - TemplateBuilder tb = TemplateBuilder.createPattern(templateType); - List types = tb.build().substitute(tb.getTemplateParameters(template)); + List types = TemplateBuilder.createPattern(templateType, template).substitute(null); if (types.size() > 1) { throw new SpoonException("Cannot generate more then one type"); }