/** * Copyright (C) 2006-2017 INRIA and contributors * Spoon - http://spoon.gforge.inria.fr/ * * This software is governed by the CeCILL-C License under French law and * abiding by the rules of distribution of free software. You can use, modify * and/or redistribute the software under the terms of the CeCILL-C license as * circulated by CEA, CNRS and INRIA at http://www.cecill.info. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details. * * The fact that you are presently reading this means that you have had * knowledge of the CeCILL-C license and that you accept its terms. */ package spoon.support.template; import java.util.ArrayList; import java.util.Collection; import java.util.List; import spoon.SpoonException; import spoon.reflect.code.CtAbstractInvocation; import spoon.reflect.code.CtArrayAccess; import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtCodeElement; import spoon.reflect.code.CtExpression; import spoon.reflect.code.CtFieldAccess; import spoon.reflect.code.CtFieldRead; import spoon.reflect.code.CtFieldWrite; import spoon.reflect.code.CtForEach; import spoon.reflect.code.CtInvocation; import spoon.reflect.code.CtReturn; import spoon.reflect.code.CtStatement; import spoon.reflect.code.CtThisAccess; import spoon.reflect.code.CtVariableAccess; import spoon.reflect.declaration.CtAnnotation; import spoon.reflect.declaration.CtClass; import spoon.reflect.declaration.CtElement; import spoon.reflect.declaration.CtExecutable; import spoon.reflect.declaration.CtNamedElement; import spoon.reflect.declaration.CtParameter; import spoon.reflect.declaration.CtType; import spoon.reflect.declaration.CtTypedElement; import spoon.reflect.factory.Factory; import spoon.reflect.reference.CtArrayTypeReference; import spoon.reflect.reference.CtExecutableReference; import spoon.reflect.reference.CtFieldReference; 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.Local; import spoon.template.Template; import spoon.template.TemplateParameter; class DoNotFurtherTemplateThisElement extends SpoonException { private static final long serialVersionUID = 1L; Object skipped; DoNotFurtherTemplateThisElement(Object e) { super("skipping " + e.toString()); skipped = e; } } /** * This visitor implements the substitution engine of Spoon templates. */ public class SubstitutionVisitor extends CtScanner { public class InheritanceSustitutionScanner extends CtInheritanceScanner { SubstitutionVisitor parent = null; public InheritanceSustitutionScanner(SubstitutionVisitor parent) { this.parent = parent; } /** * Replaces method parameters when defined as a list of * {@link CtParameter}. */ @Override public <R> void scanCtExecutable(CtExecutable<R> 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); } } } } super.scanCtExecutable(e); } /** * Remove template-specific {@link Local} annotations. */ @Override public void scanCtElement(CtElement e) { CtAnnotation<?> a = e.getAnnotation(e.getFactory().Type().createReference(Local.class)); if (a != null) { e.removeAnnotation(a); } super.scanCtElement(e); } /** * Replaces parameters in element names (even if detected as a * substring). */ @Override public void scanCtNamedElement(CtNamedElement element) { if (element.getDocComment() != null) { element.setDocComment(substituteInDocComment(element.getDocComment())); } // replace parameters in names String name = element.getSimpleName(); for (String pname : parameterNames) { 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); element.setSimpleName(name); } else if ((value instanceof CtTypeReference) && (element instanceof CtType)) { // replace with the type reference's name name = name.replace(pname, ((CtTypeReference<?>) value).getSimpleName()); element.setSimpleName(name); } } } super.scanCtNamedElement(element); } 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 */ @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); CtBlock<?> l = foreach.getFactory().Core().createBlock(); CtStatement body = foreach.getBody(); for (Object element : value) { CtStatement b = body.clone(); for (CtVariableAccess<?> va : Query.getElements(b, new VariableAccessFilter<>(foreach.getVariable().getReference()))) { va.replace((CtExpression) element); } if (b instanceof CtBlock && ((CtBlock) b).getStatements().size() == 1) { b = ((CtBlock) b).getStatement(0); } l.addStatement(b); } foreach.replace(l); throw new DoNotFurtherTemplateThisElement(foreach); } } super.visitCtForEach(foreach); } @Override public <T> void visitCtFieldRead(CtFieldRead<T> fieldRead) { visitFieldAccess(fieldRead); } @Override public <T> void visitCtFieldWrite(CtFieldWrite<T> fieldWrite) { visitFieldAccess(fieldWrite); } private <T> void visitFieldAccess(CtFieldAccess<T> 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); fieldAccess.replace((CtExpression) fieldAccess.getFactory().Code().createLiteral(value .length)); throw new DoNotFurtherTemplateThisElement(fieldAccess); } } } if (Parameters.isParameterSource(ref)) { // replace direct field parameter accesses Object value = Parameters.getValue(template, ref.getSimpleName(), Parameters.getIndex(fieldAccess)); CtExpression toReplace = fieldAccess; if (fieldAccess.getParent() instanceof CtArrayAccess) { toReplace = (CtExpression) fieldAccess.getParent(); } if (!(value instanceof TemplateParameter)) { if (value instanceof Class) { toReplace.replace(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( factory.Field().createReference(enumType, enumType, ((Enum<?>) value).name()), true)); } else if (value instanceof List) { // replace list of CtParameter for generic access to the // parameters List<CtParameter<?>> l = (List<CtParameter<?>>) value; List<CtExpression<?>> vas = factory.Code().createVariableReads(l); CtAbstractInvocation<?> inv = (CtAbstractInvocation<?>) fieldAccess.getParent(); int i = inv.getArguments().indexOf(fieldAccess); inv.getArguments().remove(i); inv.getExecutable().getActualTypeArguments().remove(i); for (CtExpression<?> va : vas) { va.setParent(fieldAccess.getParent()); inv.getArguments().add(i, va); inv.getExecutable().getActualTypeArguments().add(i, va.getType()); i++; } } else if ((value != null) && value.getClass().isArray()) { toReplace.replace(factory.Code().createLiteralArray((Object[]) value)); } else { toReplace.replace(factory.Code().createLiteral(value)); } } else { toReplace.clone(); } // do not visit if replaced throw new DoNotFurtherTemplateThisElement(fieldAccess); } } /** * Replaces _xx_.S(). */ @SuppressWarnings("unchecked") @Override public <T> void visitCtInvocation(CtInvocation<T> invocation) { if (invocation.getExecutable().isOverriding(S)) { CtFieldAccess<?> fa = null; if ((invocation.getTarget() instanceof CtFieldAccess)) { fa = (CtFieldAccess<?>) invocation.getTarget(); } if (((invocation.getTarget() instanceof CtArrayAccess) && (((CtArrayAccess<?, CtExpression<?>>) invocation.getTarget()) .getTarget() instanceof CtFieldAccess))) { fa = (CtFieldAccess<?>) ((CtArrayAccess<?, CtExpression<?>>) 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(); // substitute in the replacement (for fixing type // references // and // for recursive substitution) r.accept(parent); } if ((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); } } // do not visit the invocation if replaced throw new DoNotFurtherTemplateThisElement(invocation); } super.visitCtInvocation(invocation); } @SuppressWarnings("unchecked") @Override public <T> void scanCtExpression(CtExpression<T> expression) { for (int i = 0; i < expression.getTypeCasts().size(); i++) { CtTypeReference<T> t = (CtTypeReference<T>) expression.getTypeCasts().get(i); if (parameterNames.contains(t.getSimpleName())) { // 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<T>) o)); } else if (o instanceof CtTypeReference) { t = (CtTypeReference<T>) o; expression.getTypeCasts().set(i, t); } else { throw new RuntimeException("unsupported reference substitution"); } } } super.scanCtExpression(expression); } @SuppressWarnings("unchecked") @Override public <T> void scanCtTypedElement(CtTypedElement<T> e) { if ((e.getType() != null) && parameterNames.contains(e.getType().getSimpleName())) { // replace type parameters // TODO: this would probably not work with inner classes!!! CtTypeReference<T> t; Object o = Parameters.getValue(template, e.getType().getSimpleName(), null); if (o instanceof Class) { o = factory.Type().createReference(((Class<T>) o)); } if (o instanceof CtTypeReference) { if ((e.getType() instanceof CtArrayTypeReference) && !(o instanceof CtArrayTypeReference)) { t = (CtArrayTypeReference<T>) e.getFactory().Type().createArrayReference( (CtTypeReference<?>) o, ((CtArrayTypeReference<?>) e.getType()).getDimensionCount()); } else { t = (CtTypeReference<T>) o; } e.setType(t); } else { throw new RuntimeException("unsupported reference substitution"); } } super.scanCtTypedElement(e); } // fixes the references to executables in templates @Override public <T> void visitCtExecutableReference(CtExecutableReference<T> reference) { scanCtReference(reference); visitCtTypeReference(reference.getDeclaringType()); scanCtActualTypeContainer(reference); } /** * Replaces type parameters and references to the template type with * references to the target type (only if the referenced element exists * in the target). */ @Override public <T> void visitCtTypeReference(CtTypeReference<T> reference) { // if (reference.equals(templateRef) || (!reference.isPrimitif() && // f.Type().createReference(Template.class).isAssignableFrom(reference) // && reference.isAssignableFrom(templateRef))) { if (reference.equals(templateRef)) { // replace references to the template type with references // to the targetType (only if the referenced element exists // in the target) reference.setDeclaringType(targetRef.getDeclaringType()); reference.setPackage(targetRef.getPackage()); reference.setSimpleName(targetRef.getSimpleName()); } if (parameterNames.contains(reference.getSimpleName())) { // 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(); 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)) { // this can only be a template inheritance case (to be verified) CtTypeReference<?> sc = targetRef.getSuperclass(); if (sc != null) { reference.setDeclaringType(sc.getDeclaringType()); reference.setPackage(sc.getPackage()); reference.setSimpleName(sc.getSimpleName()); } else { reference.setDeclaringType(null); reference.setPackage(factory.Package().createReference("java.lang")); reference.setSimpleName("Object"); } } super.visitCtTypeReference(reference); } } Factory factory; InheritanceSustitutionScanner inheritanceScanner; CtExecutableReference<?> S; CtTypeReference<?> targetRef; CtType<?> targetType; Template<?> template; CtTypeReference<? extends Template> templateRef; CtTypeReference<Template> templateTypeRef; CtClass<? extends Template<?>> templateType; Collection<String> parameterNames; /** * Creates a new substitution visitor. * * @param f * the factory * @param targetType * the target type of the substitution * @param template * the template that holds the parameter values */ public SubstitutionVisitor(Factory f, CtType<?> targetType, Template<?> template) { inheritanceScanner = new InheritanceSustitutionScanner(this); this.factory = f; this.template = template; this.targetType = targetType; S = f.Executable().createReference(f.Type().createReference(TemplateParameter.class), f.Type().createTypeParameterReference("T"), "S"); templateRef = f.Type().createReference(template.getClass()); templateType = f.Class().get(templateRef.getQualifiedName()); parameterNames = Parameters.getNames(templateType); templateTypeRef = f.Type().createReference(Template.class); if (targetType != null) { targetRef = f.Type().createReference(targetType); // first substitute target ref targetRef.accept(this); } } /** * Override to scan on collection copies and avoid potential concurrent * modification exceptions. */ @Override public void scan(Collection<? extends CtElement> elements) { super.scan(new ArrayList<>(elements)); } @Override public void scan(CtElement element) { try { // doing the templating of this element inheritanceScanner.scan(element); // and then scan the children for doing the templating as well in them super.scan(element); } catch (DoNotFurtherTemplateThisElement ignore) { } } }