/** * 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.template; import spoon.SpoonException; import spoon.processing.FactoryAccessor; import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtExpression; import spoon.reflect.code.CtStatement; import spoon.reflect.declaration.CtAnonymousExecutable; import spoon.reflect.declaration.CtClass; import spoon.reflect.declaration.CtConstructor; import spoon.reflect.declaration.CtElement; import spoon.reflect.declaration.CtExecutable; import spoon.reflect.declaration.CtField; import spoon.reflect.declaration.CtInterface; import spoon.reflect.declaration.CtMethod; import spoon.reflect.declaration.CtType; import spoon.reflect.declaration.CtTypeMember; import spoon.reflect.factory.Factory; import spoon.reflect.reference.CtPackageReference; import spoon.reflect.reference.CtTypeReference; import spoon.reflect.visitor.Query; import spoon.reflect.visitor.filter.ReferenceTypeFilter; import spoon.support.template.Parameters; import spoon.support.template.SubstitutionVisitor; import java.lang.reflect.Field; import java.util.List; /** * This class defines the substitution API for templates (see {@link Template}). */ public abstract class Substitution { private Substitution() { } /** * Inserts all the methods, fields, constructors, initialization blocks (if * target is a class), inner types, and super interfaces (except * {@link Template}) from a given template by substituting all the template * parameters by their values. Members annotated with * {@link spoon.template.Local} or {@link Parameter} are not inserted. * * @param targetType * the target type * @param template * the source template */ public static <T extends Template<?>> void insertAll(CtType<?> targetType, T template) { CtClass<T> templateClass = getTemplateCtClass(targetType, template); // insert all the interfaces insertAllSuperInterfaces(targetType, template); // insert all the methods insertAllMethods(targetType, template); // insert all the constructors and all the initialization blocks (only for classes) insertAllConstructors(targetType, template); for (CtTypeMember typeMember : templateClass.getTypeMembers()) { if (typeMember instanceof CtField) { // insert all the fields insertGeneratedField(targetType, template, (CtField<?>) typeMember); } else if (typeMember instanceof CtType) { // insert all the inner types insertGeneratedNestedType(targetType, template, (CtType) typeMember); } } } /** * Inserts all the super interfaces (except {@link Template}) from a given * template by substituting all the template parameters by their values. * * @param targetType * the target type * @param template * the source template */ public static void insertAllSuperInterfaces(CtType<?> targetType, Template<?> template) { CtClass<? extends Template<?>> sourceClass = getTemplateCtClass(targetType, template); insertAllSuperInterfaces(targetType, template, sourceClass); } /** * Inserts all the super interfaces (except {@link Template}) from a given * template by substituting all the template parameters by their values. * * @param targetType * the target type * @param template * the source template * @param sourceClass * the model of source template */ static void insertAllSuperInterfaces(CtType<?> targetType, Template<?> template, CtClass<? extends Template<?>> sourceClass) { // insert all the interfaces for (CtTypeReference<?> t : sourceClass.getSuperInterfaces()) { if (!t.equals(targetType.getFactory().Type().createReference(Template.class))) { CtTypeReference<?> t1 = t; // substitute ref if needed if (Parameters.getNames(sourceClass).contains(t.getSimpleName())) { Object o = Parameters.getValue(template, t.getSimpleName(), null); if (o instanceof CtTypeReference) { t1 = (CtTypeReference<?>) o; } else if (o instanceof Class) { t1 = targetType.getFactory().Type().createReference((Class<?>) o); } else if (o instanceof String) { t1 = targetType.getFactory().Type().createReference((String) o); } } if (!t1.equals(targetType.getReference())) { Class<?> c = null; try { c = t1.getActualClass(); } catch (Exception e) { // swallow it } if (c != null && c.isInterface()) { targetType.addSuperInterface(t1); } if (c == null) { targetType.addSuperInterface(t1); } } } } } /** * Inserts all the methods from a given template by substituting all the * template parameters by their values. Members annotated with * {@link spoon.template.Local} or {@link Parameter} are not inserted. * * @param targetType * the target type * @param template * the source template */ public static void insertAllMethods(CtType<?> targetType, Template<?> template) { CtClass<?> sourceClass = getTemplateCtClass(targetType, template); insertAllMethods(targetType, template, sourceClass); } /** * Inserts all the methods from a given template by substituting all the * template parameters by their values. Members annotated with * {@link spoon.template.Local} or {@link Parameter} are not inserted. * * @param targetType * the target type * @param template * the source template * @param sourceClass * the model of source template */ static void insertAllMethods(CtType<?> targetType, Template<?> template, CtClass<?> sourceClass) { // insert all the methods for (CtMethod<?> m : sourceClass.getMethods()) { if (m.getAnnotation(Local.class) != null) { continue; } if (m.getAnnotation(Parameter.class) != null) { continue; } insertMethod(targetType, template, m); } } /** * Inserts all the fields from a given template by substituting all the * template parameters by their values. Members annotated with * {@link spoon.template.Local} or {@link Parameter} are not inserted. * * @param targetType * the target type * @param template * the source template */ public static void insertAllFields(CtType<?> targetType, Template<?> template) { CtClass<?> sourceClass = getTemplateCtClass(targetType, template); // insert all the fields for (CtTypeMember typeMember: sourceClass.getTypeMembers()) { if (typeMember instanceof CtField) { insertGeneratedField(targetType, template, (CtField<?>) typeMember); } } } /** * Inserts the field by substituting all the * template parameters by their values. Field annotated with * {@link spoon.template.Local} or {@link Parameter} is not inserted. * @param targetType * @param template * @param field */ static void insertGeneratedField(CtType<?> targetType, Template<?> template, CtField<?> field) { if (field.getAnnotation(Local.class) != null) { return; } if (Parameters.isParameterSource(field.getReference())) { return; } insertField(targetType, template, field); } /** * Inserts all the nested types from a given template by substituting all the * template parameters by their values. Members annotated with * {@link spoon.template.Local} are not inserted. * * @param targetType * the target type * @param template * the source template */ public static void insertAllNestedTypes(CtType<?> targetType, Template<?> template) { CtClass<?> sourceClass = getTemplateCtClass(targetType, template); // insert all the fields for (CtTypeMember typeMember: sourceClass.getTypeMembers()) { if (typeMember instanceof CtType) { insertGeneratedNestedType(targetType, template, (CtType<?>) typeMember); } } } /** * Inserts the nestedType by substituting all the * template parameters by their values. Nested type annotated with * {@link spoon.template.Local} is not inserted. * @param targetType * the target type * @param template * the source template * @param nestedType * to be insterted nested type */ static void insertGeneratedNestedType(CtType<?> targetType, Template<?> template, CtType<?> nestedType) { CtClass<?> sourceClass = getTemplateCtClass(targetType, template); if (nestedType.getAnnotation(Local.class) != null) { return; } CtType<?> result = substitute(sourceClass, template, (CtType) nestedType); targetType.addNestedType(result); } /** * Inserts all constructors and initialization blocks from a given template * by substituting all the template parameters by their values. Members * annotated with {@link spoon.template.Local} or {@link Parameter} are not * inserted. * * @param targetType * the target type * @param template * the source template */ public static void insertAllConstructors(CtType<?> targetType, Template<?> template) { CtClass<?> sourceClass = getTemplateCtClass(targetType, template); insertAllConstructors(targetType, template, sourceClass); } /** * Inserts all constructors and initialization blocks from a given template * by substituting all the template parameters by their values. Members * annotated with {@link spoon.template.Local} or {@link Parameter} are not * inserted. * * @param targetType * the target type * @param template * the source template * @param sourceClass * the model of source template */ static void insertAllConstructors(CtType<?> targetType, Template<?> template, CtClass<?> sourceClass) { // insert all the constructors if (targetType instanceof CtClass) { for (CtConstructor<?> c : sourceClass.getConstructors()) { if (c.isImplicit()) { continue; } if (c.getAnnotation(Local.class) != null) { continue; } insertConstructor((CtClass<?>) targetType, template, c); } } // insert all the initialization blocks (only for classes) if (targetType instanceof CtClass) { for (CtAnonymousExecutable e : sourceClass.getAnonymousExecutables()) { ((CtClass<?>) targetType).addAnonymousExecutable(substitute(targetType, template, e)); } } } /** * Generates a constructor from a template method by substituting all the * template parameters by their values. * * @param targetClass * the target class where to insert the generated constructor * @param template * the template instance that holds the source template method * and that defines the parameter values * @param sourceMethod * the source template method * @return the generated method */ public static <T> CtConstructor<T> insertConstructor(CtClass<T> targetClass, Template<?> template, CtMethod<?> sourceMethod) { if (targetClass instanceof CtInterface) { return null; } CtConstructor<T> newConstructor = targetClass.getFactory().Constructor().create(targetClass, sourceMethod); newConstructor = substitute(targetClass, template, newConstructor); targetClass.addConstructor(newConstructor); // newConstructor.setParent(targetClass); return newConstructor; } /** * Generates a method from a template method by substituting all the * template parameters by their values. * * @param targetType * the target type where to insert the generated method * @param template * the template instance that holds the source template method * and that defines the parameter values * @param sourceMethod * the source template method * @return the generated method */ public static <T> CtMethod<T> insertMethod(CtType<?> targetType, Template<?> template, CtMethod<T> sourceMethod) { CtMethod<T> newMethod = substitute(targetType, template, sourceMethod); if (targetType instanceof CtInterface) { newMethod.setBody(null); } targetType.addMethod(newMethod); // newMethod.setParent(targetType); return newMethod; } /** * Generates a constructor from a template constructor by substituting all * the template parameters by their values. * * @param targetClass * the target class where to insert the generated constructor * @param template * the template instance that holds the source template * constructor and that defines the parameter values * @param sourceConstructor * the source template constructor * @return the generated constructor */ @SuppressWarnings("unchecked") public static <T> CtConstructor<T> insertConstructor(CtClass<T> targetClass, Template<?> template, CtConstructor<?> sourceConstructor) { CtConstructor<T> newConstrutor = substitute(targetClass, template, (CtConstructor<T>) sourceConstructor); // remove the implicit constructor if clashing if (newConstrutor.getParameters().isEmpty()) { CtConstructor<?> c = targetClass.getConstructor(); if (c != null && c.isImplicit()) { targetClass.getConstructors().remove(c); } } targetClass.addConstructor(newConstrutor); // newConstrutor.setParent(targetClass); return newConstrutor; } /** * Gets a body from a template executable with all the template parameters * substituted. * * @param targetClass * the target class * @param template * the template that holds the executable * @param executableName * the source executable template * @param parameterTypes * the parameter types of the source executable * @return the body expression of the source executable template with all * the template parameters substituted */ public static CtBlock<?> substituteMethodBody(CtClass<?> targetClass, Template<?> template, String executableName, CtTypeReference<?>... parameterTypes) { CtClass<?> sourceClass = getTemplateCtClass(targetClass, template); CtExecutable<?> sourceExecutable = executableName.equals(template.getClass().getSimpleName()) ? sourceClass.getConstructor(parameterTypes) : sourceClass.getMethod(executableName, parameterTypes); return substitute(targetClass, template, sourceExecutable.getBody()); } /** * Gets a statement from a template executable with all the template * parameters substituted. * * @param targetClass * the target class * @param template * the template that holds the executable * @param statementIndex * the statement index in the executable's body * @param executableName * the source executable template * @param parameterTypes * the parameter types of the source executable * @return the body expression of the source executable template with all * the template parameters substituted */ public static CtStatement substituteStatement(CtClass<?> targetClass, Template<?> template, int statementIndex, String executableName, CtTypeReference<?>... parameterTypes) { CtClass<?> sourceClass = getTemplateCtClass(targetClass, template); CtExecutable<?> sourceExecutable = executableName.equals(template.getClass().getSimpleName()) ? sourceClass.getConstructor(parameterTypes) : sourceClass.getMethod(executableName, parameterTypes); return substitute(targetClass, template, sourceExecutable.getBody().getStatement(statementIndex)); } /** * Gets a default expression from a template field with all the template * parameters substituted. * * @param targetType * the target type * @param template * the template that holds the field * @param fieldName * the template source field * @return the expression of the template source field with all the template * parameters substituted */ public static CtExpression<?> substituteFieldDefaultExpression(CtType<?> targetType, Template<?> template, String fieldName) { CtClass<?> sourceClass = getTemplateCtClass(targetType, template); CtField<?> sourceField = sourceClass.getField(fieldName); return substitute(targetType, template, sourceField.getDefaultExpression()); } /** * Substitutes all the template parameters in a random piece of code. * * @param targetType * the target type * @param template * the template instance * @param code * the code * @return the code where all the template parameters has been substituted * by their values */ public static <E extends CtElement> E substitute(CtType<?> targetType, Template<?> template, E code) { if (code == null) { return null; } if (targetType == null) { throw new RuntimeException("target is null in substitution"); } E result = (E) code.clone(); new SubstitutionVisitor(targetType.getFactory(), targetType, template).scan(result); return result; } /** * Substitutes all the template parameters in the first template element * annotated with an instance of the given annotation type. * * @param targetType * the target type * @param template * the template instance * @param annotationType * the annotation type * @return the element where all the template parameters has be substituted * by their values */ // public static <E extends CtElement> E substitute( // CtSimpleType<?> targetType, Template template, // Class<? extends Annotation> annotationType) { // CtClass<? extends Template> c = targetType.getFactory().Class // .get(template.getClass()); // E element = (E) c.getAnnotatedChildren(annotationType).get(0); // if (element == null) // return null; // if (targetType == null) // throw new RuntimeException("target is null in substitution"); // E result = CtCloner.clone(element); // new SubstitutionVisitor(targetType.getFactory(), targetType, template) // .scan(result); // return result; // } /** * Substitutes all the template parameters in a given template type and * returns the resulting type. * * @param template * the template instance (holds the parameter values) * @param templateType * the template type * @return a copy of the template type where all the parameters has been * substituted */ public static <T extends CtType<?>> T substitute(Template<?> template, T templateType) { T result = (T) templateType.clone(); result.setPositions(null); // result.setParent(templateType.getParent()); new SubstitutionVisitor(templateType.getFactory(), result, template).scan(result); return result; } /** * Generates a field (and its initialization expression) from a template * field by substituting all the template parameters by their values. * * @param <T> * the type of the field * @param targetType * the target type where the field is inserted * @param template * the template that defines the source template field * @param sourceField * the source template field * @return the inserted field */ public static <T> CtField<T> insertField(CtType<?> targetType, Template<?> template, CtField<T> sourceField) { CtField<T> field = substitute(targetType, template, sourceField); targetType.addField(field); // field.setParent(targetType); return field; } /** * A helper method that recursively redirects all the type references from a * source type to a target type in the given element. */ public static void redirectTypeReferences(CtElement element, CtTypeReference<?> source, CtTypeReference<?> target) { List<CtTypeReference<?>> refs = Query.getReferences(element, new ReferenceTypeFilter<CtTypeReference<?>>(CtTypeReference.class)); String srcName = source.getQualifiedName(); String targetName = target.getSimpleName(); CtPackageReference targetPackage = target.getPackage(); for (CtTypeReference<?> ref : refs) { if (ref.getQualifiedName().equals(srcName)) { ref.setSimpleName(targetName); ref.setPackage(targetPackage); } } } /** * @param targetType - the element which is going to receive the model produced by the template. * It is needed here just to provide the spoon factory, which contains the model of the template * * @param template - java instance of the template * * @return - CtClass from the already built spoon model, which represents the template */ static <T> CtClass<T> getTemplateCtClass(CtType<?> targetType, Template<?> template) { Factory factory; // we first need a factory if (targetType != null) { // if it's template with reference replacement factory = targetType.getFactory(); } else { // else we have at least one template parameter with a factory factory = getFactory(template); } return getTemplateCtClass(factory, template); } /** * @param factory - the factory, which contains the model of the template * * @param template - java instance of the template * * @return - CtClass from the already built spoon model, which represents the template */ static <T> CtClass<T> getTemplateCtClass(Factory factory, Template<?> template) { CtClass<T> c = factory.Class().get(template.getClass()); if (c.isShadow()) { throw new SpoonException("The template " + template.getClass().getName() + " is not part of model. Add template sources to spoon template path."); } return c; } /** * returns a Spoon factory object from the first template parameter that contains one */ static Factory getFactory(Template<?> template) { try { for (Field f : Parameters.getAllTemplateParameterFields(template.getClass())) { if (f.get(template) != null && f.get(template) instanceof FactoryAccessor) { return ((FactoryAccessor) f.get(template)).getFactory(); } } } catch (Exception e) { throw new SpoonException(e); } throw new TemplateException("no factory found in template " + template.getClass().getName()); } }