diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index bd470726d5a..1b9129da050 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -42,6 +42,8 @@ import spoon.reflect.factory.Factory; import spoon.reflect.meta.RoleHandler; import spoon.reflect.meta.impl.RoleHandlerHelper; +import spoon.reflect.path.CtRole; +import spoon.reflect.reference.CtExecutableReference; import spoon.reflect.reference.CtTypeReference; import spoon.reflect.reference.CtVariableReference; import spoon.reflect.visitor.CtScanner; @@ -205,14 +207,7 @@ public PatternBuilder configureAutomaticParameters() { if (pattern.getSubstitutionRequest(varRef) == null) { //there is no substitution request registered for this variable reference yet. create one ParameterInfo pi = pb.parameter(varRef.getSimpleName()).getCurrentParameter(); - /* - * the target of substitution is always the parent node of variableReference - * - the expression - CtVariableAccess - * which can be replaced by any other CtVariableAccess. - * For example CtFieldRead can be replaced by CtVariableRead or by CtLiteral - */ - CtVariableAccess varAccess = (CtVariableAccess) varRef.getParent(); - pattern.addSubstitutionRequest(pi, varAccess); + pattern.addSubstitutionRequest(pi, getSubstitutedNodeOfVariableReference(varRef)); } } }); @@ -220,6 +215,33 @@ public PatternBuilder configureAutomaticParameters() { return this; } + /** + * @return a node, which has to be substituted instead of variable reference `varRef` + */ + private CtElement getSubstitutedNodeOfVariableReference(CtVariableReference varRef) { + /* + * the target of substitution is always the parent node of variableReference + * - the expression - CtVariableAccess + * which can be replaced by any other CtVariableAccess. + * For example CtFieldRead can be replaced by CtVariableRead or by CtLiteral + */ + CtVariableAccess varAccess = (CtVariableAccess) varRef.getParent(); + CtElement varAccessParent = varAccess.getParent(); + if (varAccessParent instanceof CtInvocation) { + CtInvocation invocation = (CtInvocation) varAccessParent; + CtExecutableReference executableRef = invocation.getExecutable(); + if (executableRef.getSimpleName().equals("S")) { + if (TemplateParameter.class.getName().equals(executableRef.getDeclaringType().getQualifiedName())) { + /* + * the invocation of TemplateParameter#S() has to be substituted + */ + return invocation; + } + } + } + return varAccess; + } + public static class TypeView { private CtType template; @@ -334,12 +356,7 @@ public PatternBuilder configureTemplateParameters(CtType templateType, Map nestedType = getLocalTypeRefBySimpleName(templateType, stringMarker); - if (nestedType == null) { - throw new SpoonException("Template parameter " + typeMember + " doesn't match to any local type"); - } - //There is a local type with such name. Replace it - params.parameter(parameterName).byType(nestedType); + params.parameter(parameterName).byLocalType(templateType, stringMarker); } else if (paramType.getQualifiedName().equals(String.class.getName())) { // CtType nestedType = getLocalTypeBySimpleName(templateType, stringMarker); // if (nestedType != null) { @@ -418,6 +435,11 @@ private CtTypeReference getLocalTypeRefBySimpleName(CtType templateType, S public class ParametersBuilder { ParameterInfo currentParameter; + /** + * Creates a parameter with name `paramName` and assigns it into context, so next calls on builder will be applied to this parameter + * @param paramName to be build parameter name + * @return this {@link ParametersBuilder} to support fluent API + */ public ParametersBuilder parameter(String paramName) { currentParameter = parameters.get(paramName); if (currentParameter == null) { @@ -427,6 +449,17 @@ public ParametersBuilder parameter(String paramName) { return this; } + /** + * @return true if parameter `paramName` already contains at least one substitution request + */ + public boolean isSubstituted(String paramName) { + ParameterInfo pi = parameters.get(paramName); + if (pi != null) { + return pi.getSubstitutionRequests().size() > 0; + } + return false; + } + protected ParameterInfo getCurrentParameter() { if (currentParameter == null) { throw new SpoonException("Parameter name must be defined first by call of #parameter(String) method."); @@ -457,8 +490,38 @@ public ParametersBuilder byType(String typeQualifiedName) { */ public ParametersBuilder byType(CtTypeReference type) { ParameterInfo pi = getCurrentParameter(); + //substitute all references to that type pattern.getModel().filterChildren((CtTypeReference typeRef) -> typeRef.equals(type)) - .forEach((CtTypeReference typeRef) -> pattern.addSubstitutionRequest(pi, typeRef)); + .forEach((CtTypeReference typeRef) -> { + pattern.addSubstitutionRequest(pi, typeRef); + }); + /** + * If Type itself is found part of model, then substitute it's simple name too + */ + String typeQName = type.getQualifiedName(); + CtType type2 = pattern.getModel() + .filterChildren((CtType t) -> t.getQualifiedName().equals(typeQName)) + .first(); + if (type2 != null) { + //Substitute name of template too + pattern.addSubstitutionRequest(pi, type2, CtRole.NAME); + } + return this; + } + + /** + * Searches for a type visible in scope `templateType`, whose simple name is equal to `localTypeSimpleName` + * @param searchScope the Type which is searched for local Type + * @param localTypeSimpleName the simple name of to be returned Type + * @return {@link ParametersBuilder} to support fluent API + */ + public ParametersBuilder byLocalType(CtType searchScope, String localTypeSimpleName) { + CtTypeReference nestedType = getLocalTypeRefBySimpleName(searchScope, localTypeSimpleName); + if (nestedType == null) { + throw new SpoonException("Template parameter " + localTypeSimpleName + " doesn't match to any local type"); + } + //There is a local type with such name. Replace it + byType(nestedType); return this; } @@ -471,14 +534,7 @@ public ParametersBuilder byVariableReference(CtVariable variable) { ParameterInfo pi = getCurrentParameter(); pattern.getModel().map(new VariableReferenceFunction(variable)) .forEach((CtVariableReference varRef) -> { - /* - * the target of substitution is always the parent node of variableReference - * - the expression - CtVariableAccess - * which can be replaced by any other CtVariableAccess. - * For example CtFieldRead can be replaced by CtVariableRead or by CtLiteral - */ - CtVariableAccess varAccess = (CtVariableAccess) varRef.getParent(); - pattern.addSubstitutionRequest(pi, varAccess); + pattern.addSubstitutionRequest(pi, getSubstitutedNodeOfVariableReference(varRef)); }); return this; } diff --git a/src/main/java/spoon/template/Substitution.java b/src/main/java/spoon/template/Substitution.java index 1f8fa3c690b..a220a857fee 100644 --- a/src/main/java/spoon/template/Substitution.java +++ b/src/main/java/spoon/template/Substitution.java @@ -114,7 +114,22 @@ 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, extendedParams).build().substitute(extendedParams); + List> generated = PatternBuilder + .createPattern(templateOfType) + .configureTemplateParameters(templateOfType, extendedParams) + .configureParameters(pb -> { + templateParameters.forEach((paramName, paramValue) -> { + if (pb.isSubstituted(paramName) == false) { + if (paramValue instanceof CtTypeReference) { + CtTypeReference typeRefParamValue = (CtTypeReference) paramValue; + pb.parameter(paramName).byLocalType(templateOfType, paramName); + } + pb.parameter(paramName).bySubstring(paramName); + } + }); + }) + .build() + .substitute(extendedParams); for (CtType ctType : generated) { targetPackage.addType(ctType); }