All Downloads are FREE. Search and download functionalities are using the official Maven repository.

spoon.template.Substitution Maven / Gradle / Ivy

Go to download

Spoon is a tool for meta-programming, analysis and transformation of Java programs.

There is a newer version: 11.1.1-beta-14
Show newest version
/*
 * SPDX-License-Identifier: (MIT OR CECILL-C)
 *
 * Copyright (C) 2006-2023 INRIA and contributors
 *
 * Spoon is available either under the terms of the MIT License (see LICENSE-MIT.txt) or the Cecill-C License (see LICENSE-CECILL-C.txt). You as the user are entitled to choose the terms under which to adopt Spoon.
 */
package spoon.template;

import spoon.SpoonException;
import spoon.pattern.PatternBuilder;
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 java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 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 > void insertAll(CtType targetType, T template) {

		CtClass templateClass = getTemplateCtClass(targetType.getFactory(), 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);
			}
		}
	}

	/**
	 * Generates a type (class, interface, enum, ...) from the template model `templateOfType`
	 * by by substituting all the template parameters by their values.
	 *
	 * Inserts all the methods, fields, constructors, initialization blocks (if
	 * target is a class), inner types, super class and super interfaces.
	 *
	 * Note!
	 * This algorithm does NOT handle interfaces or annotations
	 * {@link Template}, {@link spoon.template.Local}, {@link TemplateParameter} or {@link Parameter}
	 * in a special way, it means they all will be added to the generated type too.
	 * If you do not want to add them then clone your templateOfType and remove these nodes from that model before.
	 *
	 * @param qualifiedTypeName
	 * 		the qualified name of the new type
	 * @param templateOfType
	 * 		the model used as source of generation.
	 * @param templateParameters
	 * 		the substitution parameters
	 */
	public static > T createTypeFromTemplate(String qualifiedTypeName, CtType templateOfType, Map templateParameters) {
		return PatternBuilder
				.create(templateOfType)
				.configurePatternParameters(pc -> {
					pc.byTemplateParameter(templateParameters);
					pc.byParameterValues(templateParameters);
				})
				.build()
				.generator()
				.generate(qualifiedTypeName, templateParameters);
	}

	/**
	 * 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> sourceClass = getTemplateCtClass(targetType.getFactory(), 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> 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.getFactory(), 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) {
		Set> methodsOfTemplate = sourceClass.getFactory().Type().get(Template.class).getMethods();
		// insert all the methods
		for (CtMethod m : sourceClass.getMethods()) {
			if (m.getAnnotation(Local.class) != null) {
				continue;
			}
			if (m.getAnnotation(Parameter.class) != null) {
				continue;
			}

			boolean isOverridingTemplateItf = false;
			for (CtMethod m2 : methodsOfTemplate) {
				if (m.isOverriding(m2)) {
					isOverridingTemplateItf = true;
				}
			}

			if (isOverridingTemplateItf) {
				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.getFactory(), 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.getFactory(), 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) {

		if (nestedType.getAnnotation(Local.class) != null) {
			return;
		}
		CtType result = substitute(targetType, 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.getFactory(), 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  CtConstructor insertConstructor(CtClass targetClass, Template template, CtMethod sourceMethod) {

		if (targetClass instanceof CtInterface) {
			return null;
		}
		CtConstructor newConstructor = targetClass.getFactory().Constructor().create(targetClass, sourceMethod);
		newConstructor = substitute(targetClass, template, newConstructor);
		targetClass.addConstructor(newConstructor);
		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  CtMethod insertMethod(CtType targetType, Template template, CtMethod sourceMethod) {

		CtMethod newMethod = substitute(targetType, template, sourceMethod);
		if (targetType instanceof CtInterface) {
			newMethod.setBody(null);
		}
		targetType.addMethod(newMethod);
		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  CtConstructor insertConstructor(CtClass targetClass, Template template, CtConstructor sourceConstructor) {

		CtConstructor newConstrutor = substitute(targetClass, template, (CtConstructor) sourceConstructor);
		// remove the implicit constructor if clashing
		if (newConstrutor.getParameters().isEmpty()) {
			CtConstructor c = targetClass.getConstructor();
			if (c != null && c.isImplicit()) {
				targetClass.removeConstructor((CtConstructor) c);
			}
		}
		targetClass.addConstructor(newConstrutor);
		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.getFactory(), 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.getFactory(), 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.getFactory(), 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
	 */
	@SuppressWarnings("unchecked")
	public static  E substitute(CtType targetType, Template template, E code) {
		if (code == null) {
			return null;
		}
		if (targetType == null) {
			throw new RuntimeException("target is null in substitution");
		}
		TemplateBuilder tb = TemplateBuilder.createTemplateBuilder(code, template);
		if (template instanceof AbstractTemplate) {
			tb.setAddGeneratedBy(((AbstractTemplate) template).isAddGeneratedBy());
		}
		return (E) tb.substituteSingle(targetType, CtElement.class);
	}

	/**
	 * 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
	 */
	@SuppressWarnings("unchecked")
	public static > T substitute(Template template, T templateType) {
		// result.setParent(templateType.getParent());
		CtType result = TemplateBuilder.createTemplateBuilder(templateType, template).substituteSingle(null, CtType.class);
		//TODO check if it is still needed
		result.setPositions(null);
		return (T) result;
	}

	/**
	 * Generates a field (and its initialization expression) from a template
	 * field by substituting all the template parameters by their values.
	 *
	 * @param 
	 * 		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  CtField insertField(CtType targetType, Template template, CtField sourceField) {
		CtField field = substitute(targetType, template, sourceField);
		targetType.addField(field);
		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> refs = Query.getReferences(element, new ReferenceTypeFilter<>(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 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
	 */
	public static  CtClass getTemplateCtClass(Factory factory, Template template) {
		// we first look in the template factory
		CtClass c = factory.Templates().Class().get(template.getClass());
		if (c.isShadow()) {
			// maybe the template was put as regular input source
			CtClass creg = factory.Class().get(template.getClass());
			if (creg.isShadow()) {
				throw new SpoonException("The template " + template.getClass().getName() + " is not part of model. Add template sources to spoon template path.");
			} else {
				c = creg;
			}
		}
		checkTemplateContracts(c);
		return c;
	}

	private static  void checkTemplateContracts(CtClass c) {
		for (CtField f : c.getFields()) {
			Parameter templateParamAnnotation = f.getAnnotation(Parameter.class);
			if (templateParamAnnotation != null && !templateParamAnnotation.value().isEmpty()) {
				String proxyName = templateParamAnnotation.value();
				// contract: if value, then the field type must be String or CtTypeReference
				String fieldTypeQName = f.getType().getQualifiedName();
				if (fieldTypeQName.equals(String.class.getName())) {
					// contract: the name of the template parameter must correspond to the name of the field
					// as found, by Pavel, this is not good contract because it prevents easy refactoring of templates
					// we remove it but keep the commented code in case somebody would come up with this bad idea
//					if (!f.getSimpleName().equals("_" + f.getAnnotation(Parameter.class).value())) {
//						throw new TemplateException("the field name of a proxy template parameter must be called _" + f.getSimpleName());
//					}

					// contract: if a proxy parameter is declared and named "x" (@Parameter("x")), then a type member named "x" must exist.
					boolean found = false;
					for (CtTypeMember member: c.getTypeMembers()) {
						if (member.getSimpleName().equals(proxyName)) {
							found = true;
						}
					}
					if (!found) {
						throw new TemplateException("if a proxy parameter is declared and named \"" + proxyName + "\", then a type member named \"\" + proxyName + \"\" must exist.");
					}
				} else if (fieldTypeQName.equals(CtTypeReference.class.getName())) {
					//OK it is CtTypeReference
				} else {
					throw new TemplateException("proxy template parameter must be typed as String or CtTypeReference, but it is " + fieldTypeQName);
				}
			}
		}
	}

	/**
	 * 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());
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy