From 34c3b2e4f55f0ed7313dc3d62c94fabad045c0a8 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Sat, 3 Mar 2018 11:48:11 +0100 Subject: [PATCH 001/131] feat: Metamodel provides metadata about Spoon types and fields --- src/main/java/spoon/Metamodel.java | 1248 +++++++++++++++++ .../spoon/generating/MetamodelGenerator.java | 115 ++ .../java/spoon/test/api/MetamodelTest.java | 36 + 3 files changed, 1399 insertions(+) create mode 100644 src/test/java/spoon/generating/MetamodelGenerator.java diff --git a/src/main/java/spoon/Metamodel.java b/src/main/java/spoon/Metamodel.java index fd67e6b3ed8..7bda0513a56 100644 --- a/src/main/java/spoon/Metamodel.java +++ b/src/main/java/spoon/Metamodel.java @@ -16,17 +16,35 @@ */ package spoon; +import spoon.reflect.code.CtForEach; +import spoon.reflect.declaration.CtClass; +import spoon.reflect.declaration.CtElement; +import spoon.reflect.declaration.CtInterface; import spoon.reflect.declaration.CtModuleDirective; import spoon.reflect.declaration.CtPackageExport; import spoon.reflect.declaration.CtProvidedService; import spoon.reflect.declaration.CtType; import spoon.reflect.factory.Factory; import spoon.reflect.factory.FactoryImpl; +import spoon.reflect.meta.ContainerKind; +import spoon.reflect.meta.RoleHandler; +import spoon.reflect.meta.impl.RoleHandlerHelper; +import spoon.reflect.path.CtRole; +import spoon.reflect.visitor.CtScanner; import spoon.support.DefaultCoreFactory; import spoon.support.StandardEnvironment; +import spoon.support.reflect.code.CtForEachImpl; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.function.Consumer; /** * This class enables to reason on the Spoon metamodel directly @@ -161,4 +179,1234 @@ public static Set> getAllMetamodelInterfaces() { return result; } + public static Collection getAllMetamodelTypes() { + return typesByName.values(); + } + + public static Type getMetamodelTypeByClass(Class clazz) { + return typesByClass.get(clazz); + } + + /** + * Describes a Spoon metamodel type + */ + public static class Type { + /** + * Name of the type + */ + private final String name; + + /** + * The {@link CtClass} linked to this {@link MMType}. Is null in case of class without interface + */ + private final Class modelClass; + /** + * The {@link CtInterface} linked to this {@link MMType}. Is null in case of interface without class + */ + private final Class modelInterface; + + private final List fields; + private final Map fieldsByRole; + + private Type(String name, Class modelInterface, Class modelClass, Consumer fieldsCreator) { + super(); + this.name = name; + this.modelClass = modelClass; + this.modelInterface = modelInterface; + List fields = new ArrayList<>(); + this.fields = Collections.unmodifiableList(fields); + fieldsCreator.accept(new FieldMaker() { + @Override + public FieldMaker field(CtRole role, boolean derived, boolean unsettable) { + fields.add(new Field(Type.this, role, derived, unsettable)); + return this; + } + }); + Map fieldsByRole = new LinkedHashMap<>(fields.size()); + fields.forEach(f -> fieldsByRole.put(f.getRole(), f)); + this.fieldsByRole = Collections.unmodifiableMap(fieldsByRole); + } + + /** + * @return interface name of Spoon model type. For example CtClass, CtForEach, ... + * It is never followed by xxxImpl + */ + public String getName() { + return name; + } + + /** + * @return {@link Class} which implements this type. For example {@link CtForEachImpl} + */ + public Class getModelClass() { + return modelClass; + } + /** + * @return {@link Class} which defines interface of this type. For example {@link CtForEach} + */ + public Class getModelInterface() { + return modelInterface; + } + + @Override + public String toString() { + return getName(); + } + + /** + * @return {@link List} of {@link Field}s of this spoon model {@link Type} in the same order, like they are processed by {@link CtScanner} + */ + public List getFields() { + return fields; + } + + /** + * @param role the {@link CtRole} of to be returned {@link Field} + * @return {@link Field} of this {@link Type} by {@link CtRole} or null if this {@link CtRole} doesn't exist on this {@link Type} + */ + public Field getField(CtRole role) { + return fieldsByRole.get(role); + } + } + /** + * Describes a Spoon metamodel Field + */ + public static class Field { + private final Type owner; + private final CtRole role; + private final RoleHandler roleHandler; + private final boolean derived; + private final boolean unsettable; + + private Field(Type owner, CtRole role, boolean derived, boolean unsettable) { + super(); + this.owner = owner; + this.role = role; + this.derived = derived; + this.unsettable = unsettable; + this.roleHandler = RoleHandlerHelper.getRoleHandler(owner.modelClass, role); + } + + /** + * @return {@link Type}, which contains this {@link Field} + */ + public Type getOwner() { + return owner; + } + + /** + * @return {@link CtRole} of this {@link Field} + */ + public CtRole getRole() { + return role; + } + + /** + * @return {@link RoleHandler} providing generic access to the value of this Field + */ + public RoleHandler getRoleHandler() { + return roleHandler; + } + + /** + * @return true if this field is derived (value is somehow computed) + */ + public boolean isDerived() { + return derived; + } + + /** + * @return true if it makes no sense to set this field on this type + */ + public boolean isUnsettable() { + return unsettable; + } + + /** + * @param element an instance whose attribute value is read + * @return a value of attribute defined by this {@link Field} from the provided `element` + */ + public U getValue(T element) { + return roleHandler.getValue(element); + } + + /** + * @param element an instance whose attribute value is set + * @param value to be set value of attribute defined by this {@link Field} on the provided `element` + */ + public void setValue(T element, U value) { + roleHandler.setValue(element, value); + } + + /** + * @return {@link Class} of {@link Field}'s value. + */ + public Class getValueClass() { + return roleHandler.getValueClass(); + } + + /** + * @return the container kind, to know whether an element, a list, a map, etc is returned. + */ + public ContainerKind getContainerKind() { + return roleHandler.getContainerKind(); + } + + @Override + public String toString() { + return getOwner().toString() + "#" + getRole().getCamelCaseName(); + } + } + + private interface FieldMaker { + /** + * Creates a instance of Field in Type + * @param role a role of the {@link Field} + * @param derived marker if field is derived + * @param unsettable marker if field is unsettable + * @return this to support fluent API + */ + FieldMaker field(CtRole role, boolean derived, boolean unsettable); + } + + private static final Map typesByName = new HashMap<>(); + private static final Map, Type> typesByClass = new HashMap<>(); + + static { + List types = new ArrayList<>(); + initTypes(types); + types.forEach(type -> { + typesByName.put(type.getName(), type); + typesByClass.put(type.getModelClass(), type); + typesByClass.put(type.getModelInterface(), type); + }); + } + private static void initTypes(List types) { + /** + * body of this method was generated by /spoon-core/src/test/java/spoon/generating/MetamodelGenerator.java + * Run the method main and copy the System output here + */ + types.add(new Type("CtConditional", spoon.reflect.code.CtConditional.class, spoon.support.reflect.code.CtConditionalImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.TYPE, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.CONDITION, false, false) + .field(CtRole.THEN, false, false) + .field(CtRole.ELSE, false, false) + .field(CtRole.COMMENT, false, false) + .field(CtRole.CAST, false, false) + + )); + + types.add(new Type("CtProvidedService", spoon.reflect.declaration.CtProvidedService.class, spoon.support.reflect.declaration.CtProvidedServiceImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.COMMENT, false, false) + .field(CtRole.SERVICE_TYPE, false, false) + .field(CtRole.IMPLEMENTATION_TYPE, false, false) + .field(CtRole.ANNOTATION, false, false) + + )); + + types.add(new Type("CtParameter", spoon.reflect.declaration.CtParameter.class, spoon.support.reflect.declaration.CtParameterImpl.class, fm -> fm + .field(CtRole.NAME, false, false) + .field(CtRole.IS_SHADOW, false, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.IS_VARARGS, false, false) + .field(CtRole.DEFAULT_EXPRESSION, true, true) + .field(CtRole.MODIFIER, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.TYPE, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtWhile", spoon.reflect.code.CtWhile.class, spoon.support.reflect.code.CtWhileImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.LABEL, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.EXPRESSION, false, false) + .field(CtRole.BODY, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtTypeReference", spoon.reflect.reference.CtTypeReference.class, spoon.support.reflect.reference.CtTypeReferenceImpl.class, fm -> fm + .field(CtRole.NAME, false, false) + .field(CtRole.IS_SHADOW, false, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.MODIFIER, false, true) + .field(CtRole.INTERFACE, true, true) + .field(CtRole.SUPER_TYPE, true, true) + .field(CtRole.POSITION, false, false) + .field(CtRole.PACKAGE_REF, false, false) + .field(CtRole.DECLARING_TYPE, false, false) + .field(CtRole.TYPE_ARGUMENT, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.COMMENT, false, true) + + )); + + types.add(new Type("CtCatchVariableReference", spoon.reflect.reference.CtCatchVariableReference.class, spoon.support.reflect.reference.CtCatchVariableReferenceImpl.class, fm -> fm + .field(CtRole.NAME, false, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.COMMENT, false, true) + .field(CtRole.TYPE, false, false) + .field(CtRole.ANNOTATION, false, false) + + )); + + types.add(new Type("CtContinue", spoon.reflect.code.CtContinue.class, spoon.support.reflect.code.CtContinueImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.LABEL, false, false) + .field(CtRole.TARGET_LABEL, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtInterface", spoon.reflect.declaration.CtInterface.class, spoon.support.reflect.declaration.CtInterfaceImpl.class, fm -> fm + .field(CtRole.NAME, false, false) + .field(CtRole.IS_SHADOW, false, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.MODIFIER, false, false) + .field(CtRole.SUPER_TYPE, true, true) + .field(CtRole.NESTED_TYPE, true, false) + .field(CtRole.METHOD, true, false) + .field(CtRole.FIELD, true, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.INTERFACE, false, false) + .field(CtRole.TYPE_PARAMETER, false, false) + .field(CtRole.TYPE_MEMBER, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtAssignment", spoon.reflect.code.CtAssignment.class, spoon.support.reflect.code.CtAssignmentImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.LABEL, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.TYPE, false, false) + .field(CtRole.CAST, false, false) + .field(CtRole.ASSIGNED, false, false) + .field(CtRole.ASSIGNMENT, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtBinaryOperator", spoon.reflect.code.CtBinaryOperator.class, spoon.support.reflect.code.CtBinaryOperatorImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.OPERATOR_KIND, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.TYPE, false, false) + .field(CtRole.CAST, false, false) + .field(CtRole.LEFT_OPERAND, false, false) + .field(CtRole.RIGHT_OPERAND, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtEnumValue", spoon.reflect.declaration.CtEnumValue.class, spoon.support.reflect.declaration.CtEnumValueImpl.class, fm -> fm + .field(CtRole.NAME, false, false) + .field(CtRole.IS_SHADOW, false, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.ASSIGNMENT, true, true) + .field(CtRole.MODIFIER, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.TYPE, false, false) + .field(CtRole.DEFAULT_EXPRESSION, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtModuleRequirement", spoon.reflect.declaration.CtModuleRequirement.class, spoon.support.reflect.declaration.CtModuleRequirementImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.MODIFIER, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.COMMENT, false, false) + .field(CtRole.MODULE_REF, false, false) + .field(CtRole.ANNOTATION, false, false) + + )); + + types.add(new Type("CtForEach", spoon.reflect.code.CtForEach.class, spoon.support.reflect.code.CtForEachImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.LABEL, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.FOREACH_VARIABLE, false, false) + .field(CtRole.EXPRESSION, false, false) + .field(CtRole.BODY, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtConstructor", spoon.reflect.declaration.CtConstructor.class, spoon.support.reflect.declaration.CtConstructorImpl.class, fm -> fm + .field(CtRole.NAME, false, true) + .field(CtRole.TYPE, true, true) + .field(CtRole.IS_SHADOW, false, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.MODIFIER, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.PARAMETER, false, false) + .field(CtRole.THROWN, false, false) + .field(CtRole.TYPE_PARAMETER, false, false) + .field(CtRole.BODY, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtSuperAccess", spoon.reflect.code.CtSuperAccess.class, spoon.support.reflect.code.CtSuperAccessImpl.class, fm -> fm + .field(CtRole.TYPE, true, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.COMMENT, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.CAST, false, false) + .field(CtRole.TARGET, false, false) + .field(CtRole.VARIABLE, false, false) + + )); + + types.add(new Type("CtAnonymousExecutable", spoon.reflect.declaration.CtAnonymousExecutable.class, spoon.support.reflect.declaration.CtAnonymousExecutableImpl.class, fm -> fm + .field(CtRole.NAME, false, true) + .field(CtRole.TYPE, true, true) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.PARAMETER, true, true) + .field(CtRole.THROWN, true, true) + .field(CtRole.MODIFIER, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.BODY, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtComment", spoon.reflect.code.CtComment.class, spoon.support.reflect.code.CtCommentImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.LABEL, false, false) + .field(CtRole.COMMENT_CONTENT, false, false) + .field(CtRole.COMMENT_TYPE, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.COMMENT, false, false) + .field(CtRole.ANNOTATION, false, false) + + )); + + types.add(new Type("CtWildcardReference", spoon.reflect.reference.CtWildcardReference.class, spoon.support.reflect.reference.CtWildcardReferenceImpl.class, fm -> fm + .field(CtRole.NAME, false, true) + .field(CtRole.IS_SHADOW, false, false) + .field(CtRole.IS_UPPER, false, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.MODIFIER, false, true) + .field(CtRole.COMMENT, false, true) + .field(CtRole.INTERFACE, true, true) + .field(CtRole.SUPER_TYPE, true, true) + .field(CtRole.TYPE_ARGUMENT, true, true) + .field(CtRole.POSITION, false, false) + .field(CtRole.PACKAGE_REF, false, false) + .field(CtRole.DECLARING_TYPE, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.BOUNDING_TYPE, false, false) + + )); + + types.add(new Type("CtThisAccess", spoon.reflect.code.CtThisAccess.class, spoon.support.reflect.code.CtThisAccessImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.COMMENT, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.TYPE, false, false) + .field(CtRole.CAST, false, false) + .field(CtRole.TARGET, false, false) + + )); + + types.add(new Type("CtArrayWrite", spoon.reflect.code.CtArrayWrite.class, spoon.support.reflect.code.CtArrayWriteImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.TYPE, false, false) + .field(CtRole.CAST, false, false) + .field(CtRole.TARGET, false, false) + .field(CtRole.EXPRESSION, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtPackageReference", spoon.reflect.reference.CtPackageReference.class, spoon.support.reflect.reference.CtPackageReferenceImpl.class, fm -> fm + .field(CtRole.NAME, false, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.COMMENT, false, true) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + + )); + + types.add(new Type("CtJavaDoc", spoon.reflect.code.CtJavaDoc.class, spoon.support.reflect.code.CtJavaDocImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.LABEL, false, false) + .field(CtRole.COMMENT_CONTENT, false, false) + .field(CtRole.COMMENT_TYPE, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.COMMENT, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.COMMENT_TAG, false, false) + + )); + + types.add(new Type("CtArrayRead", spoon.reflect.code.CtArrayRead.class, spoon.support.reflect.code.CtArrayReadImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.TYPE, false, false) + .field(CtRole.CAST, false, false) + .field(CtRole.TARGET, false, false) + .field(CtRole.EXPRESSION, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtStatementList", spoon.reflect.code.CtStatementList.class, spoon.support.reflect.code.CtStatementListImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.STATEMENT, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtVariableWrite", spoon.reflect.code.CtVariableWrite.class, spoon.support.reflect.code.CtVariableWriteImpl.class, fm -> fm + .field(CtRole.TYPE, true, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.CAST, false, false) + .field(CtRole.VARIABLE, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtParameterReference", spoon.reflect.reference.CtParameterReference.class, spoon.support.reflect.reference.CtParameterReferenceImpl.class, fm -> fm + .field(CtRole.NAME, false, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.COMMENT, false, true) + .field(CtRole.POSITION, false, false) + .field(CtRole.TYPE, false, false) + .field(CtRole.ANNOTATION, false, false) + + )); + + types.add(new Type("CtOperatorAssignment", spoon.reflect.code.CtOperatorAssignment.class, spoon.support.reflect.code.CtOperatorAssignmentImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.LABEL, false, false) + .field(CtRole.OPERATOR_KIND, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.TYPE, false, false) + .field(CtRole.CAST, false, false) + .field(CtRole.ASSIGNED, false, false) + .field(CtRole.ASSIGNMENT, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtAnnotationFieldAccess", spoon.reflect.code.CtAnnotationFieldAccess.class, spoon.support.reflect.code.CtAnnotationFieldAccessImpl.class, fm -> fm + .field(CtRole.TYPE, true, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.COMMENT, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.CAST, false, false) + .field(CtRole.TARGET, false, false) + .field(CtRole.VARIABLE, false, false) + + )); + + types.add(new Type("CtUnboundVariableReference", spoon.reflect.reference.CtUnboundVariableReference.class, spoon.support.reflect.reference.CtUnboundVariableReferenceImpl.class, fm -> fm + .field(CtRole.NAME, false, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.COMMENT, false, true) + .field(CtRole.ANNOTATION, true, true) + .field(CtRole.POSITION, false, false) + .field(CtRole.TYPE, false, false) + + )); + + types.add(new Type("CtAnnotationMethod", spoon.reflect.declaration.CtAnnotationMethod.class, spoon.support.reflect.declaration.CtAnnotationMethodImpl.class, fm -> fm + .field(CtRole.NAME, false, false) + .field(CtRole.BODY, true, true) + .field(CtRole.IS_SHADOW, false, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.IS_DEFAULT, false, false) + .field(CtRole.PARAMETER, true, true) + .field(CtRole.THROWN, true, true) + .field(CtRole.MODIFIER, false, false) + .field(CtRole.TYPE_PARAMETER, true, true) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.TYPE, false, false) + .field(CtRole.DEFAULT_EXPRESSION, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtClass", spoon.reflect.declaration.CtClass.class, spoon.support.reflect.declaration.CtClassImpl.class, fm -> fm + .field(CtRole.NAME, false, false) + .field(CtRole.IS_SHADOW, false, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.LABEL, false, true) + .field(CtRole.MODIFIER, false, false) + .field(CtRole.NESTED_TYPE, true, false) + .field(CtRole.CONSTRUCTOR, true, false) + .field(CtRole.METHOD, true, false) + .field(CtRole.ANNONYMOUS_EXECUTABLE, true, false) + .field(CtRole.FIELD, true, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.SUPER_TYPE, false, false) + .field(CtRole.INTERFACE, false, false) + .field(CtRole.TYPE_PARAMETER, false, false) + .field(CtRole.TYPE_MEMBER, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtBlock", spoon.reflect.code.CtBlock.class, spoon.support.reflect.code.CtBlockImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.LABEL, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.STATEMENT, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtPackage", spoon.reflect.declaration.CtPackage.class, spoon.support.reflect.declaration.CtPackageImpl.class, fm -> fm + .field(CtRole.NAME, false, false) + .field(CtRole.IS_SHADOW, false, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.SUB_PACKAGE, false, false) + .field(CtRole.CONTAINED_TYPE, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtTryWithResource", spoon.reflect.code.CtTryWithResource.class, spoon.support.reflect.code.CtTryWithResourceImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.LABEL, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.TRY_RESOURCE, false, false) + .field(CtRole.BODY, false, false) + .field(CtRole.CATCH, false, false) + .field(CtRole.FINALIZER, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtAssert", spoon.reflect.code.CtAssert.class, spoon.support.reflect.code.CtAssertImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.LABEL, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.CONDITION, false, false) + .field(CtRole.EXPRESSION, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtSwitch", spoon.reflect.code.CtSwitch.class, spoon.support.reflect.code.CtSwitchImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.LABEL, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.EXPRESSION, false, false) + .field(CtRole.CASE, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtTry", spoon.reflect.code.CtTry.class, spoon.support.reflect.code.CtTryImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.LABEL, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.BODY, false, false) + .field(CtRole.CATCH, false, false) + .field(CtRole.FINALIZER, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtSynchronized", spoon.reflect.code.CtSynchronized.class, spoon.support.reflect.code.CtSynchronizedImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.LABEL, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.EXPRESSION, false, false) + .field(CtRole.BODY, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtImport", spoon.reflect.declaration.CtImport.class, spoon.support.reflect.declaration.CtImportImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.IMPORT_REFERENCE, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtTypeParameterReference", spoon.reflect.reference.CtTypeParameterReference.class, spoon.support.reflect.reference.CtTypeParameterReferenceImpl.class, fm -> fm + .field(CtRole.NAME, false, false) + .field(CtRole.IS_SHADOW, false, false) + .field(CtRole.IS_UPPER, false, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.MODIFIER, false, true) + .field(CtRole.COMMENT, false, true) + .field(CtRole.INTERFACE, true, true) + .field(CtRole.SUPER_TYPE, true, true) + .field(CtRole.TYPE_ARGUMENT, true, true) + .field(CtRole.POSITION, false, false) + .field(CtRole.PACKAGE_REF, false, false) + .field(CtRole.DECLARING_TYPE, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.BOUNDING_TYPE, false, false) + + )); + + types.add(new Type("CtInvocation", spoon.reflect.code.CtInvocation.class, spoon.support.reflect.code.CtInvocationImpl.class, fm -> fm + .field(CtRole.TYPE, true, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.LABEL, false, false) + .field(CtRole.TYPE_ARGUMENT, true, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.CAST, false, false) + .field(CtRole.TARGET, false, false) + .field(CtRole.EXECUTABLE_REF, false, false) + .field(CtRole.ARGUMENT, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtCodeSnippetExpression", spoon.reflect.code.CtCodeSnippetExpression.class, spoon.support.reflect.code.CtCodeSnippetExpressionImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.SNIPPET, false, false) + .field(CtRole.TYPE, false, false) + .field(CtRole.COMMENT, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.CAST, false, false) + + )); + + types.add(new Type("CtFieldWrite", spoon.reflect.code.CtFieldWrite.class, spoon.support.reflect.code.CtFieldWriteImpl.class, fm -> fm + .field(CtRole.TYPE, true, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.CAST, false, false) + .field(CtRole.TARGET, false, false) + .field(CtRole.VARIABLE, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtUnaryOperator", spoon.reflect.code.CtUnaryOperator.class, spoon.support.reflect.code.CtUnaryOperatorImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.LABEL, false, false) + .field(CtRole.OPERATOR_KIND, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.TYPE, false, false) + .field(CtRole.CAST, false, false) + .field(CtRole.EXPRESSION, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtExecutableReference", spoon.reflect.reference.CtExecutableReference.class, spoon.support.reflect.reference.CtExecutableReferenceImpl.class, fm -> fm + .field(CtRole.NAME, false, false) + .field(CtRole.IS_STATIC, false, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.DECLARING_TYPE, false, false) + .field(CtRole.TYPE, false, false) + .field(CtRole.ARGUMENT_TYPE, false, false) + .field(CtRole.TYPE_ARGUMENT, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.COMMENT, false, true) + + )); + + types.add(new Type("CtFor", spoon.reflect.code.CtFor.class, spoon.support.reflect.code.CtForImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.LABEL, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.FOR_INIT, false, false) + .field(CtRole.EXPRESSION, false, false) + .field(CtRole.FOR_UPDATE, false, false) + .field(CtRole.BODY, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtVariableRead", spoon.reflect.code.CtVariableRead.class, spoon.support.reflect.code.CtVariableReadImpl.class, fm -> fm + .field(CtRole.TYPE, true, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.CAST, false, false) + .field(CtRole.VARIABLE, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtTypeParameter", spoon.reflect.declaration.CtTypeParameter.class, spoon.support.reflect.declaration.CtTypeParameterImpl.class, fm -> fm + .field(CtRole.NAME, false, false) + .field(CtRole.IS_SHADOW, false, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.MODIFIER, false, true) + .field(CtRole.INTERFACE, true, true) + .field(CtRole.TYPE_MEMBER, true, true) + .field(CtRole.NESTED_TYPE, true, true) + .field(CtRole.METHOD, true, true) + .field(CtRole.FIELD, true, true) + .field(CtRole.TYPE_PARAMETER, true, true) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.SUPER_TYPE, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtLocalVariable", spoon.reflect.code.CtLocalVariable.class, spoon.support.reflect.code.CtLocalVariableImpl.class, fm -> fm + .field(CtRole.NAME, false, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.LABEL, false, false) + .field(CtRole.ASSIGNMENT, true, true) + .field(CtRole.MODIFIER, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.TYPE, false, false) + .field(CtRole.DEFAULT_EXPRESSION, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtIf", spoon.reflect.code.CtIf.class, spoon.support.reflect.code.CtIfImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.LABEL, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.CONDITION, false, false) + .field(CtRole.THEN, false, false) + .field(CtRole.ELSE, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtModule", spoon.reflect.declaration.CtModule.class, spoon.support.reflect.declaration.CtModuleImpl.class, fm -> fm + .field(CtRole.NAME, false, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.MODIFIER, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.REQUIRED_MODULE, true, false) + .field(CtRole.EXPORTED_PACKAGE, true, false) + .field(CtRole.OPENED_PACKAGE, true, false) + .field(CtRole.SERVICE_TYPE, true, false) + .field(CtRole.PROVIDED_SERVICE, true, false) + .field(CtRole.COMMENT, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.MODULE_DIRECTIVE, false, false) + .field(CtRole.SUB_PACKAGE, false, false) + + )); + + types.add(new Type("CtPackageExport", spoon.reflect.declaration.CtPackageExport.class, spoon.support.reflect.declaration.CtPackageExportImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.OPENED_PACKAGE, false, false) + .field(CtRole.COMMENT, false, false) + .field(CtRole.PACKAGE_REF, false, false) + .field(CtRole.MODULE_REF, false, false) + .field(CtRole.ANNOTATION, false, false) + + )); + + types.add(new Type("CtConstructorCall", spoon.reflect.code.CtConstructorCall.class, spoon.support.reflect.code.CtConstructorCallImpl.class, fm -> fm + .field(CtRole.TYPE, true, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.LABEL, false, false) + .field(CtRole.TYPE_ARGUMENT, true, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.CAST, false, false) + .field(CtRole.EXECUTABLE_REF, false, false) + .field(CtRole.TARGET, false, false) + .field(CtRole.ARGUMENT, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtCase", spoon.reflect.code.CtCase.class, spoon.support.reflect.code.CtCaseImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.LABEL, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.EXPRESSION, false, false) + .field(CtRole.STATEMENT, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtModuleReference", spoon.reflect.reference.CtModuleReference.class, spoon.support.reflect.reference.CtModuleReferenceImpl.class, fm -> fm + .field(CtRole.NAME, false, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.COMMENT, false, true) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + + )); + + types.add(new Type("CtCatch", spoon.reflect.code.CtCatch.class, spoon.support.reflect.code.CtCatchImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.PARAMETER, false, false) + .field(CtRole.BODY, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtArrayTypeReference", spoon.reflect.reference.CtArrayTypeReference.class, spoon.support.reflect.reference.CtArrayTypeReferenceImpl.class, fm -> fm + .field(CtRole.NAME, false, false) + .field(CtRole.IS_SHADOW, false, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.MODIFIER, false, true) + .field(CtRole.INTERFACE, true, true) + .field(CtRole.SUPER_TYPE, true, true) + .field(CtRole.POSITION, false, false) + .field(CtRole.COMMENT, false, true) + .field(CtRole.PACKAGE_REF, false, false) + .field(CtRole.DECLARING_TYPE, false, false) + .field(CtRole.TYPE, false, false) + .field(CtRole.TYPE_ARGUMENT, false, false) + .field(CtRole.ANNOTATION, false, false) + + )); + + types.add(new Type("CtMethod", spoon.reflect.declaration.CtMethod.class, spoon.support.reflect.declaration.CtMethodImpl.class, fm -> fm + .field(CtRole.NAME, false, false) + .field(CtRole.IS_SHADOW, false, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.IS_DEFAULT, false, false) + .field(CtRole.MODIFIER, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.TYPE_PARAMETER, false, false) + .field(CtRole.TYPE, false, false) + .field(CtRole.PARAMETER, false, false) + .field(CtRole.THROWN, false, false) + .field(CtRole.BODY, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtLambda", spoon.reflect.code.CtLambda.class, spoon.support.reflect.code.CtLambdaImpl.class, fm -> fm + .field(CtRole.NAME, false, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.TYPE, false, false) + .field(CtRole.CAST, false, false) + .field(CtRole.PARAMETER, false, false) + .field(CtRole.THROWN, false, true) + .field(CtRole.BODY, false, false) + .field(CtRole.EXPRESSION, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtNewArray", spoon.reflect.code.CtNewArray.class, spoon.support.reflect.code.CtNewArrayImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.TYPE, false, false) + .field(CtRole.CAST, false, false) + .field(CtRole.EXPRESSION, false, false) + .field(CtRole.DIMENSION, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtUsedService", spoon.reflect.declaration.CtUsedService.class, spoon.support.reflect.declaration.CtUsedServiceImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.COMMENT, false, false) + .field(CtRole.SERVICE_TYPE, false, false) + .field(CtRole.ANNOTATION, false, false) + + )); + + types.add(new Type("CtIntersectionTypeReference", spoon.reflect.reference.CtIntersectionTypeReference.class, spoon.support.reflect.reference.CtIntersectionTypeReferenceImpl.class, fm -> fm + .field(CtRole.NAME, false, false) + .field(CtRole.IS_SHADOW, false, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.MODIFIER, false, true) + .field(CtRole.COMMENT, false, true) + .field(CtRole.INTERFACE, true, true) + .field(CtRole.SUPER_TYPE, true, true) + .field(CtRole.POSITION, false, false) + .field(CtRole.PACKAGE_REF, false, false) + .field(CtRole.DECLARING_TYPE, false, false) + .field(CtRole.TYPE_ARGUMENT, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.BOUND, false, false) + + )); + + types.add(new Type("CtThrow", spoon.reflect.code.CtThrow.class, spoon.support.reflect.code.CtThrowImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.LABEL, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.EXPRESSION, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtLiteral", spoon.reflect.code.CtLiteral.class, spoon.support.reflect.code.CtLiteralImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.VALUE, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.TYPE, false, false) + .field(CtRole.CAST, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtReturn", spoon.reflect.code.CtReturn.class, spoon.support.reflect.code.CtReturnImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.LABEL, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.EXPRESSION, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtJavaDocTag", spoon.reflect.code.CtJavaDocTag.class, spoon.support.reflect.code.CtJavaDocTagImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.COMMENT_CONTENT, false, false) + .field(CtRole.DOCUMENTATION_TYPE, false, false) + .field(CtRole.JAVADOC_TAG_VALUE, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.COMMENT, false, false) + .field(CtRole.ANNOTATION, false, false) + + )); + + types.add(new Type("CtField", spoon.reflect.declaration.CtField.class, spoon.support.reflect.declaration.CtFieldImpl.class, fm -> fm + .field(CtRole.NAME, false, false) + .field(CtRole.IS_SHADOW, false, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.ASSIGNMENT, true, true) + .field(CtRole.MODIFIER, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.TYPE, false, false) + .field(CtRole.DEFAULT_EXPRESSION, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtTypeAccess", spoon.reflect.code.CtTypeAccess.class, spoon.support.reflect.code.CtTypeAccessImpl.class, fm -> fm + .field(CtRole.TYPE, true, true) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.CAST, false, false) + .field(CtRole.ACCESSED_TYPE, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtCodeSnippetStatement", spoon.reflect.code.CtCodeSnippetStatement.class, spoon.support.reflect.code.CtCodeSnippetStatementImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.LABEL, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.SNIPPET, false, false) + .field(CtRole.COMMENT, false, false) + .field(CtRole.ANNOTATION, false, false) + + )); + + types.add(new Type("CtDo", spoon.reflect.code.CtDo.class, spoon.support.reflect.code.CtDoImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.LABEL, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.EXPRESSION, false, false) + .field(CtRole.BODY, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtAnnotation", spoon.reflect.declaration.CtAnnotation.class, spoon.support.reflect.declaration.CtAnnotationImpl.class, fm -> fm + .field(CtRole.IS_SHADOW, false, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.CAST, true, true) + .field(CtRole.POSITION, false, false) + .field(CtRole.TYPE, false, false) + .field(CtRole.COMMENT, false, false) + .field(CtRole.ANNOTATION_TYPE, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.VALUE, false, false) + + )); + + types.add(new Type("CtFieldRead", spoon.reflect.code.CtFieldRead.class, spoon.support.reflect.code.CtFieldReadImpl.class, fm -> fm + .field(CtRole.TYPE, true, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.CAST, false, false) + .field(CtRole.TARGET, false, false) + .field(CtRole.VARIABLE, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtBreak", spoon.reflect.code.CtBreak.class, spoon.support.reflect.code.CtBreakImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.LABEL, false, false) + .field(CtRole.TARGET_LABEL, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtFieldReference", spoon.reflect.reference.CtFieldReference.class, spoon.support.reflect.reference.CtFieldReferenceImpl.class, fm -> fm + .field(CtRole.NAME, false, false) + .field(CtRole.IS_FINAL, false, false) + .field(CtRole.IS_STATIC, false, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.COMMENT, false, true) + .field(CtRole.POSITION, false, false) + .field(CtRole.DECLARING_TYPE, false, false) + .field(CtRole.TYPE, false, false) + .field(CtRole.ANNOTATION, false, false) + + )); + + types.add(new Type("CtEnum", spoon.reflect.declaration.CtEnum.class, spoon.support.reflect.declaration.CtEnumImpl.class, fm -> fm + .field(CtRole.NAME, false, false) + .field(CtRole.IS_SHADOW, false, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.LABEL, false, true) + .field(CtRole.MODIFIER, false, false) + .field(CtRole.SUPER_TYPE, true, true) + .field(CtRole.NESTED_TYPE, true, false) + .field(CtRole.CONSTRUCTOR, true, false) + .field(CtRole.METHOD, true, false) + .field(CtRole.ANNONYMOUS_EXECUTABLE, true, false) + .field(CtRole.FIELD, true, false) + .field(CtRole.TYPE_PARAMETER, true, true) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.INTERFACE, false, false) + .field(CtRole.TYPE_MEMBER, false, false) + .field(CtRole.VALUE, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtNewClass", spoon.reflect.code.CtNewClass.class, spoon.support.reflect.code.CtNewClassImpl.class, fm -> fm + .field(CtRole.TYPE, true, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.LABEL, false, false) + .field(CtRole.TYPE_ARGUMENT, true, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.CAST, false, false) + .field(CtRole.EXECUTABLE_REF, false, false) + .field(CtRole.TARGET, false, false) + .field(CtRole.ARGUMENT, false, false) + .field(CtRole.NESTED_TYPE, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtLocalVariableReference", spoon.reflect.reference.CtLocalVariableReference.class, spoon.support.reflect.reference.CtLocalVariableReferenceImpl.class, fm -> fm + .field(CtRole.NAME, false, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.COMMENT, false, true) + .field(CtRole.POSITION, false, false) + .field(CtRole.TYPE, false, false) + .field(CtRole.ANNOTATION, false, false) + + )); + + types.add(new Type("CtAnnotationType", spoon.reflect.declaration.CtAnnotationType.class, spoon.support.reflect.declaration.CtAnnotationTypeImpl.class, fm -> fm + .field(CtRole.NAME, false, false) + .field(CtRole.IS_SHADOW, false, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.MODIFIER, false, false) + .field(CtRole.INTERFACE, true, true) + .field(CtRole.SUPER_TYPE, true, true) + .field(CtRole.NESTED_TYPE, true, false) + .field(CtRole.METHOD, true, false) + .field(CtRole.FIELD, true, false) + .field(CtRole.TYPE_PARAMETER, true, true) + .field(CtRole.POSITION, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.TYPE_MEMBER, false, false) + .field(CtRole.COMMENT, false, false) + + )); + + types.add(new Type("CtCatchVariable", spoon.reflect.code.CtCatchVariable.class, spoon.support.reflect.code.CtCatchVariableImpl.class, fm -> fm + .field(CtRole.NAME, false, false) + .field(CtRole.TYPE, true, false) + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.DEFAULT_EXPRESSION, true, true) + .field(CtRole.MODIFIER, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.COMMENT, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.MULTI_TYPE, false, false) + + )); + + types.add(new Type("CtExecutableReferenceExpression", spoon.reflect.code.CtExecutableReferenceExpression.class, spoon.support.reflect.code.CtExecutableReferenceExpressionImpl.class, fm -> fm + .field(CtRole.IS_IMPLICIT, false, false) + .field(CtRole.POSITION, false, false) + .field(CtRole.COMMENT, false, false) + .field(CtRole.ANNOTATION, false, false) + .field(CtRole.TYPE, false, false) + .field(CtRole.CAST, false, false) + .field(CtRole.EXECUTABLE_REF, false, false) + .field(CtRole.TARGET, false, false) + + )); + } } diff --git a/src/test/java/spoon/generating/MetamodelGenerator.java b/src/test/java/spoon/generating/MetamodelGenerator.java new file mode 100644 index 00000000000..1feafc8aa5c --- /dev/null +++ b/src/test/java/spoon/generating/MetamodelGenerator.java @@ -0,0 +1,115 @@ +package spoon.generating; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.text.StrSubstitutor; + +import spoon.Metamodel.Type; +import spoon.SpoonException; +import spoon.reflect.declaration.CtClass; +import spoon.reflect.declaration.CtElement; +import spoon.reflect.factory.Factory; +import spoon.reflect.path.CtRole; +import spoon.reflect.visitor.CtScanner; +import spoon.test.metamodel.MMField; +import spoon.test.metamodel.MMType; +import spoon.test.metamodel.MMTypeKind; +import spoon.test.metamodel.SpoonMetaModel; + +public class MetamodelGenerator { + + public static void main(String[] args) { + SpoonMetaModel mm = new SpoonMetaModel(new File("src/main/java")); + mm.getFactory().getEnvironment().useTabulations(true); + StringBuilder sb = new StringBuilder(); + for (MMType type : mm.getMMTypes()) { + if (type.getKind()==MMTypeKind.LEAF) { + sb.append(printType(mm.getFactory(), type)); + } + } + System.out.println(sb.toString()); + } + + + private static String printType(Factory factory, MMType type) { + Map valuesMap = new HashMap<>(); + valuesMap.put("typeName", type.getName()); + valuesMap.put("ifaceName", type.getModelInterface().getQualifiedName()); + valuesMap.put("implName", type.getModelClass().getQualifiedName()); + valuesMap.put("fields", printFields(factory, type)); + + StrSubstitutor strSubst = new StrSubstitutor(valuesMap); + return strSubst.replace( + "types.add(new Type(\"${typeName}\", ${ifaceName}.class, ${implName}.class, fm -> fm\n" + + "${fields}\n" + + "));\n\n"); + + } + + private static String printFields(Factory factory, MMType type) { + Map allFields = new LinkedHashMap<>(type.getRole2field()); + List rolesByScanner = getRoleScanningOrderOfType(factory, (Class) type.getModelInterface().getActualClass()); + List elementFields = new ArrayList<>(); + for (CtRole ctRole : rolesByScanner) { + MMField field = allFields.remove(ctRole); + elementFields.add(printField(field)); + } + //generate remaining primitive fields, sorted by Enum#ordinal of CtRole - just to have a stable order + List primitiveFields = new ArrayList<>(); + new ArrayList(allFields.keySet()).stream().sorted().forEach(role -> { + MMField field = allFields.remove(role); + primitiveFields.add(printField(field)); + }); + if (allFields.isEmpty() == false) { + throw new SpoonException("There remained some fields?"); + } + StringBuilder sb = new StringBuilder(); + primitiveFields.addAll(elementFields); + primitiveFields.forEach(s->sb.append(s).append('\n')); + return sb.toString(); + } + + private static String printField(MMField field) { + Map valuesMap = new HashMap<>(); + valuesMap.put("role", field.getRole().name()); + valuesMap.put("derived", String.valueOf(field.isDerived())); + valuesMap.put("unsetable", String.valueOf(field.isUnsettable())); + + StrSubstitutor strSubst = new StrSubstitutor(valuesMap); + return strSubst.replace("\t.field(CtRole.${role}, ${derived}, ${unsetable})"); + } + + + private static List getRoleScanningOrderOfType(Factory factory, Class iface) { + List roles = new ArrayList<>(); + //generate fields in the same order like they are visited in CtScanner + CtElement ele = factory.Core().create(iface); + ele.accept(new CtScanner() { + @Override + public void scan(CtRole role, CtElement element) { + roles.add(role); + } + @Override + public void scan(CtRole role, Collection elements) { + roles.add(role); + } + @Override + public void scan(CtRole role, Map elements) { + roles.add(role); + } + }); + return roles; + } + +/* + types.add(new Type("CtClass", CtClassImpl.class, CtClass.class, fm -> + fm.field(CtRole.NAME, false, false) + )); +*/ +} diff --git a/src/test/java/spoon/test/api/MetamodelTest.java b/src/test/java/spoon/test/api/MetamodelTest.java index 1b850a412a5..a52959c5dc2 100644 --- a/src/test/java/spoon/test/api/MetamodelTest.java +++ b/src/test/java/spoon/test/api/MetamodelTest.java @@ -26,16 +26,26 @@ import spoon.reflect.visitor.filter.AnnotationFilter; import spoon.reflect.visitor.filter.SuperInheritanceHierarchyFunction; import spoon.reflect.visitor.filter.TypeFilter; +import spoon.test.metamodel.MMField; +import spoon.test.metamodel.MMType; +import spoon.test.metamodel.MMTypeKind; +import spoon.test.metamodel.SpoonMetaModel; +import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.IsEqual.equalTo; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; public class MetamodelTest { @@ -50,6 +60,30 @@ public void testGetAllMetamodelInterfacess() { assertThat(Metamodel.getAllMetamodelInterfaces().stream().map(x->x.getQualifiedName()).collect(Collectors.toSet()), equalTo(interfaces.getModel().getAllTypes().stream().map(x->x.getQualifiedName()).collect(Collectors.toSet()))); } + @Test + public void testRuntimeMetamodel() { + // contract: Spoon supports runtime introspection on the metamodel - all (non abstract) Spoon classes and their fields are accessible by Metamodel + SpoonMetaModel testMetaModel = new SpoonMetaModel(new File("src/main/java")); + Map expectedTypesByName = new HashMap<>(); + testMetaModel.getMMTypes().forEach(t -> { + if (t.getKind() == MMTypeKind.LEAF) { + expectedTypesByName.put(t.getName(), t); + } + }); + for (Metamodel.Type type : Metamodel.getAllMetamodelTypes()) { + MMType expectedType = expectedTypesByName.remove(type.getName()); + assertSame(expectedType.getModelClass().getActualClass(), type.getModelClass()); + assertSame(expectedType.getModelInterface().getActualClass(), type.getModelInterface()); + Map expectedRoleToField = new HashMap<>(expectedType.getRole2field()); + for (Metamodel.Field field : type.getFields()) { + MMField expectedField = expectedRoleToField.remove(field.getRole()); + assertSame(expectedField.isDerived(), field.isDerived()); + assertSame(expectedField.isUnsettable(), field.isUnsettable()); + } + assertTrue("These Metamodel.Field instances are missing on Type " + type.getName() +": " + expectedRoleToField.keySet(), expectedRoleToField.isEmpty()); + } + assertTrue("These Metamodel.Type instances are missing: " + expectedTypesByName.keySet(), expectedTypesByName.isEmpty()); + } @Test @@ -162,4 +196,6 @@ public boolean matches(CtField candidate) { } } + + } From 274efacbee53ac3c2e73c5a0d7a9410540ac2353 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Wed, 7 Mar 2018 10:38:38 +0100 Subject: [PATCH 002/131] rename MMType, MMField - runtime metamodel --- src/main/java/spoon/Metamodel.java | 4 ++-- .../spoon/generating/MetamodelGenerator.java | 18 +++++++++--------- .../java/spoon/test/api/MetamodelTest.java | 14 +++++++------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/main/java/spoon/Metamodel.java b/src/main/java/spoon/Metamodel.java index 7bda0513a56..32e42d45d88 100644 --- a/src/main/java/spoon/Metamodel.java +++ b/src/main/java/spoon/Metamodel.java @@ -197,11 +197,11 @@ public static class Type { private final String name; /** - * The {@link CtClass} linked to this {@link MMType}. Is null in case of class without interface + * The {@link CtClass} linked to this {@link MetamodelConcept}. Is null in case of class without interface */ private final Class modelClass; /** - * The {@link CtInterface} linked to this {@link MMType}. Is null in case of interface without class + * The {@link CtInterface} linked to this {@link MetamodelConcept}. Is null in case of interface without class */ private final Class modelInterface; diff --git a/src/test/java/spoon/generating/MetamodelGenerator.java b/src/test/java/spoon/generating/MetamodelGenerator.java index 1feafc8aa5c..79beb980fd5 100644 --- a/src/test/java/spoon/generating/MetamodelGenerator.java +++ b/src/test/java/spoon/generating/MetamodelGenerator.java @@ -17,8 +17,8 @@ import spoon.reflect.factory.Factory; import spoon.reflect.path.CtRole; import spoon.reflect.visitor.CtScanner; -import spoon.test.metamodel.MMField; -import spoon.test.metamodel.MMType; +import spoon.test.metamodel.MetamodelProperty; +import spoon.test.metamodel.MetamodelConcept; import spoon.test.metamodel.MMTypeKind; import spoon.test.metamodel.SpoonMetaModel; @@ -28,7 +28,7 @@ public static void main(String[] args) { SpoonMetaModel mm = new SpoonMetaModel(new File("src/main/java")); mm.getFactory().getEnvironment().useTabulations(true); StringBuilder sb = new StringBuilder(); - for (MMType type : mm.getMMTypes()) { + for (MetamodelConcept type : mm.getConcepts()) { if (type.getKind()==MMTypeKind.LEAF) { sb.append(printType(mm.getFactory(), type)); } @@ -37,7 +37,7 @@ public static void main(String[] args) { } - private static String printType(Factory factory, MMType type) { + private static String printType(Factory factory, MetamodelConcept type) { Map valuesMap = new HashMap<>(); valuesMap.put("typeName", type.getName()); valuesMap.put("ifaceName", type.getModelInterface().getQualifiedName()); @@ -52,18 +52,18 @@ private static String printType(Factory factory, MMType type) { } - private static String printFields(Factory factory, MMType type) { - Map allFields = new LinkedHashMap<>(type.getRole2field()); + private static String printFields(Factory factory, MetamodelConcept type) { + Map allFields = new LinkedHashMap<>(type.getRoleToProperty()); List rolesByScanner = getRoleScanningOrderOfType(factory, (Class) type.getModelInterface().getActualClass()); List elementFields = new ArrayList<>(); for (CtRole ctRole : rolesByScanner) { - MMField field = allFields.remove(ctRole); + MetamodelProperty field = allFields.remove(ctRole); elementFields.add(printField(field)); } //generate remaining primitive fields, sorted by Enum#ordinal of CtRole - just to have a stable order List primitiveFields = new ArrayList<>(); new ArrayList(allFields.keySet()).stream().sorted().forEach(role -> { - MMField field = allFields.remove(role); + MetamodelProperty field = allFields.remove(role); primitiveFields.add(printField(field)); }); if (allFields.isEmpty() == false) { @@ -75,7 +75,7 @@ private static String printFields(Factory factory, MMType type) { return sb.toString(); } - private static String printField(MMField field) { + private static String printField(MetamodelProperty field) { Map valuesMap = new HashMap<>(); valuesMap.put("role", field.getRole().name()); valuesMap.put("derived", String.valueOf(field.isDerived())); diff --git a/src/test/java/spoon/test/api/MetamodelTest.java b/src/test/java/spoon/test/api/MetamodelTest.java index a52959c5dc2..c42bebc2512 100644 --- a/src/test/java/spoon/test/api/MetamodelTest.java +++ b/src/test/java/spoon/test/api/MetamodelTest.java @@ -26,8 +26,8 @@ import spoon.reflect.visitor.filter.AnnotationFilter; import spoon.reflect.visitor.filter.SuperInheritanceHierarchyFunction; import spoon.reflect.visitor.filter.TypeFilter; -import spoon.test.metamodel.MMField; -import spoon.test.metamodel.MMType; +import spoon.test.metamodel.MetamodelProperty; +import spoon.test.metamodel.MetamodelConcept; import spoon.test.metamodel.MMTypeKind; import spoon.test.metamodel.SpoonMetaModel; @@ -64,19 +64,19 @@ public void testGetAllMetamodelInterfacess() { public void testRuntimeMetamodel() { // contract: Spoon supports runtime introspection on the metamodel - all (non abstract) Spoon classes and their fields are accessible by Metamodel SpoonMetaModel testMetaModel = new SpoonMetaModel(new File("src/main/java")); - Map expectedTypesByName = new HashMap<>(); - testMetaModel.getMMTypes().forEach(t -> { + Map expectedTypesByName = new HashMap<>(); + testMetaModel.getConcepts().forEach(t -> { if (t.getKind() == MMTypeKind.LEAF) { expectedTypesByName.put(t.getName(), t); } }); for (Metamodel.Type type : Metamodel.getAllMetamodelTypes()) { - MMType expectedType = expectedTypesByName.remove(type.getName()); + MetamodelConcept expectedType = expectedTypesByName.remove(type.getName()); assertSame(expectedType.getModelClass().getActualClass(), type.getModelClass()); assertSame(expectedType.getModelInterface().getActualClass(), type.getModelInterface()); - Map expectedRoleToField = new HashMap<>(expectedType.getRole2field()); + Map expectedRoleToField = new HashMap<>(expectedType.getRoleToProperty()); for (Metamodel.Field field : type.getFields()) { - MMField expectedField = expectedRoleToField.remove(field.getRole()); + MetamodelProperty expectedField = expectedRoleToField.remove(field.getRole()); assertSame(expectedField.isDerived(), field.isDerived()); assertSame(expectedField.isUnsettable(), field.isUnsettable()); } From 5c4f60562121041f4d9cfc3acbc493c1ae776f6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Wed, 7 Mar 2018 09:55:42 +0100 Subject: [PATCH 003/131] Pattern --- src/main/java/spoon/Metamodel.java | 54 +- .../spoon/pattern/AbstractItemAccessor.java | 424 ++++++++ .../pattern/AbstractPrimitiveMatcher.java | 70 ++ .../pattern/AbstractRepeatableMatcher.java | 124 +++ .../spoon/pattern/ConflictResolutionMode.java | 37 + src/main/java/spoon/pattern/ConstantNode.java | 66 ++ src/main/java/spoon/pattern/ElementNode.java | 194 ++++ src/main/java/spoon/pattern/ForEachNode.java | 139 +++ src/main/java/spoon/pattern/ListAccessor.java | 117 +++ src/main/java/spoon/pattern/ListOfNodes.java | 82 ++ .../spoon/pattern/LiveStatementsBuilder.java | 229 +++++ src/main/java/spoon/pattern/ModelNode.java | 164 +++ .../java/spoon/pattern/NamedItemAccessor.java | 135 +++ src/main/java/spoon/pattern/Node.java | 114 +++ .../java/spoon/pattern/ParameterInfo.java | 85 ++ .../java/spoon/pattern/ParameterNode.java | 86 ++ .../spoon/pattern/ParameterValueProvider.java | 40 + .../ParameterValueProviderFactory.java | 24 + .../java/spoon/pattern/ParametersBuilder.java | 694 +++++++++++++ src/main/java/spoon/pattern/Pattern.java | 266 +++++ .../java/spoon/pattern/PatternBuilder.java | 935 ++++++++++++++++++ .../java/spoon/pattern/PrimitiveMatcher.java | 30 + .../java/spoon/pattern/RepeatableMatcher.java | 56 ++ src/main/java/spoon/pattern/ResultHolder.java | 147 +++ src/main/java/spoon/pattern/SetAccessor.java | 91 ++ src/main/java/spoon/pattern/StringNode.java | 237 +++++ .../spoon/pattern/SubstitutionCloner.java | 257 +++++ .../pattern/SubstitutionRequestProvider.java | 28 + src/main/java/spoon/pattern/SwitchNode.java | 185 ++++ .../java/spoon/pattern/TemplateBuilder.java | 140 +++ .../UnmodifiableParameterValueProvider.java | 145 +++ .../java/spoon/pattern/ValueConvertor.java | 25 + .../spoon/pattern/ValueConvertorImpl.java | 171 ++++ src/main/java/spoon/pattern/concept.md | 189 ++++ .../pattern/matcher/ChainOfMatchersImpl.java | 68 ++ .../spoon/pattern/matcher/MapEntryNode.java | 69 ++ .../java/spoon/pattern/matcher/Match.java | 116 +++ .../java/spoon/pattern/matcher/Matchers.java | 32 + .../pattern/matcher/MatchingScanner.java | 139 +++ .../pattern/matcher/NodeListMatcher.java | 38 + .../spoon/pattern/matcher/Quantifier.java | 44 + .../spoon/pattern/matcher/TobeMatched.java | 235 +++++ .../reflect/reference/CtTypeReference.java | 5 + .../spoon/support/template/Parameters.java | 31 +- .../support/template/SubstitutionVisitor.java | 1 + .../java/spoon/template/AbstractTemplate.java | 5 +- .../java/spoon/template/BlockTemplate.java | 3 +- .../spoon/template/ExpressionTemplate.java | 8 +- .../spoon/template/StatementTemplate.java | 22 +- .../java/spoon/template/Substitution.java | 40 +- .../java/spoon/template/TemplateMatcher.java | 701 ++----------- src/test/java/spoon/MavenLauncherTest.java | 2 +- .../SpoonArchitectureEnforcerTest.java | 2 + .../spoon/test/template/CodeReplaceTest.java | 83 ++ .../java/spoon/test/template/PatternTest.java | 59 ++ .../test/template/TemplateMatcherTest.java | 835 ++++++++++++++++ .../spoon/test/template/TemplateTest.java | 200 +++- .../test/template/core/ParameterInfoTest.java | 433 ++++++++ .../template/testclasses/ToBeMatched.java | 25 + .../testclasses/bounds/CheckBound.java | 5 + .../testclasses/bounds/CheckBoundMatcher.java | 4 + .../template/testclasses/match/Check.java | 6 + .../testclasses/match/MatchForEach.java | 37 + .../testclasses/match/MatchForEach2.java | 44 + .../testclasses/match/MatchIfElse.java | 49 + .../template/testclasses/match/MatchMap.java | 55 ++ .../testclasses/match/MatchModifiers.java | 42 + .../testclasses/match/MatchMultiple.java | 48 + .../testclasses/match/MatchMultiple2.java | 52 + .../testclasses/match/MatchMultiple3.java | 47 + .../match/MatchWithParameterCondition.java | 36 + .../match/MatchWithParameterType.java | 34 + .../testclasses/replace/DPPSample1.java | 34 + .../template/testclasses/replace/Item.java | 4 + .../testclasses/replace/NewPattern.java | 81 ++ .../testclasses/replace/OldPattern.java | 78 ++ 76 files changed, 8907 insertions(+), 725 deletions(-) create mode 100644 src/main/java/spoon/pattern/AbstractItemAccessor.java create mode 100644 src/main/java/spoon/pattern/AbstractPrimitiveMatcher.java create mode 100644 src/main/java/spoon/pattern/AbstractRepeatableMatcher.java create mode 100644 src/main/java/spoon/pattern/ConflictResolutionMode.java create mode 100644 src/main/java/spoon/pattern/ConstantNode.java create mode 100644 src/main/java/spoon/pattern/ElementNode.java create mode 100644 src/main/java/spoon/pattern/ForEachNode.java create mode 100644 src/main/java/spoon/pattern/ListAccessor.java create mode 100644 src/main/java/spoon/pattern/ListOfNodes.java create mode 100644 src/main/java/spoon/pattern/LiveStatementsBuilder.java create mode 100644 src/main/java/spoon/pattern/ModelNode.java create mode 100644 src/main/java/spoon/pattern/NamedItemAccessor.java create mode 100644 src/main/java/spoon/pattern/Node.java create mode 100644 src/main/java/spoon/pattern/ParameterInfo.java create mode 100644 src/main/java/spoon/pattern/ParameterNode.java create mode 100644 src/main/java/spoon/pattern/ParameterValueProvider.java create mode 100644 src/main/java/spoon/pattern/ParameterValueProviderFactory.java create mode 100644 src/main/java/spoon/pattern/ParametersBuilder.java create mode 100644 src/main/java/spoon/pattern/Pattern.java create mode 100644 src/main/java/spoon/pattern/PatternBuilder.java create mode 100644 src/main/java/spoon/pattern/PrimitiveMatcher.java create mode 100644 src/main/java/spoon/pattern/RepeatableMatcher.java create mode 100644 src/main/java/spoon/pattern/ResultHolder.java create mode 100644 src/main/java/spoon/pattern/SetAccessor.java create mode 100644 src/main/java/spoon/pattern/StringNode.java create mode 100644 src/main/java/spoon/pattern/SubstitutionCloner.java create mode 100644 src/main/java/spoon/pattern/SubstitutionRequestProvider.java create mode 100644 src/main/java/spoon/pattern/SwitchNode.java create mode 100644 src/main/java/spoon/pattern/TemplateBuilder.java create mode 100644 src/main/java/spoon/pattern/UnmodifiableParameterValueProvider.java create mode 100644 src/main/java/spoon/pattern/ValueConvertor.java create mode 100644 src/main/java/spoon/pattern/ValueConvertorImpl.java create mode 100644 src/main/java/spoon/pattern/concept.md create mode 100644 src/main/java/spoon/pattern/matcher/ChainOfMatchersImpl.java create mode 100644 src/main/java/spoon/pattern/matcher/MapEntryNode.java create mode 100644 src/main/java/spoon/pattern/matcher/Match.java create mode 100644 src/main/java/spoon/pattern/matcher/Matchers.java create mode 100644 src/main/java/spoon/pattern/matcher/MatchingScanner.java create mode 100644 src/main/java/spoon/pattern/matcher/NodeListMatcher.java create mode 100644 src/main/java/spoon/pattern/matcher/Quantifier.java create mode 100644 src/main/java/spoon/pattern/matcher/TobeMatched.java create mode 100644 src/test/java/spoon/test/template/CodeReplaceTest.java create mode 100644 src/test/java/spoon/test/template/PatternTest.java create mode 100644 src/test/java/spoon/test/template/TemplateMatcherTest.java create mode 100644 src/test/java/spoon/test/template/core/ParameterInfoTest.java create mode 100644 src/test/java/spoon/test/template/testclasses/ToBeMatched.java create mode 100644 src/test/java/spoon/test/template/testclasses/match/Check.java create mode 100644 src/test/java/spoon/test/template/testclasses/match/MatchForEach.java create mode 100644 src/test/java/spoon/test/template/testclasses/match/MatchForEach2.java create mode 100644 src/test/java/spoon/test/template/testclasses/match/MatchIfElse.java create mode 100644 src/test/java/spoon/test/template/testclasses/match/MatchMap.java create mode 100644 src/test/java/spoon/test/template/testclasses/match/MatchModifiers.java create mode 100644 src/test/java/spoon/test/template/testclasses/match/MatchMultiple.java create mode 100644 src/test/java/spoon/test/template/testclasses/match/MatchMultiple2.java create mode 100644 src/test/java/spoon/test/template/testclasses/match/MatchMultiple3.java create mode 100644 src/test/java/spoon/test/template/testclasses/match/MatchWithParameterCondition.java create mode 100644 src/test/java/spoon/test/template/testclasses/match/MatchWithParameterType.java create mode 100644 src/test/java/spoon/test/template/testclasses/replace/DPPSample1.java create mode 100644 src/test/java/spoon/test/template/testclasses/replace/Item.java create mode 100644 src/test/java/spoon/test/template/testclasses/replace/NewPattern.java create mode 100644 src/test/java/spoon/test/template/testclasses/replace/OldPattern.java diff --git a/src/main/java/spoon/Metamodel.java b/src/main/java/spoon/Metamodel.java index 32e42d45d88..79b7d66f6ce 100644 --- a/src/main/java/spoon/Metamodel.java +++ b/src/main/java/spoon/Metamodel.java @@ -238,13 +238,13 @@ public String getName() { /** * @return {@link Class} which implements this type. For example {@link CtForEachImpl} */ - public Class getModelClass() { + public Class getModelClass() { return modelClass; } /** * @return {@link Class} which defines interface of this type. For example {@link CtForEach} */ - public Class getModelInterface() { + public Class getModelInterface() { return modelInterface; } @@ -438,7 +438,7 @@ private static void initTypes(List types) { .field(CtRole.NAME, false, false) .field(CtRole.IS_SHADOW, false, false) .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.MODIFIER, false, true) + .field(CtRole.MODIFIER, true, true) .field(CtRole.INTERFACE, true, true) .field(CtRole.SUPER_TYPE, true, true) .field(CtRole.POSITION, false, false) @@ -446,7 +446,7 @@ private static void initTypes(List types) { .field(CtRole.DECLARING_TYPE, false, false) .field(CtRole.TYPE_ARGUMENT, false, false) .field(CtRole.ANNOTATION, false, false) - .field(CtRole.COMMENT, false, true) + .field(CtRole.COMMENT, true, true) )); @@ -454,7 +454,7 @@ private static void initTypes(List types) { .field(CtRole.NAME, false, false) .field(CtRole.IS_IMPLICIT, false, false) .field(CtRole.POSITION, false, false) - .field(CtRole.COMMENT, false, true) + .field(CtRole.COMMENT, true, true) .field(CtRole.TYPE, false, false) .field(CtRole.ANNOTATION, false, false) @@ -551,7 +551,7 @@ private static void initTypes(List types) { )); types.add(new Type("CtConstructor", spoon.reflect.declaration.CtConstructor.class, spoon.support.reflect.declaration.CtConstructorImpl.class, fm -> fm - .field(CtRole.NAME, false, true) + .field(CtRole.NAME, true, true) .field(CtRole.TYPE, true, true) .field(CtRole.IS_SHADOW, false, false) .field(CtRole.IS_IMPLICIT, false, false) @@ -579,7 +579,7 @@ private static void initTypes(List types) { )); types.add(new Type("CtAnonymousExecutable", spoon.reflect.declaration.CtAnonymousExecutable.class, spoon.support.reflect.declaration.CtAnonymousExecutableImpl.class, fm -> fm - .field(CtRole.NAME, false, true) + .field(CtRole.NAME, true, true) .field(CtRole.TYPE, true, true) .field(CtRole.IS_IMPLICIT, false, false) .field(CtRole.PARAMETER, true, true) @@ -604,12 +604,12 @@ private static void initTypes(List types) { )); types.add(new Type("CtWildcardReference", spoon.reflect.reference.CtWildcardReference.class, spoon.support.reflect.reference.CtWildcardReferenceImpl.class, fm -> fm - .field(CtRole.NAME, false, true) + .field(CtRole.NAME, true, true) .field(CtRole.IS_SHADOW, false, false) .field(CtRole.IS_UPPER, false, false) .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.MODIFIER, false, true) - .field(CtRole.COMMENT, false, true) + .field(CtRole.MODIFIER, true, true) + .field(CtRole.COMMENT, true, true) .field(CtRole.INTERFACE, true, true) .field(CtRole.SUPER_TYPE, true, true) .field(CtRole.TYPE_ARGUMENT, true, true) @@ -647,7 +647,7 @@ private static void initTypes(List types) { types.add(new Type("CtPackageReference", spoon.reflect.reference.CtPackageReference.class, spoon.support.reflect.reference.CtPackageReferenceImpl.class, fm -> fm .field(CtRole.NAME, false, false) .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.COMMENT, false, true) + .field(CtRole.COMMENT, true, true) .field(CtRole.POSITION, false, false) .field(CtRole.ANNOTATION, false, false) @@ -700,7 +700,7 @@ private static void initTypes(List types) { types.add(new Type("CtParameterReference", spoon.reflect.reference.CtParameterReference.class, spoon.support.reflect.reference.CtParameterReferenceImpl.class, fm -> fm .field(CtRole.NAME, false, false) .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.COMMENT, false, true) + .field(CtRole.COMMENT, true, true) .field(CtRole.POSITION, false, false) .field(CtRole.TYPE, false, false) .field(CtRole.ANNOTATION, false, false) @@ -736,7 +736,7 @@ private static void initTypes(List types) { types.add(new Type("CtUnboundVariableReference", spoon.reflect.reference.CtUnboundVariableReference.class, spoon.support.reflect.reference.CtUnboundVariableReferenceImpl.class, fm -> fm .field(CtRole.NAME, false, false) .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.COMMENT, false, true) + .field(CtRole.COMMENT, true, true) .field(CtRole.ANNOTATION, true, true) .field(CtRole.POSITION, false, false) .field(CtRole.TYPE, false, false) @@ -765,7 +765,7 @@ private static void initTypes(List types) { .field(CtRole.NAME, false, false) .field(CtRole.IS_SHADOW, false, false) .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.LABEL, false, true) + .field(CtRole.LABEL, true, true) .field(CtRole.MODIFIER, false, false) .field(CtRole.NESTED_TYPE, true, false) .field(CtRole.CONSTRUCTOR, true, false) @@ -876,8 +876,8 @@ private static void initTypes(List types) { .field(CtRole.IS_SHADOW, false, false) .field(CtRole.IS_UPPER, false, false) .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.MODIFIER, false, true) - .field(CtRole.COMMENT, false, true) + .field(CtRole.MODIFIER, true, true) + .field(CtRole.COMMENT, true, true) .field(CtRole.INTERFACE, true, true) .field(CtRole.SUPER_TYPE, true, true) .field(CtRole.TYPE_ARGUMENT, true, true) @@ -950,7 +950,7 @@ private static void initTypes(List types) { .field(CtRole.ARGUMENT_TYPE, false, false) .field(CtRole.TYPE_ARGUMENT, false, false) .field(CtRole.ANNOTATION, false, false) - .field(CtRole.COMMENT, false, true) + .field(CtRole.COMMENT, true, true) )); @@ -982,7 +982,7 @@ private static void initTypes(List types) { .field(CtRole.NAME, false, false) .field(CtRole.IS_SHADOW, false, false) .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.MODIFIER, false, true) + .field(CtRole.MODIFIER, true, true) .field(CtRole.INTERFACE, true, true) .field(CtRole.TYPE_MEMBER, true, true) .field(CtRole.NESTED_TYPE, true, true) @@ -1079,7 +1079,7 @@ private static void initTypes(List types) { types.add(new Type("CtModuleReference", spoon.reflect.reference.CtModuleReference.class, spoon.support.reflect.reference.CtModuleReferenceImpl.class, fm -> fm .field(CtRole.NAME, false, false) .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.COMMENT, false, true) + .field(CtRole.COMMENT, true, true) .field(CtRole.POSITION, false, false) .field(CtRole.ANNOTATION, false, false) @@ -1099,11 +1099,11 @@ private static void initTypes(List types) { .field(CtRole.NAME, false, false) .field(CtRole.IS_SHADOW, false, false) .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.MODIFIER, false, true) + .field(CtRole.MODIFIER, true, true) .field(CtRole.INTERFACE, true, true) .field(CtRole.SUPER_TYPE, true, true) .field(CtRole.POSITION, false, false) - .field(CtRole.COMMENT, false, true) + .field(CtRole.COMMENT, true, true) .field(CtRole.PACKAGE_REF, false, false) .field(CtRole.DECLARING_TYPE, false, false) .field(CtRole.TYPE, false, false) @@ -1137,7 +1137,7 @@ private static void initTypes(List types) { .field(CtRole.TYPE, false, false) .field(CtRole.CAST, false, false) .field(CtRole.PARAMETER, false, false) - .field(CtRole.THROWN, false, true) + .field(CtRole.THROWN, true, true) .field(CtRole.BODY, false, false) .field(CtRole.EXPRESSION, false, false) .field(CtRole.COMMENT, false, false) @@ -1169,8 +1169,8 @@ private static void initTypes(List types) { .field(CtRole.NAME, false, false) .field(CtRole.IS_SHADOW, false, false) .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.MODIFIER, false, true) - .field(CtRole.COMMENT, false, true) + .field(CtRole.MODIFIER, true, true) + .field(CtRole.COMMENT, true, true) .field(CtRole.INTERFACE, true, true) .field(CtRole.SUPER_TYPE, true, true) .field(CtRole.POSITION, false, false) @@ -1310,7 +1310,7 @@ private static void initTypes(List types) { .field(CtRole.IS_FINAL, false, false) .field(CtRole.IS_STATIC, false, false) .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.COMMENT, false, true) + .field(CtRole.COMMENT, true, true) .field(CtRole.POSITION, false, false) .field(CtRole.DECLARING_TYPE, false, false) .field(CtRole.TYPE, false, false) @@ -1322,7 +1322,7 @@ private static void initTypes(List types) { .field(CtRole.NAME, false, false) .field(CtRole.IS_SHADOW, false, false) .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.LABEL, false, true) + .field(CtRole.LABEL, true, true) .field(CtRole.MODIFIER, false, false) .field(CtRole.SUPER_TYPE, true, true) .field(CtRole.NESTED_TYPE, true, false) @@ -1359,7 +1359,7 @@ private static void initTypes(List types) { types.add(new Type("CtLocalVariableReference", spoon.reflect.reference.CtLocalVariableReference.class, spoon.support.reflect.reference.CtLocalVariableReferenceImpl.class, fm -> fm .field(CtRole.NAME, false, false) .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.COMMENT, false, true) + .field(CtRole.COMMENT, true, true) .field(CtRole.POSITION, false, false) .field(CtRole.TYPE, false, false) .field(CtRole.ANNOTATION, false, false) diff --git a/src/main/java/spoon/pattern/AbstractItemAccessor.java b/src/main/java/spoon/pattern/AbstractItemAccessor.java new file mode 100644 index 00000000000..6a93ad50023 --- /dev/null +++ b/src/main/java/spoon/pattern/AbstractItemAccessor.java @@ -0,0 +1,424 @@ +/** + * 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.pattern; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; + +import spoon.Launcher; +import spoon.SpoonException; +import spoon.pattern.matcher.Quantifier; +import spoon.reflect.code.CtBlock; +import spoon.reflect.meta.ContainerKind; +import spoon.reflect.reference.CtTypeReference; + +/** + */ +abstract class AbstractItemAccessor implements ParameterInfo { + + /** + * is used as return value when value cannot be added + */ + protected static final Object NO_MERGE = new Object(); + + private final AbstractItemAccessor containerItemAccessor; + + private ContainerKind containerKind = null; + private Boolean repeatable = null; + private int minOccurences = 0; + private int maxOccurences = UNLIMITED_OCCURENCES; + private Quantifier matchingStrategy = Quantifier.GREEDY; + private ValueConvertor valueConvertor; + + private Predicate matchCondition; + private Class parameterValueType; + + protected AbstractItemAccessor(ParameterInfo containerItemAccessor) { + super(); + this.containerItemAccessor = (AbstractItemAccessor) containerItemAccessor; + } + + protected String getContainerName() { + if (containerItemAccessor != null) { + return containerItemAccessor.getPlainName(); + } + return ""; + } + + @Override + public final String getName() { + AbstractItemAccessor cca = getContainerKindAccessor(getContainerKind(null, null)); + if (cca != null) { + return cca.getWrappedName(getPlainName()); + } + return getPlainName(); + } + + protected abstract String getPlainName(); + + protected abstract String getWrappedName(String containerName); + + @Override + public ParameterValueProvider addValueAs(ParameterValueProvider parameters, Object value) { + Class requiredType = getParameterValueType(); + if (requiredType != null && value != null && requiredType.isInstance(value) == false) { + return null; + } + if (matches(value) == false) { + return null; + } + Object newContainer = addValueToContainer(parameters, existingValue -> { + return merge(existingValue, value); + }); + if (newContainer == NO_MERGE) { + return null; + } + return (ParameterValueProvider) newContainer; + } + + protected Object addValueToContainer(Object container, Function merger) { + if (containerItemAccessor != null) { + return containerItemAccessor.addValueToContainer(container, existingValue -> { + return addValueAs(existingValue, merger); + }); + } + return addValueAs(container, merger); + } + + protected Object merge(Object existingValue, Object newValue) { + ContainerKind cc = getContainerKind(existingValue, newValue); + AbstractItemAccessor cca = getContainerKindAccessor(cc); + if (cca == null) { + return mergeSingle(existingValue, newValue); + } + return cca.addValueAs(existingValue, + existingListItemValue -> mergeSingle(existingListItemValue, newValue)); + } + + protected AbstractItemAccessor getContainerKindAccessor(ContainerKind containerKind) { + switch (containerKind) { + case SINGLE: + return null; + case LIST: + return new ListAccessor(this); + case SET: + return new SetAccessor(this); + case MAP: + return new NamedItemAccessor(this); + } + throw new SpoonException("Unexpected ContainerKind " + containerKind); + } + + protected Object mergeSingle(Object existingValue, Object newValue) { + if (newValue == null && getMinOccurences() > 0) { + //the newValue is not optional. Null doesn't matches + return NO_MERGE; + } + if (existingValue != null) { + if (existingValue.equals(newValue)) { + //the value is already stored there. Keep existing value + return existingValue; + } + if (newValue != null && existingValue.getClass().equals(newValue.getClass())) { + if (newValue instanceof CtTypeReference) { + if (((CtTypeReference) newValue).getTypeErasure().equals(((CtTypeReference) existingValue).getTypeErasure())) { + //accept type references with different erasure + return existingValue; + } + } + } + // another value would be inserted. TemplateMatcher does not support + // matching of different values for the same template parameter + Launcher.LOGGER.debug("incongruent match on parameter " + getName() + " with value " + newValue); + return NO_MERGE; + } + return newValue; + } + + /** + * takes existing item value from the `container`, + * sends it as parameter into `merger` and get's new to be stored value + * stores that value into new `container` and returns it + * @param container a container of values + * @param merger a code which merges existing value from container with new value and returns merged value, which has to be stored in the container instead + * @return copy of the container with merged value + */ + protected abstract Object addValueAs(Object container, Function merger); + + protected T castTo(Object o, Class type) { + if (o == null) { + return getEmptyContainer(); + } + if (type.isInstance(o)) { + return (T) o; + } + throw new SpoonException("Cannot access parameter container of type " + o.getClass() + ". It expects " + type); + } + + protected abstract T getEmptyContainer(); + + public AbstractItemAccessor setMatchCondition(Class requiredType, Predicate matchCondition) { + this.parameterValueType = requiredType; + this.matchCondition = (Predicate) matchCondition; + return this; + } + + public boolean matches(Object value) { + if (matchCondition == null) { + //there is no matching condition. Everything matches + return true; + } + //there is a matching condition. It must match + return matchCondition.test(value); + } + /** + * @return a type of parameter value - if known + * + * Note: Pattern builder needs to know the value type to be able to select substitute node. + * For example patter: + * return _expression_.S(); + * either replaces only `_expression_.S()` if the parameter value is an expression + * or replaces `return _expression_.S()` if the parameter value is a CtBlock + */ + public Class getParameterValueType() { + return parameterValueType; + } + + public AbstractItemAccessor setParameterValueType(Class parameterValueType) { + this.parameterValueType = parameterValueType; + return this; + } + /** + * @return true if the value container has to be a List, otherwise the container will be a single value + */ + public boolean isMultiple() { + return getContainerKind(null, null) != ContainerKind.SINGLE; + } + + public AbstractItemAccessor setRepeatable(boolean repeatable) { + this.repeatable = repeatable; + return this; + } + + + public int getMinOccurences() { + return minOccurences; + } + + public AbstractItemAccessor setMinOccurences(int minOccurences) { + this.minOccurences = minOccurences; + return this; + } + + /** + * @return maximum number of values in this parameter. + * Note: if {@link #isMultiple()}==false, then it never returns value > 1 + */ + public int getMaxOccurences() { + return isMultiple() ? maxOccurences : Math.min(maxOccurences, 1); + } + + void setMaxOccurences(int maxOccurences) { + this.maxOccurences = maxOccurences; + } + + public Quantifier getMatchingStrategy() { + return matchingStrategy; + } + + public void setMatchingStrategy(Quantifier matchingStrategy) { + this.matchingStrategy = matchingStrategy; + } + + /** + * @return the {@link ValueConvertor} used by reading and writing into parameter values defined by this {@link ParameterInfo} + */ + public ValueConvertor getValueConvertor() { + if (valueConvertor != null) { + return valueConvertor; + } + if (containerItemAccessor != null) { + return containerItemAccessor.getValueConvertor(); + } + throw new SpoonException("ValueConvertor is not defined."); + } + + /** + * @param valueConvertor the {@link ValueConvertor} used by reading and writing into parameter values defined by this {@link ParameterInfo} + */ + public AbstractItemAccessor setValueConvertor(ValueConvertor valueConvertor) { + if (valueConvertor == null) { + throw new SpoonException("valueConvertor must not be null"); + } + this.valueConvertor = valueConvertor; + return this; + } + + /** + * @return true if this matcher can be applied more then once in the same container of targets + * Note: even if false, it may be applied again to another container and to match EQUAL value + */ + public boolean isRepeatable() { + if (repeatable != null) { + return repeatable; + } + return isMultiple(); + } + + /** + * @param parameters matching parameters + * @return true if the ValueResolver of this parameter MUST match with next target in the state defined by current `parameters`. + * false if match is optional + */ + public boolean isMandatory(ParameterValueProvider parameters) { + int nrOfValues = getNumberOfValues(parameters); + //current number of values is smaller then minimum number of values. Value is mandatory + return nrOfValues < getMinOccurences(); + } + + /** + * @param parameters matching parameters + * @return true if the ValueResolver of this parameter should be processed again to match next target in the state defined by current `parameters`. + */ + public boolean isTryNextMatch(ParameterValueProvider parameters) { + int nrOfValues = getNumberOfValues(parameters); + if (getContainerKind(parameters) == ContainerKind.SINGLE) { + /* + * the single value parameters should always try next match. + * If the matching value is equal to existing value, then second match is accepted and there stays single value + */ + return true; + } + //current number of values is smaller then maximum number of values. Can try next match + return nrOfValues < getMaxOccurences(); + } + + /** + * @param parameters + * @return 0 if there is no value. 1 if there is single value or null. Number of values in collection if there is a collection + */ + private int getNumberOfValues(ParameterValueProvider parameters) { + if (parameters.hasValue(getName()) == false) { + return 0; + } + Object value = parameters.get(getName()); + if (value instanceof Collection) { + return ((Collection) value).size(); + } + return 1; + } + public ContainerKind getContainerKind() { + return containerKind; + } + + public AbstractItemAccessor setContainerKind(ContainerKind containerKind) { + this.containerKind = containerKind; + return this; + } + protected ContainerKind getContainerKind(ParameterValueProvider params) { + return getContainerKind(params.get(getName()), null); + } + protected ContainerKind getContainerKind(Object existingValue, Object value) { + if (containerKind != null) { + return containerKind; + } + if (existingValue instanceof List) { + return ContainerKind.LIST; + } + if (existingValue instanceof Set) { + return ContainerKind.SET; + } + if (existingValue instanceof Map) { + return ContainerKind.MAP; + } + if (existingValue instanceof ParameterValueProvider) { + return ContainerKind.MAP; + } + if (existingValue != null) { + return ContainerKind.SINGLE; + } + + if (value instanceof List) { + return ContainerKind.LIST; + } + if (value instanceof Set) { + return ContainerKind.SET; + } + if (value instanceof Map.Entry) { + return ContainerKind.MAP; + } + if (value instanceof Map) { + return ContainerKind.MAP; + } + if (value instanceof ParameterValueProvider) { + return ContainerKind.MAP; + } + return ContainerKind.SINGLE; + } + + @Override + public void getValueAs(ResultHolder result, ParameterValueProvider parameters) { + //get raw parameter value + Object rawValue = getValue(parameters); + if (isMultiple() && rawValue instanceof CtBlock) { + /* + * The CtBlock of this parameter is just implicit container of list of statements. + * Convert it to list here, so further code see list and not the single CtBlock element + */ + rawValue = ((CtBlock) rawValue).getStatements(); + } + convertValue(result, rawValue); + } + + protected Object getValue(ParameterValueProvider parameters) { + if (containerItemAccessor != null) { + return containerItemAccessor.getValue(parameters); + } + return parameters; + } + + protected void convertValue(ResultHolder result, Object rawValue) { + ValueConvertor valueConvertor = getValueConvertor(); + //convert raw parameter value to expected type + if (result.isMultiple()) { + AbstractPrimitiveMatcher.forEachItem(rawValue, singleValue -> { + T convertedValue = (T) valueConvertor.getValueAs(singleValue, result.getRequiredClass()); + if (convertedValue != null) { + result.addResult(convertedValue); + } + }); + } else { + //single value converts arrays in rawValues into single value + result.addResult((T) valueConvertor.getValueAs(rawValue, result.getRequiredClass())); + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(getName()); + if (getParameterValueType() != null) { + sb.append(" : "); + sb.append(getParameterValueType().getName()); + } + return sb.toString(); + } +} diff --git a/src/main/java/spoon/pattern/AbstractPrimitiveMatcher.java b/src/main/java/spoon/pattern/AbstractPrimitiveMatcher.java new file mode 100644 index 00000000000..d7bee78e0a8 --- /dev/null +++ b/src/main/java/spoon/pattern/AbstractPrimitiveMatcher.java @@ -0,0 +1,70 @@ +/** + * 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.pattern; + +import java.util.function.Consumer; + +import spoon.pattern.matcher.TobeMatched; +import spoon.reflect.code.CtStatementList; + +/** + * Delivers to be substituted value + * Matches value + */ +abstract class AbstractPrimitiveMatcher extends AbstractRepeatableMatcher implements PrimitiveMatcher { + + protected AbstractPrimitiveMatcher() { + } + + + /** + * calls consumer.accept(Object) once for each item of the `multipleValues` collection or array. + * If it is not a collection or array then it calls consumer.accept(Object) once with `multipleValues` + * If `multipleValues` is null then consumer.accept(Object) is not called + * @param multipleValues to be iterated potential collection of items + * @param consumer the receiver of items + */ + @SuppressWarnings("unchecked") + static void forEachItem(Object multipleValues, Consumer consumer) { + if (multipleValues instanceof CtStatementList) { + //CtStatementList extends Iterable, but we want to handle it as one node. + consumer.accept(multipleValues); + return; + } + if (multipleValues instanceof Iterable) { + for (Object item : (Iterable) multipleValues) { + consumer.accept(item); + } + return; + } + if (multipleValues instanceof Object[]) { + for (Object item : (Object[]) multipleValues) { + consumer.accept(item); + } + return; + } + consumer.accept(multipleValues); + } + + @Override + public TobeMatched matchAllWith(TobeMatched tobeMatched) { + //we are matching single CtElement or attribute value + return tobeMatched.matchNext((target, parameters) -> { + return matchTarget(target, tobeMatched.getParameters()); + }); + } +} diff --git a/src/main/java/spoon/pattern/AbstractRepeatableMatcher.java b/src/main/java/spoon/pattern/AbstractRepeatableMatcher.java new file mode 100644 index 00000000000..8e47063f78f --- /dev/null +++ b/src/main/java/spoon/pattern/AbstractRepeatableMatcher.java @@ -0,0 +1,124 @@ +/** + * 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.pattern; + +import spoon.SpoonException; +import spoon.pattern.matcher.Matchers; +import spoon.pattern.matcher.TobeMatched; + +/** + * Defines algorithm of repeatable matcher. + */ +abstract class AbstractRepeatableMatcher implements RepeatableMatcher { + + @Override + public TobeMatched matchTargets(TobeMatched targets, Matchers next) { + if (isRepeatable() == false) { + //handle non repeatable Nodes + boolean isMandatory = isMandatory(targets.getParameters()); + //match maximum one value + TobeMatched tmp = matchAllWith(targets); + if (tmp == null) { + if (isMandatory) { + //this mandatory valueResolver didn't matched + return null; + } + //no match - OK, it was optional + } else { + targets = tmp; + } + //the `matcher` has all values. Match next + return next.matchAllWith(targets); + } + //it is repeatable node + //first match mandatory targets + while (isMandatory(targets.getParameters())) { + TobeMatched tmp = matchAllWith(targets); + if (tmp == null) { + //this mandatory valueResolver didn't matched + return null; + } + //check whether we have to match next + //we need this check because #isMandatory()==true for each state. In such case the #isTryNextMatch must be able to finish the cycle + if (isTryNextMatch(tmp.getParameters()) == false) { + //the `matcher` has all values. Match next + return next.matchAllWith(tmp); + } + //use new matching request to match next mandatory parameter + targets = tmp; + } + //then continue optional targets + return matchOptionalTargets(targets, next); + } + private TobeMatched matchOptionalTargets(TobeMatched targets, Matchers next) { + if (isTryNextMatch(targets.getParameters()) == false) { + //the `matcher` has all values. Match next + return next.matchAllWith(targets); + } + switch (getMatchingStrategy()) { + case GREEDY: { + { //first try to match using this matcher + TobeMatched match = matchAllWith(targets); + if (match != null) { + //this matcher passed, try to match next one using current SimpleValueResolver + match = matchOptionalTargets(match, next); + if (match != null) { + //all next passed too, return that match + return match; + } + } + } + //greedy matching with current nodeSubstRequest didn't passed. Try to match using remaining templates + return next.matchAllWith(targets); + } + case RELUCTANT: { + { //first try to match using next matcher. + TobeMatched match = next.matchAllWith(targets); + if (match != null) { + return match; + } + } + //reluctant matching didn't passed on next elements. Try to match using this matcher + TobeMatched match = matchAllWith(targets); + if (match == null) { + //nothing matched + return null; + } + //this matcher passed. Match next one using current SimpleValueResolver + return matchOptionalTargets(match, next); + } + case POSSESSIVE: + //match everything using this matcher. Never try other way + //Check if we should try next match + while (isTryNextMatch(targets.getParameters())) { + TobeMatched tmp = matchAllWith(targets); + if (tmp == null) { + if (isMandatory(targets.getParameters())) { + //this mandatory valueResolver didn't matched + return null; + } + //it was optional. Ignore this valueResolver and continue with next + break; + } + //use new matching request + targets = tmp; + } + return next.matchAllWith(targets); + } + throw new SpoonException("Unsupported quantifier " + getMatchingStrategy()); + } +} diff --git a/src/main/java/spoon/pattern/ConflictResolutionMode.java b/src/main/java/spoon/pattern/ConflictResolutionMode.java new file mode 100644 index 00000000000..426ea0ad0c1 --- /dev/null +++ b/src/main/java/spoon/pattern/ConflictResolutionMode.java @@ -0,0 +1,37 @@ +/** + * 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.pattern; + +import spoon.SpoonException; + +/** + * Defines what happens when before explicitly added {@link Node} has to be replaced by another {@link Node} + */ +public enum ConflictResolutionMode { + /** + * throw {@link SpoonException} + */ + FAIL, + /** + * get rid of old {@link Node} and use new {@link Node} instead + */ + USE_NEW_NODE, + /** + * keep old {@link Node} and ignore try to add any new {@link Node} + */ + KEEP_OLD_NODE +} diff --git a/src/main/java/spoon/pattern/ConstantNode.java b/src/main/java/spoon/pattern/ConstantNode.java new file mode 100644 index 00000000000..33b1f7615e0 --- /dev/null +++ b/src/main/java/spoon/pattern/ConstantNode.java @@ -0,0 +1,66 @@ +/** + * 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.pattern; + +import java.util.function.BiConsumer; + +import spoon.reflect.factory.Factory; + +/** + * Generates/Matches a copy of single template object + */ +public class ConstantNode extends AbstractPrimitiveMatcher { + protected final T template; + + public ConstantNode(T template) { + super(); + this.template = template; + } + + public T getTemplateNode() { + return template; + } + + @Override + public boolean replaceNode(Node oldNode, Node newNode) { + return false; + } + + @Override + public void forEachParameterInfo(BiConsumer consumer) { + //it has no parameters + } + + @Override + public void generateTargets(Factory factory, ResultHolder result, ParameterValueProvider parameters) { + result.addResult((U) template); + } + + @Override + public ParameterValueProvider matchTarget(Object target, ParameterValueProvider parameters) { + if (target == null && template == null) { + return parameters; + } + if (target == null || template == null) { + return null; + } + if (target.getClass() != template.getClass()) { + return null; + } + return target.equals(template) ? parameters : null; + } +} diff --git a/src/main/java/spoon/pattern/ElementNode.java b/src/main/java/spoon/pattern/ElementNode.java new file mode 100644 index 00000000000..6c35d48d4ba --- /dev/null +++ b/src/main/java/spoon/pattern/ElementNode.java @@ -0,0 +1,194 @@ +/** + * 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.pattern; + +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; + +import spoon.Metamodel; +import spoon.SpoonException; +import spoon.pattern.matcher.TobeMatched; +import spoon.reflect.declaration.CtElement; +import spoon.reflect.factory.Factory; +import spoon.reflect.meta.ContainerKind; +import spoon.reflect.path.CtRole; + +import static spoon.pattern.matcher.TobeMatched.getMatchedParameters; + +/** + * Generates/Matches a copy of a single CtElement AST node with all it's children (whole AST tree of the root CtElement) + */ +public class ElementNode extends AbstractPrimitiveMatcher { + + private Metamodel.Type elementType; + private Map attributeSubstititionRequests = new HashMap<>(); + + public ElementNode(Metamodel.Type elementType) { + super(); + this.elementType = elementType; + } + + @Override + public boolean replaceNode(Node oldNode, Node newNode) { + for (Map.Entry e : attributeSubstititionRequests.entrySet()) { + Node node = e.getValue(); + if (node == oldNode) { + e.setValue(newNode); + return true; + } + if (node.replaceNode(oldNode, newNode)) { + return true; + } + } + return false; + } + + public Map getAttributeSubstititionRequests() { + return attributeSubstititionRequests == null ? Collections.emptyMap() : Collections.unmodifiableMap(attributeSubstititionRequests); + } + + public Node getAttributeSubstititionRequest(CtRole attributeRole) { + return attributeSubstititionRequests.get(getFieldOfRole(attributeRole)); + } + + public Node setNodeOfRole(CtRole role, Node newAttrNode) { + return attributeSubstititionRequests.put(getFieldOfRole(role), newAttrNode); + } + + private Metamodel.Field getFieldOfRole(CtRole role) { + Metamodel.Field mmField = elementType.getField(role); + if (mmField == null) { + throw new SpoonException("CtRole." + role.name() + " isn't available for " + elementType); + } + if (mmField.isDerived()) { + throw new SpoonException("CtRole." + role.name() + " is derived in " + elementType + " so it can't be used for matching or generating"); + } + return mmField; + } + + @Override + public void forEachParameterInfo(BiConsumer consumer) { + if (attributeSubstititionRequests != null) { + for (Node node : attributeSubstititionRequests.values()) { + node.forEachParameterInfo(consumer); + } + } + } + + @SuppressWarnings("unchecked") + @Override + public void generateTargets(Factory factory, ResultHolder result, ParameterValueProvider parameters) { + //TODO implement create on Metamodel.Type + CtElement clone = factory.Core().create(elementType.getModelInterface()); + generateSingleNodeAttributes(clone, parameters); + result.addResult((U) clone); + } + + protected void generateSingleNodeAttributes(CtElement clone, ParameterValueProvider parameters) { + for (Map.Entry e : getAttributeSubstititionRequests().entrySet()) { + Metamodel.Field mmField = e.getKey(); + switch (mmField.getContainerKind()) { + case SINGLE: + mmField.setValue(clone, e.getValue().generateTarget(clone.getFactory(), parameters, mmField.getValueClass())); + break; + case LIST: + mmField.setValue(clone, e.getValue().generateTargets(clone.getFactory(), parameters, mmField.getValueClass())); + break; + case SET: + mmField.setValue(clone, new LinkedHashSet<>(e.getValue().generateTargets(clone.getFactory(), parameters, mmField.getValueClass()))); + break; + case MAP: + mmField.setValue(clone, entriesToMap(e.getValue().generateTargets(clone.getFactory(), parameters, Map.Entry.class))); + break; + } + } + } + + private Map entriesToMap(List entries) { + Map map = new LinkedHashMap<>(entries.size()); + for (Map.Entry entry : entries) { + map.put(entry.getKey(), entry.getValue()); + } + return map; + } + + @Override + public ParameterValueProvider matchTarget(Object target, ParameterValueProvider parameters) { + if (target == null) { + return null; + } + if (target.getClass() != elementType.getModelClass()) { + return null; + } + + //it is spoon element, it matches if to be matched attributes matches + //to be matched attributes must be same or substituted + //iterate over all attributes of to be matched class + for (Map.Entry e : attributeSubstititionRequests.entrySet()) { + parameters = matchesRole(parameters, (CtElement) target, e.getKey(), e.getValue()); + if (parameters == null) { + return null; + } + } + return parameters; + } + + protected ParameterValueProvider matchesRole(ParameterValueProvider parameters, CtElement target, Metamodel.Field mmField, Node attrNode) { + TobeMatched tobeMatched; + if (attrNode instanceof ParameterNode) { + //whole attribute value (whole List/Set/Map) has to be stored in parameter + tobeMatched = TobeMatched.create(parameters, ContainerKind.SINGLE, mmField.getValue(target)); + } else { + //each item of attribute value (item of List/Set/Map) has to be matched individually + tobeMatched = TobeMatched.create(parameters, mmField.getContainerKind(), mmField.getValue(target)); + } + return getMatchedParameters(attrNode.matchTargets(tobeMatched, Node.MATCH_ALL)); + } + +// @Override +// public String toString() { +// PrinterHelper printer = new PrinterHelper(getTemplateNode().getFactory().getEnvironment()); +// printer.write(NodeAttributeSubstitutionRequest.getElementTypeName(getTemplateNode().getParent())).writeln().incTab(); +// appendDescription(printer); +// return printer.toString(); +// } +// +// public void appendDescription(PrinterHelper printer) { +// if (attributeSubstititionRequests == null || attributeSubstititionRequests.values().isEmpty()) { +// printer.write("** no attribute substitution **"); +// } else { +// boolean multipleAttrs = attributeSubstititionRequests.size() > 1; +// if (multipleAttrs) { +// printer.incTab(); +// } +// for (Node node : attributeSubstititionRequests.values()) { +// if (multipleAttrs) { +// printer.writeln(); +// } +// printer.write(node.toString()); +// } +// if (multipleAttrs) { +// printer.decTab(); +// } +// } +// } +} diff --git a/src/main/java/spoon/pattern/ForEachNode.java b/src/main/java/spoon/pattern/ForEachNode.java new file mode 100644 index 00000000000..7f2c325deaa --- /dev/null +++ b/src/main/java/spoon/pattern/ForEachNode.java @@ -0,0 +1,139 @@ +/** + * 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.pattern; + +import java.util.Map; +import java.util.function.BiConsumer; + +import spoon.pattern.matcher.Quantifier; +import spoon.pattern.matcher.TobeMatched; +import spoon.reflect.factory.Factory; + +/** + * Pattern node of multiple occurrences of the same model, just with different parameters. + * Example with three occurrences of model `System.out.println(_x_)`, with parameter `_x_` + *

+ * System.out.println("a")
+ * System.out.println("b")
+ * System.out.println(getStringOf(p1, p2))
+ * 
+ * where parameter values are _x_ = ["a", "b", getStringOf(p1, p2)] + */ +public class ForEachNode extends AbstractRepeatableMatcher { + + private PrimitiveMatcher iterableParameter; + private Node nestedModel; + private ParameterInfo localParameter; + + public ForEachNode() { + super(); + } + + @Override + public boolean replaceNode(Node oldNode, Node newNode) { + if (iterableParameter == oldNode) { + oldNode = newNode; + return true; + } + if (iterableParameter.replaceNode(oldNode, newNode)) { + return true; + } + if (nestedModel == oldNode) { + nestedModel = newNode; + return true; + } + if (nestedModel.replaceNode(oldNode, newNode)) { + return true; + } + return false; + } + + @Override + public void generateTargets(Factory factory, ResultHolder result, ParameterValueProvider parameters) { + for (Object parameterValue : iterableParameter.generateTargets(factory, parameters, Object.class)) { + nestedModel.generateTargets(factory, result, parameters.putIntoCopy(localParameter.getName(), parameterValue)); + } + } + + @Override + public Quantifier getMatchingStrategy() { + return iterableParameter.getMatchingStrategy(); + } + + @Override + public TobeMatched matchAllWith(TobeMatched tobeMatched) { + TobeMatched localMatch = nestedModel.matchAllWith(tobeMatched.copyAndSetParams(tobeMatched.getParameters().createLocalParameterValueProvider())); + if (localMatch == null) { + //nested model did not matched. + return null; + } + //it matched. + ParameterValueProvider newParameters = tobeMatched.getParameters(); + //copy values of local parameters + for (Map.Entry e : localMatch.getParameters().asLocalMap().entrySet()) { + String name = e.getKey(); + Object value = e.getValue(); + if (name.equals(localParameter.getName())) { + //value of local parameter has to be added to iterableParameter + newParameters = iterableParameter.matchTarget(value, newParameters); + if (newParameters == null) { + //new value did not passed the iterableParameter matcher + //do not apply newParameters, which matched only partially, to parameters. + return null; + } + } else { + //it is new global parameter value. Just set it + newParameters = newParameters.putIntoCopy(name, value); + } + } + //all local parameters were applied to newParameters. We can use newParameters as result of this iteration for next iteration + return localMatch.copyAndSetParams(newParameters); + } + + @Override + public void forEachParameterInfo(BiConsumer consumer) { + iterableParameter.forEachParameterInfo(consumer); + consumer.accept(localParameter, this); + } + + public void setNestedModel(Node valueResolver) { + this.nestedModel = valueResolver; + } + + public void setIterableParameter(PrimitiveMatcher substRequestOfIterable) { + this.iterableParameter = substRequestOfIterable; + } + + public void setLocalParameter(ParameterInfo parameterInfo) { + this.localParameter = parameterInfo; + } + + @Override + public boolean isRepeatable() { + return iterableParameter.isRepeatable(); + } + + @Override + public boolean isMandatory(ParameterValueProvider parameters) { + return iterableParameter.isMandatory(parameters); + } + + @Override + public boolean isTryNextMatch(ParameterValueProvider parameters) { + return iterableParameter.isTryNextMatch(parameters); + } +} diff --git a/src/main/java/spoon/pattern/ListAccessor.java b/src/main/java/spoon/pattern/ListAccessor.java new file mode 100644 index 00000000000..881c734c79c --- /dev/null +++ b/src/main/java/spoon/pattern/ListAccessor.java @@ -0,0 +1,117 @@ +/** + * 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.pattern; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.function.Function; + +/** + */ +public class ListAccessor extends AbstractItemAccessor { + + private final int idx; + + public ListAccessor(ParameterInfo next) { + this(-1, next); + } + public ListAccessor(int idx, ParameterInfo next) { + super(next); + this.idx = idx; + } + + @Override + protected String getPlainName() { + return getWrappedName(getContainerName()); + } + + @Override + protected String getWrappedName(String containerName) { + if (idx < 0) { + return containerName; + } + return containerName + "[" + idx + "]"; + } + + @Override + protected Object addValueAs(Object container, Function merger) { + List list = castTo(container, List.class); + Object existingValue = getExistingValue(list); + Object newValue = merger.apply(existingValue); + if (newValue == NO_MERGE) { + return NO_MERGE; + } + if (existingValue == newValue) { + //the value is already there. Keep existing list + return list; + } + if (newValue == null) { + //nothing to add. Keep existing list + return list; + } + List newList = new ArrayList<>(list.size() + 1); + newList.addAll(list); + if (idx >= 0) { + while (idx >= newList.size()) { + newList.add(null); + } + newList.set(idx, newValue); + } else { + if (newValue instanceof Collection) { + newList.addAll((Collection) newValue); + } else { + newList.add(newValue); + } + } + return Collections.unmodifiableList(newList); + } + + protected Object getExistingValue(List list) { + if (list == null || idx < 0 || idx >= list.size()) { + return null; + } + return list.get(idx); + } + @Override + protected List getEmptyContainer() { + return Collections.emptyList(); + } + @Override + protected Object getValue(ParameterValueProvider parameters) { + List list = castTo(super.getValue(parameters), List.class); + if (idx < 0) { + return list; + } + if (idx < list.size()) { + return list.get(idx); + } + return null; + } + @Override + protected T castTo(Object o, Class type) { + if (o instanceof Set) { + o = new ArrayList<>((Set) o); + } else if (o instanceof Object[]) { + o = Arrays.asList((Object[]) o); + } + return super.castTo(o, type); + } +} diff --git a/src/main/java/spoon/pattern/ListOfNodes.java b/src/main/java/spoon/pattern/ListOfNodes.java new file mode 100644 index 00000000000..9b4f1eeda50 --- /dev/null +++ b/src/main/java/spoon/pattern/ListOfNodes.java @@ -0,0 +1,82 @@ +/** + * 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.pattern; + +import java.util.List; +import java.util.function.BiConsumer; + +import spoon.pattern.matcher.ChainOfMatchersImpl; +import spoon.pattern.matcher.Matchers; +import spoon.pattern.matcher.TobeMatched; +import spoon.reflect.factory.Factory; + +/** + * List of {@link Node}s. The {@link Node}s are processed in same order like they were inserted in the list + */ +public class ListOfNodes implements Node { + protected List nodes; + + public ListOfNodes(List nodes) { + super(); + this.nodes = nodes; + } + + @Override + public void forEachParameterInfo(BiConsumer consumer) { + for (Node node : nodes) { + node.forEachParameterInfo(consumer); + } + } + + @Override + public void generateTargets(Factory factory, ResultHolder result, ParameterValueProvider parameters) { + for (Node node : nodes) { + node.generateTargets(factory, result, parameters); + } + } + + @Override + public TobeMatched matchTargets(TobeMatched targets, Matchers nextMatchers) { + return ChainOfMatchersImpl.create(nodes, nextMatchers).matchAllWith(targets); + } + + /** + * @param oldNode a {@link CtElement} whose {@link Node} we are looking for + * @return a {@link NodeContainer} of an {@link ElementNode}, whose {@link ElementNode#getTemplateNode()} == `element` + * null if element is not referred by any node of this {@link ListOfNodes} + */ + public boolean replaceNode(Node oldNode, Node newNode) { + for (int i = 0; i < nodes.size(); i++) { + Node node = nodes.get(i); + if (node == oldNode) { + nodes.set(i, newNode); + return true; + } + if (node.replaceNode(oldNode, newNode)) { + return true; + } + } + return false; + } + + /** + * @return {@link List} of {@link Node}s of this {@link ListOfNodes} + */ + public List getNodes() { + return nodes; + } +} diff --git a/src/main/java/spoon/pattern/LiveStatementsBuilder.java b/src/main/java/spoon/pattern/LiveStatementsBuilder.java new file mode 100644 index 00000000000..9d6645f5616 --- /dev/null +++ b/src/main/java/spoon/pattern/LiveStatementsBuilder.java @@ -0,0 +1,229 @@ +/** + * 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.pattern; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiConsumer; + +import spoon.SpoonException; +import spoon.reflect.code.CtBlock; +import spoon.reflect.code.CtExpression; +import spoon.reflect.code.CtForEach; +import spoon.reflect.code.CtIf; +import spoon.reflect.code.CtLocalVariable; +import spoon.reflect.code.CtStatement; +import spoon.reflect.declaration.CtElement; +import spoon.reflect.path.CtRole; +import spoon.reflect.reference.CtVariableReference; +import spoon.reflect.visitor.CtAbstractVisitor; +import spoon.reflect.visitor.filter.TypeFilter; + +import static spoon.pattern.PatternBuilder.bodyToStatements; + +/** + * Builds live statements of Pattern + * + * For example if the `for` statement in this pattern model + *

+ * for(Object x : $iterable$) {
+ *	System.out.println(x);
+ * }
+ * 
+ * is configured as live statement and a Pattern is substituted + * using parameter $iterable$ = new String[]{"A", "B", "C"} + * then pattern generated this code + *

+ * System.out.println("A");
+ * System.out.println("B");
+ * System.out.println("C");
+ * 
+ * because live statements are executed during substitution process and are not included in generated result. + * + * The live statements may be used in PatternMatching process (opposite to Pattern substitution) too. + */ +public class LiveStatementsBuilder { + + private final PatternBuilder patternBuilder; + private boolean failOnMissingParameter = true; + private ConflictResolutionMode conflictResolutionMode = ConflictResolutionMode.FAIL; + + public LiveStatementsBuilder(PatternBuilder patternBuilder) { + this.patternBuilder = patternBuilder; + } + + /** + * @return current {@link ConflictResolutionMode} + */ + public ConflictResolutionMode getConflictResolutionMode() { + return conflictResolutionMode; + } + + /** + * Defines what happens when before explicitly added {@link Node} has to be replaced by another {@link Node} + * @param conflictResolutionMode to be applied mode + * @return this to support fluent API + */ + public LiveStatementsBuilder setConflictResolutionMode(ConflictResolutionMode conflictResolutionMode) { + this.conflictResolutionMode = conflictResolutionMode; + return this; + } + + public LiveStatementsBuilder byVariableName(String variableName) { + patternBuilder.patternQuery + .filterChildren(new TypeFilter<>(CtVariableReference.class)) + .map((CtVariableReference varRef) -> { + return variableName.equals(varRef.getSimpleName()) ? varRef.getParent(CtStatement.class) : null; + }).forEach((CtStatement stmt) -> { + //called for first parent statement of all variables named `variableName` + stmt.accept(new CtAbstractVisitor() { + @Override + public void visitCtForEach(CtForEach foreach) { + markLive(foreach); + } + @Override + public void visitCtIf(CtIf ifElement) { + markLive(ifElement); + } + }); + }); + return this; + } + + public LiveStatementsBuilder markLive(CtForEach foreach) { + //detect meta elements by different way - e.g. comments? + Node vr = patternBuilder.getPatternNode(foreach.getExpression()); + if ((vr instanceof PrimitiveMatcher) == false) { + throw new SpoonException("Each live `for(x : iterable)` statement must have defined pattern parameter for `iterable` expression"); + } + PrimitiveMatcher parameterOfExpression = (PrimitiveMatcher) vr; +// PatternBuilder localPatternBuilder = patternBuilder.create(bodyToStatements(foreach.getBody())); + ForEachNode mvr = new ForEachNode(); + mvr.setIterableParameter(parameterOfExpression); + CtLocalVariable lv = foreach.getVariable(); + //create locally unique name of this local parameter + String paramName = lv.getSimpleName(); + patternBuilder.configureLocalParameters(pb -> { + pb.parameter(paramName).byVariable(lv); + mvr.setLocalParameter(pb.getCurrentParameter()); + }); + mvr.setNestedModel(patternBuilder.getPatternNode(foreach, CtRole.BODY, CtRole.STATEMENT)); + /* + * create Substitution request for whole `foreach`, + * resolve the expressions at substitution time + * and substitute the body of `foreach` as subpattern + * 0 or more times - once for each value of Iterable expression. + */ + patternBuilder.setNodeOfElement(foreach, mvr, conflictResolutionMode); + return this; + } + + public LiveStatementsBuilder markLive(CtIf ifElement) { + SwitchNode osp = new SwitchNode(); + boolean[] canBeLive = new boolean[]{true}; + forEachIfCase(ifElement, (expression, block) -> { + //detect meta elements by different way - e.g. comments? + if (expression != null) { + //expression is not null, it is: if(expression) {} + Node vrOfExpression = patternBuilder.getPatternNode(expression); + if (vrOfExpression instanceof ParameterNode == false) { + if (failOnMissingParameter) { + throw new SpoonException("Each live `if` statement must have defined pattern parameter in expression. If you want to ignore this, then call LiveStatementsBuilder#setFailOnMissingParameter(false) first."); + } else { + canBeLive[0] = false; + return; + } + } + if (vrOfExpression instanceof PrimitiveMatcher) { + osp.addCase((PrimitiveMatcher) vrOfExpression, getPatternNode(bodyToStatements(block))); + } else { + throw new SpoonException("Live `if` statement have defined single value pattern parameter in expression. But there is " + vrOfExpression.getClass().getName()); + } + } else { + //expression is null, it is: else {} + osp.addCase(null, getPatternNode(bodyToStatements(block))); + } + }); + if (canBeLive[0]) { + /* + * create Substitution request for whole `if`, + * resolve the expressions at substitution time and substitute only the `if` then/else statements, not `if` itself. + */ + patternBuilder.setNodeOfElement(ifElement, osp, conflictResolutionMode); + } + return this; + } + + private ListOfNodes getPatternNode(List template) { + List nodes = new ArrayList<>(template.size()); + for (CtElement element : template) { + nodes.add(patternBuilder.getPatternNode(element)); + } + return new ListOfNodes(nodes); + } + + /** + * calls function once for each expression/then block and at the end calls function for last else block. + * + * @param ifElement + * @param consumer + * @return true if all function calls returns true or if there is no function call + */ + private void forEachIfCase(CtIf ifElement, BiConsumer, CtStatement> consumer) { + consumer.accept(ifElement.getCondition(), ifElement.getThenStatement()); + CtStatement elseStmt = getElseIfStatement(ifElement.getElseStatement()); + if (elseStmt instanceof CtIf) { + //another else if case + forEachIfCase((CtIf) elseStmt, consumer); + } else if (elseStmt != null) { + //last else + consumer.accept(null, elseStmt); + } + } + + private CtStatement getElseIfStatement(CtStatement elseStmt) { + if (elseStmt instanceof CtBlock) { + CtBlock block = (CtBlock) elseStmt; + if (block.isImplicit()) { + List stmts = block.getStatements(); + if (stmts.size() == 1) { + if (stmts.get(0) instanceof CtIf) { + return stmts.get(0); + } + } + } + } + if (elseStmt instanceof CtIf) { + return (CtIf) elseStmt; + } + return elseStmt; + } + + public boolean isFailOnMissingParameter() { + return failOnMissingParameter; + } + + /** + * @param failOnMissingParameter set true if it should fail when some statement cannot be handled as live + * set false if ssuch statement should be kept as part of template. + * @return this to support fluent API + */ + public LiveStatementsBuilder setFailOnMissingParameter(boolean failOnMissingParameter) { + this.failOnMissingParameter = failOnMissingParameter; + return this; + } +} diff --git a/src/main/java/spoon/pattern/ModelNode.java b/src/main/java/spoon/pattern/ModelNode.java new file mode 100644 index 00000000000..4fb0ed48047 --- /dev/null +++ b/src/main/java/spoon/pattern/ModelNode.java @@ -0,0 +1,164 @@ +/** + * 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.pattern; + +import java.util.List; + +/** + * The AST model based parameterized model, which can generate or match other AST models. + * + * The instance is created by {@link PatternBuilder} + */ +public class ModelNode extends ListOfNodes { + + ModelNode(List nodes) { + super(nodes); + } + +/* + private static class SubstReqOnPosition { + final int sourceStart; + final CtElement sourceElement; + final Node valueResolver; + SubstReqOnPosition(CtElement sourceElement, Node substReq) { + this.sourceElement = sourceElement; + this.sourceStart = getSourceStart(sourceElement); + this.valueResolver = substReq; + } + + @Override + public String toString() { + return String.valueOf(sourceStart) + ":" + valueResolver; + } + } + + + @Override + public String toString() { + Factory f = getFactory(); + Environment env = f.getEnvironment(); + final List allRequestWithSourcePos = new ArrayList<>(); + for (Map.Entry e : patternElementToSubstRequests.entrySet()) { + allRequestWithSourcePos.add(new SubstReqOnPosition(e.getKey(), e.getValue())); + } + allRequestWithSourcePos.sort((a, b) -> a.sourceStart - b.sourceStart); + class Iter { + int off = 0; + List getAndRemoveRequestUntil(int sourcePos) { + List res = new ArrayList<>(); + while (off < allRequestWithSourcePos.size() && allRequestWithSourcePos.get(off).sourceStart <= sourcePos) { + res.add(allRequestWithSourcePos.get(off)); + off++; + } + return res; + } + } + Iter iter = new Iter(); +// PrinterHelper printerHelper = new PrinterHelper(env); +// DefaultTokenWriter tokenWriter = new DefaultTokenWriter(printerHelper); + DefaultJavaPrettyPrinter printer = new DefaultJavaPrettyPrinter(env) { + protected void enter(CtElement e) { + int sourceStart = getSourceStart(e); + List requestOnPos = iter.getAndRemoveRequestUntil(sourceStart); + if (requestOnPos.size() > 0) { + getPrinterTokenWriter() + .writeComment(f.createComment(getSubstitutionRequestsDescription(e, sourceStart, requestOnPos), CommentType.BLOCK)) + .writeln(); + } + } + + }; + try { + for (CtElement ele : patternModel) { + printer.computeImports(ele); + } + for (CtElement ele : patternModel) { + printer.scan(ele); + } + } catch (ParentNotInitializedException ignore) { + return "Failed with: " + ignore.toString(); + } + // in line-preservation mode, newlines are added at the beginning to matches the lines + // removing them from the toString() representation + return printer.toString().replaceFirst("^\\s+", ""); + } + + private static int getSourceStart(CtElement ele) { + while (true) { + SourcePosition sp = ele.getPosition(); + if (sp != null && sp.getSourceStart() >= 0) { + //we have found a element with source position + return sp.getSourceStart(); + } + if (ele.isParentInitialized() == false) { + return -1; + } + ele = ele.getParent(); + } + } + + private String getSubstitutionRequestsDescription(CtElement ele, int sourceStart, List requestsOnPos) { + //sort requestsOnPos by their path + Map reqByPath = new TreeMap<>(); + StringBuilder sb = new StringBuilder(); + for (SubstReqOnPosition reqPos : requestsOnPos) { + sb.setLength(0); + appendPathIn(sb, reqPos.sourceElement, ele); + String path = sb.toString(); + reqByPath.put(path, reqPos); + } + + PrinterHelper printer = new PrinterHelper(getFactory().getEnvironment()); + //all comments in Spoon are using \n as separator + printer.setLineSeparator("\n"); + printer.write(getElementTypeName(ele)).incTab(); + for (Map.Entry e : reqByPath.entrySet()) { + printer.writeln(); + boolean isLate = e.getValue().sourceStart != sourceStart; + if (isLate) { + printer.write("!").write(String.valueOf(e.getValue().sourceStart)).write("!=").write(String.valueOf(sourceStart)).write("!"); + } + printer.write(e.getKey()).write('/'); + printer.write(" <= ").write(e.getValue().valueResolver.toString()); + } + return printer.toString(); + } + + private boolean appendPathIn(StringBuilder sb, CtElement element, CtElement parent) { + if (element != parent && element != null) { + CtRole roleInParent = element.getRoleInParent(); + if (roleInParent == null) { + return false; + } + if (appendPathIn(sb, element.getParent(), parent)) { + sb.append("/").append(getElementTypeName(element.getParent())); + } + sb.append(".").append(roleInParent.getCamelCaseName()); + return true; + } + return false; + }; + + static String getElementTypeName(CtElement element) { + String name = element.getClass().getSimpleName(); + if (name.endsWith("Impl")) { + return name.substring(0, name.length() - 4); + } + return name; + } +*/ +} diff --git a/src/main/java/spoon/pattern/NamedItemAccessor.java b/src/main/java/spoon/pattern/NamedItemAccessor.java new file mode 100644 index 00000000000..646543d1419 --- /dev/null +++ b/src/main/java/spoon/pattern/NamedItemAccessor.java @@ -0,0 +1,135 @@ +/** + * 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.pattern; + +import java.util.Map; +import java.util.function.Function; + +/** + * A kind of {@link ParameterInfo} which returns value by the named parameter + * From a container of type {@link ParameterValueProvider} or {@link Map} + */ +public class NamedItemAccessor extends AbstractItemAccessor { + + private final String name; + + public NamedItemAccessor(String name) { + this(name, null); + } + public NamedItemAccessor(AbstractItemAccessor next) { + this(null, next); + } + public NamedItemAccessor(String name, AbstractItemAccessor next) { + super(next); + this.name = name; + } + + @Override + protected String getPlainName() { + return getWrappedName(getContainerName()); + } + + @Override + protected String getWrappedName(String containerName) { + if (name == null) { + return containerName; + } + if (containerName.length() > 0) { + containerName += "."; + } + return containerName + name; + } + + @Override + protected Object addValueAs(Object container, Function merger) { + ParameterValueProvider parameters = castTo(container, ParameterValueProvider.class); + if (name == null) { + //This accessor matches any entry - has no predefined key + Object newValue = merger.apply(null); + if (newValue == null) { + //The accessor has no key, so null value means null Entry so nothing to add. Keep existing map + return parameters; + } + if (newValue == NO_MERGE) { + return NO_MERGE; + } + if (newValue instanceof Map.Entry) { + Map.Entry newEntry = (Map.Entry) newValue; + String newEntryKey = (String) newEntry.getKey(); + Object existingValue = parameters.get(newEntryKey); + Object newEntryValue = merge(existingValue, newEntry.getValue()); + if (newEntryValue == NO_MERGE) { + return NO_MERGE; + } + if (existingValue == newEntryValue) { + //it is already there + return parameters; + } + return parameters.putIntoCopy(newEntryKey, newEntryValue); + } + if (newValue instanceof Map) { + Map newMap = (Map) newValue; + for (Map.Entry newEntry : newMap.entrySet()) { + String newEntryKey = newEntry.getKey(); + Object existingValue = parameters.get(newEntryKey); + Object newEntryValue = merge(existingValue, newEntry.getValue()); + if (newEntryValue == NO_MERGE) { + return NO_MERGE; + } + if (existingValue != newEntryValue) { + //it is not there yet. Add it + parameters = parameters.putIntoCopy(newEntryKey, newEntryValue); + } + //it is there, continue to check next entry + } + return parameters; + } + //only Map.Entries can be added to the Map if there is missing key + return NO_MERGE; + } + Object existingValue = parameters.get(name); + Object newValue = merger.apply(existingValue); + if (newValue == NO_MERGE) { + return NO_MERGE; + } + if (existingValue == newValue) { + //it is already there. + return parameters; + } + return parameters.putIntoCopy(name, newValue); + } + + @Override + protected Object getValue(ParameterValueProvider parameters) { + ParameterValueProvider map = castTo(super.getValue(parameters), ParameterValueProvider.class); + return name == null ? map : map.get(name); + } + + @Override + protected T castTo(Object o, Class type) { + if (o instanceof Map) { + o = new UnmodifiableParameterValueProvider((Map) o); + } + return super.castTo(o, type); + } + + private static final ParameterValueProvider EMPTY = new UnmodifiableParameterValueProvider(); + @Override + protected ParameterValueProvider getEmptyContainer() { + return EMPTY; + } +} diff --git a/src/main/java/spoon/pattern/Node.java b/src/main/java/spoon/pattern/Node.java new file mode 100644 index 00000000000..93d535169ce --- /dev/null +++ b/src/main/java/spoon/pattern/Node.java @@ -0,0 +1,114 @@ +/** + * 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.pattern; + +import java.util.List; +import java.util.function.BiConsumer; + +import spoon.pattern.matcher.Matchers; +import spoon.pattern.matcher.TobeMatched; +import spoon.reflect.factory.Factory; + +/** + * Represents a parameterized Pattern ValueResolver, which can be used + *
    + *
  • to generate a zero, one or more copies of model using provided parameters
  • + *
  • to match zero, one or more instances of model and deliver a matching parameters
  • + *
+ */ +public interface Node extends Matchers { + /** + * Calls consumer for each pair of parameter definition ({@link ParameterInfo}) and {@link Node}, which uses it + * @param consumer the receiver of pairs of {@link ParameterInfo} and {@link Node} + */ + void forEachParameterInfo(BiConsumer consumer); + + /** + * Generates zero, one or more target depending on kind of this {@link Node}, expected `result` and input `parameters` + * @param factory TODO + */ + void generateTargets(Factory factory, ResultHolder result, ParameterValueProvider parameters); + + /** + * Generates one target depending on kind of this {@link Node}, expected `expectedType` and input `parameters` + * @param factory TODO + * @param parameters {@link ParameterValueProvider} + * @param expectedType defines {@link Class} of returned value + * + * @return a generate value or null + */ + default T generateTarget(Factory factory, ParameterValueProvider parameters, Class expectedType) { + ResultHolder.Single result = new ResultHolder.Single<>(expectedType); + generateTargets(factory, result, parameters); + return result.getResult(); + } + + /** + * Generates zero, one or more targets depending on kind of this {@link Node}, expected `expectedType` and input `parameters` + * @param factory TODO + * @param parameters {@link ParameterValueProvider} + * @param expectedType defines {@link Class} of returned value + * + * @return a {@link List} of generated targets + */ + default List generateTargets(Factory factory, ParameterValueProvider parameters, Class expectedType) { + ResultHolder.Multiple result = new ResultHolder.Multiple<>(expectedType); + generateTargets(factory, result, parameters); + return result.getResult(); + } + + /** + * @param targets to be matched target nodes and input parameters + * @param nextMatchers Chain of matchers which has to be processed after this {@link Node} + * @return new parameters and container with remaining targets + */ + TobeMatched matchTargets(TobeMatched targets, Matchers nextMatchers); + + /** + * The special implementation of {@link Matchers}, which is used as last {@link Node} in case when ALL target nodes must match with all template nodes + */ + Matchers MATCH_ALL = new Matchers() { + @Override + public TobeMatched matchAllWith(TobeMatched targets) { + //It matches only when there is no remaining target element + return targets.hasTargets() ? null : targets; + } + }; + /** + * The special implementation of {@link Matchers}, which is used as last {@link Node} in case when SOME target nodes must match with all template nodes + */ + Matchers MATCH_PART = new Matchers() { + @Override + public TobeMatched matchAllWith(TobeMatched targets) { + //There can remain some unmatched target(s) - it is OK in this context. + return targets; + } + }; + + @Override + default TobeMatched matchAllWith(TobeMatched targets) { + return matchTargets(targets, MATCH_PART); + } + + /** + * @param oldNode old {@link Node} + * @param newNode new {@link Node} + * @return a true if `oldNode` was found in this {@link Node} or it's children and replaced by `newNode` + * false if `oldNode` was not found + */ + boolean replaceNode(Node oldNode, Node newNode); +} diff --git a/src/main/java/spoon/pattern/ParameterInfo.java b/src/main/java/spoon/pattern/ParameterInfo.java new file mode 100644 index 00000000000..5eb3eca1e33 --- /dev/null +++ b/src/main/java/spoon/pattern/ParameterInfo.java @@ -0,0 +1,85 @@ +/** + * 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.pattern; + +import spoon.pattern.matcher.Quantifier; + +/** + * Represents the parameter of {@link Pattern} + * defines acceptable value of parameter value during matching. For example type, filter on attribute values. + */ +public interface ParameterInfo { + int UNLIMITED_OCCURENCES = Integer.MAX_VALUE; + + /** + * @return the full name of the parameter from the root of parameter container to the value represented by this {@link ParameterInfo} + */ + String getName(); + + /** + * Matches `value` into `parameters` under the name/structure defined by this ParameterInfo. + * 1) checks that value matches with the {@link #matchCondition} + * 2) creates new copy of {@link ParameterValueProvider} which contains the new `value` and returns that copy + * + * @param parameters + * @param value + * @return copy of `parameters` with new value or existing `parameters` if value is already there or null if value doesn't fit into these parameters + */ + ParameterValueProvider addValueAs(ParameterValueProvider parameters, Object value); + + void getValueAs(ResultHolder result, ParameterValueProvider parameters); + + /** + * @return true if the value container has to be a List, otherwise the container will be a single value + */ + boolean isMultiple(); + + /** + * @return a type of parameter value - if known + * + * Note: Pattern builder needs to know the value type to be able to select substitute node. + * For example patter: + * return _expression_.S(); + * either replaces only `_expression_.S()` if the parameter value is an expression + * or replaces `return _expression_.S()` if the parameter value is a CtBlock + */ + Class getParameterValueType(); + + /** + * @return the strategy used to resolve conflict between two {@link Node}s + */ + Quantifier getMatchingStrategy(); + + /** + * @return true if this matcher can be applied more then once in the same container of targets + * Note: even if false, it may be applied again to another container and to match EQUAL value + */ + boolean isRepeatable(); + + /** + * @param parameters matching parameters + * @return true if the ValueResolver of this parameter MUST match with next target in the state defined by current `parameters`. + * false if match is optional + */ + boolean isMandatory(ParameterValueProvider parameters); + + /** + * @param parameters matching parameters + * @return true if the ValueResolver of this parameter should be processed again to match next target in the state defined by current `parameters`. + */ + boolean isTryNextMatch(ParameterValueProvider parameters); +} diff --git a/src/main/java/spoon/pattern/ParameterNode.java b/src/main/java/spoon/pattern/ParameterNode.java new file mode 100644 index 00000000000..44e8b33685e --- /dev/null +++ b/src/main/java/spoon/pattern/ParameterNode.java @@ -0,0 +1,86 @@ +/** + * 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.pattern; + +import java.util.function.BiConsumer; + +import spoon.pattern.matcher.Quantifier; +import spoon.reflect.declaration.CtElement; +import spoon.reflect.factory.Factory; + +/** + * Represents pattern model variable + * Delivers/Matches 0, 1 or more values of defined parameter. + * The values may have type which extends {@link CtElement} or any other type of some SpoonModel attribute. E.g. String + */ +public class ParameterNode extends AbstractPrimitiveMatcher { + private final ParameterInfo parameterInfo; + + public ParameterNode(ParameterInfo parameterInfo) { + super(); + this.parameterInfo = parameterInfo; + } + + @Override + public boolean replaceNode(Node oldNode, Node newNode) { + return false; + } + + @Override + public void generateTargets(Factory factory, ResultHolder result, ParameterValueProvider parameters) { + parameterInfo.getValueAs(result, parameters); + } + + @Override + public ParameterValueProvider matchTarget(Object target, ParameterValueProvider parameters) { + return parameterInfo.addValueAs(parameters, target); + } + + public ParameterInfo getParameterInfo() { + return parameterInfo; + } + + @Override + public boolean isRepeatable() { + return parameterInfo.isRepeatable(); + } + + @Override + public boolean isMandatory(ParameterValueProvider parameters) { + return parameterInfo.isMandatory(parameters); + } + + @Override + public boolean isTryNextMatch(ParameterValueProvider parameters) { + return parameterInfo.isTryNextMatch(parameters); + } + + @Override + public Quantifier getMatchingStrategy() { + return parameterInfo.getMatchingStrategy(); + } + + @Override + public void forEachParameterInfo(BiConsumer consumer) { + consumer.accept(parameterInfo, this); + } + + @Override + public String toString() { + return "${" + parameterInfo + "}"; + } +} diff --git a/src/main/java/spoon/pattern/ParameterValueProvider.java b/src/main/java/spoon/pattern/ParameterValueProvider.java new file mode 100644 index 00000000000..d508623e36f --- /dev/null +++ b/src/main/java/spoon/pattern/ParameterValueProvider.java @@ -0,0 +1,40 @@ +/** + * 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.pattern; + +import java.util.Map; + +/** + * During substitution process it provides values of parameters from underlying storage (e.g. from Map or an instance of an class) + * During matching process it sets values of matched parameters into underlying storage + * TODO: create ParameterValueProviderFactory which creates appropriate instances of ParameterValueProviders during matching process + */ +public interface ParameterValueProvider { + + /** + * @param parameterName to be checked parameter name + * @return true if there is defined some value for the parameter. null can be a value too + */ + boolean hasValue(String parameterName); + Object get(String parameterName); + ParameterValueProvider putIntoCopy(String parameterName, Object value); + + Map asMap(); + + ParameterValueProvider createLocalParameterValueProvider(); + Map asLocalMap(); +} diff --git a/src/main/java/spoon/pattern/ParameterValueProviderFactory.java b/src/main/java/spoon/pattern/ParameterValueProviderFactory.java new file mode 100644 index 00000000000..293ef3469a4 --- /dev/null +++ b/src/main/java/spoon/pattern/ParameterValueProviderFactory.java @@ -0,0 +1,24 @@ +/** + * 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.pattern; + +/** + * Creates appropriate instances of {@link ParameterValueProvider} during matching process + */ +public interface ParameterValueProviderFactory { + ParameterValueProvider createParameterValueProvider(); +} diff --git a/src/main/java/spoon/pattern/ParametersBuilder.java b/src/main/java/spoon/pattern/ParametersBuilder.java new file mode 100644 index 00000000000..017f40ec29b --- /dev/null +++ b/src/main/java/spoon/pattern/ParametersBuilder.java @@ -0,0 +1,694 @@ +/** + * 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.pattern; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; + +import spoon.SpoonException; +import spoon.pattern.matcher.Quantifier; +import spoon.reflect.code.CtArrayAccess; +import spoon.reflect.code.CtBlock; +import spoon.reflect.code.CtExpression; +import spoon.reflect.code.CtFieldRead; +import spoon.reflect.code.CtInvocation; +import spoon.reflect.code.CtLiteral; +import spoon.reflect.code.CtReturn; +import spoon.reflect.code.CtVariableAccess; +import spoon.reflect.declaration.CtElement; +import spoon.reflect.declaration.CtMethod; +import spoon.reflect.declaration.CtNamedElement; +import spoon.reflect.declaration.CtType; +import spoon.reflect.declaration.CtVariable; +import spoon.reflect.meta.ContainerKind; +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.CtReference; +import spoon.reflect.reference.CtTypeReference; +import spoon.reflect.reference.CtVariableReference; +import spoon.reflect.visitor.CtScanner; +import spoon.reflect.visitor.Filter; +import spoon.reflect.visitor.chain.CtQueryable; +import spoon.reflect.visitor.filter.InvocationFilter; +import spoon.reflect.visitor.filter.NamedElementFilter; +import spoon.reflect.visitor.filter.PotentialVariableDeclarationFunction; +import spoon.reflect.visitor.filter.VariableReferenceFunction; +import spoon.template.TemplateParameter; + +import static spoon.pattern.PatternBuilder.getLocalTypeRefBySimpleName; + +/** + * Used to define Pattern parameters and their mapping to Pattern model + */ +public class ParametersBuilder { + private final PatternBuilder patternBuilder; + private final Map parameterInfos; + private AbstractItemAccessor currentParameter; + private ConflictResolutionMode conflictResolutionMode = ConflictResolutionMode.FAIL; + + ParametersBuilder(PatternBuilder patternBuilder, Map parameterInfos) { + this.patternBuilder = patternBuilder; + this.parameterInfos = parameterInfos; + } + + /** + * @return current {@link ConflictResolutionMode} + */ + public ConflictResolutionMode getConflictResolutionMode() { + return conflictResolutionMode; + } + + /** + * Defines what happens when before explicitly added {@link Node} has to be replaced by another {@link Node} + * @param conflictResolutionMode to be applied mode + * @return this to support fluent API + */ + public ParametersBuilder setConflictResolutionMode(ConflictResolutionMode conflictResolutionMode) { + this.conflictResolutionMode = conflictResolutionMode; + return this; + } + + public CtQueryable queryModel() { + return patternBuilder.patternQuery; + } + + private AbstractItemAccessor getParameterInfo(String parameterName, boolean createIfNotExist) { + AbstractItemAccessor pi = parameterInfos.get(parameterName); + if (pi == null) { + pi = new NamedItemAccessor(parameterName).setValueConvertor(patternBuilder.getDefaultValueConvertor()); + parameterInfos.put(parameterName, pi); + } + return pi; + } + + /** + * 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 = getParameterInfo(paramName, true); + return this; + } + + public ParametersBuilder setMinOccurence(int minOccurence) { + currentParameter.setMinOccurences(minOccurence); + return this; + } + public ParametersBuilder setMaxOccurence(int maxOccurence) { + if (maxOccurence == ParameterInfo.UNLIMITED_OCCURENCES || maxOccurence > 1 && currentParameter.isMultiple() == false) { + throw new SpoonException("Cannot set maxOccurences > 1 for single value parameter. Call setMultiple(true) first."); + } + currentParameter.setMaxOccurences(maxOccurence); + return this; + } + public ParametersBuilder setMatchingStrategy(Quantifier quantifier) { + currentParameter.setMatchingStrategy(quantifier); + return this; + } + + /** + * Set expected type of Parameter. In some cases legacy Template needs to know the type of parameter value to select substituted element. + * See {@link ValueConvertor}, which provides conversion between matched element and expected parameter type + * @param valueType a expected type of parameter value + * @return this {@link ParametersBuilder} to support fluent API + */ + public ParametersBuilder setValueType(Class valueType) { + currentParameter.setParameterValueType(valueType); + return this; + } + + /** + * Defines type of parameter value (List/Set/Map/single). + * If not defined then real value type of property is used. If null, then default is {@link ContainerKind#SINGLE} + * @param containerKind to be used {@link ContainerKind} + * @return this {@link ParametersBuilder} to support fluent API + */ + public ParametersBuilder setContainerKind(ContainerKind containerKind) { + currentParameter.setContainerKind(containerKind); + return this; + } + + public ParameterInfo getCurrentParameter() { + if (currentParameter == null) { + throw new SpoonException("Parameter name must be defined first by call of #parameter(String) method."); + } + return currentParameter; + } + + /** + * `type` itself and all the references to the `type` are subject for substitution by current parameter + * @param type to be substituted Class + * @return {@link ParametersBuilder} to support fluent API + */ + public ParametersBuilder byType(Class type) { + return byType(type.getName()); + } + /** + * type identified by `typeQualifiedName` itself and all the references to that type are subject for substitution by current parameter + * @param typeQualifiedName a fully qualified name of to be substituted Class + * @return {@link ParametersBuilder} to support fluent API + */ + public ParametersBuilder byType(String typeQualifiedName) { + return byType(patternBuilder.getFactory().Type().createReference(typeQualifiedName)); + } + /** + * type referred by {@link CtTypeReference} `type` and all the references to that type are subject for substitution by current parameter + * @param type a fully qualified name of to be substituted Class + * @return {@link ParametersBuilder} to support fluent API + */ + public ParametersBuilder byType(CtTypeReference type) { + ParameterInfo pi = getCurrentParameter(); + //substitute all references to that type + queryModel().filterChildren((CtTypeReference typeRef) -> typeRef.equals(type)) + .forEach((CtTypeReference typeRef) -> { + addSubstitutionRequest(pi, typeRef); + }); + /** + * If Type itself is found part of model, then substitute it's simple name too + */ + String typeQName = type.getQualifiedName(); + CtType type2 = queryModel().filterChildren((CtType t) -> t.getQualifiedName().equals(typeQName)).first(); + if (type2 != null) { + //Substitute name of template too + 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; + } + + /** + * variable read/write of `variable` + * @param variableName a variable whose references will be substituted + * @return {@link ParametersBuilder} to support fluent API + */ + public ParametersBuilder byVariable(String variableName) { + CtVariable var = queryModel().map(new PotentialVariableDeclarationFunction(variableName)).first(); + if (var != null) { + byVariable(var); + } //else may be we should fail? + return this; + } + /** + * variable read/write of `variable` + * @param variable a variable whose references will be substituted + * @return {@link ParametersBuilder} to support fluent API + */ + public ParametersBuilder byVariable(CtVariable variable) { + ParameterInfo pi = getCurrentParameter(); + CtQueryable root = queryModel(); + if (patternBuilder.isInModel(variable)) { + //variable is part of model, start search from variable + root = variable; + } + root.map(new VariableReferenceFunction(variable)) + .forEach((CtVariableReference varRef) -> { + addSubstitutionRequest(pi, varRef); + }); + return this; + } + + /** + * each invocation of `method` will be replaces by parameter value + * @param method the method whose invocation has to be substituted + * @return {@link ParametersBuilder} to support fluent API + */ + public ParametersBuilder byInvocation(CtMethod method) { + ParameterInfo pi = getCurrentParameter(); + queryModel().filterChildren(new InvocationFilter(method)) + .forEach((CtInvocation inv) -> { + addSubstitutionRequest(pi, inv); + }); + return this; + } + + /** + * Add parameters for each field reference of variable named `variableName` + * @param variableName the name of the variable reference + * @return {@link ParametersBuilder} to support fluent API + */ + public ParametersBuilder parametersByVariable(String... variableName) { + for (String varName : variableName) { + CtVariable var = queryModel().map(new PotentialVariableDeclarationFunction(varName)).first(); + if (var != null) { + parametersByVariable(var); + } else { + List> vars = queryModel().filterChildren(new NamedElementFilter(CtVariable.class, varName)).list(); + if (vars.size() > 1) { + throw new SpoonException("Ambiguous variable " + varName); + } else if (vars.size() == 1) { + parametersByVariable(vars.get(0)); + } //else may be we should fail when variable is not found? + } + } + return this; + } + public ParametersBuilder parametersByVariable(CtVariable variable) { + CtQueryable searchScope; + if (patternBuilder.isInModel(variable)) { + addSubstitutionRequest( + parameter(variable.getSimpleName()).getCurrentParameter(), + variable); + searchScope = variable; + } else { + searchScope = queryModel(); + } + searchScope.map(new VariableReferenceFunction(variable)) + .forEach((CtVariableReference varRef) -> { + CtFieldRead fieldRead = varRef.getParent(CtFieldRead.class); + if (fieldRead != null) { + addSubstitutionRequest( + parameter(fieldRead.getVariable().getSimpleName()).getCurrentParameter(), + fieldRead); + } else { + addSubstitutionRequest( + parameter(varRef.getSimpleName()).getCurrentParameter(), + varRef); + } + }); + return this; + } + + /** + * variable read/write of `variable` of type {@link TemplateParameter} + * @param variable a variable whose references will be substituted + * @return {@link ParametersBuilder} to support fluent API + */ + public ParametersBuilder byTemplateParameterReference(CtVariable variable) { + ParameterInfo pi = getCurrentParameter(); + queryModel().map(new VariableReferenceFunction(variable)) + .forEach((CtVariableReference varRef) -> { + /* + * the target of substitution is always the invocation of TemplateParameter#S() + */ + CtVariableAccess varAccess = (CtVariableAccess) varRef.getParent(); + CtElement invocationOfS = varAccess.getParent(); + if (invocationOfS instanceof CtInvocation) { + CtInvocation invocation = (CtInvocation) invocationOfS; + if ("S".equals(invocation.getExecutable().getSimpleName())) { + addSubstitutionRequest(pi, invocation); + return; + } + } + throw new SpoonException("TemplateParameter reference is NOT used as target of invocation of TemplateParameter#S()"); + }); + return this; + } + + /** + * CodeElement element identified by `simpleName` + * @param simpleName the name of the element or reference + * @return {@link ParametersBuilder} to support fluent API + */ +// public ParametersBuilder codeElementBySimpleName(String simpleName) { +// ParameterInfo pi = getCurrentParameter(); +// pattern.getModel().filterChildren((CtNamedElement named) -> simpleName.equals(named.getSimpleName())) +// .forEach((CtNamedElement named) -> { +// if (named instanceof CtCodeElement) { +// addSubstitutionRequest(pi, named); +// } +// }); +// pattern.getModel().filterChildren((CtReference ref) -> simpleName.equals(ref.getSimpleName())) +// .forEach((CtReference ref) -> { +// if (ref instanceof CtTypeReference) { +// return; +// } +// CtCodeElement codeElement = ref.getParent(CtCodeElement.class); +// if (codeElement != null) { +// addSubstitutionRequest(pi, codeElement); +// } +// }); +// return this; +// } + + /** + * All spoon model string attributes whose value is equal to `stringMarker` + * are subject for substitution by current parameter + * @param stringMarker a string value which has to be substituted + * @return {@link ParametersBuilder} to support fluent API + */ + public ParametersBuilder byString(String stringMarker) { + ParameterInfo pi = getCurrentParameter(); + new StringAttributeScanner() { + @Override + protected void visitStringAttribute(RoleHandler roleHandler, CtElement element, String value) { + if (stringMarker.equals(value)) { + addSubstitutionRequest(pi, element, roleHandler.getRole()); + } + } + }.scan(patternBuilder.getPatternModel()); + return this; + } + + /** + * All spoon model string attributes whose value contains whole string or a substring equal to `stringMarker` + * are subject for substitution by current parameter. Only the `stringMarker` substring of the string value is substituted! + * @param stringMarker a string value which has to be substituted + * @return {@link ParametersBuilder} to support fluent API + */ + public ParametersBuilder bySubstring(String stringMarker) { + ParameterInfo pi = getCurrentParameter(); + new StringAttributeScanner() { + @Override + protected void visitStringAttribute(RoleHandler roleHandler, CtElement element, String value) { + if (value != null && value.indexOf(stringMarker) >= 0) { + addSubstitutionRequest(pi, element, roleHandler.getRole(), stringMarker); + } + } + }.scan(patternBuilder.getPatternModel()); + return this; + } + + /** + * CtScanner implementation, which calls {@link #visitStringAttribute(RoleHandler, CtElement, String)} + * for each String attribute of each {@link CtElement} of scanned AST + */ + private abstract static class StringAttributeScanner extends CtScanner { + /** + * List of all Spoon model {@link RoleHandler}s, which provides access to attribute value of type String + */ + private static List stringAttributeRoleHandlers = new ArrayList<>(); + static { + RoleHandlerHelper.forEachRoleHandler(rh -> { + if (rh.getValueClass().isAssignableFrom(String.class)) { + //accept String and Object class + stringAttributeRoleHandlers.add(rh); + } + }); + } + + @Override + public void scan(CtElement element) { + visitStringAttribute(element); + super.scan(element); + } + private void visitStringAttribute(CtElement element) { + for (RoleHandler roleHandler : stringAttributeRoleHandlers) { + if (roleHandler.getTargetType().isInstance(element)) { + Object value = roleHandler.getValue(element); + if (value instanceof String) { + visitStringAttribute(roleHandler, element, (String) value); + } + //else it is a CtLiteral with non string value + } + } + } + protected abstract void visitStringAttribute(RoleHandler roleHandler, CtElement element, String value); + } + + + /** + * Any named element or reference identified by it's simple name + * @param simpleName simple name of {@link CtNamedElement} or {@link CtReference} + * @return {@link ParametersBuilder} to support fluent API + */ + public ParametersBuilder bySimpleName(String simpleName) { + byNamedElementSimpleName(simpleName); + byReferenceSimpleName(simpleName); + return this; + } + + /** + * Any named element by it's simple name + * @param simpleName simple name of {@link CtNamedElement} + * @return {@link ParametersBuilder} to support fluent API + */ + public ParametersBuilder byNamedElementSimpleName(String simpleName) { + ParameterInfo pi = getCurrentParameter(); + queryModel().filterChildren((CtNamedElement named) -> simpleName.equals(named.getSimpleName())) + .forEach((CtNamedElement named) -> { + addSubstitutionRequest(pi, named); + }); + return this; + } + + /** + * Any reference identified by it's simple name + * @param simpleName simple name of {@link CtReference} + * @return {@link ParametersBuilder} to support fluent API + */ + public ParametersBuilder byReferenceSimpleName(String simpleName) { + ParameterInfo pi = getCurrentParameter(); + queryModel().filterChildren((CtReference ref) -> simpleName.equals(ref.getSimpleName())) + .forEach((CtReference ref) -> { + addSubstitutionRequest(pi, ref); + }); + return this; + } + + /** + * All elements matched by {@link Filter} will be substituted by parameter value + * @param filter {@link Filter}, which defines to be substituted elements + * @return {@link ParametersBuilder} to support fluent API + */ + public ParametersBuilder byFilter(Filter filter) { + ParameterInfo pi = getCurrentParameter(); + queryModel().filterChildren(filter) + .forEach((CtElement ele) -> { + addSubstitutionRequest(pi, ele); + }); + return this; + } + + /** + * Attribute defined by `role` of all elements matched by {@link Filter} will be substituted by parameter value + * @param role {@link CtRole}, which defines to be substituted elements + * @param filter {@link Filter}, which defines to be substituted elements + * @return {@link ParametersBuilder} to support fluent API + */ + public ParametersBuilder attributeOfElementByFilter(CtRole role, Filter filter) { + ParameterInfo pi = getCurrentParameter(); + queryModel().filterChildren(filter) + .forEach((CtElement ele) -> { + addSubstitutionRequest(pi, ele, role); + }); + return this; + } + + public ParametersBuilder matchCondition(Class type, Predicate matchCondition) { + currentParameter.setMatchCondition(type, matchCondition); + return this; + } + + public boolean isSubstituted(String paramName) { + if (patternBuilder.hasParameterInfo(paramName) == false) { + return false; + } + ParameterInfo pi = getParameterInfo(paramName, false); + if (pi == null) { + return false; + } + class Result { + boolean isUsed = false; + } + Result result = new Result(); + patternBuilder.forEachNodeOfParameter(pi, parameterized -> result.isUsed = true); + return result.isUsed; + } + + void addSubstitutionRequest(ParameterInfo parameter, CtElement element) { + ParameterElementPair pep = getSubstitutedNodeOfElement(parameter, element); + patternBuilder.setNodeOfElement(pep.element, new ParameterNode(pep.parameter), conflictResolutionMode); + } + /** + * Adds request to substitute value of `attributeRole` of `element`, by the value of this {@link ModelNode} parameter {@link ParameterInfo} value + * @param element whose attribute of {@link CtRole} `attributeRole` have to be replaced + */ + void addSubstitutionRequest(ParameterInfo parameter, CtElement element, CtRole attributeRole) { + patternBuilder.setNodeOfAttributeOfElement(element, attributeRole, new ParameterNode(parameter), conflictResolutionMode); + } + /** + * Adds request to substitute substring of {@link String} value of `attributeRole` of `element`, by the value of this {@link ModelNode} parameter {@link ParameterInfo} value + * @param element whose part of String attribute of {@link CtRole} `attributeRole` have to be replaced + */ + void addSubstitutionRequest(ParameterInfo parameter, CtElement element, CtRole attributeRole, String subStringMarker) { + patternBuilder.modifyNodeOfAttributeOfElement(element, attributeRole, conflictResolutionMode, oldAttrNode -> { + StringNode stringNode = null; + if (oldAttrNode instanceof ConstantNode) { + ConstantNode constantNode = (ConstantNode) oldAttrNode; + if (constantNode.getTemplateNode() instanceof String) { + stringNode = new StringNode((String) constantNode.getTemplateNode()); + } + } else if (oldAttrNode instanceof StringNode) { + stringNode = (StringNode) oldAttrNode; + } + if (stringNode == null) { + throw new SpoonException("Cannot add StringNode"); + } + stringNode.setReplaceMarker(subStringMarker, parameter); + return stringNode; + }); + } + + public static class ParameterElementPair { + final ParameterInfo parameter; + final CtElement element; + public ParameterElementPair(ParameterInfo parameter, CtElement element) { + super(); + this.parameter = parameter; + this.element = element; + } + public ParameterElementPair copyAndSet(ParameterInfo param) { + return new ParameterElementPair(param, element); + } + public ParameterElementPair copyAndSet(CtElement element) { + return new ParameterElementPair(parameter, element); + } + } + + public ParameterElementPair getSubstitutedNodeOfElement(ParameterInfo parameter, CtElement element) { + ParameterElementPair parameterElementPair = new ParameterElementPair(parameter, element); + parameterElementPair = transformVariableAccessToVariableReference(parameterElementPair); + parameterElementPair = transformArrayAccess(parameterElementPair); + parameterElementPair = transformTemplateParameterInvocationOfS(parameterElementPair); + parameterElementPair = transformExecutableRefToInvocation(parameterElementPair); + parameterElementPair = transformCtReturnIfNeeded(parameterElementPair); + //if spoon creates an implicit parent (e.g. CtBlock) around the pattern parameter, then replace that implicit parent + parameterElementPair = getLastImplicitParent(parameterElementPair); + return parameterElementPair; + } + + /** + * for input `element` expression `X` in expression `X[Y]` it returns expression `X[Y]` + * and registers extra {@link ListAccessor} to the parameter assigned to `X` + * @param parameter TODO + * @param valueResolver + * @param element + * @return + */ + private ParameterElementPair transformArrayAccess(ParameterElementPair pep) { + CtElement element = pep.element; + if (element.isParentInitialized()) { + CtElement parent = element.getParent(); + if (parent instanceof CtArrayAccess) { + CtArrayAccess arrayAccess = (CtArrayAccess) parent; + CtExpression expr = arrayAccess.getIndexExpression(); + if (expr instanceof CtLiteral) { + CtLiteral idxLiteral = (CtLiteral) expr; + Object idx = idxLiteral.getValue(); + if (idx instanceof Number) { + return new ParameterElementPair(new ListAccessor(((Number) idx).intValue(), pep.parameter), arrayAccess); + } + } + } + } + return pep; + } + + /** + * @return a node, which has to be substituted instead of variable reference `varRef` + */ + private ParameterElementPair transformVariableAccessToVariableReference(ParameterElementPair pep) { + if (pep.element instanceof CtVariableReference) { + CtVariableReference varRef = (CtVariableReference) pep.element; + /* + * 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 + */ + return pep.copyAndSet(varRef.getParent()); + } + return pep; + } + /** + * @return an invocation of {@link TemplateParameter#S()} if it is parent of `element` + */ + private ParameterElementPair transformTemplateParameterInvocationOfS(ParameterElementPair pep) { + CtElement element = pep.element; + if (element.isParentInitialized()) { + CtElement parent = element.getParent(); + if (parent instanceof CtInvocation) { + CtInvocation invocation = (CtInvocation) parent; + 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 pep.copyAndSet(invocation); + } + } + } + } + return pep; + } + + private ParameterElementPair transformExecutableRefToInvocation(ParameterElementPair pep) { + CtElement element = pep.element; + if (element instanceof CtExecutableReference) { + CtExecutableReference execRef = (CtExecutableReference) element; + if (element.isParentInitialized()) { + CtElement parent = execRef.getParent(); + if (parent instanceof CtInvocation) { + /* + * the invocation has to be substituted + */ + return pep.copyAndSet(parent); + } + } + } + return pep; + } + + private ParameterElementPair transformCtReturnIfNeeded(ParameterElementPair pep) { + CtElement element = pep.element; + if (element.isParentInitialized() && element.getParent() instanceof CtReturn) { + //we are substituting return expression. If the parameter value is CtBlock, then we have to substitute CtReturn instead + Class valueType = pep.parameter.getParameterValueType(); + if (valueType != null && CtBlock.class.isAssignableFrom(valueType)) { + //substitute CtReturn + return pep.copyAndSet(element.getParent()); + } + } + return pep; + } + + /** + * @return last implicit parent of element + */ + private ParameterElementPair getLastImplicitParent(ParameterElementPair pep) { + CtElement element = pep.element; + while (element.isParentInitialized()) { + CtElement parent = element.getParent(); + if ((parent instanceof CtBlock) == false || parent.isImplicit() == false) { + break; + } + element = parent; + } + return pep.copyAndSet(element); + } +} diff --git a/src/main/java/spoon/pattern/Pattern.java b/src/main/java/spoon/pattern/Pattern.java new file mode 100644 index 00000000000..f31d8c14432 --- /dev/null +++ b/src/main/java/spoon/pattern/Pattern.java @@ -0,0 +1,266 @@ +/** + * 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.pattern; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import spoon.SpoonException; +import spoon.pattern.matcher.Match; +import spoon.pattern.matcher.MatchingScanner; +import spoon.reflect.declaration.CtElement; +import spoon.reflect.declaration.CtPackage; +import spoon.reflect.declaration.CtType; +import spoon.reflect.declaration.CtTypeMember; +import spoon.reflect.factory.Factory; +import spoon.reflect.reference.CtTypeReference; +import spoon.reflect.visitor.chain.CtConsumableFunction; +import spoon.reflect.visitor.chain.CtConsumer; + +/** + * Represents a pattern. It means an AST model, where some parts of that model are substituted by pattern parameters. + * The {@link Pattern} can be used to process these two "itself opposite" operations + *
    + *
  • Generation of code from pattern. It means (Pattern) + (pattern parameters) => (copy of pattern where parameters are replaced by parameter values)
  • + *
  • Matching of code by pattern. It means (Pattern) + (code fitting to pattern) => (pattern parameters)
  • + *
+ * Note: that code generated by pattern using some set of parameters ParamsA, matches with the same pattern and produces same set of ParametersA + */ +public class Pattern implements CtConsumableFunction { + private ParameterValueProviderFactory parameterValueProviderFactory = UnmodifiableParameterValueProvider.Factory.INSTANCE; + private ModelNode modelValueResolver; + + Pattern(ModelNode modelValueResolver) { + this.modelValueResolver = modelValueResolver; + } + + /** + * @return a {@link ModelNode} of this pattern + */ + public ModelNode getModelValueResolver() { + return modelValueResolver; + } + + /** + * @return Map of parameter names to {@link ParameterInfo} for each parameter of this {@link Pattern} + */ + public Map getParameterInfos() { + Map parameters = new HashMap<>(); + modelValueResolver.forEachParameterInfo((parameter, valueResolver) -> { + ParameterInfo existingParameter = parameters.get(parameter.getName()); + if (existingParameter != null) { + if (existingParameter == parameter) { + //OK, this parameter is already there + return; + } + throw new SpoonException("There is already a parameter: " + parameter.getName()); + } + parameters.put(parameter.getName(), parameter); + }); + return Collections.unmodifiableMap(parameters); + } + + /** + * generates a new AST made by cloning of `patternModel` and by substitution of parameters by values in `params` + * @param factory TODO + * @param valueType - the expected type of returned items + * @param params - the substitution parameters + * @return one generated element + */ + public T substituteSingle(Factory factory, Class valueType, Map params) { + return substituteSingle(factory, valueType, new UnmodifiableParameterValueProvider(params)); + } + /** + * generates a new AST made by cloning of `patternModel` and by substitution of parameters by values in `params` + * @param factory TODO + * @param valueType - the expected type of returned items + * @param params - the substitution parameters + * @return one generated element + */ + public T substituteSingle(Factory factory, Class valueType, ParameterValueProvider params) { + return modelValueResolver.generateTarget(factory, params, valueType); + } + /** + * generates a new AST made by cloning of `patternModel` and by substitution of parameters by values in `params` + * @param factory TODO + * @param valueType - the expected type of returned items + * @param params - the substitution parameters + * @return List of generated elements + */ + public List substituteList(Factory factory, Class valueType, Map params) { + return substituteList(factory, valueType, new UnmodifiableParameterValueProvider(params)); + } + /** + * generates a new AST made by cloning of `patternModel` and by substitution of parameters by values in `params` + * @param factory TODO + * @param valueType - the expected type of returned items + * @param params - the substitution parameters + * @return List of generated elements + */ + public List substituteList(Factory factory, Class valueType, ParameterValueProvider params) { + return modelValueResolver.generateTargets(factory, params, valueType); + } + + + /** + * Generates type with qualified name `typeQualifiedName` using this {@link Pattern} and provided `params`. + * + * Note: the root of pattern element must be one or more types. + * + * @param typeQualifiedName the qualified name of to be generated type + * @param params the pattern parameters + * @return the generated type + */ + public > T createType(Factory factory, String typeQualifiedName, Map params) { + return createType(factory.Type().createReference(typeQualifiedName), params); + } + + /** + * Generates type following `newTypeRef` using this {@link Pattern} and provided `params` + * + * Note: the root of pattern element must be one or more types. + * + * @param newTypeRef the type reference which refers to future generated type + * @param params the pattern parameters + * @return the generated type + */ + public > T createType(CtTypeReference newTypeRef, Map params) { + CtPackage ownerPackage = newTypeRef.getFactory().Package().getOrCreate(newTypeRef.getPackage().getQualifiedName()); + return createType(ownerPackage, newTypeRef.getSimpleName(), params); + } + + /** + * Generates type in the package `ownerPackage` with simple name `typeSimpleName` using this {@link Pattern} and provided `params` + * + * Note: the root of pattern element must be one or more types. + * + * @param ownerPackage the target package + * @param typeSimpleName the simple name of future generated type + * @param params the pattern parameters + * @return the generated type + */ + @SuppressWarnings("unchecked") + public > T createType(CtPackage ownerPackage, String typeSimpleName, Map params) { + @SuppressWarnings({ "rawtypes" }) + List types = substituteList(ownerPackage.getFactory(), CtType.class, new UnmodifiableParameterValueProvider(params, + PatternBuilder.TARGET_TYPE, ownerPackage.getFactory().Type().createReference(getQualifiedName(ownerPackage, typeSimpleName)))); + T result = null; + for (CtType type : types) { + ownerPackage.addType(type); + if (type.getSimpleName().equals(typeSimpleName)) { + result = (T) type; + } + } + return result; + } + + /** + * generates elements following this template with expected target scope `targetType` + * If they are {@link CtTypeMember} then adds them into `targetType`. + * + * @param targetType the existing type, which will contain newly generates {@link CtElement}s + * @param valueType the type of generated elements + * @param params the pattern parameters + * @return List of generated elements + */ + public List applyToType(CtType targetType, Class valueType, Map params) { + List results = substituteList(targetType.getFactory(), valueType, new UnmodifiableParameterValueProvider(params, PatternBuilder.TARGET_TYPE, targetType.getReference())); + for (T result : results) { + if (result instanceof CtTypeMember) { + targetType.addTypeMember((CtTypeMember) result); + } + } + return results; + } + + /** + * Consumer {@link Match} objects + */ + @Override + public void apply(Object input, CtConsumer outputConsumer) { + if (input == null) { + return; + } + if (input.getClass().isArray()) { + input = Arrays.asList((Object[]) input); + } + + MatchingScanner scanner = new MatchingScanner(modelValueResolver, parameterValueProviderFactory, outputConsumer); + ParameterValueProvider parameters = parameterValueProviderFactory.createParameterValueProvider(); + if (input instanceof Collection) { + scanner.scan(null, (Collection) input); + } else { + scanner.scan(null, (CtElement) input); + } + } + + /** + * Finds all target program sub-trees that correspond to a template + * and calls consumer.accept(matchingElement, parameterValues) + * @param rootElement the root of to be searched AST + * @param consumer the receiver of matches + */ + public void forEachMatch(Object input, CtConsumer consumer) { + if (input == null) { + return; + } + if (input.getClass().isArray()) { + input = Arrays.asList((Object[]) input); + } + + MatchingScanner scanner = new MatchingScanner(modelValueResolver, parameterValueProviderFactory, consumer); + ParameterValueProvider parameters = parameterValueProviderFactory.createParameterValueProvider(); + if (input instanceof Collection) { + scanner.scan(null, (Collection) input); + } else if (input instanceof Map) { + scanner.scan(null, (Map) input); + } else { + scanner.scan(null, (CtElement) input); + } + } + + /** + * Finds all target program sub-trees that correspond to a template + * and returns them. + * @param root the root of to be searched AST. It can be a CtElement or List, Set, Map of CtElements + * @return List of {@link Match} + */ + public List getMatches(Object root) { + List matches = new ArrayList<>(); + forEachMatch(root, match -> { + matches.add(match); + }); + return matches; + } + + @Override + public String toString() { + return modelValueResolver.toString(); + } + + private static String getQualifiedName(CtPackage pckg, String simpleName) { + if (pckg.isUnnamedPackage()) { + return simpleName; + } + return pckg.getQualifiedName() + CtPackage.PACKAGE_SEPARATOR + simpleName; + } +} diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java new file mode 100644 index 00000000000..7f8cc99c1ab --- /dev/null +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -0,0 +1,935 @@ +/** + * 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.pattern; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; + +import spoon.Metamodel; +import spoon.SpoonException; +import spoon.pattern.ParametersBuilder.ParameterElementPair; +import spoon.pattern.matcher.MapEntryNode; +import spoon.pattern.matcher.Matchers; +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.declaration.CtAnnotation; +import spoon.reflect.declaration.CtElement; +import spoon.reflect.declaration.CtExecutable; +import spoon.reflect.declaration.CtField; +import spoon.reflect.declaration.CtMethod; +import spoon.reflect.declaration.CtType; +import spoon.reflect.declaration.CtTypeMember; +import spoon.reflect.declaration.CtVariable; +import spoon.reflect.factory.Factory; +import spoon.reflect.factory.QueryFactory; +import spoon.reflect.meta.ContainerKind; +import spoon.reflect.path.CtRole; +import spoon.reflect.reference.CtArrayTypeReference; +import spoon.reflect.reference.CtTypeReference; +import spoon.reflect.reference.CtVariableReference; +import spoon.reflect.visitor.Filter; +import spoon.reflect.visitor.chain.CtConsumableFunction; +import spoon.reflect.visitor.chain.CtFunction; +import spoon.reflect.visitor.chain.CtQuery; +import spoon.reflect.visitor.chain.CtQueryable; +import spoon.reflect.visitor.filter.AllTypeMembersFunction; +import spoon.reflect.visitor.filter.TypeFilter; +import spoon.template.Parameter; +import spoon.template.TemplateParameter; + +/** + * The builder which creates a {@link Pattern} + */ +public class PatternBuilder { + + public static final String TARGET_TYPE = "targetType"; + + public static PatternBuilder create(Factory factory, Class templateClass) { + return create(factory, templateClass, null); + } + public static PatternBuilder create(Factory factory, Class templateClass, Consumer selector) { + return create(factory.Type().get(templateClass), selector); + } + + public static PatternBuilder create(CtTypeReference templateTypeRef, Consumer selector) { + return create(templateTypeRef.getTypeDeclaration(), selector); + } + + public static PatternBuilder create(CtType templateType) { + return create(templateType, null); + } + public static PatternBuilder create(CtType templateType, Consumer selector) { + checkTemplateType(templateType); + List templateModel; + if (selector != null) { + TemplateModelBuilder model = new TemplateModelBuilder(templateType); + selector.accept(model); + templateModel = model.getTemplateModel(); + } else { + templateModel = Collections.singletonList(templateType); + } + return new PatternBuilder(templateType.getReference(), templateModel); + } + + private final List patternModel; + private final ListOfNodes patternNodes; + private final Map patternElementToSubstRequests = new IdentityHashMap<>(); + private final Set explicitNodes = Collections.newSetFromMap(new IdentityHashMap<>()); + + private CtTypeReference templateTypeRef; + private final Factory factory; + private final Map parameterInfos = new HashMap<>(); +// ModelNode pattern; + CtQueryable patternQuery; + private ValueConvertor valueConvertor; + private boolean built = false; + + static class PatternQuery implements CtQueryable { + private final QueryFactory queryFactory; + private final List modelElements; + PatternQuery(QueryFactory queryFactory, List modelElements) { + this.queryFactory = queryFactory; + this.modelElements = modelElements; + } + @Override + public CtQuery filterChildren(Filter filter) { + return queryFactory.createQuery(modelElements).filterChildren(filter); + } + @Override + public CtQuery map(CtFunction function) { + return queryFactory.createQuery(modelElements).map(function); + } + @Override + public CtQuery map(CtConsumableFunction queryStep) { + return queryFactory.createQuery(modelElements).map(queryStep); + } + } + + private PatternBuilder(CtTypeReference templateTypeRef, List template) { + this.templateTypeRef = templateTypeRef; + this.patternModel = template; + if (template == null) { + throw new SpoonException("Cannot create a Pattern from an null model"); + } + this.factory = templateTypeRef.getFactory(); + this.valueConvertor = new ValueConvertorImpl(factory); + patternNodes = new ListOfNodes(createImplicitNodes(template)); + patternQuery = new PatternBuilder.PatternQuery(factory.Query(), patternModel); + configureParameters(pb -> { + pb.parameter(TARGET_TYPE).byType(templateTypeRef).setValueType(CtTypeReference.class); + }); + } + + private List createImplicitNodes(List elements) { + List nodes = new ArrayList<>(elements.size()); + for (CtElement element : elements) { + nodes.add(createImplicitNode(element)); + } + return nodes; + } + + private final Set IGNORED_ROLES = Collections.unmodifiableSet((new HashSet<>(Arrays.asList(CtRole.POSITION)))); + + private Node createImplicitNode(Object object) { + if (object instanceof CtElement) { + //it is a spoon element + CtElement element = (CtElement) object; + Metamodel.Type mmType = Metamodel.getMetamodelTypeByClass(element.getClass()); + ElementNode elementNode = new ElementNode(mmType); + if (patternElementToSubstRequests.put(element, elementNode) != null) { + throw new SpoonException("Each pattern element can have only one implicit Node."); + } + //iterate over all attributes of that element + for (Metamodel.Field mmField : mmType.getFields()) { + if (mmField.isDerived() || IGNORED_ROLES.contains(mmField.getRole())) { + //skip derived fields, they are not relevant for matching or generating + continue; + } + elementNode.setNodeOfRole(mmField.getRole(), createImplicitNode(mmField.getContainerKind(), mmField.getValue(element))); + } + return elementNode; + } + //TODO share instances of ConstantNode between equal `object`s - e.g. null, booleans, Strings, ... + return new ConstantNode(object); + } + + private Node createImplicitNode(ContainerKind containerKind, Object templates) { + switch (containerKind) { + case LIST: + return createImplicitNode((List) templates); + case SET: + return createImplicitNode((Set) templates); + case MAP: + return createImplicitNode((Map) templates); + case SINGLE: + return createImplicitNode(templates); + } + throw new SpoonException("Unexpected RoleHandler containerKind: " + containerKind); + } + + private Node createImplicitNode(List objects) { + return listOfNodesToNode(objects.stream().map(i -> createImplicitNode(i)).collect(Collectors.toList())); + } + + private Node createImplicitNode(Set templates) { + //collect plain template nodes without any substitution request as List, because Spoon Sets have predictable order. + List constantMatchers = new ArrayList<>(templates.size()); + //collect template nodes with a substitution request + List variableMatchers = new ArrayList<>(); + for (Object template : templates) { + Node matcher = createImplicitNode(template); + if (matcher instanceof ElementNode) { + constantMatchers.add(matcher); + } else { + variableMatchers.add(matcher); + } + } + //first match the Set with constant matchers and then with variable matchers + constantMatchers.addAll(variableMatchers); + return listOfNodesToNode(constantMatchers); + } + + private Node createImplicitNode(Map map) { + //collect Entries with constant matcher keys + List constantMatchers = new ArrayList<>(map.size()); + //collect Entries with variable matcher keys + List variableMatchers = new ArrayList<>(); + Matchers last = null; + for (Map.Entry entry : map.entrySet()) { + MapEntryNode mem = new MapEntryNode( + createImplicitNode(entry.getKey()), + createImplicitNode(entry.getValue())); + if (mem.getKey() == entry.getKey()) { + constantMatchers.add(mem); + } else { + variableMatchers.add(mem); + } + } + //first match the Map.Entries with constant matchers and then with variable matchers + constantMatchers.addAll(variableMatchers); + return listOfNodesToNode(constantMatchers); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private static Node listOfNodesToNode(List nodes) { + //The attribute is matched different if there is List of one ParameterizedNode and when there is one ParameterizedNode +// if (nodes.size() == 1) { +// return nodes.get(0); +// } + return new ListOfNodes((List) nodes); + } + +// private static final Map roleToSkippedClass = new HashMap<>(); +// static { +// roleToSkippedClass.put(CtRole.COMMENT, new Class[]{Object.class}); +// roleToSkippedClass.put(CtRole.POSITION, new Class[]{Object.class}); +// roleToSkippedClass.put(CtRole.TYPE, new Class[]{CtInvocation.class, CtExecutableReference.class}); +// roleToSkippedClass.put(CtRole.DECLARING_TYPE, new Class[]{CtExecutableReference.class}); +// roleToSkippedClass.put(CtRole.INTERFACE, new Class[]{CtTypeReference.class}); +// roleToSkippedClass.put(CtRole.MODIFIER, new Class[]{CtTypeReference.class}); +// roleToSkippedClass.put(CtRole.SUPER_TYPE, new Class[]{CtTypeReference.class}); +// } +// +// /** +// * @param roleHandler the to be checked role +// * @param targetClass the class which is going to be checked +// * @return true if the role is relevant for matching process +// */ +// private static boolean isMatchingRole(RoleHandler roleHandler, Class targetClass) { +// //match on super roles only. Ignore derived roles +// if (roleHandler.getRole().getSuperRole() != null) { +// return false; +// } +// Class[] classes = roleToSkippedClass.get(roleHandler.getRole()); +// if (classes != null) { +// for (Class cls : classes) { +// if (cls.isAssignableFrom(targetClass)) { +// return false; +// } +// } +// } +// return true; +// } + + /** + * @param element a CtElement + * @return {@link Node}, which handles matching/generation of an `object` from the source spoon AST. + * or null, if there is none + */ + public Node getPatternNode(CtElement element, CtRole... roles) { + Node node = patternElementToSubstRequests.get(element); + for (CtRole role : roles) { + if (node instanceof ElementNode) { + ElementNode elementNode = (ElementNode) node; + node = elementNode.getAttributeSubstititionRequest(role); + if (node == null) { + throw new SpoonException("The role " + role + " resolved to null Node"); + } + } else { + throw new SpoonException("The role " + role + " can't be resolved on Node of class " + node.getClass()); + } + } + if (node == null) { + throw new SpoonException("There is no Node for element"); + } + return node; + } + + void modifyNodeOfElement(CtElement element, ConflictResolutionMode conflictMode, Function elementNodeChanger) { + Node oldNode = patternElementToSubstRequests.get(element); + Node newNode = elementNodeChanger.apply(oldNode); + if (newNode == null) { + throw new SpoonException("Removing of Node is not supported"); + } + handleConflict(conflictMode, oldNode, newNode, () -> { + if (patternNodes.replaceNode(oldNode, newNode) == false) { + if (conflictMode == ConflictResolutionMode.KEEP_OLD_NODE) { + //The parent of oldNode was already replaced. OK - Keep that parent old node + return; + } + throw new SpoonException("Old node was not found"); + } + //update element to node mapping + patternElementToSubstRequests.put(element, newNode); + }); + } + + void modifyNodeOfAttributeOfElement(CtElement element, CtRole role, ConflictResolutionMode conflictMode, Function elementNodeChanger) { + modifyNodeOfElement(element, conflictMode, node -> { + if (node instanceof ElementNode) { + ElementNode elementNode = (ElementNode) node; + Node oldAttrNode = elementNode.getAttributeSubstititionRequest(role); + Node newAttrNode = elementNodeChanger.apply(oldAttrNode); + if (newAttrNode == null) { + throw new SpoonException("Removing of Node is not supported"); + } + handleConflict(conflictMode, oldAttrNode, newAttrNode, () -> { + elementNode.setNodeOfRole(role, newAttrNode); + }); + return node; + } + if (conflictMode == ConflictResolutionMode.KEEP_OLD_NODE) { + return node; + } + throw new SpoonException("The Node of atttribute of element cannot be set because element has a Node of class: " + node.getClass().getName()); + }); + } + + private void handleConflict(ConflictResolutionMode conflictMode, Node oldNode, Node newNode, Runnable applyNewNode) { + if (oldNode != newNode) { + if (explicitNodes.contains(oldNode)) { + //the oldNode was explicitly added before + if (conflictMode == ConflictResolutionMode.FAIL) { + throw new SpoonException("Can't replace once assigned Node " + oldNode + " by a " + newNode); + } + if (conflictMode == ConflictResolutionMode.KEEP_OLD_NODE) { + return; + } + } + explicitNodes.remove(oldNode); + explicitNodes.add(newNode); + applyNewNode.run(); + } + } + + public void setNodeOfElement(CtElement element, Node node, ConflictResolutionMode conflictMode) { + modifyNodeOfElement(element, conflictMode, oldNode -> { + return node; + }); + } + + public void setNodeOfAttributeOfElement(CtElement element, CtRole role, Node node, ConflictResolutionMode conflictMode) { + modifyNodeOfAttributeOfElement(element, role, conflictMode, oldAttrNode -> { + return node; + }); + } + + /** + * @param element to be checked element + * @return true if element `element` is a template or a child of template + */ + public boolean isInModel(CtElement element) { + if (element != null) { + for (CtElement patternElement : patternModel) { + if (element == patternElement || element.hasParent(patternElement)) { + return true; + } + } + } + return false; + } + + public Pattern build() { + if (built) { + throw new SpoonException("The Pattern may be built only once"); + } + built = true; + //clean the mapping so it is not possible to further modify built pattern using this builder + patternElementToSubstRequests.clear(); + return new Pattern(new ModelNode(patternNodes.getNodes())); + } + + static List bodyToStatements(CtStatement statementOrBlock) { + if (statementOrBlock instanceof CtBlock) { + return ((CtBlock) statementOrBlock).getStatements(); + } + return Collections.singletonList(statementOrBlock); + } + + /** + * @return default {@link ValueConvertor}, which will be assigned to all new {@link ParameterInfo}s + */ + public ValueConvertor getDefaultValueConvertor() { + return valueConvertor; + } + + /** + * @param valueConvertor default {@link ValueConvertor}, which will be assigned to all {@link ParameterInfo}s created after this call + * @return this to support fluent API + */ + public PatternBuilder setDefaultValueConvertor(ValueConvertor valueConvertor) { + this.valueConvertor = valueConvertor; + return this; + } + + public PatternBuilder configureAutomaticParameters() { + configureParameters(pb -> { + /* + * detect other parameters. + * contract: All variable references, which are declared outside of template are automatically considered as template parameters + */ + pb.queryModel().filterChildren(new TypeFilter<>(CtVariableReference.class)) + .forEach((CtVariableReference varRef) -> { + CtVariable var = varRef.getDeclaration(); + if (var == null || isInModel(var) == false) { + //the varRef has declaration out of the scope of the template. It must be a template parameter. + ParameterInfo parameter = pb.parameter(varRef.getSimpleName()).getCurrentParameter(); + ParameterElementPair pep = pb.getSubstitutedNodeOfElement(parameter, varRef); + //add this substitution request only if there is no one yet + setNodeOfElement(pep.element, new ParameterNode(pep.parameter), ConflictResolutionMode.KEEP_OLD_NODE); + } + }); + }); + return this; + } + + public static class TemplateModelBuilder { + + private final CtType templateType; + private CtType clonedTemplateType; + private List templateModel = null; + + public TemplateModelBuilder(CtType templateTemplate) { + this.templateType = templateTemplate; + } + + public CtType getTemplateType() { + return templateType; + } + + private CtType getClonedTemplateType() { + if (clonedTemplateType == null) { + clonedTemplateType = templateType.clone(); + if (templateType.isParentInitialized()) { + //set parent package, to keep origin qualified name of the Template. It is needed for correct substitution of Template name by target type reference + clonedTemplateType.setParent(templateType.getParent()); + } + } + return clonedTemplateType; + } + + /** + * Sets a template model from {@link CtTypeMember} of a template type + * @param typeMemberName the name of the {@link CtTypeMember} of a template type + */ + public TemplateModelBuilder setTypeMember(String typeMemberName) { + setTypeMember(tm -> typeMemberName.equals(tm.getSimpleName())); + return this; + } + /** + * Sets a template model from {@link CtTypeMember} of a template type + * @param filter the {@link Filter} whose match defines to be used {@link CtTypeMember} + */ + public TemplateModelBuilder setTypeMember(Filter filter) { + setTemplateModel(getByFilter(filter)); + return this; + } + + public TemplateModelBuilder removeTag(Class... classes) { + List elements = getClonedTemplateModel(); + for (Class class1 : classes) { + for (CtElement element : elements) { + CtAnnotation annotation = element.getAnnotation(element.getFactory().Type().createReference(class1)); + if (annotation != null) { + element.removeAnnotation(annotation); + } + } + } + return this; + } + + private List getClonedTemplateModel() { + if (templateModel == null) { + throw new SpoonException("Template model is not defined yet"); + } + for (ListIterator iter = templateModel.listIterator(); iter.hasNext();) { + CtElement ele = iter.next(); + if (ele.getRoleInParent() != null) { + iter.set(ele.clone()); + } + } + return templateModel; + } + + /** + * Sets a template model from body of the method of template type + * @param methodName the name of {@link CtMethod} + */ + public void setBodyOfMethod(String methodName) { + setBodyOfMethod(tm -> methodName.equals(tm.getSimpleName())); + } + /** + * Sets a template model from body of the method of template type selected by filter + * @param filter the {@link Filter} whose match defines to be used {@link CtMethod} + */ + public void setBodyOfMethod(Filter> filter) { + CtBlock body = getOneByFilter(filter).getBody(); + setTemplateModel(body.getStatements()); + } + + /** + * Sets a template model from return expression of the method of template type selected by filter + * @param methodName the name of {@link CtMethod} + */ + public void setReturnExpressionOfMethod(String methodName) { + setReturnExpressionOfMethod(tm -> methodName.equals(tm.getSimpleName())); + } + /** + * Sets a template model from return expression of the method of template type selected by filter + * @param filter the {@link Filter} whose match defines to be used {@link CtExecutable} + */ + public void setReturnExpressionOfMethod(Filter> filter) { + CtMethod method = getOneByFilter(filter); + CtBlock body = method.getBody(); + if (body.getStatements().size() != 1) { + throw new SpoonException("The body of " + method.getSignature() + " must contain exactly one statement. But there is:\n" + body.toString()); + } + CtStatement firstStatement = body.getStatements().get(0); + if (firstStatement instanceof CtReturn == false) { + throw new SpoonException("The body of " + method.getSignature() + " must contain return statement. But there is:\n" + body.toString()); + } + setTemplateModel(((CtReturn) firstStatement).getReturnedExpression()); + } + + private List getByFilter(Filter filter) { + List elements = templateType.filterChildren(filter).list(); + if (elements == null || elements.isEmpty()) { + throw new SpoonException("Element not found in " + templateType.getShortRepresentation()); + } + return elements; + } + private T getOneByFilter(Filter filter) { + List elements = getByFilter(filter); + if (elements.size() != 1) { + throw new SpoonException("Only one element must be selected, but there are: " + elements); + } + return elements.get(0); + } + /** + * @param filter whose matches will be removed from the template + */ + public TemplateModelBuilder removeTypeMembers(Filter filter) { + for (CtTypeMember ctTypeMember : new ArrayList<>(getClonedTemplateType().getTypeMembers())) { + if (filter.matches(ctTypeMember)) { + ctTypeMember.delete(); + } + } + return this; + } + + /** + * Removes all type members which are annotated by `annotationClass` + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public TemplateModelBuilder removeTypeMembersAnnotatedBy(Class... annotationClass) { + for (Class ac : annotationClass) { + removeTypeMembers(tm -> tm.getAnnotation((Class) ac) != null); + } + return this; + } + + /** + * @param filter whose matches will be kept in the template. All others will be removed + */ + public TemplateModelBuilder keepTypeMembers(Filter filter) { + for (CtTypeMember ctTypeMember : new ArrayList<>(getClonedTemplateType().getTypeMembers())) { + if (filter.matches(ctTypeMember) == false) { + ctTypeMember.delete(); + } + } + return this; + } + + /** + * Keeps only type members, which are annotated by `annotationClass`. All others will be removed + */ + public TemplateModelBuilder keepTypeMembersAnnotatedBy(Class annotationClass) { + keepTypeMembers(tm -> tm.getAnnotation(annotationClass) != null); + return this; + } + + /** + * removes super class from the template + */ + public TemplateModelBuilder removeSuperClass() { + getClonedTemplateType().setSuperclass(null); + return this; + } + + /** + * @param filter super interfaces which matches the filter will be removed + */ + public TemplateModelBuilder removeSuperInterfaces(Filter> filter) { + Set> superIfaces = new HashSet<>(getClonedTemplateType().getSuperInterfaces()); + boolean changed = false; + for (Iterator> iter = superIfaces.iterator(); iter.hasNext();) { + if (filter.matches(iter.next())) { + iter.remove(); + changed = true; + } + } + if (changed) { + getClonedTemplateType().setSuperInterfaces(superIfaces); + } + return this; + } + + /** + * @param filter super interfaces which matches the filter will be kept. Others will be removed + */ + public TemplateModelBuilder keepSuperInterfaces(Filter> filter) { + Set> superIfaces = new HashSet<>(getClonedTemplateType().getSuperInterfaces()); + boolean changed = false; + for (Iterator> iter = superIfaces.iterator(); iter.hasNext();) { + if (filter.matches(iter.next())) { + iter.remove(); + changed = true; + } + } + if (changed) { + getClonedTemplateType().setSuperInterfaces(superIfaces); + } + return this; + } + + public List getTemplateModel() { + return templateModel; + } + + public void setTemplateModel(CtElement template) { + this.templateModel = Collections.singletonList(template); + } + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void setTemplateModel(List template) { + this.templateModel = (List) template; + } + } + + public PatternBuilder configureParameters(Consumer parametersBuilder) { + ParametersBuilder pb = new ParametersBuilder(this, parameterInfos); + parametersBuilder.accept(pb); + return this; + } + + public PatternBuilder configureLocalParameters(Consumer parametersBuilder) { + ParametersBuilder pb = new ParametersBuilder(this, new HashMap<>()); + parametersBuilder.accept(pb); + return this; + } + /** + * adds all standard Template parameters based on {@link TemplateParameter} and {@link Parameter} annotation + * @return this to support fluent API + */ + public PatternBuilder configureTemplateParameters() { + return configureTemplateParameters(templateTypeRef.getTypeDeclaration(), null); + } + + /** + * adds all standard Template parameters based on {@link TemplateParameter} and {@link Parameter} annotation + * @param templateParameters parameters, which will be used in substitution. It is needed here, + * when parameter value types influences which AST nodes will be the target of substitution in legacy template patterns + * @return this to support fluent API + */ + public PatternBuilder configureTemplateParameters(Map templateParameters) { + return configureTemplateParameters(templateTypeRef.getTypeDeclaration(), templateParameters); + } + + /** + * 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 + */ + private PatternBuilder configureTemplateParameters(CtType templateType, Map templateParameters) { + configureParameters(pb -> { + templateType.map(new AllTypeMembersFunction()).forEach((CtTypeMember typeMember) -> { + configureTemplateParameter(templateType, templateParameters, pb, typeMember); + }); + if (templateParameters != null) { + //configure template parameters based on parameter values only - these without any declaration in Template + templateParameters.forEach((paramName, paramValue) -> { + if (pb.isSubstituted(paramName) == false) { + //and only these parameters whose name isn't already handled by explicit template parameters + if (paramValue instanceof CtTypeReference) { + pb.parameter(paramName) + .setConflictResolutionMode(ConflictResolutionMode.KEEP_OLD_NODE) + .byLocalType(templateType, paramName); + } + pb.parameter(paramName) + .setConflictResolutionMode(ConflictResolutionMode.KEEP_OLD_NODE) + .bySubstring(paramName); + } + }); + } + }); + return this; + } + + private void configureTemplateParameter(CtType templateType, Map templateParameters, ParametersBuilder pb, CtTypeMember typeMember) { + Factory f = typeMember.getFactory(); + CtTypeReference templateParamRef = f.Type().createReference(TemplateParameter.class); + CtTypeReference typeReferenceRef = f.Type().createReference(CtTypeReference.class); + CtTypeReference ctStatementRef = f.Type().createReference(CtStatement.class); + Parameter param = typeMember.getAnnotation(Parameter.class); + if (param != null) { + if (typeMember instanceof CtField) { + CtField paramField = (CtField) typeMember; + /* + * We have found a CtField annotated by @Parameter. + * Use it as Pattern parameter + */ + String fieldName = typeMember.getSimpleName(); + String stringMarker = (param.value() != null && param.value().length() > 0) ? param.value() : fieldName; + //for the compatibility reasons with Parameters.getNamesToValues(), use the proxy name as parameter name + String parameterName = stringMarker; + + CtTypeReference paramType = paramField.getType(); + + if (paramType.isSubtypeOf(f.Type().ITERABLE) || paramType instanceof CtArrayTypeReference) { + //parameter is a multivalue + pb.parameter(parameterName).setContainerKind(ContainerKind.LIST).bySimpleName(stringMarker); + } else if (paramType.isSubtypeOf(typeReferenceRef) || paramType.getQualifiedName().equals(Class.class.getName())) { + /* + * parameter with value type TypeReference or Class, identifies replacement of local type whose name is equal to parameter name + */ + CtTypeReference nestedType = getLocalTypeRefBySimpleName(templateType, stringMarker); + if (nestedType != null) { + //all references to nestedType has to be replaced + pb.parameter(parameterName).byType(nestedType); + } + //and replace the variable references by class access + pb.parameter(parameterName).byVariable(paramField); + } else if (paramType.getQualifiedName().equals(String.class.getName())) { + CtTypeReference nestedType = getLocalTypeRefBySimpleName(templateType, stringMarker); + if (nestedType != null) { + //There is a local type with such name. Replace it + pb.parameter(parameterName).byType(nestedType); + } + } else if (paramType.isSubtypeOf(templateParamRef)) { + pb.parameter(parameterName) + .byTemplateParameterReference(paramField); + //if there is any invocation of method with name matching to stringMarker, then substitute their invocations too. + templateType.getMethodsByName(stringMarker).forEach(m -> { + pb.parameter(parameterName).byInvocation(m); + }); + } else if (paramType.isSubtypeOf(ctStatementRef)) { + //if there is any invocation of method with name matching to stringMarker, then substitute their invocations too. + templateType.getMethodsByName(stringMarker).forEach(m -> { + pb.parameter(parameterName).setContainerKind(ContainerKind.LIST).byInvocation(m); + }); + } else { + //it is not a String. It is used to substitute CtLiteral of parameter value + pb.parameter(parameterName) + //all occurrences of parameter name in pattern model are subject of substitution + .byVariable(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 = pb.parameter(parameterName).getCurrentParameter(); + pb.queryModel().filterChildren((CtInvocation inv) -> { + return inv.getExecutable().getSimpleName().equals(stringMarker); + }).forEach((CtInvocation inv) -> { + pb.addSubstitutionRequest(pi, inv); + }); + } + } + + //any value can be converted to String. Substitute content of all string attributes + pb.parameter(parameterName).setConflictResolutionMode(ConflictResolutionMode.KEEP_OLD_NODE) + .bySubstring(stringMarker); + + if (templateParameters != null) { + //handle automatic live statements + addLiveStatements(fieldName, templateParameters.get(parameterName)); + } + } else { + //TODO CtMethod was may be supported in old Template engine!!! + throw new SpoonException("Template Parameter annotation on " + typeMember.getClass().getName() + " is not supported"); + } + } else if (typeMember instanceof CtField && ((CtField) typeMember).getType().isSubtypeOf(templateParamRef)) { + CtField field = (CtField) typeMember; + String parameterName = typeMember.getSimpleName(); + Object value = templateParameters == null ? null : templateParameters.get(parameterName); + Class valueType = null; + boolean multiple = false; + if (value != null) { + valueType = value.getClass(); + if (value instanceof CtBlock) { + //the CtBlock in this situation is expected as container of Statements in legacy templates. + multiple = true; + } + } + pb.parameter(parameterName).setValueType(valueType).setContainerKind(ContainerKind.LIST) + .byTemplateParameterReference(field); + + if (templateParameters != null) { + //handle automatic live statements + addLiveStatements(parameterName, templateParameters.get(parameterName)); + } + } + + } + + private void addLiveStatements(String variableName, Object paramValue) { + if (paramValue != null && paramValue.getClass().isArray()) { + //the parameters with Array value are meta parameters in legacy templates + configureLiveStatements(sb -> { + //we are adding live statements automatically from legacy templates, + //so do not fail if it is sometime not possible - it means that it is not a live statement then + sb.setFailOnMissingParameter(false); + sb.byVariableName(variableName); + }); + } + } + + /** + * Configures live statements + * + * For example if the `for` statement in this pattern model + *

+	 * for(Object x : $iterable$) {
+	 *	System.out.println(x);
+	 * }
+	 * 
+ * is configured as live statement and a Pattern is substituted + * using parameter $iterable$ = new String[]{"A", "B", "C"} + * then pattern generated this code + *

+	 * System.out.println("A");
+	 * System.out.println("B");
+	 * System.out.println("C");
+	 * 
+ * because live statements are executed during substitution process and are not included in generated result. + * + * The live statements may be used in PatternMatching process (opposite to Pattern substitution) too. + * @param consumer + * @return this to support fluent API + */ + public PatternBuilder configureLiveStatements(Consumer consumer) { + LiveStatementsBuilder sb = new LiveStatementsBuilder(this); + consumer.accept(sb); + return this; + } + + static CtTypeReference getLocalTypeRefBySimpleName(CtType templateType, String typeSimpleName) { + CtType type = templateType.getNestedType(typeSimpleName); + if (type != null) { + return type.getReference(); + } + type = templateType.getPackage().getType(typeSimpleName); + if (type != null) { + return type.getReference(); + } + Set typeQNames = new HashSet<>(); + templateType + .filterChildren((CtTypeReference ref) -> typeSimpleName.equals(ref.getSimpleName())) + .forEach((CtTypeReference ref) -> typeQNames.add(ref.getQualifiedName())); + if (typeQNames.size() > 1) { + throw new SpoonException("The type parameter " + typeSimpleName + " is ambiguous. It matches multiple types: " + typeQNames); + } + if (typeQNames.size() == 1) { + return templateType.getFactory().Type().createReference(typeQNames.iterator().next()); + } + return null; + } + + public boolean hasParameterInfo(String parameterName) { + return parameterInfos.containsKey(parameterName); + } + + protected Factory getFactory() { + return factory; + } + + private static void checkTemplateType(CtType type) { + if (type == null) { + throw new SpoonException("Cannot create Pattern from null Template type."); + } + if (type.isShadow()) { + throw new SpoonException("Cannot create Pattern from shadow Template type. Add sources of Template type into spoon model."); + } + } + public List getPatternModel() { + return patternModel; + } + /** + * Calls `consumer` once for each {@link Node} element which uses `parameter` + * @param parameter to be checked {@link ParameterInfo} + * @param consumer receiver of calls + */ + public void forEachNodeOfParameter(ParameterInfo parameter, Consumer consumer) { + patternNodes.forEachParameterInfo((paramInfo, vr) -> { + if (paramInfo == parameter) { + consumer.accept(vr); + } + }); + } +} diff --git a/src/main/java/spoon/pattern/PrimitiveMatcher.java b/src/main/java/spoon/pattern/PrimitiveMatcher.java new file mode 100644 index 00000000000..c7cfa383c2d --- /dev/null +++ b/src/main/java/spoon/pattern/PrimitiveMatcher.java @@ -0,0 +1,30 @@ +/** + * 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.pattern; + +/** + * Defines API of a primitive matcher - matcher for single target object + */ +public interface PrimitiveMatcher extends RepeatableMatcher { + + /** + * @param target - to be matched element + * @param parameters will receive the matching parameter values + * @return true if `element` matches with pattern of this matcher + */ + ParameterValueProvider matchTarget(Object target, ParameterValueProvider parameters); +} diff --git a/src/main/java/spoon/pattern/RepeatableMatcher.java b/src/main/java/spoon/pattern/RepeatableMatcher.java new file mode 100644 index 00000000000..f4637d6aef5 --- /dev/null +++ b/src/main/java/spoon/pattern/RepeatableMatcher.java @@ -0,0 +1,56 @@ +/** + * 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.pattern; + +import spoon.pattern.matcher.Quantifier; + +/** + * Defines API of a repeatable matcher. + * It is kind of {@link Node}, where one {@link Node} may match 0, 1 or more `target` nodes. + */ +public interface RepeatableMatcher extends Node { + /** + * If two {@link RepeatableMatcher}s in a list are matching the same element, + * then returned {@link Quantifier} defines how resolve this conflict + * @return {@link Quantifier} + */ + default Quantifier getMatchingStrategy() { + return Quantifier.POSSESSIVE; + } + /** + * @return true if this matcher can be applied more then once in the same container of targets + * Note: even if false, it may be applied again to another container and to match EQUAL value + */ + default boolean isRepeatable() { + return false; + } + /** + * @param parameters matching parameters + * @return true if this ValueResolver MUST match with next target in the state defined by current `parameters`. + * false if match is optional + */ + default boolean isMandatory(ParameterValueProvider parameters) { + return true; + } + /** + * @param parameters matching parameters + * @return true if this ValueResolver should be processed again to match next target in the state defined by current `parameters`. + */ + default boolean isTryNextMatch(ParameterValueProvider parameters) { + return false; + } +} diff --git a/src/main/java/spoon/pattern/ResultHolder.java b/src/main/java/spoon/pattern/ResultHolder.java new file mode 100644 index 00000000000..85a324e6a57 --- /dev/null +++ b/src/main/java/spoon/pattern/ResultHolder.java @@ -0,0 +1,147 @@ +/** + * 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.pattern; + +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; +import java.util.function.Function; + +import spoon.SpoonException; + +/** + * Container for single or multiple values of required type + */ +public abstract class ResultHolder { + private Class requiredClass; + + public ResultHolder(Class requiredClass) { + this.requiredClass = requiredClass; + } + + /** + * @return the class of values, which acceptable by this result holder + */ + public Class getRequiredClass() { + return requiredClass; + } + + /** + * @param requiredClass only the values of this class are acceptable by this result holder + * @return this to support fluent API + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public ResultHolder setRequiredClass(Class requiredClass) { + this.requiredClass = (Class) requiredClass; + return (ResultHolder) this; + } + + /** + * @return true if it accepts 0, 1 or more values. false if it accepts exactly one value. If none, then value is null + */ + public abstract boolean isMultiple(); + + /** + * adds a result into this result holder + * @param value a new value of result holder + */ + public abstract void addResult(T value); + + /** + * calls consumer.accept(value) once for each contained value + * @param consumer + */ + public abstract void mapEachResult(Function consumer); + + /** + * Container of single value of required type + * + * @param + */ + public static class Single extends ResultHolder { + + private T result; + + public Single(Class requiredClass) { + super(requiredClass); + } + + @Override + public boolean isMultiple() { + return false; + } + + @Override + public void addResult(T value) { + if (this.result != null) { + throw new SpoonException("Cannot add second value into single value ConversionContext"); + } + this.result = value; + } + + public T getResult() { + return result; + } + + @Override + public void mapEachResult(Function consumer) { + result = consumer.apply(result); + }; + + public ResultHolder.Single setRequiredClass(Class requiredClass) { + return (ResultHolder.Single) super.setRequiredClass(requiredClass); + } + } + + /** + * Container of multiple values of required type + * + * @param + */ + public static class Multiple extends ResultHolder { + + List result = new ArrayList<>(); + + Multiple(Class requiredClass) { + super(requiredClass); + } + + @Override + public boolean isMultiple() { + return true; + } + @Override + public void addResult(T value) { + this.result.add(value); + } + + public List getResult() { + return result; + } + + @Override + public void mapEachResult(Function consumer) { + for (ListIterator iter = result.listIterator(); iter.hasNext();) { + iter.set(consumer.apply(iter.next())); + } + } + + public ResultHolder.Multiple setRequiredClass(Class requiredClass) { + return (ResultHolder.Multiple) super.setRequiredClass(requiredClass); + } + } +} diff --git a/src/main/java/spoon/pattern/SetAccessor.java b/src/main/java/spoon/pattern/SetAccessor.java new file mode 100644 index 00000000000..0a2e1441316 --- /dev/null +++ b/src/main/java/spoon/pattern/SetAccessor.java @@ -0,0 +1,91 @@ +/** + * 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.pattern; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Function; + +/** + */ +public class SetAccessor extends AbstractItemAccessor { + + public SetAccessor(AbstractItemAccessor next) { + super(next); + } + + @Override + protected String getPlainName() { + return getWrappedName(getContainerName()); + } + + @Override + protected String getWrappedName(String containerName) { + return containerName; + } + + @Override + protected Object addValueAs(Object container, Function merger) { + Set set = castTo(container, Set.class); + Object newValue = merger.apply(null); + if (newValue == NO_MERGE) { + return NO_MERGE; + } + if (newValue == null) { + //nothing to add. Keep existing set + return set; + } + if (set.contains(newValue)) { + //the value is already there + return set; + } + Set newSet = new LinkedHashSet<>(set.size() + 1); + newSet.addAll(set); + if (newValue instanceof Collection) { + if (newSet.addAll((Collection) newValue) == false) { + //all the values were already there. Return original set + return set; + } + } else { + newSet.add(newValue); + } + return Collections.unmodifiableSet(newSet); + } + + @Override + protected Set getEmptyContainer() { + return Collections.emptySet(); + } + @Override + protected Object getValue(ParameterValueProvider parameters) { + return castTo(super.getValue(parameters), Set.class); + } + + @Override + protected T castTo(Object o, Class type) { + if (o instanceof List) { + o = new LinkedHashSet<>((List) o); + } else if (o instanceof Object[]) { + o = new LinkedHashSet<>(Arrays.asList((Object[]) o)); + } + return super.castTo(o, type); + } +} diff --git a/src/main/java/spoon/pattern/StringNode.java b/src/main/java/spoon/pattern/StringNode.java new file mode 100644 index 00000000000..05425356118 --- /dev/null +++ b/src/main/java/spoon/pattern/StringNode.java @@ -0,0 +1,237 @@ +/** + * 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.pattern; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import spoon.SpoonException; +import spoon.reflect.factory.Factory; + +/** + * Delivers single String value, which is created by replacing string markers in constant String template + * by String value of appropriate parameter. + */ +public class StringNode extends ConstantNode { + /* + * Use LinkedHashMap to assure defined replacement order + */ + private final Map tobeReplacedSubstrings = new LinkedHashMap<>(); + private ParameterInfo[] params; + private Pattern regExpPattern; + + public StringNode(String stringValueWithMarkers) { + super(stringValueWithMarkers); + } + + private String getStringValueWithMarkers() { + return getTemplateNode(); + } + + @SuppressWarnings("unchecked") + @Override + public void generateTargets(Factory factory, ResultHolder result, ParameterValueProvider parameters) { + Class requiredClass = result.getRequiredClass(); + if (requiredClass != null && requiredClass.isAssignableFrom(String.class) == false) { + throw new SpoonException("StringValueResolver provides only String values. It doesn't support: " + requiredClass); + } + /* + * initial value of result String. It usually contains some substrings (markers), + * which are replaced by values of related parameters + */ + String stringValue = getStringValueWithMarkers(); + for (Map.Entry requests : tobeReplacedSubstrings.entrySet()) { + ParameterInfo param = requests.getValue(); + String replaceMarker = requests.getKey(); + ResultHolder.Single ctx = new ResultHolder.Single<>(String.class); + param.getValueAs(ctx, parameters); + String substrValue = ctx.getResult() == null ? "" : ctx.getResult(); + stringValue = substituteSubstring(stringValue, replaceMarker, substrValue); + } + //convert stringValue from String to type required by result and add it into result + result.addResult((T) stringValue); + } + + @Override + public ParameterValueProvider matchTarget(Object target, ParameterValueProvider parameters) { + if ((target instanceof String) == false) { + return null; + } + String targetString = (String) target; + java.util.regex.Pattern re = getMatchingPattern(); + Matcher m = re.matcher(targetString); + if (m.matches() == false) { + return null; + } + ParameterInfo[] params = getMatchingParameterInfos(); + for (int i = 0; i < params.length; i++) { + String paramValue = m.group(i + 1); + parameters = params[i].addValueAs(parameters, paramValue); + if (parameters == null) { + //two occurrences of the same parameter are matching on different value + //whole string doesn't matches + return null; + } + } + return parameters; + } + + /** + * @return The string whose occurrence in target string will be replaced by parameter value + */ + public ParameterInfo getParameterInfo(String replaceMarker) { + return tobeReplacedSubstrings.get(replaceMarker); + } + + /** + * Defines that this Substitution request will replace all occurrences of `replaceMarker` in target string by value of `param` + * @param replaceMarker the substring whose occurrences will be substituted + * @param param the declaration of to be replaced parameter + */ + public void setReplaceMarker(String replaceMarker, ParameterInfo param) { + tobeReplacedSubstrings.put(replaceMarker, param); + } + + /** + * @return {@link ParameterInfo} to replace marker map + */ + public Map getReplaceMarkers() { + return Collections.unmodifiableMap(tobeReplacedSubstrings); + } + + @Override + public void forEachParameterInfo(BiConsumer consumer) { + Map visitedParams = new IdentityHashMap<>(tobeReplacedSubstrings.size()); + for (ParameterInfo parameterInfo : tobeReplacedSubstrings.values()) { + //assure that each parameterInfo is called only once + if (visitedParams.put(parameterInfo, Boolean.TRUE) == null) { + consumer.accept(parameterInfo, this); + } + } + } + + private ParameterInfo[] getMatchingParameterInfos() { + getMatchingPattern(); + return params; + } + + private List getRegions() { + List regions = new ArrayList<>(); + for (Map.Entry markers : tobeReplacedSubstrings.entrySet()) { + addRegionsOf(regions, markers.getValue(), markers.getKey()); + } + regions.sort((a, b) -> a.from - b.from); + return regions; + } + + private synchronized Pattern getMatchingPattern() { + if (regExpPattern == null) { + List regions = getRegions(); + StringBuilder re = new StringBuilder(); + List paramsByRegions = new ArrayList<>(); + int start = 0; + for (Region region : regions) { + if (region.from > start) { + re.append(escapeRegExp(getStringValueWithMarkers().substring(start, region.from))); + } else if (start > 0) { + throw new SpoonException("Cannot detect string parts if parameter separators are missing in pattern value: " + getStringValueWithMarkers()); + } + re.append("(") //start RE matching group + .append(".*?") //match any character, but not greedy + .append(")"); //end of RE matching group + paramsByRegions.add(region.param); + start = region.to; + } + if (start < getStringValueWithMarkers().length()) { + re.append(escapeRegExp(getStringValueWithMarkers().substring(start))); + } + regExpPattern = Pattern.compile(re.toString()); + params = paramsByRegions.toArray(new ParameterInfo[paramsByRegions.size()]); + } + return regExpPattern; + } + + /** + * Represents a to be replaced region of `getStringValueWithMarkers()` + */ + private static class Region { + ParameterInfo param; + int from; + int to; + + Region(ParameterInfo param, int from, int to) { + super(); + this.param = param; + this.from = from; + this.to = to; + } + } + + private void addRegionsOf(List regions, ParameterInfo param, String marker) { + int start = 0; + while (start < getStringValueWithMarkers().length()) { + start = getStringValueWithMarkers().indexOf(marker, start); + if (start < 0) { + return; + } + regions.add(new Region(param, start, start + marker.length())); + start += marker.length(); + } + } + + /** + * Replaces all occurrences of `tobeReplacedSubstring` in `str` by `substrValue` + * @param str to be modified string + * @param tobeReplacedSubstring all occurences of this String will be replaced by `substrValue` + * @param substrValue a replacement + * @return replaced string + */ + private String substituteSubstring(String str, String tobeReplacedSubstring, String substrValue) { + return str.replaceAll(escapeRegExp(tobeReplacedSubstring), escapeRegReplace(substrValue)); + } + + private String escapeRegExp(String str) { + return "\\Q" + str + "\\E"; + } + private String escapeRegReplace(String str) { + return str.replaceAll("\\$", "\\\\\\$"); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + int off = 0; + for (Region region : getRegions()) { + if (region.from > off) { + sb.append(getStringValueWithMarkers().substring(off, region.from)); + } + sb.append("${").append(region.param.getName()).append("}"); + off = region.to; + } + if (getStringValueWithMarkers().length() > off) { + sb.append(getStringValueWithMarkers().substring(off)); + } + return sb.toString(); + } +} diff --git a/src/main/java/spoon/pattern/SubstitutionCloner.java b/src/main/java/spoon/pattern/SubstitutionCloner.java new file mode 100644 index 00000000000..76b9550b189 --- /dev/null +++ b/src/main/java/spoon/pattern/SubstitutionCloner.java @@ -0,0 +1,257 @@ +//package spoon.pattern; +// +//import java.util.Collection; +//import java.util.Collections; +//import java.util.IdentityHashMap; +//import java.util.Map; +//import java.util.Set; +// +//import spoon.reflect.code.CtCodeElement; +//import spoon.reflect.code.CtComment; +//import spoon.reflect.cu.CompilationUnit; +//import spoon.reflect.cu.SourcePosition; +//import spoon.reflect.declaration.CtElement; +//import spoon.reflect.declaration.CtType; +//import spoon.reflect.declaration.CtTypeMember; +//import spoon.reflect.meta.RoleHandler; +//import spoon.reflect.meta.impl.RoleHandlerHelper; +//import spoon.support.SpoonClassNotFoundException; +//import spoon.support.visitor.equals.CloneHelper; +// +///** +// * Clones provided AST and substitutes required nodes and attributes using defined parameters +// */ +//class SubstitutionCloner extends CloneHelper { +// private final SubstitutionRequestProvider modelValueResolver; +// private final ParameterValueProvider parameters; +// /* +// * Set of parents of substituted elements, which might be potentially simplified (partially evaluated) +// */ +// Set toBeSimplifiedElements = Collections.newSetFromMap(new IdentityHashMap<>()); +// +// SubstitutionCloner(SubstitutionRequestProvider pattern, ParameterValueProvider parameters) { +// super(); +// this.modelValueResolver = pattern; +// this.parameters = parameters; +// } +// +// /** +// * Handle substitution of single value element. +// * Zero or one element can be result of substitution of `element` +// */ +// @Override +// public T clone(T origin) { +// if (origin == null) { +// return null; +// } +// //TODO refactor clone process by way it directly delivers correct CtRole. Next line has some performance impact +// ResultHolder.Single result = new ResultHolder.Single<>(getExpectedClassInParent(origin)); +// substituteOrClone(origin, result); +// return result.getResult(); +// } +// +// /** +// * Handle substitution of element in a collection. +// * Zero, one or more elements can be result of substitution of `element` +// */ +// @Override +// protected void addClone(Collection targetCollection, T origin) { +// //TODO refactor clone process by way it directly delivers correct CtRole. Next line has some performance impact +// ResultHolder.Multiple result = new ResultHolder.Multiple<>(getExpectedClassInParent(origin)); +// substituteOrClone(origin, result); +// targetCollection.addAll(result.getResult()); +// } +// +// /** +// * Handle substitution of element in a Map. +// * Zero or one element can be result of substitution of `element` +// */ +// @Override +// protected void addClone(Map targetMap, String key, T originValue) { +// //TODO refactor clone process by way it directly delivers correct CtRole. Next line has some performance impact +// ResultHolder.Single result = new ResultHolder.Single<>(getExpectedClassInParent(originValue)); +// substituteOrClone(originValue, result); +// if (result.getResult() == null) { +// //the pattern removes this element. Do not add it into target map +// return; +// } +// targetMap.put(key, result.getResult()); +// return; +// } +// +// private Class getExpectedClassInParent(CtElement element) { +// RoleHandler rh = RoleHandlerHelper.getRoleHandlerWrtParent(element); +// if (rh == null) { +// return null; +// } +// return (Class) rh.getValueClass(); +// } +// +// @SuppressWarnings("unchecked") +// protected void substituteOrClone(T origin, ResultHolder result) { +// String generatedBy = origin instanceof CtTypeMember ? getGeneratedByComment(origin) : null; +// //substitute the origin element and write result into context +// substituteOrClone2(origin, result); +// +// if (generatedBy != null) { +// //add generated by comment +// result.mapEachResult(element -> { +// if (element instanceof CtTypeMember) { +// addGeneratedByComment(element, generatedBy); +// } +// return element; +// }); +// } +// if (toBeSimplifiedElements.remove(origin)) { +// //simplify this element, it contains a substituted element +// //TODO it would be nice if CloneHelper would directly set the required class, but it would need more changes... +// if (origin.isParentInitialized()) { +// RoleHandler rh = RoleHandlerHelper.getRoleHandlerWrtParent(origin); +// if (rh != null) { +// result.setRequiredClass(rh.getValueClass()); +// } else { +// result.setRequiredClass(origin.getClass()); +// } +// } +// result.mapEachResult(element -> { +// if (element instanceof CtCodeElement) { +// CtCodeElement code = (CtCodeElement) element; +// try { +// code = code.partiallyEvaluate(); +// if (result.getRequiredClass().isInstance(code)) { +// return (T) code; +// } +// /* +// * else the simplified code is not matching with required type. +// * For example statement String.class.getName() was converted to expression "java.lang.String" +// */ +// } catch (SpoonClassNotFoundException e) { +// //ignore it. Do not simplify this element +// origin.getFactory().getEnvironment().debugMessage("Partial evaluation was skipped because of: " + e.getMessage()); +// } +// } +// return element; +// }); +// } +// } +// +// /** +// * Clones or substitutes origin element using {@link Node} of the role of this attribute +// * for `origin` element, then element is substituted +// * @param origin to be cloned or substituted element +// * @param result holder for result +// */ +// private void substituteOrClone2(T origin, ResultHolder result) { +// Node valueResolver = modelValueResolver.getTemplateValueResolver(origin); +// if ((valueResolver instanceof ConstantNode) == false) { +// //it is not just copying of node or substitution of node attributes +// //the node is replaced by different 0, 1 or more nodes +// planSimplification(origin.getParent()); +// } +// valueResolver.generateTargets(factory, result, parameters); +// } +// +// private static String getGeneratedByComment(CtElement ele) { +// SourcePosition pos = ele.getPosition(); +// if (pos != null) { +// CompilationUnit cu = pos.getCompilationUnit(); +// if (cu != null) { +// CtType mainType = cu.getMainType(); +// if (mainType != null) { +// StringBuilder result = new StringBuilder(); +// result.append("Generated by "); +// result.append(mainType.getQualifiedName()); +// appendInnerTypedElements(result, mainType, ele); +// result.append('('); +// result.append(mainType.getSimpleName()); +// result.append(".java:"); +// result.append(pos.getLine()); +// result.append(')'); +// return result.toString(); +// } +// } +// } +// return null; +// } +// +// private static void appendInnerTypedElements(StringBuilder result, CtType mainType, CtElement ele) { +// CtTypeMember typeMember = getFirst(ele, CtTypeMember.class); +// if (typeMember != null && typeMember != mainType) { +// if (typeMember.isParentInitialized()) { +// appendInnerTypedElements(result, mainType, typeMember.getParent()); +// } +// if (typeMember instanceof CtType) { +// result.append('$'); +// } else { +// result.append('#'); +// } +// result.append(typeMember.getSimpleName()); +// } +// } +// +// private static void addGeneratedByComment(CtElement ele, String generatedBy) { +// if (generatedBy == null) { +// return; +// } +// String EOL = System.getProperty("line.separator"); +// CtComment comment = getJavaDoc(ele); +// String content = comment.getContent(); +// if (content.trim().length() > 0) { +// content += EOL + EOL; +// } +// content += generatedBy; +// comment.setContent(content); +// } +// +// private static CtComment getJavaDoc(CtElement ele) { +// for (CtComment comment : ele.getComments()) { +// if (comment.getCommentType() == CtComment.CommentType.JAVADOC) { +// return comment; +// } +// } +// CtComment c = ele.getFactory().Code().createComment("", CtComment.CommentType.JAVADOC); +// ele.addComment(c); +// return c; +// } +// +// @SuppressWarnings("unchecked") +// private static T getFirst(CtElement ele, Class clazz) { +// if (ele != null) { +// if (clazz.isAssignableFrom(ele.getClass())) { +// return (T) ele; +// } +// if (ele.isParentInitialized()) { +// return getFirst(ele.getParent(), clazz); +// } +// } +// return null; +// } +// +// /** +// * @param element to be cloned element +// * @return a clone which is not substituted +// */ +// public T originClone(T element) { +// T clone = super.clone(element); +// if (clone instanceof CtType) { +// SourcePosition pos = clone.getPosition(); +// if (pos != null) { +// CompilationUnit cu = pos.getCompilationUnit(); +// if (cu != null && cu.getImports().size() > 0) { +// //avoid usage of invalid template imports in generated code +// //TODO - this is just dirty workaround, which removes imports for templates too - but it should be no big problem ... +// cu.setImports(Collections.emptySet()); +// } +// } +// } +// return clone; +// } +// +// /** +// * plans simplification of clone of `element` after cloning of it's children is finished +// * @param element origin (not cloned) element, whose clone has to be simplified +// */ +// private void planSimplification(CtElement element) { +// toBeSimplifiedElements.add(element); +// } +//} diff --git a/src/main/java/spoon/pattern/SubstitutionRequestProvider.java b/src/main/java/spoon/pattern/SubstitutionRequestProvider.java new file mode 100644 index 00000000000..a5d766be768 --- /dev/null +++ b/src/main/java/spoon/pattern/SubstitutionRequestProvider.java @@ -0,0 +1,28 @@ +/** + * 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.pattern; + +/** + * Maps AST model object to the {@link Node} + */ +public interface SubstitutionRequestProvider { + /** + * @param object a node from the Pattern model to be matched + * @return {@link Node}, which has to be used to match `object` from model of {@link SubstitutionRequestProvider} + */ + Node getTemplateValueResolver(Object object); +} diff --git a/src/main/java/spoon/pattern/SwitchNode.java b/src/main/java/spoon/pattern/SwitchNode.java new file mode 100644 index 00000000000..5194d665179 --- /dev/null +++ b/src/main/java/spoon/pattern/SwitchNode.java @@ -0,0 +1,185 @@ +/** + * 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.pattern; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiConsumer; + +import spoon.pattern.matcher.Matchers; +import spoon.pattern.matcher.TobeMatched; +import spoon.reflect.factory.Factory; + +/** + * List of conditional cases + * {code} + * if (a) { + * ... someStatements if a == true.. + * } else if (b) { + * ... someStatements if b == true.. + * } else { + * ... someStatements in other cases ... + * } + */ +public class SwitchNode implements Node { + + private List cases = new ArrayList<>(); + + public SwitchNode() { + super(); + } + + @Override + public boolean replaceNode(Node oldNode, Node newNode) { + for (CaseNode caseNode : cases) { + if (caseNode.replaceNode(oldNode, newNode)) { + return true; + } + } + return false; + } + + /** + * Adds another case into this switch statement + * @param vrOfExpression if value of this parameter is true then statement has to be used. If vrOfExpression is null, then statement is always used + * @param statement optional statement + */ + public void addCase(PrimitiveMatcher vrOfExpression, Node statement) { + cases.add(new CaseNode(vrOfExpression, statement)); + } + + @Override + public void generateTargets(Factory factory, ResultHolder result, ParameterValueProvider parameters) { + for (CaseNode case1 : cases) { + case1.generateTargets(factory, result, parameters); + } + } + + @Override + public void forEachParameterInfo(BiConsumer consumer) { + for (CaseNode case1 : cases) { + if (case1.vrOfExpression != null) { + case1.vrOfExpression.forEachParameterInfo(consumer); + } + if (case1.statement != null) { + case1.statement.forEachParameterInfo(consumer); + } + } + } + + @Override + public TobeMatched matchTargets(TobeMatched targets, Matchers nextMatchers) { + boolean hasDefaultCase = false; + //detect which case is matching - if any + for (CaseNode case1 : cases) { + TobeMatched match = case1.matchTargets(targets, nextMatchers); + if (match != null) { + return match; + } + if (case1.vrOfExpression == null) { + hasDefaultCase = true; + } + } + //no case matched + if (hasDefaultCase) { + //nothing matched and even the default case didn't matched, so whole switch cannot match + return null; + } + /* + * else this switch is optional and matches 0 targets - OK, it is match too. + * 1) set all expressions to false + * 2) match nextMatchers + */ + return new CaseNode(null, null).matchTargets(targets, nextMatchers); + } + + private class CaseNode implements Node { + /* + * is null for the default case + */ + private PrimitiveMatcher vrOfExpression; + private Node statement; + private CaseNode(PrimitiveMatcher vrOfExpression, Node statement) { + super(); + this.vrOfExpression = vrOfExpression; + this.statement = statement; + } + + @Override + public boolean replaceNode(Node oldNode, Node newNode) { + if (vrOfExpression != null) { + if (vrOfExpression == oldNode) { + vrOfExpression = (PrimitiveMatcher) newNode; + return true; + } + if (vrOfExpression.replaceNode(oldNode, newNode)) { + return true; + } + } + if (statement != null) { + if (statement == oldNode) { + statement = newNode; + return true; + } + if (statement.replaceNode(oldNode, newNode)) { + return true; + } + } + return false; + } + + @Override + public TobeMatched matchTargets(TobeMatched targets, Matchers nextMatchers) { + ParameterValueProvider parameters = targets.getParameters(); + //set all switch parameter values following match case. Even no matching case is OK - everything is false then + for (CaseNode case1 : cases) { + if (case1.vrOfExpression != null) { + //set expression of this `if` depending on if this case matched or not + parameters = case1.vrOfExpression.matchTarget(case1 == this, parameters); + if (parameters == null) { + //this value doesn't matches we cannot match this case + return null; + } + } + } + targets = targets.copyAndSetParams(parameters); + if (statement != null) { + return statement.matchTargets(targets, nextMatchers); + } + return nextMatchers.matchAllWith(targets); + } + @Override + public void forEachParameterInfo(BiConsumer consumer) { + SwitchNode.this.forEachParameterInfo(consumer); + } + @Override + public void generateTargets(Factory factory, ResultHolder result, ParameterValueProvider parameters) { + if (statement != null) { + if (isCaseSelected(factory, parameters)) { + statement.generateTargets(factory, result, parameters); + } + } + } + private boolean isCaseSelected(Factory factory, ParameterValueProvider parameters) { + if (vrOfExpression == null) { + return true; + } + Boolean value = vrOfExpression.generateTarget(factory, parameters, Boolean.class); + return value == null ? false : value.booleanValue(); + } + } +} diff --git a/src/main/java/spoon/pattern/TemplateBuilder.java b/src/main/java/spoon/pattern/TemplateBuilder.java new file mode 100644 index 00000000000..98e975f36fc --- /dev/null +++ b/src/main/java/spoon/pattern/TemplateBuilder.java @@ -0,0 +1,140 @@ +/** + * 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.pattern; + + +import java.util.List; +import java.util.Map; + +import spoon.SpoonException; +import spoon.reflect.declaration.CtClass; +import spoon.reflect.declaration.CtElement; +import spoon.reflect.declaration.CtField; +import spoon.reflect.declaration.CtType; +import spoon.reflect.factory.Factory; +import spoon.reflect.reference.CtTypeReference; +import spoon.support.template.Parameters; +import spoon.template.Local; +import spoon.template.Parameter; +import spoon.template.Substitution; +import spoon.template.Template; +import spoon.template.TemplateParameter; + +/** + * The builder which creates a {@link Pattern} from the {@link Template} + */ +public class TemplateBuilder { + + /** + * 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) { + CtClass> templateType = Substitution.getTemplateCtClass(templateRoot.getFactory(), template); + return createPattern(templateRoot, templateType, template); + } + public static TemplateBuilder createPattern(CtElement templateRoot, CtClass templateType, Template template) { + Factory f = templateRoot.getFactory(); + + if (template != null && templateType.getQualifiedName().equals(template.getClass().getName()) == false) { + throw new SpoonException("Unexpected template instance " + template.getClass().getName() + ". Expects " + templateType.getQualifiedName()); + } + + PatternBuilder pb; + + @SuppressWarnings("rawtypes") + CtTypeReference templateParamRef = f.Type().createReference(TemplateParameter.class); + if (templateType == templateRoot) { + //templateRoot is a class which extends from Template. We have to remove all Templating stuff from the patter model + pb = PatternBuilder.create(templateType, tv -> { + tv.keepTypeMembers(typeMember -> { + if (typeMember.getAnnotation(Parameter.class) != null) { + //remove all type members annotated with @Parameter + return false; + } + if (typeMember.getAnnotation(Local.class) != null) { + //remove all type members annotated with @Local + return false; + } + //remove all Fields of type TemplateParameter + if (typeMember instanceof CtField && ((CtField) typeMember).getType().isSubtypeOf(templateParamRef)) { + return false; + } + //all other type members have to be part of the pattern model + return true; + }); + //remove `... extends Template`, which doesn't have to be part of pattern model + tv.removeSuperClass(); + }); + } else { + pb = PatternBuilder.create(templateType, model -> model.setTemplateModel(templateRoot)); + } + Map templateParameters = template == null ? null : Parameters.getTemplateParametersAsMap(f, null, template); + pb.configureTemplateParameters(templateParameters); + return new TemplateBuilder(templateType, pb, template); + } + + private Template template; + private PatternBuilder patternBuilder; + private CtClass templateType; + + private TemplateBuilder(CtClass templateType, PatternBuilder patternBuilder, Template template) { + this.template = template; + this.patternBuilder = patternBuilder; + this.templateType = templateType; + } + + public Pattern build() { + return patternBuilder.build(); + } + + /** + * @return Map of template parameters from `template` + */ + public Map getTemplateParameters() { + return getTemplateParameters(null); + } + /** + * @param targetType the type which will receive the model generated using returned parameters + * @return Map of template parameters from `template` + */ + public Map getTemplateParameters(CtType targetType) { + Factory f = templateType.getFactory(); + return Parameters.getTemplateParametersAsMap(f, targetType, template); + } + + /** + * generates a new AST node 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 a substituted element + */ + public T substituteSingle(CtType targetType, Class itemType) { + return build().substituteSingle(targetType.getFactory(), itemType, getTemplateParameters(targetType)); + } + /** + * generates a new AST nodes made by cloning of `patternModel` and by substitution of parameters by their values + * @param factory TODO + * @param targetType the CtType, which will receive the result of substitution + * @return List of substituted elements + */ + public List substituteList(Factory factory, CtType targetType, Class itemType) { + return build().substituteList(factory, itemType, getTemplateParameters(targetType)); + } +} diff --git a/src/main/java/spoon/pattern/UnmodifiableParameterValueProvider.java b/src/main/java/spoon/pattern/UnmodifiableParameterValueProvider.java new file mode 100644 index 00000000000..5640524106e --- /dev/null +++ b/src/main/java/spoon/pattern/UnmodifiableParameterValueProvider.java @@ -0,0 +1,145 @@ +/** + * 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.pattern; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Provides value of parameter + */ +public class UnmodifiableParameterValueProvider implements ParameterValueProvider { + + public static class Factory implements ParameterValueProviderFactory { + public static final Factory INSTANCE = new Factory(); + @Override + public ParameterValueProvider createParameterValueProvider() { + return new UnmodifiableParameterValueProvider(); + } + } + + protected final ParameterValueProvider parent; + protected final Map map; + + public UnmodifiableParameterValueProvider(Map map) { + this(null, map); + } + private UnmodifiableParameterValueProvider(ParameterValueProvider parent, Map map) { + this.parent = parent; + this.map = Collections.unmodifiableMap(map); + } + + public UnmodifiableParameterValueProvider(Map map, String parameterName, Object value) { + this(null, map, parameterName, value); + } + + private UnmodifiableParameterValueProvider(ParameterValueProvider parent, Map map, String parameterName, Object value) { + this.parent = null; + Map copy = new HashMap<>(map.size() + 1); + copy.putAll(map); + copy.put(parameterName, value); + this.map = Collections.unmodifiableMap(copy); + } + + public UnmodifiableParameterValueProvider() { + this.parent = null; + this.map = Collections.emptyMap(); + } + + @Override + public UnmodifiableParameterValueProvider createLocalParameterValueProvider() { + return new UnmodifiableParameterValueProvider(this, Collections.emptyMap()); + } + + @Override + public boolean hasValue(String parameterName) { + if (map.containsKey(parameterName)) { + return true; + } + if (parent != null) { + return parent.hasValue(parameterName); + } + return false; + } + + @Override + public Object get(String parameterName) { + Object v = map.get(parameterName); + if (v == null && parent != null) { + v = parent.get(parameterName); + } + return v; + } + + @Override + public ParameterValueProvider putIntoCopy(String parameterName, Object value) { + return new UnmodifiableParameterValueProvider(parent, map, parameterName, value); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + appendMap(sb, map); + if (parent != null) { + sb.append("\nparent:\n"); + sb.append(parent.toString()); + } + return sb.toString(); + } + + private static void appendMap(StringBuilder sb, Map map) { + List paramNames = new ArrayList<>(map.keySet()); + paramNames.sort((a, b) -> a.compareTo(b)); + for (String name : paramNames) { + if (sb.length() > 0) { + sb.append("\n"); + } + sb.append(name).append('=').append(map.get(name)); + } + } + + @Override + public Map asMap() { + if (parent != null) { + Map merged = new HashMap<>(); + merged.putAll(parent.asMap()); + merged.putAll(map); + return Collections.unmodifiableMap(merged); + } + return map; + } + + @Override + public Map asLocalMap() { + return map; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof ParameterValueProvider) { + obj = ((ParameterValueProvider) obj).asMap(); + } + if (obj instanceof Map) { + Map map = (Map) obj; + return asMap().equals(map); + } + return false; + } +} diff --git a/src/main/java/spoon/pattern/ValueConvertor.java b/src/main/java/spoon/pattern/ValueConvertor.java new file mode 100644 index 00000000000..629f71f9fbf --- /dev/null +++ b/src/main/java/spoon/pattern/ValueConvertor.java @@ -0,0 +1,25 @@ +/** + * 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.pattern; + +/** + * Converts the individual parameter values to required type after substitution + * Converts the matching model values to parameter values during matching process + */ +public interface ValueConvertor { + T getValueAs(Object value, Class valueClass); +} diff --git a/src/main/java/spoon/pattern/ValueConvertorImpl.java b/src/main/java/spoon/pattern/ValueConvertorImpl.java new file mode 100644 index 00000000000..16dfd5e8bd8 --- /dev/null +++ b/src/main/java/spoon/pattern/ValueConvertorImpl.java @@ -0,0 +1,171 @@ +/** + * 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.pattern; + +import java.util.List; + +import spoon.SpoonException; +import spoon.reflect.code.CtBlock; +import spoon.reflect.code.CtExpression; +import spoon.reflect.code.CtInvocation; +import spoon.reflect.code.CtLiteral; +import spoon.reflect.code.CtNewArray; +import spoon.reflect.code.CtStatement; +import spoon.reflect.declaration.CtElement; +import spoon.reflect.declaration.CtExecutable; +import spoon.reflect.declaration.CtNamedElement; +import spoon.reflect.declaration.CtType; +import spoon.reflect.factory.Factory; +import spoon.reflect.reference.CtExecutableReference; +import spoon.reflect.reference.CtReference; +import spoon.reflect.reference.CtTypeReference; +import spoon.support.visitor.SignaturePrinter; + +/** + * Converts the individual parameter values to required type + */ +public class ValueConvertorImpl implements ValueConvertor { + + private final Factory factory; + + public ValueConvertorImpl(Factory factory) { + this.factory = factory; + } + + @Override + public T getValueAs(Object value, Class valueClass) { + if (valueClass.isInstance(value)) { + return cloneIfNeeded(valueClass.cast(value)); + } + if (CtExpression.class.isAssignableFrom(valueClass)) { + if (value instanceof Class) { + return (T) factory.Code().createClassAccess(factory.Type().createReference((Class) value)); + } + if (value instanceof CtTypeReference) { + //convert type reference into code element as class access + CtTypeReference tr = (CtTypeReference) value; + return (T) factory.Code().createClassAccess(tr); + } + if (value == null || value instanceof String || value instanceof Number || value instanceof Boolean || value instanceof Character) { + //convert String to code element as Literal + return (T) factory.Code().createLiteral(value); + } + if (value.getClass().isArray()) { + Class itemClass = value.getClass().getComponentType(); + if (CtExpression.class.isAssignableFrom(itemClass)) { + CtNewArray arr = factory.Core().createNewArray().setType(factory.Type().objectType()); + for (CtExpression expr : (CtExpression[]) value) { + arr.addElement(expr); + } + return (T) arr; + } + @SuppressWarnings({ "unchecked", "rawtypes" }) + CtNewArray arr = factory.Core().createNewArray().setType(factory.Type().createArrayReference(itemClass.getName())); + for (Object v : (Object[]) value) { + if (v == null || v instanceof String || v instanceof Number || v instanceof Boolean || v instanceof Character) { + //convert String to code element as Literal + arr.addElement(factory.Code().createLiteral(v)); + } else { + throw new SpoonException("Parameter value item class: " + v.getClass().getName() + " cannot be converted to class is: " + valueClass.getName()); + } + } + return (T) arr; + } + } + if (CtStatement.class.isAssignableFrom(valueClass)) { + if (value == null) { + //skip null statements + return null; + } + if (value instanceof List) { + List list = (List) value; + if (list.size() == 0) { + return null; + } + if (list.size() == 1) { + return (T) list.get(0); + } + CtBlock block = getFactory().createBlock(); + block.setImplicit(true); + for (CtStatement statement : ((Iterable) value)) { + block.addStatement(statement); + } + return (T) block; + } + } + if (valueClass.equals(String.class)) { + if (value instanceof CtNamedElement) { + return (T) ((CtNamedElement) value).getSimpleName(); + } else if (value instanceof CtReference) { + return (T) ((CtReference) value).getSimpleName(); + } else if (value instanceof Class) { + return (T) ((Class) value).getSimpleName(); + } else if (value instanceof CtInvocation) { + return (T) getShortSignatureForJavadoc(((CtInvocation) value).getExecutable()); + } else if (value instanceof CtExecutableReference) { + return (T) getShortSignatureForJavadoc((CtExecutableReference) value); + } else if (value instanceof CtExecutable) { + return (T) getShortSignatureForJavadoc(((CtExecutable) value).getReference()); + } else if (value instanceof CtLiteral) { + Object val = ((CtLiteral) value).getValue(); + return val == null ? null : (T) val.toString(); + } else if (value instanceof Enum) { + return (T) ((Enum) value).name(); + } + throw new SpoonException("Parameter value has unexpected class: " + value.getClass().getName() + ", whose conversion to String is not supported"); + } + if (CtTypeReference.class.isAssignableFrom(valueClass)) { + if (value == null) { + throw new SpoonException("The null value is not valid substitution for CtTypeReference"); + } + if (value instanceof Class) { + return (T) factory.Type().createReference((Class) value); + } else if (value instanceof CtTypeReference) { + return (T) ((CtTypeReference) value).clone(); + } else if (value instanceof CtType) { + return (T) ((CtType) value).getReference(); + } else if (value instanceof String) { + return (T) factory.Type().createReference((String) value); + } else { + throw new RuntimeException("unsupported reference substitution"); + } + } + + throw new SpoonException("Parameter value class: " + value.getClass().getName() + " cannot be converted to class is: " + valueClass.getName()); + } + + /* + * return the typical Javadoc style link Foo#method(). The class name is not fully qualified. + */ + private static String getShortSignatureForJavadoc(CtExecutableReference ref) { + SignaturePrinter sp = new SignaturePrinter(); + sp.writeNameAndParameters(ref); + return ref.getDeclaringType().getSimpleName() + CtExecutable.EXECUTABLE_SEPARATOR + sp.getSignature(); + } + + @SuppressWarnings("unchecked") + protected T cloneIfNeeded(T value) { + if (value instanceof CtElement) { + return (T) ((CtElement) value).clone(); + } + return value; + } + + public Factory getFactory() { + return factory; + } +} diff --git a/src/main/java/spoon/pattern/concept.md b/src/main/java/spoon/pattern/concept.md new file mode 100644 index 00000000000..4e86ba671ef --- /dev/null +++ b/src/main/java/spoon/pattern/concept.md @@ -0,0 +1,189 @@ +Template definition +1) compilable easy understandable Template +2) The good names of template parameters - well assigned to AST nodes +3) even the attributes of AST nodes might be a parameters (e.g. modifier of class) + +Notes: +- it doesn't matter what is the current AST node at place of parameter. It will be replaced by parameter value converted to instance of expected type +- we have to define which Types, methodNames are variables and which already have required value +- we have to define which statements, expressions are optional/mandatory + e.g. by + if (optional1) { + ...some optional statements... + } + +Generation of code from Template +1) filling template parameters by values +2) cloning template AST +3) substituting cloned AST parameter nodes by values + +Types of template parameters +A) AST node of type CtStatement, CtExpression (e.g. CtVariableAccess, ...) +B) replacing of CtTypeReference by another CtTypeReference +C) replacing of whole or part of simpleName or Reference.name by String value +D) replacing of any attribute of AST node by value of appropriate type + +Searching for code, which matches template +1) definition of filters on searched nodes +2) matching with AST +3) creating of Map/Structure of parameter to matching value from AST + + +{@link Pattern} knows the AST of the pattern model. +It knows list of parts of pattern model, which are target for substitution. + +The substitution target can be: +A) node replace parameters - it means whole AST node (subtree) is replaced by value of parameter + The type of such value is defined by parent attribute which holds this node: + Examples: CtTypeMember, CtStatement, CtExpression, CtParameter, ... + A1) Single node replace parameter - there must be exactly one node (with arbitrary subtree) as parameter value + Examples: + CtCatch.parameter, CtReturn.expression, CtBinaryOperator.leftOperand, + CtForEach.variable, + CtLoop.body, + CtField.type + ... + A2) Multiple nodes replace parameter - there must be 0, 1 or more nodes (with arbitrary subtrees) as parameter value + Examples: + CtType.interfaces, CtType.typeMembers + note: There can be 0 or 1 parameter assigned to model node + +Definition of such CtElement based parameters: +------------------------------ +- by `TemplateParameter.S()` - it works only for some node types. Does not work for CtCase, CtCatch, CtComment, CtAnnotation, CtEnumValue, ... +- by pointing to such node(s) - it works for all nodes. How? During building of Pattern, the client's code has to somehow select the parameter nodes + and add them into list of to be substituted nodes. Client may use + - Filter ... here we can filter for `TemplateParameter.S()` + - CtPath ... after it is fully implemented + - Filtering by their name - legacy templates are using that approach together with Parameter annotation + - manual navigation and collecting of substituted nodes + +B) node attribute replace - it means value of node attribute is replaced by value of parameter + B1) Single value attribute - there must be exactly one value as parameter value + Types are String, boolean, BinaryOperatorKind, UnaryOperatorKind, CommentType, primitive type of Literal.value + B2) Unordered multiple value attribute - there must be exactly one value as parameter value + There is only: CtModifiable.modifiers with type Enum ModifierKind + + note: There can be no parameter of type (A) assigned to node whose attributes are going to be replaced. + There can be more attributes replaced for one node + But there can be 0 or 1 parameter assigned to attribute of model node + +Definition of such Object based parameters: +------------------------------------------------------ +by pointing to such node(s) + + with specification of CtRole of that attribute + +C) Substring attribute replace - it means substring of string value is replaced + Examples: CtNamedElement.simpleName, CtStatement.label, CtComment.comment + + note: There can be no parameter of type (A) assigned to node whose String attributes are going to be replaced. + There can be 0, 1 or more parameter assigned to String of model node. Each must have different identifier. + +Definition of such parameters: +------------------------------ +by pointing to such node(s) + + with specification of CtRole of that attribute + + with specification of to be replaced substring +It can be done by searching in all String attributes of each node and searching for a variable marker. E.g. "$var_name$" + +Optionally there might be defined a variable value formatter, which assures that variable value is converted to expected string representation + +Why {@link Pattern} needs such high flexibility? +Usecase: The Pattern instance might be created by comparing of two similar models (not templates, but part of normal code). +All the differences anywhere would be considered as parameters of generated Pattern instance. +Request: Such pattern instance must be printable and compilable, so client can use it for further matching and replacing by different pattern. + + +Why ParameterInfo type? +---------------------- +Can early check whether parameter values can be accepted by Pattern +Needs a validation by SubstitutionRequests of ParameterInfo +Can act as a filter of TemplateMatcher parameter value + + +Matching algorithms +------------------- + +There are following kinds of Matching algorithms: + +MA-1) matching of one target value with one Matcher +Target value can be +A) single CtElement, single String, Enum +B) List of T, Set of T , Map of String to T, where T is a type defined above + +Input: +- matcher - to be matched Matcher. Supports `Constant Matcher`, `Variable Matcher` +- parameters - input Parameters +- target - to be matched target object + +Output: +- status - 'matched' only if whole target value matched with this Matcher. 'not matched' if something did not matched or if there remained some unmatched items. +- (if status==matched) parameters - matched parameter values + +MA-2) matching of container of targets with one Matcher +Target can be: +A) Single T +B) List of T +C) Set of T +D) Map of String to T + +Input: +- matcher - to be matched Matcher. Supports `Constant Matcher`, `Variable Matcher`, +- parameters - input Parameters +- targets - to be matched container of targets + +Output: +- status - 'matched' only if one or more target container items matched with `matcher`. 'not matched' otherwise. +- (if status==matched) parameters - matched Parameters +- (if status==matched) remainingTargets - container of remaining targets which did not matched - these which has to be matched next + +MA-3) matching of container of targets with container of Matchers +Target can be: +A) Single T +B) List of T +C) Set of T +D) Map of String to T +Input: +- matchers - container of to be matched Matchers - M, List of M, Set of M, Map of M to M, which has to match to container of targets, Where M is a Matcher +- parameters - input Parameters +- targets - to be matched container of targets +- mode - `matchAll` ALL items of target container must match with ALL `matchers`, `matchSubset` if SUBSET of items of target container must match with ALL `matchers` +Output: +- status - 'matched' only if ALL/SUBSET of target container items matched with all `matchers`. 'not matched' otherwise. +- (if status==matched) parameters - matched Parameters +- (if status==matched && mode==matchSubset) remaintargets - container of targets with subset of items which did not matched +- (if status==matched) matchedTargets - container of targets with subset of items which did matched + + +Primitive Matchers +----------------- +**Constant Matcher** +Match: matches equal value (String, Enum, CtElement or List/Set/Map of previous). Is implemented as that value +Generate: generates copy of template value + +**Variable Matcher** +matches any value (String, Enum, CtElement or List/Set/Map of previous) to the zero, one or more Parameters of type String, Enum, CtElement or List/Set/Map of previous. +Matcher has these options: +- mandatory - true if this Matcher must match in current state. false - Matching algorithm ignores this Matcher when there is no (more) match). +- repeatable - true if it may match again in current state (Matching algorithm tries to match this Matcher repeatedly until there is no match). false if can match maximum once. + +Compound matchers +----------------- +consists of Primitive Matchers or Compound matchers. They are always implemented as a ordered chain of matchers. +The matching algorithm evaluates first Matcher from the chain and then remaining matchers + +**XORMatcher** +Contains ordered List of Matchers, which are evaluated sequentially until first Matcher matches. Others are ignored + +**Container matcher** +Contains List, Set or Map of Matchers, which have to all match with provided targets + +Wrapping matchers +----------------- +**Optional matcher** +Consists of a condition and a Matcher. +If the Matcher matches then Parameters are set by the way the condition is true, +If the Matcher doesn't match, then Condition is set to false.' + **Nested matcher** +Consists of parameter mapping and Matcher. * All the matched parameters are collected in local Parameters, which are then mapped to outer Parameters. + diff --git a/src/main/java/spoon/pattern/matcher/ChainOfMatchersImpl.java b/src/main/java/spoon/pattern/matcher/ChainOfMatchersImpl.java new file mode 100644 index 00000000000..070a97c6494 --- /dev/null +++ b/src/main/java/spoon/pattern/matcher/ChainOfMatchersImpl.java @@ -0,0 +1,68 @@ +/** + * 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.pattern.matcher; + +import java.util.List; + +import spoon.SpoonException; +import spoon.pattern.Node; + +/** + * Chain of {@link Node}s. {@link Node}s are processed in the same order as they were added into chain + */ +public class ChainOfMatchersImpl implements Matchers { + private final Node firstMatcher; + private final Matchers next; + + public static Matchers create(List items, Matchers next) { + return createFromList(next, items, 0); + } + public static Matchers create(Node firstNode, Matchers next) { + return new ChainOfMatchersImpl(firstNode, next); + } + private static Matchers createFromList(Matchers next, List items, int idx) { + Node matcher; + while (true) { + if (idx >= items.size()) { + return next; + } + matcher = items.get(idx); + if (matcher != null) { + break; + } + idx++; + } + return new ChainOfMatchersImpl(matcher, createFromList(next, items, idx + 1)); + } + + private ChainOfMatchersImpl(Node firstMatcher, Matchers next) { + super(); + if (firstMatcher == null) { + throw new SpoonException("The firstMatcher Node MUST NOT be null"); + } + this.firstMatcher = firstMatcher; + if (next == null) { + throw new SpoonException("The next Node MUST NOT be null"); + } + this.next = next; + } + + @Override + public TobeMatched matchAllWith(TobeMatched targets) { + return firstMatcher.matchTargets(targets, next); + } +} diff --git a/src/main/java/spoon/pattern/matcher/MapEntryNode.java b/src/main/java/spoon/pattern/matcher/MapEntryNode.java new file mode 100644 index 00000000000..0a0e495d0b0 --- /dev/null +++ b/src/main/java/spoon/pattern/matcher/MapEntryNode.java @@ -0,0 +1,69 @@ +/** + * 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.pattern.matcher; + +import java.util.function.BiConsumer; + +import spoon.pattern.ParameterInfo; +import spoon.pattern.ParameterValueProvider; +import spoon.pattern.Node; +import spoon.pattern.ResultHolder; +import spoon.reflect.factory.Factory; + +/** + * Represents a ValueResolver of one Map.Entry + */ +public class MapEntryNode implements Node { + private final Node key; + private final Node value; + + public MapEntryNode(Node key, Node value) { + super(); + this.key = key; + this.value = value; + } + + public Node getKey() { + return key; + } + public Node getValue() { + return value; + } + + @Override + public boolean replaceNode(Node oldNode, Node newNode) { + //TODO + throw new UnsupportedOperationException("TODO"); + } + + @Override + public TobeMatched matchTargets(TobeMatched targets, Matchers nextMatchers) { + //TODO + throw new UnsupportedOperationException("TODO"); + } + @Override + public void forEachParameterInfo(BiConsumer consumer) { + // TODO + throw new UnsupportedOperationException("TODO"); + } + + @Override + public void generateTargets(Factory factory, ResultHolder result, ParameterValueProvider parameters) { + // TODO + throw new UnsupportedOperationException("TODO"); + } +} diff --git a/src/main/java/spoon/pattern/matcher/Match.java b/src/main/java/spoon/pattern/matcher/Match.java new file mode 100644 index 00000000000..616457b56ec --- /dev/null +++ b/src/main/java/spoon/pattern/matcher/Match.java @@ -0,0 +1,116 @@ +/** + * 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.pattern.matcher; + +import java.util.List; +import java.util.Map; + +import spoon.SpoonException; +import spoon.pattern.ParameterValueProvider; +import spoon.reflect.declaration.CtElement; + +/** + * Represents a Match of TemplateMatcher + */ +public class Match { + private final List matchingElements; + private final ParameterValueProvider parameters; + + Match(List matches, ParameterValueProvider parameters) { + this.parameters = parameters; + this.matchingElements = matches; + } + + public List getMatchingElements() { + return getMatchingElements(CtElement.class); + } + @SuppressWarnings("unchecked") + public List getMatchingElements(Class clazz) { + for (Object object : matchingElements) { + if (object != null && clazz.isInstance(object) == false) { + throw new SpoonException("Match contains a " + object.getClass() + " which cannot be cast to " + clazz); + } + } + return (List) matchingElements; + } + public CtElement getMatchingElement() { + return getMatchingElement(CtElement.class, true); + } + + public T getMatchingElement(Class clazz) { + return getMatchingElement(clazz, true); + } + + /** + * @param clazz the Class of returned element. throws SpoonException if matching value is not assignable to `clazz` + * @param failIfMany if there is more then one matching element and `failIfMany` == true, then it throws SpoonException. + * @return first matching element + */ + public T getMatchingElement(Class clazz, boolean failIfMany) { + if (matchingElements.isEmpty()) { + return null; + } + if (failIfMany && matchingElements.size() != 1) { + throw new SpoonException("There is more then one match"); + } + Object object = matchingElements.get(0); + if (object != null && clazz.isInstance(object) == false) { + throw new SpoonException("Match contains a " + object.getClass() + " which cannot be cast to " + clazz); + } + return clazz.cast(object); + } + + /** + * Replaces all matching elements with `newElements` + * @param newElements the elements which has to be used instead of matched element + */ + public void replaceMatchesBy(List newElements) { + if (matchingElements.isEmpty()) { + throw new SpoonException("Cannot replace empty list of elements"); + } + CtElement last = null; + for (CtElement oldElement : getMatchingElements(CtElement.class)) { + if (last != null) { + //delete all excluding last + last.delete(); + } + last = oldElement; + } + //replace last element + last.replace(newElements); + } + + public ParameterValueProvider getParameters() { + return parameters; + } + + public Map getParametersMap() { + return parameters.asMap(); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("{\n"); + sb.append(parameters.toString()); + sb.append("\n}\n----------"); + for (int i = 0; i < matchingElements.size(); i++) { + sb.append("\n").append(i + 1).append(") ").append(matchingElements.get(i)); + } + return sb.toString(); + } +} diff --git a/src/main/java/spoon/pattern/matcher/Matchers.java b/src/main/java/spoon/pattern/matcher/Matchers.java new file mode 100644 index 00000000000..8da86a6e3d1 --- /dev/null +++ b/src/main/java/spoon/pattern/matcher/Matchers.java @@ -0,0 +1,32 @@ +/** + * 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.pattern.matcher; + +import spoon.pattern.Node; + +/** + * A container of {@link Node}s. + */ +public interface Matchers { + + /** + * Matches all matchers of this {@link Matchers} instance with `targets` + * @param targets to be matched target nodes and input parameters + * @return {@link TobeMatched} with targets which remained after all {@link Node}s were matched + matched parameters + */ + TobeMatched matchAllWith(TobeMatched targets); +} diff --git a/src/main/java/spoon/pattern/matcher/MatchingScanner.java b/src/main/java/spoon/pattern/matcher/MatchingScanner.java new file mode 100644 index 00000000000..df66e3ac1cf --- /dev/null +++ b/src/main/java/spoon/pattern/matcher/MatchingScanner.java @@ -0,0 +1,139 @@ +/** + * 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.pattern.matcher; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import spoon.SpoonException; +import spoon.pattern.ModelNode; +import spoon.pattern.ParameterValueProviderFactory; +import spoon.reflect.declaration.CtElement; +import spoon.reflect.meta.ContainerKind; +import spoon.reflect.path.CtRole; +import spoon.reflect.visitor.EarlyTerminatingScanner; +import spoon.reflect.visitor.chain.CtConsumer; + +/** + * Represents a Match of TemplateMatcher + */ +public class MatchingScanner extends EarlyTerminatingScanner { + private final ModelNode pattern; + private ParameterValueProviderFactory parameterValueProviderFactory; + private CtConsumer matchConsumer; + + public MatchingScanner(ModelNode pattern, ParameterValueProviderFactory parameterValueProviderFactory, CtConsumer matchConsumer) { + this.pattern = pattern; + this.parameterValueProviderFactory = parameterValueProviderFactory; + this.matchConsumer = matchConsumer; + } + + @Override + public void scan(CtRole role, CtElement element) { + //This is called only for elements which are in single value attribute. Like `CtType#superClass` + if (searchMatchInList(role, Collections.singletonList(element), false) == 0) { + super.scan(role, element); + } + } + + @Override + public void scan(CtRole role, Collection elements) { + if (elements == null) { + return; + } + if (elements instanceof List) { + searchMatchInList(role, (List) elements, true); + } else if (elements instanceof Set) { + searchMatchInSet(role, (Set) elements); + } else { + throw new SpoonException("Unexpected Collection type " + elements.getClass()); + } + } + + private int searchMatchInList(CtRole role, List list, boolean scanChildren) { + int matchCount = 0; + if (list.size() > 0) { + TobeMatched tobeMatched = TobeMatched.create( + parameterValueProviderFactory.createParameterValueProvider(), + ContainerKind.LIST, + list); + while (tobeMatched.hasTargets()) { + TobeMatched nextTobeMatched = pattern.matchAllWith(tobeMatched); + if (nextTobeMatched != null) { + List matchedTargets = tobeMatched.getMatchedTargets(nextTobeMatched); + if (matchedTargets.size() > 0) { + matchCount++; + //send information about match to client + matchConsumer.accept(new Match(matchedTargets, nextTobeMatched.getParameters())); + //do not scan children of matched elements. They already matched, so we must not scan them again + //use targets of last match together with new parameters for next match + tobeMatched = nextTobeMatched.copyAndSetParams(parameterValueProviderFactory.createParameterValueProvider()); + continue; + } //else the template matches nothing. Understand it as no match in this context + } + if (scanChildren) { + //scan children of each not matched element too + super.scan(role, tobeMatched.getTargets().get(0)); + } + //try match with sub list starting on second element + tobeMatched = tobeMatched.removeTarget(0); + } + } + return matchCount; + } + + private void searchMatchInSet(CtRole role, Set set) { + if (set.size() > 0) { + //copy targets, because it might be modified by call of matchConsumer, when refactoring spoon model + //use List, because Spoon uses Sets with predictable order - so keep the order + TobeMatched tobeMatched = TobeMatched.create( + parameterValueProviderFactory.createParameterValueProvider(), + ContainerKind.SET, + set); + while (tobeMatched.hasTargets()) { + TobeMatched nextTobeMatched = pattern.matchAllWith(tobeMatched); + if (nextTobeMatched != null) { + List matchedTargets = tobeMatched.getMatchedTargets(nextTobeMatched); + if (matchedTargets.size() > 0) { + //send information about match to client + matchConsumer.accept(new Match(matchedTargets, nextTobeMatched.getParameters())); + //do not scan children of matched elements. They already matched, so we must not scan them again + tobeMatched = nextTobeMatched; + //we have found a match. Try next match + continue; + } //else the template matches nothing. Understand it as no more match in this context + } + //there was no match. Do not try it again + break; + } + //scan remaining not matched items of the Set + for (Object object : tobeMatched.getTargets()) { + //scan children of each not matched element too + super.scan(role, object); + } + } + } + + @Override + public void scan(CtRole role, Map elements) { + // TODO Auto-generated method stub + super.scan(role, elements); + } +} diff --git a/src/main/java/spoon/pattern/matcher/NodeListMatcher.java b/src/main/java/spoon/pattern/matcher/NodeListMatcher.java new file mode 100644 index 00000000000..99421673606 --- /dev/null +++ b/src/main/java/spoon/pattern/matcher/NodeListMatcher.java @@ -0,0 +1,38 @@ +/** + * 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.pattern.matcher; +//TODO move to spoon.pattern.matcher + +import java.util.List; + +import spoon.pattern.ParameterValueProvider; +import spoon.reflect.declaration.CtElement; + +/** + * Marks the SubstitutionRequest which has to match whole AST node (not only some attribute of node) + */ +public interface NodeListMatcher { + /** + * matches this {@link NodeListMatcher} with `nextTargets` and `nextTemplates` storing the matched values into `parameters` + * @param parameters the collector of matched parameters + * @param nextTargets the List of + * @param nextTemplates + * @return if this {@link NodeListMatcher} matched and all `nextTemplates` matched, then return number of matching items from `nextTargets` + * if something doesn't match, then return -1 + */ + int matches(ParameterValueProvider parameters, List nextTargets, List nextTemplates); +} diff --git a/src/main/java/spoon/pattern/matcher/Quantifier.java b/src/main/java/spoon/pattern/matcher/Quantifier.java new file mode 100644 index 00000000000..b7d31041863 --- /dev/null +++ b/src/main/java/spoon/pattern/matcher/Quantifier.java @@ -0,0 +1,44 @@ +/** + * 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.pattern.matcher; + +import spoon.pattern.Node; + +/** + * Defines a strategy used to resolve conflict between two {@link Node}s + */ +public enum Quantifier { + /** + * Greedy quantifiers are considered "greedy" because they force the matcher to read in, or eat, + * the entire input prior to attempting the next match. + * If the next match attempt (the entire input) fails, the matcher backs off the input by one and tries again, + * repeating the process until a match is found or there are no more elements left to back off from. + */ + GREEDY, + /** + * The reluctant quantifier takes the opposite approach: It start at the beginning of the input, + * then reluctantly eat one character at a time looking for a match. + * The last thing it tries is the entire input. + */ + RELUCTANT, + /** + * The possessive quantifier always eats the entire input string, + * trying once (and only once) for a match. Unlike the greedy quantifiers, possessive quantifiers never back off, + * even if doing so would allow the overall match to succeed. + */ + POSSESSIVE +} diff --git a/src/main/java/spoon/pattern/matcher/TobeMatched.java b/src/main/java/spoon/pattern/matcher/TobeMatched.java new file mode 100644 index 00000000000..2c4cdd286e5 --- /dev/null +++ b/src/main/java/spoon/pattern/matcher/TobeMatched.java @@ -0,0 +1,235 @@ +/** + * 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.pattern.matcher; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.BiFunction; + +import spoon.SpoonException; +import spoon.pattern.ParameterValueProvider; +import spoon.reflect.meta.ContainerKind; + +/** + * Describes what next has to be matched. + * It consists of current `parameters` represented by {@link ParameterValueProvider} + * and by a to be matched target elements. + * See children of {@link TobeMatched} for supported collections of targer elements. + */ +public class TobeMatched { + //TODO remove parameters. Send them individually into matching methods and return MatchResult + private final ParameterValueProvider parameters; + //Use list for everything because Spoon uses Sets with predictable iteration order + private final List targets; + private final boolean ordered; + + /** + * @param parameters to be matched parameters + * @param containerKind the type of container in `target` value + * @param target the to be matched target data. List, Set, Map or single value + * @return new instance of {@link TobeMatched}, which contains `parameters` and `target` mapped using containerKind + */ + public static TobeMatched create(ParameterValueProvider parameters, ContainerKind containerKind, Object target) { + switch (containerKind) { + case LIST: + return new TobeMatched(parameters, (List) target, true); + case SET: + return new TobeMatched(parameters, (Set) target, false); + case MAP: + return new TobeMatched(parameters, (Map) target); + case SINGLE: + return new TobeMatched(parameters, target); + } + throw new SpoonException("Unexpected RoleHandler containerKind: " + containerKind); + } + + private TobeMatched(ParameterValueProvider parameters, Object target) { + //It is correct to put whole container as single value in cases when ParameterNode matches agains whole attribute value +// if (target instanceof Collection || target instanceof Map) { +// throw new SpoonException("Invalid argument. Use other constructors"); +// } + this.parameters = parameters; + //make a copy of origin collection, because it might be modified during matching process (by a refactoring algorithm) + this.targets = Collections.singletonList(target); + this.ordered = true; + } + /** + * @param parameters current parameters + * @param targets List or Set of to be matched targets + * @param ordered defines the way how targets are matched. If true then first target is matched with first ValueResolver. + * If false then all targets are tried with first ValueResolver. + */ + private TobeMatched(ParameterValueProvider parameters, Collection targets, boolean ordered) { + this.parameters = parameters; + //make a copy of origin collection, because it might be modified during matching process (by a refactoring algorithm) + this.targets = targets == null ? Collections.emptyList() : Collections.unmodifiableList(new ArrayList<>(targets)); + this.ordered = ordered; + } + + private TobeMatched(ParameterValueProvider parameters, Map targets) { + this.parameters = parameters; + //make a copy of origin collection, because it might be modified during matching process (by a refactoring algorithm) + this.targets = targets == null ? Collections.emptyList() : Collections.unmodifiableList(new ArrayList<>(targets.entrySet())); + this.ordered = false; + } + + private TobeMatched(ParameterValueProvider parameters, List targets, boolean ordered, int tobeRemovedIndex) { + this.parameters = parameters; + this.targets = new ArrayList<>(targets); + if (tobeRemovedIndex >= 0) { + this.targets.remove(tobeRemovedIndex); + } + this.ordered = ordered; + } + + /** + * @return parameters of last successful match. + */ + public ParameterValueProvider getParameters() { + return parameters; + } + + /** + * @return {@link List} of to be matched targets, which + * A) have to be matched by current matching step + * B) remained after matching of all template nodes + */ + public List getTargets() { + return targets; + } + + /** + * @param tobeMatchedTargets {@link TobeMatched} with targets, which didn't matched yet. These which has to be matched next. + * @return matched targets. It means these targets, which are not contained in `notMatchedTargets` + */ + public List getMatchedTargets(TobeMatched tobeMatchedTargets) { + int nrOfMatches = getTargets().size() - tobeMatchedTargets.getTargets().size(); + if (nrOfMatches >= 0) { + if (nrOfMatches == 0) { + return Collections.emptyList(); + } + List matched = new ArrayList(nrOfMatches); + for (Object target : getTargets()) { + if (containsSame(tobeMatchedTargets.getTargets(), target)) { + //this origin target is still available in this to be matched targets + continue; + } + //this origin target is NOT available in this to be matched targets. It means it matched + matched.add(target); + } + if (matched.size() == nrOfMatches) { + return matched; + } + } + throw new SpoonException("Invalid input `originTobeMatched`"); + } + + private boolean containsSame(List items, Object object) { + for (Object item : items) { + if (item == object) { + return true; + } + } + return false; + } + + /** + * @return true if there is anything to match. + */ + public boolean hasTargets() { + return targets.size() > 0; + } + + /** + * Makes a copy of this match context with the same targets, but with new `parameters` + * @param newParams to be used parameters + * @return copy of {@link TobeMatched} with new parameters + */ + public TobeMatched copyAndSetParams(ParameterValueProvider newParams) { + if (parameters == newParams) { + return this; + } + return new TobeMatched(newParams, targets, ordered, -1); + } + + /** + * Calls matcher algorithm to match target item + * @param matcher a matching algorithm + * @return {@link TobeMatched} with List of remaining (to be matched) targets or null if there is no match + */ + public TobeMatched matchNext(BiFunction matcher) { + if (targets.isEmpty()) { + //no target -> no match + return null; + } + if (ordered) { + //handle ordered list of targets - match with first target + ParameterValueProvider parameters = matcher.apply(targets.get(0), getParameters()); + if (parameters != null) { + //return remaining match + return removeTarget(parameters, 0); + } + return null; + } else { + //handle un-ordered list of targets - match with all targets, stop at first matching + int idxOfMatch = 0; + while (idxOfMatch < targets.size()) { + ParameterValueProvider parameters = matcher.apply(targets.get(idxOfMatch), getParameters()); + if (parameters != null) { + return removeTarget(parameters, idxOfMatch); + } + //try to match next target + idxOfMatch++; + } + return null; + } + } + + /** + * @param remainingMatch the {@link TobeMatched} whose parameters has to be returned + * @return parameters from `remainingMatch`, if it exists. Else returns null + */ + public static ParameterValueProvider getMatchedParameters(TobeMatched remainingMatch) { + return remainingMatch == null ? null : remainingMatch.getParameters(); + } + /** + * @param idxOfTobeRemovedTarget index of to be removed target + * @return new {@link TobeMatched} without the target on the index `idxOfTobeRemovedTarget` + */ + public TobeMatched removeTarget(int idxOfTobeRemovedTarget) { + return removeTarget(parameters, idxOfTobeRemovedTarget); + } + public TobeMatched removeTarget(ParameterValueProvider parameters, int idxOfTobeRemovedTarget) { + return new TobeMatched(parameters, targets, ordered, idxOfTobeRemovedTarget); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Parameters:\n----------------\n") + .append(parameters) + .append("\nTobe matched target elements\n-----------------------\n"); + for (int i = 0; i < targets.size(); i++) { + sb.append('\n').append(i + 1).append('/').append(targets.size()).append(": ").append(targets.get(i)); + } + return sb.toString(); + } +} diff --git a/src/main/java/spoon/reflect/reference/CtTypeReference.java b/src/main/java/spoon/reflect/reference/CtTypeReference.java index cbf9a621fec..dd58dae6ea5 100644 --- a/src/main/java/spoon/reflect/reference/CtTypeReference.java +++ b/src/main/java/spoon/reflect/reference/CtTypeReference.java @@ -22,6 +22,7 @@ import spoon.reflect.declaration.CtType; import spoon.reflect.declaration.CtTypeInformation; import spoon.reflect.declaration.CtTypeParameter; +import spoon.reflect.declaration.ModifierKind; import spoon.reflect.path.CtRole; import spoon.support.DerivedProperty; import spoon.support.SpoonClassNotFoundException; @@ -145,6 +146,10 @@ public interface CtTypeReference extends CtReference, CtActualTypeContainer, @DerivedProperty CtTypeReference getSuperclass(); + @Override + @DerivedProperty + Set getModifiers(); + /** * Checks visibility based on public, protected, package protected and private modifiers of type * @param type diff --git a/src/main/java/spoon/support/template/Parameters.java b/src/main/java/spoon/support/template/Parameters.java index 09beac8f19b..386f3d3d778 100644 --- a/src/main/java/spoon/support/template/Parameters.java +++ b/src/main/java/spoon/support/template/Parameters.java @@ -17,6 +17,7 @@ package spoon.support.template; import spoon.SpoonException; +import spoon.pattern.PatternBuilder; import spoon.reflect.code.CtArrayAccess; import spoon.reflect.code.CtExpression; import spoon.reflect.code.CtLiteral; @@ -98,6 +99,7 @@ private static Object getValue(Template template, String parameterName, Field if (Modifier.isFinal(rtField.getModifiers())) { Map m = finals.get(template); if (m == null) { + //BUG: parameters marked as final will always return null, even if they have a value! return null; } return m.get(parameterName); @@ -237,12 +239,29 @@ public static Map getNamesToValues(Template template, CtClass */ public static Map getTemplateParametersAsMap(Factory f, CtType targetType, Template template) { Map params = new HashMap<>(Parameters.getNamesToValues(template, (CtClass) f.Class().get(template.getClass()))); - if (targetType != null) { - /* - * there is required to replace all template model references by target type reference. - * Handle that request as template parameter too - */ - params.put(template.getClass().getSimpleName(), targetType.getReference()); + //detect reference to to be generated type + CtTypeReference targetTypeRef = targetType == null ? null : targetType.getReference(); + if (targetType == null) { + //legacy templates has target type stored under variable whose name was equal to simple name of template type + Object targetTypeObject = params.get(template.getClass().getSimpleName()); + if (targetTypeObject != null) { + if (targetTypeObject instanceof CtTypeReference) { + targetTypeRef = (CtTypeReference) targetTypeObject; + } else if (targetTypeObject instanceof String) { + targetTypeRef = f.Type().createReference((String) targetTypeObject); + } else if (targetTypeObject instanceof Class) { + targetTypeRef = f.Type().createReference((Class) targetTypeObject); + } else { + throw new SpoonException("Unsupported definition of target type by value of class " + targetTypeObject.getClass()); + } + } + } + /* + * there is required to replace all template model references by target type reference. + * Handle that request as template parameter too + */ + if (targetTypeRef != null) { + params.put(PatternBuilder.TARGET_TYPE, targetTypeRef); } return params; } diff --git a/src/main/java/spoon/support/template/SubstitutionVisitor.java b/src/main/java/spoon/support/template/SubstitutionVisitor.java index 20924cff0ce..5e2b832d818 100644 --- a/src/main/java/spoon/support/template/SubstitutionVisitor.java +++ b/src/main/java/spoon/support/template/SubstitutionVisitor.java @@ -75,6 +75,7 @@ class DoNotFurtherTemplateThisElement extends SpoonException { /** * This visitor implements the substitution engine of Spoon templates. */ +@Deprecated public class SubstitutionVisitor extends CtScanner { private static final Object NULL_VALUE = new Object(); diff --git a/src/main/java/spoon/template/AbstractTemplate.java b/src/main/java/spoon/template/AbstractTemplate.java index b0363ea1f59..ee7a9e2af72 100644 --- a/src/main/java/spoon/template/AbstractTemplate.java +++ b/src/main/java/spoon/template/AbstractTemplate.java @@ -22,7 +22,6 @@ import spoon.reflect.declaration.CtElement; import spoon.reflect.factory.Factory; import spoon.support.template.Parameters; -import spoon.support.template.SubstitutionVisitor; /** * handles the well-formedness and helper methods of templates @@ -61,14 +60,14 @@ public Factory getFactory() { } /** - * @return true if the template engine ({@link SubstitutionVisitor}) adds Generated by ... comments into generated code + * @return true if the template engine adds Generated by ... comments into generated code */ public boolean isAddGeneratedBy() { return addGeneratedBy; } /** - * @param addGeneratedBy if true the template engine ({@link SubstitutionVisitor}) will add Generated by ... comments into generated code + * @param addGeneratedBy if true the template engine will add Generated by ... comments into generated code */ public AbstractTemplate addGeneratedBy(boolean addGeneratedBy) { this.addGeneratedBy = addGeneratedBy; diff --git a/src/main/java/spoon/template/BlockTemplate.java b/src/main/java/spoon/template/BlockTemplate.java index e50202f760c..d622dccb9a0 100644 --- a/src/main/java/spoon/template/BlockTemplate.java +++ b/src/main/java/spoon/template/BlockTemplate.java @@ -16,6 +16,7 @@ */ package spoon.template; +import spoon.pattern.TemplateBuilder; import spoon.reflect.code.CtBlock; import spoon.reflect.declaration.CtClass; import spoon.reflect.declaration.CtType; @@ -48,7 +49,7 @@ public BlockTemplate() { public CtBlock apply(CtType targetType) { CtClass c = Substitution.getTemplateCtClass(targetType, this); - return Substitution.substitute(targetType, this, getBlock(c)); + return TemplateBuilder.createPattern(getBlock(c), this).substituteSingle(targetType, CtBlock.class); } public Void S() { diff --git a/src/main/java/spoon/template/ExpressionTemplate.java b/src/main/java/spoon/template/ExpressionTemplate.java index 478b210f85a..bd50a71a045 100644 --- a/src/main/java/spoon/template/ExpressionTemplate.java +++ b/src/main/java/spoon/template/ExpressionTemplate.java @@ -16,6 +16,7 @@ */ package spoon.template; +import spoon.pattern.TemplateBuilder; import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtExpression; import spoon.reflect.code.CtReturn; @@ -64,8 +65,11 @@ public ExpressionTemplate() { @SuppressWarnings("unchecked") public CtExpression apply(CtType targetType) { CtClass> c = Substitution.getTemplateCtClass(targetType, this); - CtBlock b = Substitution.substitute(targetType, this, getExpressionBlock(c)); - return ((CtReturn) b.getStatements().get(0)).getReturnedExpression(); + CtBlock block = TemplateBuilder.createPattern(getExpressionBlock(c), this).substituteSingle(targetType, CtBlock.class); + if (block == null || block.getStatements().isEmpty()) { + return null; + } + return ((CtReturn) block.getStatements().get(0)).getReturnedExpression(); } public T S() { diff --git a/src/main/java/spoon/template/StatementTemplate.java b/src/main/java/spoon/template/StatementTemplate.java index 624b4577ba8..2fdd05b4836 100644 --- a/src/main/java/spoon/template/StatementTemplate.java +++ b/src/main/java/spoon/template/StatementTemplate.java @@ -18,11 +18,11 @@ import java.util.List; -import spoon.SpoonException; +import spoon.pattern.TemplateBuilder; +import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtStatement; import spoon.reflect.declaration.CtClass; import spoon.reflect.declaration.CtType; -import spoon.support.template.SubstitutionVisitor; /** * This class represents a template parameter that defines a statement list @@ -45,12 +45,20 @@ public StatementTemplate() { public CtStatement apply(CtType targetType) { CtClass c = Substitution.getTemplateCtClass(targetType, this); // we substitute the first statement of method statement - CtStatement result = c.getMethod("statement").getBody().getStatements().get(0).clone(); - List statements = new SubstitutionVisitor(c.getFactory(), targetType, this).substitute(result); - if (statements.size() > 1) { - throw new SpoonException("StatementTemplate cannot return more then one statement"); + CtStatement patternModel = c.getMethod("statement").getBody().getStatements().get(0); + List statements = TemplateBuilder.createPattern(patternModel, this).substituteList(c.getFactory(), targetType, CtStatement.class); + if (statements.isEmpty()) { + return null; } - return statements.isEmpty() ? null : statements.get(0); + if (statements.size() == 1) { + return statements.get(0); + } + CtBlock block = patternModel.getFactory().createBlock(); + block.setImplicit(true); + for (CtStatement stmt : statements) { + block.addStatement(stmt); + } + return block; } public Void S() { diff --git a/src/main/java/spoon/template/Substitution.java b/src/main/java/spoon/template/Substitution.java index b60f1ad6028..65d3570bd8d 100644 --- a/src/main/java/spoon/template/Substitution.java +++ b/src/main/java/spoon/template/Substitution.java @@ -17,6 +17,8 @@ package spoon.template; import spoon.SpoonException; +import spoon.pattern.PatternBuilder; +import spoon.pattern.TemplateBuilder; import spoon.processing.FactoryAccessor; import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtExpression; @@ -29,7 +31,6 @@ import spoon.reflect.declaration.CtField; import spoon.reflect.declaration.CtInterface; import spoon.reflect.declaration.CtMethod; -import spoon.reflect.declaration.CtPackage; import spoon.reflect.declaration.CtType; import spoon.reflect.declaration.CtTypeMember; import spoon.reflect.factory.Factory; @@ -38,10 +39,8 @@ 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.HashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -106,18 +105,12 @@ public static > void insertAll(CtType targetType, T tem * @param templateParameters * the substitution parameters */ - @SuppressWarnings("unchecked") public static > T createTypeFromTemplate(String qualifiedTypeName, CtType templateOfType, Map templateParameters) { - final Factory f = templateOfType.getFactory(); - CtTypeReference typeRef = f.Type().createReference(qualifiedTypeName); - CtPackage targetPackage = f.Package().getOrCreate(typeRef.getPackage().getSimpleName()); - final Map extendedParams = new HashMap(templateParameters); - extendedParams.put(templateOfType.getSimpleName(), typeRef); - List> generated = (List) new SubstitutionVisitor(f, extendedParams).substitute(templateOfType.clone()); - for (CtType ctType : generated) { - targetPackage.addType(ctType); - } - return (T) typeRef.getTypeDeclaration(); + return PatternBuilder + .create(templateOfType) + .configureTemplateParameters(templateParameters) + .build() + .createType(templateOfType.getFactory(), qualifiedTypeName, templateParameters); } /** @@ -525,6 +518,7 @@ public static CtExpression substituteFieldDefaultExpression(CtType targetT * @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; @@ -532,12 +526,7 @@ public static E substitute(CtType targetType, Template< if (targetType == null) { throw new RuntimeException("target is null in substitution"); } - E result = (E) code.clone(); - List results = new SubstitutionVisitor(targetType.getFactory(), targetType, template).substitute(result); - if (results.size() > 1) { - throw new SpoonException("StatementTemplate cannot return more then one statement"); - } - return results.isEmpty() ? null : results.get(0); + return (E) TemplateBuilder.createPattern(code, template).substituteSingle(targetType, CtElement.class); } /** @@ -580,12 +569,13 @@ public static E substitute(CtType targetType, Template< * @return a copy of the template type where all the parameters has been * substituted */ + @SuppressWarnings("unchecked") public static > T substitute(Template template, T templateType) { - T result = (T) templateType.clone(); - result.setPositions(null); // result.setParent(templateType.getParent()); - new SubstitutionVisitor(templateType.getFactory(), result, template).substitute(result); - return result; + CtType result = TemplateBuilder.createPattern(templateType, template).substituteSingle(null, CtType.class); + //TODO check if it is still needed + result.setPositions(null); + return (T) result; } /** @@ -657,7 +647,7 @@ static CtClass getTemplateCtClass(CtType targetType, Template templ * * @return - CtClass from the already built spoon model, which represents the template */ - static CtClass getTemplateCtClass(Factory factory, Template template) { + public static CtClass getTemplateCtClass(Factory factory, Template template) { CtClass 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."); diff --git a/src/main/java/spoon/template/TemplateMatcher.java b/src/main/java/spoon/template/TemplateMatcher.java index af084239b20..ca85be103b4 100644 --- a/src/main/java/spoon/template/TemplateMatcher.java +++ b/src/main/java/spoon/template/TemplateMatcher.java @@ -16,157 +16,43 @@ */ package spoon.template; -import spoon.Launcher; -import spoon.SpoonException; -import spoon.reflect.code.CtBlock; -import spoon.reflect.code.CtFieldAccess; -import spoon.reflect.code.CtInvocation; -import spoon.reflect.code.CtStatement; -import spoon.reflect.code.CtStatementList; +import java.util.List; + +import spoon.pattern.ModelNode; +import spoon.pattern.ParameterValueProvider; +import spoon.pattern.ParameterValueProviderFactory; +import spoon.pattern.Pattern; +import spoon.pattern.TemplateBuilder; +import spoon.pattern.UnmodifiableParameterValueProvider; +import spoon.pattern.matcher.Match; +import spoon.pattern.matcher.TobeMatched; import spoon.reflect.declaration.CtClass; import spoon.reflect.declaration.CtElement; -import spoon.reflect.declaration.CtField; -import spoon.reflect.declaration.CtNamedElement; -import spoon.reflect.declaration.CtParameter; -import spoon.reflect.declaration.CtVariable; -import spoon.reflect.declaration.ParentNotInitializedException; -import spoon.reflect.reference.CtExecutableReference; -import spoon.reflect.reference.CtFieldReference; -import spoon.reflect.reference.CtPackageReference; -import spoon.reflect.reference.CtReference; -import spoon.reflect.reference.CtTypeParameterReference; -import spoon.reflect.reference.CtTypeReference; -import spoon.reflect.visitor.CtScanner; +import spoon.reflect.meta.ContainerKind; import spoon.reflect.visitor.Filter; -import spoon.reflect.visitor.Query; -import spoon.reflect.visitor.filter.InvocationFilter; -import spoon.support.template.DefaultParameterMatcher; -import spoon.support.template.ParameterMatcher; -import spoon.support.template.Parameters; -import spoon.support.util.RtHelper; +import spoon.reflect.visitor.chain.CtConsumer; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import static spoon.pattern.matcher.TobeMatched.getMatchedParameters; /** * This class defines an engine for matching a template to pieces of code. */ public class TemplateMatcher implements Filter { - /** - * Searches for all invocations of {@link TemplateParameter#S()} in "root", a CtClass model of {@link Template} - * - * @param root CtClass model of {@link Template} - */ - private List> getMethods(CtClass> root) { - CtExecutableReference methodRef = root.getFactory().Executable() - .createReference(root.getFactory().Type().createReference(TemplateParameter.class), root.getFactory().Type().createTypeParameterReference("T"), "S"); - List> meths = Query.getElements(root, new InvocationFilter(methodRef)); - - return meths; - } - - /** - * @param templateType CtClass model of {@link Template} - * @return list of all names of template parameters. - * It includes parameters typed by {@link TemplateParameter} and parameters with annotation {@link Parameter}. - */ - private List getTemplateNameParameters(CtClass> templateType) { - return Parameters.getNames(templateType); - } - - private List> getTemplateTypeParameters(final CtClass> templateType) { - - final List> ts = new ArrayList<>(); - final Collection c = Parameters.getNames(templateType); - new CtScanner() { - @Override - public void visitCtTypeParameterReference(CtTypeParameterReference reference) { - if (c.contains(reference.getSimpleName())) { - ts.add(reference); - } - } - - @Override - public void visitCtTypeReference(CtTypeReference reference) { - if (c.contains(reference.getSimpleName())) { - ts.add(reference); - } - } - - }.scan(templateType); - return ts; - } - - /** - * Looks for fields of type {@link CtStatementList} in the template and returns these fields, - * @param root CtClass model of {@link Template} - * @param variables - * @return returns for fields of type {@link CtStatementList} in the template - */ - private List> getVarargs(CtClass> root, List> variables) { - List> fields = new ArrayList<>(); - for (CtFieldReference field : root.getAllFields()) { - if (field.getType().getActualClass() == CtStatementList.class) { - boolean alreadyAdded = false; - for (CtInvocation invocation : variables) { - alreadyAdded |= ((CtFieldAccess) invocation.getTarget()).getVariable().getDeclaration().equals(field); - } - if (!alreadyAdded) { - fields.add(field); - } - } - } - return fields; - } - - /** the template itself */ - private CtElement templateRoot; - - /** - * Holds matches of template parameters (keys) to nodes from matched target - */ - private Map matches = new HashMap<>(); - - /** - * Names of all template parameters declared in `templateType` and it's super types/interfaces. - * There are - * 1) names of all fields of type {@link TemplateParameter} - * 2) value of annotation {@link Parameter#value()} applied to an parameter field - * 3) name of an field annotated with {@link Parameter} with undefined {@link Parameter#value()} - */ - private List names; - - /** - * The {@link CtClass} model of java class {@link Template}, - * which contains to be matched elements defined by `templateRoot` - */ - private CtClass> templateType; - - /** - * All the {@link CtTypeReference}s from `templateType`, whose name is a parameter name - * (is contained in `names`) - */ - private List> typeVariables; - - /** - * List of all fields of type {@link CtStatementList}, - * which are not covered by `variables` - */ - private List> varArgs; + private final Pattern pattern; + private final CtElement templateRoot; /** - * List of all invocations of {@link TemplateParameter#S()}) in scope of `templateType` + * Holds matches of template parameters name to matching values. + * The values can be: + *
  • + *
      single CtElement + *
        list or set of CtElements + *
          any value of primitive attribute, like String, Enum value, number, ... + * */ - private List> variables; + private ParameterValueProvider matches; + private ParameterValueProviderFactory parameterValueProviderFactory = UnmodifiableParameterValueProvider.Factory.INSTANCE; /** * Constructs a matcher for a given template. @@ -174,534 +60,65 @@ private List> getVarargs(CtClass> root * @param templateRoot the template to match against * */ - @SuppressWarnings("unchecked") public TemplateMatcher(CtElement templateRoot) { - this.templateType = templateRoot.getParent(CtClass.class); - this.templateRoot = templateRoot; - variables = getMethods(templateType); - typeVariables = getTemplateTypeParameters(templateType); - names = getTemplateNameParameters(templateType); - varArgs = getVarargs(templateType, variables); - //check that template matches itself - if (helperMatch(this.templateRoot, this.templateRoot) == false) { - throw new SpoonException("TemplateMatcher was unable to find itself, it certainly indicates a bug. Please revise your template or report an issue."); - } - } - - /** - * adds a target element which matches and template element - * @param template an object template. It can be: - * - CtInvocation - represents an variable - * - CtTypeReference - represents an type variable - * - String - represents a matching name in a reference - * - CtParameter - ?? - * - ...? - * @param target an matching target object - * @return false if there was already a different match to the same `template` object - */ - private boolean addMatch(Object template, Object target) { - Object inv = matches.get(template); - Object o = matches.put(template, target); - return (null == inv) || inv.equals(o); - } - - /** - * Detects whether `teList` contains a multiElement template parameter - * @param teList a list of template nodes - * @return a first found multiElement template parameter - */ - private CtElement checkListStatements(List teList) { - for (Object tem : teList) { - if (variables.contains(tem) && (tem instanceof CtInvocation)) { - CtInvocation listCand = (CtInvocation) tem; - boolean ok = listCand.getFactory().Type().createReference(TemplateParameter.class).isSubtypeOf(listCand.getTarget().getType()); - return ok ? listCand : null; - } - if (tem instanceof CtVariable) { - CtVariable var = (CtVariable) tem; - String name = var.getSimpleName(); - for (CtFieldReference f : varArgs) { - if (f.getSimpleName().equals(name)) { - return f.getDeclaration(); - } - } - } - } - - return null; - } - - /** - * Finds all target program sub-trees that correspond to a template. - * - * @param targetRoot - * the target to be tested for match - * @return the matched elements - */ - public List find(final CtElement targetRoot) { - return targetRoot.filterChildren(this).list(); - } - - /** - * - * returns an appropriate ParameterMatcher defined in a template parameter, or else a default one - * - * if a template parameter (field annotated with @Parameter) whose name (field name) is a substring of the template name, it also works - */ - private ParameterMatcher findParameterMatcher(CtNamedElement templateDeclaration) throws InstantiationException, IllegalAccessException { - if (templateDeclaration == null) { - return new DefaultParameterMatcher(); - } - String name = templateDeclaration.getSimpleName(); - CtClass clazz = null; - try { - clazz = templateDeclaration.getParent(CtClass.class); - } catch (ParentNotInitializedException e) { - Launcher.LOGGER.error(e.getMessage(), e); - } - if (clazz == null) { - return new DefaultParameterMatcher(); - } - - Collection> fields = clazz.getAllFields(); - - CtFieldReference param = null; - for (CtFieldReference fieldRef : fields) { - Parameter p = fieldRef.getDeclaration().getAnnotation(Parameter.class); - if (p == null) { - continue; // not a parameter. - } - String proxy = p.value(); - if (!"".equals(proxy)) { - if (name.contains(proxy)) { - param = fieldRef; - break; - } - } - - if (name.contains(fieldRef.getSimpleName())) { - param = fieldRef; - break; - } - // todo: check for field hack. - } - return getParameterInstance(param); - } - - @SuppressWarnings("unused") - private String getBindedParameter(String pname) { - final String[] x = new String[1]; // HACK! jeje - x[0] = pname; - new CtScanner() { - @Override - public void visitCtField(CtField f) { - Parameter p = f.getAnnotation(Parameter.class); - if ((p != null) && p.value().equals(x[0])) { - x[0] = f.getSimpleName(); - return; - } - super.visitCtField(f); - } - }.scan(templateType); - - return x[0]; + this(templateRoot, templateRoot.getParent(CtClass.class)); } /** - * Returns all the matches in a map where the keys are the corresponding - * template parameters. The {@link #match(CtElement, CtElement)} method must - * have been called before. - */ - private Map getMatches() { - return matches; - } - - /** returns a specific ParameterMatcher corresponding to the field acting as template parameter */ - private ParameterMatcher getParameterInstance(CtFieldReference param) throws InstantiationException, IllegalAccessException { - if (param == null) { - // return a default impl - return new DefaultParameterMatcher(); - } - Parameter anParam = param.getDeclaration().getAnnotation(Parameter.class); - if (anParam == null) { - // Parameter not annotated. Probably is a TemplateParameter. Just - // return a default impl - return new DefaultParameterMatcher(); - } - Class pm = anParam.match(); - ParameterMatcher instance = pm.newInstance(); - return instance; - } - - /** - * Detects whether `template` AST node and `target` AST node are matching. - * This method is called for each node of to be matched template - * and for appropriate node of `target` - * - * @param target actually checked AST node from target model - * @param template actually checked AST node from template - * - * @return true if template matches this node, false if it does not matches + * Constructs a matcher for a given template. All parameters must be declared using Template fields. * - * note: Made private to hide the Objects. + * @param templateRoot the template model to match against. It must be a child of `templateType` + * @param templateType the class of the template, which contains all the template parameters */ - private boolean helperMatch(Object target, Object template) { - if ((target == null) && (template == null)) { - return true; - } - if ((target == null) || (template == null)) { - return false; - } - if (containsSame(variables, template) || containsSame(typeVariables, template)) { - /* - * we are just matching a template parameter. - * Check that defined ParameterMatcher matches the target too - */ - boolean add = invokeCallBack(target, template); - if (add) { - //ParameterMatcher matches the target too, add that match - return addMatch(template, target); - } - return false; - } - if (target.getClass() != template.getClass()) { - return false; - } - if ((template instanceof CtTypeReference) && template.equals(templateType.getReference())) { - return true; - } - if ((template instanceof CtPackageReference) && template.equals(templateType.getPackage())) { - return true; - } - if (template instanceof CtReference) { - CtReference tRef = (CtReference) template; - /* - * Check whether name of a template reference matches with name of target reference - * after replacing of variables in template name - */ - boolean ok = matchNames(tRef.getSimpleName(), ((CtReference) target).getSimpleName()); - if (ok && !template.equals(target)) { - boolean remove = !invokeCallBack(target, template); - if (remove) { - matches.remove(tRef.getSimpleName()); - return false; - } - return true; - } - } - - if (template instanceof CtNamedElement) { - CtNamedElement named = (CtNamedElement) template; - boolean ok = matchNames(named.getSimpleName(), ((CtNamedElement) target).getSimpleName()); - if (ok && !template.equals(target)) { - boolean remove = !invokeCallBack(target, template); - if (remove) { - matches.remove(named.getSimpleName()); - return false; - } - } - } - - if (template instanceof Collection) { - return matchCollections((Collection) target, (Collection) template); - } - - if (template instanceof Map) { - if (template.equals(target)) { - return true; - } - - Map temMap = (Map) template; - Map tarMap = (Map) target; - - if (!temMap.keySet().equals(tarMap.keySet())) { - return false; - } - - return matchCollections(tarMap.values(), temMap.values()); - } - - if (template instanceof CtBlock) { - final List statements = ((CtBlock) template).getStatements(); - if (statements.size() == 1 && statements.get(0) instanceof CtInvocation) { - final CtInvocation ctStatement = (CtInvocation) statements.get(0); - if ("S".equals(ctStatement.getExecutable().getSimpleName()) && CtBlock.class.equals(ctStatement.getType().getActualClass())) { - return true; - } - } - } - - if (target instanceof CtElement) { - for (Field f : RtHelper.getAllFields(target.getClass())) { - f.setAccessible(true); - if (Modifier.isStatic(f.getModifiers())) { - continue; - } - if (f.getName().equals("parent")) { - continue; - } - if (f.getName().equals("position")) { - continue; - } - if (f.getName().equals("docComment")) { - continue; - } - if (f.getName().equals("factory")) { - continue; - } - if (f.getName().equals("comments")) { - continue; - } - if (f.getName().equals("metadata")) { - continue; - } - try { - if (!helperMatch(f.get(target), f.get(template))) { - return false; - } - } catch (IllegalAccessException ignore) { - } - } - return true; - } else if (target instanceof String) { - return matchNames((String) template, (String) target); - } else { - return target.equals(template); - } - } - - /** - * invokes {@link ParameterMatcher} associated to the `template` (= template parameter) - * @param target a potentially matching element - * @param template a matching parameter, which may define extra {@link ParameterMatcher} - * @return true if {@link ParameterMatcher} of `template` matches on `target` - */ - private boolean invokeCallBack(Object target, Object template) { - try { - if (template instanceof CtInvocation) { - CtFieldAccess param = (CtFieldAccess) ((CtInvocation) template).getTarget(); - ParameterMatcher instance = getParameterInstance(param.getVariable()); - return instance.match(this, (CtInvocation) template, (CtElement) target); - } else if (template instanceof CtReference) { - // Get parameter - CtReference ref = (CtReference) template; - ParameterMatcher instance; - if (ref.getDeclaration() == null || ref.getDeclaration().getAnnotation(Parameter.class) == null) { - instance = new DefaultParameterMatcher(); - } else { - Parameter param = ref.getDeclaration().getAnnotation(Parameter.class); - instance = param.match().newInstance(); - } - return instance.match(this, (CtReference) template, (CtReference) target); - } else if (template instanceof CtNamedElement) { - CtNamedElement named = (CtNamedElement) template; - ParameterMatcher instance = findParameterMatcher(named); - return instance.match(this, (CtElement) template, (CtElement) target); - } else { - // Should not happen - throw new RuntimeException(); - } - } catch (InstantiationException e) { - Launcher.LOGGER.error(e.getMessage(), e); - return true; - } catch (IllegalAccessException e) { - Launcher.LOGGER.error(e.getMessage(), e); - return true; - } - } - - /** - * Detects whether `object` represent a template variable `inMulti` - */ - private boolean isCurrentTemplate(Object object, CtElement inMulti) { - if (object instanceof CtInvocation) { - return object.equals(inMulti); - } - if (object instanceof CtParameter) { - CtParameter param = (CtParameter) object; - for (CtFieldReference varArg : varArgs) { - if (param.getSimpleName().equals(varArg.getSimpleName())) { - return varArg.equals(inMulti); - } - } - } - return false; + public TemplateMatcher(CtElement templateRoot, CtClass templateType) { + this.pattern = TemplateBuilder.createPattern(templateRoot, templateType, null).build(); + this.templateRoot = templateRoot; } - /** - * Matches a target program sub-tree against a template. - * - * @param targetRoot - * the target to be tested for match - * @return true if matches - */ @Override - public boolean matches(CtElement targetRoot) { - if (targetRoot == templateRoot) { + public boolean matches(CtElement element) { + //clear all matches from previous run before we start matching with `element` + ModelNode patternModel = pattern.getModelValueResolver(); + if (element == templateRoot) { // This case can occur when we are scanning the entire package for example see TemplateTest#testTemplateMatcherWithWholePackage // Correct template matches itself of course, but client does not want that return false; } - return helperMatch(targetRoot, templateRoot); - } - - @SuppressWarnings("unchecked") - private boolean matchCollections(Collection target, Collection template) { - final List teList = new ArrayList<>(template); - final List taList = new ArrayList<>(target); - - // inMulti keeps the multiElement templateVariable we are at - CtElement inMulti = nextListStatement(teList, null); - - // multi keeps the values to assign to inMulti - List multi = new ArrayList<>(); - - if (null == inMulti) { - // If we are not looking at template with multiElements - // the sizes should then be the same - if (teList.size() != taList.size()) { - return false; - } - - for (int te = 0, ta = 0; (te < teList.size()) && (ta < taList.size()); te++, ta++) { - if (!helperMatch(taList.get(ta), teList.get(te))) { - return false; - } - } - return true; - } - for (int te = 0, ta = 0; (te < teList.size()) && (ta < taList.size()); te++, ta++) { - - if (isCurrentTemplate(teList.get(te), inMulti)) { - //te index points to template parameter, which accepts multiple statements - if (te + 1 >= teList.size()) { - //it is the last parameter of template list. Add all remaining target list items - multi.addAll(taList.subList(te, taList.size())); - //create statement list and add match - CtStatementList tpl = templateType.getFactory().Core().createStatementList(); - tpl.setStatements((List) (List) multi); - if (!invokeCallBack(tpl, inMulti)) { - return false; - } - boolean ret = addMatch(inMulti, multi); - return ret; - } - //there is next template parameter. Move to it - te++; - //adds all target list items, which are not matching to next template parameter, to the actual template parameter - while ((te < teList.size()) && (ta < taList.size()) && !helperMatch(taList.get(ta), teList.get(te))) { - multi.add(taList.get(ta)); - ta++; - } - //we have found first target parameter, which fits to next template parameter - //create statement list for previous parameter and add it's match - CtStatementList tpl = templateType.getFactory().Core().createStatementList(); - tpl.setStatements((List) (List) multi); - if (!invokeCallBack(tpl, inMulti)) { - return false; - } - addMatch(inMulti, tpl); - // update inMulti - inMulti = nextListStatement(teList, inMulti); - multi = new ArrayList<>(); - } else { - //parameter on te index is not a multivalue statement - if (!helperMatch(taList.get(ta), teList.get(te))) { - return false; - } - if (!(ta + 1 < taList.size()) && (inMulti != null)) { - /* - * there is no next target item in taList, - * but there is still some template parameter, - * which expects one - */ - CtStatementList tpl = templateType.getFactory().Core().createStatementList(); - for (Object o : multi) { - tpl.addStatement((CtStatement) o); - } - //so it returns empty statement list - if (!invokeCallBack(tpl, inMulti)) { - return false; - } - addMatch(inMulti, tpl); - // update inMulti - inMulti = nextListStatement(teList, inMulti); - multi = new ArrayList<>(); - } - } - } - return true; + matches = getMatchedParameters(patternModel.matchAllWith(TobeMatched.create( + parameterValueProviderFactory.createParameterValueProvider(), + ContainerKind.SINGLE, + element))); + return matches != null; } /** - * Detects if `templateName` (a name from template) matches with `elementName` (a name from target), - * after replacing parameter names in `templateName` - * @param templateName the name from template - * @param elementName the name from target - * @return true if matching + * Returns all the matches where the keys are the corresponding + * template parameters. + * The {@link #matches(CtElement)} method must have been called before and must return true. + * Otherwise it returns null. */ - private boolean matchNames(String templateName, String elementName) { - - for (String templateParameterName : names) { - // pname = pname.replace("_FIELD_", ""); - if (templateName.contains(templateParameterName)) { - String newName = templateName.replace(templateParameterName, "(.*)"); - Pattern p = Pattern.compile(newName); - Matcher m = p.matcher(elementName); - if (!m.matches()) { - return false; - } - // TODO: fix with parameter from @Parameter - // boolean ok = addMatch(getBindedParameter(pname), - // m.group(1)); - boolean ok = addMatch(templateParameterName, m.group(1)); - if (!ok) { - Launcher.LOGGER.debug("incongruent match"); - return false; - } - return true; - } - } - return templateName.equals(elementName); + public ParameterValueProvider getMatches() { + return matches; } /** - * returns next ListStatement parameter from teList + * Finds all target program sub-trees that correspond to a template. + * + * @param targetRoot + * the target to be tested for match + * @return the matched elements */ - private CtElement nextListStatement(List teList, CtElement inMulti) { - if (inMulti == null) { - return checkListStatements(teList); - } - List teList2 = new ArrayList(teList); - if (inMulti instanceof CtInvocation) { - teList2.remove(inMulti); - } else if (inMulti instanceof CtVariable) { - CtVariable var = (CtVariable) inMulti; - for (Iterator iter = teList2.iterator(); iter.hasNext();) { - CtVariable teVar = (CtVariable) iter.next(); - if (teVar.getSimpleName().equals(var.getSimpleName())) { - iter.remove(); - } - } - } - return checkListStatements(teList2); + public List find(final CtElement targetRoot) { + return targetRoot.filterChildren(this).list(); } /** - * Is used instead of Collection#contains(Object), - * which uses Object#equals operator, - * which returns true even for not same objects. - * - * @param collection to be checked collection - * @param item to be searched object - * @return true if `collection` contains instance of `item`. + * Finds all target program sub-trees that correspond to a template + * and calls consumer.accept(matchingElement, ) + * @param rootElement the root of to be searched AST + * @param consumer the receiver of matches */ - private static boolean containsSame(Iterable collection, Object item) { - for (Object object : collection) { - if (object == item) { - return true; - } - } - return false; + public void forEachMatch(CtElement rootElement, CtConsumer consumer) { + rootElement.map(pattern).forEach(consumer); } } diff --git a/src/test/java/spoon/MavenLauncherTest.java b/src/test/java/spoon/MavenLauncherTest.java index e9cd1c0adda..0fb589195c8 100644 --- a/src/test/java/spoon/MavenLauncherTest.java +++ b/src/test/java/spoon/MavenLauncherTest.java @@ -18,7 +18,7 @@ public void spoonMavenLauncherTest() { assertEquals(7, launcher.getEnvironment().getSourceClasspath().length); // 52 because of the sub folders of src/main/java - assertEquals(52, launcher.getModelBuilder().getInputSources().size()); + assertEquals(54, launcher.getModelBuilder().getInputSources().size()); // with the tests launcher = new MavenLauncher("./", MavenLauncher.SOURCE_TYPE.ALL_SOURCE); diff --git a/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java b/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java index 267202ce423..fabf508ab7c 100644 --- a/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java +++ b/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java @@ -310,6 +310,8 @@ public void testSpecPackage() throws Exception { officialPackages.add("spoon.experimental.modelobs"); officialPackages.add("spoon.experimental"); officialPackages.add("spoon.legacy"); + officialPackages.add("spoon.pattern"); + officialPackages.add("spoon.pattern.matcher"); officialPackages.add("spoon.processing"); officialPackages.add("spoon.refactoring"); officialPackages.add("spoon.reflect.annotations"); diff --git a/src/test/java/spoon/test/template/CodeReplaceTest.java b/src/test/java/spoon/test/template/CodeReplaceTest.java new file mode 100644 index 00000000000..5e8ee7e5bb8 --- /dev/null +++ b/src/test/java/spoon/test/template/CodeReplaceTest.java @@ -0,0 +1,83 @@ +package spoon.test.template; + +import org.junit.Test; + +import spoon.Launcher; +import spoon.OutputType; +import spoon.SpoonModelBuilder; +import spoon.pattern.ParameterValueProvider; +import spoon.pattern.Pattern; +import spoon.reflect.declaration.CtClass; +import spoon.reflect.factory.Factory; +import spoon.reflect.visitor.DefaultJavaPrettyPrinter; +import spoon.test.template.testclasses.replace.DPPSample1; +import spoon.test.template.testclasses.replace.NewPattern; +import spoon.test.template.testclasses.replace.OldPattern; +import spoon.testing.utils.ModelUtils; + +import static org.junit.Assert.*; + +import java.io.File; + +public class CodeReplaceTest { + + @Test + public void testMatchSample1() throws Exception { + Factory f = ModelUtils.build( + new File("./src/test/java/spoon/test/template/testclasses/replace/DPPSample1.java"), + new File("./src/test/java/spoon/test/template/testclasses/replace") + ); + CtClass classDJPP = f.Class().get(DPPSample1.class); + assertNotNull(classDJPP); + assertFalse(classDJPP.isShadow()); + Pattern p = OldPattern.createPattern(f); + class Context { + int count = 0; + } + Context context = new Context(); + p.forEachMatch(classDJPP, (match) -> { + ParameterValueProvider params = match.getParameters(); + if (context.count == 0) { + assertEquals("\"extends\"", params.get("startKeyword").toString()); + assertEquals(Boolean.TRUE, params.get("useStartKeyword")); + } else { + assertEquals(null, params.get("startKeyword")); + assertEquals(Boolean.FALSE, params.get("useStartKeyword")); + } + assertEquals("false", params.get("startPrefixSpace").toString()); + assertEquals("null", params.get("start").toString()); + assertEquals("false", params.get("startSuffixSpace").toString()); + assertEquals("false", params.get("nextPrefixSpace").toString()); + assertEquals("\",\"", params.get("next").toString()); + assertEquals("true", params.get("nextSuffixSpace").toString()); + assertEquals("false", params.get("endPrefixSpace").toString()); + assertEquals("\";\"", params.get("end").toString()); + assertEquals("ctEnum.getEnumValues()", params.get("getIterable").toString()); + assertEquals("[scan(enumValue)]", params.get("statements").toString()); + context.count++; + }); + assertEquals(2, context.count); + } + + @Test + public void testTemplateReplace() throws Exception { + Launcher launcher = new Launcher(); + final Factory factory = launcher.getFactory(); + factory.getEnvironment().setComplianceLevel(8); + factory.getEnvironment().setNoClasspath(true); + factory.getEnvironment().setCommentEnabled(true); + factory.getEnvironment().setAutoImports(true); + final SpoonModelBuilder compiler = launcher.createCompiler(factory); + compiler.addInputSource(new File("./src/main/java/spoon/reflect/visitor")); + compiler.addInputSource(new File("./src/test/java/spoon/test/template/testclasses/replace")); + compiler.build(); + CtClass classDJPP = factory.Class().get(DefaultJavaPrettyPrinter.class); + assertNotNull(classDJPP); + assertFalse(classDJPP.isShadow()); + NewPattern.replaceOldByNew(classDJPP); + + launcher.setSourceOutputDirectory(new File("./target/spooned-template-replace/")); + launcher.getModelBuilder().generateProcessedSourceFiles(OutputType.CLASSES); + } + +} diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java new file mode 100644 index 00000000000..2412c3d4865 --- /dev/null +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -0,0 +1,59 @@ +package spoon.test.template; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; + +import org.junit.Ignore; +import org.junit.Test; + +import spoon.pattern.ParameterInfo; +import spoon.pattern.Pattern; +import spoon.reflect.factory.Factory; +import spoon.test.template.testclasses.replace.OldPattern; +import spoon.testing.utils.ModelUtils; + +public class PatternTest { + + @Test + public void testPatternParameters() { + //contract: all the parameters of Pattern are available + Factory f = ModelUtils.build( + new File("./src/test/java/spoon/test/template/testclasses/replace/DPPSample1.java"), + new File("./src/test/java/spoon/test/template/testclasses/replace") + ); + Pattern p = OldPattern.createPattern(f); + Map parameterInfos = p.getParameterInfos(); + + assertEquals(15, parameterInfos.size()); + assertEquals(new HashSet<>(Arrays.asList("next","item","startPrefixSpace","printer","start", + "statements","nextPrefixSpace","startSuffixSpace","elementPrinterHelper", + "endPrefixSpace","startKeyword","useStartKeyword","end","nextSuffixSpace","getIterable" + )), parameterInfos.keySet()); + for (Map.Entry e : parameterInfos.entrySet()) { + assertEquals(e.getKey(), e.getValue().getName()); + } + } + + @Test + @Ignore + public void testPatternToString() { + //contract: Pattern can be printed to String and each parameter is defined there + Factory f = ModelUtils.build( + new File("./src/test/java/spoon/test/template/testclasses/replace/DPPSample1.java"), + new File("./src/test/java/spoon/test/template/testclasses/replace") + ); + Pattern p = OldPattern.createPattern(f); + String strOfPattern = p.toString(); + + Map parameterInfos = p.getParameterInfos(); + assertEquals(15, parameterInfos.size()); + for (Map.Entry e : parameterInfos.entrySet()) { + assertTrue("The parameter " + e.getKey() + " is missing", strOfPattern.indexOf("<= ${"+e.getKey()+"}")>=0); + } + } +} diff --git a/src/test/java/spoon/test/template/TemplateMatcherTest.java b/src/test/java/spoon/test/template/TemplateMatcherTest.java new file mode 100644 index 00000000000..8a37e8b090f --- /dev/null +++ b/src/test/java/spoon/test/template/TemplateMatcherTest.java @@ -0,0 +1,835 @@ +package spoon.test.template; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.junit.Ignore; +import org.junit.Test; + +import spoon.pattern.ParameterValueProvider; +import spoon.pattern.Pattern; +import spoon.pattern.matcher.Match; +import spoon.pattern.matcher.Quantifier; +import spoon.reflect.code.CtInvocation; +import spoon.reflect.code.CtLiteral; +import spoon.reflect.code.CtStatement; +import spoon.reflect.code.CtVariableRead; +import spoon.reflect.declaration.CtElement; +import spoon.reflect.declaration.CtMethod; +import spoon.reflect.declaration.CtType; +import spoon.reflect.declaration.ModifierKind; +import spoon.reflect.meta.ContainerKind; +import spoon.reflect.path.CtRole; +import spoon.test.template.testclasses.match.MatchForEach; +import spoon.test.template.testclasses.match.MatchForEach2; +import spoon.test.template.testclasses.match.MatchIfElse; +import spoon.test.template.testclasses.match.MatchMap; +import spoon.test.template.testclasses.match.MatchModifiers; +import spoon.test.template.testclasses.match.MatchMultiple; +import spoon.test.template.testclasses.match.MatchMultiple2; +import spoon.test.template.testclasses.match.MatchMultiple3; +import spoon.test.template.testclasses.match.MatchWithParameterCondition; +import spoon.test.template.testclasses.match.MatchWithParameterType; +import spoon.testing.utils.ModelUtils; + +public class TemplateMatcherTest { + + @Test + public void testMatchForeach() throws Exception { + //contract: live foreach template can match multiple models into list of parameter values + CtType ctClass = ModelUtils.buildClass(MatchForEach.class); + + Pattern pattern = MatchForEach.createPattern(ctClass.getFactory()); + + List matches = pattern.getMatches(ctClass); + + assertEquals(2, matches.size()); + { + Match match = matches.get(0); + assertEquals(Arrays.asList("java.lang.System.out.println(value)"), listToListOfStrings(match.getMatchingElements())); + //FIX IT +// assertEquals(Arrays.asList(""), listToListOfStrings((List) match.getParameters().get("values"))); + } + { + Match match = matches.get(1); + assertEquals(Arrays.asList( + "java.lang.System.out.println(\"a\")", + "java.lang.System.out.println(\"Xxxx\")", + "java.lang.System.out.println(((java.lang.String) (null)))", + "java.lang.System.out.println(java.lang.Long.class.toString())"), listToListOfStrings(match.getMatchingElements())); + assertEquals(Arrays.asList( + "\"a\"", + "\"Xxxx\"", + "((java.lang.String) (null))", + "java.lang.Long.class.toString()"), listToListOfStrings((List) match.getParameters().get("values"))); + } + } + + @Test + public void testMatchForeachWithOuterSubstitution() throws Exception { + //contract: live foreach template can match multiple models into list of parameter values including outer parameters + CtType ctClass = ModelUtils.buildClass(MatchForEach2.class); + + Pattern pattern = MatchForEach2.createPattern(ctClass.getFactory()); + + List matches = pattern.getMatches(ctClass); + + assertEquals(3, matches.size()); + { + Match match = matches.get(0); + assertEquals(Arrays.asList("int var = 0"), listToListOfStrings(match.getMatchingElements())); + //FIX IT +// assertEquals(Arrays.asList(""), listToListOfStrings((List) match.getParameters().get("values"))); + } + { + Match match = matches.get(1); + assertEquals(Arrays.asList( + "int cc = 0", + "java.lang.System.out.println(\"Xxxx\")", + "cc++", + "java.lang.System.out.println(((java.lang.String) (null)))", + "cc++"), listToListOfStrings(match.getMatchingElements())); + assertEquals(Arrays.asList( + "\"Xxxx\"", + "((java.lang.String) (null))"), listToListOfStrings((List) match.getParameters().get("values"))); + } + { + Match match = matches.get(2); + assertEquals(Arrays.asList( + "int dd = 0", + "java.lang.System.out.println(java.lang.Long.class.toString())", + "dd++"), listToListOfStrings(match.getMatchingElements())); + assertEquals(Arrays.asList( + "java.lang.Long.class.toString()"), listToListOfStrings((List) match.getParameters().get("values"))); + } + } + + @Test + public void testMatchIfElse() throws Exception { + //contract: live switch Pattern can match one of the models + CtType ctClass = ModelUtils.buildClass(MatchIfElse.class); + + Pattern pattern = MatchIfElse.createPattern(ctClass.getFactory()); + + List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0)); + + assertEquals(7, matches.size()); + { + Match match = matches.get(0); + assertEquals(Arrays.asList("java.lang.System.out.println(i)"), listToListOfStrings(match.getMatchingElements())); + assertEquals(false, match.getParameters().get("option")); + assertEquals(true, match.getParameters().get("option2")); + assertEquals("i", match.getParameters().get("value").toString()); + } + { + Match match = matches.get(1); + assertEquals(Arrays.asList("java.lang.System.out.println(\"a\")"), listToListOfStrings(match.getMatchingElements())); + assertEquals(true, match.getParameters().get("option")); + assertEquals(false, match.getParameters().get("option2")); + assertEquals("\"a\"", match.getParameters().get("value").toString()); + } + { + Match match = matches.get(2); + assertEquals(Arrays.asList("java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings(match.getMatchingElements())); + assertEquals(true, match.getParameters().get("option")); + assertEquals(false, match.getParameters().get("option2")); + assertEquals("\"Xxxx\"", match.getParameters().get("value").toString()); + } + { + Match match = matches.get(3); + assertEquals(Arrays.asList("java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings(match.getMatchingElements())); + assertEquals(true, match.getParameters().get("option")); + assertEquals(false, match.getParameters().get("option2")); + assertEquals("((java.lang.String) (null))", match.getParameters().get("value").toString()); + } + { + Match match = matches.get(4); + assertEquals(Arrays.asList("java.lang.System.out.println(2018)"), listToListOfStrings(match.getMatchingElements())); + assertEquals(false, match.getParameters().get("option")); + assertEquals(true, match.getParameters().get("option2")); + assertEquals("2018", match.getParameters().get("value").toString()); + } + { + Match match = matches.get(5); + assertEquals(Arrays.asList("java.lang.System.out.println(java.lang.Long.class.toString())"), listToListOfStrings(match.getMatchingElements())); + assertEquals(true, match.getParameters().get("option")); + assertEquals(false, match.getParameters().get("option2")); + assertEquals("java.lang.Long.class.toString()", match.getParameters().get("value").toString()); + } + { + Match match = matches.get(6); + assertEquals(Arrays.asList("java.lang.System.out.println(3.14)"), listToListOfStrings(match.getMatchingElements())); + assertEquals(false, match.getParameters().get("option")); + assertEquals(false, match.getParameters().get("option2")); + assertEquals("3.14", match.getParameters().get("value").toString()); + } + } + @Test + public void testGenerateMultiValues() throws Exception { + CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); + Pattern pattern = MatchMultiple.createPattern(ctClass.getFactory(), null, null, null); + Map params = new HashMap<>(); + params.put("printedValue", "does it work?"); + params.put("statements", ctClass.getMethodsByName("testMatch1").get(0).getBody().getStatements().subList(0, 3)); + List generated = pattern.substituteList(ctClass.getFactory(), CtStatement.class, params); + assertEquals(Arrays.asList( + "int i = 0", + "i++", + "java.lang.System.out.println(i)", + "java.lang.System.out.println(\"does it work?\")"), generated.stream().map(Object::toString).collect(Collectors.toList())); + } + @Test + public void testMatchGreedyMultiValueUnlimited() throws Exception { + //contract: multivalue parameter can match multiple nodes into list of parameter values. + //contract: default greedy matching eats everything but can leave some matches if it is needed to match remaining template parameters + CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); + Pattern pattern = MatchMultiple.createPattern(ctClass.getFactory(), null, null, null); + + List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0)); + + assertEquals(1, matches.size()); + Match match = matches.get(0); + //check all statements are matched + assertEquals(Arrays.asList( + "int i = 0", + "i++", + "java.lang.System.out.println(i)", + "java.lang.System.out.println(\"Xxxx\")", + "java.lang.System.out.println(((java.lang.String) (null)))", + "java.lang.System.out.println(\"last one\")"), listToListOfStrings(match.getMatchingElements())); + + //check all statements excluding last are stored as value of "statements" parameter + assertEquals(Arrays.asList( + "int i = 0", + "i++", + "java.lang.System.out.println(i)", + "java.lang.System.out.println(\"Xxxx\")", + "java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings((List) match.getParameters().get("statements"))); + //last statement is matched by last template, which saves printed value + assertTrue(match.getParameters().get("printedValue") instanceof CtLiteral); + assertEquals("\"last one\"", match.getParameters().get("printedValue").toString()); + } + + @Test + public void testMatchGreedyMultiValueMaxCountLimit() throws Exception { + //contract: default greedy matching eats everything until max count = 3 + CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); + Pattern pattern = MatchMultiple.createPattern(ctClass.getFactory(), null, null, 3); + + List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0)); + + assertEquals(2, matches.size()); + { + Match match = matches.get(0); + //check 3 + 1 statements are matched + assertEquals(Arrays.asList( + "int i = 0", + "i++", + "java.lang.System.out.println(i)", + "java.lang.System.out.println(\"Xxxx\")" + ), listToListOfStrings(match.getMatchingElements())); + + //check 3 statements are stored as value of "statements" parameter + assertEquals(Arrays.asList( + "int i = 0", + "i++", + "java.lang.System.out.println(i)"), listToListOfStrings((List) match.getParameters().get("statements"))); + //4th statement is matched by last template, which saves printed value + assertTrue(match.getParameters().get("printedValue") instanceof CtLiteral); + assertEquals("\"Xxxx\"", match.getParameters().get("printedValue").toString()); + } + { + Match match = matches.get(1); + //check remaining next 2 statements are matched + assertEquals(Arrays.asList( + "java.lang.System.out.println(((java.lang.String) (null)))", + "java.lang.System.out.println(\"last one\")"), listToListOfStrings(match.getMatchingElements())); + + //check all statements excluding last are stored as value of "statements" parameter + assertEquals(Arrays.asList( + "java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings((List) match.getParameters().get("statements"))); + //last statement is matched by last template, which saves printed value + assertTrue(match.getParameters().get("printedValue") instanceof CtLiteral); + assertEquals("\"last one\"", match.getParameters().get("printedValue").toString()); + } + } + + + @Test + public void testMatchReluctantMultivalue() throws Exception { + //contract: multivalue parameter can match multiple nodes into list of parameter values. + //contract: reluctant matches only minimal amount + CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); + + Pattern pattern = MatchMultiple.createPattern(ctClass.getFactory(), Quantifier.RELUCTANT, null, null); + + List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0)); + + assertEquals(3, matches.size()); + { + Match match = matches.get(0); + //check all statements are matched + assertEquals(Arrays.asList( + "int i = 0", + "i++", + "java.lang.System.out.println(i)", //this is println(int), but last temple matches println(String) - it is question if it is wanted or not ... + "java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings(match.getMatchingElements())); + + //check all statements excluding last are stored as value of "statements" parameter + assertEquals(Arrays.asList( + "int i = 0", + "i++", + "java.lang.System.out.println(i)"), listToListOfStrings((List) match.getParameters().get("statements"))); + //last statement is matched by last template, which saves printed value + assertTrue(match.getParameters().get("printedValue") instanceof CtLiteral); + assertEquals("\"Xxxx\"", match.getParameters().get("printedValue").toString()); + } + { + Match match = matches.get(1); + //check all statements are matched + assertEquals(Arrays.asList( + "java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings(match.getMatchingElements())); + + //check all statements excluding last are stored as value of "statements" parameter + assertEquals(Arrays.asList(), listToListOfStrings((List) match.getParameters().get("statements"))); + //last statement is matched by last template, which saves printed value + assertTrue(match.getParameters().get("printedValue") instanceof CtLiteral); + assertEquals("((java.lang.String) (null))", match.getParameters().get("printedValue").toString()); + } + { + Match match = matches.get(2); + //check all statements are matched + assertEquals(Arrays.asList( + "java.lang.System.out.println(\"last one\")"), listToListOfStrings(match.getMatchingElements())); + + //check all statements excluding last are stored as value of "statements" parameter + assertEquals(Arrays.asList(), listToListOfStrings((List) match.getParameters().get("statements"))); + //last statement is matched by last template, which saves printed value + assertTrue(match.getParameters().get("printedValue") instanceof CtLiteral); + assertEquals("\"last one\"", match.getParameters().get("printedValue").toString()); + } + } + @Test + public void testMatchReluctantMultivalueMinCount1() throws Exception { + //contract: multivalue parameter can match multiple nodes into list of parameter values. + //contract: reluctant matches only at least 1 node in this case + CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); + + Pattern pattern = MatchMultiple.createPattern(ctClass.getFactory(), Quantifier.RELUCTANT, 1, null); + + List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0)); + + assertEquals(2, matches.size()); + { + Match match = matches.get(0); + //check all statements are matched + assertEquals(Arrays.asList( + "int i = 0", + "i++", + "java.lang.System.out.println(i)", //this is println(int), but last temple matches println(String) - it is question if it is wanted or not ... + "java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings(match.getMatchingElements())); + + //check all statements excluding last are stored as value of "statements" parameter + assertEquals(Arrays.asList( + "int i = 0", + "i++", + "java.lang.System.out.println(i)"), listToListOfStrings((List) match.getParameters().get("statements"))); + //last statement is matched by last template, which saves printed value + assertTrue(match.getParameters().get("printedValue") instanceof CtLiteral); + assertEquals("\"Xxxx\"", match.getParameters().get("printedValue").toString()); + } + { + Match match = matches.get(1); + //check all statements are matched + assertEquals(Arrays.asList( + "java.lang.System.out.println(((java.lang.String) (null)))", + "java.lang.System.out.println(\"last one\")"), listToListOfStrings(match.getMatchingElements())); + + //check all statements excluding last are stored as value of "statements" parameter + assertEquals(Arrays.asList( + "java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings((List) match.getParameters().get("statements"))); + //last statement is matched by last template, which saves printed value + assertTrue(match.getParameters().get("printedValue") instanceof CtLiteral); + assertEquals("\"last one\"", match.getParameters().get("printedValue").toString()); + } + } + @Test + public void testMatchReluctantMultivalueExactly2() throws Exception { + //contract: multivalue parameter can match multiple nodes into list of parameter values. + //contract: reluctant matches min 2 and max 2 nodes in this case + CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); + + Pattern pattern = MatchMultiple.createPattern(ctClass.getFactory(), Quantifier.RELUCTANT, 2, 2); + + List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0)); + + assertEquals(1, matches.size()); + { + Match match = matches.get(0); + //check only 2 statements are matched + next one + assertEquals(Arrays.asList( + "i++", + "java.lang.System.out.println(i)", //this is println(int), but last temple matches println(String) - it is question if it is wanted or not ... + "java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings(match.getMatchingElements())); + + //check 2 statements excluding last are stored as value of "statements" parameter + assertEquals(Arrays.asList( + "i++", + "java.lang.System.out.println(i)"), listToListOfStrings((List) match.getParameters().get("statements"))); + //last statement is matched by last template, which saves printed value + assertTrue(match.getParameters().get("printedValue") instanceof CtLiteral); + assertEquals("\"Xxxx\"", match.getParameters().get("printedValue").toString()); + } + } + + @Test + public void testMatchPossesiveMultiValueUnlimited() throws Exception { + //contract: multivalue parameter can match multiple nodes into list of parameter values. + //contract: possessive matching eats everything and never returns back + CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); + Pattern pattern = MatchMultiple.createPattern(ctClass.getFactory(), Quantifier.POSSESSIVE, null, null); + + List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0).getBody()); + //the last template has nothing to match -> no match + assertEquals(0, matches.size()); + } + @Test + public void testMatchPossesiveMultiValueMaxCount4() throws Exception { + //contract: multivalue parameter can match multiple nodes into list of parameter values. + //contract: possessive matching eats everything and never returns back + CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); + Pattern pattern = MatchMultiple.createPattern(ctClass.getFactory(), Quantifier.POSSESSIVE, null, 4); + + List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0)); + + assertEquals(1, matches.size()); + Match match = matches.get(0); + //check 4 statements are matched + last template + assertEquals(Arrays.asList( + "int i = 0", + "i++", + "java.lang.System.out.println(i)", + "java.lang.System.out.println(\"Xxxx\")", + "java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings(match.getMatchingElements())); + + //check 4 statements excluding last are stored as value of "statements" parameter + assertEquals(Arrays.asList( + "int i = 0", + "i++", + "java.lang.System.out.println(i)", + "java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings((List) match.getParameters().get("statements"))); + //last statement is matched by last template, which saves printed value + assertTrue(match.getParameters().get("printedValue") instanceof CtLiteral); + assertEquals("((java.lang.String) (null))", match.getParameters().get("printedValue").toString()); + } + @Test + public void testMatchPossesiveMultiValueMinCount() throws Exception { + //contract: check possessive matching with min count limit and GREEDY back off + CtType ctClass = ModelUtils.buildClass(MatchMultiple3.class); + for (int i = 0; i < 7; i++) { + final int count = i; + Pattern pattern = MatchMultiple3.createPattern(ctClass.getFactory(), pb -> { + pb.parameter("statements1").setMatchingStrategy(Quantifier.GREEDY); + pb.parameter("statements2").setMatchingStrategy(Quantifier.POSSESSIVE).setMinOccurence(count).setMaxOccurence(count); + }); + + List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0).getBody()); + if (count < 6) { + //the last template has nothing to match -> no match + assertEquals("count="+count, 1, matches.size()); + assertEquals("count="+count, 5-count, getSize(matches.get(0).getParameters().get("statements1"))); + assertEquals("count="+count, count, getSize(matches.get(0).getParameters().get("statements2"))); + } else { + //the possessive matcher eat too much. There is no target element for last `printedValue` variable + assertEquals("count="+count, 0, matches.size()); + } + } + } + + @Test + public void testMatchPossesiveMultiValueMinCount2() throws Exception { + //contract: check possessive matching with min count limit and GREEDY back off + CtType ctClass = ModelUtils.buildClass(MatchMultiple2.class); + for (int i = 0; i < 7; i++) { + final int count = i; + Pattern pattern = MatchMultiple2.createPattern(ctClass.getFactory(), pb -> { + pb.parameter("statements1").setMatchingStrategy(Quantifier.GREEDY); + pb.parameter("statements2").setMatchingStrategy(Quantifier.POSSESSIVE).setMinOccurence(count).setMaxOccurence(count); + pb.parameter("printedValue").setMatchingStrategy(Quantifier.POSSESSIVE).setContainerKind(ContainerKind.LIST).setMinOccurence(2); + }); + + List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0).getBody()); + if (count < 5) { + //the last template has nothing to match -> no match + assertEquals("count="+count, 1, matches.size()); + assertEquals("count="+count, 4-count, getSize(matches.get(0).getParameters().get("statements1"))); + assertEquals("count="+count, count, getSize(matches.get(0).getParameters().get("statements2"))); + assertEquals("count="+count, 2, getSize(matches.get(0).getParameters().get("printedValue"))); + } else { + //the possessive matcher eat too much. There is no target element for last `printedValue` variable + assertEquals("count="+count, 0, matches.size()); + } + } + } + @Test + public void testMatchGreedyMultiValueMinCount2() throws Exception { + //contract: check possessive matching with min count limit and GREEDY back off + CtType ctClass = ModelUtils.buildClass(MatchMultiple2.class); + for (int i = 0; i < 7; i++) { + final int count = i; + Pattern pattern = MatchMultiple2.createPattern(ctClass.getFactory(), pb -> { + pb.parameter("statements1").setMatchingStrategy(Quantifier.RELUCTANT); + pb.parameter("statements2").setMatchingStrategy(Quantifier.GREEDY).setMaxOccurence(count); + pb.parameter("printedValue").setMatchingStrategy(Quantifier.GREEDY).setContainerKind(ContainerKind.LIST).setMinOccurence(2); + }); + + List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0).getBody()); + if (count < 7) { + //the last template has nothing to match -> no match + assertEquals("count="+count, 1, matches.size()); + assertEquals("count="+count, Math.max(0, 3-count), getSize(matches.get(0).getParameters().get("statements1"))); + assertEquals("count="+count, count - Math.max(0, count-4), getSize(matches.get(0).getParameters().get("statements2"))); + assertEquals("count="+count, Math.max(2, 3 - Math.max(0, count-3)), getSize(matches.get(0).getParameters().get("printedValue"))); + } else { + //the possessive matcher eat too much. There is no target element for last `printedValue` variable + assertEquals("count="+count, 0, matches.size()); + } + } + } + + private int getSize(Object o) { + if (o instanceof List) { + return ((List) o).size(); + } + if (o == null) { + return 0; + } + fail("Unexpected object of type " + o.getClass()); + return -1; + } + + @Test + public void testMatchParameterValue() throws Exception { + //contract: by default the parameter value is the reference to real node from the model + CtType ctClass = ModelUtils.buildClass(MatchWithParameterType.class); + + Pattern pattern = MatchWithParameterType.createPattern(ctClass.getFactory(), null); + + List matches = pattern.getMatches(ctClass); + + assertEquals(5, matches.size()); + { + Match match = matches.get(0); + assertEquals(Arrays.asList("java.lang.System.out.println(value)"), listToListOfStrings(match.getMatchingElements())); + Object value = match.getParameters().get("value"); + assertTrue(value instanceof CtVariableRead); + assertEquals("value", value.toString()); + //contract: the value is reference to found node (not a clone) + assertTrue(((CtElement)value).isParentInitialized()); + assertSame(CtRole.ARGUMENT, ((CtElement)value).getRoleInParent()); + } + { + Match match = matches.get(1); + assertEquals(Arrays.asList("java.lang.System.out.println(\"a\")"), listToListOfStrings(match.getMatchingElements())); + assertTrue(match.getParameters().get("value") instanceof CtLiteral); + assertEquals("\"a\"", match.getParameters().get("value").toString()); + } + { + Match match = matches.get(2); + assertEquals(Arrays.asList("java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings(match.getMatchingElements())); + assertTrue(match.getParameters().get("value") instanceof CtLiteral); + assertEquals("\"Xxxx\"", match.getParameters().get("value").toString()); + } + { + Match match = matches.get(3); + assertEquals(Arrays.asList("java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings(match.getMatchingElements())); + assertTrue(match.getParameters().get("value") instanceof CtLiteral); + assertEquals("((java.lang.String) (null))", match.getParameters().get("value").toString()); + } + { + Match match = matches.get(4); + assertEquals(Arrays.asList("java.lang.System.out.println(java.lang.Long.class.toString())"), listToListOfStrings(match.getMatchingElements())); + assertTrue(match.getParameters().get("value") instanceof CtInvocation); + assertEquals("java.lang.Long.class.toString()", match.getParameters().get("value").toString()); + } + } + + @Test + public void testMatchParameterValueType() throws Exception { + //contract: the parameter value type matches only values of required type + CtType ctClass = ModelUtils.buildClass(MatchWithParameterType.class); + { + Pattern pattern = MatchWithParameterType.createPattern(ctClass.getFactory(), CtLiteral.class); + + List matches = pattern.getMatches(ctClass); + + assertEquals(3, matches.size()); + { + Match match = matches.get(0); + assertEquals(Arrays.asList("java.lang.System.out.println(\"a\")"), listToListOfStrings(match.getMatchingElements())); + assertTrue(match.getParameters().get("value") instanceof CtLiteral); + assertEquals("\"a\"", match.getParameters().get("value").toString()); + } + { + Match match = matches.get(1); + assertEquals(Arrays.asList("java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings(match.getMatchingElements())); + assertTrue(match.getParameters().get("value") instanceof CtLiteral); + assertEquals("\"Xxxx\"", match.getParameters().get("value").toString()); + } + { + Match match = matches.get(2); + assertEquals(Arrays.asList("java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings(match.getMatchingElements())); + assertTrue(match.getParameters().get("value") instanceof CtLiteral); + assertEquals("((java.lang.String) (null))", match.getParameters().get("value").toString()); + } + } + { + Pattern pattern = MatchWithParameterType.createPattern(ctClass.getFactory(), CtInvocation.class); + + List matches = pattern.getMatches(ctClass); + + assertEquals(1, matches.size()); + { + Match match = matches.get(0); + assertEquals(Arrays.asList("java.lang.System.out.println(java.lang.Long.class.toString())"), listToListOfStrings(match.getMatchingElements())); + assertTrue(match.getParameters().get("value") instanceof CtInvocation); + assertEquals("java.lang.Long.class.toString()", match.getParameters().get("value").toString()); + } + + } + } + + @Test + public void testMatchParameterCondition() throws Exception { + //contract: the parameter value matching condition causes that only matching parameter values are accepted + //if the value isn't matching then node is not matched + CtType ctClass = ModelUtils.buildClass(MatchWithParameterCondition.class); + { + Pattern pattern = MatchWithParameterCondition.createPattern(ctClass.getFactory(), (Object value) -> value instanceof CtLiteral); + + List matches = pattern.getMatches(ctClass); + + assertEquals(3, matches.size()); + { + Match match = matches.get(0); + assertEquals(Arrays.asList("java.lang.System.out.println(\"a\")"), listToListOfStrings(match.getMatchingElements())); + assertTrue(match.getParameters().get("value") instanceof CtLiteral); + assertEquals("\"a\"", match.getParameters().get("value").toString()); + } + { + Match match = matches.get(1); + assertEquals(Arrays.asList("java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings(match.getMatchingElements())); + assertTrue(match.getParameters().get("value") instanceof CtLiteral); + assertEquals("\"Xxxx\"", match.getParameters().get("value").toString()); + } + { + Match match = matches.get(2); + assertEquals(Arrays.asList("java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings(match.getMatchingElements())); + assertTrue(match.getParameters().get("value") instanceof CtLiteral); + assertEquals("((java.lang.String) (null))", match.getParameters().get("value").toString()); + } + } + } + + @Test + public void testMatchOfAttribute() throws Exception { + //contract: match some nodes like template, but with some variable attributes + CtType ctClass = ModelUtils.buildClass(MatchModifiers.class); + { + //match all methods with arbitrary name, modifiers, parameters, but with empty body and return type void + Pattern pattern = MatchModifiers.createPattern(ctClass.getFactory(), false); + List matches = pattern.getMatches(ctClass); + assertEquals(3, matches.size()); + { + Match match = matches.get(0); + assertEquals(1, match.getMatchingElements().size()); + assertEquals("matcher1", match.getMatchingElement(CtMethod.class).getSimpleName()); + assertEquals(3, match.getParametersMap().size()); + assertEquals("matcher1", match.getParametersMap().get("methodName")); + assertEquals(new HashSet<>(Arrays.asList(ModifierKind.PUBLIC)), match.getParametersMap().get("modifiers")); + assertEquals(Arrays.asList(), match.getParametersMap().get("parameters")); + } + { + Match match = matches.get(1); + assertEquals(1, match.getMatchingElements().size()); + assertEquals("publicStaticMethod", match.getMatchingElement(CtMethod.class).getSimpleName()); + assertEquals(3, match.getParametersMap().size()); + assertEquals("publicStaticMethod", match.getParametersMap().get("methodName")); + assertEquals(new HashSet<>(Arrays.asList(ModifierKind.PUBLIC, ModifierKind.STATIC)), match.getParametersMap().get("modifiers")); + assertEquals(Arrays.asList(), match.getParametersMap().get("parameters")); + } + { + Match match = matches.get(2); + assertEquals(1, match.getMatchingElements().size()); + assertEquals("packageProtectedMethodWithParam", match.getMatchingElement(CtMethod.class).getSimpleName()); + assertEquals(3, match.getParametersMap().size()); + assertEquals("packageProtectedMethodWithParam", match.getParametersMap().get("methodName")); + assertEquals(new HashSet<>(), match.getParametersMap().get("modifiers")); + assertEquals(2, ((List) match.getParametersMap().get("parameters")).size()); + } + } + { + //match all methods with arbitrary name, modifiers, parameters and body, but with return type void + Pattern pattern = MatchModifiers.createPattern(ctClass.getFactory(), true); + List matches = pattern.getMatches(ctClass); + assertEquals(4, matches.size()); + { + Match match = matches.get(0); + assertEquals(1, match.getMatchingElements().size()); + assertEquals("matcher1", match.getMatchingElement(CtMethod.class).getSimpleName()); + assertEquals(4, match.getParametersMap().size()); + assertEquals("matcher1", match.getParametersMap().get("methodName")); + assertEquals(new HashSet<>(Arrays.asList(ModifierKind.PUBLIC)), match.getParametersMap().get("modifiers")); + assertEquals(Arrays.asList(), match.getParametersMap().get("parameters")); + assertEquals(Arrays.asList(), match.getParametersMap().get("statements")); + } + { + Match match = matches.get(1); + assertEquals(1, match.getMatchingElements().size()); + assertEquals("publicStaticMethod", match.getMatchingElement(CtMethod.class).getSimpleName()); + assertEquals(4, match.getParametersMap().size()); + assertEquals("publicStaticMethod", match.getParametersMap().get("methodName")); + assertEquals(new HashSet<>(Arrays.asList(ModifierKind.PUBLIC, ModifierKind.STATIC)), match.getParametersMap().get("modifiers")); + assertEquals(Arrays.asList(), match.getParametersMap().get("parameters")); + assertEquals(Arrays.asList(), match.getParametersMap().get("statements")); + } + { + Match match = matches.get(2); + assertEquals(1, match.getMatchingElements().size()); + assertEquals("packageProtectedMethodWithParam", match.getMatchingElement(CtMethod.class).getSimpleName()); + assertEquals(4, match.getParametersMap().size()); + assertEquals("packageProtectedMethodWithParam", match.getParametersMap().get("methodName")); + assertEquals(new HashSet<>(), match.getParametersMap().get("modifiers")); + assertEquals(2, ((List) match.getParametersMap().get("parameters")).size()); + assertEquals(Arrays.asList(), match.getParametersMap().get("statements")); + } + { + Match match = matches.get(3); + assertEquals(1, match.getMatchingElements().size()); + assertEquals("withBody", match.getMatchingElement(CtMethod.class).getSimpleName()); + assertEquals(4, match.getParametersMap().size()); + assertEquals("withBody", match.getParametersMap().get("methodName")); + assertEquals(new HashSet<>(Arrays.asList(ModifierKind.PRIVATE)), match.getParametersMap().get("modifiers")); + assertEquals(0, ((List) match.getParametersMap().get("parameters")).size()); + assertEquals(2, ((List) match.getParametersMap().get("statements")).size()); + assertEquals("this.getClass()", ((List) match.getParametersMap().get("statements")).get(0).toString()); + assertEquals("java.lang.System.out.println()", ((List) match.getParametersMap().get("statements")).get(1).toString()); + } + } + } + + @Test + public void testMatchOfMapAttribute() throws Exception { + //contract: match attribute of type Map - annotations + CtType ctClass = ModelUtils.buildClass(MatchMap.class); + { + //match all methods with arbitrary name, and Annotation Test modifiers, parameters, but with empty body and return type void + Pattern pattern = MatchMap.createPattern(ctClass.getFactory(), false); + List matches = pattern.getMatches(ctClass); +// List matches = pattern.getMatches(ctClass.getMethodsByName("matcher1").get(0)); + assertEquals(3, matches.size()); + { + Match match = matches.get(0); + assertEquals(1, match.getMatchingElements().size()); + assertEquals("matcher1", match.getMatchingElement(CtMethod.class).getSimpleName()); + assertEquals(2, match.getParametersMap().size()); + assertEquals("matcher1", match.getParametersMap().get("methodName")); + Map values = getMap(match, "testAnnotations"); + assertEquals(0, values.size()); + } + { + Match match = matches.get(1); + assertEquals(1, match.getMatchingElements().size()); + assertEquals("m1", match.getMatchingElement(CtMethod.class).getSimpleName()); + assertEquals(2, match.getParametersMap().size()); + assertEquals("m1", match.getParametersMap().get("methodName")); + Map values = getMap(match, "testAnnotations"); + assertEquals(1, values.size()); + assertEquals("\"xyz\"", values.get("value").toString()); + } + { + Match match = matches.get(2); + assertEquals(1, match.getMatchingElements().size()); + assertEquals("m2", match.getMatchingElement(CtMethod.class).getSimpleName()); + assertEquals(2, match.getParametersMap().size()); + assertEquals("m2", match.getParametersMap().get("methodName")); + Map values = getMap(match, "testAnnotations"); + assertEquals(2, values.size()); + assertEquals("\"abc\"", values.get("value").toString()); + assertEquals("123", values.get("timeout").toString()); + } + } + } + + private Map getMap(Match match, String name) { + Object v = match.getParametersMap().get(name); + assertNotNull(v); + return ((ParameterValueProvider) v).asMap(); + } + + @Test + @Ignore //TODO allow partial matching of children by template and remaining elements by ParameterValueProvider + public void testMatchOfMapAttributeAndOtherAnnotations() throws Exception { + //contract: match attribute of type Map - annotations + CtType ctClass = ModelUtils.buildClass(MatchMap.class); + { + //match all methods with arbitrary name, and Annotation Test modifiers, parameters, but with empty body and return type void + Pattern pattern = MatchMap.createPattern(ctClass.getFactory(), true); + List matches = pattern.getMatches(ctClass); + assertEquals(5, matches.size()); + { + Match match = matches.get(0); + assertEquals(1, match.getMatchingElements().size()); + assertEquals("matcher1", match.getMatchingElement(CtMethod.class).getSimpleName()); + assertEquals(2, match.getParametersMap().size()); + assertEquals("matcher1", match.getParametersMap().get("methodName")); + assertTrue(match.getParametersMap().get("allAnnotations") instanceof List); + } + { + Match match = matches.get(1); + assertEquals(1, match.getMatchingElements().size()); + assertEquals("m1", match.getMatchingElement(CtMethod.class).getSimpleName()); + assertEquals(2, match.getParametersMap().size()); + assertEquals("m1", match.getParametersMap().get("methodName")); + Map values = getMap(match, "testAnnotations"); + assertEquals(1, values.size()); + assertEquals("java.lang.Exception.class", values.get("expected").toString()); + } + { + Match match = matches.get(2); + assertEquals(1, match.getMatchingElements().size()); + assertEquals("m2", match.getMatchingElement(CtMethod.class).getSimpleName()); + assertEquals(2, match.getParametersMap().size()); + assertEquals("m2", match.getParametersMap().get("methodName")); + Map values = getMap(match, "testAnnotations"); + assertEquals(2, values.size()); + assertEquals("java.lang.RuntimeException.class", values.get("expected").toString()); + assertEquals("123", values.get("timeout").toString()); + } + } + } + + private List listToListOfStrings(List list) { + if (list == null) { + return Collections.emptyList(); + } + List strings = new ArrayList<>(list.size()); + for (Object obj : list) { + strings.add(obj == null ? "null" : obj.toString()); + } + return strings; + } + +} diff --git a/src/test/java/spoon/test/template/TemplateTest.java b/src/test/java/spoon/test/template/TemplateTest.java index 815ce58c839..077517791a3 100644 --- a/src/test/java/spoon/test/template/TemplateTest.java +++ b/src/test/java/spoon/test/template/TemplateTest.java @@ -4,11 +4,15 @@ import spoon.Launcher; import spoon.SpoonException; import spoon.compiler.SpoonResourceHelper; +import spoon.pattern.ParameterValueProvider; +import spoon.pattern.PatternBuilder; +import spoon.pattern.matcher.Match; import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtExpression; import spoon.reflect.code.CtForEach; import spoon.reflect.code.CtIf; import spoon.reflect.code.CtInvocation; +import spoon.reflect.code.CtLiteral; import spoon.reflect.code.CtStatement; import spoon.reflect.code.CtTry; import spoon.reflect.declaration.CtClass; @@ -27,7 +31,6 @@ import spoon.support.compiler.FileSystemFile; import spoon.support.compiler.FileSystemFolder; import spoon.support.template.Parameters; -import spoon.support.template.SubstitutionVisitor; import spoon.template.Substitution; import spoon.template.TemplateMatcher; import spoon.template.TemplateParameter; @@ -45,6 +48,7 @@ import spoon.test.template.testclasses.SubStringTemplate; import spoon.test.template.testclasses.SubstituteLiteralTemplate; import spoon.test.template.testclasses.SubstituteRootTemplate; +import spoon.test.template.testclasses.ToBeMatched; import spoon.test.template.testclasses.TypeReferenceClassAccessTemplate; import spoon.test.template.testclasses.bounds.CheckBound; import spoon.test.template.testclasses.bounds.CheckBoundMatcher; @@ -89,6 +93,7 @@ import static org.junit.Assert.fail; import static org.hamcrest.CoreMatchers.is; import static java.util.Arrays.asList; +import static spoon.testing.utils.ModelUtils.getOptimizedString; public class TemplateTest { @@ -434,7 +439,12 @@ public void testTemplateMatcher() throws Exception { CtIf templateRoot = (CtIf) ((CtMethod) templateKlass.getElements(new NamedElementFilter<>(CtMethod.class,"matcher1")).get(0)).getBody().getStatement(0); TemplateMatcher matcher = new TemplateMatcher(templateRoot); assertEquals(2, matcher.find(klass).size()); - assertThat(asList("foo","fbar"), is(klass.filterChildren(matcher).map((CtElement e)->e.getParent(CtMethod.class).getSimpleName()).list())) ; + assertThat(asList("foo","fbar"), is(klass.filterChildren(matcher).map((CtElement e)->getMethodName(e)).list())) ; + matcher.forEachMatch(klass, (match) -> { + assertTrue(checkParameters("foo", match, "_col_", "new java.util.ArrayList<>()") + || checkParameters("fbar", match, "_col_", "l") + ); + }); } {// testing matcher2 @@ -443,7 +453,10 @@ public void testTemplateMatcher() throws Exception { CtIf templateRoot = (CtIf) ((CtMethod) templateKlass.getElements(new NamedElementFilter<>(CtMethod.class,"matcher2")).get(0)).getBody().getStatement(0); TemplateMatcher matcher = new TemplateMatcher(templateRoot); assertEquals(1, matcher.find(klass).size()); - assertThat(asList("bov"), is(klass.filterChildren(matcher).map((CtElement e)->e.getParent(CtMethod.class).getSimpleName()).list())) ; + assertThat(asList("bov"), is(klass.filterChildren(matcher).map((CtElement e)->getMethodName(e)).list())) ; + matcher.forEachMatch(klass, (match) -> { + assertTrue(checkParameters("bov", match, "_col_", "new java.util.ArrayList<>()")); + }); } {// testing matcher3 @@ -452,7 +465,12 @@ public void testTemplateMatcher() throws Exception { CtIf templateRoot = (CtIf) ((CtMethod) templateKlass.getElements(new NamedElementFilter<>(CtMethod.class,"matcher3")).get(0)).getBody().getStatement(0); TemplateMatcher matcher = new TemplateMatcher(templateRoot); assertEquals(2, matcher.find(klass).size()); - assertThat(asList("foo","fbar"), is(klass.filterChildren(matcher).map((CtElement e)->e.getParent(CtMethod.class).getSimpleName()).list())) ; + assertThat(asList("foo","fbar"), is(klass.filterChildren(matcher).map((CtElement e)->getMethodName(e)).list())) ; + matcher.forEachMatch(klass, (match) -> { + assertTrue(checkParameters("foo", match, "_x_", "(new java.util.ArrayList<>().size())") + ||checkParameters("fbar", match, "_x_", "(l.size())") + ); + }); } {// testing matcher4 @@ -461,7 +479,20 @@ public void testTemplateMatcher() throws Exception { CtIf templateRoot = (CtIf) ((CtMethod) templateKlass.getElements(new NamedElementFilter<>(CtMethod.class,"matcher4")).get(0)).getBody().getStatement(0); TemplateMatcher matcher = new TemplateMatcher(templateRoot); assertEquals(3, matcher.find(klass).size()); - assertThat(asList("foo","foo2","fbar"), is(klass.filterChildren(matcher).map((CtElement e)->e.getParent(CtMethod.class).getSimpleName()).list())) ; + assertThat(asList("foo","foo2","fbar"), is(klass.filterChildren(matcher).map((CtElement e)->getMethodName(e)).list())) ; + matcher.forEachMatch(klass, (match) -> { + assertTrue( + checkParameters("foo", match, + "_x_", "(new java.util.ArrayList<>().size())", + "_y_", "10") + ||checkParameters("foo2", match, + "_x_", "(new java.util.ArrayList<>().size())", + "_y_", "11") + ||checkParameters("fbar", match, + "_x_", "(l.size())", + "_y_", "10") + ); + }); } {// testing matcher5 @@ -470,7 +501,35 @@ public void testTemplateMatcher() throws Exception { CtIf templateRoot = (CtIf) ((CtMethod) templateKlass.getElements(new NamedElementFilter<>(CtMethod.class,"matcher5")).get(0)).getBody().getStatement(0); TemplateMatcher matcher = new TemplateMatcher(templateRoot); assertEquals(6, matcher.find(klass).size()); - assertThat(asList("foo","foo2","fbar","baz","bou","bov"), is(klass.filterChildren(matcher).map((CtElement e)->e.getParent(CtMethod.class).getSimpleName()).list())) ; + assertThat(asList("foo","foo2","fbar","baz","bou","bov"), is(klass.filterChildren(matcher).map((CtElement e)->getMethodName(e)).list())) ; + matcher.forEachMatch(klass, (match) -> { + assertTrue( + checkParameters("foo", match, + "_x_", "(new java.util.ArrayList<>().size())", + "_y_", "10", + "_block_", "throw new java.lang.IndexOutOfBoundsException();") + ||checkParameters("foo2", match, + "_x_", "(new java.util.ArrayList<>().size())", + "_y_", "11", + "_block_", "throw new java.lang.IndexOutOfBoundsException();") + ||checkParameters("fbar", match, + "_x_", "(l.size())", + "_y_", "10", + "_block_", "throw new java.lang.IndexOutOfBoundsException();") + ||checkParameters("baz", match, + "_x_", "(new java.util.ArrayList<>().size())", + "_y_", "10", + "_block_", "{}") + ||checkParameters("bou", match, + "_x_", "(new java.util.ArrayList<>().size())", + "_y_", "10", + "_block_", "{ java.lang.System.out.println();}") + ||checkParameters("bov", match, + "_x_", "(new java.util.ArrayList<>().size())", + "_y_", "10", + "_block_", "java.lang.System.out.println();") + ); + }); } {// testing matcher6 @@ -479,9 +538,36 @@ public void testTemplateMatcher() throws Exception { CtIf templateRoot = (CtIf) ((CtMethod) templateKlass.getElements(new NamedElementFilter<>(CtMethod.class,"matcher6")).get(0)).getBody().getStatement(0); TemplateMatcher matcher = new TemplateMatcher(templateRoot); assertEquals(2, matcher.find(klass).size()); - assertThat(asList("baz","bou"), is(klass.filterChildren(matcher).map((CtElement e)->e.getParent(CtMethod.class).getSimpleName()).list())) ; + assertThat(asList("baz","bou"), is(klass.filterChildren(matcher).map((CtElement e)->getMethodName(e)).list())) ; + matcher.forEachMatch(klass, (match) -> { + assertTrue( + checkParameters("baz", match, + "_x_", "(new java.util.ArrayList<>().size())", + "_y_", "10", + "_stmt_", "null") + ||checkParameters("bou", match, + "_x_", "(new java.util.ArrayList<>().size())", + "_y_", "10", + "_stmt_", "java.lang.System.out.println()") + ); + }); } + {// testing matcher7 + CtClass templateKlass = factory.Class().get(CheckBoundMatcher.class); + CtClass klass = factory.Class().get(CheckBound.class); + CtIf templateRoot = (CtIf) ((CtMethod) templateKlass.getElements(new NamedElementFilter<>(CtMethod.class,"matcher7")).get(0)).getBody().getStatement(0); + TemplateMatcher matcher = new TemplateMatcher(templateRoot); + assertEquals(1, matcher.find(klass).size()); + assertThat(asList("bos"), is(klass.filterChildren(matcher).map((CtElement e)->getMethodName(e)).list())) ; + matcher.forEachMatch(klass, (match) -> { + assertTrue( + checkParameters("bos", match, + "_x_", "(new java.util.ArrayList<>().size())", + "_block_", "java.lang.System.out.println();") + ); + }); + } // testing with named elements, at the method level { @@ -514,6 +600,27 @@ public void testTemplateMatcher() throws Exception { assertEquals("fbar", ctElements.get(2).getSimpleName()); } } + + private boolean checkParameters(String methodName, Match match, String... keyValues) { + if (methodName.equals(getMethodName(match.getMatchingElement()))) { + assertEquals("The arguments of keyValues must be in pairs", 0, keyValues.length % 2); + Map allParams = new HashMap<>(match.getParameters().asMap()); + int count = keyValues.length / 2; + for (int i = 0; i < count; i++) { + String key = keyValues[i * 2]; + String expectedValue = keyValues[i * 2 + 1]; + Object realValue = allParams.remove(key); + assertEquals(expectedValue, getOptimizedString(realValue)); + } + assertTrue("Unexpected parameter values: " + allParams, allParams.isEmpty()); + return true; + } + return false; + } + + private String getMethodName(CtElement e) { + return e.getParent(CtMethod.class).getSimpleName(); + } @Test public void testExtensionBlock() throws Exception { @@ -538,6 +645,7 @@ public void testExtensionBlock() throws Exception { assertTrue(aTry.getBody().getStatement(0) instanceof CtInvocation); assertEquals("spoon.test.template.testclasses.logger.Logger.enter(\"Logger\", \"enter\")", aTry.getBody().getStatement(0).toString()); assertTrue(aTry.getBody().getStatements().size() > 1); + assertEquals("java.lang.System.out.println((((\"enter: \" + className) + \" - \") + methodName))", aTry.getBody().getStatement(1).toString()); } @Test @@ -551,8 +659,6 @@ public void testExtensionDecoupledSubstitutionVisitor() throws Exception { launcher.buildModel(); Factory factory = launcher.getFactory(); - final CtClass aTemplateModelType = launcher.getFactory().Class().get(LoggerModel.class); - final CtMethod aTemplateModel = aTemplateModelType.getMethod("block"); final CtClass aTargetType = launcher.getFactory().Class().get(Logger.class); final CtMethod toBeLoggedMethod = aTargetType.getMethodsByName("enter").get(0); @@ -561,7 +667,9 @@ public void testExtensionDecoupledSubstitutionVisitor() throws Exception { params.put("_classname_", factory.Code().createLiteral(aTargetType.getSimpleName())); params.put("_methodName_", factory.Code().createLiteral(toBeLoggedMethod.getSimpleName())); params.put("_block_", toBeLoggedMethod.getBody()); - final List> aMethods = new SubstitutionVisitor(factory, params).substitute(aTemplateModel.clone()); + final List aMethods = PatternBuilder.create(launcher.getFactory(), LoggerModel.class, model -> model.setTypeMember("block")) + .configureAutomaticParameters() + .build().applyToType(aTargetType, CtMethod.class, params); assertEquals(1, aMethods.size()); final CtMethod aMethod = aMethods.get(0); assertTrue(aMethod.getBody().getStatement(0) instanceof CtTry); @@ -1030,7 +1138,7 @@ public void substituteTypeAccessReference() throws Exception { Factory factory = spoon.getFactory(); //contract: String value is substituted in substring of literal, named element and reference - CtTypeReference typeRef = factory.Type().createReference(TypeReferenceClassAccessTemplate.Example.class); + CtTypeReference typeRef = factory.Type().createReference("spoon.test.template.TypeReferenceClassAccess$Example"); typeRef.addActualTypeArgument(factory.Type().DATE); final CtClass result = (CtClass) new TypeReferenceClassAccessTemplate(typeRef).apply(factory.Class().create("spoon.test.template.TypeReferenceClassAccess")); @@ -1046,4 +1154,74 @@ public void substituteTypeAccessReference() throws Exception { assertEquals("o = (o) instanceof spoon.test.template.TypeReferenceClassAccess.Example", method.getBody().getStatement(4).toString()); assertEquals("java.util.function.Supplier p = spoon.test.template.TypeReferenceClassAccess.Example::currentTimeMillis", method.getBody().getStatement(5).toString()); } + + @Test + public void testTemplateMatchOfMultipleElements() throws Exception { + CtType type = ModelUtils.buildClass(ToBeMatched.class); + + List> literals1 = getFirstStmt(type, "match1", CtInvocation.class).getArguments(); + List> literals2 = getFirstStmt(type, "match2", CtInvocation.class).getArguments(); + assertEquals("a", literals1.get(0).getValue()); + + { //contract: matches one element + List> found = new ArrayList<>(); + ToBeMatched.patternOfStringLiterals(type.getFactory(), "a").forEachMatch(type, (match) -> { + found.add(match.getMatchingElements()); + }); + assertEquals(3, found.size()); + assertSequenceOn(literals1, 0, 1, found.get(0)); + assertSequenceOn(literals1, 6, 1, found.get(1)); + assertSequenceOn(literals2, 0, 1, found.get(2)); + } + { //contract: matches sequence of elements + List> found = new ArrayList<>(); + ToBeMatched.patternOfStringLiterals(type.getFactory(), "a", "b", "c").forEachMatch(type, (match) -> { + found.add(match.getMatchingElements()); + }); + assertEquals(2, found.size()); + assertSequenceOn(literals1, 0, 3, found.get(0)); + assertSequenceOn(literals1, 6, 3, found.get(1)); + } + { //contract: matches sequence of elements not starting at the beginning + List> found = new ArrayList<>(); + ToBeMatched.patternOfStringLiterals(type.getFactory(), "b", "c").forEachMatch(type, (match) -> { + found.add(match.getMatchingElements()); + }); + assertEquals(3, found.size()); + assertSequenceOn(literals1, 1, 2, found.get(0)); + assertSequenceOn(literals1, 7, 2, found.get(1)); + assertSequenceOn(literals2, 3, 2, found.get(2)); + } + { //contract: matches sequence of repeated elements, but match each element only once + List> found = new ArrayList<>(); + ToBeMatched.patternOfStringLiterals(type.getFactory(), "d", "d").forEachMatch(type, (match) -> { + found.add(match.getMatchingElements()); + }); + assertEquals(2, found.size()); + assertSequenceOn(literals2, 6, 2, found.get(0)); + assertSequenceOn(literals2, 8, 2, found.get(1)); + } + } + + private void assertSequenceOn(List source, int expectedOffset, int expectedSize, List matches) { + //check the number of matches + assertEquals(expectedSize, matches.size()); + //check that each match fits to source collection on the expected offset + for (int i = 0; i < expectedSize; i++) { + assertSame(source.get(expectedOffset + i), matches.get(i)); + } + } + + private T getFirstStmt(CtType type, String methodName, Class stmtType) { + return (T) type.filterChildren((CtMethod m) -> m.getSimpleName().equals(methodName)).first(CtMethod.class).getBody().getStatement(0); + } + + private int indexOf(List list, Object o) { + for(int i=0; i means NO match, null is not allowed. + //because minCount == 0 means that value is optional + ParameterInfo namedParam = new NamedItemAccessor("year") + .setMinOccurences(1); + { + ParameterValueProvider container = new UnmodifiableParameterValueProvider().putIntoCopy("a", "b"); + assertNull(namedParam.addValueAs(container, null)); + assertEquals(map().put("a", "b"), container.asMap()); + } + } + @Test + public void testSingleValueParameterByNameConditionalMatcher() { + ParameterInfo namedParam = new NamedItemAccessor("year").setMatchCondition(Integer.class, i -> i > 2000); + + //matching value is accepted + ParameterValueProvider val = namedParam.addValueAs(null, 2018); + assertNotNull(val); + assertEquals(map().put("year", 2018), val.asMap()); + //not matching value is not accepted + assertNull(namedParam.addValueAs(null, 1000)); + assertNull(namedParam.addValueAs(null, "3000")); + //even matching value is STILL not accepted when there is already a different value + assertNull(namedParam.addValueAs(new UnmodifiableParameterValueProvider().putIntoCopy("year", 3000), 2018)); + } + + @Test + public void testListParameterByNameIntoNull() { + ParameterInfo namedParam = new NamedItemAccessor("year").setContainerKind(ContainerKind.LIST); + {//adding value into null container, creates a new container with List which contains that value + ParameterValueProvider val = namedParam.addValueAs(null, 2018); + assertNotNull(val); + assertEquals(map().put("year", Arrays.asList(2018)), val.asMap()); + } + } + @Test + public void testListParameterByNameIntoEmptyContainer() { + ParameterInfo namedParam = new NamedItemAccessor("year").setContainerKind(ContainerKind.LIST); + {//adding value into empty container, creates a new container with List which contains that value + ParameterValueProvider empty = new UnmodifiableParameterValueProvider(); + ParameterValueProvider val = namedParam.addValueAs(empty, 2018); + assertNotNull(val); + assertEquals(map().put("year", Arrays.asList(2018)), val.asMap()); + //empty is still empty + assertEquals(map(), empty.asMap()); + } + } + @Test + public void testListParameterByNameIntoEmptyContainerWithEmptyList() { + Consumer check = (namedParam) -> + {//adding value into container, which already contains a empty list, creates a new container with List which contains that value + ParameterValueProvider empty = new UnmodifiableParameterValueProvider().putIntoCopy("year", Collections.emptyList()); + + ParameterValueProvider val = namedParam.addValueAs(empty, 2018); + //adding same value - adds the second value again + ParameterValueProvider val2 = namedParam.addValueAs(val, 2018); + //adding null value - adds nothing. Same container is returned + assertSame(val2, namedParam.addValueAs(val2, null)); + + //empty is still empty + assertEquals(map().put("year", Collections.emptyList()), empty.asMap()); + assertNotNull(val); + assertEquals(map().put("year", Arrays.asList(2018)), val.asMap()); + assertNotNull(val2); + assertEquals(map().put("year", Arrays.asList(2018, 2018)), val2.asMap()); + }; + //contract: it behaves like this when container kind is defined as LIST + check.accept(new NamedItemAccessor("year").setContainerKind(ContainerKind.LIST)); + //contract: it behaves like this even when container kind is not defined, so it is automatically detected from the existing parameter value + check.accept(new NamedItemAccessor("year")); + //contract: it behaves like this when ListAccessor + NamedAccessor is used + check.accept(new ListAccessor(new NamedItemAccessor("year"))); + //contract: it behaves like this when ListAccessor + NamedAccessor with defined container is used with + check.accept(new ListAccessor(new NamedItemAccessor("year").setContainerKind(ContainerKind.LIST))); + } + + @Test + public void testMergeOnDifferentValueTypeContainers() { + BiConsumer checker = (parameter, params) -> { + //contract: the same value can be always applied when it already exists there independent on container type + assertSame(params, parameter.addValueAs(params, "x")); + //contract: the different value must be never applied independent on container type + assertNull(parameter.addValueAs(params, "y")); + //contract: the different value must be never applied independent on container type + assertNull(parameter.addValueAs(params, null)); + }; + ParameterValueProvider empty = new UnmodifiableParameterValueProvider(); + checker.accept(new NamedItemAccessor("year"), empty.putIntoCopy("year", "x")); + checker.accept(new ListAccessor(0, new NamedItemAccessor("year")), empty.putIntoCopy("year", Collections.singletonList("x"))); + checker.accept(new ListAccessor(1, new NamedItemAccessor("year")), empty.putIntoCopy("year", Arrays.asList("zz","x"))); + checker.accept(new NamedItemAccessor("key", new ListAccessor(1, new NamedItemAccessor("year"))), empty.putIntoCopy("year", Arrays.asList("zz",empty.putIntoCopy("key", "x")))); + checker.accept(new NamedItemAccessor("key", new NamedItemAccessor("year")), empty.putIntoCopy("year", empty.putIntoCopy("key", "x"))); + } + + @Test + public void testAppendIntoList() { + ParameterInfo parameter = new NamedItemAccessor("years").setContainerKind(ContainerKind.LIST); + ParameterValueProvider params = parameter.addValueAs(null, 1000); + assertNotNull(params); + assertEquals(map().put("years", Arrays.asList(1000)), params.asMap()); + + params = parameter.addValueAs(params, 100); + assertNotNull(params); + assertEquals(map().put("years", Arrays.asList(1000, 100)), params.asMap()); + + params = parameter.addValueAs(params, "a"); + assertNotNull(params); + assertEquals(map().put("years", Arrays.asList(1000, 100, "a")), params.asMap()); + + params = parameter.addValueAs(params, "a"); + assertNotNull(params); + assertEquals(map().put("years", Arrays.asList(1000, 100, "a", "a")), params.asMap()); + } + + @Test + public void testSetIntoList() { + ParameterInfo named = new NamedItemAccessor("years"); + ParameterValueProvider params = new ListAccessor(2, named).addValueAs(null, 1000); + assertNotNull(params); + assertEquals(map().put("years", Arrays.asList(null, null, 1000)), params.asMap()); + + params = new ListAccessor(0, named).addValueAs(params, 10); + assertNotNull(params); + assertEquals(map().put("years", Arrays.asList(10, null, 1000)), params.asMap()); + + params = new ListAccessor(3, named).addValueAs(params, 10000); + assertNotNull(params); + assertEquals(map().put("years", Arrays.asList(10, null, 1000, 10000)), params.asMap()); + } + + @Test + public void testAppendIntoSet() { + ParameterInfo parameter = new NamedItemAccessor("years").setContainerKind(ContainerKind.SET); + ParameterValueProvider params = parameter.addValueAs(null, 1000); + assertNotNull(params); + assertEquals(map().put("years", asSet(1000)), params.asMap()); + + params = parameter.addValueAs(params, 100); + assertNotNull(params); + assertEquals(map().put("years", asSet(1000, 100)), params.asMap()); + + params = parameter.addValueAs(params, "a"); + assertNotNull(params); + assertEquals(map().put("years", asSet(1000, 100, "a")), params.asMap()); + + assertSame(params, parameter.addValueAs(params, "a")); + assertNotNull(params); + assertEquals(map().put("years", asSet(1000, 100, "a")), params.asMap()); + } + @Test + public void testMapEntryInParameterByName() { + BiConsumer checker = (namedParam, empty) -> + {//the Map.Entry value is added into property of type Map + //only Map.Entries can be added + assertNull(namedParam.addValueAs(empty, "a value")); + + final ParameterValueProvider val = namedParam.addValueAs(empty, entry("year", 2018)); + assertNotNull(val); + assertEquals(map().put("map", new UnmodifiableParameterValueProvider().putIntoCopy("year", 2018)), val.asMap()); + + //adding null entry changes nothing + assertSame(val, namedParam.addValueAs(val, null)); + //adding same value changes nothing + assertSame(val, namedParam.addValueAs(val, entry("year", 2018))); + //adding entry value with same key, but different value - no match + assertNull(namedParam.addValueAs(val, entry("year", 1111))); + + ParameterValueProvider val2 = namedParam.addValueAs(val, entry("age", "best")); + assertNotNull(val2); + assertEquals(map().put("map", new UnmodifiableParameterValueProvider() + .putIntoCopy("year", 2018) + .putIntoCopy("age", "best")), val2.asMap()); + + //after all the once returned val is still the same - unmodified + assertEquals(map().put("map", new UnmodifiableParameterValueProvider().putIntoCopy("year", 2018)), val.asMap()); + }; + checker.accept(new NamedItemAccessor("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider()); + checker.accept(new NamedItemAccessor("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider().putIntoCopy("map", null)); + checker.accept(new NamedItemAccessor("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider().putIntoCopy("map", Collections.emptyMap())); + //the map container is detected automatically from the type of value + checker.accept(new NamedItemAccessor("map"), new UnmodifiableParameterValueProvider().putIntoCopy("map", Collections.emptyMap())); + //the map container is detected automatically from the type of value + checker.accept(new NamedItemAccessor("map"), new UnmodifiableParameterValueProvider().putIntoCopy("map", new UnmodifiableParameterValueProvider())); + } + @Test + public void testAddMapIntoParameterByName() { + BiConsumer checker = (namedParam, empty) -> + {//the Map value is added into property of type Map + ParameterValueProvider val = namedParam.addValueAs(empty, Collections.emptyMap()); + assertEquals(map().put("map", new UnmodifiableParameterValueProvider()), val.asMap()); + val = namedParam.addValueAs(empty, map().put("year", 2018)); + assertEquals(map().put("map", new UnmodifiableParameterValueProvider().putIntoCopy("year", 2018)), val.asMap()); + val = namedParam.addValueAs(empty, map().put("year", 2018).put("age", 1111)); + assertEquals(map().put("map", new UnmodifiableParameterValueProvider() + .putIntoCopy("year", 2018) + .putIntoCopy("age", 1111)), val.asMap()); + + //adding null entry changes nothing + assertSame(val, namedParam.addValueAs(val, null)); + //adding same value changes nothing + assertSame(val, namedParam.addValueAs(val, entry("year", 2018))); + //adding entry value with same key, but different value - no match + assertNull(namedParam.addValueAs(val, entry("year", 1111))); + }; + checker.accept(new NamedItemAccessor("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider()); + checker.accept(new NamedItemAccessor("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider().putIntoCopy("map", null)); + checker.accept(new NamedItemAccessor("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider().putIntoCopy("map", Collections.emptyMap())); + //the map container is detected automatically from the type of value + checker.accept(new NamedItemAccessor("map"), new UnmodifiableParameterValueProvider().putIntoCopy("map", Collections.emptyMap())); + //the map container is detected automatically from the type of value + checker.accept(new NamedItemAccessor("map"), new UnmodifiableParameterValueProvider().putIntoCopy("map", new UnmodifiableParameterValueProvider())); + //the map container is detected automatically from the type of new value + checker.accept(new NamedItemAccessor("map"), null); + } + + @Test + public void testAddListIntoParameterByName() { + BiConsumer checker = (namedParam, empty) -> + {//the List value is added into property of type List + ParameterValueProvider val = namedParam.addValueAs(empty, Collections.emptyList()); + assertEquals(map().put("list", Collections.emptyList()), val.asMap()); + val = namedParam.addValueAs(empty, Arrays.asList(2018)); + assertEquals(map().put("list", Arrays.asList(2018)), val.asMap()); + val = namedParam.addValueAs(empty, Arrays.asList(2018, 1111)); + assertEquals(map().put("list", Arrays.asList(2018, 1111)), val.asMap()); + + //adding null entry changes nothing + assertSame(val, namedParam.addValueAs(val, null)); + }; + checker.accept(new NamedItemAccessor("list").setContainerKind(ContainerKind.LIST), new UnmodifiableParameterValueProvider()); + checker.accept(new NamedItemAccessor("list").setContainerKind(ContainerKind.LIST), new UnmodifiableParameterValueProvider().putIntoCopy("list", null)); + checker.accept(new NamedItemAccessor("list").setContainerKind(ContainerKind.LIST), new UnmodifiableParameterValueProvider().putIntoCopy("list", Collections.emptyList())); + //Set can be converted to List + checker.accept(new NamedItemAccessor("list").setContainerKind(ContainerKind.LIST), new UnmodifiableParameterValueProvider().putIntoCopy("list", Collections.emptySet())); + //the list container is detected automatically from the type of value + checker.accept(new NamedItemAccessor("list"), new UnmodifiableParameterValueProvider().putIntoCopy("list", Collections.emptyList())); + //the list container is detected automatically from the type of new value + checker.accept(new NamedItemAccessor("list"), null); + } + @Test + public void testAddSetIntoParameterByName() { + BiConsumer checker = (namedParam, empty) -> + {//the Set value is added into property of type Set + ParameterValueProvider val = namedParam.addValueAs(empty, Collections.emptySet()); + assertEquals(map().put("list", Collections.emptySet()), val.asMap()); + val = namedParam.addValueAs(empty, asSet(2018)); + assertEquals(map().put("list", asSet(2018)), val.asMap()); + val = namedParam.addValueAs(empty, asSet(2018, 1111)); + assertEquals(map().put("list", asSet(2018, 1111)), val.asMap()); + + //adding null entry changes nothing + assertSame(val, namedParam.addValueAs(val, null)); + //adding same entry changes nothing + assertSame(val, namedParam.addValueAs(val, 1111)); + //adding Set with same entry changes nothing + assertSame(val, namedParam.addValueAs(val, asSet(1111))); + //adding Set with same entry changes nothing + assertSame(val, namedParam.addValueAs(val, asSet(2018, 1111))); + }; + checker.accept(new NamedItemAccessor("list").setContainerKind(ContainerKind.SET), new UnmodifiableParameterValueProvider()); + checker.accept(new NamedItemAccessor("list").setContainerKind(ContainerKind.SET), new UnmodifiableParameterValueProvider().putIntoCopy("list", null)); + checker.accept(new NamedItemAccessor("list").setContainerKind(ContainerKind.SET), new UnmodifiableParameterValueProvider().putIntoCopy("list", Collections.emptySet())); + //The container kind has higher priority, so List will be converted to Set + checker.accept(new NamedItemAccessor("list").setContainerKind(ContainerKind.SET), new UnmodifiableParameterValueProvider().putIntoCopy("list", Collections.emptyList())); + //the list container is detected automatically from the type of value + checker.accept(new NamedItemAccessor("list"), new UnmodifiableParameterValueProvider().putIntoCopy("list", Collections.emptySet())); + //the list container is detected automatically from the type of new value + checker.accept(new NamedItemAccessor("list"), null); + } + @Test + public void testFailOnUnpectedContainer() { + ParameterInfo namedParam = new NamedItemAccessor("year").setContainerKind(ContainerKind.LIST); + try { + namedParam.addValueAs(new UnmodifiableParameterValueProvider().putIntoCopy("year", "unexpected"), 1); + fail(); + } catch (Exception e) { + //OK + } + } + + @Test + public void testSetEmptyMap() { + ParameterInfo namedParam = new NamedItemAccessor("year").setContainerKind(ContainerKind.MAP); + {//adding empty Map works + ParameterValueProvider val = namedParam.addValueAs(null, null); + assertNotNull(val); + assertEquals(map().put("year", new UnmodifiableParameterValueProvider()), val.asMap()); + } + } + + private MapBuilder map() { + return new MapBuilder(); + } + + private Map.Entry entry(String key, Object value) { + return new Map.Entry() { + @Override + public Object setValue(Object value) { + throw new RuntimeException(); + } + + @Override + public Object getValue() { + return value; + } + + @Override + public String getKey() { + return key; + } + }; + } + + class MapBuilder extends LinkedHashMap { + public MapBuilder put(String key, Object value) { + super.put(key, value); + return this; + } + } + + private static Set asSet(Object... objects) { + return new HashSet<>(Arrays.asList(objects)); + } +} + diff --git a/src/test/java/spoon/test/template/testclasses/ToBeMatched.java b/src/test/java/spoon/test/template/testclasses/ToBeMatched.java new file mode 100644 index 00000000000..0114219f1f1 --- /dev/null +++ b/src/test/java/spoon/test/template/testclasses/ToBeMatched.java @@ -0,0 +1,25 @@ +package spoon.test.template.testclasses; + +import java.util.Arrays; +import java.util.stream.Collectors; + +import spoon.pattern.Pattern; +import spoon.pattern.PatternBuilder; +import spoon.reflect.factory.Factory; + +public class ToBeMatched { + + public void match1() { + Arrays.asList("a", "b", "c", "d", "e", "f", "a", "b", "c", "d", "e"); + } + + public void match2() { + Arrays.asList("a", "b", "b", "b", "c", "c", "d", "d", "d", "d", "d"); + } + + public static Pattern patternOfStringLiterals(Factory f, String... strs) { + return PatternBuilder.create(f, ToBeMatched.class, ts -> ts.setTemplateModel( + Arrays.asList(strs).stream().map(s -> f.createLiteral(s)).collect(Collectors.toList())) + ).build(); + } +} diff --git a/src/test/java/spoon/test/template/testclasses/bounds/CheckBound.java b/src/test/java/spoon/test/template/testclasses/bounds/CheckBound.java index b8cc9379093..ba5d49b3cd0 100644 --- a/src/test/java/spoon/test/template/testclasses/bounds/CheckBound.java +++ b/src/test/java/spoon/test/template/testclasses/bounds/CheckBound.java @@ -40,4 +40,9 @@ public void bov() { System.out.println(); } + public void bos() { + if (new ArrayList<>().size() == new ArrayList<>().size()) // same expressions + System.out.println(); + } + } \ No newline at end of file diff --git a/src/test/java/spoon/test/template/testclasses/bounds/CheckBoundMatcher.java b/src/test/java/spoon/test/template/testclasses/bounds/CheckBoundMatcher.java index 0d28a23f2b1..444cf21c42a 100644 --- a/src/test/java/spoon/test/template/testclasses/bounds/CheckBoundMatcher.java +++ b/src/test/java/spoon/test/template/testclasses/bounds/CheckBoundMatcher.java @@ -43,6 +43,10 @@ public void matcher5() { public void matcher6() { if (_x_.S() > _y_.S()) { _stmt_.S(); } } + + public void matcher7() { + if (_x_.S() == _x_.S()) _block_.S(); + } /** defines a matcher f* for named templates */ @Parameter diff --git a/src/test/java/spoon/test/template/testclasses/match/Check.java b/src/test/java/spoon/test/template/testclasses/match/Check.java new file mode 100644 index 00000000000..7bd98b07e40 --- /dev/null +++ b/src/test/java/spoon/test/template/testclasses/match/Check.java @@ -0,0 +1,6 @@ +package spoon.test.template.testclasses.match; + +public @interface Check { + long timeout() default 0L; + String value() default "def"; +} \ No newline at end of file diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchForEach.java b/src/test/java/spoon/test/template/testclasses/match/MatchForEach.java new file mode 100644 index 00000000000..6d4aa5d8456 --- /dev/null +++ b/src/test/java/spoon/test/template/testclasses/match/MatchForEach.java @@ -0,0 +1,37 @@ +package spoon.test.template.testclasses.match; + +import java.util.List; + +import spoon.pattern.Pattern; +import spoon.pattern.PatternBuilder; +import spoon.reflect.factory.Factory; +import spoon.reflect.meta.ContainerKind; + +import static java.lang.System.out; + +public class MatchForEach { + + public static Pattern createPattern(Factory factory) { + return PatternBuilder.create(factory, MatchForEach.class, tmb -> tmb.setBodyOfMethod("matcher1")) + .configureParameters(pb -> { + pb.parameter("values").byVariable("values").setContainerKind(ContainerKind.LIST); + }) + .configureLiveStatements(lsb -> lsb.byVariableName("values")) + .build(); + } + + public void matcher1(List values) { + for (String value : values) { + System.out.println(value); + } + } + + public void testMatch1() { + + System.out.println("a"); + out.println("Xxxx"); + System.out.println((String) null); + System.out.println(Long.class.toString()); + } + +} diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchForEach2.java b/src/test/java/spoon/test/template/testclasses/match/MatchForEach2.java new file mode 100644 index 00000000000..cce8b6c35a4 --- /dev/null +++ b/src/test/java/spoon/test/template/testclasses/match/MatchForEach2.java @@ -0,0 +1,44 @@ +package spoon.test.template.testclasses.match; + +import java.util.List; + +import spoon.pattern.Pattern; +import spoon.pattern.PatternBuilder; +import spoon.reflect.factory.Factory; +import spoon.reflect.meta.ContainerKind; + +import static java.lang.System.out; + +public class MatchForEach2 { + + public static Pattern createPattern(Factory factory) { + return PatternBuilder.create(factory, MatchForEach2.class, tmb -> tmb.setBodyOfMethod("matcher1")) + .configureParameters(pb -> { + pb.parameter("values").byVariable("values").setContainerKind(ContainerKind.LIST); + pb.parameter("varName").byString("var"); + }) + .configureLiveStatements(lsb -> lsb.byVariableName("values")) + .build(); + } + + public void matcher1(List values) { + int var = 0; + for (String value : values) { + System.out.println(value); + var++; + } + } + + public void testMatch1() { + System.out.println("a"); + int cc = 0; + out.println("Xxxx"); + cc++; + System.out.println((String) null); + cc++; + int dd = 0; + System.out.println(Long.class.toString()); + dd++; + } + +} diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchIfElse.java b/src/test/java/spoon/test/template/testclasses/match/MatchIfElse.java new file mode 100644 index 00000000000..e237a199268 --- /dev/null +++ b/src/test/java/spoon/test/template/testclasses/match/MatchIfElse.java @@ -0,0 +1,49 @@ +package spoon.test.template.testclasses.match; + +import spoon.pattern.Pattern; +import spoon.pattern.PatternBuilder; +import spoon.reflect.code.CtLiteral; +import spoon.reflect.factory.Factory; +import spoon.reflect.visitor.filter.TypeFilter; + +import static java.lang.System.out; + +public class MatchIfElse { + + public static Pattern createPattern(Factory factory) { + return PatternBuilder.create(factory, MatchIfElse.class, tmb -> tmb.setBodyOfMethod("matcher1")) + .configureParameters(pb -> { + pb.parameter("option").byVariable("option"); + pb.parameter("option2").byVariable("option2"); + pb.parameter("value").byFilter(new TypeFilter(CtLiteral.class)); + }) + .configureLiveStatements(lsb -> lsb.byVariableName("option")) + .build(); + } + + public void matcher1(boolean option, boolean option2) { + if (option) { + //matches String argument + System.out.println("string"); + } else if (option2) { + //matches int argument + System.out.println(1); + } else { + //matches double argument + System.out.println(4.5); + } + } + + public void testMatch1() { + int i = 0; + System.out.println(i); + System.out.println("a"); + out.println("Xxxx"); + System.out.println((String) null); + System.out.println((Integer) null); + System.out.println(2018); + System.out.println(Long.class.toString()); + System.out.println(3.14); + } + +} diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchMap.java b/src/test/java/spoon/test/template/testclasses/match/MatchMap.java new file mode 100644 index 00000000000..4e284d95ad3 --- /dev/null +++ b/src/test/java/spoon/test/template/testclasses/match/MatchMap.java @@ -0,0 +1,55 @@ +package spoon.test.template.testclasses.match; + +import spoon.pattern.Pattern; +import spoon.pattern.PatternBuilder; +import spoon.reflect.declaration.CtAnnotation; +import spoon.reflect.declaration.CtMethod; +import spoon.reflect.factory.Factory; +import spoon.reflect.meta.ContainerKind; +import spoon.reflect.path.CtRole; +import spoon.reflect.visitor.filter.TypeFilter; + +public class MatchMap { + + public static Pattern createPattern(Factory factory, boolean acceptOtherAnnotations) { + return PatternBuilder.create(factory, MatchMap.class, tmb -> tmb.setTypeMember("matcher1")) + .configureParameters(pb -> { + //match any value of @Check annotation to parameter `testAnnotations` + pb.parameter("testAnnotations").attributeOfElementByFilter(CtRole.VALUE, new TypeFilter(CtAnnotation.class)).setContainerKind(ContainerKind.MAP); +// pb.parameter("testAnnotations").attributeOfElementByFilter(CtRole.VALUE, new TypeFilter(CtAnnotation.class)); + //match any method name + pb.parameter("methodName").byString("matcher1"); + if (acceptOtherAnnotations) { + //match on all annotations of method + pb.parameter("allAnnotations").attributeOfElementByFilter(CtRole.ANNOTATION, new TypeFilter<>(CtMethod.class)); + } + }) + .build(); + } + + @Check() + void matcher1() { + } + + @Check(value = "xyz") + void m1() { + } + + @Check(timeout = 123, value = "abc") + void m2() { + } + + @Deprecated + void notATestAnnotation() { + } + + @Deprecated + @Check(timeout = 456) + void deprecatedTestAnnotation1() { + } + + @Check(timeout = 4567) + @Deprecated + void deprecatedTestAnnotation2() { + } +} diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchModifiers.java b/src/test/java/spoon/test/template/testclasses/match/MatchModifiers.java new file mode 100644 index 00000000000..e6d605bf7f7 --- /dev/null +++ b/src/test/java/spoon/test/template/testclasses/match/MatchModifiers.java @@ -0,0 +1,42 @@ +package spoon.test.template.testclasses.match; + +import spoon.pattern.Pattern; +import spoon.pattern.PatternBuilder; +import spoon.reflect.code.CtBlock; +import spoon.reflect.declaration.CtMethod; +import spoon.reflect.factory.Factory; +import spoon.reflect.path.CtRole; +import spoon.reflect.visitor.filter.TypeFilter; + +public class MatchModifiers { + + public static Pattern createPattern(Factory factory, boolean matchBody) { + return PatternBuilder.create(factory, MatchModifiers.class, tmb -> tmb.setTypeMember("matcher1")) + .configureParameters(pb -> { + pb.parameter("modifiers").attributeOfElementByFilter(CtRole.MODIFIER, new TypeFilter(CtMethod.class)); + pb.parameter("methodName").byString("matcher1"); + pb.parameter("parameters").attributeOfElementByFilter(CtRole.PARAMETER, new TypeFilter(CtMethod.class)); + if (matchBody) { + pb.parameter("statements").attributeOfElementByFilter(CtRole.STATEMENT, new TypeFilter(CtBlock.class)); + } + }) + .build(); + } + + + public void matcher1() { + } + + public static void publicStaticMethod() { + } + + void packageProtectedMethodWithParam(int a, MatchModifiers me) { + } + + private void withBody() { + this.getClass(); + System.out.println(); + } + + int noMatchBecauseReturnsInt() {return 0;} +} diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple.java b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple.java new file mode 100644 index 00000000000..a0133e6d98b --- /dev/null +++ b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple.java @@ -0,0 +1,48 @@ +package spoon.test.template.testclasses.match; + +import spoon.pattern.Pattern; +import spoon.pattern.PatternBuilder; +import spoon.pattern.matcher.Quantifier; +import spoon.reflect.code.CtLiteral; +import spoon.reflect.factory.Factory; +import spoon.reflect.meta.ContainerKind; + +import static java.lang.System.out; + +public class MatchMultiple { + + public static Pattern createPattern(Factory factory, Quantifier matchingStrategy, Integer minCount, Integer maxCount) { + return PatternBuilder.create(factory, MatchMultiple.class, tmb -> tmb.setBodyOfMethod("matcher1")) + .configureParameters(pb -> { + pb.parameter("statements").bySimpleName("statements").setContainerKind(ContainerKind.LIST); + if (matchingStrategy != null) { + pb.setMatchingStrategy(matchingStrategy); + } + if (minCount != null) { + pb.setMinOccurence(minCount); + } + if (maxCount != null) { + pb.setMaxOccurence(maxCount); + } + pb.parameter("printedValue").byFilter((CtLiteral literal) -> "something".equals(literal.getValue())); + }) + .build(); + } + + public void matcher1() { + statements(); + System.out.println("something"); + } + + void statements() {} + + public void testMatch1() { + int i = 0; + i++; + System.out.println(i); + out.println("Xxxx"); + System.out.println((String) null); + System.out.println("last one"); + } + +} diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple2.java b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple2.java new file mode 100644 index 00000000000..f6d00950e3c --- /dev/null +++ b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple2.java @@ -0,0 +1,52 @@ +package spoon.test.template.testclasses.match; + +import spoon.pattern.ParametersBuilder; +import spoon.pattern.Pattern; +import spoon.pattern.PatternBuilder; +import spoon.reflect.factory.Factory; +import spoon.reflect.meta.ContainerKind; +import spoon.template.TemplateParameter; + +import static java.lang.System.out; + +import java.util.List; +import java.util.function.Consumer; + +public class MatchMultiple2 { + + public static Pattern createPattern(Factory factory, Consumer cfgParams) { + return PatternBuilder.create(factory, MatchMultiple2.class, tmb -> tmb.setBodyOfMethod("matcher1")) + .configureTemplateParameters() + .configureParameters(pb -> { + pb.parameter("statements1").setContainerKind(ContainerKind.LIST); + pb.parameter("statements2").setContainerKind(ContainerKind.LIST); + pb.parameter("printedValue").byVariable("something"); + cfgParams.accept(pb); + }) + .configureLiveStatements(ls -> { + ls.byVariableName("something"); + }) + .build(); + } + + public void matcher1(List something) { + statements1.S(); + statements2.S(); + for (String v : something) { + System.out.println(v); + } + } + + TemplateParameter statements1; + TemplateParameter statements2; + + public void testMatch1() { + int i = 0; + i++; + System.out.println(i); + out.println("Xxxx"); + System.out.println((String) null); + System.out.println("last one"); + } + +} diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple3.java b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple3.java new file mode 100644 index 00000000000..744f482a0bd --- /dev/null +++ b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple3.java @@ -0,0 +1,47 @@ +package spoon.test.template.testclasses.match; + +import spoon.pattern.ParametersBuilder; +import spoon.pattern.Pattern; +import spoon.pattern.PatternBuilder; +import spoon.reflect.code.CtLiteral; +import spoon.reflect.factory.Factory; +import spoon.reflect.meta.ContainerKind; +import spoon.template.TemplateParameter; + +import static java.lang.System.out; + +import java.util.function.Consumer; + +public class MatchMultiple3 { + + public static Pattern createPattern(Factory factory, Consumer cfgParams) { + return PatternBuilder.create(factory, MatchMultiple3.class, tmb -> tmb.setBodyOfMethod("matcher1")) + .configureTemplateParameters() + .configureParameters(pb -> { + pb.parameter("statements1").setContainerKind(ContainerKind.LIST); + pb.parameter("statements2").setContainerKind(ContainerKind.LIST); + pb.parameter("printedValue").byFilter((CtLiteral literal) -> "something".equals(literal.getValue())); + cfgParams.accept(pb); + }) + .build(); + } + + public void matcher1() { + statements1.S(); + statements2.S(); + System.out.println("something"); + } + + TemplateParameter statements1; + TemplateParameter statements2; + + public void testMatch1() { + int i = 0; + i++; + System.out.println(i); + out.println("Xxxx"); + System.out.println((String) null); + System.out.println("last one"); + } + +} diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterCondition.java b/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterCondition.java new file mode 100644 index 00000000000..add154ce25b --- /dev/null +++ b/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterCondition.java @@ -0,0 +1,36 @@ +package spoon.test.template.testclasses.match; + +import spoon.pattern.Pattern; +import spoon.pattern.PatternBuilder; +import spoon.reflect.factory.Factory; +import static java.lang.System.out; + +import java.util.function.Predicate; + +public class MatchWithParameterCondition { + + public static Pattern createPattern(Factory factory, Predicate condition) { + return PatternBuilder.create(factory, MatchWithParameterCondition.class, tmb -> tmb.setBodyOfMethod("matcher1")) + .configureParameters(pb -> { + pb.parameter("value").byVariable("value"); + if (condition != null) { + pb.matchCondition(null, condition); + } + }) + .configureLiveStatements(lsb -> lsb.byVariableName("values")) + .build(); + } + + public void matcher1(String value) { + System.out.println(value); + } + + public void testMatch1() { + + System.out.println("a"); + out.println("Xxxx"); + System.out.println((String) null); + System.out.println(Long.class.toString()); + } + +} diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterType.java b/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterType.java new file mode 100644 index 00000000000..c18ea363fcb --- /dev/null +++ b/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterType.java @@ -0,0 +1,34 @@ +package spoon.test.template.testclasses.match; + +import spoon.pattern.Pattern; +import spoon.pattern.PatternBuilder; +import spoon.reflect.factory.Factory; +import static java.lang.System.out; + +public class MatchWithParameterType { + + public static Pattern createPattern(Factory factory, Class valueType) { + return PatternBuilder.create(factory, MatchWithParameterType.class, tmb -> tmb.setBodyOfMethod("matcher1")) + .configureParameters(pb -> { + pb.parameter("value").byVariable("value"); + if (valueType != null) { + pb.setValueType(valueType); + } + }) + .configureLiveStatements(lsb -> lsb.byVariableName("values")) + .build(); + } + + public void matcher1(String value) { + System.out.println(value); + } + + public void testMatch1() { + + System.out.println("a"); + out.println("Xxxx"); + System.out.println((String) null); + System.out.println(Long.class.toString()); + } + +} diff --git a/src/test/java/spoon/test/template/testclasses/replace/DPPSample1.java b/src/test/java/spoon/test/template/testclasses/replace/DPPSample1.java new file mode 100644 index 00000000000..a1bc706341d --- /dev/null +++ b/src/test/java/spoon/test/template/testclasses/replace/DPPSample1.java @@ -0,0 +1,34 @@ +package spoon.test.template.testclasses.replace; + +import spoon.reflect.declaration.CtEnum; +import spoon.reflect.declaration.CtEnumValue; +import spoon.reflect.visitor.ElementPrinterHelper; +import spoon.reflect.visitor.TokenWriter; + +public class DPPSample1 { + private ElementPrinterHelper elementPrinterHelper; + private TokenWriter printer; + + public > void method1(CtEnum ctEnum) { + printer.writeSpace().writeKeyword("extends").writeSpace(); + try (spoon.reflect.visitor.ListPrinter lp = elementPrinterHelper.createListPrinter(false, null, false, false, ",", true, false, ";")) { + for (spoon.reflect.declaration.CtEnumValue enumValue : ctEnum.getEnumValues()) { + lp.printSeparatorIfAppropriate(); + scan(enumValue); + } + } + } + + public > void method2(CtEnum ctEnum) { + try (spoon.reflect.visitor.ListPrinter lp = elementPrinterHelper.createListPrinter(false, null, false, false, ",", true, false, ";")) { + for (spoon.reflect.declaration.CtEnumValue enumValue : ctEnum.getEnumValues()) { + lp.printSeparatorIfAppropriate(); + scan(enumValue); + } + } + } + + private void scan(CtEnumValue enumValue) { + } + +} diff --git a/src/test/java/spoon/test/template/testclasses/replace/Item.java b/src/test/java/spoon/test/template/testclasses/replace/Item.java new file mode 100644 index 00000000000..f8c88837ab7 --- /dev/null +++ b/src/test/java/spoon/test/template/testclasses/replace/Item.java @@ -0,0 +1,4 @@ +package spoon.test.template.testclasses.replace; + +interface Item { +} \ No newline at end of file diff --git a/src/test/java/spoon/test/template/testclasses/replace/NewPattern.java b/src/test/java/spoon/test/template/testclasses/replace/NewPattern.java new file mode 100644 index 00000000000..fb60a10db07 --- /dev/null +++ b/src/test/java/spoon/test/template/testclasses/replace/NewPattern.java @@ -0,0 +1,81 @@ +package spoon.test.template.testclasses.replace; + +import java.util.function.Consumer; + +import spoon.pattern.Pattern; +import spoon.pattern.PatternBuilder; +import spoon.reflect.declaration.CtElement; +import spoon.reflect.declaration.CtType; +import spoon.reflect.factory.Factory; +import spoon.reflect.meta.ContainerKind; +import spoon.reflect.meta.RoleHandler; +import spoon.reflect.meta.impl.RoleHandlerHelper; + +public class NewPattern { + + /** + * The body of this method contains a model of transformed code + */ + private void patternModel(OldPattern.Parameters params) throws Exception { + elementPrinterHelper.printList(params.getIterable.S(), + params.startPrefixSpace, + params.start, + params.startSuffixSpace, + params.nextPrefixSpace, + params.next, + params.nextSuffixSpace, + params.endPrefixSpace, + params.end, + v -> { + params.statements.S(); + }); + } + + /** + * Creates a Pattern for this model + */ + public static Pattern createPattern(Factory factory) { + return PatternBuilder + .create(factory, NewPattern.class, model->model.setBodyOfMethod("patternModel")) + .configureAutomaticParameters() + .configureParameters(pb -> { + pb.parameter("statements").setContainerKind(ContainerKind.LIST); + }) + .build(); + } + + /** + * Looks for all matches of Pattern defined by `OldPattern_ParamsInNestedType.class` + * and replaces each match with code generated by Pattern defined by `NewPattern.class` + * @param rootElement the root element of AST whose children has to be transformed + */ + @SuppressWarnings("unchecked") + public static void replaceOldByNew(CtElement rootElement) { + CtType targetType = (rootElement instanceof CtType) ? (CtType) rootElement : rootElement.getParent(CtType.class); + Factory f = rootElement.getFactory(); + Pattern newPattern = NewPattern.createPattern(f); + Pattern oldPattern = OldPattern.createPattern(f); + oldPattern.forEachMatch(rootElement, (match) -> { + RoleHandler rh = RoleHandlerHelper.getRoleHandlerWrtParent(match.getMatchingElement(CtElement.class, false)); + match.replaceMatchesBy(newPattern.applyToType(targetType, (Class) rh.getValueClass(), match.getParametersMap())); + }); + } + + /* + * Helper type members + */ + + private ElementPrinterHelper elementPrinterHelper; + + interface Entity { + Iterable $getItems$(); + } + + interface ElementPrinterHelper { + void printList(Iterable $getItems$, + boolean startPrefixSpace, String start, boolean startSufficSpace, + boolean nextPrefixSpace, String next, boolean nextSuffixSpace, + boolean endPrefixSpace, String end, + Consumer consumer); + } +} diff --git a/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java b/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java new file mode 100644 index 00000000000..185b1b5c289 --- /dev/null +++ b/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java @@ -0,0 +1,78 @@ +package spoon.test.template.testclasses.replace; + +import spoon.pattern.Pattern; +import spoon.pattern.PatternBuilder; +import spoon.reflect.code.CtBlock; +import spoon.reflect.code.CtInvocation; +import spoon.reflect.factory.Factory; +import spoon.reflect.meta.ContainerKind; +import spoon.reflect.reference.CtTypeReference; +import spoon.reflect.visitor.ElementPrinterHelper; +import spoon.reflect.visitor.TokenWriter; + +public class OldPattern { + + //Here are parameters this Pattern + public static class Parameters { + public boolean useStartKeyword; + + public String startKeyword; + public boolean startPrefixSpace; + public String start; + public boolean startSuffixSpace; + public boolean nextPrefixSpace; + public String next; + public boolean nextSuffixSpace; + public boolean endPrefixSpace; + public String end; + + public CtBlock statements; + + public CtTypeReference entityType; + public CtTypeReference itemType; + + public CtInvocation> getIterable; + } + + void patternModel(Parameters params) throws Exception { + //This is a model of this Pattern + if(params.useStartKeyword) { + printer.writeSpace().writeKeyword(params.startKeyword).writeSpace(); + } + try (spoon.reflect.visitor.ListPrinter lp = elementPrinterHelper.createListPrinter( + params.startPrefixSpace, + params.start, + params.startSuffixSpace, + params.nextPrefixSpace, + params.next, + params.nextSuffixSpace, + params.endPrefixSpace, + params.end + )) { + for (Item item : params.getIterable.S()) { + lp.printSeparatorIfAppropriate(); + params.statements.S(); + } + } + } + + /** + * @param factory a to be used factory + * @return a Pattern instance of this Pattern + */ + public static Pattern createPattern(Factory factory) { + return PatternBuilder + //Create a pattern from all statements of OldPattern_ParamsInNestedType#patternModel + .create(factory, OldPattern.class, model->model.setBodyOfMethod("patternModel")) + .configureParameters(pb->pb + .parametersByVariable("params", "item") + .parameter("statements").setContainerKind(ContainerKind.LIST) + ) + .configureAutomaticParameters() + .configureLiveStatements(ls -> ls.byVariableName("useStartKeyword")) + .build(); + } + + private ElementPrinterHelper elementPrinterHelper; + private TokenWriter printer; +} From 40d9f01651d3def4e2f1f315240fd54b1c5e1686 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Wed, 7 Mar 2018 10:38:57 +0100 Subject: [PATCH 004/131] rename MMType, MMField - pattern --- src/main/java/spoon/pattern/PatternBuilder.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index 7f8cc99c1ab..4c069d69399 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -164,13 +164,13 @@ private Node createImplicitNode(Object object) { if (object instanceof CtElement) { //it is a spoon element CtElement element = (CtElement) object; - Metamodel.Type mmType = Metamodel.getMetamodelTypeByClass(element.getClass()); - ElementNode elementNode = new ElementNode(mmType); + Metamodel.Type mmConcept = Metamodel.getMetamodelTypeByClass(element.getClass()); + ElementNode elementNode = new ElementNode(mmConcept); if (patternElementToSubstRequests.put(element, elementNode) != null) { throw new SpoonException("Each pattern element can have only one implicit Node."); } //iterate over all attributes of that element - for (Metamodel.Field mmField : mmType.getFields()) { + for (Metamodel.Field mmField : mmConcept.getFields()) { if (mmField.isDerived() || IGNORED_ROLES.contains(mmField.getRole())) { //skip derived fields, they are not relevant for matching or generating continue; From 25b3fc1d9a0fc09c4cc349995d953eca7e707fdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Wed, 7 Mar 2018 13:18:17 +0100 Subject: [PATCH 005/131] Factory is available during whole generation process --- .../spoon/pattern/AbstractItemAccessor.java | 17 +++++++++++------ src/main/java/spoon/pattern/ConstantNode.java | 6 ++++++ src/main/java/spoon/pattern/ParameterInfo.java | 3 ++- src/main/java/spoon/pattern/ParameterNode.java | 2 +- src/main/java/spoon/pattern/PatternBuilder.java | 2 +- src/main/java/spoon/pattern/StringNode.java | 2 +- src/main/java/spoon/pattern/ValueConvertor.java | 4 +++- .../java/spoon/pattern/ValueConvertorImpl.java | 13 +++---------- 8 files changed, 28 insertions(+), 21 deletions(-) diff --git a/src/main/java/spoon/pattern/AbstractItemAccessor.java b/src/main/java/spoon/pattern/AbstractItemAccessor.java index 6a93ad50023..d314802d150 100644 --- a/src/main/java/spoon/pattern/AbstractItemAccessor.java +++ b/src/main/java/spoon/pattern/AbstractItemAccessor.java @@ -27,6 +27,7 @@ import spoon.SpoonException; import spoon.pattern.matcher.Quantifier; import spoon.reflect.code.CtBlock; +import spoon.reflect.factory.Factory; import spoon.reflect.meta.ContainerKind; import spoon.reflect.reference.CtTypeReference; @@ -375,7 +376,7 @@ protected ContainerKind getContainerKind(Object existingValue, Object value) { } @Override - public void getValueAs(ResultHolder result, ParameterValueProvider parameters) { + public void getValueAs(Factory factory, ResultHolder result, ParameterValueProvider parameters) { //get raw parameter value Object rawValue = getValue(parameters); if (isMultiple() && rawValue instanceof CtBlock) { @@ -385,7 +386,7 @@ public void getValueAs(ResultHolder result, ParameterValueProvider parame */ rawValue = ((CtBlock) rawValue).getStatements(); } - convertValue(result, rawValue); + convertValue(factory, result, rawValue); } protected Object getValue(ParameterValueProvider parameters) { @@ -395,22 +396,26 @@ protected Object getValue(ParameterValueProvider parameters) { return parameters; } - protected void convertValue(ResultHolder result, Object rawValue) { - ValueConvertor valueConvertor = getValueConvertor(); + protected void convertValue(Factory factory, ResultHolder result, Object rawValue) { //convert raw parameter value to expected type if (result.isMultiple()) { AbstractPrimitiveMatcher.forEachItem(rawValue, singleValue -> { - T convertedValue = (T) valueConvertor.getValueAs(singleValue, result.getRequiredClass()); + T convertedValue = convertSingleValue(factory, singleValue, result.getRequiredClass()); if (convertedValue != null) { result.addResult(convertedValue); } }); } else { //single value converts arrays in rawValues into single value - result.addResult((T) valueConvertor.getValueAs(rawValue, result.getRequiredClass())); + result.addResult(convertSingleValue(factory, rawValue, result.getRequiredClass())); } } + protected T convertSingleValue(Factory factory, Object value, Class type) { + ValueConvertor valueConvertor = getValueConvertor(); + return (T) valueConvertor.getValueAs(factory, value, type); + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); diff --git a/src/main/java/spoon/pattern/ConstantNode.java b/src/main/java/spoon/pattern/ConstantNode.java index 33b1f7615e0..b7112716d8f 100644 --- a/src/main/java/spoon/pattern/ConstantNode.java +++ b/src/main/java/spoon/pattern/ConstantNode.java @@ -63,4 +63,10 @@ public ParameterValueProvider matchTarget(Object target, ParameterValueProvider } return target.equals(template) ? parameters : null; } + + @Override + public String toString() { + return String.valueOf(template); + } } + diff --git a/src/main/java/spoon/pattern/ParameterInfo.java b/src/main/java/spoon/pattern/ParameterInfo.java index 5eb3eca1e33..fa4ab559782 100644 --- a/src/main/java/spoon/pattern/ParameterInfo.java +++ b/src/main/java/spoon/pattern/ParameterInfo.java @@ -17,6 +17,7 @@ package spoon.pattern; import spoon.pattern.matcher.Quantifier; +import spoon.reflect.factory.Factory; /** * Represents the parameter of {@link Pattern} @@ -41,7 +42,7 @@ public interface ParameterInfo { */ ParameterValueProvider addValueAs(ParameterValueProvider parameters, Object value); - void getValueAs(ResultHolder result, ParameterValueProvider parameters); + void getValueAs(Factory factory, ResultHolder result, ParameterValueProvider parameters); /** * @return true if the value container has to be a List, otherwise the container will be a single value diff --git a/src/main/java/spoon/pattern/ParameterNode.java b/src/main/java/spoon/pattern/ParameterNode.java index 44e8b33685e..957216ffdaf 100644 --- a/src/main/java/spoon/pattern/ParameterNode.java +++ b/src/main/java/spoon/pattern/ParameterNode.java @@ -42,7 +42,7 @@ public boolean replaceNode(Node oldNode, Node newNode) { @Override public void generateTargets(Factory factory, ResultHolder result, ParameterValueProvider parameters) { - parameterInfo.getValueAs(result, parameters); + parameterInfo.getValueAs(factory, result, parameters); } @Override diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index 4c069d69399..a255d54a549 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -142,7 +142,7 @@ private PatternBuilder(CtTypeReference templateTypeRef, List templ throw new SpoonException("Cannot create a Pattern from an null model"); } this.factory = templateTypeRef.getFactory(); - this.valueConvertor = new ValueConvertorImpl(factory); + this.valueConvertor = new ValueConvertorImpl(); patternNodes = new ListOfNodes(createImplicitNodes(template)); patternQuery = new PatternBuilder.PatternQuery(factory.Query(), patternModel); configureParameters(pb -> { diff --git a/src/main/java/spoon/pattern/StringNode.java b/src/main/java/spoon/pattern/StringNode.java index 05425356118..bf4fe4f741f 100644 --- a/src/main/java/spoon/pattern/StringNode.java +++ b/src/main/java/spoon/pattern/StringNode.java @@ -65,7 +65,7 @@ public void generateTargets(Factory factory, ResultHolder result, Paramet ParameterInfo param = requests.getValue(); String replaceMarker = requests.getKey(); ResultHolder.Single ctx = new ResultHolder.Single<>(String.class); - param.getValueAs(ctx, parameters); + param.getValueAs(factory, ctx, parameters); String substrValue = ctx.getResult() == null ? "" : ctx.getResult(); stringValue = substituteSubstring(stringValue, replaceMarker, substrValue); } diff --git a/src/main/java/spoon/pattern/ValueConvertor.java b/src/main/java/spoon/pattern/ValueConvertor.java index 629f71f9fbf..4b8980ab79e 100644 --- a/src/main/java/spoon/pattern/ValueConvertor.java +++ b/src/main/java/spoon/pattern/ValueConvertor.java @@ -16,10 +16,12 @@ */ package spoon.pattern; +import spoon.reflect.factory.Factory; + /** * Converts the individual parameter values to required type after substitution * Converts the matching model values to parameter values during matching process */ public interface ValueConvertor { - T getValueAs(Object value, Class valueClass); + T getValueAs(Factory factory, Object value, Class valueClass); } diff --git a/src/main/java/spoon/pattern/ValueConvertorImpl.java b/src/main/java/spoon/pattern/ValueConvertorImpl.java index 16dfd5e8bd8..b1a464194ca 100644 --- a/src/main/java/spoon/pattern/ValueConvertorImpl.java +++ b/src/main/java/spoon/pattern/ValueConvertorImpl.java @@ -40,14 +40,11 @@ */ public class ValueConvertorImpl implements ValueConvertor { - private final Factory factory; - - public ValueConvertorImpl(Factory factory) { - this.factory = factory; + public ValueConvertorImpl() { } @Override - public T getValueAs(Object value, Class valueClass) { + public T getValueAs(Factory factory, Object value, Class valueClass) { if (valueClass.isInstance(value)) { return cloneIfNeeded(valueClass.cast(value)); } @@ -99,7 +96,7 @@ public T getValueAs(Object value, Class valueClass) { if (list.size() == 1) { return (T) list.get(0); } - CtBlock block = getFactory().createBlock(); + CtBlock block = factory.createBlock(); block.setImplicit(true); for (CtStatement statement : ((Iterable) value)) { block.addStatement(statement); @@ -164,8 +161,4 @@ protected T cloneIfNeeded(T value) { } return value; } - - public Factory getFactory() { - return factory; - } } From 967f4059a97592c1dbbb2b3348ec985dc0b8668e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Wed, 7 Mar 2018 14:13:21 +0100 Subject: [PATCH 006/131] move parameter classes to own package --- .../pattern/AbstractPrimitiveMatcher.java | 32 ----- src/main/java/spoon/pattern/ConstantNode.java | 2 + src/main/java/spoon/pattern/ElementNode.java | 2 + src/main/java/spoon/pattern/ForEachNode.java | 2 + src/main/java/spoon/pattern/ListOfNodes.java | 2 + src/main/java/spoon/pattern/Node.java | 2 + .../java/spoon/pattern/ParameterNode.java | 2 + .../java/spoon/pattern/ParametersBuilder.java | 4 + src/main/java/spoon/pattern/Pattern.java | 3 + .../java/spoon/pattern/PatternBuilder.java | 2 + .../java/spoon/pattern/PatternPrinter.java | 112 ++++++++++++++++++ .../java/spoon/pattern/PrimitiveMatcher.java | 2 + .../java/spoon/pattern/RepeatableMatcher.java | 1 + src/main/java/spoon/pattern/StringNode.java | 2 + src/main/java/spoon/pattern/SwitchNode.java | 2 + .../UnmodifiableParameterValueProvider.java | 3 + .../spoon/pattern/matcher/MapEntryNode.java | 4 +- .../java/spoon/pattern/matcher/Match.java | 2 +- .../pattern/matcher/MatchingScanner.java | 2 +- .../pattern/matcher/NodeListMatcher.java | 2 +- .../spoon/pattern/matcher/TobeMatched.java | 2 +- .../{ => parameter}/AbstractItemAccessor.java | 41 ++++++- .../pattern/{ => parameter}/ListAccessor.java | 2 +- .../{ => parameter}/NamedItemAccessor.java | 4 +- .../{ => parameter}/ParameterInfo.java | 5 +- .../ParameterValueProvider.java | 2 +- .../ParameterValueProviderFactory.java | 2 +- .../pattern/{ => parameter}/SetAccessor.java | 2 +- .../java/spoon/template/TemplateMatcher.java | 4 +- .../spoon/test/template/CodeReplaceTest.java | 2 +- .../java/spoon/test/template/PatternTest.java | 3 +- .../test/template/TemplateMatcherTest.java | 2 +- .../spoon/test/template/TemplateTest.java | 2 +- .../test/template/core/ParameterInfoTest.java | 10 +- 34 files changed, 208 insertions(+), 60 deletions(-) create mode 100644 src/main/java/spoon/pattern/PatternPrinter.java rename src/main/java/spoon/pattern/{ => parameter}/AbstractItemAccessor.java (90%) rename src/main/java/spoon/pattern/{ => parameter}/ListAccessor.java (98%) rename src/main/java/spoon/pattern/{ => parameter}/NamedItemAccessor.java (97%) rename src/main/java/spoon/pattern/{ => parameter}/ParameterInfo.java (96%) rename src/main/java/spoon/pattern/{ => parameter}/ParameterValueProvider.java (98%) rename src/main/java/spoon/pattern/{ => parameter}/ParameterValueProviderFactory.java (96%) rename src/main/java/spoon/pattern/{ => parameter}/SetAccessor.java (98%) diff --git a/src/main/java/spoon/pattern/AbstractPrimitiveMatcher.java b/src/main/java/spoon/pattern/AbstractPrimitiveMatcher.java index d7bee78e0a8..ae2834efd55 100644 --- a/src/main/java/spoon/pattern/AbstractPrimitiveMatcher.java +++ b/src/main/java/spoon/pattern/AbstractPrimitiveMatcher.java @@ -16,10 +16,7 @@ */ package spoon.pattern; -import java.util.function.Consumer; - import spoon.pattern.matcher.TobeMatched; -import spoon.reflect.code.CtStatementList; /** * Delivers to be substituted value @@ -31,35 +28,6 @@ protected AbstractPrimitiveMatcher() { } - /** - * calls consumer.accept(Object) once for each item of the `multipleValues` collection or array. - * If it is not a collection or array then it calls consumer.accept(Object) once with `multipleValues` - * If `multipleValues` is null then consumer.accept(Object) is not called - * @param multipleValues to be iterated potential collection of items - * @param consumer the receiver of items - */ - @SuppressWarnings("unchecked") - static void forEachItem(Object multipleValues, Consumer consumer) { - if (multipleValues instanceof CtStatementList) { - //CtStatementList extends Iterable, but we want to handle it as one node. - consumer.accept(multipleValues); - return; - } - if (multipleValues instanceof Iterable) { - for (Object item : (Iterable) multipleValues) { - consumer.accept(item); - } - return; - } - if (multipleValues instanceof Object[]) { - for (Object item : (Object[]) multipleValues) { - consumer.accept(item); - } - return; - } - consumer.accept(multipleValues); - } - @Override public TobeMatched matchAllWith(TobeMatched tobeMatched) { //we are matching single CtElement or attribute value diff --git a/src/main/java/spoon/pattern/ConstantNode.java b/src/main/java/spoon/pattern/ConstantNode.java index b7112716d8f..2a28543a3ce 100644 --- a/src/main/java/spoon/pattern/ConstantNode.java +++ b/src/main/java/spoon/pattern/ConstantNode.java @@ -18,6 +18,8 @@ import java.util.function.BiConsumer; +import spoon.pattern.parameter.ParameterInfo; +import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.factory.Factory; /** diff --git a/src/main/java/spoon/pattern/ElementNode.java b/src/main/java/spoon/pattern/ElementNode.java index 6c35d48d4ba..b1949877df1 100644 --- a/src/main/java/spoon/pattern/ElementNode.java +++ b/src/main/java/spoon/pattern/ElementNode.java @@ -27,6 +27,8 @@ import spoon.Metamodel; import spoon.SpoonException; import spoon.pattern.matcher.TobeMatched; +import spoon.pattern.parameter.ParameterInfo; +import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.declaration.CtElement; import spoon.reflect.factory.Factory; import spoon.reflect.meta.ContainerKind; diff --git a/src/main/java/spoon/pattern/ForEachNode.java b/src/main/java/spoon/pattern/ForEachNode.java index 7f2c325deaa..b7feff608db 100644 --- a/src/main/java/spoon/pattern/ForEachNode.java +++ b/src/main/java/spoon/pattern/ForEachNode.java @@ -21,6 +21,8 @@ import spoon.pattern.matcher.Quantifier; import spoon.pattern.matcher.TobeMatched; +import spoon.pattern.parameter.ParameterInfo; +import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.factory.Factory; /** diff --git a/src/main/java/spoon/pattern/ListOfNodes.java b/src/main/java/spoon/pattern/ListOfNodes.java index 9b4f1eeda50..14f739441e9 100644 --- a/src/main/java/spoon/pattern/ListOfNodes.java +++ b/src/main/java/spoon/pattern/ListOfNodes.java @@ -22,6 +22,8 @@ import spoon.pattern.matcher.ChainOfMatchersImpl; import spoon.pattern.matcher.Matchers; import spoon.pattern.matcher.TobeMatched; +import spoon.pattern.parameter.ParameterInfo; +import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.factory.Factory; /** diff --git a/src/main/java/spoon/pattern/Node.java b/src/main/java/spoon/pattern/Node.java index 93d535169ce..810cb06cb54 100644 --- a/src/main/java/spoon/pattern/Node.java +++ b/src/main/java/spoon/pattern/Node.java @@ -21,6 +21,8 @@ import spoon.pattern.matcher.Matchers; import spoon.pattern.matcher.TobeMatched; +import spoon.pattern.parameter.ParameterInfo; +import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.factory.Factory; /** diff --git a/src/main/java/spoon/pattern/ParameterNode.java b/src/main/java/spoon/pattern/ParameterNode.java index 957216ffdaf..04abdaf3336 100644 --- a/src/main/java/spoon/pattern/ParameterNode.java +++ b/src/main/java/spoon/pattern/ParameterNode.java @@ -19,6 +19,8 @@ import java.util.function.BiConsumer; import spoon.pattern.matcher.Quantifier; +import spoon.pattern.parameter.ParameterInfo; +import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.declaration.CtElement; import spoon.reflect.factory.Factory; diff --git a/src/main/java/spoon/pattern/ParametersBuilder.java b/src/main/java/spoon/pattern/ParametersBuilder.java index 017f40ec29b..42e76f61be8 100644 --- a/src/main/java/spoon/pattern/ParametersBuilder.java +++ b/src/main/java/spoon/pattern/ParametersBuilder.java @@ -23,6 +23,10 @@ import spoon.SpoonException; import spoon.pattern.matcher.Quantifier; +import spoon.pattern.parameter.AbstractItemAccessor; +import spoon.pattern.parameter.ListAccessor; +import spoon.pattern.parameter.NamedItemAccessor; +import spoon.pattern.parameter.ParameterInfo; import spoon.reflect.code.CtArrayAccess; import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtExpression; diff --git a/src/main/java/spoon/pattern/Pattern.java b/src/main/java/spoon/pattern/Pattern.java index f31d8c14432..7eb30c99bc3 100644 --- a/src/main/java/spoon/pattern/Pattern.java +++ b/src/main/java/spoon/pattern/Pattern.java @@ -27,6 +27,9 @@ import spoon.SpoonException; import spoon.pattern.matcher.Match; import spoon.pattern.matcher.MatchingScanner; +import spoon.pattern.parameter.ParameterInfo; +import spoon.pattern.parameter.ParameterValueProvider; +import spoon.pattern.parameter.ParameterValueProviderFactory; import spoon.reflect.declaration.CtElement; import spoon.reflect.declaration.CtPackage; import spoon.reflect.declaration.CtType; diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index a255d54a549..f96c892836d 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -37,6 +37,8 @@ import spoon.pattern.ParametersBuilder.ParameterElementPair; import spoon.pattern.matcher.MapEntryNode; import spoon.pattern.matcher.Matchers; +import spoon.pattern.parameter.AbstractItemAccessor; +import spoon.pattern.parameter.ParameterInfo; import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtInvocation; import spoon.reflect.code.CtLiteral; diff --git a/src/main/java/spoon/pattern/PatternPrinter.java b/src/main/java/spoon/pattern/PatternPrinter.java new file mode 100644 index 00000000000..672c43f32ef --- /dev/null +++ b/src/main/java/spoon/pattern/PatternPrinter.java @@ -0,0 +1,112 @@ +/** + * 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.pattern; + +import java.util.List; +import java.util.Map; + +import spoon.SpoonException; +import spoon.pattern.parameter.AbstractItemAccessor; +import spoon.pattern.parameter.ParameterValueProvider; +import spoon.reflect.code.CtInvocation; +import spoon.reflect.code.CtLocalVariable; +import spoon.reflect.declaration.CtElement; +import spoon.reflect.factory.Factory; +import spoon.reflect.factory.FactoryImpl; +import spoon.support.DefaultCoreFactory; +import spoon.support.StandardEnvironment; + +/** + */ +public class PatternPrinter { + private static final Factory DEFAULT_FACTORY = new FactoryImpl(new DefaultCoreFactory(), new StandardEnvironment()); + + public String printNode(Node node) { + List generated = node.generateTargets(DEFAULT_FACTORY, new Params(), null); + StringBuilder sb = new StringBuilder(); + for (CtElement ele : generated) { + sb.append(ele.toString()).append('\n'); + } + return sb.toString(); + } + + private static class Params implements ParameterValueProvider { + @Override + public boolean hasValue(String parameterName) { + return true; + } + + @Override + public Object get(String parameterName) { + return new ParameterMarker(parameterName); + } + + @Override + public ParameterValueProvider putIntoCopy(String parameterName, Object value) { + throw new SpoonException("This provider is just for printing"); + } + + @Override + public Map asMap() { + throw new SpoonException("This provider is just for printing"); + } + + @Override + public ParameterValueProvider createLocalParameterValueProvider() { + throw new SpoonException("This provider is just for printing"); + } + + @Override + public Map asLocalMap() { + throw new SpoonException("This provider is just for printing"); + } + } + + private static class ParameterMarker { + final String name; + + ParameterMarker(String name) { + super(); + this.name = name; + } + } + + /** + * Creates a element which will be printed in source code of pattern as marker of parameter + * @param factory a SpoonFactory which has to be used to create new elements + * @param potentialParameterMarker + * @param type + * @return dummy template element, which represents a template type in source of generated Pattern. + * Or null if potentialParameterMarker is not a marker of parameter + */ + public static T generatePatternParameterElement(Factory factory, Object potentialParameterMarker, Class type, AbstractItemAccessor parameterInfo) { + if (potentialParameterMarker instanceof ParameterMarker == false) { + return null; + } + ParameterMarker paramMarker = (ParameterMarker) potentialParameterMarker; + if (type != null) { + if (type.isAssignableFrom(CtInvocation.class)) { + return (T) factory.createInvocation(factory.createThisAccess(factory.Type().objectType(), true), factory.createExecutableReference().setSimpleName(paramMarker.name)); + } + if (type.isAssignableFrom(CtLocalVariable.class)) { + return (T) factory.createLocalVariable(factory.Type().objectType(), paramMarker.name, null); + } + } + PatternPrinter.class.getClass(); + return null; + } +} diff --git a/src/main/java/spoon/pattern/PrimitiveMatcher.java b/src/main/java/spoon/pattern/PrimitiveMatcher.java index c7cfa383c2d..995101ccd3c 100644 --- a/src/main/java/spoon/pattern/PrimitiveMatcher.java +++ b/src/main/java/spoon/pattern/PrimitiveMatcher.java @@ -16,6 +16,8 @@ */ package spoon.pattern; +import spoon.pattern.parameter.ParameterValueProvider; + /** * Defines API of a primitive matcher - matcher for single target object */ diff --git a/src/main/java/spoon/pattern/RepeatableMatcher.java b/src/main/java/spoon/pattern/RepeatableMatcher.java index f4637d6aef5..0db6053ffb6 100644 --- a/src/main/java/spoon/pattern/RepeatableMatcher.java +++ b/src/main/java/spoon/pattern/RepeatableMatcher.java @@ -17,6 +17,7 @@ package spoon.pattern; import spoon.pattern.matcher.Quantifier; +import spoon.pattern.parameter.ParameterValueProvider; /** * Defines API of a repeatable matcher. diff --git a/src/main/java/spoon/pattern/StringNode.java b/src/main/java/spoon/pattern/StringNode.java index bf4fe4f741f..6f5981cc105 100644 --- a/src/main/java/spoon/pattern/StringNode.java +++ b/src/main/java/spoon/pattern/StringNode.java @@ -27,6 +27,8 @@ import java.util.regex.Pattern; import spoon.SpoonException; +import spoon.pattern.parameter.ParameterInfo; +import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.factory.Factory; /** diff --git a/src/main/java/spoon/pattern/SwitchNode.java b/src/main/java/spoon/pattern/SwitchNode.java index 5194d665179..fbf9a15d10c 100644 --- a/src/main/java/spoon/pattern/SwitchNode.java +++ b/src/main/java/spoon/pattern/SwitchNode.java @@ -22,6 +22,8 @@ import spoon.pattern.matcher.Matchers; import spoon.pattern.matcher.TobeMatched; +import spoon.pattern.parameter.ParameterInfo; +import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.factory.Factory; /** diff --git a/src/main/java/spoon/pattern/UnmodifiableParameterValueProvider.java b/src/main/java/spoon/pattern/UnmodifiableParameterValueProvider.java index 5640524106e..7e129f8751e 100644 --- a/src/main/java/spoon/pattern/UnmodifiableParameterValueProvider.java +++ b/src/main/java/spoon/pattern/UnmodifiableParameterValueProvider.java @@ -22,6 +22,9 @@ import java.util.List; import java.util.Map; +import spoon.pattern.parameter.ParameterValueProvider; +import spoon.pattern.parameter.ParameterValueProviderFactory; + /** * Provides value of parameter */ diff --git a/src/main/java/spoon/pattern/matcher/MapEntryNode.java b/src/main/java/spoon/pattern/matcher/MapEntryNode.java index 0a0e495d0b0..d9ecec1e0e2 100644 --- a/src/main/java/spoon/pattern/matcher/MapEntryNode.java +++ b/src/main/java/spoon/pattern/matcher/MapEntryNode.java @@ -18,10 +18,10 @@ import java.util.function.BiConsumer; -import spoon.pattern.ParameterInfo; -import spoon.pattern.ParameterValueProvider; import spoon.pattern.Node; import spoon.pattern.ResultHolder; +import spoon.pattern.parameter.ParameterInfo; +import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.factory.Factory; /** diff --git a/src/main/java/spoon/pattern/matcher/Match.java b/src/main/java/spoon/pattern/matcher/Match.java index 616457b56ec..3595d5a95c9 100644 --- a/src/main/java/spoon/pattern/matcher/Match.java +++ b/src/main/java/spoon/pattern/matcher/Match.java @@ -20,7 +20,7 @@ import java.util.Map; import spoon.SpoonException; -import spoon.pattern.ParameterValueProvider; +import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.declaration.CtElement; /** diff --git a/src/main/java/spoon/pattern/matcher/MatchingScanner.java b/src/main/java/spoon/pattern/matcher/MatchingScanner.java index df66e3ac1cf..ac98e60391e 100644 --- a/src/main/java/spoon/pattern/matcher/MatchingScanner.java +++ b/src/main/java/spoon/pattern/matcher/MatchingScanner.java @@ -24,7 +24,7 @@ import spoon.SpoonException; import spoon.pattern.ModelNode; -import spoon.pattern.ParameterValueProviderFactory; +import spoon.pattern.parameter.ParameterValueProviderFactory; import spoon.reflect.declaration.CtElement; import spoon.reflect.meta.ContainerKind; import spoon.reflect.path.CtRole; diff --git a/src/main/java/spoon/pattern/matcher/NodeListMatcher.java b/src/main/java/spoon/pattern/matcher/NodeListMatcher.java index 99421673606..19fa19df055 100644 --- a/src/main/java/spoon/pattern/matcher/NodeListMatcher.java +++ b/src/main/java/spoon/pattern/matcher/NodeListMatcher.java @@ -19,7 +19,7 @@ import java.util.List; -import spoon.pattern.ParameterValueProvider; +import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.declaration.CtElement; /** diff --git a/src/main/java/spoon/pattern/matcher/TobeMatched.java b/src/main/java/spoon/pattern/matcher/TobeMatched.java index 2c4cdd286e5..0a897156b85 100644 --- a/src/main/java/spoon/pattern/matcher/TobeMatched.java +++ b/src/main/java/spoon/pattern/matcher/TobeMatched.java @@ -25,7 +25,7 @@ import java.util.function.BiFunction; import spoon.SpoonException; -import spoon.pattern.ParameterValueProvider; +import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.meta.ContainerKind; /** diff --git a/src/main/java/spoon/pattern/AbstractItemAccessor.java b/src/main/java/spoon/pattern/parameter/AbstractItemAccessor.java similarity index 90% rename from src/main/java/spoon/pattern/AbstractItemAccessor.java rename to src/main/java/spoon/pattern/parameter/AbstractItemAccessor.java index d314802d150..9197379c1b3 100644 --- a/src/main/java/spoon/pattern/AbstractItemAccessor.java +++ b/src/main/java/spoon/pattern/parameter/AbstractItemAccessor.java @@ -14,26 +14,30 @@ * 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.pattern; +package spoon.pattern.parameter; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import spoon.Launcher; import spoon.SpoonException; +import spoon.pattern.ResultHolder; +import spoon.pattern.ValueConvertor; import spoon.pattern.matcher.Quantifier; import spoon.reflect.code.CtBlock; +import spoon.reflect.code.CtStatementList; import spoon.reflect.factory.Factory; import spoon.reflect.meta.ContainerKind; import spoon.reflect.reference.CtTypeReference; /** */ -abstract class AbstractItemAccessor implements ParameterInfo { +public abstract class AbstractItemAccessor implements ParameterInfo { /** * is used as return value when value cannot be added @@ -237,7 +241,7 @@ public int getMaxOccurences() { return isMultiple() ? maxOccurences : Math.min(maxOccurences, 1); } - void setMaxOccurences(int maxOccurences) { + public void setMaxOccurences(int maxOccurences) { this.maxOccurences = maxOccurences; } @@ -399,7 +403,7 @@ protected Object getValue(ParameterValueProvider parameters) { protected void convertValue(Factory factory, ResultHolder result, Object rawValue) { //convert raw parameter value to expected type if (result.isMultiple()) { - AbstractPrimitiveMatcher.forEachItem(rawValue, singleValue -> { + forEachItem(rawValue, singleValue -> { T convertedValue = convertSingleValue(factory, singleValue, result.getRequiredClass()); if (convertedValue != null) { result.addResult(convertedValue); @@ -416,6 +420,35 @@ protected T convertSingleValue(Factory factory, Object value, Class type) return (T) valueConvertor.getValueAs(factory, value, type); } + /** + * calls consumer.accept(Object) once for each item of the `multipleValues` collection or array. + * If it is not a collection or array then it calls consumer.accept(Object) once with `multipleValues` + * If `multipleValues` is null then consumer.accept(Object) is not called + * @param multipleValues to be iterated potential collection of items + * @param consumer the receiver of items + */ + @SuppressWarnings("unchecked") + static void forEachItem(Object multipleValues, Consumer consumer) { + if (multipleValues instanceof CtStatementList) { + //CtStatementList extends Iterable, but we want to handle it as one node. + consumer.accept(multipleValues); + return; + } + if (multipleValues instanceof Iterable) { + for (Object item : (Iterable) multipleValues) { + consumer.accept(item); + } + return; + } + if (multipleValues instanceof Object[]) { + for (Object item : (Object[]) multipleValues) { + consumer.accept(item); + } + return; + } + consumer.accept(multipleValues); + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); diff --git a/src/main/java/spoon/pattern/ListAccessor.java b/src/main/java/spoon/pattern/parameter/ListAccessor.java similarity index 98% rename from src/main/java/spoon/pattern/ListAccessor.java rename to src/main/java/spoon/pattern/parameter/ListAccessor.java index 881c734c79c..34ddf4acee5 100644 --- a/src/main/java/spoon/pattern/ListAccessor.java +++ b/src/main/java/spoon/pattern/parameter/ListAccessor.java @@ -14,7 +14,7 @@ * 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.pattern; +package spoon.pattern.parameter; import java.util.ArrayList; import java.util.Arrays; diff --git a/src/main/java/spoon/pattern/NamedItemAccessor.java b/src/main/java/spoon/pattern/parameter/NamedItemAccessor.java similarity index 97% rename from src/main/java/spoon/pattern/NamedItemAccessor.java rename to src/main/java/spoon/pattern/parameter/NamedItemAccessor.java index 646543d1419..cf84aec0c41 100644 --- a/src/main/java/spoon/pattern/NamedItemAccessor.java +++ b/src/main/java/spoon/pattern/parameter/NamedItemAccessor.java @@ -14,11 +14,13 @@ * 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.pattern; +package spoon.pattern.parameter; import java.util.Map; import java.util.function.Function; +import spoon.pattern.UnmodifiableParameterValueProvider; + /** * A kind of {@link ParameterInfo} which returns value by the named parameter * From a container of type {@link ParameterValueProvider} or {@link Map} diff --git a/src/main/java/spoon/pattern/ParameterInfo.java b/src/main/java/spoon/pattern/parameter/ParameterInfo.java similarity index 96% rename from src/main/java/spoon/pattern/ParameterInfo.java rename to src/main/java/spoon/pattern/parameter/ParameterInfo.java index fa4ab559782..39ef764a0a1 100644 --- a/src/main/java/spoon/pattern/ParameterInfo.java +++ b/src/main/java/spoon/pattern/parameter/ParameterInfo.java @@ -14,8 +14,11 @@ * 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.pattern; +package spoon.pattern.parameter; +import spoon.pattern.Node; +import spoon.pattern.Pattern; +import spoon.pattern.ResultHolder; import spoon.pattern.matcher.Quantifier; import spoon.reflect.factory.Factory; diff --git a/src/main/java/spoon/pattern/ParameterValueProvider.java b/src/main/java/spoon/pattern/parameter/ParameterValueProvider.java similarity index 98% rename from src/main/java/spoon/pattern/ParameterValueProvider.java rename to src/main/java/spoon/pattern/parameter/ParameterValueProvider.java index d508623e36f..ddc24a1e8a5 100644 --- a/src/main/java/spoon/pattern/ParameterValueProvider.java +++ b/src/main/java/spoon/pattern/parameter/ParameterValueProvider.java @@ -14,7 +14,7 @@ * 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.pattern; +package spoon.pattern.parameter; import java.util.Map; diff --git a/src/main/java/spoon/pattern/ParameterValueProviderFactory.java b/src/main/java/spoon/pattern/parameter/ParameterValueProviderFactory.java similarity index 96% rename from src/main/java/spoon/pattern/ParameterValueProviderFactory.java rename to src/main/java/spoon/pattern/parameter/ParameterValueProviderFactory.java index 293ef3469a4..1d1aa4a7fb5 100644 --- a/src/main/java/spoon/pattern/ParameterValueProviderFactory.java +++ b/src/main/java/spoon/pattern/parameter/ParameterValueProviderFactory.java @@ -14,7 +14,7 @@ * 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.pattern; +package spoon.pattern.parameter; /** * Creates appropriate instances of {@link ParameterValueProvider} during matching process diff --git a/src/main/java/spoon/pattern/SetAccessor.java b/src/main/java/spoon/pattern/parameter/SetAccessor.java similarity index 98% rename from src/main/java/spoon/pattern/SetAccessor.java rename to src/main/java/spoon/pattern/parameter/SetAccessor.java index 0a2e1441316..d8fb28eb5e0 100644 --- a/src/main/java/spoon/pattern/SetAccessor.java +++ b/src/main/java/spoon/pattern/parameter/SetAccessor.java @@ -14,7 +14,7 @@ * 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.pattern; +package spoon.pattern.parameter; import java.util.Arrays; import java.util.Collection; diff --git a/src/main/java/spoon/template/TemplateMatcher.java b/src/main/java/spoon/template/TemplateMatcher.java index ca85be103b4..a062cb65b08 100644 --- a/src/main/java/spoon/template/TemplateMatcher.java +++ b/src/main/java/spoon/template/TemplateMatcher.java @@ -19,13 +19,13 @@ import java.util.List; import spoon.pattern.ModelNode; -import spoon.pattern.ParameterValueProvider; -import spoon.pattern.ParameterValueProviderFactory; import spoon.pattern.Pattern; import spoon.pattern.TemplateBuilder; import spoon.pattern.UnmodifiableParameterValueProvider; import spoon.pattern.matcher.Match; import spoon.pattern.matcher.TobeMatched; +import spoon.pattern.parameter.ParameterValueProvider; +import spoon.pattern.parameter.ParameterValueProviderFactory; import spoon.reflect.declaration.CtClass; import spoon.reflect.declaration.CtElement; import spoon.reflect.meta.ContainerKind; diff --git a/src/test/java/spoon/test/template/CodeReplaceTest.java b/src/test/java/spoon/test/template/CodeReplaceTest.java index 5e8ee7e5bb8..e9bbb889a25 100644 --- a/src/test/java/spoon/test/template/CodeReplaceTest.java +++ b/src/test/java/spoon/test/template/CodeReplaceTest.java @@ -5,8 +5,8 @@ import spoon.Launcher; import spoon.OutputType; import spoon.SpoonModelBuilder; -import spoon.pattern.ParameterValueProvider; import spoon.pattern.Pattern; +import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.declaration.CtClass; import spoon.reflect.factory.Factory; import spoon.reflect.visitor.DefaultJavaPrettyPrinter; diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index 2412c3d4865..d052479dc5a 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -11,8 +11,8 @@ import org.junit.Ignore; import org.junit.Test; -import spoon.pattern.ParameterInfo; import spoon.pattern.Pattern; +import spoon.pattern.parameter.ParameterInfo; import spoon.reflect.factory.Factory; import spoon.test.template.testclasses.replace.OldPattern; import spoon.testing.utils.ModelUtils; @@ -40,7 +40,6 @@ public void testPatternParameters() { } @Test - @Ignore public void testPatternToString() { //contract: Pattern can be printed to String and each parameter is defined there Factory f = ModelUtils.build( diff --git a/src/test/java/spoon/test/template/TemplateMatcherTest.java b/src/test/java/spoon/test/template/TemplateMatcherTest.java index 8a37e8b090f..5e61764d464 100644 --- a/src/test/java/spoon/test/template/TemplateMatcherTest.java +++ b/src/test/java/spoon/test/template/TemplateMatcherTest.java @@ -18,10 +18,10 @@ import org.junit.Ignore; import org.junit.Test; -import spoon.pattern.ParameterValueProvider; import spoon.pattern.Pattern; import spoon.pattern.matcher.Match; import spoon.pattern.matcher.Quantifier; +import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.code.CtInvocation; import spoon.reflect.code.CtLiteral; import spoon.reflect.code.CtStatement; diff --git a/src/test/java/spoon/test/template/TemplateTest.java b/src/test/java/spoon/test/template/TemplateTest.java index 077517791a3..2aa06117747 100644 --- a/src/test/java/spoon/test/template/TemplateTest.java +++ b/src/test/java/spoon/test/template/TemplateTest.java @@ -4,9 +4,9 @@ import spoon.Launcher; import spoon.SpoonException; import spoon.compiler.SpoonResourceHelper; -import spoon.pattern.ParameterValueProvider; import spoon.pattern.PatternBuilder; import spoon.pattern.matcher.Match; +import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtExpression; import spoon.reflect.code.CtForEach; diff --git a/src/test/java/spoon/test/template/core/ParameterInfoTest.java b/src/test/java/spoon/test/template/core/ParameterInfoTest.java index c1d0ef69d20..f45dea869e1 100644 --- a/src/test/java/spoon/test/template/core/ParameterInfoTest.java +++ b/src/test/java/spoon/test/template/core/ParameterInfoTest.java @@ -18,12 +18,12 @@ import org.junit.Test; -import spoon.pattern.ListAccessor; -import spoon.pattern.NamedItemAccessor; -import spoon.pattern.ParameterInfo; -import spoon.pattern.ParameterValueProvider; -import spoon.pattern.SetAccessor; import spoon.pattern.UnmodifiableParameterValueProvider; +import spoon.pattern.parameter.ListAccessor; +import spoon.pattern.parameter.NamedItemAccessor; +import spoon.pattern.parameter.ParameterInfo; +import spoon.pattern.parameter.ParameterValueProvider; +import spoon.pattern.parameter.SetAccessor; import spoon.reflect.meta.ContainerKind; public class ParameterInfoTest { From 5806741b4121565a727dd2812c09024f57ef6aaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Wed, 7 Mar 2018 14:16:54 +0100 Subject: [PATCH 007/131] rename Accessor to ParameterInfo --- .../java/spoon/pattern/ParametersBuilder.java | 22 +-- .../java/spoon/pattern/PatternBuilder.java | 4 +- .../java/spoon/pattern/PatternPrinter.java | 4 +- ...cessor.java => AbstractParameterInfo.java} | 32 ++--- ...stAccessor.java => ListParameterInfo.java} | 6 +- ...temAccessor.java => MapParameterInfo.java} | 8 +- ...SetAccessor.java => SetParameterInfo.java} | 4 +- .../test/template/core/ParameterInfoTest.java | 136 +++++++++--------- 8 files changed, 108 insertions(+), 108 deletions(-) rename src/main/java/spoon/pattern/parameter/{AbstractItemAccessor.java => AbstractParameterInfo.java} (92%) rename src/main/java/spoon/pattern/parameter/{ListAccessor.java => ListParameterInfo.java} (94%) rename src/main/java/spoon/pattern/parameter/{NamedItemAccessor.java => MapParameterInfo.java} (94%) rename src/main/java/spoon/pattern/parameter/{SetAccessor.java => SetParameterInfo.java} (95%) diff --git a/src/main/java/spoon/pattern/ParametersBuilder.java b/src/main/java/spoon/pattern/ParametersBuilder.java index 42e76f61be8..393eda6fce0 100644 --- a/src/main/java/spoon/pattern/ParametersBuilder.java +++ b/src/main/java/spoon/pattern/ParametersBuilder.java @@ -23,9 +23,9 @@ import spoon.SpoonException; import spoon.pattern.matcher.Quantifier; -import spoon.pattern.parameter.AbstractItemAccessor; -import spoon.pattern.parameter.ListAccessor; -import spoon.pattern.parameter.NamedItemAccessor; +import spoon.pattern.parameter.AbstractParameterInfo; +import spoon.pattern.parameter.ListParameterInfo; +import spoon.pattern.parameter.MapParameterInfo; import spoon.pattern.parameter.ParameterInfo; import spoon.reflect.code.CtArrayAccess; import spoon.reflect.code.CtBlock; @@ -64,11 +64,11 @@ */ public class ParametersBuilder { private final PatternBuilder patternBuilder; - private final Map parameterInfos; - private AbstractItemAccessor currentParameter; + private final Map parameterInfos; + private AbstractParameterInfo currentParameter; private ConflictResolutionMode conflictResolutionMode = ConflictResolutionMode.FAIL; - ParametersBuilder(PatternBuilder patternBuilder, Map parameterInfos) { + ParametersBuilder(PatternBuilder patternBuilder, Map parameterInfos) { this.patternBuilder = patternBuilder; this.parameterInfos = parameterInfos; } @@ -94,10 +94,10 @@ public CtQueryable queryModel() { return patternBuilder.patternQuery; } - private AbstractItemAccessor getParameterInfo(String parameterName, boolean createIfNotExist) { - AbstractItemAccessor pi = parameterInfos.get(parameterName); + private AbstractParameterInfo getParameterInfo(String parameterName, boolean createIfNotExist) { + AbstractParameterInfo pi = parameterInfos.get(parameterName); if (pi == null) { - pi = new NamedItemAccessor(parameterName).setValueConvertor(patternBuilder.getDefaultValueConvertor()); + pi = new MapParameterInfo(parameterName).setValueConvertor(patternBuilder.getDefaultValueConvertor()); parameterInfos.put(parameterName, pi); } return pi; @@ -587,7 +587,7 @@ public ParameterElementPair getSubstitutedNodeOfElement(ParameterInfo parameter, /** * for input `element` expression `X` in expression `X[Y]` it returns expression `X[Y]` - * and registers extra {@link ListAccessor} to the parameter assigned to `X` + * and registers extra {@link ListParameterInfo} to the parameter assigned to `X` * @param parameter TODO * @param valueResolver * @param element @@ -604,7 +604,7 @@ private ParameterElementPair transformArrayAccess(ParameterElementPair pep) { CtLiteral idxLiteral = (CtLiteral) expr; Object idx = idxLiteral.getValue(); if (idx instanceof Number) { - return new ParameterElementPair(new ListAccessor(((Number) idx).intValue(), pep.parameter), arrayAccess); + return new ParameterElementPair(new ListParameterInfo(((Number) idx).intValue(), pep.parameter), arrayAccess); } } } diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index f96c892836d..05737f7d96b 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -37,7 +37,7 @@ import spoon.pattern.ParametersBuilder.ParameterElementPair; import spoon.pattern.matcher.MapEntryNode; import spoon.pattern.matcher.Matchers; -import spoon.pattern.parameter.AbstractItemAccessor; +import spoon.pattern.parameter.AbstractParameterInfo; import spoon.pattern.parameter.ParameterInfo; import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtInvocation; @@ -110,7 +110,7 @@ public static PatternBuilder create(CtType templateType, Consumer templateTypeRef; private final Factory factory; - private final Map parameterInfos = new HashMap<>(); + private final Map parameterInfos = new HashMap<>(); // ModelNode pattern; CtQueryable patternQuery; private ValueConvertor valueConvertor; diff --git a/src/main/java/spoon/pattern/PatternPrinter.java b/src/main/java/spoon/pattern/PatternPrinter.java index 672c43f32ef..4fdc754da31 100644 --- a/src/main/java/spoon/pattern/PatternPrinter.java +++ b/src/main/java/spoon/pattern/PatternPrinter.java @@ -20,7 +20,7 @@ import java.util.Map; import spoon.SpoonException; -import spoon.pattern.parameter.AbstractItemAccessor; +import spoon.pattern.parameter.AbstractParameterInfo; import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.code.CtInvocation; import spoon.reflect.code.CtLocalVariable; @@ -93,7 +93,7 @@ private static class ParameterMarker { * @return dummy template element, which represents a template type in source of generated Pattern. * Or null if potentialParameterMarker is not a marker of parameter */ - public static T generatePatternParameterElement(Factory factory, Object potentialParameterMarker, Class type, AbstractItemAccessor parameterInfo) { + public static T generatePatternParameterElement(Factory factory, Object potentialParameterMarker, Class type, AbstractParameterInfo parameterInfo) { if (potentialParameterMarker instanceof ParameterMarker == false) { return null; } diff --git a/src/main/java/spoon/pattern/parameter/AbstractItemAccessor.java b/src/main/java/spoon/pattern/parameter/AbstractParameterInfo.java similarity index 92% rename from src/main/java/spoon/pattern/parameter/AbstractItemAccessor.java rename to src/main/java/spoon/pattern/parameter/AbstractParameterInfo.java index 9197379c1b3..398719a6947 100644 --- a/src/main/java/spoon/pattern/parameter/AbstractItemAccessor.java +++ b/src/main/java/spoon/pattern/parameter/AbstractParameterInfo.java @@ -37,14 +37,14 @@ /** */ -public abstract class AbstractItemAccessor implements ParameterInfo { +public abstract class AbstractParameterInfo implements ParameterInfo { /** * is used as return value when value cannot be added */ protected static final Object NO_MERGE = new Object(); - private final AbstractItemAccessor containerItemAccessor; + private final AbstractParameterInfo containerItemAccessor; private ContainerKind containerKind = null; private Boolean repeatable = null; @@ -56,9 +56,9 @@ public abstract class AbstractItemAccessor implements ParameterInfo { private Predicate matchCondition; private Class parameterValueType; - protected AbstractItemAccessor(ParameterInfo containerItemAccessor) { + protected AbstractParameterInfo(ParameterInfo containerItemAccessor) { super(); - this.containerItemAccessor = (AbstractItemAccessor) containerItemAccessor; + this.containerItemAccessor = (AbstractParameterInfo) containerItemAccessor; } protected String getContainerName() { @@ -70,7 +70,7 @@ protected String getContainerName() { @Override public final String getName() { - AbstractItemAccessor cca = getContainerKindAccessor(getContainerKind(null, null)); + AbstractParameterInfo cca = getContainerKindAccessor(getContainerKind(null, null)); if (cca != null) { return cca.getWrappedName(getPlainName()); } @@ -110,7 +110,7 @@ protected Object addValueToContainer(Object container, Function protected Object merge(Object existingValue, Object newValue) { ContainerKind cc = getContainerKind(existingValue, newValue); - AbstractItemAccessor cca = getContainerKindAccessor(cc); + AbstractParameterInfo cca = getContainerKindAccessor(cc); if (cca == null) { return mergeSingle(existingValue, newValue); } @@ -118,16 +118,16 @@ protected Object merge(Object existingValue, Object newValue) { existingListItemValue -> mergeSingle(existingListItemValue, newValue)); } - protected AbstractItemAccessor getContainerKindAccessor(ContainerKind containerKind) { + protected AbstractParameterInfo getContainerKindAccessor(ContainerKind containerKind) { switch (containerKind) { case SINGLE: return null; case LIST: - return new ListAccessor(this); + return new ListParameterInfo(this); case SET: - return new SetAccessor(this); + return new SetParameterInfo(this); case MAP: - return new NamedItemAccessor(this); + return new MapParameterInfo(this); } throw new SpoonException("Unexpected ContainerKind " + containerKind); } @@ -180,7 +180,7 @@ protected T castTo(Object o, Class type) { protected abstract T getEmptyContainer(); - public AbstractItemAccessor setMatchCondition(Class requiredType, Predicate matchCondition) { + public AbstractParameterInfo setMatchCondition(Class requiredType, Predicate matchCondition) { this.parameterValueType = requiredType; this.matchCondition = (Predicate) matchCondition; return this; @@ -207,7 +207,7 @@ public Class getParameterValueType() { return parameterValueType; } - public AbstractItemAccessor setParameterValueType(Class parameterValueType) { + public AbstractParameterInfo setParameterValueType(Class parameterValueType) { this.parameterValueType = parameterValueType; return this; } @@ -218,7 +218,7 @@ public boolean isMultiple() { return getContainerKind(null, null) != ContainerKind.SINGLE; } - public AbstractItemAccessor setRepeatable(boolean repeatable) { + public AbstractParameterInfo setRepeatable(boolean repeatable) { this.repeatable = repeatable; return this; } @@ -228,7 +228,7 @@ public int getMinOccurences() { return minOccurences; } - public AbstractItemAccessor setMinOccurences(int minOccurences) { + public AbstractParameterInfo setMinOccurences(int minOccurences) { this.minOccurences = minOccurences; return this; } @@ -269,7 +269,7 @@ public ValueConvertor getValueConvertor() { /** * @param valueConvertor the {@link ValueConvertor} used by reading and writing into parameter values defined by this {@link ParameterInfo} */ - public AbstractItemAccessor setValueConvertor(ValueConvertor valueConvertor) { + public AbstractParameterInfo setValueConvertor(ValueConvertor valueConvertor) { if (valueConvertor == null) { throw new SpoonException("valueConvertor must not be null"); } @@ -334,7 +334,7 @@ public ContainerKind getContainerKind() { return containerKind; } - public AbstractItemAccessor setContainerKind(ContainerKind containerKind) { + public AbstractParameterInfo setContainerKind(ContainerKind containerKind) { this.containerKind = containerKind; return this; } diff --git a/src/main/java/spoon/pattern/parameter/ListAccessor.java b/src/main/java/spoon/pattern/parameter/ListParameterInfo.java similarity index 94% rename from src/main/java/spoon/pattern/parameter/ListAccessor.java rename to src/main/java/spoon/pattern/parameter/ListParameterInfo.java index 34ddf4acee5..885e82f2cef 100644 --- a/src/main/java/spoon/pattern/parameter/ListAccessor.java +++ b/src/main/java/spoon/pattern/parameter/ListParameterInfo.java @@ -26,14 +26,14 @@ /** */ -public class ListAccessor extends AbstractItemAccessor { +public class ListParameterInfo extends AbstractParameterInfo { private final int idx; - public ListAccessor(ParameterInfo next) { + public ListParameterInfo(ParameterInfo next) { this(-1, next); } - public ListAccessor(int idx, ParameterInfo next) { + public ListParameterInfo(int idx, ParameterInfo next) { super(next); this.idx = idx; } diff --git a/src/main/java/spoon/pattern/parameter/NamedItemAccessor.java b/src/main/java/spoon/pattern/parameter/MapParameterInfo.java similarity index 94% rename from src/main/java/spoon/pattern/parameter/NamedItemAccessor.java rename to src/main/java/spoon/pattern/parameter/MapParameterInfo.java index cf84aec0c41..5ce872d6a99 100644 --- a/src/main/java/spoon/pattern/parameter/NamedItemAccessor.java +++ b/src/main/java/spoon/pattern/parameter/MapParameterInfo.java @@ -25,17 +25,17 @@ * A kind of {@link ParameterInfo} which returns value by the named parameter * From a container of type {@link ParameterValueProvider} or {@link Map} */ -public class NamedItemAccessor extends AbstractItemAccessor { +public class MapParameterInfo extends AbstractParameterInfo { private final String name; - public NamedItemAccessor(String name) { + public MapParameterInfo(String name) { this(name, null); } - public NamedItemAccessor(AbstractItemAccessor next) { + public MapParameterInfo(AbstractParameterInfo next) { this(null, next); } - public NamedItemAccessor(String name, AbstractItemAccessor next) { + public MapParameterInfo(String name, AbstractParameterInfo next) { super(next); this.name = name; } diff --git a/src/main/java/spoon/pattern/parameter/SetAccessor.java b/src/main/java/spoon/pattern/parameter/SetParameterInfo.java similarity index 95% rename from src/main/java/spoon/pattern/parameter/SetAccessor.java rename to src/main/java/spoon/pattern/parameter/SetParameterInfo.java index d8fb28eb5e0..75cd93e4ae0 100644 --- a/src/main/java/spoon/pattern/parameter/SetAccessor.java +++ b/src/main/java/spoon/pattern/parameter/SetParameterInfo.java @@ -26,9 +26,9 @@ /** */ -public class SetAccessor extends AbstractItemAccessor { +public class SetParameterInfo extends AbstractParameterInfo { - public SetAccessor(AbstractItemAccessor next) { + public SetParameterInfo(AbstractParameterInfo next) { super(next); } diff --git a/src/test/java/spoon/test/template/core/ParameterInfoTest.java b/src/test/java/spoon/test/template/core/ParameterInfoTest.java index f45dea869e1..48ab6d8664f 100644 --- a/src/test/java/spoon/test/template/core/ParameterInfoTest.java +++ b/src/test/java/spoon/test/template/core/ParameterInfoTest.java @@ -19,40 +19,40 @@ import org.junit.Test; import spoon.pattern.UnmodifiableParameterValueProvider; -import spoon.pattern.parameter.ListAccessor; -import spoon.pattern.parameter.NamedItemAccessor; +import spoon.pattern.parameter.ListParameterInfo; +import spoon.pattern.parameter.MapParameterInfo; import spoon.pattern.parameter.ParameterInfo; import spoon.pattern.parameter.ParameterValueProvider; -import spoon.pattern.parameter.SetAccessor; +import spoon.pattern.parameter.SetParameterInfo; import spoon.reflect.meta.ContainerKind; public class ParameterInfoTest { @Test public void testParameterNames() { - assertEquals("year", new NamedItemAccessor("year").getName()); - assertEquals("year", ((ParameterInfo) new NamedItemAccessor("year").setContainerKind(ContainerKind.MAP)).getName()); - assertEquals("year", new NamedItemAccessor(new NamedItemAccessor("year")).getName()); - assertEquals("year", new NamedItemAccessor(new NamedItemAccessor("year").setContainerKind(ContainerKind.MAP)).getName()); - assertEquals("year.age", new NamedItemAccessor("age", new NamedItemAccessor("year")).getName()); - assertEquals("year.age", new NamedItemAccessor("age", new NamedItemAccessor("year").setContainerKind(ContainerKind.MAP)).getName()); + assertEquals("year", new MapParameterInfo("year").getName()); + assertEquals("year", ((ParameterInfo) new MapParameterInfo("year").setContainerKind(ContainerKind.MAP)).getName()); + assertEquals("year", new MapParameterInfo(new MapParameterInfo("year")).getName()); + assertEquals("year", new MapParameterInfo(new MapParameterInfo("year").setContainerKind(ContainerKind.MAP)).getName()); + assertEquals("year.age", new MapParameterInfo("age", new MapParameterInfo("year")).getName()); + assertEquals("year.age", new MapParameterInfo("age", new MapParameterInfo("year").setContainerKind(ContainerKind.MAP)).getName()); - assertEquals("year", ((ParameterInfo) new NamedItemAccessor("year").setContainerKind(ContainerKind.LIST)).getName()); - assertEquals("year", new ListAccessor(new NamedItemAccessor("year")).getName()); - assertEquals("year", new ListAccessor(new NamedItemAccessor("year").setContainerKind(ContainerKind.LIST)).getName()); - assertEquals("year[7]", new ListAccessor(7, new NamedItemAccessor("year")).getName()); - assertEquals("year[7]", new ListAccessor(new ListAccessor(7, new NamedItemAccessor("year"))).getName()); - assertEquals("year[7][2]", new ListAccessor(2, new ListAccessor(7, new NamedItemAccessor("year"))).getName()); - assertEquals("year[7][2].age", new NamedItemAccessor("age", new ListAccessor(2, new ListAccessor(7, new NamedItemAccessor("year")))).getName()); + assertEquals("year", ((ParameterInfo) new MapParameterInfo("year").setContainerKind(ContainerKind.LIST)).getName()); + assertEquals("year", new ListParameterInfo(new MapParameterInfo("year")).getName()); + assertEquals("year", new ListParameterInfo(new MapParameterInfo("year").setContainerKind(ContainerKind.LIST)).getName()); + assertEquals("year[7]", new ListParameterInfo(7, new MapParameterInfo("year")).getName()); + assertEquals("year[7]", new ListParameterInfo(new ListParameterInfo(7, new MapParameterInfo("year"))).getName()); + assertEquals("year[7][2]", new ListParameterInfo(2, new ListParameterInfo(7, new MapParameterInfo("year"))).getName()); + assertEquals("year[7][2].age", new MapParameterInfo("age", new ListParameterInfo(2, new ListParameterInfo(7, new MapParameterInfo("year")))).getName()); - assertEquals("year", ((ParameterInfo) new NamedItemAccessor("year").setContainerKind(ContainerKind.SET)).getName()); - assertEquals("year", new SetAccessor(new NamedItemAccessor("year")).getName()); - assertEquals("year", new SetAccessor(new NamedItemAccessor("year").setContainerKind(ContainerKind.SET)).getName()); + assertEquals("year", ((ParameterInfo) new MapParameterInfo("year").setContainerKind(ContainerKind.SET)).getName()); + assertEquals("year", new SetParameterInfo(new MapParameterInfo("year")).getName()); + assertEquals("year", new SetParameterInfo(new MapParameterInfo("year").setContainerKind(ContainerKind.SET)).getName()); } @Test public void testSingleValueParameterByNameIntoNullContainer() { - ParameterInfo namedParam = new NamedItemAccessor("year"); + ParameterInfo namedParam = new MapParameterInfo("year"); {//adding value into null container, creates a new container with that value ParameterValueProvider val = namedParam.addValueAs(null, 2018); assertNotNull(val); @@ -61,7 +61,7 @@ public void testSingleValueParameterByNameIntoNullContainer() { } @Test public void testSingleValueParameterByNameIntoEmptyContainer() { - ParameterInfo namedParam = new NamedItemAccessor("year"); + ParameterInfo namedParam = new MapParameterInfo("year"); {//adding value into empty container, creates a new container with that value ParameterValueProvider empty = new UnmodifiableParameterValueProvider(); ParameterValueProvider val = namedParam.addValueAs(empty, 2018); @@ -73,7 +73,7 @@ public void testSingleValueParameterByNameIntoEmptyContainer() { } @Test public void testSingleValueParameterByNameWhenAlreadyExists() { - ParameterInfo namedParam = new NamedItemAccessor("year"); + ParameterInfo namedParam = new MapParameterInfo("year"); {//adding value into container, which already contains that value changes nothing and returns origin container ParameterValueProvider oldContainer = new UnmodifiableParameterValueProvider().putIntoCopy("year", 2018); assertEquals(map().put("year", 2018), oldContainer.asMap()); @@ -84,7 +84,7 @@ public void testSingleValueParameterByNameWhenAlreadyExists() { } @Test public void testSingleValueParameterByNameWhenDifferentExists() { - ParameterInfo namedParam = new NamedItemAccessor("year"); + ParameterInfo namedParam = new MapParameterInfo("year"); {//adding a value into container, which already contains a different value returns null - no match ParameterValueProvider oldContainer = new UnmodifiableParameterValueProvider().putIntoCopy("year", 2018); assertNull(namedParam.addValueAs(oldContainer, 2111)); @@ -96,7 +96,7 @@ public void testSingleValueParameterByNameWhenDifferentExists() { } @Test public void testOptionalSingleValueParameterByName() { - ParameterInfo namedParam = new NamedItemAccessor("year") + ParameterInfo namedParam = new MapParameterInfo("year") .setMinOccurences(0); {//adding null value into an container with minCount == 0, returns unchanged container. //because minCount == 0 means that value is optional @@ -109,7 +109,7 @@ public void testOptionalSingleValueParameterByName() { public void testMandatorySingleValueParameterByName() { //adding null value into an container with minCount == 1, returns null -> means NO match, null is not allowed. //because minCount == 0 means that value is optional - ParameterInfo namedParam = new NamedItemAccessor("year") + ParameterInfo namedParam = new MapParameterInfo("year") .setMinOccurences(1); { ParameterValueProvider container = new UnmodifiableParameterValueProvider().putIntoCopy("a", "b"); @@ -119,7 +119,7 @@ public void testMandatorySingleValueParameterByName() { } @Test public void testSingleValueParameterByNameConditionalMatcher() { - ParameterInfo namedParam = new NamedItemAccessor("year").setMatchCondition(Integer.class, i -> i > 2000); + ParameterInfo namedParam = new MapParameterInfo("year").setMatchCondition(Integer.class, i -> i > 2000); //matching value is accepted ParameterValueProvider val = namedParam.addValueAs(null, 2018); @@ -134,7 +134,7 @@ public void testSingleValueParameterByNameConditionalMatcher() { @Test public void testListParameterByNameIntoNull() { - ParameterInfo namedParam = new NamedItemAccessor("year").setContainerKind(ContainerKind.LIST); + ParameterInfo namedParam = new MapParameterInfo("year").setContainerKind(ContainerKind.LIST); {//adding value into null container, creates a new container with List which contains that value ParameterValueProvider val = namedParam.addValueAs(null, 2018); assertNotNull(val); @@ -143,7 +143,7 @@ public void testListParameterByNameIntoNull() { } @Test public void testListParameterByNameIntoEmptyContainer() { - ParameterInfo namedParam = new NamedItemAccessor("year").setContainerKind(ContainerKind.LIST); + ParameterInfo namedParam = new MapParameterInfo("year").setContainerKind(ContainerKind.LIST); {//adding value into empty container, creates a new container with List which contains that value ParameterValueProvider empty = new UnmodifiableParameterValueProvider(); ParameterValueProvider val = namedParam.addValueAs(empty, 2018); @@ -173,13 +173,13 @@ public void testListParameterByNameIntoEmptyContainerWithEmptyList() { assertEquals(map().put("year", Arrays.asList(2018, 2018)), val2.asMap()); }; //contract: it behaves like this when container kind is defined as LIST - check.accept(new NamedItemAccessor("year").setContainerKind(ContainerKind.LIST)); + check.accept(new MapParameterInfo("year").setContainerKind(ContainerKind.LIST)); //contract: it behaves like this even when container kind is not defined, so it is automatically detected from the existing parameter value - check.accept(new NamedItemAccessor("year")); + check.accept(new MapParameterInfo("year")); //contract: it behaves like this when ListAccessor + NamedAccessor is used - check.accept(new ListAccessor(new NamedItemAccessor("year"))); + check.accept(new ListParameterInfo(new MapParameterInfo("year"))); //contract: it behaves like this when ListAccessor + NamedAccessor with defined container is used with - check.accept(new ListAccessor(new NamedItemAccessor("year").setContainerKind(ContainerKind.LIST))); + check.accept(new ListParameterInfo(new MapParameterInfo("year").setContainerKind(ContainerKind.LIST))); } @Test @@ -193,16 +193,16 @@ public void testMergeOnDifferentValueTypeContainers() { assertNull(parameter.addValueAs(params, null)); }; ParameterValueProvider empty = new UnmodifiableParameterValueProvider(); - checker.accept(new NamedItemAccessor("year"), empty.putIntoCopy("year", "x")); - checker.accept(new ListAccessor(0, new NamedItemAccessor("year")), empty.putIntoCopy("year", Collections.singletonList("x"))); - checker.accept(new ListAccessor(1, new NamedItemAccessor("year")), empty.putIntoCopy("year", Arrays.asList("zz","x"))); - checker.accept(new NamedItemAccessor("key", new ListAccessor(1, new NamedItemAccessor("year"))), empty.putIntoCopy("year", Arrays.asList("zz",empty.putIntoCopy("key", "x")))); - checker.accept(new NamedItemAccessor("key", new NamedItemAccessor("year")), empty.putIntoCopy("year", empty.putIntoCopy("key", "x"))); + checker.accept(new MapParameterInfo("year"), empty.putIntoCopy("year", "x")); + checker.accept(new ListParameterInfo(0, new MapParameterInfo("year")), empty.putIntoCopy("year", Collections.singletonList("x"))); + checker.accept(new ListParameterInfo(1, new MapParameterInfo("year")), empty.putIntoCopy("year", Arrays.asList("zz","x"))); + checker.accept(new MapParameterInfo("key", new ListParameterInfo(1, new MapParameterInfo("year"))), empty.putIntoCopy("year", Arrays.asList("zz",empty.putIntoCopy("key", "x")))); + checker.accept(new MapParameterInfo("key", new MapParameterInfo("year")), empty.putIntoCopy("year", empty.putIntoCopy("key", "x"))); } @Test public void testAppendIntoList() { - ParameterInfo parameter = new NamedItemAccessor("years").setContainerKind(ContainerKind.LIST); + ParameterInfo parameter = new MapParameterInfo("years").setContainerKind(ContainerKind.LIST); ParameterValueProvider params = parameter.addValueAs(null, 1000); assertNotNull(params); assertEquals(map().put("years", Arrays.asList(1000)), params.asMap()); @@ -222,23 +222,23 @@ public void testAppendIntoList() { @Test public void testSetIntoList() { - ParameterInfo named = new NamedItemAccessor("years"); - ParameterValueProvider params = new ListAccessor(2, named).addValueAs(null, 1000); + ParameterInfo named = new MapParameterInfo("years"); + ParameterValueProvider params = new ListParameterInfo(2, named).addValueAs(null, 1000); assertNotNull(params); assertEquals(map().put("years", Arrays.asList(null, null, 1000)), params.asMap()); - params = new ListAccessor(0, named).addValueAs(params, 10); + params = new ListParameterInfo(0, named).addValueAs(params, 10); assertNotNull(params); assertEquals(map().put("years", Arrays.asList(10, null, 1000)), params.asMap()); - params = new ListAccessor(3, named).addValueAs(params, 10000); + params = new ListParameterInfo(3, named).addValueAs(params, 10000); assertNotNull(params); assertEquals(map().put("years", Arrays.asList(10, null, 1000, 10000)), params.asMap()); } @Test public void testAppendIntoSet() { - ParameterInfo parameter = new NamedItemAccessor("years").setContainerKind(ContainerKind.SET); + ParameterInfo parameter = new MapParameterInfo("years").setContainerKind(ContainerKind.SET); ParameterValueProvider params = parameter.addValueAs(null, 1000); assertNotNull(params); assertEquals(map().put("years", asSet(1000)), params.asMap()); @@ -282,13 +282,13 @@ public void testMapEntryInParameterByName() { //after all the once returned val is still the same - unmodified assertEquals(map().put("map", new UnmodifiableParameterValueProvider().putIntoCopy("year", 2018)), val.asMap()); }; - checker.accept(new NamedItemAccessor("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider()); - checker.accept(new NamedItemAccessor("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider().putIntoCopy("map", null)); - checker.accept(new NamedItemAccessor("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider().putIntoCopy("map", Collections.emptyMap())); + checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider()); + checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider().putIntoCopy("map", null)); + checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider().putIntoCopy("map", Collections.emptyMap())); //the map container is detected automatically from the type of value - checker.accept(new NamedItemAccessor("map"), new UnmodifiableParameterValueProvider().putIntoCopy("map", Collections.emptyMap())); + checker.accept(new MapParameterInfo("map"), new UnmodifiableParameterValueProvider().putIntoCopy("map", Collections.emptyMap())); //the map container is detected automatically from the type of value - checker.accept(new NamedItemAccessor("map"), new UnmodifiableParameterValueProvider().putIntoCopy("map", new UnmodifiableParameterValueProvider())); + checker.accept(new MapParameterInfo("map"), new UnmodifiableParameterValueProvider().putIntoCopy("map", new UnmodifiableParameterValueProvider())); } @Test public void testAddMapIntoParameterByName() { @@ -310,15 +310,15 @@ public void testAddMapIntoParameterByName() { //adding entry value with same key, but different value - no match assertNull(namedParam.addValueAs(val, entry("year", 1111))); }; - checker.accept(new NamedItemAccessor("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider()); - checker.accept(new NamedItemAccessor("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider().putIntoCopy("map", null)); - checker.accept(new NamedItemAccessor("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider().putIntoCopy("map", Collections.emptyMap())); + checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider()); + checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider().putIntoCopy("map", null)); + checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider().putIntoCopy("map", Collections.emptyMap())); //the map container is detected automatically from the type of value - checker.accept(new NamedItemAccessor("map"), new UnmodifiableParameterValueProvider().putIntoCopy("map", Collections.emptyMap())); + checker.accept(new MapParameterInfo("map"), new UnmodifiableParameterValueProvider().putIntoCopy("map", Collections.emptyMap())); //the map container is detected automatically from the type of value - checker.accept(new NamedItemAccessor("map"), new UnmodifiableParameterValueProvider().putIntoCopy("map", new UnmodifiableParameterValueProvider())); + checker.accept(new MapParameterInfo("map"), new UnmodifiableParameterValueProvider().putIntoCopy("map", new UnmodifiableParameterValueProvider())); //the map container is detected automatically from the type of new value - checker.accept(new NamedItemAccessor("map"), null); + checker.accept(new MapParameterInfo("map"), null); } @Test @@ -335,15 +335,15 @@ public void testAddListIntoParameterByName() { //adding null entry changes nothing assertSame(val, namedParam.addValueAs(val, null)); }; - checker.accept(new NamedItemAccessor("list").setContainerKind(ContainerKind.LIST), new UnmodifiableParameterValueProvider()); - checker.accept(new NamedItemAccessor("list").setContainerKind(ContainerKind.LIST), new UnmodifiableParameterValueProvider().putIntoCopy("list", null)); - checker.accept(new NamedItemAccessor("list").setContainerKind(ContainerKind.LIST), new UnmodifiableParameterValueProvider().putIntoCopy("list", Collections.emptyList())); + checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.LIST), new UnmodifiableParameterValueProvider()); + checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.LIST), new UnmodifiableParameterValueProvider().putIntoCopy("list", null)); + checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.LIST), new UnmodifiableParameterValueProvider().putIntoCopy("list", Collections.emptyList())); //Set can be converted to List - checker.accept(new NamedItemAccessor("list").setContainerKind(ContainerKind.LIST), new UnmodifiableParameterValueProvider().putIntoCopy("list", Collections.emptySet())); + checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.LIST), new UnmodifiableParameterValueProvider().putIntoCopy("list", Collections.emptySet())); //the list container is detected automatically from the type of value - checker.accept(new NamedItemAccessor("list"), new UnmodifiableParameterValueProvider().putIntoCopy("list", Collections.emptyList())); + checker.accept(new MapParameterInfo("list"), new UnmodifiableParameterValueProvider().putIntoCopy("list", Collections.emptyList())); //the list container is detected automatically from the type of new value - checker.accept(new NamedItemAccessor("list"), null); + checker.accept(new MapParameterInfo("list"), null); } @Test public void testAddSetIntoParameterByName() { @@ -365,19 +365,19 @@ public void testAddSetIntoParameterByName() { //adding Set with same entry changes nothing assertSame(val, namedParam.addValueAs(val, asSet(2018, 1111))); }; - checker.accept(new NamedItemAccessor("list").setContainerKind(ContainerKind.SET), new UnmodifiableParameterValueProvider()); - checker.accept(new NamedItemAccessor("list").setContainerKind(ContainerKind.SET), new UnmodifiableParameterValueProvider().putIntoCopy("list", null)); - checker.accept(new NamedItemAccessor("list").setContainerKind(ContainerKind.SET), new UnmodifiableParameterValueProvider().putIntoCopy("list", Collections.emptySet())); + checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.SET), new UnmodifiableParameterValueProvider()); + checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.SET), new UnmodifiableParameterValueProvider().putIntoCopy("list", null)); + checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.SET), new UnmodifiableParameterValueProvider().putIntoCopy("list", Collections.emptySet())); //The container kind has higher priority, so List will be converted to Set - checker.accept(new NamedItemAccessor("list").setContainerKind(ContainerKind.SET), new UnmodifiableParameterValueProvider().putIntoCopy("list", Collections.emptyList())); + checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.SET), new UnmodifiableParameterValueProvider().putIntoCopy("list", Collections.emptyList())); //the list container is detected automatically from the type of value - checker.accept(new NamedItemAccessor("list"), new UnmodifiableParameterValueProvider().putIntoCopy("list", Collections.emptySet())); + checker.accept(new MapParameterInfo("list"), new UnmodifiableParameterValueProvider().putIntoCopy("list", Collections.emptySet())); //the list container is detected automatically from the type of new value - checker.accept(new NamedItemAccessor("list"), null); + checker.accept(new MapParameterInfo("list"), null); } @Test public void testFailOnUnpectedContainer() { - ParameterInfo namedParam = new NamedItemAccessor("year").setContainerKind(ContainerKind.LIST); + ParameterInfo namedParam = new MapParameterInfo("year").setContainerKind(ContainerKind.LIST); try { namedParam.addValueAs(new UnmodifiableParameterValueProvider().putIntoCopy("year", "unexpected"), 1); fail(); @@ -388,7 +388,7 @@ public void testFailOnUnpectedContainer() { @Test public void testSetEmptyMap() { - ParameterInfo namedParam = new NamedItemAccessor("year").setContainerKind(ContainerKind.MAP); + ParameterInfo namedParam = new MapParameterInfo("year").setContainerKind(ContainerKind.MAP); {//adding empty Map works ParameterValueProvider val = namedParam.addValueAs(null, null); assertNotNull(val); From 8fc3ed5c9304379f7f12a78ac6ed4a56ec78c9f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Wed, 7 Mar 2018 14:35:02 +0100 Subject: [PATCH 008/131] Rename Parameters --- src/main/java/spoon/pattern/ForEachNode.java | 4 +- src/main/java/spoon/pattern/Pattern.java | 1 + .../java/spoon/pattern/PatternPrinter.java | 4 +- .../parameter/AbstractParameterInfo.java | 4 +- .../pattern/parameter/MapParameterInfo.java | 16 +- .../parameter/ParameterValueProvider.java | 31 +++- .../UnmodifiableParameterValueProvider.java | 11 +- .../java/spoon/template/TemplateMatcher.java | 2 +- .../spoon/test/template/CodeReplaceTest.java | 28 +-- .../test/template/TemplateMatcherTest.java | 174 +++++++++--------- .../test/template/core/ParameterInfoTest.java | 72 ++++---- 11 files changed, 182 insertions(+), 165 deletions(-) rename src/main/java/spoon/pattern/{ => parameter}/UnmodifiableParameterValueProvider.java (92%) diff --git a/src/main/java/spoon/pattern/ForEachNode.java b/src/main/java/spoon/pattern/ForEachNode.java index b7feff608db..ff212970d93 100644 --- a/src/main/java/spoon/pattern/ForEachNode.java +++ b/src/main/java/spoon/pattern/ForEachNode.java @@ -67,7 +67,7 @@ public boolean replaceNode(Node oldNode, Node newNode) { @Override public void generateTargets(Factory factory, ResultHolder result, ParameterValueProvider parameters) { for (Object parameterValue : iterableParameter.generateTargets(factory, parameters, Object.class)) { - nestedModel.generateTargets(factory, result, parameters.putIntoCopy(localParameter.getName(), parameterValue)); + nestedModel.generateTargets(factory, result, parameters.putValueToCopy(localParameter.getName(), parameterValue)); } } @@ -99,7 +99,7 @@ public TobeMatched matchAllWith(TobeMatched tobeMatched) { } } else { //it is new global parameter value. Just set it - newParameters = newParameters.putIntoCopy(name, value); + newParameters = newParameters.putValueToCopy(name, value); } } //all local parameters were applied to newParameters. We can use newParameters as result of this iteration for next iteration diff --git a/src/main/java/spoon/pattern/Pattern.java b/src/main/java/spoon/pattern/Pattern.java index 7eb30c99bc3..df44e82a8df 100644 --- a/src/main/java/spoon/pattern/Pattern.java +++ b/src/main/java/spoon/pattern/Pattern.java @@ -30,6 +30,7 @@ import spoon.pattern.parameter.ParameterInfo; import spoon.pattern.parameter.ParameterValueProvider; import spoon.pattern.parameter.ParameterValueProviderFactory; +import spoon.pattern.parameter.UnmodifiableParameterValueProvider; import spoon.reflect.declaration.CtElement; import spoon.reflect.declaration.CtPackage; import spoon.reflect.declaration.CtType; diff --git a/src/main/java/spoon/pattern/PatternPrinter.java b/src/main/java/spoon/pattern/PatternPrinter.java index 4fdc754da31..01401126a62 100644 --- a/src/main/java/spoon/pattern/PatternPrinter.java +++ b/src/main/java/spoon/pattern/PatternPrinter.java @@ -51,12 +51,12 @@ public boolean hasValue(String parameterName) { } @Override - public Object get(String parameterName) { + public Object getValue(String parameterName) { return new ParameterMarker(parameterName); } @Override - public ParameterValueProvider putIntoCopy(String parameterName, Object value) { + public ParameterValueProvider putValueToCopy(String parameterName, Object value) { throw new SpoonException("This provider is just for printing"); } diff --git a/src/main/java/spoon/pattern/parameter/AbstractParameterInfo.java b/src/main/java/spoon/pattern/parameter/AbstractParameterInfo.java index 398719a6947..2e783a7835d 100644 --- a/src/main/java/spoon/pattern/parameter/AbstractParameterInfo.java +++ b/src/main/java/spoon/pattern/parameter/AbstractParameterInfo.java @@ -324,7 +324,7 @@ private int getNumberOfValues(ParameterValueProvider parameters) { if (parameters.hasValue(getName()) == false) { return 0; } - Object value = parameters.get(getName()); + Object value = parameters.getValue(getName()); if (value instanceof Collection) { return ((Collection) value).size(); } @@ -339,7 +339,7 @@ public AbstractParameterInfo setContainerKind(ContainerKind containerKind) { return this; } protected ContainerKind getContainerKind(ParameterValueProvider params) { - return getContainerKind(params.get(getName()), null); + return getContainerKind(params.getValue(getName()), null); } protected ContainerKind getContainerKind(Object existingValue, Object value) { if (containerKind != null) { diff --git a/src/main/java/spoon/pattern/parameter/MapParameterInfo.java b/src/main/java/spoon/pattern/parameter/MapParameterInfo.java index 5ce872d6a99..340a13e6f02 100644 --- a/src/main/java/spoon/pattern/parameter/MapParameterInfo.java +++ b/src/main/java/spoon/pattern/parameter/MapParameterInfo.java @@ -19,8 +19,6 @@ import java.util.Map; import java.util.function.Function; -import spoon.pattern.UnmodifiableParameterValueProvider; - /** * A kind of {@link ParameterInfo} which returns value by the named parameter * From a container of type {@link ParameterValueProvider} or {@link Map} @@ -72,7 +70,7 @@ protected Object addValueAs(Object container, Function merger) { if (newValue instanceof Map.Entry) { Map.Entry newEntry = (Map.Entry) newValue; String newEntryKey = (String) newEntry.getKey(); - Object existingValue = parameters.get(newEntryKey); + Object existingValue = parameters.getValue(newEntryKey); Object newEntryValue = merge(existingValue, newEntry.getValue()); if (newEntryValue == NO_MERGE) { return NO_MERGE; @@ -81,20 +79,20 @@ protected Object addValueAs(Object container, Function merger) { //it is already there return parameters; } - return parameters.putIntoCopy(newEntryKey, newEntryValue); + return parameters.putValueToCopy(newEntryKey, newEntryValue); } if (newValue instanceof Map) { Map newMap = (Map) newValue; for (Map.Entry newEntry : newMap.entrySet()) { String newEntryKey = newEntry.getKey(); - Object existingValue = parameters.get(newEntryKey); + Object existingValue = parameters.getValue(newEntryKey); Object newEntryValue = merge(existingValue, newEntry.getValue()); if (newEntryValue == NO_MERGE) { return NO_MERGE; } if (existingValue != newEntryValue) { //it is not there yet. Add it - parameters = parameters.putIntoCopy(newEntryKey, newEntryValue); + parameters = parameters.putValueToCopy(newEntryKey, newEntryValue); } //it is there, continue to check next entry } @@ -103,7 +101,7 @@ protected Object addValueAs(Object container, Function merger) { //only Map.Entries can be added to the Map if there is missing key return NO_MERGE; } - Object existingValue = parameters.get(name); + Object existingValue = parameters.getValue(name); Object newValue = merger.apply(existingValue); if (newValue == NO_MERGE) { return NO_MERGE; @@ -112,13 +110,13 @@ protected Object addValueAs(Object container, Function merger) { //it is already there. return parameters; } - return parameters.putIntoCopy(name, newValue); + return parameters.putValueToCopy(name, newValue); } @Override protected Object getValue(ParameterValueProvider parameters) { ParameterValueProvider map = castTo(super.getValue(parameters), ParameterValueProvider.class); - return name == null ? map : map.get(name); + return name == null ? map : map.getValue(name); } @Override diff --git a/src/main/java/spoon/pattern/parameter/ParameterValueProvider.java b/src/main/java/spoon/pattern/parameter/ParameterValueProvider.java index ddc24a1e8a5..7a535eccb6d 100644 --- a/src/main/java/spoon/pattern/parameter/ParameterValueProvider.java +++ b/src/main/java/spoon/pattern/parameter/ParameterValueProvider.java @@ -19,9 +19,9 @@ import java.util.Map; /** - * During substitution process it provides values of parameters from underlying storage (e.g. from Map or an instance of an class) - * During matching process it sets values of matched parameters into underlying storage - * TODO: create ParameterValueProviderFactory which creates appropriate instances of ParameterValueProviders during matching process + * It is unmodifiable storage of parameter name-value pairs. + * The values may be primitive values or List,Set,Map of values. + * All internal containers are unmodifiable too. */ public interface ParameterValueProvider { @@ -30,11 +30,32 @@ public interface ParameterValueProvider { * @return true if there is defined some value for the parameter. null can be a value too */ boolean hasValue(String parameterName); - Object get(String parameterName); - ParameterValueProvider putIntoCopy(String parameterName, Object value); + /** + * @param parameterName the name of the parameter + * @return a value of the parameter under the name `parameterNamer + */ + Object getValue(String parameterName); + /** + * @param parameterName to be set parameter name + * @param value the new value + * @return copies this {@link ParameterValueProvider}, sets the new value there and returns that copy + */ + ParameterValueProvider putValueToCopy(String parameterName, Object value); + /** + * @return underlying unmodifiable Map<String, Object> + */ Map asMap(); + /** + * @return a new instance of {@link ParameterValueProvider}, which inherits all values from this {@link ParameterValueProvider} + * Any call of {@link #putValueToCopy(String, Object)} is remembered in local Map of parameters. + * At the end of process the {@link #asLocalMap()} can be used to return all the parameters which were changed + * after local {@link ParameterValueProvider} was created + */ ParameterValueProvider createLocalParameterValueProvider(); + /** + * @return {@link Map} with all modified parameters after {@link #createLocalParameterValueProvider()} has been called + */ Map asLocalMap(); } diff --git a/src/main/java/spoon/pattern/UnmodifiableParameterValueProvider.java b/src/main/java/spoon/pattern/parameter/UnmodifiableParameterValueProvider.java similarity index 92% rename from src/main/java/spoon/pattern/UnmodifiableParameterValueProvider.java rename to src/main/java/spoon/pattern/parameter/UnmodifiableParameterValueProvider.java index 7e129f8751e..bd1dff50599 100644 --- a/src/main/java/spoon/pattern/UnmodifiableParameterValueProvider.java +++ b/src/main/java/spoon/pattern/parameter/UnmodifiableParameterValueProvider.java @@ -14,7 +14,7 @@ * 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.pattern; +package spoon.pattern.parameter; import java.util.ArrayList; import java.util.Collections; @@ -22,9 +22,6 @@ import java.util.List; import java.util.Map; -import spoon.pattern.parameter.ParameterValueProvider; -import spoon.pattern.parameter.ParameterValueProviderFactory; - /** * Provides value of parameter */ @@ -83,16 +80,16 @@ public boolean hasValue(String parameterName) { } @Override - public Object get(String parameterName) { + public Object getValue(String parameterName) { Object v = map.get(parameterName); if (v == null && parent != null) { - v = parent.get(parameterName); + v = parent.getValue(parameterName); } return v; } @Override - public ParameterValueProvider putIntoCopy(String parameterName, Object value) { + public ParameterValueProvider putValueToCopy(String parameterName, Object value) { return new UnmodifiableParameterValueProvider(parent, map, parameterName, value); } diff --git a/src/main/java/spoon/template/TemplateMatcher.java b/src/main/java/spoon/template/TemplateMatcher.java index a062cb65b08..551c954e9a9 100644 --- a/src/main/java/spoon/template/TemplateMatcher.java +++ b/src/main/java/spoon/template/TemplateMatcher.java @@ -21,11 +21,11 @@ import spoon.pattern.ModelNode; import spoon.pattern.Pattern; import spoon.pattern.TemplateBuilder; -import spoon.pattern.UnmodifiableParameterValueProvider; import spoon.pattern.matcher.Match; import spoon.pattern.matcher.TobeMatched; import spoon.pattern.parameter.ParameterValueProvider; import spoon.pattern.parameter.ParameterValueProviderFactory; +import spoon.pattern.parameter.UnmodifiableParameterValueProvider; import spoon.reflect.declaration.CtClass; import spoon.reflect.declaration.CtElement; import spoon.reflect.meta.ContainerKind; diff --git a/src/test/java/spoon/test/template/CodeReplaceTest.java b/src/test/java/spoon/test/template/CodeReplaceTest.java index e9bbb889a25..c8843c2fc54 100644 --- a/src/test/java/spoon/test/template/CodeReplaceTest.java +++ b/src/test/java/spoon/test/template/CodeReplaceTest.java @@ -38,22 +38,22 @@ class Context { p.forEachMatch(classDJPP, (match) -> { ParameterValueProvider params = match.getParameters(); if (context.count == 0) { - assertEquals("\"extends\"", params.get("startKeyword").toString()); - assertEquals(Boolean.TRUE, params.get("useStartKeyword")); + assertEquals("\"extends\"", params.getValue("startKeyword").toString()); + assertEquals(Boolean.TRUE, params.getValue("useStartKeyword")); } else { - assertEquals(null, params.get("startKeyword")); - assertEquals(Boolean.FALSE, params.get("useStartKeyword")); + assertEquals(null, params.getValue("startKeyword")); + assertEquals(Boolean.FALSE, params.getValue("useStartKeyword")); } - assertEquals("false", params.get("startPrefixSpace").toString()); - assertEquals("null", params.get("start").toString()); - assertEquals("false", params.get("startSuffixSpace").toString()); - assertEquals("false", params.get("nextPrefixSpace").toString()); - assertEquals("\",\"", params.get("next").toString()); - assertEquals("true", params.get("nextSuffixSpace").toString()); - assertEquals("false", params.get("endPrefixSpace").toString()); - assertEquals("\";\"", params.get("end").toString()); - assertEquals("ctEnum.getEnumValues()", params.get("getIterable").toString()); - assertEquals("[scan(enumValue)]", params.get("statements").toString()); + assertEquals("false", params.getValue("startPrefixSpace").toString()); + assertEquals("null", params.getValue("start").toString()); + assertEquals("false", params.getValue("startSuffixSpace").toString()); + assertEquals("false", params.getValue("nextPrefixSpace").toString()); + assertEquals("\",\"", params.getValue("next").toString()); + assertEquals("true", params.getValue("nextSuffixSpace").toString()); + assertEquals("false", params.getValue("endPrefixSpace").toString()); + assertEquals("\";\"", params.getValue("end").toString()); + assertEquals("ctEnum.getEnumValues()", params.getValue("getIterable").toString()); + assertEquals("[scan(enumValue)]", params.getValue("statements").toString()); context.count++; }); assertEquals(2, context.count); diff --git a/src/test/java/spoon/test/template/TemplateMatcherTest.java b/src/test/java/spoon/test/template/TemplateMatcherTest.java index 5e61764d464..4bb25bd8924 100644 --- a/src/test/java/spoon/test/template/TemplateMatcherTest.java +++ b/src/test/java/spoon/test/template/TemplateMatcherTest.java @@ -60,7 +60,7 @@ public void testMatchForeach() throws Exception { Match match = matches.get(0); assertEquals(Arrays.asList("java.lang.System.out.println(value)"), listToListOfStrings(match.getMatchingElements())); //FIX IT -// assertEquals(Arrays.asList(""), listToListOfStrings((List) match.getParameters().get("values"))); +// assertEquals(Arrays.asList(""), listToListOfStrings((List) match.getParameters().getValue("values"))); } { Match match = matches.get(1); @@ -73,7 +73,7 @@ public void testMatchForeach() throws Exception { "\"a\"", "\"Xxxx\"", "((java.lang.String) (null))", - "java.lang.Long.class.toString()"), listToListOfStrings((List) match.getParameters().get("values"))); + "java.lang.Long.class.toString()"), listToListOfStrings((List) match.getParameters().getValue("values"))); } } @@ -91,7 +91,7 @@ public void testMatchForeachWithOuterSubstitution() throws Exception { Match match = matches.get(0); assertEquals(Arrays.asList("int var = 0"), listToListOfStrings(match.getMatchingElements())); //FIX IT -// assertEquals(Arrays.asList(""), listToListOfStrings((List) match.getParameters().get("values"))); +// assertEquals(Arrays.asList(""), listToListOfStrings((List) match.getParameters().getValue("values"))); } { Match match = matches.get(1); @@ -103,7 +103,7 @@ public void testMatchForeachWithOuterSubstitution() throws Exception { "cc++"), listToListOfStrings(match.getMatchingElements())); assertEquals(Arrays.asList( "\"Xxxx\"", - "((java.lang.String) (null))"), listToListOfStrings((List) match.getParameters().get("values"))); + "((java.lang.String) (null))"), listToListOfStrings((List) match.getParameters().getValue("values"))); } { Match match = matches.get(2); @@ -112,7 +112,7 @@ public void testMatchForeachWithOuterSubstitution() throws Exception { "java.lang.System.out.println(java.lang.Long.class.toString())", "dd++"), listToListOfStrings(match.getMatchingElements())); assertEquals(Arrays.asList( - "java.lang.Long.class.toString()"), listToListOfStrings((List) match.getParameters().get("values"))); + "java.lang.Long.class.toString()"), listToListOfStrings((List) match.getParameters().getValue("values"))); } } @@ -129,51 +129,51 @@ public void testMatchIfElse() throws Exception { { Match match = matches.get(0); assertEquals(Arrays.asList("java.lang.System.out.println(i)"), listToListOfStrings(match.getMatchingElements())); - assertEquals(false, match.getParameters().get("option")); - assertEquals(true, match.getParameters().get("option2")); - assertEquals("i", match.getParameters().get("value").toString()); + assertEquals(false, match.getParameters().getValue("option")); + assertEquals(true, match.getParameters().getValue("option2")); + assertEquals("i", match.getParameters().getValue("value").toString()); } { Match match = matches.get(1); assertEquals(Arrays.asList("java.lang.System.out.println(\"a\")"), listToListOfStrings(match.getMatchingElements())); - assertEquals(true, match.getParameters().get("option")); - assertEquals(false, match.getParameters().get("option2")); - assertEquals("\"a\"", match.getParameters().get("value").toString()); + assertEquals(true, match.getParameters().getValue("option")); + assertEquals(false, match.getParameters().getValue("option2")); + assertEquals("\"a\"", match.getParameters().getValue("value").toString()); } { Match match = matches.get(2); assertEquals(Arrays.asList("java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings(match.getMatchingElements())); - assertEquals(true, match.getParameters().get("option")); - assertEquals(false, match.getParameters().get("option2")); - assertEquals("\"Xxxx\"", match.getParameters().get("value").toString()); + assertEquals(true, match.getParameters().getValue("option")); + assertEquals(false, match.getParameters().getValue("option2")); + assertEquals("\"Xxxx\"", match.getParameters().getValue("value").toString()); } { Match match = matches.get(3); assertEquals(Arrays.asList("java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings(match.getMatchingElements())); - assertEquals(true, match.getParameters().get("option")); - assertEquals(false, match.getParameters().get("option2")); - assertEquals("((java.lang.String) (null))", match.getParameters().get("value").toString()); + assertEquals(true, match.getParameters().getValue("option")); + assertEquals(false, match.getParameters().getValue("option2")); + assertEquals("((java.lang.String) (null))", match.getParameters().getValue("value").toString()); } { Match match = matches.get(4); assertEquals(Arrays.asList("java.lang.System.out.println(2018)"), listToListOfStrings(match.getMatchingElements())); - assertEquals(false, match.getParameters().get("option")); - assertEquals(true, match.getParameters().get("option2")); - assertEquals("2018", match.getParameters().get("value").toString()); + assertEquals(false, match.getParameters().getValue("option")); + assertEquals(true, match.getParameters().getValue("option2")); + assertEquals("2018", match.getParameters().getValue("value").toString()); } { Match match = matches.get(5); assertEquals(Arrays.asList("java.lang.System.out.println(java.lang.Long.class.toString())"), listToListOfStrings(match.getMatchingElements())); - assertEquals(true, match.getParameters().get("option")); - assertEquals(false, match.getParameters().get("option2")); - assertEquals("java.lang.Long.class.toString()", match.getParameters().get("value").toString()); + assertEquals(true, match.getParameters().getValue("option")); + assertEquals(false, match.getParameters().getValue("option2")); + assertEquals("java.lang.Long.class.toString()", match.getParameters().getValue("value").toString()); } { Match match = matches.get(6); assertEquals(Arrays.asList("java.lang.System.out.println(3.14)"), listToListOfStrings(match.getMatchingElements())); - assertEquals(false, match.getParameters().get("option")); - assertEquals(false, match.getParameters().get("option2")); - assertEquals("3.14", match.getParameters().get("value").toString()); + assertEquals(false, match.getParameters().getValue("option")); + assertEquals(false, match.getParameters().getValue("option2")); + assertEquals("3.14", match.getParameters().getValue("value").toString()); } } @Test @@ -216,10 +216,10 @@ public void testMatchGreedyMultiValueUnlimited() throws Exception { "i++", "java.lang.System.out.println(i)", "java.lang.System.out.println(\"Xxxx\")", - "java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings((List) match.getParameters().get("statements"))); + "java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings((List) match.getParameters().getValue("statements"))); //last statement is matched by last template, which saves printed value - assertTrue(match.getParameters().get("printedValue") instanceof CtLiteral); - assertEquals("\"last one\"", match.getParameters().get("printedValue").toString()); + assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); + assertEquals("\"last one\"", match.getParameters().getValue("printedValue").toString()); } @Test @@ -245,10 +245,10 @@ public void testMatchGreedyMultiValueMaxCountLimit() throws Exception { assertEquals(Arrays.asList( "int i = 0", "i++", - "java.lang.System.out.println(i)"), listToListOfStrings((List) match.getParameters().get("statements"))); + "java.lang.System.out.println(i)"), listToListOfStrings((List) match.getParameters().getValue("statements"))); //4th statement is matched by last template, which saves printed value - assertTrue(match.getParameters().get("printedValue") instanceof CtLiteral); - assertEquals("\"Xxxx\"", match.getParameters().get("printedValue").toString()); + assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); + assertEquals("\"Xxxx\"", match.getParameters().getValue("printedValue").toString()); } { Match match = matches.get(1); @@ -259,10 +259,10 @@ public void testMatchGreedyMultiValueMaxCountLimit() throws Exception { //check all statements excluding last are stored as value of "statements" parameter assertEquals(Arrays.asList( - "java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings((List) match.getParameters().get("statements"))); + "java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings((List) match.getParameters().getValue("statements"))); //last statement is matched by last template, which saves printed value - assertTrue(match.getParameters().get("printedValue") instanceof CtLiteral); - assertEquals("\"last one\"", match.getParameters().get("printedValue").toString()); + assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); + assertEquals("\"last one\"", match.getParameters().getValue("printedValue").toString()); } } @@ -291,10 +291,10 @@ public void testMatchReluctantMultivalue() throws Exception { assertEquals(Arrays.asList( "int i = 0", "i++", - "java.lang.System.out.println(i)"), listToListOfStrings((List) match.getParameters().get("statements"))); + "java.lang.System.out.println(i)"), listToListOfStrings((List) match.getParameters().getValue("statements"))); //last statement is matched by last template, which saves printed value - assertTrue(match.getParameters().get("printedValue") instanceof CtLiteral); - assertEquals("\"Xxxx\"", match.getParameters().get("printedValue").toString()); + assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); + assertEquals("\"Xxxx\"", match.getParameters().getValue("printedValue").toString()); } { Match match = matches.get(1); @@ -303,10 +303,10 @@ public void testMatchReluctantMultivalue() throws Exception { "java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings(match.getMatchingElements())); //check all statements excluding last are stored as value of "statements" parameter - assertEquals(Arrays.asList(), listToListOfStrings((List) match.getParameters().get("statements"))); + assertEquals(Arrays.asList(), listToListOfStrings((List) match.getParameters().getValue("statements"))); //last statement is matched by last template, which saves printed value - assertTrue(match.getParameters().get("printedValue") instanceof CtLiteral); - assertEquals("((java.lang.String) (null))", match.getParameters().get("printedValue").toString()); + assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); + assertEquals("((java.lang.String) (null))", match.getParameters().getValue("printedValue").toString()); } { Match match = matches.get(2); @@ -315,10 +315,10 @@ public void testMatchReluctantMultivalue() throws Exception { "java.lang.System.out.println(\"last one\")"), listToListOfStrings(match.getMatchingElements())); //check all statements excluding last are stored as value of "statements" parameter - assertEquals(Arrays.asList(), listToListOfStrings((List) match.getParameters().get("statements"))); + assertEquals(Arrays.asList(), listToListOfStrings((List) match.getParameters().getValue("statements"))); //last statement is matched by last template, which saves printed value - assertTrue(match.getParameters().get("printedValue") instanceof CtLiteral); - assertEquals("\"last one\"", match.getParameters().get("printedValue").toString()); + assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); + assertEquals("\"last one\"", match.getParameters().getValue("printedValue").toString()); } } @Test @@ -345,10 +345,10 @@ public void testMatchReluctantMultivalueMinCount1() throws Exception { assertEquals(Arrays.asList( "int i = 0", "i++", - "java.lang.System.out.println(i)"), listToListOfStrings((List) match.getParameters().get("statements"))); + "java.lang.System.out.println(i)"), listToListOfStrings((List) match.getParameters().getValue("statements"))); //last statement is matched by last template, which saves printed value - assertTrue(match.getParameters().get("printedValue") instanceof CtLiteral); - assertEquals("\"Xxxx\"", match.getParameters().get("printedValue").toString()); + assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); + assertEquals("\"Xxxx\"", match.getParameters().getValue("printedValue").toString()); } { Match match = matches.get(1); @@ -359,10 +359,10 @@ public void testMatchReluctantMultivalueMinCount1() throws Exception { //check all statements excluding last are stored as value of "statements" parameter assertEquals(Arrays.asList( - "java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings((List) match.getParameters().get("statements"))); + "java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings((List) match.getParameters().getValue("statements"))); //last statement is matched by last template, which saves printed value - assertTrue(match.getParameters().get("printedValue") instanceof CtLiteral); - assertEquals("\"last one\"", match.getParameters().get("printedValue").toString()); + assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); + assertEquals("\"last one\"", match.getParameters().getValue("printedValue").toString()); } } @Test @@ -387,10 +387,10 @@ public void testMatchReluctantMultivalueExactly2() throws Exception { //check 2 statements excluding last are stored as value of "statements" parameter assertEquals(Arrays.asList( "i++", - "java.lang.System.out.println(i)"), listToListOfStrings((List) match.getParameters().get("statements"))); + "java.lang.System.out.println(i)"), listToListOfStrings((List) match.getParameters().getValue("statements"))); //last statement is matched by last template, which saves printed value - assertTrue(match.getParameters().get("printedValue") instanceof CtLiteral); - assertEquals("\"Xxxx\"", match.getParameters().get("printedValue").toString()); + assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); + assertEquals("\"Xxxx\"", match.getParameters().getValue("printedValue").toString()); } } @@ -429,10 +429,10 @@ public void testMatchPossesiveMultiValueMaxCount4() throws Exception { "int i = 0", "i++", "java.lang.System.out.println(i)", - "java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings((List) match.getParameters().get("statements"))); + "java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings((List) match.getParameters().getValue("statements"))); //last statement is matched by last template, which saves printed value - assertTrue(match.getParameters().get("printedValue") instanceof CtLiteral); - assertEquals("((java.lang.String) (null))", match.getParameters().get("printedValue").toString()); + assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); + assertEquals("((java.lang.String) (null))", match.getParameters().getValue("printedValue").toString()); } @Test public void testMatchPossesiveMultiValueMinCount() throws Exception { @@ -449,8 +449,8 @@ public void testMatchPossesiveMultiValueMinCount() throws Exception { if (count < 6) { //the last template has nothing to match -> no match assertEquals("count="+count, 1, matches.size()); - assertEquals("count="+count, 5-count, getSize(matches.get(0).getParameters().get("statements1"))); - assertEquals("count="+count, count, getSize(matches.get(0).getParameters().get("statements2"))); + assertEquals("count="+count, 5-count, getSize(matches.get(0).getParameters().getValue("statements1"))); + assertEquals("count="+count, count, getSize(matches.get(0).getParameters().getValue("statements2"))); } else { //the possessive matcher eat too much. There is no target element for last `printedValue` variable assertEquals("count="+count, 0, matches.size()); @@ -474,9 +474,9 @@ public void testMatchPossesiveMultiValueMinCount2() throws Exception { if (count < 5) { //the last template has nothing to match -> no match assertEquals("count="+count, 1, matches.size()); - assertEquals("count="+count, 4-count, getSize(matches.get(0).getParameters().get("statements1"))); - assertEquals("count="+count, count, getSize(matches.get(0).getParameters().get("statements2"))); - assertEquals("count="+count, 2, getSize(matches.get(0).getParameters().get("printedValue"))); + assertEquals("count="+count, 4-count, getSize(matches.get(0).getParameters().getValue("statements1"))); + assertEquals("count="+count, count, getSize(matches.get(0).getParameters().getValue("statements2"))); + assertEquals("count="+count, 2, getSize(matches.get(0).getParameters().getValue("printedValue"))); } else { //the possessive matcher eat too much. There is no target element for last `printedValue` variable assertEquals("count="+count, 0, matches.size()); @@ -499,9 +499,9 @@ public void testMatchGreedyMultiValueMinCount2() throws Exception { if (count < 7) { //the last template has nothing to match -> no match assertEquals("count="+count, 1, matches.size()); - assertEquals("count="+count, Math.max(0, 3-count), getSize(matches.get(0).getParameters().get("statements1"))); - assertEquals("count="+count, count - Math.max(0, count-4), getSize(matches.get(0).getParameters().get("statements2"))); - assertEquals("count="+count, Math.max(2, 3 - Math.max(0, count-3)), getSize(matches.get(0).getParameters().get("printedValue"))); + assertEquals("count="+count, Math.max(0, 3-count), getSize(matches.get(0).getParameters().getValue("statements1"))); + assertEquals("count="+count, count - Math.max(0, count-4), getSize(matches.get(0).getParameters().getValue("statements2"))); + assertEquals("count="+count, Math.max(2, 3 - Math.max(0, count-3)), getSize(matches.get(0).getParameters().getValue("printedValue"))); } else { //the possessive matcher eat too much. There is no target element for last `printedValue` variable assertEquals("count="+count, 0, matches.size()); @@ -533,7 +533,7 @@ public void testMatchParameterValue() throws Exception { { Match match = matches.get(0); assertEquals(Arrays.asList("java.lang.System.out.println(value)"), listToListOfStrings(match.getMatchingElements())); - Object value = match.getParameters().get("value"); + Object value = match.getParameters().getValue("value"); assertTrue(value instanceof CtVariableRead); assertEquals("value", value.toString()); //contract: the value is reference to found node (not a clone) @@ -543,26 +543,26 @@ public void testMatchParameterValue() throws Exception { { Match match = matches.get(1); assertEquals(Arrays.asList("java.lang.System.out.println(\"a\")"), listToListOfStrings(match.getMatchingElements())); - assertTrue(match.getParameters().get("value") instanceof CtLiteral); - assertEquals("\"a\"", match.getParameters().get("value").toString()); + assertTrue(match.getParameters().getValue("value") instanceof CtLiteral); + assertEquals("\"a\"", match.getParameters().getValue("value").toString()); } { Match match = matches.get(2); assertEquals(Arrays.asList("java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings(match.getMatchingElements())); - assertTrue(match.getParameters().get("value") instanceof CtLiteral); - assertEquals("\"Xxxx\"", match.getParameters().get("value").toString()); + assertTrue(match.getParameters().getValue("value") instanceof CtLiteral); + assertEquals("\"Xxxx\"", match.getParameters().getValue("value").toString()); } { Match match = matches.get(3); assertEquals(Arrays.asList("java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings(match.getMatchingElements())); - assertTrue(match.getParameters().get("value") instanceof CtLiteral); - assertEquals("((java.lang.String) (null))", match.getParameters().get("value").toString()); + assertTrue(match.getParameters().getValue("value") instanceof CtLiteral); + assertEquals("((java.lang.String) (null))", match.getParameters().getValue("value").toString()); } { Match match = matches.get(4); assertEquals(Arrays.asList("java.lang.System.out.println(java.lang.Long.class.toString())"), listToListOfStrings(match.getMatchingElements())); - assertTrue(match.getParameters().get("value") instanceof CtInvocation); - assertEquals("java.lang.Long.class.toString()", match.getParameters().get("value").toString()); + assertTrue(match.getParameters().getValue("value") instanceof CtInvocation); + assertEquals("java.lang.Long.class.toString()", match.getParameters().getValue("value").toString()); } } @@ -579,20 +579,20 @@ public void testMatchParameterValueType() throws Exception { { Match match = matches.get(0); assertEquals(Arrays.asList("java.lang.System.out.println(\"a\")"), listToListOfStrings(match.getMatchingElements())); - assertTrue(match.getParameters().get("value") instanceof CtLiteral); - assertEquals("\"a\"", match.getParameters().get("value").toString()); + assertTrue(match.getParameters().getValue("value") instanceof CtLiteral); + assertEquals("\"a\"", match.getParameters().getValue("value").toString()); } { Match match = matches.get(1); assertEquals(Arrays.asList("java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings(match.getMatchingElements())); - assertTrue(match.getParameters().get("value") instanceof CtLiteral); - assertEquals("\"Xxxx\"", match.getParameters().get("value").toString()); + assertTrue(match.getParameters().getValue("value") instanceof CtLiteral); + assertEquals("\"Xxxx\"", match.getParameters().getValue("value").toString()); } { Match match = matches.get(2); assertEquals(Arrays.asList("java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings(match.getMatchingElements())); - assertTrue(match.getParameters().get("value") instanceof CtLiteral); - assertEquals("((java.lang.String) (null))", match.getParameters().get("value").toString()); + assertTrue(match.getParameters().getValue("value") instanceof CtLiteral); + assertEquals("((java.lang.String) (null))", match.getParameters().getValue("value").toString()); } } { @@ -604,8 +604,8 @@ public void testMatchParameterValueType() throws Exception { { Match match = matches.get(0); assertEquals(Arrays.asList("java.lang.System.out.println(java.lang.Long.class.toString())"), listToListOfStrings(match.getMatchingElements())); - assertTrue(match.getParameters().get("value") instanceof CtInvocation); - assertEquals("java.lang.Long.class.toString()", match.getParameters().get("value").toString()); + assertTrue(match.getParameters().getValue("value") instanceof CtInvocation); + assertEquals("java.lang.Long.class.toString()", match.getParameters().getValue("value").toString()); } } @@ -625,20 +625,20 @@ public void testMatchParameterCondition() throws Exception { { Match match = matches.get(0); assertEquals(Arrays.asList("java.lang.System.out.println(\"a\")"), listToListOfStrings(match.getMatchingElements())); - assertTrue(match.getParameters().get("value") instanceof CtLiteral); - assertEquals("\"a\"", match.getParameters().get("value").toString()); + assertTrue(match.getParameters().getValue("value") instanceof CtLiteral); + assertEquals("\"a\"", match.getParameters().getValue("value").toString()); } { Match match = matches.get(1); assertEquals(Arrays.asList("java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings(match.getMatchingElements())); - assertTrue(match.getParameters().get("value") instanceof CtLiteral); - assertEquals("\"Xxxx\"", match.getParameters().get("value").toString()); + assertTrue(match.getParameters().getValue("value") instanceof CtLiteral); + assertEquals("\"Xxxx\"", match.getParameters().getValue("value").toString()); } { Match match = matches.get(2); assertEquals(Arrays.asList("java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings(match.getMatchingElements())); - assertTrue(match.getParameters().get("value") instanceof CtLiteral); - assertEquals("((java.lang.String) (null))", match.getParameters().get("value").toString()); + assertTrue(match.getParameters().getValue("value") instanceof CtLiteral); + assertEquals("((java.lang.String) (null))", match.getParameters().getValue("value").toString()); } } } diff --git a/src/test/java/spoon/test/template/core/ParameterInfoTest.java b/src/test/java/spoon/test/template/core/ParameterInfoTest.java index 48ab6d8664f..3d1a187d1a1 100644 --- a/src/test/java/spoon/test/template/core/ParameterInfoTest.java +++ b/src/test/java/spoon/test/template/core/ParameterInfoTest.java @@ -18,12 +18,12 @@ import org.junit.Test; -import spoon.pattern.UnmodifiableParameterValueProvider; import spoon.pattern.parameter.ListParameterInfo; import spoon.pattern.parameter.MapParameterInfo; import spoon.pattern.parameter.ParameterInfo; import spoon.pattern.parameter.ParameterValueProvider; import spoon.pattern.parameter.SetParameterInfo; +import spoon.pattern.parameter.UnmodifiableParameterValueProvider; import spoon.reflect.meta.ContainerKind; public class ParameterInfoTest { @@ -75,7 +75,7 @@ public void testSingleValueParameterByNameIntoEmptyContainer() { public void testSingleValueParameterByNameWhenAlreadyExists() { ParameterInfo namedParam = new MapParameterInfo("year"); {//adding value into container, which already contains that value changes nothing and returns origin container - ParameterValueProvider oldContainer = new UnmodifiableParameterValueProvider().putIntoCopy("year", 2018); + ParameterValueProvider oldContainer = new UnmodifiableParameterValueProvider().putValueToCopy("year", 2018); assertEquals(map().put("year", 2018), oldContainer.asMap()); //it returned the same container assertSame(oldContainer, namedParam.addValueAs(oldContainer, 2018)); @@ -86,7 +86,7 @@ public void testSingleValueParameterByNameWhenAlreadyExists() { public void testSingleValueParameterByNameWhenDifferentExists() { ParameterInfo namedParam = new MapParameterInfo("year"); {//adding a value into container, which already contains a different value returns null - no match - ParameterValueProvider oldContainer = new UnmodifiableParameterValueProvider().putIntoCopy("year", 2018); + ParameterValueProvider oldContainer = new UnmodifiableParameterValueProvider().putValueToCopy("year", 2018); assertNull(namedParam.addValueAs(oldContainer, 2111)); assertNull(namedParam.addValueAs(oldContainer, 0)); assertNull(namedParam.addValueAs(oldContainer, null)); @@ -100,7 +100,7 @@ public void testOptionalSingleValueParameterByName() { .setMinOccurences(0); {//adding null value into an container with minCount == 0, returns unchanged container. //because minCount == 0 means that value is optional - ParameterValueProvider container = new UnmodifiableParameterValueProvider().putIntoCopy("a", "b"); + ParameterValueProvider container = new UnmodifiableParameterValueProvider().putValueToCopy("a", "b"); assertSame(container, namedParam.addValueAs(container, null)); assertEquals(map().put("a", "b"), container.asMap()); } @@ -112,7 +112,7 @@ public void testMandatorySingleValueParameterByName() { ParameterInfo namedParam = new MapParameterInfo("year") .setMinOccurences(1); { - ParameterValueProvider container = new UnmodifiableParameterValueProvider().putIntoCopy("a", "b"); + ParameterValueProvider container = new UnmodifiableParameterValueProvider().putValueToCopy("a", "b"); assertNull(namedParam.addValueAs(container, null)); assertEquals(map().put("a", "b"), container.asMap()); } @@ -129,7 +129,7 @@ public void testSingleValueParameterByNameConditionalMatcher() { assertNull(namedParam.addValueAs(null, 1000)); assertNull(namedParam.addValueAs(null, "3000")); //even matching value is STILL not accepted when there is already a different value - assertNull(namedParam.addValueAs(new UnmodifiableParameterValueProvider().putIntoCopy("year", 3000), 2018)); + assertNull(namedParam.addValueAs(new UnmodifiableParameterValueProvider().putValueToCopy("year", 3000), 2018)); } @Test @@ -157,7 +157,7 @@ public void testListParameterByNameIntoEmptyContainer() { public void testListParameterByNameIntoEmptyContainerWithEmptyList() { Consumer check = (namedParam) -> {//adding value into container, which already contains a empty list, creates a new container with List which contains that value - ParameterValueProvider empty = new UnmodifiableParameterValueProvider().putIntoCopy("year", Collections.emptyList()); + ParameterValueProvider empty = new UnmodifiableParameterValueProvider().putValueToCopy("year", Collections.emptyList()); ParameterValueProvider val = namedParam.addValueAs(empty, 2018); //adding same value - adds the second value again @@ -193,11 +193,11 @@ public void testMergeOnDifferentValueTypeContainers() { assertNull(parameter.addValueAs(params, null)); }; ParameterValueProvider empty = new UnmodifiableParameterValueProvider(); - checker.accept(new MapParameterInfo("year"), empty.putIntoCopy("year", "x")); - checker.accept(new ListParameterInfo(0, new MapParameterInfo("year")), empty.putIntoCopy("year", Collections.singletonList("x"))); - checker.accept(new ListParameterInfo(1, new MapParameterInfo("year")), empty.putIntoCopy("year", Arrays.asList("zz","x"))); - checker.accept(new MapParameterInfo("key", new ListParameterInfo(1, new MapParameterInfo("year"))), empty.putIntoCopy("year", Arrays.asList("zz",empty.putIntoCopy("key", "x")))); - checker.accept(new MapParameterInfo("key", new MapParameterInfo("year")), empty.putIntoCopy("year", empty.putIntoCopy("key", "x"))); + checker.accept(new MapParameterInfo("year"), empty.putValueToCopy("year", "x")); + checker.accept(new ListParameterInfo(0, new MapParameterInfo("year")), empty.putValueToCopy("year", Collections.singletonList("x"))); + checker.accept(new ListParameterInfo(1, new MapParameterInfo("year")), empty.putValueToCopy("year", Arrays.asList("zz","x"))); + checker.accept(new MapParameterInfo("key", new ListParameterInfo(1, new MapParameterInfo("year"))), empty.putValueToCopy("year", Arrays.asList("zz",empty.putValueToCopy("key", "x")))); + checker.accept(new MapParameterInfo("key", new MapParameterInfo("year")), empty.putValueToCopy("year", empty.putValueToCopy("key", "x"))); } @Test @@ -264,7 +264,7 @@ public void testMapEntryInParameterByName() { final ParameterValueProvider val = namedParam.addValueAs(empty, entry("year", 2018)); assertNotNull(val); - assertEquals(map().put("map", new UnmodifiableParameterValueProvider().putIntoCopy("year", 2018)), val.asMap()); + assertEquals(map().put("map", new UnmodifiableParameterValueProvider().putValueToCopy("year", 2018)), val.asMap()); //adding null entry changes nothing assertSame(val, namedParam.addValueAs(val, null)); @@ -276,19 +276,19 @@ public void testMapEntryInParameterByName() { ParameterValueProvider val2 = namedParam.addValueAs(val, entry("age", "best")); assertNotNull(val2); assertEquals(map().put("map", new UnmodifiableParameterValueProvider() - .putIntoCopy("year", 2018) - .putIntoCopy("age", "best")), val2.asMap()); + .putValueToCopy("year", 2018) + .putValueToCopy("age", "best")), val2.asMap()); //after all the once returned val is still the same - unmodified - assertEquals(map().put("map", new UnmodifiableParameterValueProvider().putIntoCopy("year", 2018)), val.asMap()); + assertEquals(map().put("map", new UnmodifiableParameterValueProvider().putValueToCopy("year", 2018)), val.asMap()); }; checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider()); - checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider().putIntoCopy("map", null)); - checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider().putIntoCopy("map", Collections.emptyMap())); + checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider().putValueToCopy("map", null)); + checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider().putValueToCopy("map", Collections.emptyMap())); //the map container is detected automatically from the type of value - checker.accept(new MapParameterInfo("map"), new UnmodifiableParameterValueProvider().putIntoCopy("map", Collections.emptyMap())); + checker.accept(new MapParameterInfo("map"), new UnmodifiableParameterValueProvider().putValueToCopy("map", Collections.emptyMap())); //the map container is detected automatically from the type of value - checker.accept(new MapParameterInfo("map"), new UnmodifiableParameterValueProvider().putIntoCopy("map", new UnmodifiableParameterValueProvider())); + checker.accept(new MapParameterInfo("map"), new UnmodifiableParameterValueProvider().putValueToCopy("map", new UnmodifiableParameterValueProvider())); } @Test public void testAddMapIntoParameterByName() { @@ -297,11 +297,11 @@ public void testAddMapIntoParameterByName() { ParameterValueProvider val = namedParam.addValueAs(empty, Collections.emptyMap()); assertEquals(map().put("map", new UnmodifiableParameterValueProvider()), val.asMap()); val = namedParam.addValueAs(empty, map().put("year", 2018)); - assertEquals(map().put("map", new UnmodifiableParameterValueProvider().putIntoCopy("year", 2018)), val.asMap()); + assertEquals(map().put("map", new UnmodifiableParameterValueProvider().putValueToCopy("year", 2018)), val.asMap()); val = namedParam.addValueAs(empty, map().put("year", 2018).put("age", 1111)); assertEquals(map().put("map", new UnmodifiableParameterValueProvider() - .putIntoCopy("year", 2018) - .putIntoCopy("age", 1111)), val.asMap()); + .putValueToCopy("year", 2018) + .putValueToCopy("age", 1111)), val.asMap()); //adding null entry changes nothing assertSame(val, namedParam.addValueAs(val, null)); @@ -311,12 +311,12 @@ public void testAddMapIntoParameterByName() { assertNull(namedParam.addValueAs(val, entry("year", 1111))); }; checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider()); - checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider().putIntoCopy("map", null)); - checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider().putIntoCopy("map", Collections.emptyMap())); + checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider().putValueToCopy("map", null)); + checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider().putValueToCopy("map", Collections.emptyMap())); //the map container is detected automatically from the type of value - checker.accept(new MapParameterInfo("map"), new UnmodifiableParameterValueProvider().putIntoCopy("map", Collections.emptyMap())); + checker.accept(new MapParameterInfo("map"), new UnmodifiableParameterValueProvider().putValueToCopy("map", Collections.emptyMap())); //the map container is detected automatically from the type of value - checker.accept(new MapParameterInfo("map"), new UnmodifiableParameterValueProvider().putIntoCopy("map", new UnmodifiableParameterValueProvider())); + checker.accept(new MapParameterInfo("map"), new UnmodifiableParameterValueProvider().putValueToCopy("map", new UnmodifiableParameterValueProvider())); //the map container is detected automatically from the type of new value checker.accept(new MapParameterInfo("map"), null); } @@ -336,12 +336,12 @@ public void testAddListIntoParameterByName() { assertSame(val, namedParam.addValueAs(val, null)); }; checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.LIST), new UnmodifiableParameterValueProvider()); - checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.LIST), new UnmodifiableParameterValueProvider().putIntoCopy("list", null)); - checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.LIST), new UnmodifiableParameterValueProvider().putIntoCopy("list", Collections.emptyList())); + checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.LIST), new UnmodifiableParameterValueProvider().putValueToCopy("list", null)); + checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.LIST), new UnmodifiableParameterValueProvider().putValueToCopy("list", Collections.emptyList())); //Set can be converted to List - checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.LIST), new UnmodifiableParameterValueProvider().putIntoCopy("list", Collections.emptySet())); + checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.LIST), new UnmodifiableParameterValueProvider().putValueToCopy("list", Collections.emptySet())); //the list container is detected automatically from the type of value - checker.accept(new MapParameterInfo("list"), new UnmodifiableParameterValueProvider().putIntoCopy("list", Collections.emptyList())); + checker.accept(new MapParameterInfo("list"), new UnmodifiableParameterValueProvider().putValueToCopy("list", Collections.emptyList())); //the list container is detected automatically from the type of new value checker.accept(new MapParameterInfo("list"), null); } @@ -366,12 +366,12 @@ public void testAddSetIntoParameterByName() { assertSame(val, namedParam.addValueAs(val, asSet(2018, 1111))); }; checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.SET), new UnmodifiableParameterValueProvider()); - checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.SET), new UnmodifiableParameterValueProvider().putIntoCopy("list", null)); - checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.SET), new UnmodifiableParameterValueProvider().putIntoCopy("list", Collections.emptySet())); + checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.SET), new UnmodifiableParameterValueProvider().putValueToCopy("list", null)); + checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.SET), new UnmodifiableParameterValueProvider().putValueToCopy("list", Collections.emptySet())); //The container kind has higher priority, so List will be converted to Set - checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.SET), new UnmodifiableParameterValueProvider().putIntoCopy("list", Collections.emptyList())); + checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.SET), new UnmodifiableParameterValueProvider().putValueToCopy("list", Collections.emptyList())); //the list container is detected automatically from the type of value - checker.accept(new MapParameterInfo("list"), new UnmodifiableParameterValueProvider().putIntoCopy("list", Collections.emptySet())); + checker.accept(new MapParameterInfo("list"), new UnmodifiableParameterValueProvider().putValueToCopy("list", Collections.emptySet())); //the list container is detected automatically from the type of new value checker.accept(new MapParameterInfo("list"), null); } @@ -379,7 +379,7 @@ public void testAddSetIntoParameterByName() { public void testFailOnUnpectedContainer() { ParameterInfo namedParam = new MapParameterInfo("year").setContainerKind(ContainerKind.LIST); try { - namedParam.addValueAs(new UnmodifiableParameterValueProvider().putIntoCopy("year", "unexpected"), 1); + namedParam.addValueAs(new UnmodifiableParameterValueProvider().putValueToCopy("year", "unexpected"), 1); fail(); } catch (Exception e) { //OK From f9a5060b86fdabae2219a5214f533407d550bf35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Wed, 7 Mar 2018 14:39:56 +0100 Subject: [PATCH 009/131] All Node classes moved to package node --- .../spoon/pattern/ConflictResolutionMode.java | 1 + .../spoon/pattern/LiveStatementsBuilder.java | 6 +++ .../java/spoon/pattern/ParametersBuilder.java | 5 +++ src/main/java/spoon/pattern/Pattern.java | 1 + .../java/spoon/pattern/PatternBuilder.java | 8 +++- .../java/spoon/pattern/PatternPrinter.java | 1 + src/main/java/spoon/pattern/ResultHolder.java | 2 +- .../pattern/SubstitutionRequestProvider.java | 2 + .../pattern/matcher/ChainOfMatchersImpl.java | 2 +- .../java/spoon/pattern/matcher/Matchers.java | 2 +- .../pattern/matcher/MatchingScanner.java | 2 +- .../pattern/matcher/NodeListMatcher.java | 38 ------------------- .../spoon/pattern/matcher/Quantifier.java | 2 +- .../{ => node}/AbstractPrimitiveMatcher.java | 2 +- .../{ => node}/AbstractRepeatableMatcher.java | 2 +- .../pattern/{ => node}/ConstantNode.java | 3 +- .../spoon/pattern/{ => node}/ElementNode.java | 3 +- .../spoon/pattern/{ => node}/ForEachNode.java | 3 +- .../spoon/pattern/{ => node}/ListOfNodes.java | 3 +- .../{matcher => node}/MapEntryNode.java | 5 ++- .../spoon/pattern/{ => node}/ModelNode.java | 6 ++- .../java/spoon/pattern/{ => node}/Node.java | 5 ++- .../pattern/{ => node}/ParameterNode.java | 3 +- .../pattern/{ => node}/PrimitiveMatcher.java | 2 +- .../pattern/{ => node}/RepeatableMatcher.java | 2 +- .../spoon/pattern/{ => node}/StringNode.java | 4 +- .../spoon/pattern/{ => node}/SwitchNode.java | 3 +- .../pattern/parameter/ParameterInfo.java | 2 +- .../java/spoon/template/TemplateMatcher.java | 2 +- 29 files changed, 60 insertions(+), 62 deletions(-) delete mode 100644 src/main/java/spoon/pattern/matcher/NodeListMatcher.java rename src/main/java/spoon/pattern/{ => node}/AbstractPrimitiveMatcher.java (97%) rename src/main/java/spoon/pattern/{ => node}/AbstractRepeatableMatcher.java (99%) rename src/main/java/spoon/pattern/{ => node}/ConstantNode.java (97%) rename src/main/java/spoon/pattern/{ => node}/ElementNode.java (99%) rename src/main/java/spoon/pattern/{ => node}/ForEachNode.java (98%) rename src/main/java/spoon/pattern/{ => node}/ListOfNodes.java (97%) rename src/main/java/spoon/pattern/{matcher => node}/MapEntryNode.java (94%) rename src/main/java/spoon/pattern/{ => node}/ModelNode.java (98%) rename src/main/java/spoon/pattern/{ => node}/Node.java (96%) rename src/main/java/spoon/pattern/{ => node}/ParameterNode.java (97%) rename src/main/java/spoon/pattern/{ => node}/PrimitiveMatcher.java (97%) rename src/main/java/spoon/pattern/{ => node}/RepeatableMatcher.java (98%) rename src/main/java/spoon/pattern/{ => node}/StringNode.java (98%) rename src/main/java/spoon/pattern/{ => node}/SwitchNode.java (98%) diff --git a/src/main/java/spoon/pattern/ConflictResolutionMode.java b/src/main/java/spoon/pattern/ConflictResolutionMode.java index 426ea0ad0c1..7a9e8dc4f02 100644 --- a/src/main/java/spoon/pattern/ConflictResolutionMode.java +++ b/src/main/java/spoon/pattern/ConflictResolutionMode.java @@ -17,6 +17,7 @@ package spoon.pattern; import spoon.SpoonException; +import spoon.pattern.node.Node; /** * Defines what happens when before explicitly added {@link Node} has to be replaced by another {@link Node} diff --git a/src/main/java/spoon/pattern/LiveStatementsBuilder.java b/src/main/java/spoon/pattern/LiveStatementsBuilder.java index 9d6645f5616..45cae7b79ad 100644 --- a/src/main/java/spoon/pattern/LiveStatementsBuilder.java +++ b/src/main/java/spoon/pattern/LiveStatementsBuilder.java @@ -21,6 +21,12 @@ import java.util.function.BiConsumer; import spoon.SpoonException; +import spoon.pattern.node.ForEachNode; +import spoon.pattern.node.ListOfNodes; +import spoon.pattern.node.Node; +import spoon.pattern.node.ParameterNode; +import spoon.pattern.node.PrimitiveMatcher; +import spoon.pattern.node.SwitchNode; import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtExpression; import spoon.reflect.code.CtForEach; diff --git a/src/main/java/spoon/pattern/ParametersBuilder.java b/src/main/java/spoon/pattern/ParametersBuilder.java index 393eda6fce0..662900bf688 100644 --- a/src/main/java/spoon/pattern/ParametersBuilder.java +++ b/src/main/java/spoon/pattern/ParametersBuilder.java @@ -23,6 +23,11 @@ import spoon.SpoonException; import spoon.pattern.matcher.Quantifier; +import spoon.pattern.node.ConstantNode; +import spoon.pattern.node.ModelNode; +import spoon.pattern.node.Node; +import spoon.pattern.node.ParameterNode; +import spoon.pattern.node.StringNode; import spoon.pattern.parameter.AbstractParameterInfo; import spoon.pattern.parameter.ListParameterInfo; import spoon.pattern.parameter.MapParameterInfo; diff --git a/src/main/java/spoon/pattern/Pattern.java b/src/main/java/spoon/pattern/Pattern.java index df44e82a8df..fda988d663e 100644 --- a/src/main/java/spoon/pattern/Pattern.java +++ b/src/main/java/spoon/pattern/Pattern.java @@ -27,6 +27,7 @@ import spoon.SpoonException; import spoon.pattern.matcher.Match; import spoon.pattern.matcher.MatchingScanner; +import spoon.pattern.node.ModelNode; import spoon.pattern.parameter.ParameterInfo; import spoon.pattern.parameter.ParameterValueProvider; import spoon.pattern.parameter.ParameterValueProviderFactory; diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index 05737f7d96b..0d1a2709ae7 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -35,8 +35,14 @@ import spoon.Metamodel; import spoon.SpoonException; import spoon.pattern.ParametersBuilder.ParameterElementPair; -import spoon.pattern.matcher.MapEntryNode; import spoon.pattern.matcher.Matchers; +import spoon.pattern.node.ConstantNode; +import spoon.pattern.node.ElementNode; +import spoon.pattern.node.ListOfNodes; +import spoon.pattern.node.MapEntryNode; +import spoon.pattern.node.ModelNode; +import spoon.pattern.node.Node; +import spoon.pattern.node.ParameterNode; import spoon.pattern.parameter.AbstractParameterInfo; import spoon.pattern.parameter.ParameterInfo; import spoon.reflect.code.CtBlock; diff --git a/src/main/java/spoon/pattern/PatternPrinter.java b/src/main/java/spoon/pattern/PatternPrinter.java index 01401126a62..e67e22e11eb 100644 --- a/src/main/java/spoon/pattern/PatternPrinter.java +++ b/src/main/java/spoon/pattern/PatternPrinter.java @@ -20,6 +20,7 @@ import java.util.Map; import spoon.SpoonException; +import spoon.pattern.node.Node; import spoon.pattern.parameter.AbstractParameterInfo; import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.code.CtInvocation; diff --git a/src/main/java/spoon/pattern/ResultHolder.java b/src/main/java/spoon/pattern/ResultHolder.java index 85a324e6a57..f30c086494e 100644 --- a/src/main/java/spoon/pattern/ResultHolder.java +++ b/src/main/java/spoon/pattern/ResultHolder.java @@ -116,7 +116,7 @@ public static class Multiple extends ResultHolder { List result = new ArrayList<>(); - Multiple(Class requiredClass) { + public Multiple(Class requiredClass) { super(requiredClass); } diff --git a/src/main/java/spoon/pattern/SubstitutionRequestProvider.java b/src/main/java/spoon/pattern/SubstitutionRequestProvider.java index a5d766be768..9c11a637766 100644 --- a/src/main/java/spoon/pattern/SubstitutionRequestProvider.java +++ b/src/main/java/spoon/pattern/SubstitutionRequestProvider.java @@ -16,6 +16,8 @@ */ package spoon.pattern; +import spoon.pattern.node.Node; + /** * Maps AST model object to the {@link Node} */ diff --git a/src/main/java/spoon/pattern/matcher/ChainOfMatchersImpl.java b/src/main/java/spoon/pattern/matcher/ChainOfMatchersImpl.java index 070a97c6494..99e5e573e9c 100644 --- a/src/main/java/spoon/pattern/matcher/ChainOfMatchersImpl.java +++ b/src/main/java/spoon/pattern/matcher/ChainOfMatchersImpl.java @@ -19,7 +19,7 @@ import java.util.List; import spoon.SpoonException; -import spoon.pattern.Node; +import spoon.pattern.node.Node; /** * Chain of {@link Node}s. {@link Node}s are processed in the same order as they were added into chain diff --git a/src/main/java/spoon/pattern/matcher/Matchers.java b/src/main/java/spoon/pattern/matcher/Matchers.java index 8da86a6e3d1..4642191acd3 100644 --- a/src/main/java/spoon/pattern/matcher/Matchers.java +++ b/src/main/java/spoon/pattern/matcher/Matchers.java @@ -16,7 +16,7 @@ */ package spoon.pattern.matcher; -import spoon.pattern.Node; +import spoon.pattern.node.Node; /** * A container of {@link Node}s. diff --git a/src/main/java/spoon/pattern/matcher/MatchingScanner.java b/src/main/java/spoon/pattern/matcher/MatchingScanner.java index ac98e60391e..cc5ad70e720 100644 --- a/src/main/java/spoon/pattern/matcher/MatchingScanner.java +++ b/src/main/java/spoon/pattern/matcher/MatchingScanner.java @@ -23,7 +23,7 @@ import java.util.Set; import spoon.SpoonException; -import spoon.pattern.ModelNode; +import spoon.pattern.node.ModelNode; import spoon.pattern.parameter.ParameterValueProviderFactory; import spoon.reflect.declaration.CtElement; import spoon.reflect.meta.ContainerKind; diff --git a/src/main/java/spoon/pattern/matcher/NodeListMatcher.java b/src/main/java/spoon/pattern/matcher/NodeListMatcher.java deleted file mode 100644 index 19fa19df055..00000000000 --- a/src/main/java/spoon/pattern/matcher/NodeListMatcher.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * 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.pattern.matcher; -//TODO move to spoon.pattern.matcher - -import java.util.List; - -import spoon.pattern.parameter.ParameterValueProvider; -import spoon.reflect.declaration.CtElement; - -/** - * Marks the SubstitutionRequest which has to match whole AST node (not only some attribute of node) - */ -public interface NodeListMatcher { - /** - * matches this {@link NodeListMatcher} with `nextTargets` and `nextTemplates` storing the matched values into `parameters` - * @param parameters the collector of matched parameters - * @param nextTargets the List of - * @param nextTemplates - * @return if this {@link NodeListMatcher} matched and all `nextTemplates` matched, then return number of matching items from `nextTargets` - * if something doesn't match, then return -1 - */ - int matches(ParameterValueProvider parameters, List nextTargets, List nextTemplates); -} diff --git a/src/main/java/spoon/pattern/matcher/Quantifier.java b/src/main/java/spoon/pattern/matcher/Quantifier.java index b7d31041863..c402ea1d6f9 100644 --- a/src/main/java/spoon/pattern/matcher/Quantifier.java +++ b/src/main/java/spoon/pattern/matcher/Quantifier.java @@ -16,7 +16,7 @@ */ package spoon.pattern.matcher; -import spoon.pattern.Node; +import spoon.pattern.node.Node; /** * Defines a strategy used to resolve conflict between two {@link Node}s diff --git a/src/main/java/spoon/pattern/AbstractPrimitiveMatcher.java b/src/main/java/spoon/pattern/node/AbstractPrimitiveMatcher.java similarity index 97% rename from src/main/java/spoon/pattern/AbstractPrimitiveMatcher.java rename to src/main/java/spoon/pattern/node/AbstractPrimitiveMatcher.java index ae2834efd55..89e9d2fdd2f 100644 --- a/src/main/java/spoon/pattern/AbstractPrimitiveMatcher.java +++ b/src/main/java/spoon/pattern/node/AbstractPrimitiveMatcher.java @@ -14,7 +14,7 @@ * 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.pattern; +package spoon.pattern.node; import spoon.pattern.matcher.TobeMatched; diff --git a/src/main/java/spoon/pattern/AbstractRepeatableMatcher.java b/src/main/java/spoon/pattern/node/AbstractRepeatableMatcher.java similarity index 99% rename from src/main/java/spoon/pattern/AbstractRepeatableMatcher.java rename to src/main/java/spoon/pattern/node/AbstractRepeatableMatcher.java index 8e47063f78f..a3372af70e1 100644 --- a/src/main/java/spoon/pattern/AbstractRepeatableMatcher.java +++ b/src/main/java/spoon/pattern/node/AbstractRepeatableMatcher.java @@ -14,7 +14,7 @@ * 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.pattern; +package spoon.pattern.node; import spoon.SpoonException; import spoon.pattern.matcher.Matchers; diff --git a/src/main/java/spoon/pattern/ConstantNode.java b/src/main/java/spoon/pattern/node/ConstantNode.java similarity index 97% rename from src/main/java/spoon/pattern/ConstantNode.java rename to src/main/java/spoon/pattern/node/ConstantNode.java index 2a28543a3ce..fc66fb13299 100644 --- a/src/main/java/spoon/pattern/ConstantNode.java +++ b/src/main/java/spoon/pattern/node/ConstantNode.java @@ -14,10 +14,11 @@ * 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.pattern; +package spoon.pattern.node; import java.util.function.BiConsumer; +import spoon.pattern.ResultHolder; import spoon.pattern.parameter.ParameterInfo; import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.factory.Factory; diff --git a/src/main/java/spoon/pattern/ElementNode.java b/src/main/java/spoon/pattern/node/ElementNode.java similarity index 99% rename from src/main/java/spoon/pattern/ElementNode.java rename to src/main/java/spoon/pattern/node/ElementNode.java index b1949877df1..ba99ca2e812 100644 --- a/src/main/java/spoon/pattern/ElementNode.java +++ b/src/main/java/spoon/pattern/node/ElementNode.java @@ -14,7 +14,7 @@ * 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.pattern; +package spoon.pattern.node; import java.util.Collections; import java.util.HashMap; @@ -26,6 +26,7 @@ import spoon.Metamodel; import spoon.SpoonException; +import spoon.pattern.ResultHolder; import spoon.pattern.matcher.TobeMatched; import spoon.pattern.parameter.ParameterInfo; import spoon.pattern.parameter.ParameterValueProvider; diff --git a/src/main/java/spoon/pattern/ForEachNode.java b/src/main/java/spoon/pattern/node/ForEachNode.java similarity index 98% rename from src/main/java/spoon/pattern/ForEachNode.java rename to src/main/java/spoon/pattern/node/ForEachNode.java index ff212970d93..064ee168e3b 100644 --- a/src/main/java/spoon/pattern/ForEachNode.java +++ b/src/main/java/spoon/pattern/node/ForEachNode.java @@ -14,11 +14,12 @@ * 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.pattern; +package spoon.pattern.node; import java.util.Map; import java.util.function.BiConsumer; +import spoon.pattern.ResultHolder; import spoon.pattern.matcher.Quantifier; import spoon.pattern.matcher.TobeMatched; import spoon.pattern.parameter.ParameterInfo; diff --git a/src/main/java/spoon/pattern/ListOfNodes.java b/src/main/java/spoon/pattern/node/ListOfNodes.java similarity index 97% rename from src/main/java/spoon/pattern/ListOfNodes.java rename to src/main/java/spoon/pattern/node/ListOfNodes.java index 14f739441e9..40f1763fd13 100644 --- a/src/main/java/spoon/pattern/ListOfNodes.java +++ b/src/main/java/spoon/pattern/node/ListOfNodes.java @@ -14,11 +14,12 @@ * 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.pattern; +package spoon.pattern.node; import java.util.List; import java.util.function.BiConsumer; +import spoon.pattern.ResultHolder; import spoon.pattern.matcher.ChainOfMatchersImpl; import spoon.pattern.matcher.Matchers; import spoon.pattern.matcher.TobeMatched; diff --git a/src/main/java/spoon/pattern/matcher/MapEntryNode.java b/src/main/java/spoon/pattern/node/MapEntryNode.java similarity index 94% rename from src/main/java/spoon/pattern/matcher/MapEntryNode.java rename to src/main/java/spoon/pattern/node/MapEntryNode.java index d9ecec1e0e2..59f6a8f32b2 100644 --- a/src/main/java/spoon/pattern/matcher/MapEntryNode.java +++ b/src/main/java/spoon/pattern/node/MapEntryNode.java @@ -14,12 +14,13 @@ * 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.pattern.matcher; +package spoon.pattern.node; import java.util.function.BiConsumer; -import spoon.pattern.Node; import spoon.pattern.ResultHolder; +import spoon.pattern.matcher.Matchers; +import spoon.pattern.matcher.TobeMatched; import spoon.pattern.parameter.ParameterInfo; import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.factory.Factory; diff --git a/src/main/java/spoon/pattern/ModelNode.java b/src/main/java/spoon/pattern/node/ModelNode.java similarity index 98% rename from src/main/java/spoon/pattern/ModelNode.java rename to src/main/java/spoon/pattern/node/ModelNode.java index 4fb0ed48047..2c95ca5f75b 100644 --- a/src/main/java/spoon/pattern/ModelNode.java +++ b/src/main/java/spoon/pattern/node/ModelNode.java @@ -14,10 +14,12 @@ * 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.pattern; +package spoon.pattern.node; import java.util.List; +import spoon.pattern.PatternBuilder; + /** * The AST model based parameterized model, which can generate or match other AST models. * @@ -25,7 +27,7 @@ */ public class ModelNode extends ListOfNodes { - ModelNode(List nodes) { + public ModelNode(List nodes) { super(nodes); } diff --git a/src/main/java/spoon/pattern/Node.java b/src/main/java/spoon/pattern/node/Node.java similarity index 96% rename from src/main/java/spoon/pattern/Node.java rename to src/main/java/spoon/pattern/node/Node.java index 810cb06cb54..c5f633027f6 100644 --- a/src/main/java/spoon/pattern/Node.java +++ b/src/main/java/spoon/pattern/node/Node.java @@ -14,11 +14,14 @@ * 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.pattern; +package spoon.pattern.node; import java.util.List; import java.util.function.BiConsumer; +import spoon.pattern.ResultHolder; +import spoon.pattern.ResultHolder.Multiple; +import spoon.pattern.ResultHolder.Single; import spoon.pattern.matcher.Matchers; import spoon.pattern.matcher.TobeMatched; import spoon.pattern.parameter.ParameterInfo; diff --git a/src/main/java/spoon/pattern/ParameterNode.java b/src/main/java/spoon/pattern/node/ParameterNode.java similarity index 97% rename from src/main/java/spoon/pattern/ParameterNode.java rename to src/main/java/spoon/pattern/node/ParameterNode.java index 04abdaf3336..3d07af535ec 100644 --- a/src/main/java/spoon/pattern/ParameterNode.java +++ b/src/main/java/spoon/pattern/node/ParameterNode.java @@ -14,10 +14,11 @@ * 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.pattern; +package spoon.pattern.node; import java.util.function.BiConsumer; +import spoon.pattern.ResultHolder; import spoon.pattern.matcher.Quantifier; import spoon.pattern.parameter.ParameterInfo; import spoon.pattern.parameter.ParameterValueProvider; diff --git a/src/main/java/spoon/pattern/PrimitiveMatcher.java b/src/main/java/spoon/pattern/node/PrimitiveMatcher.java similarity index 97% rename from src/main/java/spoon/pattern/PrimitiveMatcher.java rename to src/main/java/spoon/pattern/node/PrimitiveMatcher.java index 995101ccd3c..7ef079f337b 100644 --- a/src/main/java/spoon/pattern/PrimitiveMatcher.java +++ b/src/main/java/spoon/pattern/node/PrimitiveMatcher.java @@ -14,7 +14,7 @@ * 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.pattern; +package spoon.pattern.node; import spoon.pattern.parameter.ParameterValueProvider; diff --git a/src/main/java/spoon/pattern/RepeatableMatcher.java b/src/main/java/spoon/pattern/node/RepeatableMatcher.java similarity index 98% rename from src/main/java/spoon/pattern/RepeatableMatcher.java rename to src/main/java/spoon/pattern/node/RepeatableMatcher.java index 0db6053ffb6..1634540c8e0 100644 --- a/src/main/java/spoon/pattern/RepeatableMatcher.java +++ b/src/main/java/spoon/pattern/node/RepeatableMatcher.java @@ -14,7 +14,7 @@ * 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.pattern; +package spoon.pattern.node; import spoon.pattern.matcher.Quantifier; import spoon.pattern.parameter.ParameterValueProvider; diff --git a/src/main/java/spoon/pattern/StringNode.java b/src/main/java/spoon/pattern/node/StringNode.java similarity index 98% rename from src/main/java/spoon/pattern/StringNode.java rename to src/main/java/spoon/pattern/node/StringNode.java index 6f5981cc105..ab404cdf15f 100644 --- a/src/main/java/spoon/pattern/StringNode.java +++ b/src/main/java/spoon/pattern/node/StringNode.java @@ -14,7 +14,7 @@ * 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.pattern; +package spoon.pattern.node; import java.util.ArrayList; import java.util.Collections; @@ -27,6 +27,8 @@ import java.util.regex.Pattern; import spoon.SpoonException; +import spoon.pattern.ResultHolder; +import spoon.pattern.ResultHolder.Single; import spoon.pattern.parameter.ParameterInfo; import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.factory.Factory; diff --git a/src/main/java/spoon/pattern/SwitchNode.java b/src/main/java/spoon/pattern/node/SwitchNode.java similarity index 98% rename from src/main/java/spoon/pattern/SwitchNode.java rename to src/main/java/spoon/pattern/node/SwitchNode.java index fbf9a15d10c..d0de21f82a7 100644 --- a/src/main/java/spoon/pattern/SwitchNode.java +++ b/src/main/java/spoon/pattern/node/SwitchNode.java @@ -14,12 +14,13 @@ * 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.pattern; +package spoon.pattern.node; import java.util.ArrayList; import java.util.List; import java.util.function.BiConsumer; +import spoon.pattern.ResultHolder; import spoon.pattern.matcher.Matchers; import spoon.pattern.matcher.TobeMatched; import spoon.pattern.parameter.ParameterInfo; diff --git a/src/main/java/spoon/pattern/parameter/ParameterInfo.java b/src/main/java/spoon/pattern/parameter/ParameterInfo.java index 39ef764a0a1..8c16e4092e3 100644 --- a/src/main/java/spoon/pattern/parameter/ParameterInfo.java +++ b/src/main/java/spoon/pattern/parameter/ParameterInfo.java @@ -16,10 +16,10 @@ */ package spoon.pattern.parameter; -import spoon.pattern.Node; import spoon.pattern.Pattern; import spoon.pattern.ResultHolder; import spoon.pattern.matcher.Quantifier; +import spoon.pattern.node.Node; import spoon.reflect.factory.Factory; /** diff --git a/src/main/java/spoon/template/TemplateMatcher.java b/src/main/java/spoon/template/TemplateMatcher.java index 551c954e9a9..a70668c7965 100644 --- a/src/main/java/spoon/template/TemplateMatcher.java +++ b/src/main/java/spoon/template/TemplateMatcher.java @@ -18,11 +18,11 @@ import java.util.List; -import spoon.pattern.ModelNode; import spoon.pattern.Pattern; import spoon.pattern.TemplateBuilder; import spoon.pattern.matcher.Match; import spoon.pattern.matcher.TobeMatched; +import spoon.pattern.node.ModelNode; import spoon.pattern.parameter.ParameterValueProvider; import spoon.pattern.parameter.ParameterValueProviderFactory; import spoon.pattern.parameter.UnmodifiableParameterValueProvider; From 5ce66bbddd5633508ffdff7d7f48129163e56e13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Wed, 7 Mar 2018 15:12:50 +0100 Subject: [PATCH 010/131] Generator used in generation process --- .../java/spoon/pattern/DefaultGenerator.java | 50 +++++++++++++ src/main/java/spoon/pattern/Generator.java | 75 +++++++++++++++++++ src/main/java/spoon/pattern/Pattern.java | 4 +- .../java/spoon/pattern/PatternPrinter.java | 9 ++- .../java/spoon/pattern/node/ConstantNode.java | 4 +- .../java/spoon/pattern/node/ElementNode.java | 18 ++--- .../java/spoon/pattern/node/ForEachNode.java | 8 +- .../java/spoon/pattern/node/ListOfNodes.java | 6 +- .../java/spoon/pattern/node/MapEntryNode.java | 4 +- src/main/java/spoon/pattern/node/Node.java | 35 ++------- .../spoon/pattern/node/ParameterNode.java | 6 +- .../java/spoon/pattern/node/StringNode.java | 6 +- .../java/spoon/pattern/node/SwitchNode.java | 15 ++-- 13 files changed, 173 insertions(+), 67 deletions(-) create mode 100644 src/main/java/spoon/pattern/DefaultGenerator.java create mode 100644 src/main/java/spoon/pattern/Generator.java diff --git a/src/main/java/spoon/pattern/DefaultGenerator.java b/src/main/java/spoon/pattern/DefaultGenerator.java new file mode 100644 index 00000000000..414cfe2d54e --- /dev/null +++ b/src/main/java/spoon/pattern/DefaultGenerator.java @@ -0,0 +1,50 @@ +/** + * 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.pattern; + +import spoon.pattern.node.Node; +import spoon.pattern.parameter.ParameterInfo; +import spoon.pattern.parameter.ParameterValueProvider; +import spoon.reflect.factory.Factory; + +/** + * Drives generation process + */ +public class DefaultGenerator implements Generator { + private final Factory factory; + + public DefaultGenerator(Factory factory) { + super(); + this.factory = factory; + } + + @Override + public void generateTargets(Node node, ResultHolder result, ParameterValueProvider parameters) { + node.generateTargets(this, result, parameters); + } + + @Override + public void getValueAs(ParameterInfo parameterInfo, ResultHolder result, ParameterValueProvider parameters) { + parameterInfo.getValueAs(factory, result, parameters); + } + + @Override + public Factory getFactory() { + return factory; + } + +} diff --git a/src/main/java/spoon/pattern/Generator.java b/src/main/java/spoon/pattern/Generator.java new file mode 100644 index 00000000000..f5ebb586de4 --- /dev/null +++ b/src/main/java/spoon/pattern/Generator.java @@ -0,0 +1,75 @@ +/** + * 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.pattern; + +import java.util.List; + +import spoon.pattern.node.Node; +import spoon.pattern.parameter.ParameterInfo; +import spoon.pattern.parameter.ParameterValueProvider; +import spoon.reflect.factory.Factory; + +/** + * Drives generation process + */ +public interface Generator { + /** + * @return a {@link Factory}, which has to be used to generate instances + */ + Factory getFactory(); + /** + * Generates zero, one or more target depending on kind of this {@link Node}, expected `result` and input `parameters` + * @param factory TODO + */ + void generateTargets(Node node, ResultHolder result, ParameterValueProvider parameters); + + /** + * Returns zero, one or more values into `result`. The value comes from `parameters` from the location defined by `parameterInfo` + * @param parameterInfo + * @param result + * @param parameters + */ + void getValueAs(ParameterInfo parameterInfo, ResultHolder result, ParameterValueProvider parameters); + + /** + * Generates one target depending on kind of this {@link Node}, expected `expectedType` and input `parameters` + * @param factory TODO + * @param parameters {@link ParameterValueProvider} + * @param expectedType defines {@link Class} of returned value + * + * @return a generate value or null + */ + default T generateTarget(Node node, ParameterValueProvider parameters, Class expectedType) { + ResultHolder.Single result = new ResultHolder.Single<>(expectedType); + generateTargets(node, result, parameters); + return result.getResult(); + } + + /** + * Generates zero, one or more targets depending on kind of this {@link Node}, expected `expectedType` and input `parameters` + * @param factory TODO + * @param parameters {@link ParameterValueProvider} + * @param expectedType defines {@link Class} of returned value + * + * @return a {@link List} of generated targets + */ + default List generateTargets(Node node, ParameterValueProvider parameters, Class expectedType) { + ResultHolder.Multiple result = new ResultHolder.Multiple<>(expectedType); + generateTargets(node, result, parameters); + return result.getResult(); + } +} diff --git a/src/main/java/spoon/pattern/Pattern.java b/src/main/java/spoon/pattern/Pattern.java index fda988d663e..5af1133fc95 100644 --- a/src/main/java/spoon/pattern/Pattern.java +++ b/src/main/java/spoon/pattern/Pattern.java @@ -102,7 +102,7 @@ public T substituteSingle(Factory factory, Class valueT * @return one generated element */ public T substituteSingle(Factory factory, Class valueType, ParameterValueProvider params) { - return modelValueResolver.generateTarget(factory, params, valueType); + return new DefaultGenerator(factory).generateTarget(modelValueResolver, params, valueType); } /** * generates a new AST made by cloning of `patternModel` and by substitution of parameters by values in `params` @@ -122,7 +122,7 @@ public List substituteList(Factory factory, Class va * @return List of generated elements */ public List substituteList(Factory factory, Class valueType, ParameterValueProvider params) { - return modelValueResolver.generateTargets(factory, params, valueType); + return new DefaultGenerator(factory).generateTargets(modelValueResolver, params, valueType); } diff --git a/src/main/java/spoon/pattern/PatternPrinter.java b/src/main/java/spoon/pattern/PatternPrinter.java index e67e22e11eb..86205e238a7 100644 --- a/src/main/java/spoon/pattern/PatternPrinter.java +++ b/src/main/java/spoon/pattern/PatternPrinter.java @@ -33,11 +33,16 @@ /** */ -public class PatternPrinter { +public class PatternPrinter extends DefaultGenerator { + private static final Factory DEFAULT_FACTORY = new FactoryImpl(new DefaultCoreFactory(), new StandardEnvironment()); + public PatternPrinter() { + super(DEFAULT_FACTORY); + } + public String printNode(Node node) { - List generated = node.generateTargets(DEFAULT_FACTORY, new Params(), null); + List generated = generateTargets(node, new Params(), null); StringBuilder sb = new StringBuilder(); for (CtElement ele : generated) { sb.append(ele.toString()).append('\n'); diff --git a/src/main/java/spoon/pattern/node/ConstantNode.java b/src/main/java/spoon/pattern/node/ConstantNode.java index fc66fb13299..304def1c2a2 100644 --- a/src/main/java/spoon/pattern/node/ConstantNode.java +++ b/src/main/java/spoon/pattern/node/ConstantNode.java @@ -18,10 +18,10 @@ import java.util.function.BiConsumer; +import spoon.pattern.Generator; import spoon.pattern.ResultHolder; import spoon.pattern.parameter.ParameterInfo; import spoon.pattern.parameter.ParameterValueProvider; -import spoon.reflect.factory.Factory; /** * Generates/Matches a copy of single template object @@ -49,7 +49,7 @@ public void forEachParameterInfo(BiConsumer consumer) { } @Override - public void generateTargets(Factory factory, ResultHolder result, ParameterValueProvider parameters) { + public void generateTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters) { result.addResult((U) template); } diff --git a/src/main/java/spoon/pattern/node/ElementNode.java b/src/main/java/spoon/pattern/node/ElementNode.java index ba99ca2e812..153309c1717 100644 --- a/src/main/java/spoon/pattern/node/ElementNode.java +++ b/src/main/java/spoon/pattern/node/ElementNode.java @@ -26,12 +26,12 @@ import spoon.Metamodel; import spoon.SpoonException; +import spoon.pattern.Generator; import spoon.pattern.ResultHolder; import spoon.pattern.matcher.TobeMatched; import spoon.pattern.parameter.ParameterInfo; import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.declaration.CtElement; -import spoon.reflect.factory.Factory; import spoon.reflect.meta.ContainerKind; import spoon.reflect.path.CtRole; @@ -99,28 +99,28 @@ public void forEachParameterInfo(BiConsumer consumer) { @SuppressWarnings("unchecked") @Override - public void generateTargets(Factory factory, ResultHolder result, ParameterValueProvider parameters) { + public void generateTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters) { //TODO implement create on Metamodel.Type - CtElement clone = factory.Core().create(elementType.getModelInterface()); - generateSingleNodeAttributes(clone, parameters); + CtElement clone = generator.getFactory().Core().create(elementType.getModelInterface()); + generateSingleNodeAttributes(generator, clone, parameters); result.addResult((U) clone); } - protected void generateSingleNodeAttributes(CtElement clone, ParameterValueProvider parameters) { + protected void generateSingleNodeAttributes(Generator generator, CtElement clone, ParameterValueProvider parameters) { for (Map.Entry e : getAttributeSubstititionRequests().entrySet()) { Metamodel.Field mmField = e.getKey(); switch (mmField.getContainerKind()) { case SINGLE: - mmField.setValue(clone, e.getValue().generateTarget(clone.getFactory(), parameters, mmField.getValueClass())); + mmField.setValue(clone, generator.generateTarget(e.getValue(), parameters, mmField.getValueClass())); break; case LIST: - mmField.setValue(clone, e.getValue().generateTargets(clone.getFactory(), parameters, mmField.getValueClass())); + mmField.setValue(clone, generator.generateTargets(e.getValue(), parameters, mmField.getValueClass())); break; case SET: - mmField.setValue(clone, new LinkedHashSet<>(e.getValue().generateTargets(clone.getFactory(), parameters, mmField.getValueClass()))); + mmField.setValue(clone, new LinkedHashSet<>(generator.generateTargets(e.getValue(), parameters, mmField.getValueClass()))); break; case MAP: - mmField.setValue(clone, entriesToMap(e.getValue().generateTargets(clone.getFactory(), parameters, Map.Entry.class))); + mmField.setValue(clone, entriesToMap(generator.generateTargets(e.getValue(), parameters, Map.Entry.class))); break; } } diff --git a/src/main/java/spoon/pattern/node/ForEachNode.java b/src/main/java/spoon/pattern/node/ForEachNode.java index 064ee168e3b..a01da3545b2 100644 --- a/src/main/java/spoon/pattern/node/ForEachNode.java +++ b/src/main/java/spoon/pattern/node/ForEachNode.java @@ -19,12 +19,12 @@ import java.util.Map; import java.util.function.BiConsumer; +import spoon.pattern.Generator; import spoon.pattern.ResultHolder; import spoon.pattern.matcher.Quantifier; import spoon.pattern.matcher.TobeMatched; import spoon.pattern.parameter.ParameterInfo; import spoon.pattern.parameter.ParameterValueProvider; -import spoon.reflect.factory.Factory; /** * Pattern node of multiple occurrences of the same model, just with different parameters. @@ -66,9 +66,9 @@ public boolean replaceNode(Node oldNode, Node newNode) { } @Override - public void generateTargets(Factory factory, ResultHolder result, ParameterValueProvider parameters) { - for (Object parameterValue : iterableParameter.generateTargets(factory, parameters, Object.class)) { - nestedModel.generateTargets(factory, result, parameters.putValueToCopy(localParameter.getName(), parameterValue)); + public void generateTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters) { + for (Object parameterValue : generator.generateTargets(iterableParameter, parameters, Object.class)) { + generator.generateTargets(nestedModel, result, parameters.putValueToCopy(localParameter.getName(), parameterValue)); } } diff --git a/src/main/java/spoon/pattern/node/ListOfNodes.java b/src/main/java/spoon/pattern/node/ListOfNodes.java index 40f1763fd13..af594a58ea8 100644 --- a/src/main/java/spoon/pattern/node/ListOfNodes.java +++ b/src/main/java/spoon/pattern/node/ListOfNodes.java @@ -19,13 +19,13 @@ import java.util.List; import java.util.function.BiConsumer; +import spoon.pattern.Generator; import spoon.pattern.ResultHolder; import spoon.pattern.matcher.ChainOfMatchersImpl; import spoon.pattern.matcher.Matchers; import spoon.pattern.matcher.TobeMatched; import spoon.pattern.parameter.ParameterInfo; import spoon.pattern.parameter.ParameterValueProvider; -import spoon.reflect.factory.Factory; /** * List of {@link Node}s. The {@link Node}s are processed in same order like they were inserted in the list @@ -46,9 +46,9 @@ public void forEachParameterInfo(BiConsumer consumer) { } @Override - public void generateTargets(Factory factory, ResultHolder result, ParameterValueProvider parameters) { + public void generateTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters) { for (Node node : nodes) { - node.generateTargets(factory, result, parameters); + generator.generateTargets(node, result, parameters); } } diff --git a/src/main/java/spoon/pattern/node/MapEntryNode.java b/src/main/java/spoon/pattern/node/MapEntryNode.java index 59f6a8f32b2..f77ff34ee27 100644 --- a/src/main/java/spoon/pattern/node/MapEntryNode.java +++ b/src/main/java/spoon/pattern/node/MapEntryNode.java @@ -18,12 +18,12 @@ import java.util.function.BiConsumer; +import spoon.pattern.Generator; import spoon.pattern.ResultHolder; import spoon.pattern.matcher.Matchers; import spoon.pattern.matcher.TobeMatched; import spoon.pattern.parameter.ParameterInfo; import spoon.pattern.parameter.ParameterValueProvider; -import spoon.reflect.factory.Factory; /** * Represents a ValueResolver of one Map.Entry @@ -63,7 +63,7 @@ public void forEachParameterInfo(BiConsumer consumer) { } @Override - public void generateTargets(Factory factory, ResultHolder result, ParameterValueProvider parameters) { + public void generateTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters) { // TODO throw new UnsupportedOperationException("TODO"); } diff --git a/src/main/java/spoon/pattern/node/Node.java b/src/main/java/spoon/pattern/node/Node.java index c5f633027f6..871667abe89 100644 --- a/src/main/java/spoon/pattern/node/Node.java +++ b/src/main/java/spoon/pattern/node/Node.java @@ -19,6 +19,7 @@ import java.util.List; import java.util.function.BiConsumer; +import spoon.pattern.Generator; import spoon.pattern.ResultHolder; import spoon.pattern.ResultHolder.Multiple; import spoon.pattern.ResultHolder.Single; @@ -44,37 +45,11 @@ public interface Node extends Matchers { /** * Generates zero, one or more target depending on kind of this {@link Node}, expected `result` and input `parameters` - * @param factory TODO + * @param generator {@link Generator} which drives generation process + * @param result holder for the generated objects + * @param parameters a {@link ParameterValueProvider} holding parameters */ - void generateTargets(Factory factory, ResultHolder result, ParameterValueProvider parameters); - - /** - * Generates one target depending on kind of this {@link Node}, expected `expectedType` and input `parameters` - * @param factory TODO - * @param parameters {@link ParameterValueProvider} - * @param expectedType defines {@link Class} of returned value - * - * @return a generate value or null - */ - default T generateTarget(Factory factory, ParameterValueProvider parameters, Class expectedType) { - ResultHolder.Single result = new ResultHolder.Single<>(expectedType); - generateTargets(factory, result, parameters); - return result.getResult(); - } - - /** - * Generates zero, one or more targets depending on kind of this {@link Node}, expected `expectedType` and input `parameters` - * @param factory TODO - * @param parameters {@link ParameterValueProvider} - * @param expectedType defines {@link Class} of returned value - * - * @return a {@link List} of generated targets - */ - default List generateTargets(Factory factory, ParameterValueProvider parameters, Class expectedType) { - ResultHolder.Multiple result = new ResultHolder.Multiple<>(expectedType); - generateTargets(factory, result, parameters); - return result.getResult(); - } + void generateTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters); /** * @param targets to be matched target nodes and input parameters diff --git a/src/main/java/spoon/pattern/node/ParameterNode.java b/src/main/java/spoon/pattern/node/ParameterNode.java index 3d07af535ec..175c06f9139 100644 --- a/src/main/java/spoon/pattern/node/ParameterNode.java +++ b/src/main/java/spoon/pattern/node/ParameterNode.java @@ -18,12 +18,12 @@ import java.util.function.BiConsumer; +import spoon.pattern.Generator; import spoon.pattern.ResultHolder; import spoon.pattern.matcher.Quantifier; import spoon.pattern.parameter.ParameterInfo; import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.declaration.CtElement; -import spoon.reflect.factory.Factory; /** * Represents pattern model variable @@ -44,8 +44,8 @@ public boolean replaceNode(Node oldNode, Node newNode) { } @Override - public void generateTargets(Factory factory, ResultHolder result, ParameterValueProvider parameters) { - parameterInfo.getValueAs(factory, result, parameters); + public void generateTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters) { + generator.getValueAs(parameterInfo, result, parameters); } @Override diff --git a/src/main/java/spoon/pattern/node/StringNode.java b/src/main/java/spoon/pattern/node/StringNode.java index ab404cdf15f..13732e4a67d 100644 --- a/src/main/java/spoon/pattern/node/StringNode.java +++ b/src/main/java/spoon/pattern/node/StringNode.java @@ -27,11 +27,11 @@ import java.util.regex.Pattern; import spoon.SpoonException; +import spoon.pattern.Generator; import spoon.pattern.ResultHolder; import spoon.pattern.ResultHolder.Single; import spoon.pattern.parameter.ParameterInfo; import spoon.pattern.parameter.ParameterValueProvider; -import spoon.reflect.factory.Factory; /** * Delivers single String value, which is created by replacing string markers in constant String template @@ -55,7 +55,7 @@ private String getStringValueWithMarkers() { @SuppressWarnings("unchecked") @Override - public void generateTargets(Factory factory, ResultHolder result, ParameterValueProvider parameters) { + public void generateTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters) { Class requiredClass = result.getRequiredClass(); if (requiredClass != null && requiredClass.isAssignableFrom(String.class) == false) { throw new SpoonException("StringValueResolver provides only String values. It doesn't support: " + requiredClass); @@ -69,7 +69,7 @@ public void generateTargets(Factory factory, ResultHolder result, Paramet ParameterInfo param = requests.getValue(); String replaceMarker = requests.getKey(); ResultHolder.Single ctx = new ResultHolder.Single<>(String.class); - param.getValueAs(factory, ctx, parameters); + generator.getValueAs(param, ctx, parameters); String substrValue = ctx.getResult() == null ? "" : ctx.getResult(); stringValue = substituteSubstring(stringValue, replaceMarker, substrValue); } diff --git a/src/main/java/spoon/pattern/node/SwitchNode.java b/src/main/java/spoon/pattern/node/SwitchNode.java index d0de21f82a7..8e8f595e7fc 100644 --- a/src/main/java/spoon/pattern/node/SwitchNode.java +++ b/src/main/java/spoon/pattern/node/SwitchNode.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.function.BiConsumer; +import spoon.pattern.Generator; import spoon.pattern.ResultHolder; import spoon.pattern.matcher.Matchers; import spoon.pattern.matcher.TobeMatched; @@ -66,9 +67,9 @@ public void addCase(PrimitiveMatcher vrOfExpression, Node statement) { } @Override - public void generateTargets(Factory factory, ResultHolder result, ParameterValueProvider parameters) { + public void generateTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters) { for (CaseNode case1 : cases) { - case1.generateTargets(factory, result, parameters); + generator.generateTargets(case1, result, parameters); } } @@ -170,18 +171,18 @@ public void forEachParameterInfo(BiConsumer consumer) { SwitchNode.this.forEachParameterInfo(consumer); } @Override - public void generateTargets(Factory factory, ResultHolder result, ParameterValueProvider parameters) { + public void generateTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters) { if (statement != null) { - if (isCaseSelected(factory, parameters)) { - statement.generateTargets(factory, result, parameters); + if (isCaseSelected(generator, parameters)) { + generator.generateTargets(statement, result, parameters); } } } - private boolean isCaseSelected(Factory factory, ParameterValueProvider parameters) { + private boolean isCaseSelected(Generator generator, ParameterValueProvider parameters) { if (vrOfExpression == null) { return true; } - Boolean value = vrOfExpression.generateTarget(factory, parameters, Boolean.class); + Boolean value = generator.generateTarget(vrOfExpression, parameters, Boolean.class); return value == null ? false : value.booleanValue(); } } From 964bf8480b69307476587d65e71d21a6341189a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Wed, 7 Mar 2018 15:25:30 +0100 Subject: [PATCH 011/131] rename Node to RootNode (to avoid Eclipse problem with `Node`) --- .../spoon/pattern/ConflictResolutionMode.java | 4 +- .../java/spoon/pattern/DefaultGenerator.java | 4 +- src/main/java/spoon/pattern/Generator.java | 14 ++--- .../spoon/pattern/LiveStatementsBuilder.java | 10 ++-- .../java/spoon/pattern/ParametersBuilder.java | 4 +- .../java/spoon/pattern/PatternBuilder.java | 56 +++++++++---------- .../java/spoon/pattern/PatternPrinter.java | 4 +- .../pattern/SubstitutionRequestProvider.java | 8 +-- .../pattern/matcher/ChainOfMatchersImpl.java | 16 +++--- .../java/spoon/pattern/matcher/Matchers.java | 6 +- .../spoon/pattern/matcher/Quantifier.java | 4 +- .../java/spoon/pattern/node/ConstantNode.java | 4 +- .../java/spoon/pattern/node/ElementNode.java | 26 ++++----- .../java/spoon/pattern/node/ForEachNode.java | 8 +-- .../java/spoon/pattern/node/ListOfNodes.java | 24 ++++---- .../java/spoon/pattern/node/MapEntryNode.java | 16 +++--- .../java/spoon/pattern/node/ModelNode.java | 2 +- .../spoon/pattern/node/ParameterNode.java | 4 +- .../spoon/pattern/node/RepeatableMatcher.java | 4 +- .../pattern/node/{Node.java => RootNode.java} | 24 ++++---- .../java/spoon/pattern/node/StringNode.java | 2 +- .../java/spoon/pattern/node/SwitchNode.java | 18 +++--- .../pattern/parameter/ParameterInfo.java | 4 +- 23 files changed, 133 insertions(+), 133 deletions(-) rename src/main/java/spoon/pattern/node/{Node.java => RootNode.java} (80%) diff --git a/src/main/java/spoon/pattern/ConflictResolutionMode.java b/src/main/java/spoon/pattern/ConflictResolutionMode.java index 7a9e8dc4f02..6dfc77c2989 100644 --- a/src/main/java/spoon/pattern/ConflictResolutionMode.java +++ b/src/main/java/spoon/pattern/ConflictResolutionMode.java @@ -17,10 +17,10 @@ package spoon.pattern; import spoon.SpoonException; -import spoon.pattern.node.Node; +import spoon.pattern.node.RootNode; /** - * Defines what happens when before explicitly added {@link Node} has to be replaced by another {@link Node} + * Defines what happens when before explicitly added {@link RootNode} has to be replaced by another {@link RootNode} */ public enum ConflictResolutionMode { /** diff --git a/src/main/java/spoon/pattern/DefaultGenerator.java b/src/main/java/spoon/pattern/DefaultGenerator.java index 414cfe2d54e..0b58c8c1b5c 100644 --- a/src/main/java/spoon/pattern/DefaultGenerator.java +++ b/src/main/java/spoon/pattern/DefaultGenerator.java @@ -16,7 +16,7 @@ */ package spoon.pattern; -import spoon.pattern.node.Node; +import spoon.pattern.node.RootNode; import spoon.pattern.parameter.ParameterInfo; import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.factory.Factory; @@ -33,7 +33,7 @@ public DefaultGenerator(Factory factory) { } @Override - public void generateTargets(Node node, ResultHolder result, ParameterValueProvider parameters) { + public void generateTargets(RootNode node, ResultHolder result, ParameterValueProvider parameters) { node.generateTargets(this, result, parameters); } diff --git a/src/main/java/spoon/pattern/Generator.java b/src/main/java/spoon/pattern/Generator.java index f5ebb586de4..853775a66f5 100644 --- a/src/main/java/spoon/pattern/Generator.java +++ b/src/main/java/spoon/pattern/Generator.java @@ -18,7 +18,7 @@ import java.util.List; -import spoon.pattern.node.Node; +import spoon.pattern.node.RootNode; import spoon.pattern.parameter.ParameterInfo; import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.factory.Factory; @@ -32,10 +32,10 @@ public interface Generator { */ Factory getFactory(); /** - * Generates zero, one or more target depending on kind of this {@link Node}, expected `result` and input `parameters` + * Generates zero, one or more target depending on kind of this {@link RootNode}, expected `result` and input `parameters` * @param factory TODO */ - void generateTargets(Node node, ResultHolder result, ParameterValueProvider parameters); + void generateTargets(RootNode node, ResultHolder result, ParameterValueProvider parameters); /** * Returns zero, one or more values into `result`. The value comes from `parameters` from the location defined by `parameterInfo` @@ -46,28 +46,28 @@ public interface Generator { void getValueAs(ParameterInfo parameterInfo, ResultHolder result, ParameterValueProvider parameters); /** - * Generates one target depending on kind of this {@link Node}, expected `expectedType` and input `parameters` + * Generates one target depending on kind of this {@link RootNode}, expected `expectedType` and input `parameters` * @param factory TODO * @param parameters {@link ParameterValueProvider} * @param expectedType defines {@link Class} of returned value * * @return a generate value or null */ - default T generateTarget(Node node, ParameterValueProvider parameters, Class expectedType) { + default T generateTarget(RootNode node, ParameterValueProvider parameters, Class expectedType) { ResultHolder.Single result = new ResultHolder.Single<>(expectedType); generateTargets(node, result, parameters); return result.getResult(); } /** - * Generates zero, one or more targets depending on kind of this {@link Node}, expected `expectedType` and input `parameters` + * Generates zero, one or more targets depending on kind of this {@link RootNode}, expected `expectedType` and input `parameters` * @param factory TODO * @param parameters {@link ParameterValueProvider} * @param expectedType defines {@link Class} of returned value * * @return a {@link List} of generated targets */ - default List generateTargets(Node node, ParameterValueProvider parameters, Class expectedType) { + default List generateTargets(RootNode node, ParameterValueProvider parameters, Class expectedType) { ResultHolder.Multiple result = new ResultHolder.Multiple<>(expectedType); generateTargets(node, result, parameters); return result.getResult(); diff --git a/src/main/java/spoon/pattern/LiveStatementsBuilder.java b/src/main/java/spoon/pattern/LiveStatementsBuilder.java index 45cae7b79ad..141b0e78ac6 100644 --- a/src/main/java/spoon/pattern/LiveStatementsBuilder.java +++ b/src/main/java/spoon/pattern/LiveStatementsBuilder.java @@ -23,7 +23,7 @@ import spoon.SpoonException; import spoon.pattern.node.ForEachNode; import spoon.pattern.node.ListOfNodes; -import spoon.pattern.node.Node; +import spoon.pattern.node.RootNode; import spoon.pattern.node.ParameterNode; import spoon.pattern.node.PrimitiveMatcher; import spoon.pattern.node.SwitchNode; @@ -80,7 +80,7 @@ public ConflictResolutionMode getConflictResolutionMode() { } /** - * Defines what happens when before explicitly added {@link Node} has to be replaced by another {@link Node} + * Defines what happens when before explicitly added {@link RootNode} has to be replaced by another {@link RootNode} * @param conflictResolutionMode to be applied mode * @return this to support fluent API */ @@ -112,7 +112,7 @@ public void visitCtIf(CtIf ifElement) { public LiveStatementsBuilder markLive(CtForEach foreach) { //detect meta elements by different way - e.g. comments? - Node vr = patternBuilder.getPatternNode(foreach.getExpression()); + RootNode vr = patternBuilder.getPatternNode(foreach.getExpression()); if ((vr instanceof PrimitiveMatcher) == false) { throw new SpoonException("Each live `for(x : iterable)` statement must have defined pattern parameter for `iterable` expression"); } @@ -145,7 +145,7 @@ public LiveStatementsBuilder markLive(CtIf ifElement) { //detect meta elements by different way - e.g. comments? if (expression != null) { //expression is not null, it is: if(expression) {} - Node vrOfExpression = patternBuilder.getPatternNode(expression); + RootNode vrOfExpression = patternBuilder.getPatternNode(expression); if (vrOfExpression instanceof ParameterNode == false) { if (failOnMissingParameter) { throw new SpoonException("Each live `if` statement must have defined pattern parameter in expression. If you want to ignore this, then call LiveStatementsBuilder#setFailOnMissingParameter(false) first."); @@ -175,7 +175,7 @@ public LiveStatementsBuilder markLive(CtIf ifElement) { } private ListOfNodes getPatternNode(List template) { - List nodes = new ArrayList<>(template.size()); + List nodes = new ArrayList<>(template.size()); for (CtElement element : template) { nodes.add(patternBuilder.getPatternNode(element)); } diff --git a/src/main/java/spoon/pattern/ParametersBuilder.java b/src/main/java/spoon/pattern/ParametersBuilder.java index 662900bf688..f724955db26 100644 --- a/src/main/java/spoon/pattern/ParametersBuilder.java +++ b/src/main/java/spoon/pattern/ParametersBuilder.java @@ -25,7 +25,7 @@ import spoon.pattern.matcher.Quantifier; import spoon.pattern.node.ConstantNode; import spoon.pattern.node.ModelNode; -import spoon.pattern.node.Node; +import spoon.pattern.node.RootNode; import spoon.pattern.node.ParameterNode; import spoon.pattern.node.StringNode; import spoon.pattern.parameter.AbstractParameterInfo; @@ -86,7 +86,7 @@ public ConflictResolutionMode getConflictResolutionMode() { } /** - * Defines what happens when before explicitly added {@link Node} has to be replaced by another {@link Node} + * Defines what happens when before explicitly added {@link RootNode} has to be replaced by another {@link RootNode} * @param conflictResolutionMode to be applied mode * @return this to support fluent API */ diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index 0d1a2709ae7..8a0a8b2ea9f 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -41,7 +41,7 @@ import spoon.pattern.node.ListOfNodes; import spoon.pattern.node.MapEntryNode; import spoon.pattern.node.ModelNode; -import spoon.pattern.node.Node; +import spoon.pattern.node.RootNode; import spoon.pattern.node.ParameterNode; import spoon.pattern.parameter.AbstractParameterInfo; import spoon.pattern.parameter.ParameterInfo; @@ -111,8 +111,8 @@ public static PatternBuilder create(CtType templateType, Consumer patternModel; private final ListOfNodes patternNodes; - private final Map patternElementToSubstRequests = new IdentityHashMap<>(); - private final Set explicitNodes = Collections.newSetFromMap(new IdentityHashMap<>()); + private final Map patternElementToSubstRequests = new IdentityHashMap<>(); + private final Set explicitNodes = Collections.newSetFromMap(new IdentityHashMap<>()); private CtTypeReference templateTypeRef; private final Factory factory; @@ -158,8 +158,8 @@ private PatternBuilder(CtTypeReference templateTypeRef, List templ }); } - private List createImplicitNodes(List elements) { - List nodes = new ArrayList<>(elements.size()); + private List createImplicitNodes(List elements) { + List nodes = new ArrayList<>(elements.size()); for (CtElement element : elements) { nodes.add(createImplicitNode(element)); } @@ -168,7 +168,7 @@ private List createImplicitNodes(List elements) { private final Set IGNORED_ROLES = Collections.unmodifiableSet((new HashSet<>(Arrays.asList(CtRole.POSITION)))); - private Node createImplicitNode(Object object) { + private RootNode createImplicitNode(Object object) { if (object instanceof CtElement) { //it is a spoon element CtElement element = (CtElement) object; @@ -191,7 +191,7 @@ private Node createImplicitNode(Object object) { return new ConstantNode(object); } - private Node createImplicitNode(ContainerKind containerKind, Object templates) { + private RootNode createImplicitNode(ContainerKind containerKind, Object templates) { switch (containerKind) { case LIST: return createImplicitNode((List) templates); @@ -205,17 +205,17 @@ private Node createImplicitNode(ContainerKind containerKind, Object templates) { throw new SpoonException("Unexpected RoleHandler containerKind: " + containerKind); } - private Node createImplicitNode(List objects) { + private RootNode createImplicitNode(List objects) { return listOfNodesToNode(objects.stream().map(i -> createImplicitNode(i)).collect(Collectors.toList())); } - private Node createImplicitNode(Set templates) { + private RootNode createImplicitNode(Set templates) { //collect plain template nodes without any substitution request as List, because Spoon Sets have predictable order. - List constantMatchers = new ArrayList<>(templates.size()); + List constantMatchers = new ArrayList<>(templates.size()); //collect template nodes with a substitution request - List variableMatchers = new ArrayList<>(); + List variableMatchers = new ArrayList<>(); for (Object template : templates) { - Node matcher = createImplicitNode(template); + RootNode matcher = createImplicitNode(template); if (matcher instanceof ElementNode) { constantMatchers.add(matcher); } else { @@ -227,7 +227,7 @@ private Node createImplicitNode(Set templates) { return listOfNodesToNode(constantMatchers); } - private Node createImplicitNode(Map map) { + private RootNode createImplicitNode(Map map) { //collect Entries with constant matcher keys List constantMatchers = new ArrayList<>(map.size()); //collect Entries with variable matcher keys @@ -249,7 +249,7 @@ private Node createImplicitNode(Map map) { } @SuppressWarnings({ "unchecked", "rawtypes" }) - private static Node listOfNodesToNode(List nodes) { + private static RootNode listOfNodesToNode(List nodes) { //The attribute is matched different if there is List of one ParameterizedNode and when there is one ParameterizedNode // if (nodes.size() == 1) { // return nodes.get(0); @@ -291,11 +291,11 @@ private static Node listOfNodesToNode(List nodes) { /** * @param element a CtElement - * @return {@link Node}, which handles matching/generation of an `object` from the source spoon AST. + * @return {@link RootNode}, which handles matching/generation of an `object` from the source spoon AST. * or null, if there is none */ - public Node getPatternNode(CtElement element, CtRole... roles) { - Node node = patternElementToSubstRequests.get(element); + public RootNode getPatternNode(CtElement element, CtRole... roles) { + RootNode node = patternElementToSubstRequests.get(element); for (CtRole role : roles) { if (node instanceof ElementNode) { ElementNode elementNode = (ElementNode) node; @@ -313,9 +313,9 @@ public Node getPatternNode(CtElement element, CtRole... roles) { return node; } - void modifyNodeOfElement(CtElement element, ConflictResolutionMode conflictMode, Function elementNodeChanger) { - Node oldNode = patternElementToSubstRequests.get(element); - Node newNode = elementNodeChanger.apply(oldNode); + void modifyNodeOfElement(CtElement element, ConflictResolutionMode conflictMode, Function elementNodeChanger) { + RootNode oldNode = patternElementToSubstRequests.get(element); + RootNode newNode = elementNodeChanger.apply(oldNode); if (newNode == null) { throw new SpoonException("Removing of Node is not supported"); } @@ -332,12 +332,12 @@ void modifyNodeOfElement(CtElement element, ConflictResolutionMode conflictMode, }); } - void modifyNodeOfAttributeOfElement(CtElement element, CtRole role, ConflictResolutionMode conflictMode, Function elementNodeChanger) { + void modifyNodeOfAttributeOfElement(CtElement element, CtRole role, ConflictResolutionMode conflictMode, Function elementNodeChanger) { modifyNodeOfElement(element, conflictMode, node -> { if (node instanceof ElementNode) { ElementNode elementNode = (ElementNode) node; - Node oldAttrNode = elementNode.getAttributeSubstititionRequest(role); - Node newAttrNode = elementNodeChanger.apply(oldAttrNode); + RootNode oldAttrNode = elementNode.getAttributeSubstititionRequest(role); + RootNode newAttrNode = elementNodeChanger.apply(oldAttrNode); if (newAttrNode == null) { throw new SpoonException("Removing of Node is not supported"); } @@ -353,7 +353,7 @@ void modifyNodeOfAttributeOfElement(CtElement element, CtRole role, ConflictReso }); } - private void handleConflict(ConflictResolutionMode conflictMode, Node oldNode, Node newNode, Runnable applyNewNode) { + private void handleConflict(ConflictResolutionMode conflictMode, RootNode oldNode, RootNode newNode, Runnable applyNewNode) { if (oldNode != newNode) { if (explicitNodes.contains(oldNode)) { //the oldNode was explicitly added before @@ -370,13 +370,13 @@ private void handleConflict(ConflictResolutionMode conflictMode, Node oldNode, N } } - public void setNodeOfElement(CtElement element, Node node, ConflictResolutionMode conflictMode) { + public void setNodeOfElement(CtElement element, RootNode node, ConflictResolutionMode conflictMode) { modifyNodeOfElement(element, conflictMode, oldNode -> { return node; }); } - public void setNodeOfAttributeOfElement(CtElement element, CtRole role, Node node, ConflictResolutionMode conflictMode) { + public void setNodeOfAttributeOfElement(CtElement element, CtRole role, RootNode node, ConflictResolutionMode conflictMode) { modifyNodeOfAttributeOfElement(element, role, conflictMode, oldAttrNode -> { return node; }); @@ -929,11 +929,11 @@ public List getPatternModel() { return patternModel; } /** - * Calls `consumer` once for each {@link Node} element which uses `parameter` + * Calls `consumer` once for each {@link RootNode} element which uses `parameter` * @param parameter to be checked {@link ParameterInfo} * @param consumer receiver of calls */ - public void forEachNodeOfParameter(ParameterInfo parameter, Consumer consumer) { + public void forEachNodeOfParameter(ParameterInfo parameter, Consumer consumer) { patternNodes.forEachParameterInfo((paramInfo, vr) -> { if (paramInfo == parameter) { consumer.accept(vr); diff --git a/src/main/java/spoon/pattern/PatternPrinter.java b/src/main/java/spoon/pattern/PatternPrinter.java index 86205e238a7..ec2bf075c14 100644 --- a/src/main/java/spoon/pattern/PatternPrinter.java +++ b/src/main/java/spoon/pattern/PatternPrinter.java @@ -20,7 +20,7 @@ import java.util.Map; import spoon.SpoonException; -import spoon.pattern.node.Node; +import spoon.pattern.node.RootNode; import spoon.pattern.parameter.AbstractParameterInfo; import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.code.CtInvocation; @@ -41,7 +41,7 @@ public PatternPrinter() { super(DEFAULT_FACTORY); } - public String printNode(Node node) { + public String printNode(RootNode node) { List generated = generateTargets(node, new Params(), null); StringBuilder sb = new StringBuilder(); for (CtElement ele : generated) { diff --git a/src/main/java/spoon/pattern/SubstitutionRequestProvider.java b/src/main/java/spoon/pattern/SubstitutionRequestProvider.java index 9c11a637766..5c163c8aeb0 100644 --- a/src/main/java/spoon/pattern/SubstitutionRequestProvider.java +++ b/src/main/java/spoon/pattern/SubstitutionRequestProvider.java @@ -16,15 +16,15 @@ */ package spoon.pattern; -import spoon.pattern.node.Node; +import spoon.pattern.node.RootNode; /** - * Maps AST model object to the {@link Node} + * Maps AST model object to the {@link RootNode} */ public interface SubstitutionRequestProvider { /** * @param object a node from the Pattern model to be matched - * @return {@link Node}, which has to be used to match `object` from model of {@link SubstitutionRequestProvider} + * @return {@link RootNode}, which has to be used to match `object` from model of {@link SubstitutionRequestProvider} */ - Node getTemplateValueResolver(Object object); + RootNode getTemplateValueResolver(Object object); } diff --git a/src/main/java/spoon/pattern/matcher/ChainOfMatchersImpl.java b/src/main/java/spoon/pattern/matcher/ChainOfMatchersImpl.java index 99e5e573e9c..c26bd6c4ec0 100644 --- a/src/main/java/spoon/pattern/matcher/ChainOfMatchersImpl.java +++ b/src/main/java/spoon/pattern/matcher/ChainOfMatchersImpl.java @@ -19,23 +19,23 @@ import java.util.List; import spoon.SpoonException; -import spoon.pattern.node.Node; +import spoon.pattern.node.RootNode; /** - * Chain of {@link Node}s. {@link Node}s are processed in the same order as they were added into chain + * Chain of {@link RootNode}s. {@link RootNode}s are processed in the same order as they were added into chain */ public class ChainOfMatchersImpl implements Matchers { - private final Node firstMatcher; + private final RootNode firstMatcher; private final Matchers next; - public static Matchers create(List items, Matchers next) { + public static Matchers create(List items, Matchers next) { return createFromList(next, items, 0); } - public static Matchers create(Node firstNode, Matchers next) { + public static Matchers create(RootNode firstNode, Matchers next) { return new ChainOfMatchersImpl(firstNode, next); } - private static Matchers createFromList(Matchers next, List items, int idx) { - Node matcher; + private static Matchers createFromList(Matchers next, List items, int idx) { + RootNode matcher; while (true) { if (idx >= items.size()) { return next; @@ -49,7 +49,7 @@ private static Matchers createFromList(Matchers next, List items return new ChainOfMatchersImpl(matcher, createFromList(next, items, idx + 1)); } - private ChainOfMatchersImpl(Node firstMatcher, Matchers next) { + private ChainOfMatchersImpl(RootNode firstMatcher, Matchers next) { super(); if (firstMatcher == null) { throw new SpoonException("The firstMatcher Node MUST NOT be null"); diff --git a/src/main/java/spoon/pattern/matcher/Matchers.java b/src/main/java/spoon/pattern/matcher/Matchers.java index 4642191acd3..3cce0b9da4d 100644 --- a/src/main/java/spoon/pattern/matcher/Matchers.java +++ b/src/main/java/spoon/pattern/matcher/Matchers.java @@ -16,17 +16,17 @@ */ package spoon.pattern.matcher; -import spoon.pattern.node.Node; +import spoon.pattern.node.RootNode; /** - * A container of {@link Node}s. + * A container of {@link RootNode}s. */ public interface Matchers { /** * Matches all matchers of this {@link Matchers} instance with `targets` * @param targets to be matched target nodes and input parameters - * @return {@link TobeMatched} with targets which remained after all {@link Node}s were matched + matched parameters + * @return {@link TobeMatched} with targets which remained after all {@link RootNode}s were matched + matched parameters */ TobeMatched matchAllWith(TobeMatched targets); } diff --git a/src/main/java/spoon/pattern/matcher/Quantifier.java b/src/main/java/spoon/pattern/matcher/Quantifier.java index c402ea1d6f9..ca9103683fd 100644 --- a/src/main/java/spoon/pattern/matcher/Quantifier.java +++ b/src/main/java/spoon/pattern/matcher/Quantifier.java @@ -16,10 +16,10 @@ */ package spoon.pattern.matcher; -import spoon.pattern.node.Node; +import spoon.pattern.node.RootNode; /** - * Defines a strategy used to resolve conflict between two {@link Node}s + * Defines a strategy used to resolve conflict between two {@link RootNode}s */ public enum Quantifier { /** diff --git a/src/main/java/spoon/pattern/node/ConstantNode.java b/src/main/java/spoon/pattern/node/ConstantNode.java index 304def1c2a2..53eb6cb9dc7 100644 --- a/src/main/java/spoon/pattern/node/ConstantNode.java +++ b/src/main/java/spoon/pattern/node/ConstantNode.java @@ -39,12 +39,12 @@ public T getTemplateNode() { } @Override - public boolean replaceNode(Node oldNode, Node newNode) { + public boolean replaceNode(RootNode oldNode, RootNode newNode) { return false; } @Override - public void forEachParameterInfo(BiConsumer consumer) { + public void forEachParameterInfo(BiConsumer consumer) { //it has no parameters } diff --git a/src/main/java/spoon/pattern/node/ElementNode.java b/src/main/java/spoon/pattern/node/ElementNode.java index 153309c1717..5608d2bda89 100644 --- a/src/main/java/spoon/pattern/node/ElementNode.java +++ b/src/main/java/spoon/pattern/node/ElementNode.java @@ -43,7 +43,7 @@ public class ElementNode extends AbstractPrimitiveMatcher { private Metamodel.Type elementType; - private Map attributeSubstititionRequests = new HashMap<>(); + private Map attributeSubstititionRequests = new HashMap<>(); public ElementNode(Metamodel.Type elementType) { super(); @@ -51,9 +51,9 @@ public ElementNode(Metamodel.Type elementType) { } @Override - public boolean replaceNode(Node oldNode, Node newNode) { - for (Map.Entry e : attributeSubstititionRequests.entrySet()) { - Node node = e.getValue(); + public boolean replaceNode(RootNode oldNode, RootNode newNode) { + for (Map.Entry e : attributeSubstititionRequests.entrySet()) { + RootNode node = e.getValue(); if (node == oldNode) { e.setValue(newNode); return true; @@ -65,15 +65,15 @@ public boolean replaceNode(Node oldNode, Node newNode) { return false; } - public Map getAttributeSubstititionRequests() { + public Map getAttributeSubstititionRequests() { return attributeSubstititionRequests == null ? Collections.emptyMap() : Collections.unmodifiableMap(attributeSubstititionRequests); } - public Node getAttributeSubstititionRequest(CtRole attributeRole) { + public RootNode getAttributeSubstititionRequest(CtRole attributeRole) { return attributeSubstititionRequests.get(getFieldOfRole(attributeRole)); } - public Node setNodeOfRole(CtRole role, Node newAttrNode) { + public RootNode setNodeOfRole(CtRole role, RootNode newAttrNode) { return attributeSubstititionRequests.put(getFieldOfRole(role), newAttrNode); } @@ -89,9 +89,9 @@ private Metamodel.Field getFieldOfRole(CtRole role) { } @Override - public void forEachParameterInfo(BiConsumer consumer) { + public void forEachParameterInfo(BiConsumer consumer) { if (attributeSubstititionRequests != null) { - for (Node node : attributeSubstititionRequests.values()) { + for (RootNode node : attributeSubstititionRequests.values()) { node.forEachParameterInfo(consumer); } } @@ -107,7 +107,7 @@ public void generateTargets(Generator generator, ResultHolder result, Par } protected void generateSingleNodeAttributes(Generator generator, CtElement clone, ParameterValueProvider parameters) { - for (Map.Entry e : getAttributeSubstititionRequests().entrySet()) { + for (Map.Entry e : getAttributeSubstititionRequests().entrySet()) { Metamodel.Field mmField = e.getKey(); switch (mmField.getContainerKind()) { case SINGLE: @@ -146,7 +146,7 @@ public ParameterValueProvider matchTarget(Object target, ParameterValueProvider //it is spoon element, it matches if to be matched attributes matches //to be matched attributes must be same or substituted //iterate over all attributes of to be matched class - for (Map.Entry e : attributeSubstititionRequests.entrySet()) { + for (Map.Entry e : attributeSubstititionRequests.entrySet()) { parameters = matchesRole(parameters, (CtElement) target, e.getKey(), e.getValue()); if (parameters == null) { return null; @@ -155,7 +155,7 @@ public ParameterValueProvider matchTarget(Object target, ParameterValueProvider return parameters; } - protected ParameterValueProvider matchesRole(ParameterValueProvider parameters, CtElement target, Metamodel.Field mmField, Node attrNode) { + protected ParameterValueProvider matchesRole(ParameterValueProvider parameters, CtElement target, Metamodel.Field mmField, RootNode attrNode) { TobeMatched tobeMatched; if (attrNode instanceof ParameterNode) { //whole attribute value (whole List/Set/Map) has to be stored in parameter @@ -164,7 +164,7 @@ protected ParameterValueProvider matchesRole(ParameterValueProvider parameters, //each item of attribute value (item of List/Set/Map) has to be matched individually tobeMatched = TobeMatched.create(parameters, mmField.getContainerKind(), mmField.getValue(target)); } - return getMatchedParameters(attrNode.matchTargets(tobeMatched, Node.MATCH_ALL)); + return getMatchedParameters(attrNode.matchTargets(tobeMatched, RootNode.MATCH_ALL)); } // @Override diff --git a/src/main/java/spoon/pattern/node/ForEachNode.java b/src/main/java/spoon/pattern/node/ForEachNode.java index a01da3545b2..daeda3b7862 100644 --- a/src/main/java/spoon/pattern/node/ForEachNode.java +++ b/src/main/java/spoon/pattern/node/ForEachNode.java @@ -39,7 +39,7 @@ public class ForEachNode extends AbstractRepeatableMatcher { private PrimitiveMatcher iterableParameter; - private Node nestedModel; + private RootNode nestedModel; private ParameterInfo localParameter; public ForEachNode() { @@ -47,7 +47,7 @@ public ForEachNode() { } @Override - public boolean replaceNode(Node oldNode, Node newNode) { + public boolean replaceNode(RootNode oldNode, RootNode newNode) { if (iterableParameter == oldNode) { oldNode = newNode; return true; @@ -108,12 +108,12 @@ public TobeMatched matchAllWith(TobeMatched tobeMatched) { } @Override - public void forEachParameterInfo(BiConsumer consumer) { + public void forEachParameterInfo(BiConsumer consumer) { iterableParameter.forEachParameterInfo(consumer); consumer.accept(localParameter, this); } - public void setNestedModel(Node valueResolver) { + public void setNestedModel(RootNode valueResolver) { this.nestedModel = valueResolver; } diff --git a/src/main/java/spoon/pattern/node/ListOfNodes.java b/src/main/java/spoon/pattern/node/ListOfNodes.java index af594a58ea8..0c557bd2254 100644 --- a/src/main/java/spoon/pattern/node/ListOfNodes.java +++ b/src/main/java/spoon/pattern/node/ListOfNodes.java @@ -28,26 +28,26 @@ import spoon.pattern.parameter.ParameterValueProvider; /** - * List of {@link Node}s. The {@link Node}s are processed in same order like they were inserted in the list + * List of {@link RootNode}s. The {@link RootNode}s are processed in same order like they were inserted in the list */ -public class ListOfNodes implements Node { - protected List nodes; +public class ListOfNodes implements RootNode { + protected List nodes; - public ListOfNodes(List nodes) { + public ListOfNodes(List nodes) { super(); this.nodes = nodes; } @Override - public void forEachParameterInfo(BiConsumer consumer) { - for (Node node : nodes) { + public void forEachParameterInfo(BiConsumer consumer) { + for (RootNode node : nodes) { node.forEachParameterInfo(consumer); } } @Override public void generateTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters) { - for (Node node : nodes) { + for (RootNode node : nodes) { generator.generateTargets(node, result, parameters); } } @@ -58,13 +58,13 @@ public TobeMatched matchTargets(TobeMatched targets, Matchers nextMatchers) { } /** - * @param oldNode a {@link CtElement} whose {@link Node} we are looking for + * @param oldNode a {@link CtElement} whose {@link RootNode} we are looking for * @return a {@link NodeContainer} of an {@link ElementNode}, whose {@link ElementNode#getTemplateNode()} == `element` * null if element is not referred by any node of this {@link ListOfNodes} */ - public boolean replaceNode(Node oldNode, Node newNode) { + public boolean replaceNode(RootNode oldNode, RootNode newNode) { for (int i = 0; i < nodes.size(); i++) { - Node node = nodes.get(i); + RootNode node = nodes.get(i); if (node == oldNode) { nodes.set(i, newNode); return true; @@ -77,9 +77,9 @@ public boolean replaceNode(Node oldNode, Node newNode) { } /** - * @return {@link List} of {@link Node}s of this {@link ListOfNodes} + * @return {@link List} of {@link RootNode}s of this {@link ListOfNodes} */ - public List getNodes() { + public List getNodes() { return nodes; } } diff --git a/src/main/java/spoon/pattern/node/MapEntryNode.java b/src/main/java/spoon/pattern/node/MapEntryNode.java index f77ff34ee27..5e209a99d18 100644 --- a/src/main/java/spoon/pattern/node/MapEntryNode.java +++ b/src/main/java/spoon/pattern/node/MapEntryNode.java @@ -28,25 +28,25 @@ /** * Represents a ValueResolver of one Map.Entry */ -public class MapEntryNode implements Node { - private final Node key; - private final Node value; +public class MapEntryNode implements RootNode { + private final RootNode key; + private final RootNode value; - public MapEntryNode(Node key, Node value) { + public MapEntryNode(RootNode key, RootNode value) { super(); this.key = key; this.value = value; } - public Node getKey() { + public RootNode getKey() { return key; } - public Node getValue() { + public RootNode getValue() { return value; } @Override - public boolean replaceNode(Node oldNode, Node newNode) { + public boolean replaceNode(RootNode oldNode, RootNode newNode) { //TODO throw new UnsupportedOperationException("TODO"); } @@ -57,7 +57,7 @@ public TobeMatched matchTargets(TobeMatched targets, Matchers nextMatchers) { throw new UnsupportedOperationException("TODO"); } @Override - public void forEachParameterInfo(BiConsumer consumer) { + public void forEachParameterInfo(BiConsumer consumer) { // TODO throw new UnsupportedOperationException("TODO"); } diff --git a/src/main/java/spoon/pattern/node/ModelNode.java b/src/main/java/spoon/pattern/node/ModelNode.java index 2c95ca5f75b..904438852d3 100644 --- a/src/main/java/spoon/pattern/node/ModelNode.java +++ b/src/main/java/spoon/pattern/node/ModelNode.java @@ -27,7 +27,7 @@ */ public class ModelNode extends ListOfNodes { - public ModelNode(List nodes) { + public ModelNode(List nodes) { super(nodes); } diff --git a/src/main/java/spoon/pattern/node/ParameterNode.java b/src/main/java/spoon/pattern/node/ParameterNode.java index 175c06f9139..c4f421666d3 100644 --- a/src/main/java/spoon/pattern/node/ParameterNode.java +++ b/src/main/java/spoon/pattern/node/ParameterNode.java @@ -39,7 +39,7 @@ public ParameterNode(ParameterInfo parameterInfo) { } @Override - public boolean replaceNode(Node oldNode, Node newNode) { + public boolean replaceNode(RootNode oldNode, RootNode newNode) { return false; } @@ -78,7 +78,7 @@ public Quantifier getMatchingStrategy() { } @Override - public void forEachParameterInfo(BiConsumer consumer) { + public void forEachParameterInfo(BiConsumer consumer) { consumer.accept(parameterInfo, this); } diff --git a/src/main/java/spoon/pattern/node/RepeatableMatcher.java b/src/main/java/spoon/pattern/node/RepeatableMatcher.java index 1634540c8e0..2b3a8740649 100644 --- a/src/main/java/spoon/pattern/node/RepeatableMatcher.java +++ b/src/main/java/spoon/pattern/node/RepeatableMatcher.java @@ -21,9 +21,9 @@ /** * Defines API of a repeatable matcher. - * It is kind of {@link Node}, where one {@link Node} may match 0, 1 or more `target` nodes. + * It is kind of {@link RootNode}, where one {@link RootNode} may match 0, 1 or more `target` nodes. */ -public interface RepeatableMatcher extends Node { +public interface RepeatableMatcher extends RootNode { /** * If two {@link RepeatableMatcher}s in a list are matching the same element, * then returned {@link Quantifier} defines how resolve this conflict diff --git a/src/main/java/spoon/pattern/node/Node.java b/src/main/java/spoon/pattern/node/RootNode.java similarity index 80% rename from src/main/java/spoon/pattern/node/Node.java rename to src/main/java/spoon/pattern/node/RootNode.java index 871667abe89..01444eae4ad 100644 --- a/src/main/java/spoon/pattern/node/Node.java +++ b/src/main/java/spoon/pattern/node/RootNode.java @@ -36,15 +36,15 @@ *
        • to match zero, one or more instances of model and deliver a matching parameters
        • * */ -public interface Node extends Matchers { +public interface RootNode extends Matchers { /** - * Calls consumer for each pair of parameter definition ({@link ParameterInfo}) and {@link Node}, which uses it - * @param consumer the receiver of pairs of {@link ParameterInfo} and {@link Node} + * Calls consumer for each pair of parameter definition ({@link ParameterInfo}) and {@link RootNode}, which uses it + * @param consumer the receiver of pairs of {@link ParameterInfo} and {@link RootNode} */ - void forEachParameterInfo(BiConsumer consumer); + void forEachParameterInfo(BiConsumer consumer); /** - * Generates zero, one or more target depending on kind of this {@link Node}, expected `result` and input `parameters` + * Generates zero, one or more target depending on kind of this {@link RootNode}, expected `result` and input `parameters` * @param generator {@link Generator} which drives generation process * @param result holder for the generated objects * @param parameters a {@link ParameterValueProvider} holding parameters @@ -53,13 +53,13 @@ public interface Node extends Matchers { /** * @param targets to be matched target nodes and input parameters - * @param nextMatchers Chain of matchers which has to be processed after this {@link Node} + * @param nextMatchers Chain of matchers which has to be processed after this {@link RootNode} * @return new parameters and container with remaining targets */ TobeMatched matchTargets(TobeMatched targets, Matchers nextMatchers); /** - * The special implementation of {@link Matchers}, which is used as last {@link Node} in case when ALL target nodes must match with all template nodes + * The special implementation of {@link Matchers}, which is used as last {@link RootNode} in case when ALL target nodes must match with all template nodes */ Matchers MATCH_ALL = new Matchers() { @Override @@ -69,7 +69,7 @@ public TobeMatched matchAllWith(TobeMatched targets) { } }; /** - * The special implementation of {@link Matchers}, which is used as last {@link Node} in case when SOME target nodes must match with all template nodes + * The special implementation of {@link Matchers}, which is used as last {@link RootNode} in case when SOME target nodes must match with all template nodes */ Matchers MATCH_PART = new Matchers() { @Override @@ -85,10 +85,10 @@ default TobeMatched matchAllWith(TobeMatched targets) { } /** - * @param oldNode old {@link Node} - * @param newNode new {@link Node} - * @return a true if `oldNode` was found in this {@link Node} or it's children and replaced by `newNode` + * @param oldNode old {@link RootNode} + * @param newNode new {@link RootNode} + * @return a true if `oldNode` was found in this {@link RootNode} or it's children and replaced by `newNode` * false if `oldNode` was not found */ - boolean replaceNode(Node oldNode, Node newNode); + boolean replaceNode(RootNode oldNode, RootNode newNode); } diff --git a/src/main/java/spoon/pattern/node/StringNode.java b/src/main/java/spoon/pattern/node/StringNode.java index 13732e4a67d..dd8a9a481ec 100644 --- a/src/main/java/spoon/pattern/node/StringNode.java +++ b/src/main/java/spoon/pattern/node/StringNode.java @@ -125,7 +125,7 @@ public Map getReplaceMarkers() { } @Override - public void forEachParameterInfo(BiConsumer consumer) { + public void forEachParameterInfo(BiConsumer consumer) { Map visitedParams = new IdentityHashMap<>(tobeReplacedSubstrings.size()); for (ParameterInfo parameterInfo : tobeReplacedSubstrings.values()) { //assure that each parameterInfo is called only once diff --git a/src/main/java/spoon/pattern/node/SwitchNode.java b/src/main/java/spoon/pattern/node/SwitchNode.java index 8e8f595e7fc..e61ef4e0794 100644 --- a/src/main/java/spoon/pattern/node/SwitchNode.java +++ b/src/main/java/spoon/pattern/node/SwitchNode.java @@ -39,7 +39,7 @@ * ... someStatements in other cases ... * } */ -public class SwitchNode implements Node { +public class SwitchNode implements RootNode { private List cases = new ArrayList<>(); @@ -48,7 +48,7 @@ public SwitchNode() { } @Override - public boolean replaceNode(Node oldNode, Node newNode) { + public boolean replaceNode(RootNode oldNode, RootNode newNode) { for (CaseNode caseNode : cases) { if (caseNode.replaceNode(oldNode, newNode)) { return true; @@ -62,7 +62,7 @@ public boolean replaceNode(Node oldNode, Node newNode) { * @param vrOfExpression if value of this parameter is true then statement has to be used. If vrOfExpression is null, then statement is always used * @param statement optional statement */ - public void addCase(PrimitiveMatcher vrOfExpression, Node statement) { + public void addCase(PrimitiveMatcher vrOfExpression, RootNode statement) { cases.add(new CaseNode(vrOfExpression, statement)); } @@ -74,7 +74,7 @@ public void generateTargets(Generator generator, ResultHolder result, Par } @Override - public void forEachParameterInfo(BiConsumer consumer) { + public void forEachParameterInfo(BiConsumer consumer) { for (CaseNode case1 : cases) { if (case1.vrOfExpression != null) { case1.vrOfExpression.forEachParameterInfo(consumer); @@ -111,20 +111,20 @@ public TobeMatched matchTargets(TobeMatched targets, Matchers nextMatchers) { return new CaseNode(null, null).matchTargets(targets, nextMatchers); } - private class CaseNode implements Node { + private class CaseNode implements RootNode { /* * is null for the default case */ private PrimitiveMatcher vrOfExpression; - private Node statement; - private CaseNode(PrimitiveMatcher vrOfExpression, Node statement) { + private RootNode statement; + private CaseNode(PrimitiveMatcher vrOfExpression, RootNode statement) { super(); this.vrOfExpression = vrOfExpression; this.statement = statement; } @Override - public boolean replaceNode(Node oldNode, Node newNode) { + public boolean replaceNode(RootNode oldNode, RootNode newNode) { if (vrOfExpression != null) { if (vrOfExpression == oldNode) { vrOfExpression = (PrimitiveMatcher) newNode; @@ -167,7 +167,7 @@ public TobeMatched matchTargets(TobeMatched targets, Matchers nextMatchers) { return nextMatchers.matchAllWith(targets); } @Override - public void forEachParameterInfo(BiConsumer consumer) { + public void forEachParameterInfo(BiConsumer consumer) { SwitchNode.this.forEachParameterInfo(consumer); } @Override diff --git a/src/main/java/spoon/pattern/parameter/ParameterInfo.java b/src/main/java/spoon/pattern/parameter/ParameterInfo.java index 8c16e4092e3..95c79e86650 100644 --- a/src/main/java/spoon/pattern/parameter/ParameterInfo.java +++ b/src/main/java/spoon/pattern/parameter/ParameterInfo.java @@ -19,7 +19,7 @@ import spoon.pattern.Pattern; import spoon.pattern.ResultHolder; import spoon.pattern.matcher.Quantifier; -import spoon.pattern.node.Node; +import spoon.pattern.node.RootNode; import spoon.reflect.factory.Factory; /** @@ -64,7 +64,7 @@ public interface ParameterInfo { Class getParameterValueType(); /** - * @return the strategy used to resolve conflict between two {@link Node}s + * @return the strategy used to resolve conflict between two {@link RootNode}s */ Quantifier getMatchingStrategy(); From 7414078f7e36c599426b6852f0ecf5b773edbd68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Wed, 7 Mar 2018 15:29:06 +0100 Subject: [PATCH 012/131] AbstractNode#toString --- .../java/spoon/pattern/node/AbstractNode.java | 33 +++++++++++++++++++ .../node/AbstractRepeatableMatcher.java | 2 +- .../java/spoon/pattern/node/ListOfNodes.java | 2 +- .../java/spoon/pattern/node/MapEntryNode.java | 2 +- .../java/spoon/pattern/node/SwitchNode.java | 4 +-- 5 files changed, 38 insertions(+), 5 deletions(-) create mode 100644 src/main/java/spoon/pattern/node/AbstractNode.java diff --git a/src/main/java/spoon/pattern/node/AbstractNode.java b/src/main/java/spoon/pattern/node/AbstractNode.java new file mode 100644 index 00000000000..d1f213d6f55 --- /dev/null +++ b/src/main/java/spoon/pattern/node/AbstractNode.java @@ -0,0 +1,33 @@ +/** + * 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.pattern.node; + +import spoon.pattern.PatternPrinter; + +/** + * Represents a parameterized Pattern ValueResolver, which can be used + *
            + *
          • to generate a zero, one or more copies of model using provided parameters
          • + *
          • to match zero, one or more instances of model and deliver a matching parameters
          • + *
          + */ +public abstract class AbstractNode implements RootNode { + @Override + public String toString() { + return new PatternPrinter().printNode(this); + } +} diff --git a/src/main/java/spoon/pattern/node/AbstractRepeatableMatcher.java b/src/main/java/spoon/pattern/node/AbstractRepeatableMatcher.java index a3372af70e1..6726158f6f7 100644 --- a/src/main/java/spoon/pattern/node/AbstractRepeatableMatcher.java +++ b/src/main/java/spoon/pattern/node/AbstractRepeatableMatcher.java @@ -23,7 +23,7 @@ /** * Defines algorithm of repeatable matcher. */ -abstract class AbstractRepeatableMatcher implements RepeatableMatcher { +abstract class AbstractRepeatableMatcher extends AbstractNode implements RepeatableMatcher { @Override public TobeMatched matchTargets(TobeMatched targets, Matchers next) { diff --git a/src/main/java/spoon/pattern/node/ListOfNodes.java b/src/main/java/spoon/pattern/node/ListOfNodes.java index 0c557bd2254..eaf7eaf3f34 100644 --- a/src/main/java/spoon/pattern/node/ListOfNodes.java +++ b/src/main/java/spoon/pattern/node/ListOfNodes.java @@ -30,7 +30,7 @@ /** * List of {@link RootNode}s. The {@link RootNode}s are processed in same order like they were inserted in the list */ -public class ListOfNodes implements RootNode { +public class ListOfNodes extends AbstractNode { protected List nodes; public ListOfNodes(List nodes) { diff --git a/src/main/java/spoon/pattern/node/MapEntryNode.java b/src/main/java/spoon/pattern/node/MapEntryNode.java index 5e209a99d18..b965ce7799e 100644 --- a/src/main/java/spoon/pattern/node/MapEntryNode.java +++ b/src/main/java/spoon/pattern/node/MapEntryNode.java @@ -28,7 +28,7 @@ /** * Represents a ValueResolver of one Map.Entry */ -public class MapEntryNode implements RootNode { +public class MapEntryNode extends AbstractNode { private final RootNode key; private final RootNode value; diff --git a/src/main/java/spoon/pattern/node/SwitchNode.java b/src/main/java/spoon/pattern/node/SwitchNode.java index e61ef4e0794..945422e3662 100644 --- a/src/main/java/spoon/pattern/node/SwitchNode.java +++ b/src/main/java/spoon/pattern/node/SwitchNode.java @@ -39,7 +39,7 @@ * ... someStatements in other cases ... * } */ -public class SwitchNode implements RootNode { +public class SwitchNode extends AbstractNode { private List cases = new ArrayList<>(); @@ -111,7 +111,7 @@ public TobeMatched matchTargets(TobeMatched targets, Matchers nextMatchers) { return new CaseNode(null, null).matchTargets(targets, nextMatchers); } - private class CaseNode implements RootNode { + private class CaseNode extends AbstractNode { /* * is null for the default case */ From e71c08515bc32d962ddc2e92935772589288394e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Wed, 7 Mar 2018 16:29:27 +0100 Subject: [PATCH 013/131] Printing of pattern sources of live statements --- .../java/spoon/pattern/DefaultGenerator.java | 2 +- .../java/spoon/pattern/PatternPrinter.java | 65 +++++------------ .../java/spoon/pattern/node/ForEachNode.java | 19 ++++- .../java/spoon/pattern/node/LiveNode.java | 38 ++++++++++ .../java/spoon/pattern/node/SwitchNode.java | 69 ++++++++++++++++++- 5 files changed, 141 insertions(+), 52 deletions(-) create mode 100644 src/main/java/spoon/pattern/node/LiveNode.java diff --git a/src/main/java/spoon/pattern/DefaultGenerator.java b/src/main/java/spoon/pattern/DefaultGenerator.java index 0b58c8c1b5c..d8a8dd89b7c 100644 --- a/src/main/java/spoon/pattern/DefaultGenerator.java +++ b/src/main/java/spoon/pattern/DefaultGenerator.java @@ -25,7 +25,7 @@ * Drives generation process */ public class DefaultGenerator implements Generator { - private final Factory factory; + protected final Factory factory; public DefaultGenerator(Factory factory) { super(); diff --git a/src/main/java/spoon/pattern/PatternPrinter.java b/src/main/java/spoon/pattern/PatternPrinter.java index ec2bf075c14..4d2f9a15710 100644 --- a/src/main/java/spoon/pattern/PatternPrinter.java +++ b/src/main/java/spoon/pattern/PatternPrinter.java @@ -17,11 +17,11 @@ package spoon.pattern; import java.util.List; -import java.util.Map; import spoon.SpoonException; +import spoon.pattern.node.LiveNode; import spoon.pattern.node.RootNode; -import spoon.pattern.parameter.AbstractParameterInfo; +import spoon.pattern.parameter.ParameterInfo; import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.code.CtInvocation; import spoon.reflect.code.CtLocalVariable; @@ -42,7 +42,7 @@ public PatternPrinter() { } public String printNode(RootNode node) { - List generated = generateTargets(node, new Params(), null); + List generated = generateTargets(node, (ParameterValueProvider) null, null); StringBuilder sb = new StringBuilder(); for (CtElement ele : generated) { sb.append(ele.toString()).append('\n'); @@ -50,45 +50,19 @@ public String printNode(RootNode node) { return sb.toString(); } - private static class Params implements ParameterValueProvider { - @Override - public boolean hasValue(String parameterName) { - return true; - } - - @Override - public Object getValue(String parameterName) { - return new ParameterMarker(parameterName); - } - - @Override - public ParameterValueProvider putValueToCopy(String parameterName, Object value) { - throw new SpoonException("This provider is just for printing"); - } - - @Override - public Map asMap() { - throw new SpoonException("This provider is just for printing"); - } - - @Override - public ParameterValueProvider createLocalParameterValueProvider() { - throw new SpoonException("This provider is just for printing"); - } - - @Override - public Map asLocalMap() { - throw new SpoonException("This provider is just for printing"); + @Override + public void generateTargets(RootNode node, ResultHolder result, ParameterValueProvider parameters) { + if (node instanceof LiveNode) { + //this is a Live node. Do not generated nodes normally, but generate origin live statements + ((LiveNode) node).generateLiveTargets(this, result, parameters); + return; } + super.generateTargets(node, result, parameters); } - private static class ParameterMarker { - final String name; - - ParameterMarker(String name) { - super(); - this.name = name; - } + @Override + public void getValueAs(ParameterInfo parameterInfo, ResultHolder result, ParameterValueProvider parameters) { + result.addResult(generatePatternParameterElement(parameterInfo, result.getRequiredClass())); } /** @@ -99,20 +73,15 @@ private static class ParameterMarker { * @return dummy template element, which represents a template type in source of generated Pattern. * Or null if potentialParameterMarker is not a marker of parameter */ - public static T generatePatternParameterElement(Factory factory, Object potentialParameterMarker, Class type, AbstractParameterInfo parameterInfo) { - if (potentialParameterMarker instanceof ParameterMarker == false) { - return null; - } - ParameterMarker paramMarker = (ParameterMarker) potentialParameterMarker; + private T generatePatternParameterElement(ParameterInfo parameterInfo, Class type) { if (type != null) { if (type.isAssignableFrom(CtInvocation.class)) { - return (T) factory.createInvocation(factory.createThisAccess(factory.Type().objectType(), true), factory.createExecutableReference().setSimpleName(paramMarker.name)); + return (T) factory.createInvocation(factory.createThisAccess(factory.Type().objectType(), true), factory.createExecutableReference().setSimpleName(parameterInfo.getName())); } if (type.isAssignableFrom(CtLocalVariable.class)) { - return (T) factory.createLocalVariable(factory.Type().objectType(), paramMarker.name, null); + return (T) factory.createLocalVariable(factory.Type().objectType(), parameterInfo.getName(), null); } } - PatternPrinter.class.getClass(); - return null; + throw new SpoonException("Pattern Parameter is on Unsupported place"); } } diff --git a/src/main/java/spoon/pattern/node/ForEachNode.java b/src/main/java/spoon/pattern/node/ForEachNode.java index daeda3b7862..94c5c911505 100644 --- a/src/main/java/spoon/pattern/node/ForEachNode.java +++ b/src/main/java/spoon/pattern/node/ForEachNode.java @@ -25,6 +25,11 @@ import spoon.pattern.matcher.TobeMatched; import spoon.pattern.parameter.ParameterInfo; import spoon.pattern.parameter.ParameterValueProvider; +import spoon.reflect.code.CtBlock; +import spoon.reflect.code.CtExpression; +import spoon.reflect.code.CtForEach; +import spoon.reflect.code.CtStatement; +import spoon.reflect.factory.Factory; /** * Pattern node of multiple occurrences of the same model, just with different parameters. @@ -36,7 +41,7 @@ * * where parameter values are _x_ = ["a", "b", getStringOf(p1, p2)] */ -public class ForEachNode extends AbstractRepeatableMatcher { +public class ForEachNode extends AbstractRepeatableMatcher implements LiveNode { private PrimitiveMatcher iterableParameter; private RootNode nestedModel; @@ -139,4 +144,16 @@ public boolean isMandatory(ParameterValueProvider parameters) { public boolean isTryNextMatch(ParameterValueProvider parameters) { return iterableParameter.isTryNextMatch(parameters); } + + @Override + public void generateLiveTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters) { + Factory f = generator.getFactory(); + CtForEach forEach = f.Core().createForEach(); + forEach.setVariable(f.Code().createLocalVariable(f.Type().objectType(), localParameter.getName(), null)); + forEach.setExpression(generator.generateTarget(iterableParameter, parameters, CtExpression.class)); + CtBlock body = f.createBlock(); + body.setStatements(generator.generateTargets(nestedModel, parameters, CtStatement.class)); + forEach.setBody(body); + result.addResult((T) forEach); + } } diff --git a/src/main/java/spoon/pattern/node/LiveNode.java b/src/main/java/spoon/pattern/node/LiveNode.java new file mode 100644 index 00000000000..6db5dd673f7 --- /dev/null +++ b/src/main/java/spoon/pattern/node/LiveNode.java @@ -0,0 +1,38 @@ +/** + * 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.pattern.node; + +import spoon.pattern.Generator; +import spoon.pattern.ResultHolder; +import spoon.pattern.parameter.ParameterValueProvider; + +/** + * Represents a kind of {@link RootNode}, + * whose AST statements are understood as pattern statements. + * For example CtForEach statement is handled as repeated generation of pattern + * Or CtIf statement is handled as optionally generated pattern + */ +public interface LiveNode extends RootNode { + /** + * Generates Live statements of this live {@link RootNode}. + * This method is used when sources of pattern have to be printed + * @param generator + * @param result holder of the result + * @param parameters + */ + void generateLiveTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters); +} diff --git a/src/main/java/spoon/pattern/node/SwitchNode.java b/src/main/java/spoon/pattern/node/SwitchNode.java index 945422e3662..95065595a18 100644 --- a/src/main/java/spoon/pattern/node/SwitchNode.java +++ b/src/main/java/spoon/pattern/node/SwitchNode.java @@ -20,12 +20,18 @@ import java.util.List; import java.util.function.BiConsumer; +import spoon.SpoonException; import spoon.pattern.Generator; import spoon.pattern.ResultHolder; import spoon.pattern.matcher.Matchers; import spoon.pattern.matcher.TobeMatched; import spoon.pattern.parameter.ParameterInfo; import spoon.pattern.parameter.ParameterValueProvider; +import spoon.reflect.code.CtBlock; +import spoon.reflect.code.CtExpression; +import spoon.reflect.code.CtIf; +import spoon.reflect.code.CtStatement; +import spoon.reflect.factory.CoreFactory; import spoon.reflect.factory.Factory; /** @@ -39,7 +45,7 @@ * ... someStatements in other cases ... * } */ -public class SwitchNode extends AbstractNode { +public class SwitchNode extends AbstractNode implements LiveNode { private List cases = new ArrayList<>(); @@ -111,7 +117,7 @@ public TobeMatched matchTargets(TobeMatched targets, Matchers nextMatchers) { return new CaseNode(null, null).matchTargets(targets, nextMatchers); } - private class CaseNode extends AbstractNode { + private class CaseNode extends AbstractNode implements LiveNode { /* * is null for the default case */ @@ -185,5 +191,64 @@ private boolean isCaseSelected(Generator generator, ParameterValueProvider param Boolean value = generator.generateTarget(vrOfExpression, parameters, Boolean.class); return value == null ? false : value.booleanValue(); } + + @Override + public void generateLiveTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters) { + Factory f = generator.getFactory(); + CoreFactory cf = f.Core(); + CtBlock block = cf.createBlock(); + if (statement != null) { + block.setStatements(generator.generateTargets(statement, parameters, CtStatement.class)); + } + if (vrOfExpression != null) { + //There is if expression + CtIf ifStmt = cf.createIf(); + ifStmt.setCondition(generator.generateTarget(vrOfExpression, parameters, CtExpression.class)); + ifStmt.setThenStatement(block); + result.addResult((T) ifStmt); + } else { + //There is no expression. It represents the last else block + result.addResult((T) block); + } + } + } + + @Override + public void generateLiveTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters) { + CtStatement resultStmt = null; + CtStatement lastElse = null; + CtIf lastIf = null; + for (CaseNode caseNode : cases) { + CtStatement stmt = generator.generateTarget(caseNode, parameters, CtStatement.class); + if (stmt instanceof CtIf) { + CtIf ifStmt = (CtIf) stmt; + if (lastIf == null) { + //it is first IF + resultStmt = ifStmt; + lastIf = ifStmt; + } else { + //it is next IF. Append it as else into last IF + lastIf.setElseStatement(ifStmt); + lastIf = ifStmt; + } + } else { + if (lastElse != null) { + throw new SpoonException("Only one SwitchNode can have no expression."); + } + lastElse = stmt; + } + } + if (lastIf == null) { + //there is no IF + if (lastElse != null) { + result.addResult((T) lastElse); + } + return; + } + if (lastElse != null) { + //append last else into lastIf + lastIf.setElseStatement(lastElse); + } + result.addResult((T) resultStmt); } } From c54079b260274aba3fce150cf32f2256a110ae77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Wed, 7 Mar 2018 16:39:38 +0100 Subject: [PATCH 014/131] ElementNode toString --- src/main/java/spoon/pattern/node/ElementNode.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/spoon/pattern/node/ElementNode.java b/src/main/java/spoon/pattern/node/ElementNode.java index 5608d2bda89..8a902c1fd03 100644 --- a/src/main/java/spoon/pattern/node/ElementNode.java +++ b/src/main/java/spoon/pattern/node/ElementNode.java @@ -167,6 +167,10 @@ protected ParameterValueProvider matchesRole(ParameterValueProvider parameters, return getMatchedParameters(attrNode.matchTargets(tobeMatched, RootNode.MATCH_ALL)); } + @Override + public String toString() { + return elementType.getName() + ": " + super.toString(); + } // @Override // public String toString() { // PrinterHelper printer = new PrinterHelper(getTemplateNode().getFactory().getEnvironment()); From 0fd2162ad941d41aefb595eb5df9cd902a19fcda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Wed, 7 Mar 2018 19:03:30 +0100 Subject: [PATCH 015/131] matcher ignores some roles --- .../java/spoon/pattern/PatternBuilder.java | 32 ------------------- .../java/spoon/pattern/node/ElementNode.java | 30 +++++++++++++++++ 2 files changed, 30 insertions(+), 32 deletions(-) diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index 8a0a8b2ea9f..64cfef12a6f 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -257,38 +257,6 @@ private static RootNode listOfNodesToNode(List nodes) { return new ListOfNodes((List) nodes); } -// private static final Map roleToSkippedClass = new HashMap<>(); -// static { -// roleToSkippedClass.put(CtRole.COMMENT, new Class[]{Object.class}); -// roleToSkippedClass.put(CtRole.POSITION, new Class[]{Object.class}); -// roleToSkippedClass.put(CtRole.TYPE, new Class[]{CtInvocation.class, CtExecutableReference.class}); -// roleToSkippedClass.put(CtRole.DECLARING_TYPE, new Class[]{CtExecutableReference.class}); -// roleToSkippedClass.put(CtRole.INTERFACE, new Class[]{CtTypeReference.class}); -// roleToSkippedClass.put(CtRole.MODIFIER, new Class[]{CtTypeReference.class}); -// roleToSkippedClass.put(CtRole.SUPER_TYPE, new Class[]{CtTypeReference.class}); -// } -// -// /** -// * @param roleHandler the to be checked role -// * @param targetClass the class which is going to be checked -// * @return true if the role is relevant for matching process -// */ -// private static boolean isMatchingRole(RoleHandler roleHandler, Class targetClass) { -// //match on super roles only. Ignore derived roles -// if (roleHandler.getRole().getSuperRole() != null) { -// return false; -// } -// Class[] classes = roleToSkippedClass.get(roleHandler.getRole()); -// if (classes != null) { -// for (Class cls : classes) { -// if (cls.isAssignableFrom(targetClass)) { -// return false; -// } -// } -// } -// return true; -// } - /** * @param element a CtElement * @return {@link RootNode}, which handles matching/generation of an `object` from the source spoon AST. diff --git a/src/main/java/spoon/pattern/node/ElementNode.java b/src/main/java/spoon/pattern/node/ElementNode.java index 8a902c1fd03..ca1f38d1860 100644 --- a/src/main/java/spoon/pattern/node/ElementNode.java +++ b/src/main/java/spoon/pattern/node/ElementNode.java @@ -34,6 +34,7 @@ import spoon.reflect.declaration.CtElement; import spoon.reflect.meta.ContainerKind; import spoon.reflect.path.CtRole; +import spoon.reflect.reference.CtExecutableReference; import static spoon.pattern.matcher.TobeMatched.getMatchedParameters; @@ -156,6 +157,9 @@ public ParameterValueProvider matchTarget(Object target, ParameterValueProvider } protected ParameterValueProvider matchesRole(ParameterValueProvider parameters, CtElement target, Metamodel.Field mmField, RootNode attrNode) { + if (isMatchingRole(mmField.getRole(), elementType.getModelInterface()) == false) { + return parameters; + } TobeMatched tobeMatched; if (attrNode instanceof ParameterNode) { //whole attribute value (whole List/Set/Map) has to be stored in parameter @@ -167,6 +171,32 @@ protected ParameterValueProvider matchesRole(ParameterValueProvider parameters, return getMatchedParameters(attrNode.matchTargets(tobeMatched, RootNode.MATCH_ALL)); } + private static final Map roleToSkippedClass = new HashMap<>(); + static { + roleToSkippedClass.put(CtRole.COMMENT, new Class[]{Object.class}); + roleToSkippedClass.put(CtRole.POSITION, new Class[]{Object.class}); + roleToSkippedClass.put(CtRole.TYPE, new Class[]{CtExecutableReference.class}); + roleToSkippedClass.put(CtRole.DECLARING_TYPE, new Class[]{CtExecutableReference.class}); + } + + /** + * @param roleHandler the to be checked role + * @param targetClass the class which is going to be checked + * @return true if the role is relevant for matching process + */ + private static boolean isMatchingRole(CtRole role, Class targetClass) { + Class[] classes = roleToSkippedClass.get(role); + if (classes != null) { + for (Class cls : classes) { + if (cls.isAssignableFrom(targetClass)) { + return false; + } + } + } + return true; + } + + @Override public String toString() { return elementType.getName() + ": " + super.toString(); From 94881eacf5cfbb5cf3bc49d834c36bb331e46663 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Wed, 7 Mar 2018 19:13:21 +0100 Subject: [PATCH 016/131] fix Template containerKind --- src/main/java/spoon/pattern/PatternBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index 64cfef12a6f..3bb60280682 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -803,7 +803,7 @@ private void configureTemplateParameter(CtType templateType, Map Date: Wed, 7 Mar 2018 19:52:31 +0100 Subject: [PATCH 017/131] position is generated too - same like before --- src/main/java/spoon/pattern/PatternBuilder.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index 3bb60280682..1276ca99032 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -18,7 +18,6 @@ import java.lang.annotation.Annotation; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -166,8 +165,6 @@ private List createImplicitNodes(List elements) { return nodes; } - private final Set IGNORED_ROLES = Collections.unmodifiableSet((new HashSet<>(Arrays.asList(CtRole.POSITION)))); - private RootNode createImplicitNode(Object object) { if (object instanceof CtElement) { //it is a spoon element @@ -179,7 +176,7 @@ private RootNode createImplicitNode(Object object) { } //iterate over all attributes of that element for (Metamodel.Field mmField : mmConcept.getFields()) { - if (mmField.isDerived() || IGNORED_ROLES.contains(mmField.getRole())) { + if (mmField.isDerived()) { //skip derived fields, they are not relevant for matching or generating continue; } From 89970cc73f6a83c357cc00efb8c36c24b73778bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Wed, 7 Mar 2018 21:52:29 +0100 Subject: [PATCH 018/131] generate by --- .../spoon/pattern/GeneratedByProvider.java | 120 +++++++++++++ .../java/spoon/pattern/PatternBuilder.java | 137 ++++---------- .../spoon/pattern/SubstitutionCloner.java | 48 ----- .../java/spoon/pattern/TemplateBuilder.java | 5 + .../java/spoon/pattern/node/ElementNode.java | 167 ++++++++++++++++-- .../java/spoon/template/BlockTemplate.java | 2 +- .../spoon/template/ExpressionTemplate.java | 2 +- .../spoon/template/StatementTemplate.java | 4 +- .../java/spoon/template/Substitution.java | 6 +- 9 files changed, 323 insertions(+), 168 deletions(-) create mode 100644 src/main/java/spoon/pattern/GeneratedByProvider.java diff --git a/src/main/java/spoon/pattern/GeneratedByProvider.java b/src/main/java/spoon/pattern/GeneratedByProvider.java new file mode 100644 index 00000000000..747d351d5ac --- /dev/null +++ b/src/main/java/spoon/pattern/GeneratedByProvider.java @@ -0,0 +1,120 @@ +/** + * 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.pattern; + +import java.util.HashMap; + +import spoon.Metamodel; +import spoon.pattern.node.ConstantNode; +import spoon.pattern.node.ElementNode; +import spoon.pattern.node.ListOfNodes; +import spoon.pattern.node.RootNode; +import spoon.reflect.code.CtJavaDoc; +import spoon.reflect.code.CtComment.CommentType; +import spoon.reflect.cu.CompilationUnit; +import spoon.reflect.cu.SourcePosition; +import spoon.reflect.declaration.CtElement; +import spoon.reflect.declaration.CtType; +import spoon.reflect.declaration.CtTypeMember; +import spoon.reflect.path.CtRole; + +/** + */ +class GeneratedByProvider { + public String getGeneratedByComment(CtElement ele) { + SourcePosition pos = ele.getPosition(); + if (pos != null) { + CompilationUnit cu = pos.getCompilationUnit(); + if (cu != null) { + CtType mainType = cu.getMainType(); + if (mainType != null) { + StringBuilder result = new StringBuilder(); + result.append("Generated by "); + result.append(mainType.getQualifiedName()); + appendInnerTypedElements(result, mainType, ele); + result.append('('); + result.append(mainType.getSimpleName()); + result.append(".java:"); + result.append(pos.getLine()); + result.append(')'); + return result.toString(); + } + } + } + return null; + } + protected void appendInnerTypedElements(StringBuilder result, CtType mainType, CtElement ele) { + CtTypeMember typeMember = getFirst(ele, CtTypeMember.class); + if (typeMember != null && typeMember != mainType) { + if (typeMember.isParentInitialized()) { + appendInnerTypedElements(result, mainType, typeMember.getParent()); + } + if (typeMember instanceof CtType) { + result.append('$'); + } else { + result.append('#'); + } + result.append(typeMember.getSimpleName()); + } + } + @SuppressWarnings("unchecked") + protected T getFirst(CtElement ele, Class clazz) { + if (ele != null) { + if (clazz.isAssignableFrom(ele.getClass())) { + return (T) ele; + } + if (ele.isParentInitialized()) { + return getFirst(ele.getParent(), clazz); + } + } + return null; + } + + public void addGeneratedByComment(RootNode node, String generatedBy) { + if (node instanceof ElementNode) { + ElementNode elementNode = (ElementNode) node; + ListOfNodes commentNode = (ListOfNodes) elementNode.getOrCreateNodeOfRole(CtRole.COMMENT, new HashMap<>()); + ElementNode javaDocComment = getJavaDocComment(commentNode); + String content = javaDocComment.getValueOfRole(CtRole.COMMENT_CONTENT, String.class); + if (content == null) { + return; + } + String EOL = System.getProperty("line.separator"); + if (content.trim().length() > 0) { + content += EOL + EOL; + } + content += generatedBy; + javaDocComment.setNodeOfRole(CtRole.COMMENT_CONTENT, new ConstantNode(content)); + } + } + + private ElementNode getJavaDocComment(ListOfNodes commentNode) { + for (RootNode node : commentNode.getNodes()) { + if (node instanceof ElementNode) { + ElementNode eleNode = (ElementNode) node; + if (eleNode.getElementType().getModelInterface() == CtJavaDoc.class) { + return eleNode; + } + } + } + ElementNode node = new ElementNode(Metamodel.getMetamodelTypeByClass(CtJavaDoc.class)); + node.setNodeOfRole(CtRole.COMMENT_TYPE, new ConstantNode(CommentType.JAVADOC)); + node.setNodeOfRole(CtRole.COMMENT_CONTENT, new ConstantNode("")); + commentNode.getNodes().add(node); + return node; + } +} diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index 1276ca99032..18367a18674 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -29,16 +29,11 @@ import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; -import java.util.stream.Collectors; -import spoon.Metamodel; import spoon.SpoonException; import spoon.pattern.ParametersBuilder.ParameterElementPair; -import spoon.pattern.matcher.Matchers; -import spoon.pattern.node.ConstantNode; import spoon.pattern.node.ElementNode; import spoon.pattern.node.ListOfNodes; -import spoon.pattern.node.MapEntryNode; import spoon.pattern.node.ModelNode; import spoon.pattern.node.RootNode; import spoon.pattern.node.ParameterNode; @@ -119,6 +114,7 @@ public static PatternBuilder create(CtType templateType, Consumer templateTypeRef, List templ } this.factory = templateTypeRef.getFactory(); this.valueConvertor = new ValueConvertorImpl(); - patternNodes = new ListOfNodes(createImplicitNodes(template)); + patternNodes = ElementNode.create(template, patternElementToSubstRequests); patternQuery = new PatternBuilder.PatternQuery(factory.Query(), patternModel); configureParameters(pb -> { pb.parameter(TARGET_TYPE).byType(templateTypeRef).setValueType(CtTypeReference.class); }); } - private List createImplicitNodes(List elements) { - List nodes = new ArrayList<>(elements.size()); - for (CtElement element : elements) { - nodes.add(createImplicitNode(element)); - } - return nodes; - } - - private RootNode createImplicitNode(Object object) { - if (object instanceof CtElement) { - //it is a spoon element - CtElement element = (CtElement) object; - Metamodel.Type mmConcept = Metamodel.getMetamodelTypeByClass(element.getClass()); - ElementNode elementNode = new ElementNode(mmConcept); - if (patternElementToSubstRequests.put(element, elementNode) != null) { - throw new SpoonException("Each pattern element can have only one implicit Node."); - } - //iterate over all attributes of that element - for (Metamodel.Field mmField : mmConcept.getFields()) { - if (mmField.isDerived()) { - //skip derived fields, they are not relevant for matching or generating - continue; - } - elementNode.setNodeOfRole(mmField.getRole(), createImplicitNode(mmField.getContainerKind(), mmField.getValue(element))); - } - return elementNode; - } - //TODO share instances of ConstantNode between equal `object`s - e.g. null, booleans, Strings, ... - return new ConstantNode(object); - } - - private RootNode createImplicitNode(ContainerKind containerKind, Object templates) { - switch (containerKind) { - case LIST: - return createImplicitNode((List) templates); - case SET: - return createImplicitNode((Set) templates); - case MAP: - return createImplicitNode((Map) templates); - case SINGLE: - return createImplicitNode(templates); - } - throw new SpoonException("Unexpected RoleHandler containerKind: " + containerKind); - } - - private RootNode createImplicitNode(List objects) { - return listOfNodesToNode(objects.stream().map(i -> createImplicitNode(i)).collect(Collectors.toList())); - } - - private RootNode createImplicitNode(Set templates) { - //collect plain template nodes without any substitution request as List, because Spoon Sets have predictable order. - List constantMatchers = new ArrayList<>(templates.size()); - //collect template nodes with a substitution request - List variableMatchers = new ArrayList<>(); - for (Object template : templates) { - RootNode matcher = createImplicitNode(template); - if (matcher instanceof ElementNode) { - constantMatchers.add(matcher); - } else { - variableMatchers.add(matcher); - } - } - //first match the Set with constant matchers and then with variable matchers - constantMatchers.addAll(variableMatchers); - return listOfNodesToNode(constantMatchers); - } - - private RootNode createImplicitNode(Map map) { - //collect Entries with constant matcher keys - List constantMatchers = new ArrayList<>(map.size()); - //collect Entries with variable matcher keys - List variableMatchers = new ArrayList<>(); - Matchers last = null; - for (Map.Entry entry : map.entrySet()) { - MapEntryNode mem = new MapEntryNode( - createImplicitNode(entry.getKey()), - createImplicitNode(entry.getValue())); - if (mem.getKey() == entry.getKey()) { - constantMatchers.add(mem); - } else { - variableMatchers.add(mem); - } - } - //first match the Map.Entries with constant matchers and then with variable matchers - constantMatchers.addAll(variableMatchers); - return listOfNodesToNode(constantMatchers); - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - private static RootNode listOfNodesToNode(List nodes) { - //The attribute is matched different if there is List of one ParameterizedNode and when there is one ParameterizedNode -// if (nodes.size() == 1) { -// return nodes.get(0); -// } - return new ListOfNodes((List) nodes); - } - /** * @param element a CtElement * @return {@link RootNode}, which handles matching/generation of an `object` from the source spoon AST. @@ -264,7 +163,7 @@ public RootNode getPatternNode(CtElement element, CtRole... roles) { for (CtRole role : roles) { if (node instanceof ElementNode) { ElementNode elementNode = (ElementNode) node; - node = elementNode.getAttributeSubstititionRequest(role); + node = elementNode.getNodeOfRole(role); if (node == null) { throw new SpoonException("The role " + role + " resolved to null Node"); } @@ -301,7 +200,7 @@ void modifyNodeOfAttributeOfElement(CtElement element, CtRole role, ConflictReso modifyNodeOfElement(element, conflictMode, node -> { if (node instanceof ElementNode) { ElementNode elementNode = (ElementNode) node; - RootNode oldAttrNode = elementNode.getAttributeSubstititionRequest(role); + RootNode oldAttrNode = elementNode.getNodeOfRole(role); RootNode newAttrNode = elementNodeChanger.apply(oldAttrNode); if (newAttrNode == null) { throw new SpoonException("Removing of Node is not supported"); @@ -366,6 +265,10 @@ public Pattern build() { if (built) { throw new SpoonException("The Pattern may be built only once"); } + if (addGeneratedBy) { + //add generated by comments + addGeneratedByComments(); + } built = true; //clean the mapping so it is not possible to further modify built pattern using this builder patternElementToSubstRequests.clear(); @@ -905,4 +808,28 @@ public void forEachNodeOfParameter(ParameterInfo parameter, Consumer c } }); } + /** + * @return true if produced Pattern will append generated by comments + */ + public boolean isAddGeneratedBy() { + return addGeneratedBy; + } + /** + * @param addGeneratedBy true when generated by comments have to be appended to each generated type member + * @return this to support fluent API + */ + public PatternBuilder setAddGeneratedBy(boolean addGeneratedBy) { + this.addGeneratedBy = addGeneratedBy; + return this; + } + private void addGeneratedByComments() { + GeneratedByProvider gbp = new GeneratedByProvider(); +// patternQuery.filterChildren(new TypeFilter<>(CtTypeMember.class)).forEach((CtTypeMember tm) -> { +// String generateByComment = gbp.getGeneratedByComment(tm); +// if (generateByComment != null) { +// RootNode node = getPatternNode(tm); +// gbp.addGeneratedByComment(node, generateByComment); +// } +// }); + } } diff --git a/src/main/java/spoon/pattern/SubstitutionCloner.java b/src/main/java/spoon/pattern/SubstitutionCloner.java index 76b9550b189..6c90f90b0e7 100644 --- a/src/main/java/spoon/pattern/SubstitutionCloner.java +++ b/src/main/java/spoon/pattern/SubstitutionCloner.java @@ -151,43 +151,7 @@ // valueResolver.generateTargets(factory, result, parameters); // } // -// private static String getGeneratedByComment(CtElement ele) { -// SourcePosition pos = ele.getPosition(); -// if (pos != null) { -// CompilationUnit cu = pos.getCompilationUnit(); -// if (cu != null) { -// CtType mainType = cu.getMainType(); -// if (mainType != null) { -// StringBuilder result = new StringBuilder(); -// result.append("Generated by "); -// result.append(mainType.getQualifiedName()); -// appendInnerTypedElements(result, mainType, ele); -// result.append('('); -// result.append(mainType.getSimpleName()); -// result.append(".java:"); -// result.append(pos.getLine()); -// result.append(')'); -// return result.toString(); -// } -// } -// } -// return null; -// } // -// private static void appendInnerTypedElements(StringBuilder result, CtType mainType, CtElement ele) { -// CtTypeMember typeMember = getFirst(ele, CtTypeMember.class); -// if (typeMember != null && typeMember != mainType) { -// if (typeMember.isParentInitialized()) { -// appendInnerTypedElements(result, mainType, typeMember.getParent()); -// } -// if (typeMember instanceof CtType) { -// result.append('$'); -// } else { -// result.append('#'); -// } -// result.append(typeMember.getSimpleName()); -// } -// } // // private static void addGeneratedByComment(CtElement ele, String generatedBy) { // if (generatedBy == null) { @@ -214,18 +178,6 @@ // return c; // } // -// @SuppressWarnings("unchecked") -// private static T getFirst(CtElement ele, Class clazz) { -// if (ele != null) { -// if (clazz.isAssignableFrom(ele.getClass())) { -// return (T) ele; -// } -// if (ele.isParentInitialized()) { -// return getFirst(ele.getParent(), clazz); -// } -// } -// return null; -// } // // /** // * @param element to be cloned element diff --git a/src/main/java/spoon/pattern/TemplateBuilder.java b/src/main/java/spoon/pattern/TemplateBuilder.java index 98e975f36fc..0de3efb6aa7 100644 --- a/src/main/java/spoon/pattern/TemplateBuilder.java +++ b/src/main/java/spoon/pattern/TemplateBuilder.java @@ -105,6 +105,11 @@ public Pattern build() { return patternBuilder.build(); } + public TemplateBuilder setAddGeneratedBy(boolean addGeneratedBy) { + patternBuilder.setAddGeneratedBy(addGeneratedBy); + return this; + } + /** * @return Map of template parameters from `template` */ diff --git a/src/main/java/spoon/pattern/node/ElementNode.java b/src/main/java/spoon/pattern/node/ElementNode.java index ca1f38d1860..45a34a98d4e 100644 --- a/src/main/java/spoon/pattern/node/ElementNode.java +++ b/src/main/java/spoon/pattern/node/ElementNode.java @@ -16,18 +16,22 @@ */ package spoon.pattern.node; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.function.BiConsumer; +import java.util.stream.Collectors; import spoon.Metamodel; import spoon.SpoonException; import spoon.pattern.Generator; import spoon.pattern.ResultHolder; +import spoon.pattern.matcher.Matchers; import spoon.pattern.matcher.TobeMatched; import spoon.pattern.parameter.ParameterInfo; import spoon.pattern.parameter.ParameterValueProvider; @@ -43,8 +47,107 @@ */ public class ElementNode extends AbstractPrimitiveMatcher { + public static ElementNode create(CtElement element, Map patternElementToSubstRequests) { + Metamodel.Type mmConcept = Metamodel.getMetamodelTypeByClass(element.getClass()); + ElementNode elementNode = new ElementNode(mmConcept); + if (patternElementToSubstRequests.put(element, elementNode) != null) { + throw new SpoonException("Each pattern element can have only one implicit Node."); + } + //iterate over all attributes of that element + for (Metamodel.Field mmField : mmConcept.getFields()) { + if (mmField.isDerived()) { + //skip derived fields, they are not relevant for matching or generating + continue; + } + elementNode.setNodeOfRole(mmField.getRole(), create(mmField.getContainerKind(), mmField.getValue(element), patternElementToSubstRequests)); + } + return elementNode; + } + + private static RootNode create(Object object, Map patternElementToSubstRequests) { + if (object instanceof CtElement) { + return create((CtElement) object, patternElementToSubstRequests); + } + return new ConstantNode(object); + } + + private static RootNode create(ContainerKind containerKind, Object templates, Map patternElementToSubstRequests) { + switch (containerKind) { + case LIST: + return create((List) templates, patternElementToSubstRequests); + case SET: + return create((Set) templates, patternElementToSubstRequests); + case MAP: + return create((Map) templates, patternElementToSubstRequests); + case SINGLE: + return create(templates, patternElementToSubstRequests); + } + throw new SpoonException("Unexpected RoleHandler containerKind: " + containerKind); + } + + public static ListOfNodes create(List objects, Map patternElementToSubstRequests) { + if (objects == null) { + objects = Collections.emptyList(); + } + return listOfNodesToNode(objects.stream().map(i -> create(i, patternElementToSubstRequests)).collect(Collectors.toList())); + } + + public static ListOfNodes create(Set templates, Map patternElementToSubstRequests) { + if (templates == null) { + templates = Collections.emptySet(); + } + //collect plain template nodes without any substitution request as List, because Spoon Sets have predictable order. + List constantMatchers = new ArrayList<>(templates.size()); + //collect template nodes with a substitution request + List variableMatchers = new ArrayList<>(); + for (Object template : templates) { + RootNode matcher = create(template, patternElementToSubstRequests); + if (matcher instanceof ElementNode) { + constantMatchers.add(matcher); + } else { + variableMatchers.add(matcher); + } + } + //first match the Set with constant matchers and then with variable matchers + constantMatchers.addAll(variableMatchers); + return listOfNodesToNode(constantMatchers); + } + + public static ListOfNodes create(Map map, Map patternElementToSubstRequests) { + if (map == null) { + map = Collections.emptyMap(); + } + //collect Entries with constant matcher keys + List constantMatchers = new ArrayList<>(map.size()); + //collect Entries with variable matcher keys + List variableMatchers = new ArrayList<>(); + Matchers last = null; + for (Map.Entry entry : map.entrySet()) { + MapEntryNode mem = new MapEntryNode( + create(entry.getKey(), patternElementToSubstRequests), + create(entry.getValue(), patternElementToSubstRequests)); + if (mem.getKey() == entry.getKey()) { + constantMatchers.add(mem); + } else { + variableMatchers.add(mem); + } + } + //first match the Map.Entries with constant matchers and then with variable matchers + constantMatchers.addAll(variableMatchers); + return listOfNodesToNode(constantMatchers); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private static ListOfNodes listOfNodesToNode(List nodes) { + //The attribute is matched different if there is List of one ParameterizedNode and when there is one ParameterizedNode +// if (nodes.size() == 1) { +// return nodes.get(0); +// } + return new ListOfNodes((List) nodes); + } + private Metamodel.Type elementType; - private Map attributeSubstititionRequests = new HashMap<>(); + private Map roleToNode = new HashMap<>(); public ElementNode(Metamodel.Type elementType) { super(); @@ -53,7 +156,7 @@ public ElementNode(Metamodel.Type elementType) { @Override public boolean replaceNode(RootNode oldNode, RootNode newNode) { - for (Map.Entry e : attributeSubstititionRequests.entrySet()) { + for (Map.Entry e : roleToNode.entrySet()) { RootNode node = e.getValue(); if (node == oldNode) { e.setValue(newNode); @@ -66,16 +169,50 @@ public boolean replaceNode(RootNode oldNode, RootNode newNode) { return false; } - public Map getAttributeSubstititionRequests() { - return attributeSubstititionRequests == null ? Collections.emptyMap() : Collections.unmodifiableMap(attributeSubstititionRequests); + public Map getRoleToNode() { + return roleToNode == null ? Collections.emptyMap() : Collections.unmodifiableMap(roleToNode); } - public RootNode getAttributeSubstititionRequest(CtRole attributeRole) { - return attributeSubstititionRequests.get(getFieldOfRole(attributeRole)); + public RootNode getNodeOfRole(CtRole attributeRole) { + return roleToNode.get(getFieldOfRole(attributeRole)); } public RootNode setNodeOfRole(CtRole role, RootNode newAttrNode) { - return attributeSubstititionRequests.put(getFieldOfRole(role), newAttrNode); + return roleToNode.put(getFieldOfRole(role), newAttrNode); + } + + /** + * @param role + * @return a {@link RootNode}, which exists on the `role` or creates implicit container for that role + */ + public RootNode getOrCreateNodeOfRole(CtRole role, Map patternElementToSubstRequests) { + RootNode node = getNodeOfRole(role); + if (node == null) { + Metamodel.Field mmField = elementType.getField(role); + if (mmField == null || mmField.isDerived()) { + throw new SpoonException("The role " + role + " doesn't exist or is derived for " + elementType); + } + node = create(mmField.getContainerKind(), null, patternElementToSubstRequests); + setNodeOfRole(role, node); + } + return node; + } + + /** + * @param role to be returned {@link CtRole} + * @param type required type of returned value + * @return value of {@link ConstantNode} on the `role` attribute of this {@link ElementNode} or null if there is none or has different type + */ + public T getValueOfRole(CtRole role, Class type) { + RootNode node = getNodeOfRole(role); + if (node instanceof ConstantNode) { +// FIX it delivers value of StringNode too ... generated by must be added into produced elements + ConstantNode cn = (ConstantNode) node; + if (type.isInstance(cn.getTemplateNode())) { + return (T) cn.getTemplateNode(); + } + } + return null; } private Metamodel.Field getFieldOfRole(CtRole role) { @@ -91,8 +228,8 @@ private Metamodel.Field getFieldOfRole(CtRole role) { @Override public void forEachParameterInfo(BiConsumer consumer) { - if (attributeSubstititionRequests != null) { - for (RootNode node : attributeSubstititionRequests.values()) { + if (roleToNode != null) { + for (RootNode node : roleToNode.values()) { node.forEachParameterInfo(consumer); } } @@ -108,7 +245,7 @@ public void generateTargets(Generator generator, ResultHolder result, Par } protected void generateSingleNodeAttributes(Generator generator, CtElement clone, ParameterValueProvider parameters) { - for (Map.Entry e : getAttributeSubstititionRequests().entrySet()) { + for (Map.Entry e : getRoleToNode().entrySet()) { Metamodel.Field mmField = e.getKey(); switch (mmField.getContainerKind()) { case SINGLE: @@ -147,7 +284,7 @@ public ParameterValueProvider matchTarget(Object target, ParameterValueProvider //it is spoon element, it matches if to be matched attributes matches //to be matched attributes must be same or substituted //iterate over all attributes of to be matched class - for (Map.Entry e : attributeSubstititionRequests.entrySet()) { + for (Map.Entry e : roleToNode.entrySet()) { parameters = matchesRole(parameters, (CtElement) target, e.getKey(), e.getValue()); if (parameters == null) { return null; @@ -228,4 +365,12 @@ public String toString() { // } // } // } + + public Metamodel.Type getElementType() { + return elementType; + } + + public void setElementType(Metamodel.Type elementType) { + this.elementType = elementType; + } } diff --git a/src/main/java/spoon/template/BlockTemplate.java b/src/main/java/spoon/template/BlockTemplate.java index d622dccb9a0..2af94dec007 100644 --- a/src/main/java/spoon/template/BlockTemplate.java +++ b/src/main/java/spoon/template/BlockTemplate.java @@ -49,7 +49,7 @@ public BlockTemplate() { public CtBlock apply(CtType targetType) { CtClass c = Substitution.getTemplateCtClass(targetType, this); - return TemplateBuilder.createPattern(getBlock(c), this).substituteSingle(targetType, CtBlock.class); + return TemplateBuilder.createPattern(getBlock(c), this).setAddGeneratedBy(isAddGeneratedBy()).substituteSingle(targetType, CtBlock.class); } public Void S() { diff --git a/src/main/java/spoon/template/ExpressionTemplate.java b/src/main/java/spoon/template/ExpressionTemplate.java index bd50a71a045..9d6fed71ea6 100644 --- a/src/main/java/spoon/template/ExpressionTemplate.java +++ b/src/main/java/spoon/template/ExpressionTemplate.java @@ -65,7 +65,7 @@ public ExpressionTemplate() { @SuppressWarnings("unchecked") public CtExpression apply(CtType targetType) { CtClass> c = Substitution.getTemplateCtClass(targetType, this); - CtBlock block = TemplateBuilder.createPattern(getExpressionBlock(c), this).substituteSingle(targetType, CtBlock.class); + CtBlock block = TemplateBuilder.createPattern(getExpressionBlock(c), this).setAddGeneratedBy(isAddGeneratedBy()).substituteSingle(targetType, CtBlock.class); if (block == null || block.getStatements().isEmpty()) { return null; } diff --git a/src/main/java/spoon/template/StatementTemplate.java b/src/main/java/spoon/template/StatementTemplate.java index 2fdd05b4836..09e2383686a 100644 --- a/src/main/java/spoon/template/StatementTemplate.java +++ b/src/main/java/spoon/template/StatementTemplate.java @@ -46,7 +46,9 @@ 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); - List statements = TemplateBuilder.createPattern(patternModel, this).substituteList(c.getFactory(), targetType, CtStatement.class); + List statements = TemplateBuilder.createPattern(patternModel, this) + .setAddGeneratedBy(isAddGeneratedBy()) + .substituteList(c.getFactory(), targetType, CtStatement.class); if (statements.isEmpty()) { return null; } diff --git a/src/main/java/spoon/template/Substitution.java b/src/main/java/spoon/template/Substitution.java index 65d3570bd8d..eb7b84e18ac 100644 --- a/src/main/java/spoon/template/Substitution.java +++ b/src/main/java/spoon/template/Substitution.java @@ -526,7 +526,11 @@ public static E substitute(CtType targetType, Template< if (targetType == null) { throw new RuntimeException("target is null in substitution"); } - return (E) TemplateBuilder.createPattern(code, template).substituteSingle(targetType, CtElement.class); + TemplateBuilder tb = TemplateBuilder.createPattern(code, template); + if (template instanceof AbstractTemplate) { + tb.setAddGeneratedBy(((AbstractTemplate) template).isAddGeneratedBy()); + } + return (E) tb.substituteSingle(targetType, CtElement.class); } /** From cdf3685795059c63535fae8debefaa05329ac105 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Thu, 8 Mar 2018 18:25:03 +0100 Subject: [PATCH 019/131] support of generated by comments --- .../java/spoon/pattern/DefaultGenerator.java | 99 +++++++++++++++ .../spoon/pattern/GeneratedByProvider.java | 120 ------------------ src/main/java/spoon/pattern/Generator.java | 19 ++- src/main/java/spoon/pattern/Pattern.java | 14 +- .../java/spoon/pattern/PatternBuilder.java | 16 +-- .../spoon/pattern/SubstitutionCloner.java | 106 +++++++--------- .../java/spoon/pattern/node/ElementNode.java | 12 +- .../java/spoon/pattern/node/StringNode.java | 13 +- 8 files changed, 194 insertions(+), 205 deletions(-) delete mode 100644 src/main/java/spoon/pattern/GeneratedByProvider.java diff --git a/src/main/java/spoon/pattern/DefaultGenerator.java b/src/main/java/spoon/pattern/DefaultGenerator.java index d8a8dd89b7c..caeb346ad61 100644 --- a/src/main/java/spoon/pattern/DefaultGenerator.java +++ b/src/main/java/spoon/pattern/DefaultGenerator.java @@ -19,6 +19,12 @@ import spoon.pattern.node.RootNode; import spoon.pattern.parameter.ParameterInfo; import spoon.pattern.parameter.ParameterValueProvider; +import spoon.reflect.code.CtComment; +import spoon.reflect.cu.CompilationUnit; +import spoon.reflect.cu.SourcePosition; +import spoon.reflect.declaration.CtElement; +import spoon.reflect.declaration.CtType; +import spoon.reflect.declaration.CtTypeMember; import spoon.reflect.factory.Factory; /** @@ -26,6 +32,7 @@ */ public class DefaultGenerator implements Generator { protected final Factory factory; + private boolean addGeneratedBy = false; public DefaultGenerator(Factory factory) { super(); @@ -47,4 +54,96 @@ public Factory getFactory() { return factory; } + public boolean isAddGeneratedBy() { + return addGeneratedBy; + } + + public DefaultGenerator setAddGeneratedBy(boolean addGeneratedBy) { + this.addGeneratedBy = addGeneratedBy; + return this; + } + + @Override + public void applyGeneratedBy(CtElement generatedElement, CtElement templateElement) { + if (isAddGeneratedBy() && generatedElement instanceof CtTypeMember) { + String genBy = getGeneratedByComment(templateElement); + if (genBy != null) { + addGeneratedByComment(generatedElement, genBy); + } + } + } + private String getGeneratedByComment(CtElement ele) { + SourcePosition pos = ele.getPosition(); + if (pos != null) { + CompilationUnit cu = pos.getCompilationUnit(); + if (cu != null) { + CtType mainType = cu.getMainType(); + if (mainType != null) { + StringBuilder result = new StringBuilder(); + result.append("Generated by "); + result.append(mainType.getQualifiedName()); + appendInnerTypedElements(result, mainType, ele); + result.append('('); + result.append(mainType.getSimpleName()); + result.append(".java:"); + result.append(pos.getLine()); + result.append(')'); + return result.toString(); + } + } + } + return null; + } + private void appendInnerTypedElements(StringBuilder result, CtType mainType, CtElement ele) { + CtTypeMember typeMember = getFirst(ele, CtTypeMember.class); + if (typeMember != null && typeMember != mainType) { + if (typeMember.isParentInitialized()) { + appendInnerTypedElements(result, mainType, typeMember.getParent()); + } + if (typeMember instanceof CtType) { + result.append('$'); + } else { + result.append('#'); + } + result.append(typeMember.getSimpleName()); + } + } + @SuppressWarnings("unchecked") + private T getFirst(CtElement ele, Class clazz) { + if (ele != null) { + if (clazz.isAssignableFrom(ele.getClass())) { + return (T) ele; + } + if (ele.isParentInitialized()) { + return getFirst(ele.getParent(), clazz); + } + } + return null; + } + + private void addGeneratedByComment(CtElement ele, String generatedBy) { + if (generatedBy == null) { + return; + } + String EOL = System.getProperty("line.separator"); + CtComment comment = getJavaDoc(ele); + String content = comment.getContent(); + if (content.trim().length() > 0) { + content += EOL + EOL; + } + content += generatedBy; + comment.setContent(content); + } + + private CtComment getJavaDoc(CtElement ele) { + for (CtComment comment : ele.getComments()) { + if (comment.getCommentType() == CtComment.CommentType.JAVADOC) { + return comment; + } + } + CtComment c = ele.getFactory().Code().createComment("", CtComment.CommentType.JAVADOC); + ele.addComment(c); + return c; + } + } diff --git a/src/main/java/spoon/pattern/GeneratedByProvider.java b/src/main/java/spoon/pattern/GeneratedByProvider.java deleted file mode 100644 index 747d351d5ac..00000000000 --- a/src/main/java/spoon/pattern/GeneratedByProvider.java +++ /dev/null @@ -1,120 +0,0 @@ -/** - * 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.pattern; - -import java.util.HashMap; - -import spoon.Metamodel; -import spoon.pattern.node.ConstantNode; -import spoon.pattern.node.ElementNode; -import spoon.pattern.node.ListOfNodes; -import spoon.pattern.node.RootNode; -import spoon.reflect.code.CtJavaDoc; -import spoon.reflect.code.CtComment.CommentType; -import spoon.reflect.cu.CompilationUnit; -import spoon.reflect.cu.SourcePosition; -import spoon.reflect.declaration.CtElement; -import spoon.reflect.declaration.CtType; -import spoon.reflect.declaration.CtTypeMember; -import spoon.reflect.path.CtRole; - -/** - */ -class GeneratedByProvider { - public String getGeneratedByComment(CtElement ele) { - SourcePosition pos = ele.getPosition(); - if (pos != null) { - CompilationUnit cu = pos.getCompilationUnit(); - if (cu != null) { - CtType mainType = cu.getMainType(); - if (mainType != null) { - StringBuilder result = new StringBuilder(); - result.append("Generated by "); - result.append(mainType.getQualifiedName()); - appendInnerTypedElements(result, mainType, ele); - result.append('('); - result.append(mainType.getSimpleName()); - result.append(".java:"); - result.append(pos.getLine()); - result.append(')'); - return result.toString(); - } - } - } - return null; - } - protected void appendInnerTypedElements(StringBuilder result, CtType mainType, CtElement ele) { - CtTypeMember typeMember = getFirst(ele, CtTypeMember.class); - if (typeMember != null && typeMember != mainType) { - if (typeMember.isParentInitialized()) { - appendInnerTypedElements(result, mainType, typeMember.getParent()); - } - if (typeMember instanceof CtType) { - result.append('$'); - } else { - result.append('#'); - } - result.append(typeMember.getSimpleName()); - } - } - @SuppressWarnings("unchecked") - protected T getFirst(CtElement ele, Class clazz) { - if (ele != null) { - if (clazz.isAssignableFrom(ele.getClass())) { - return (T) ele; - } - if (ele.isParentInitialized()) { - return getFirst(ele.getParent(), clazz); - } - } - return null; - } - - public void addGeneratedByComment(RootNode node, String generatedBy) { - if (node instanceof ElementNode) { - ElementNode elementNode = (ElementNode) node; - ListOfNodes commentNode = (ListOfNodes) elementNode.getOrCreateNodeOfRole(CtRole.COMMENT, new HashMap<>()); - ElementNode javaDocComment = getJavaDocComment(commentNode); - String content = javaDocComment.getValueOfRole(CtRole.COMMENT_CONTENT, String.class); - if (content == null) { - return; - } - String EOL = System.getProperty("line.separator"); - if (content.trim().length() > 0) { - content += EOL + EOL; - } - content += generatedBy; - javaDocComment.setNodeOfRole(CtRole.COMMENT_CONTENT, new ConstantNode(content)); - } - } - - private ElementNode getJavaDocComment(ListOfNodes commentNode) { - for (RootNode node : commentNode.getNodes()) { - if (node instanceof ElementNode) { - ElementNode eleNode = (ElementNode) node; - if (eleNode.getElementType().getModelInterface() == CtJavaDoc.class) { - return eleNode; - } - } - } - ElementNode node = new ElementNode(Metamodel.getMetamodelTypeByClass(CtJavaDoc.class)); - node.setNodeOfRole(CtRole.COMMENT_TYPE, new ConstantNode(CommentType.JAVADOC)); - node.setNodeOfRole(CtRole.COMMENT_CONTENT, new ConstantNode("")); - commentNode.getNodes().add(node); - return node; - } -} diff --git a/src/main/java/spoon/pattern/Generator.java b/src/main/java/spoon/pattern/Generator.java index 853775a66f5..4270b176547 100644 --- a/src/main/java/spoon/pattern/Generator.java +++ b/src/main/java/spoon/pattern/Generator.java @@ -21,6 +21,7 @@ import spoon.pattern.node.RootNode; import spoon.pattern.parameter.ParameterInfo; import spoon.pattern.parameter.ParameterValueProvider; +import spoon.reflect.declaration.CtElement; import spoon.reflect.factory.Factory; /** @@ -31,17 +32,27 @@ public interface Generator { * @return a {@link Factory}, which has to be used to generate instances */ Factory getFactory(); + + /** + * Adds a Generated by comment to the javadoc of generatedElement + * @param generatedElement a newly generated element + * @param templateElement a source template element, which was used to generate `generatedElement` + */ + void applyGeneratedBy(CtElement generatedElement, CtElement templateElement); + /** * Generates zero, one or more target depending on kind of this {@link RootNode}, expected `result` and input `parameters` - * @param factory TODO + * @param node to be generated node + * @param result the holder which receives the generated node + * @param parameters the input parameters */ void generateTargets(RootNode node, ResultHolder result, ParameterValueProvider parameters); /** * Returns zero, one or more values into `result`. The value comes from `parameters` from the location defined by `parameterInfo` - * @param parameterInfo - * @param result - * @param parameters + * @param parameterInfo the {@link ParameterInfo}, which describes exact parameter from `parameters` + * @param result the holder which receives the generated node + * @param parameters the input parameters */ void getValueAs(ParameterInfo parameterInfo, ResultHolder result, ParameterValueProvider parameters); diff --git a/src/main/java/spoon/pattern/Pattern.java b/src/main/java/spoon/pattern/Pattern.java index 5af1133fc95..947afb643bb 100644 --- a/src/main/java/spoon/pattern/Pattern.java +++ b/src/main/java/spoon/pattern/Pattern.java @@ -53,6 +53,7 @@ public class Pattern implements CtConsumableFunction { private ParameterValueProviderFactory parameterValueProviderFactory = UnmodifiableParameterValueProvider.Factory.INSTANCE; private ModelNode modelValueResolver; + private boolean addGeneratedBy = false; Pattern(ModelNode modelValueResolver) { this.modelValueResolver = modelValueResolver; @@ -102,7 +103,7 @@ public T substituteSingle(Factory factory, Class valueT * @return one generated element */ public T substituteSingle(Factory factory, Class valueType, ParameterValueProvider params) { - return new DefaultGenerator(factory).generateTarget(modelValueResolver, params, valueType); + return new DefaultGenerator(factory).setAddGeneratedBy(isAddGeneratedBy()).generateTarget(modelValueResolver, params, valueType); } /** * generates a new AST made by cloning of `patternModel` and by substitution of parameters by values in `params` @@ -122,7 +123,7 @@ public List substituteList(Factory factory, Class va * @return List of generated elements */ public List substituteList(Factory factory, Class valueType, ParameterValueProvider params) { - return new DefaultGenerator(factory).generateTargets(modelValueResolver, params, valueType); + return new DefaultGenerator(factory).setAddGeneratedBy(isAddGeneratedBy()).generateTargets(modelValueResolver, params, valueType); } @@ -268,4 +269,13 @@ private static String getQualifiedName(CtPackage pckg, String simpleName) { } return pckg.getQualifiedName() + CtPackage.PACKAGE_SEPARATOR + simpleName; } + + public boolean isAddGeneratedBy() { + return addGeneratedBy; + } + + public Pattern setAddGeneratedBy(boolean addGeneratedBy) { + this.addGeneratedBy = addGeneratedBy; + return this; + } } diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index 18367a18674..162917b02be 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -265,14 +265,10 @@ public Pattern build() { if (built) { throw new SpoonException("The Pattern may be built only once"); } - if (addGeneratedBy) { - //add generated by comments - addGeneratedByComments(); - } built = true; //clean the mapping so it is not possible to further modify built pattern using this builder patternElementToSubstRequests.clear(); - return new Pattern(new ModelNode(patternNodes.getNodes())); + return new Pattern(new ModelNode(patternNodes.getNodes())).setAddGeneratedBy(isAddGeneratedBy()); } static List bodyToStatements(CtStatement statementOrBlock) { @@ -822,14 +818,4 @@ public PatternBuilder setAddGeneratedBy(boolean addGeneratedBy) { this.addGeneratedBy = addGeneratedBy; return this; } - private void addGeneratedByComments() { - GeneratedByProvider gbp = new GeneratedByProvider(); -// patternQuery.filterChildren(new TypeFilter<>(CtTypeMember.class)).forEach((CtTypeMember tm) -> { -// String generateByComment = gbp.getGeneratedByComment(tm); -// if (generateByComment != null) { -// RootNode node = getPatternNode(tm); -// gbp.addGeneratedByComment(node, generateByComment); -// } -// }); - } } diff --git a/src/main/java/spoon/pattern/SubstitutionCloner.java b/src/main/java/spoon/pattern/SubstitutionCloner.java index 6c90f90b0e7..a2156d2a553 100644 --- a/src/main/java/spoon/pattern/SubstitutionCloner.java +++ b/src/main/java/spoon/pattern/SubstitutionCloner.java @@ -19,13 +19,15 @@ //import spoon.support.visitor.equals.CloneHelper; // ///** -// * Clones provided AST and substitutes required nodes and attributes using defined parameters +// * Clones provided AST and substitutes required nodes and attributes using +// * defined parameters // */ //class SubstitutionCloner extends CloneHelper { // private final SubstitutionRequestProvider modelValueResolver; // private final ParameterValueProvider parameters; // /* -// * Set of parents of substituted elements, which might be potentially simplified (partially evaluated) +// * Set of parents of substituted elements, which might be potentially simplified +// * (partially evaluated) // */ // Set toBeSimplifiedElements = Collections.newSetFromMap(new IdentityHashMap<>()); // @@ -36,43 +38,46 @@ // } // // /** -// * Handle substitution of single value element. -// * Zero or one element can be result of substitution of `element` +// * Handle substitution of single value element. Zero or one element can be +// * result of substitution of `element` // */ // @Override // public T clone(T origin) { // if (origin == null) { // return null; // } -// //TODO refactor clone process by way it directly delivers correct CtRole. Next line has some performance impact +// // TODO refactor clone process by way it directly delivers correct CtRole. Next +// // line has some performance impact // ResultHolder.Single result = new ResultHolder.Single<>(getExpectedClassInParent(origin)); // substituteOrClone(origin, result); // return result.getResult(); // } // // /** -// * Handle substitution of element in a collection. -// * Zero, one or more elements can be result of substitution of `element` +// * Handle substitution of element in a collection. Zero, one or more elements +// * can be result of substitution of `element` // */ // @Override // protected void addClone(Collection targetCollection, T origin) { -// //TODO refactor clone process by way it directly delivers correct CtRole. Next line has some performance impact +// // TODO refactor clone process by way it directly delivers correct CtRole. Next +// // line has some performance impact // ResultHolder.Multiple result = new ResultHolder.Multiple<>(getExpectedClassInParent(origin)); // substituteOrClone(origin, result); // targetCollection.addAll(result.getResult()); // } // // /** -// * Handle substitution of element in a Map. -// * Zero or one element can be result of substitution of `element` +// * Handle substitution of element in a Map. Zero or one element can be result of +// * substitution of `element` // */ // @Override // protected void addClone(Map targetMap, String key, T originValue) { -// //TODO refactor clone process by way it directly delivers correct CtRole. Next line has some performance impact +// // TODO refactor clone process by way it directly delivers correct CtRole. Next +// // line has some performance impact // ResultHolder.Single result = new ResultHolder.Single<>(getExpectedClassInParent(originValue)); // substituteOrClone(originValue, result); // if (result.getResult() == null) { -// //the pattern removes this element. Do not add it into target map +// // the pattern removes this element. Do not add it into target map // return; // } // targetMap.put(key, result.getResult()); @@ -90,11 +95,11 @@ // @SuppressWarnings("unchecked") // protected void substituteOrClone(T origin, ResultHolder result) { // String generatedBy = origin instanceof CtTypeMember ? getGeneratedByComment(origin) : null; -// //substitute the origin element and write result into context +// // substitute the origin element and write result into context // substituteOrClone2(origin, result); // // if (generatedBy != null) { -// //add generated by comment +// // add generated by comment // result.mapEachResult(element -> { // if (element instanceof CtTypeMember) { // addGeneratedByComment(element, generatedBy); @@ -103,8 +108,9 @@ // }); // } // if (toBeSimplifiedElements.remove(origin)) { -// //simplify this element, it contains a substituted element -// //TODO it would be nice if CloneHelper would directly set the required class, but it would need more changes... +// // simplify this element, it contains a substituted element +// // TODO it would be nice if CloneHelper would directly set the required class, +// // but it would need more changes... // if (origin.isParentInitialized()) { // RoleHandler rh = RoleHandlerHelper.getRoleHandlerWrtParent(origin); // if (rh != null) { @@ -122,12 +128,14 @@ // return (T) code; // } // /* -// * else the simplified code is not matching with required type. -// * For example statement String.class.getName() was converted to expression "java.lang.String" +// * else the simplified code is not matching with required type. For example +// * statement String.class.getName() was converted to expression +// * "java.lang.String" // */ // } catch (SpoonClassNotFoundException e) { -// //ignore it. Do not simplify this element -// origin.getFactory().getEnvironment().debugMessage("Partial evaluation was skipped because of: " + e.getMessage()); +// // ignore it. Do not simplify this element +// origin.getFactory().getEnvironment() +// .debugMessage("Partial evaluation was skipped because of: " + e.getMessage()); // } // } // return element; @@ -136,51 +144,27 @@ // } // // /** -// * Clones or substitutes origin element using {@link Node} of the role of this attribute -// * for `origin` element, then element is substituted -// * @param origin to be cloned or substituted element -// * @param result holder for result +// * Clones or substitutes origin element using {@link Node} of the role of this +// * attribute for `origin` element, then element is substituted +// * +// * @param origin +// * to be cloned or substituted element +// * @param result +// * holder for result // */ // private void substituteOrClone2(T origin, ResultHolder result) { // Node valueResolver = modelValueResolver.getTemplateValueResolver(origin); // if ((valueResolver instanceof ConstantNode) == false) { -// //it is not just copying of node or substitution of node attributes -// //the node is replaced by different 0, 1 or more nodes +// // it is not just copying of node or substitution of node attributes +// // the node is replaced by different 0, 1 or more nodes // planSimplification(origin.getParent()); // } // valueResolver.generateTargets(factory, result, parameters); // } // -// -// -// private static void addGeneratedByComment(CtElement ele, String generatedBy) { -// if (generatedBy == null) { -// return; -// } -// String EOL = System.getProperty("line.separator"); -// CtComment comment = getJavaDoc(ele); -// String content = comment.getContent(); -// if (content.trim().length() > 0) { -// content += EOL + EOL; -// } -// content += generatedBy; -// comment.setContent(content); -// } -// -// private static CtComment getJavaDoc(CtElement ele) { -// for (CtComment comment : ele.getComments()) { -// if (comment.getCommentType() == CtComment.CommentType.JAVADOC) { -// return comment; -// } -// } -// CtComment c = ele.getFactory().Code().createComment("", CtComment.CommentType.JAVADOC); -// ele.addComment(c); -// return c; -// } -// -// // /** -// * @param element to be cloned element +// * @param element +// * to be cloned element // * @return a clone which is not substituted // */ // public T originClone(T element) { @@ -190,8 +174,9 @@ // if (pos != null) { // CompilationUnit cu = pos.getCompilationUnit(); // if (cu != null && cu.getImports().size() > 0) { -// //avoid usage of invalid template imports in generated code -// //TODO - this is just dirty workaround, which removes imports for templates too - but it should be no big problem ... +// // avoid usage of invalid template imports in generated code +// // TODO - this is just dirty workaround, which removes imports for templates too +// // - but it should be no big problem ... // cu.setImports(Collections.emptySet()); // } // } @@ -200,8 +185,11 @@ // } // // /** -// * plans simplification of clone of `element` after cloning of it's children is finished -// * @param element origin (not cloned) element, whose clone has to be simplified +// * plans simplification of clone of `element` after cloning of it's children is +// * finished +// * +// * @param element +// * origin (not cloned) element, whose clone has to be simplified // */ // private void planSimplification(CtElement element) { // toBeSimplifiedElements.add(element); diff --git a/src/main/java/spoon/pattern/node/ElementNode.java b/src/main/java/spoon/pattern/node/ElementNode.java index 45a34a98d4e..333111e0710 100644 --- a/src/main/java/spoon/pattern/node/ElementNode.java +++ b/src/main/java/spoon/pattern/node/ElementNode.java @@ -49,7 +49,7 @@ public class ElementNode extends AbstractPrimitiveMatcher { public static ElementNode create(CtElement element, Map patternElementToSubstRequests) { Metamodel.Type mmConcept = Metamodel.getMetamodelTypeByClass(element.getClass()); - ElementNode elementNode = new ElementNode(mmConcept); + ElementNode elementNode = new ElementNode(mmConcept, element); if (patternElementToSubstRequests.put(element, elementNode) != null) { throw new SpoonException("Each pattern element can have only one implicit Node."); } @@ -146,12 +146,19 @@ private static ListOfNodes listOfNodesToNode(List nodes) { return new ListOfNodes((List) nodes); } + private CtElement templateElement; private Metamodel.Type elementType; private Map roleToNode = new HashMap<>(); - public ElementNode(Metamodel.Type elementType) { + /** + * @param elementType The type of Spoon node which has to be generated/matched by this {@link ElementNode} + * @param templateElement - optional ref to template element which was used to created this {@link ElementNode}. + * It is used e.g. to generate generatedBy comment + */ + public ElementNode(Metamodel.Type elementType, CtElement templateElement) { super(); this.elementType = elementType; + this.templateElement = templateElement; } @Override @@ -241,6 +248,7 @@ public void generateTargets(Generator generator, ResultHolder result, Par //TODO implement create on Metamodel.Type CtElement clone = generator.getFactory().Core().create(elementType.getModelInterface()); generateSingleNodeAttributes(generator, clone, parameters); + generator.applyGeneratedBy(clone, templateElement); result.addResult((U) clone); } diff --git a/src/main/java/spoon/pattern/node/StringNode.java b/src/main/java/spoon/pattern/node/StringNode.java index dd8a9a481ec..e2dde8d6964 100644 --- a/src/main/java/spoon/pattern/node/StringNode.java +++ b/src/main/java/spoon/pattern/node/StringNode.java @@ -37,7 +37,8 @@ * Delivers single String value, which is created by replacing string markers in constant String template * by String value of appropriate parameter. */ -public class StringNode extends ConstantNode { +public class StringNode extends AbstractPrimitiveMatcher { + private final String stringValueWithMarkers; /* * Use LinkedHashMap to assure defined replacement order */ @@ -46,11 +47,11 @@ public class StringNode extends ConstantNode { private Pattern regExpPattern; public StringNode(String stringValueWithMarkers) { - super(stringValueWithMarkers); + this.stringValueWithMarkers = stringValueWithMarkers; } private String getStringValueWithMarkers() { - return getTemplateNode(); + return stringValueWithMarkers; } @SuppressWarnings("unchecked") @@ -218,10 +219,16 @@ private String substituteSubstring(String str, String tobeReplacedSubstring, Str private String escapeRegExp(String str) { return "\\Q" + str + "\\E"; } + private String escapeRegReplace(String str) { return str.replaceAll("\\$", "\\\\\\$"); } + @Override + public boolean replaceNode(RootNode oldNode, RootNode newNode) { + return false; + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); From 34a0cfd7006fcb30fcebce6d086355aef51d64f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Thu, 8 Mar 2018 20:29:38 +0100 Subject: [PATCH 020/131] simplify generated code --- .../java/spoon/pattern/DefaultGenerator.java | 26 ++++++++++++ .../java/spoon/pattern/ParametersBuilder.java | 7 ++++ .../java/spoon/pattern/PatternBuilder.java | 40 ++++++++++++++++--- .../java/spoon/pattern/TemplateBuilder.java | 2 + .../java/spoon/pattern/node/AbstractNode.java | 9 +++++ .../java/spoon/pattern/node/RootNode.java | 14 +++++-- 6 files changed, 89 insertions(+), 9 deletions(-) diff --git a/src/main/java/spoon/pattern/DefaultGenerator.java b/src/main/java/spoon/pattern/DefaultGenerator.java index caeb346ad61..3982f692d8a 100644 --- a/src/main/java/spoon/pattern/DefaultGenerator.java +++ b/src/main/java/spoon/pattern/DefaultGenerator.java @@ -19,6 +19,7 @@ import spoon.pattern.node.RootNode; import spoon.pattern.parameter.ParameterInfo; import spoon.pattern.parameter.ParameterValueProvider; +import spoon.reflect.code.CtCodeElement; import spoon.reflect.code.CtComment; import spoon.reflect.cu.CompilationUnit; import spoon.reflect.cu.SourcePosition; @@ -26,6 +27,7 @@ import spoon.reflect.declaration.CtType; import spoon.reflect.declaration.CtTypeMember; import spoon.reflect.factory.Factory; +import spoon.support.SpoonClassNotFoundException; /** * Drives generation process @@ -42,6 +44,30 @@ public DefaultGenerator(Factory factory) { @Override public void generateTargets(RootNode node, ResultHolder result, ParameterValueProvider parameters) { node.generateTargets(this, result, parameters); + if (node.isSimplifyGenerated()) { + // simplify this element, it contains a substituted element + result.mapEachResult(element -> { + if (element instanceof CtCodeElement) { + CtCodeElement code = (CtCodeElement) element; + try { + code = code.partiallyEvaluate(); + if (result.getRequiredClass().isInstance(code)) { + return (T) code; + } + /* + * else the simplified code is not matching with required type. For example + * statement String.class.getName() was converted to expression + * "java.lang.String" + */ + } catch (SpoonClassNotFoundException e) { + // ignore it. Do not simplify this element + getFactory().getEnvironment() + .debugMessage("Partial evaluation was skipped because of: " + e.getMessage()); + } + } + return element; + }); + } } @Override diff --git a/src/main/java/spoon/pattern/ParametersBuilder.java b/src/main/java/spoon/pattern/ParametersBuilder.java index f724955db26..c0d56a39ae9 100644 --- a/src/main/java/spoon/pattern/ParametersBuilder.java +++ b/src/main/java/spoon/pattern/ParametersBuilder.java @@ -531,7 +531,14 @@ class Result { void addSubstitutionRequest(ParameterInfo parameter, CtElement element) { ParameterElementPair pep = getSubstitutedNodeOfElement(parameter, element); patternBuilder.setNodeOfElement(pep.element, new ParameterNode(pep.parameter), conflictResolutionMode); + if (patternBuilder.isAutoSimplifySubstitutions() && pep.element.isParentInitialized()) { + RootNode node = patternBuilder.getOptionalPatternNode(pep.element.getParent()); + if (node != null) { + node.setSimplifyGenerated(true); + } + } } + /** * Adds request to substitute value of `attributeRole` of `element`, by the value of this {@link ModelNode} parameter {@link ParameterInfo} value * @param element whose attribute of {@link CtRole} `attributeRole` have to be replaced diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index 162917b02be..2d041049406 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -31,12 +31,10 @@ import java.util.function.Function; import spoon.SpoonException; -import spoon.pattern.ParametersBuilder.ParameterElementPair; import spoon.pattern.node.ElementNode; import spoon.pattern.node.ListOfNodes; import spoon.pattern.node.ModelNode; import spoon.pattern.node.RootNode; -import spoon.pattern.node.ParameterNode; import spoon.pattern.parameter.AbstractParameterInfo; import spoon.pattern.parameter.ParameterInfo; import spoon.reflect.code.CtBlock; @@ -115,6 +113,7 @@ public static PatternBuilder create(CtType templateType, Consumer templateTypeRef, List templ * @return {@link RootNode}, which handles matching/generation of an `object` from the source spoon AST. * or null, if there is none */ + public RootNode getOptionalPatternNode(CtElement element, CtRole... roles) { + return getPatternNode(true, element, roles); + } public RootNode getPatternNode(CtElement element, CtRole... roles) { + return getPatternNode(false, element, roles); + } + private RootNode getPatternNode(boolean optional, CtElement element, CtRole... roles) { RootNode node = patternElementToSubstRequests.get(element); for (CtRole role : roles) { if (node instanceof ElementNode) { ElementNode elementNode = (ElementNode) node; node = elementNode.getNodeOfRole(role); if (node == null) { + if (optional) { + return null; + } throw new SpoonException("The role " + role + " resolved to null Node"); } } else { + if (optional) { + return null; + } throw new SpoonException("The role " + role + " can't be resolved on Node of class " + node.getClass()); } } if (node == null) { + if (optional) { + return null; + } throw new SpoonException("There is no Node for element"); } return node; @@ -296,6 +310,8 @@ public PatternBuilder setDefaultValueConvertor(ValueConvertor valueConvertor) { public PatternBuilder configureAutomaticParameters() { configureParameters(pb -> { + //add this substitution request only if there isn't another one yet + pb.setConflictResolutionMode(ConflictResolutionMode.KEEP_OLD_NODE); /* * detect other parameters. * contract: All variable references, which are declared outside of template are automatically considered as template parameters @@ -306,9 +322,7 @@ public PatternBuilder configureAutomaticParameters() { if (var == null || isInModel(var) == false) { //the varRef has declaration out of the scope of the template. It must be a template parameter. ParameterInfo parameter = pb.parameter(varRef.getSimpleName()).getCurrentParameter(); - ParameterElementPair pep = pb.getSubstitutedNodeOfElement(parameter, varRef); - //add this substitution request only if there is no one yet - setNodeOfElement(pep.element, new ParameterNode(pep.parameter), ConflictResolutionMode.KEEP_OLD_NODE); + pb.addSubstitutionRequest(parameter, varRef); } }); }); @@ -818,4 +832,20 @@ public PatternBuilder setAddGeneratedBy(boolean addGeneratedBy) { this.addGeneratedBy = addGeneratedBy; return this; } + + /** + * @return true if generated result has to be evaluated to apply simplifications. + */ + public boolean isAutoSimplifySubstitutions() { + return autoSimplifySubstitutions; + } + /** + * @param autoSimplifySubstitutions true if generated result of each substituted has to be evaluated to apply simplifications. + * The rule is applied only to substitutions defined after this call + * @return this to support fluent API + */ + public PatternBuilder setAutoSimplifySubstitutions(boolean autoSimplifySubstitutions) { + this.autoSimplifySubstitutions = autoSimplifySubstitutions; + return this; + } } diff --git a/src/main/java/spoon/pattern/TemplateBuilder.java b/src/main/java/spoon/pattern/TemplateBuilder.java index 0de3efb6aa7..a455d40107f 100644 --- a/src/main/java/spoon/pattern/TemplateBuilder.java +++ b/src/main/java/spoon/pattern/TemplateBuilder.java @@ -87,6 +87,8 @@ public static TemplateBuilder createPattern(CtElement templateRoot, CtClass t pb = PatternBuilder.create(templateType, model -> model.setTemplateModel(templateRoot)); } Map templateParameters = template == null ? null : Parameters.getTemplateParametersAsMap(f, null, template); + //legacy templates always automatically simplifies generated code + pb.setAutoSimplifySubstitutions(true); pb.configureTemplateParameters(templateParameters); return new TemplateBuilder(templateType, pb, template); } diff --git a/src/main/java/spoon/pattern/node/AbstractNode.java b/src/main/java/spoon/pattern/node/AbstractNode.java index d1f213d6f55..43bfbc8b8f2 100644 --- a/src/main/java/spoon/pattern/node/AbstractNode.java +++ b/src/main/java/spoon/pattern/node/AbstractNode.java @@ -26,8 +26,17 @@ * */ public abstract class AbstractNode implements RootNode { + private boolean simplifyGenerated = false; @Override public String toString() { return new PatternPrinter().printNode(this); } + @Override + public boolean isSimplifyGenerated() { + return simplifyGenerated; + } + @Override + public void setSimplifyGenerated(boolean simplifyGenerated) { + this.simplifyGenerated = simplifyGenerated; + } } diff --git a/src/main/java/spoon/pattern/node/RootNode.java b/src/main/java/spoon/pattern/node/RootNode.java index 01444eae4ad..fd0abff490a 100644 --- a/src/main/java/spoon/pattern/node/RootNode.java +++ b/src/main/java/spoon/pattern/node/RootNode.java @@ -16,18 +16,14 @@ */ package spoon.pattern.node; -import java.util.List; import java.util.function.BiConsumer; import spoon.pattern.Generator; import spoon.pattern.ResultHolder; -import spoon.pattern.ResultHolder.Multiple; -import spoon.pattern.ResultHolder.Single; import spoon.pattern.matcher.Matchers; import spoon.pattern.matcher.TobeMatched; import spoon.pattern.parameter.ParameterInfo; import spoon.pattern.parameter.ParameterValueProvider; -import spoon.reflect.factory.Factory; /** * Represents a parameterized Pattern ValueResolver, which can be used @@ -51,6 +47,16 @@ public interface RootNode extends Matchers { */ void generateTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters); + /** + * @return true if generated result has to be evaluated to apply simplifications. + * e.g. ("a" + "b") id simplified to "ab" + */ + boolean isSimplifyGenerated(); + /** + * @param simplifyGenerated true if generated result of this {@link RootNode} has to be evaluated to apply simplifications. + */ + void setSimplifyGenerated(boolean simplifyGenerated); + /** * @param targets to be matched target nodes and input parameters * @param nextMatchers Chain of matchers which has to be processed after this {@link RootNode} From 4cf1675b26fb7dd92d1e82d801e1e4ddc084a410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Thu, 8 Mar 2018 21:40:16 +0100 Subject: [PATCH 021/131] Pattern printing --- .../java/spoon/pattern/PatternPrinter.java | 113 +++++++++++++++++- src/main/java/spoon/pattern/ResultHolder.java | 15 +++ .../java/spoon/pattern/node/ModelNode.java | 49 -------- .../test/template/TemplateMatcherTest.java | 1 - 4 files changed, 125 insertions(+), 53 deletions(-) diff --git a/src/main/java/spoon/pattern/PatternPrinter.java b/src/main/java/spoon/pattern/PatternPrinter.java index 4d2f9a15710..69a3c67b7b4 100644 --- a/src/main/java/spoon/pattern/PatternPrinter.java +++ b/src/main/java/spoon/pattern/PatternPrinter.java @@ -16,18 +16,25 @@ */ package spoon.pattern; +import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import spoon.Metamodel; import spoon.SpoonException; import spoon.pattern.node.LiveNode; import spoon.pattern.node.RootNode; import spoon.pattern.parameter.ParameterInfo; import spoon.pattern.parameter.ParameterValueProvider; +import spoon.reflect.code.CtComment.CommentType; import spoon.reflect.code.CtInvocation; import spoon.reflect.code.CtLocalVariable; import spoon.reflect.declaration.CtElement; import spoon.reflect.factory.Factory; import spoon.reflect.factory.FactoryImpl; +import spoon.reflect.path.CtRole; +import spoon.reflect.visitor.PrinterHelper; import spoon.support.DefaultCoreFactory; import spoon.support.StandardEnvironment; @@ -36,6 +43,11 @@ public class PatternPrinter extends DefaultGenerator { private static final Factory DEFAULT_FACTORY = new FactoryImpl(new DefaultCoreFactory(), new StandardEnvironment()); + static { + DEFAULT_FACTORY.getEnvironment().setCommentEnabled(true); + } + + private List params = new ArrayList<>(); public PatternPrinter() { super(DEFAULT_FACTORY); @@ -52,17 +64,53 @@ public String printNode(RootNode node) { @Override public void generateTargets(RootNode node, ResultHolder result, ParameterValueProvider parameters) { + int firstResultIdx = result.getResults().size(); if (node instanceof LiveNode) { //this is a Live node. Do not generated nodes normally, but generate origin live statements ((LiveNode) node).generateLiveTargets(this, result, parameters); - return; + } else { + super.generateTargets(node, result, parameters); + } + T firstResult = getFirstResult(result, firstResultIdx); + if (firstResult instanceof CtElement) { + addParameterCommentTo((CtElement) firstResult, null); + } + } + + private boolean isCommentVisible(Object obj) { + if (obj instanceof CtElement) { + Metamodel.Type mmType = Metamodel.getMetamodelTypeByClass((Class) obj.getClass()); + Metamodel.Field mmCommentField = mmType.getField(CtRole.COMMENT); + if (mmCommentField != null && mmCommentField.isDerived() == false) { + return true; + } + } + return false; + } + + private T getFirstResult(ResultHolder result, int firstResultIdx) { + List results = result.getResults(); + if (firstResultIdx < results.size()) { + return results.get(firstResultIdx); } - super.generateTargets(node, result, parameters); + return null; } @Override public void getValueAs(ParameterInfo parameterInfo, ResultHolder result, ParameterValueProvider parameters) { - result.addResult(generatePatternParameterElement(parameterInfo, result.getRequiredClass())); + CtElement ele = (CtElement) generatePatternParameterElement(parameterInfo, result.getRequiredClass()); + addParameterCommentTo(ele, parameterInfo); + result.addResult((T) ele); + } + + private void addParameterCommentTo(CtElement ele, ParameterInfo parameterInfo) { + if (parameterInfo != null) { + params.add(new ParamOnElement((CtElement) ele, parameterInfo)); + } + if (isCommentVisible(ele) && params.size() > 0) { + ele.addComment(ele.getFactory().Code().createComment(getSubstitutionRequestsDescription(ele, params), CommentType.BLOCK)); + params.clear(); + } } /** @@ -84,4 +132,63 @@ private T generatePatternParameterElement(ParameterInfo parameterInfo, Class } throw new SpoonException("Pattern Parameter is on Unsupported place"); } + + private static class ParamOnElement { + final CtElement sourceElement; + final ParameterInfo param; + ParamOnElement(CtElement sourceElement, ParameterInfo param) { + this.sourceElement = sourceElement; + this.param = param; + } + + @Override + public String toString() { + return sourceElement.getClass().getName() + ": ${" + param.getName() + "}"; + } + } + private String getSubstitutionRequestsDescription(CtElement ele, List requestsOnPos) { + //sort requestsOnPos by their path + Map reqByPath = new TreeMap<>(); + StringBuilder sb = new StringBuilder(); + for (ParamOnElement reqPos : requestsOnPos) { + sb.setLength(0); + appendPathIn(sb, reqPos.sourceElement, ele); + String path = sb.toString(); + reqByPath.put(path, reqPos); + } + + PrinterHelper printer = new PrinterHelper(getFactory().getEnvironment()); + //all comments in Spoon are using \n as separator + printer.setLineSeparator("\n"); + printer.write(getElementTypeName(ele)).incTab(); + for (Map.Entry e : reqByPath.entrySet()) { + printer.writeln(); + printer.write(e.getKey()).write('/'); + printer.write(" <= ${").write(e.getValue().param.getName() + "}"); + } + return printer.toString(); + } + + private boolean appendPathIn(StringBuilder sb, CtElement element, CtElement parent) { + if (element != parent && element != null) { + CtRole roleInParent = element.getRoleInParent(); + if (roleInParent == null) { + return false; + } + if (appendPathIn(sb, element.getParent(), parent)) { + sb.append("/").append(getElementTypeName(element.getParent())); + } + sb.append(".").append(roleInParent.getCamelCaseName()); + return true; + } + return false; + }; + + static String getElementTypeName(CtElement element) { + String name = element.getClass().getSimpleName(); + if (name.endsWith("Impl")) { + return name.substring(0, name.length() - 4); + } + return name; + } } diff --git a/src/main/java/spoon/pattern/ResultHolder.java b/src/main/java/spoon/pattern/ResultHolder.java index f30c086494e..15ddbba60f8 100644 --- a/src/main/java/spoon/pattern/ResultHolder.java +++ b/src/main/java/spoon/pattern/ResultHolder.java @@ -17,6 +17,7 @@ package spoon.pattern; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.ListIterator; import java.util.function.Function; @@ -66,6 +67,10 @@ public ResultHolder setRequiredClass(Class requiredClass) { * @param consumer */ public abstract void mapEachResult(Function consumer); + /** + * @return List of actually stored results + */ + public abstract List getResults(); /** * Container of single value of required type @@ -102,6 +107,11 @@ public void mapEachResult(Function consumer) { result = consumer.apply(result); }; + @Override + public List getResults() { + return result == null ? Collections.emptyList() : Collections.singletonList(result); + } + public ResultHolder.Single setRequiredClass(Class requiredClass) { return (ResultHolder.Single) super.setRequiredClass(requiredClass); } @@ -133,6 +143,11 @@ public List getResult() { return result; } + @Override + public List getResults() { + return result; + } + @Override public void mapEachResult(Function consumer) { for (ListIterator iter = result.listIterator(); iter.hasNext();) { diff --git a/src/main/java/spoon/pattern/node/ModelNode.java b/src/main/java/spoon/pattern/node/ModelNode.java index 904438852d3..2316d1d6c2a 100644 --- a/src/main/java/spoon/pattern/node/ModelNode.java +++ b/src/main/java/spoon/pattern/node/ModelNode.java @@ -113,54 +113,5 @@ private static int getSourceStart(CtElement ele) { } } - private String getSubstitutionRequestsDescription(CtElement ele, int sourceStart, List requestsOnPos) { - //sort requestsOnPos by their path - Map reqByPath = new TreeMap<>(); - StringBuilder sb = new StringBuilder(); - for (SubstReqOnPosition reqPos : requestsOnPos) { - sb.setLength(0); - appendPathIn(sb, reqPos.sourceElement, ele); - String path = sb.toString(); - reqByPath.put(path, reqPos); - } - - PrinterHelper printer = new PrinterHelper(getFactory().getEnvironment()); - //all comments in Spoon are using \n as separator - printer.setLineSeparator("\n"); - printer.write(getElementTypeName(ele)).incTab(); - for (Map.Entry e : reqByPath.entrySet()) { - printer.writeln(); - boolean isLate = e.getValue().sourceStart != sourceStart; - if (isLate) { - printer.write("!").write(String.valueOf(e.getValue().sourceStart)).write("!=").write(String.valueOf(sourceStart)).write("!"); - } - printer.write(e.getKey()).write('/'); - printer.write(" <= ").write(e.getValue().valueResolver.toString()); - } - return printer.toString(); - } - - private boolean appendPathIn(StringBuilder sb, CtElement element, CtElement parent) { - if (element != parent && element != null) { - CtRole roleInParent = element.getRoleInParent(); - if (roleInParent == null) { - return false; - } - if (appendPathIn(sb, element.getParent(), parent)) { - sb.append("/").append(getElementTypeName(element.getParent())); - } - sb.append(".").append(roleInParent.getCamelCaseName()); - return true; - } - return false; - }; - - static String getElementTypeName(CtElement element) { - String name = element.getClass().getSimpleName(); - if (name.endsWith("Impl")) { - return name.substring(0, name.length() - 4); - } - return name; - } */ } diff --git a/src/test/java/spoon/test/template/TemplateMatcherTest.java b/src/test/java/spoon/test/template/TemplateMatcherTest.java index 4bb25bd8924..505c55cde1d 100644 --- a/src/test/java/spoon/test/template/TemplateMatcherTest.java +++ b/src/test/java/spoon/test/template/TemplateMatcherTest.java @@ -780,7 +780,6 @@ private Map getMap(Match match, String name) { } @Test - @Ignore //TODO allow partial matching of children by template and remaining elements by ParameterValueProvider public void testMatchOfMapAttributeAndOtherAnnotations() throws Exception { //contract: match attribute of type Map - annotations CtType ctClass = ModelUtils.buildClass(MatchMap.class); From 18e78000ba39624f125ac056ba8c2dc0304470ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Fri, 9 Mar 2018 22:23:51 +0100 Subject: [PATCH 022/131] Support specific matcher + generic matcher for other container items --- .../spoon/pattern/ConflictResolutionMode.java | 8 +- .../java/spoon/pattern/PatternBuilder.java | 29 +++++-- .../java/spoon/pattern/PatternPrinter.java | 87 ++++++++++++++++--- .../test/template/TemplateMatcherTest.java | 42 ++++++--- .../template/testclasses/match/MatchMap.java | 7 +- 5 files changed, 135 insertions(+), 38 deletions(-) diff --git a/src/main/java/spoon/pattern/ConflictResolutionMode.java b/src/main/java/spoon/pattern/ConflictResolutionMode.java index 6dfc77c2989..4a2baa758ea 100644 --- a/src/main/java/spoon/pattern/ConflictResolutionMode.java +++ b/src/main/java/spoon/pattern/ConflictResolutionMode.java @@ -32,7 +32,11 @@ public enum ConflictResolutionMode { */ USE_NEW_NODE, /** - * keep old {@link Node} and ignore try to add any new {@link Node} + * keep old {@link Node} and ignore requests to add new {@link Node} */ - KEEP_OLD_NODE + KEEP_OLD_NODE, + /** + * add new {@link RootNode} after existing nodes + */ + APPEND } diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index 2d041049406..2906d6b992b 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -18,6 +18,7 @@ import java.lang.annotation.Annotation; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -197,8 +198,8 @@ void modifyNodeOfElement(CtElement element, ConflictResolutionMode conflictMode, if (newNode == null) { throw new SpoonException("Removing of Node is not supported"); } - handleConflict(conflictMode, oldNode, newNode, () -> { - if (patternNodes.replaceNode(oldNode, newNode) == false) { + handleConflict(conflictMode, oldNode, newNode, (tobeUsedNode) -> { + if (patternNodes.replaceNode(oldNode, tobeUsedNode) == false) { if (conflictMode == ConflictResolutionMode.KEEP_OLD_NODE) { //The parent of oldNode was already replaced. OK - Keep that parent old node return; @@ -206,7 +207,7 @@ void modifyNodeOfElement(CtElement element, ConflictResolutionMode conflictMode, throw new SpoonException("Old node was not found"); } //update element to node mapping - patternElementToSubstRequests.put(element, newNode); + patternElementToSubstRequests.put(element, tobeUsedNode); }); } @@ -219,8 +220,8 @@ void modifyNodeOfAttributeOfElement(CtElement element, CtRole role, ConflictReso if (newAttrNode == null) { throw new SpoonException("Removing of Node is not supported"); } - handleConflict(conflictMode, oldAttrNode, newAttrNode, () -> { - elementNode.setNodeOfRole(role, newAttrNode); + handleConflict(conflictMode, oldAttrNode, newAttrNode, (tobeUsedNode) -> { + elementNode.setNodeOfRole(role, tobeUsedNode); }); return node; } @@ -231,8 +232,22 @@ void modifyNodeOfAttributeOfElement(CtElement element, CtRole role, ConflictReso }); } - private void handleConflict(ConflictResolutionMode conflictMode, RootNode oldNode, RootNode newNode, Runnable applyNewNode) { + private void handleConflict(ConflictResolutionMode conflictMode, RootNode oldNode, RootNode newNode, Consumer applyNewNode) { if (oldNode != newNode) { + if (conflictMode == ConflictResolutionMode.APPEND) { + if (oldNode instanceof ListOfNodes == false) { + oldNode = new ListOfNodes(new ArrayList<>(Arrays.asList(oldNode))); + } + if (newNode instanceof ListOfNodes) { + ((ListOfNodes) oldNode).getNodes().addAll(((ListOfNodes) newNode).getNodes()); + } else { + ((ListOfNodes) oldNode).getNodes().add(newNode); + } + explicitNodes.add(oldNode); + explicitNodes.add(newNode); + applyNewNode.accept(oldNode); + return; + } if (explicitNodes.contains(oldNode)) { //the oldNode was explicitly added before if (conflictMode == ConflictResolutionMode.FAIL) { @@ -244,7 +259,7 @@ private void handleConflict(ConflictResolutionMode conflictMode, RootNode oldNod } explicitNodes.remove(oldNode); explicitNodes.add(newNode); - applyNewNode.run(); + applyNewNode.accept(newNode); } } diff --git a/src/main/java/spoon/pattern/PatternPrinter.java b/src/main/java/spoon/pattern/PatternPrinter.java index 69a3c67b7b4..39ba1939b19 100644 --- a/src/main/java/spoon/pattern/PatternPrinter.java +++ b/src/main/java/spoon/pattern/PatternPrinter.java @@ -20,10 +20,14 @@ import java.util.List; import java.util.Map; import java.util.TreeMap; +import java.util.function.Consumer; import spoon.Metamodel; -import spoon.SpoonException; +import spoon.pattern.node.ConstantNode; +import spoon.pattern.node.ElementNode; +import spoon.pattern.node.ListOfNodes; import spoon.pattern.node.LiveNode; +import spoon.pattern.node.ParameterNode; import spoon.pattern.node.RootNode; import spoon.pattern.parameter.ParameterInfo; import spoon.pattern.parameter.ParameterValueProvider; @@ -73,7 +77,35 @@ public void generateTargets(RootNode node, ResultHolder result, Parameter } T firstResult = getFirstResult(result, firstResultIdx); if (firstResult instanceof CtElement) { - addParameterCommentTo((CtElement) firstResult, null); + if (node instanceof ElementNode) { + ElementNode elementNode = (ElementNode) node; + List paramsOnElement = new ArrayList<>(); + for (Map.Entry e : elementNode.getRoleToNode().entrySet()) { + Metamodel.Field mmField = e.getKey(); + foreachNode(e.getValue(), attrNode -> { + if (attrNode instanceof ConstantNode || attrNode instanceof ElementNode) { + return; + } + //it is an attribute with an substitution + //it will be added only if it is not already added linked to an CtElement + paramsOnElement.add(new ParamOnElement((CtElement) firstResult, mmField.getRole(), attrNode)); + }); + } + addParameterCommentTo((CtElement) firstResult, paramsOnElement.toArray(new ParamOnElement[paramsOnElement.size()])); + } else if (node instanceof ParameterNode) { + addParameterCommentTo((CtElement) firstResult, new ParamOnElement((CtElement) firstResult, node)); + } + } + } + + private void foreachNode(RootNode rootNode, Consumer consumer) { + if (rootNode instanceof ListOfNodes) { + ListOfNodes list = (ListOfNodes) rootNode; + for (RootNode node : list.getNodes()) { + foreachNode(node, consumer); + } + } else { + consumer.accept(rootNode); } } @@ -98,14 +130,17 @@ private T getFirstResult(ResultHolder result, int firstResultIdx) { @Override public void getValueAs(ParameterInfo parameterInfo, ResultHolder result, ParameterValueProvider parameters) { - CtElement ele = (CtElement) generatePatternParameterElement(parameterInfo, result.getRequiredClass()); - addParameterCommentTo(ele, parameterInfo); - result.addResult((T) ele); + Object obj = generatePatternParameterElement(parameterInfo, result.getRequiredClass()); + if (obj != null) { + result.addResult((T) obj); + } } - private void addParameterCommentTo(CtElement ele, ParameterInfo parameterInfo) { - if (parameterInfo != null) { - params.add(new ParamOnElement((CtElement) ele, parameterInfo)); + private void addParameterCommentTo(CtElement ele, ParamOnElement... paramsOnElement) { + for (ParamOnElement paramOnElement : paramsOnElement) { + if (isNodeContained(paramOnElement.node) == false) { + params.add(paramOnElement); + } } if (isCommentVisible(ele) && params.size() > 0) { ele.addComment(ele.getFactory().Code().createComment(getSubstitutionRequestsDescription(ele, params), CommentType.BLOCK)); @@ -113,6 +148,15 @@ private void addParameterCommentTo(CtElement ele, ParameterInfo parameterInfo) { } } + private boolean isNodeContained(RootNode node) { + for (ParamOnElement paramOnElement : params) { + if (paramOnElement.node == node) { + return true; + } + } + return false; + } + /** * Creates a element which will be printed in source code of pattern as marker of parameter * @param factory a SpoonFactory which has to be used to create new elements @@ -129,21 +173,33 @@ private T generatePatternParameterElement(ParameterInfo parameterInfo, Class if (type.isAssignableFrom(CtLocalVariable.class)) { return (T) factory.createLocalVariable(factory.Type().objectType(), parameterInfo.getName(), null); } + if (type.isAssignableFrom(String.class)) { + return (T) parameterInfo.getName(); + } } - throw new SpoonException("Pattern Parameter is on Unsupported place"); + return null; } private static class ParamOnElement { final CtElement sourceElement; - final ParameterInfo param; - ParamOnElement(CtElement sourceElement, ParameterInfo param) { + final RootNode node; + final CtRole role; + ParamOnElement(CtElement sourceElement, RootNode node) { + this(sourceElement, null, node); + } + ParamOnElement(CtElement sourceElement, CtRole role, RootNode node) { this.sourceElement = sourceElement; - this.param = param; + this.role = role; + this.node = node; } @Override public String toString() { - return sourceElement.getClass().getName() + ": ${" + param.getName() + "}"; + if (role == null) { + return sourceElement.getClass().getName() + ": ${" + node.toString() + "}"; + } else { + return sourceElement.getClass().getName() + "/" + role + ": " + node.toString(); + } } } private String getSubstitutionRequestsDescription(CtElement ele, List requestsOnPos) { @@ -153,6 +209,9 @@ private String getSubstitutionRequestsDescription(CtElement ele, List e : reqByPath.entrySet()) { printer.writeln(); printer.write(e.getKey()).write('/'); - printer.write(" <= ${").write(e.getValue().param.getName() + "}"); + printer.write(" <= ").write(e.getValue().node.toString()); } return printer.toString(); } diff --git a/src/test/java/spoon/test/template/TemplateMatcherTest.java b/src/test/java/spoon/test/template/TemplateMatcherTest.java index 505c55cde1d..7194e77a9df 100644 --- a/src/test/java/spoon/test/template/TemplateMatcherTest.java +++ b/src/test/java/spoon/test/template/TemplateMatcherTest.java @@ -11,6 +11,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -22,6 +23,7 @@ import spoon.pattern.matcher.Match; import spoon.pattern.matcher.Quantifier; import spoon.pattern.parameter.ParameterValueProvider; +import spoon.pattern.parameter.UnmodifiableParameterValueProvider; import spoon.reflect.code.CtInvocation; import spoon.reflect.code.CtLiteral; import spoon.reflect.code.CtStatement; @@ -746,7 +748,7 @@ public void testMatchOfMapAttribute() throws Exception { assertEquals("matcher1", match.getMatchingElement(CtMethod.class).getSimpleName()); assertEquals(2, match.getParametersMap().size()); assertEquals("matcher1", match.getParametersMap().get("methodName")); - Map values = getMap(match, "testAnnotations"); + Map values = getMap(match, "CheckAnnotationValues"); assertEquals(0, values.size()); } { @@ -755,7 +757,7 @@ public void testMatchOfMapAttribute() throws Exception { assertEquals("m1", match.getMatchingElement(CtMethod.class).getSimpleName()); assertEquals(2, match.getParametersMap().size()); assertEquals("m1", match.getParametersMap().get("methodName")); - Map values = getMap(match, "testAnnotations"); + Map values = getMap(match, "CheckAnnotationValues"); assertEquals(1, values.size()); assertEquals("\"xyz\"", values.get("value").toString()); } @@ -765,7 +767,7 @@ public void testMatchOfMapAttribute() throws Exception { assertEquals("m2", match.getMatchingElement(CtMethod.class).getSimpleName()); assertEquals(2, match.getParametersMap().size()); assertEquals("m2", match.getParametersMap().get("methodName")); - Map values = getMap(match, "testAnnotations"); + Map values = getMap(match, "CheckAnnotationValues"); assertEquals(2, values.size()); assertEquals("\"abc\"", values.get("value").toString()); assertEquals("123", values.get("timeout").toString()); @@ -787,14 +789,14 @@ public void testMatchOfMapAttributeAndOtherAnnotations() throws Exception { //match all methods with arbitrary name, and Annotation Test modifiers, parameters, but with empty body and return type void Pattern pattern = MatchMap.createPattern(ctClass.getFactory(), true); List matches = pattern.getMatches(ctClass); - assertEquals(5, matches.size()); + assertEquals(4, matches.size()); { Match match = matches.get(0); assertEquals(1, match.getMatchingElements().size()); assertEquals("matcher1", match.getMatchingElement(CtMethod.class).getSimpleName()); assertEquals(2, match.getParametersMap().size()); assertEquals("matcher1", match.getParametersMap().get("methodName")); - assertTrue(match.getParametersMap().get("allAnnotations") instanceof List); + assertEquals(map(), getMap(match, "CheckAnnotationValues")); } { Match match = matches.get(1); @@ -802,9 +804,7 @@ public void testMatchOfMapAttributeAndOtherAnnotations() throws Exception { assertEquals("m1", match.getMatchingElement(CtMethod.class).getSimpleName()); assertEquals(2, match.getParametersMap().size()); assertEquals("m1", match.getParametersMap().get("methodName")); - Map values = getMap(match, "testAnnotations"); - assertEquals(1, values.size()); - assertEquals("java.lang.Exception.class", values.get("expected").toString()); + assertEquals("{value=\"xyz\"}", getMap(match, "CheckAnnotationValues").toString()); } { Match match = matches.get(2); @@ -812,10 +812,16 @@ public void testMatchOfMapAttributeAndOtherAnnotations() throws Exception { assertEquals("m2", match.getMatchingElement(CtMethod.class).getSimpleName()); assertEquals(2, match.getParametersMap().size()); assertEquals("m2", match.getParametersMap().get("methodName")); - Map values = getMap(match, "testAnnotations"); - assertEquals(2, values.size()); - assertEquals("java.lang.RuntimeException.class", values.get("expected").toString()); - assertEquals("123", values.get("timeout").toString()); + assertEquals("{value=\"abc\", timeout=123}", getMap(match, "CheckAnnotationValues").toString()); + } + { + Match match = matches.get(3); + assertEquals(1, match.getMatchingElements().size()); + assertEquals("deprecatedTestAnnotation2", match.getMatchingElement(CtMethod.class).getSimpleName()); + assertEquals(3, match.getParametersMap().size()); + assertEquals("deprecatedTestAnnotation2", match.getParametersMap().get("methodName")); + assertEquals("{timeout=4567}", getMap(match, "CheckAnnotationValues").toString()); + assertEquals("@java.lang.Deprecated", match.getParameters().getValue("allAnnotations").toString()); } } } @@ -830,5 +836,15 @@ private List listToListOfStrings(List list) { } return strings; } + + private MapBuilder map() { + return new MapBuilder(); + } -} + class MapBuilder extends LinkedHashMap { + public MapBuilder put(String key, Object value) { + super.put(key, value); + return this; + } + } +} diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchMap.java b/src/test/java/spoon/test/template/testclasses/match/MatchMap.java index 4e284d95ad3..a83eaf95620 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchMap.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchMap.java @@ -1,5 +1,6 @@ package spoon.test.template.testclasses.match; +import spoon.pattern.ConflictResolutionMode; import spoon.pattern.Pattern; import spoon.pattern.PatternBuilder; import spoon.reflect.declaration.CtAnnotation; @@ -15,13 +16,15 @@ public static Pattern createPattern(Factory factory, boolean acceptOtherAnnotati return PatternBuilder.create(factory, MatchMap.class, tmb -> tmb.setTypeMember("matcher1")) .configureParameters(pb -> { //match any value of @Check annotation to parameter `testAnnotations` - pb.parameter("testAnnotations").attributeOfElementByFilter(CtRole.VALUE, new TypeFilter(CtAnnotation.class)).setContainerKind(ContainerKind.MAP); + pb.parameter("CheckAnnotationValues").attributeOfElementByFilter(CtRole.VALUE, new TypeFilter(CtAnnotation.class)).setContainerKind(ContainerKind.MAP); // pb.parameter("testAnnotations").attributeOfElementByFilter(CtRole.VALUE, new TypeFilter(CtAnnotation.class)); //match any method name pb.parameter("methodName").byString("matcher1"); if (acceptOtherAnnotations) { //match on all annotations of method - pb.parameter("allAnnotations").attributeOfElementByFilter(CtRole.ANNOTATION, new TypeFilter<>(CtMethod.class)); + pb.parameter("allAnnotations") + .setConflictResolutionMode(ConflictResolutionMode.APPEND) + .attributeOfElementByFilter(CtRole.ANNOTATION, new TypeFilter<>(CtMethod.class)); } }) .build(); From 03afb4c7f9e243da0bf91a86b7e78c5b5714aeab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Sat, 10 Mar 2018 13:51:54 +0100 Subject: [PATCH 023/131] Matching of MapEntries --- .../java/spoon/pattern/ParametersBuilder.java | 54 ++++++++---- .../java/spoon/pattern/PatternPrinter.java | 4 +- .../java/spoon/pattern/node/MapEntryNode.java | 86 +++++++++++++++---- .../java/spoon/pattern/node/StringNode.java | 24 ++++++ .../test/template/TemplateMatcherTest.java | 32 +++++++ .../template/testclasses/match/MatchMap.java | 17 +++- 6 files changed, 184 insertions(+), 33 deletions(-) diff --git a/src/main/java/spoon/pattern/ParametersBuilder.java b/src/main/java/spoon/pattern/ParametersBuilder.java index c0d56a39ae9..2099b5066ac 100644 --- a/src/main/java/spoon/pattern/ParametersBuilder.java +++ b/src/main/java/spoon/pattern/ParametersBuilder.java @@ -23,7 +23,8 @@ import spoon.SpoonException; import spoon.pattern.matcher.Quantifier; -import spoon.pattern.node.ConstantNode; +import spoon.pattern.node.ListOfNodes; +import spoon.pattern.node.MapEntryNode; import spoon.pattern.node.ModelNode; import spoon.pattern.node.RootNode; import spoon.pattern.node.ParameterNode; @@ -378,6 +379,18 @@ protected void visitStringAttribute(RoleHandler roleHandler, CtElement element, addSubstitutionRequest(pi, element, roleHandler.getRole()); } } + protected void visitStringAttribute(RoleHandler roleHandler, CtElement element, String mapEntryKey, CtElement mapEntryValue) { + if (stringMarker.equals(mapEntryKey)) { + patternBuilder.modifyNodeOfAttributeOfElement(element, roleHandler.getRole(), conflictResolutionMode, oldAttrNode -> { + if (oldAttrNode instanceof MapEntryNode) { + MapEntryNode mapEntryNode = (MapEntryNode) oldAttrNode; + return new MapEntryNode(new ParameterNode(pi), ((MapEntryNode) oldAttrNode).getValue()); + } + return oldAttrNode; + }); + + } + }; }.scan(patternBuilder.getPatternModel()); return this; } @@ -397,6 +410,21 @@ protected void visitStringAttribute(RoleHandler roleHandler, CtElement element, addSubstitutionRequest(pi, element, roleHandler.getRole(), stringMarker); } } + protected void visitStringAttribute(RoleHandler roleHandler, CtElement element, String mapEntryKey, CtElement mapEntryValue) { + if (mapEntryKey != null && mapEntryKey.indexOf(stringMarker) >= 0) { + patternBuilder.modifyNodeOfAttributeOfElement(element, roleHandler.getRole(), conflictResolutionMode, oldAttrNode -> { + List nodes = ((ListOfNodes) oldAttrNode).getNodes(); + for (int i = 0; i < nodes.size(); i++) { + RootNode node = nodes.get(i); + if (node instanceof MapEntryNode) { + MapEntryNode mapEntryNode = (MapEntryNode) node; + nodes.set(i, new MapEntryNode(StringNode.setReplaceMarker(mapEntryNode.getKey(), stringMarker, pi), mapEntryNode.getValue())); + } + } + return oldAttrNode; + }); + } + }; }.scan(patternBuilder.getPatternModel()); return this; } @@ -416,6 +444,10 @@ private abstract static class StringAttributeScanner extends CtScanner { //accept String and Object class stringAttributeRoleHandlers.add(rh); } + if (rh.getContainerKind() == ContainerKind.MAP) { + //accept Map where key is String too + stringAttributeRoleHandlers.add(rh); + } }); } @@ -430,12 +462,17 @@ private void visitStringAttribute(CtElement element) { Object value = roleHandler.getValue(element); if (value instanceof String) { visitStringAttribute(roleHandler, element, (String) value); + } else if (value instanceof Map) { + for (Map.Entry e : ((Map) value).entrySet()) { + visitStringAttribute(roleHandler, element, (String) e.getKey(), e.getValue()); + } } //else it is a CtLiteral with non string value } } } protected abstract void visitStringAttribute(RoleHandler roleHandler, CtElement element, String value); + protected abstract void visitStringAttribute(RoleHandler roleHandler, CtElement element, String mapEntryKey, CtElement mapEntryValue); } @@ -552,20 +589,7 @@ void addSubstitutionRequest(ParameterInfo parameter, CtElement element, CtRole a */ void addSubstitutionRequest(ParameterInfo parameter, CtElement element, CtRole attributeRole, String subStringMarker) { patternBuilder.modifyNodeOfAttributeOfElement(element, attributeRole, conflictResolutionMode, oldAttrNode -> { - StringNode stringNode = null; - if (oldAttrNode instanceof ConstantNode) { - ConstantNode constantNode = (ConstantNode) oldAttrNode; - if (constantNode.getTemplateNode() instanceof String) { - stringNode = new StringNode((String) constantNode.getTemplateNode()); - } - } else if (oldAttrNode instanceof StringNode) { - stringNode = (StringNode) oldAttrNode; - } - if (stringNode == null) { - throw new SpoonException("Cannot add StringNode"); - } - stringNode.setReplaceMarker(subStringMarker, parameter); - return stringNode; + return StringNode.setReplaceMarker(oldAttrNode, subStringMarker, parameter); }); } diff --git a/src/main/java/spoon/pattern/PatternPrinter.java b/src/main/java/spoon/pattern/PatternPrinter.java index 39ba1939b19..ccb4c223707 100644 --- a/src/main/java/spoon/pattern/PatternPrinter.java +++ b/src/main/java/spoon/pattern/PatternPrinter.java @@ -58,9 +58,9 @@ public PatternPrinter() { } public String printNode(RootNode node) { - List generated = generateTargets(node, (ParameterValueProvider) null, null); + List generated = generateTargets(node, (ParameterValueProvider) null, null); StringBuilder sb = new StringBuilder(); - for (CtElement ele : generated) { + for (Object ele : generated) { sb.append(ele.toString()).append('\n'); } return sb.toString(); diff --git a/src/main/java/spoon/pattern/node/MapEntryNode.java b/src/main/java/spoon/pattern/node/MapEntryNode.java index b965ce7799e..9ead2666297 100644 --- a/src/main/java/spoon/pattern/node/MapEntryNode.java +++ b/src/main/java/spoon/pattern/node/MapEntryNode.java @@ -16,21 +16,26 @@ */ package spoon.pattern.node; +import java.util.Map; import java.util.function.BiConsumer; +import spoon.SpoonException; import spoon.pattern.Generator; import spoon.pattern.ResultHolder; -import spoon.pattern.matcher.Matchers; import spoon.pattern.matcher.TobeMatched; import spoon.pattern.parameter.ParameterInfo; import spoon.pattern.parameter.ParameterValueProvider; +import spoon.reflect.declaration.CtElement; +import spoon.reflect.meta.ContainerKind; + +import static spoon.pattern.matcher.TobeMatched.getMatchedParameters; /** * Represents a ValueResolver of one Map.Entry */ -public class MapEntryNode extends AbstractNode { - private final RootNode key; - private final RootNode value; +public class MapEntryNode extends AbstractPrimitiveMatcher { + private RootNode key; + private RootNode value; public MapEntryNode(RootNode key, RootNode value) { super(); @@ -47,24 +52,75 @@ public RootNode getValue() { @Override public boolean replaceNode(RootNode oldNode, RootNode newNode) { - //TODO - throw new UnsupportedOperationException("TODO"); + if (key == oldNode) { + key = newNode; + return true; + } + if (value == oldNode) { + value = newNode; + return true; + } + if (key.replaceNode(oldNode, newNode)) { + return true; + } + if (value.replaceNode(oldNode, newNode)) { + return true; + } + return false; } - @Override - public TobeMatched matchTargets(TobeMatched targets, Matchers nextMatchers) { - //TODO - throw new UnsupportedOperationException("TODO"); - } @Override public void forEachParameterInfo(BiConsumer consumer) { - // TODO - throw new UnsupportedOperationException("TODO"); + key.forEachParameterInfo(consumer); + value.forEachParameterInfo(consumer); + } + + private static class Entry implements Map.Entry { + private final String key; + private CtElement value; + + Entry(String key, CtElement value) { + super(); + this.key = key; + this.value = value; + } + + @Override + public String getKey() { + return key; + } + + @Override + public CtElement getValue() { + return value; + } + + @Override + public CtElement setValue(CtElement value) { + CtElement oldV = this.value; + this.value = value; + return oldV; + } } @Override public void generateTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters) { - // TODO - throw new UnsupportedOperationException("TODO"); + String entryKey = generator.generateTarget(key, parameters, String.class); + CtElement entryValue = generator.generateTarget(value, parameters, CtElement.class); + if (entryKey != null && entryValue != null) { + result.addResult((T) new Entry(entryKey, entryValue)); + } + } + @Override + public ParameterValueProvider matchTarget(Object target, ParameterValueProvider parameters) { + if (target instanceof Map.Entry) { + Map.Entry targetEntry = (Map.Entry) target; + parameters = getMatchedParameters(getKey().matchAllWith(TobeMatched.create(parameters, ContainerKind.SINGLE, targetEntry.getKey()))); + if (parameters == null) { + return null; + } + return getMatchedParameters(getValue().matchAllWith(TobeMatched.create(parameters, ContainerKind.SINGLE, targetEntry.getValue()))); + } + throw new SpoonException("Unexpected target type " + target.getClass().getName()); } } diff --git a/src/main/java/spoon/pattern/node/StringNode.java b/src/main/java/spoon/pattern/node/StringNode.java index e2dde8d6964..b43071e2a4b 100644 --- a/src/main/java/spoon/pattern/node/StringNode.java +++ b/src/main/java/spoon/pattern/node/StringNode.java @@ -245,4 +245,28 @@ public String toString() { } return sb.toString(); } + + /** + * Applies substring substitution to `targetNode`. Converts old node to {@link StringNode} if needed. + * @param targetNode + * @param replaceMarker + * @param param + * @return {@link StringNode} which contains all the data of origin `targetNode` and new replaceMarker request + */ + public static StringNode setReplaceMarker(RootNode targetNode, String replaceMarker, ParameterInfo param) { + StringNode stringNode = null; + if (targetNode instanceof ConstantNode) { + ConstantNode constantNode = (ConstantNode) targetNode; + if (constantNode.getTemplateNode() instanceof String) { + stringNode = new StringNode((String) constantNode.getTemplateNode()); + } + } else if (targetNode instanceof StringNode) { + stringNode = (StringNode) targetNode; + } + if (stringNode == null) { + throw new SpoonException("Cannot add StringNode"); + } + stringNode.setReplaceMarker(replaceMarker, param); + return stringNode; + } } diff --git a/src/test/java/spoon/test/template/TemplateMatcherTest.java b/src/test/java/spoon/test/template/TemplateMatcherTest.java index 7194e77a9df..f550a880393 100644 --- a/src/test/java/spoon/test/template/TemplateMatcherTest.java +++ b/src/test/java/spoon/test/template/TemplateMatcherTest.java @@ -826,6 +826,38 @@ public void testMatchOfMapAttributeAndOtherAnnotations() throws Exception { } } + @Test + public void testMatchOfMapKeySubstring() throws Exception { + //contract: match substring in key of Map Entry - match key of annotation value + CtType ctClass = ModelUtils.buildClass(MatchMap.class); + { + //match all methods with arbitrary name, and Annotation Test modifiers, parameters, but with empty body and return type void + Pattern pattern = MatchMap.createMatchKeyPattern(ctClass.getFactory()); + List matches = pattern.getMatches(ctClass); + String str = pattern.toString(); + assertEquals(2, matches.size()); + { + Match match = matches.get(0); + assertEquals(1, match.getMatchingElements().size()); + assertEquals("m1", match.getMatchingElement(CtMethod.class).getSimpleName()); + assertEquals(3, match.getParametersMap().size()); + assertEquals("m1", match.getParametersMap().get("methodName")); + assertEquals("value", match.getParameters().getValue("CheckKey").toString()); + assertEquals("\"xyz\"", match.getParameters().getValue("CheckValue").toString()); + } + { + Match match = matches.get(1); + assertEquals(1, match.getMatchingElements().size()); + assertEquals("deprecatedTestAnnotation2", match.getMatchingElement(CtMethod.class).getSimpleName()); + assertEquals(4, match.getParametersMap().size()); + assertEquals("deprecatedTestAnnotation2", match.getParametersMap().get("methodName")); + assertEquals("timeout", match.getParameters().getValue("CheckKey").toString()); + assertEquals("4567", match.getParameters().getValue("CheckValue").toString()); + assertEquals("@java.lang.Deprecated", match.getParameters().getValue("allAnnotations").toString()); + } + } + } + private List listToListOfStrings(List list) { if (list == null) { return Collections.emptyList(); diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchMap.java b/src/test/java/spoon/test/template/testclasses/match/MatchMap.java index a83eaf95620..c4fb4a73d10 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchMap.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchMap.java @@ -3,6 +3,7 @@ import spoon.pattern.ConflictResolutionMode; import spoon.pattern.Pattern; import spoon.pattern.PatternBuilder; +import spoon.reflect.code.CtLiteral; import spoon.reflect.declaration.CtAnnotation; import spoon.reflect.declaration.CtMethod; import spoon.reflect.factory.Factory; @@ -17,7 +18,6 @@ public static Pattern createPattern(Factory factory, boolean acceptOtherAnnotati .configureParameters(pb -> { //match any value of @Check annotation to parameter `testAnnotations` pb.parameter("CheckAnnotationValues").attributeOfElementByFilter(CtRole.VALUE, new TypeFilter(CtAnnotation.class)).setContainerKind(ContainerKind.MAP); -// pb.parameter("testAnnotations").attributeOfElementByFilter(CtRole.VALUE, new TypeFilter(CtAnnotation.class)); //match any method name pb.parameter("methodName").byString("matcher1"); if (acceptOtherAnnotations) { @@ -29,6 +29,21 @@ public static Pattern createPattern(Factory factory, boolean acceptOtherAnnotati }) .build(); } + public static Pattern createMatchKeyPattern(Factory factory) { + return PatternBuilder.create(factory, MatchMap.class, tmb -> tmb.setTypeMember("m1")) + .configureParameters(pb -> { + //match any value of @Check annotation to parameter `testAnnotations` + pb.parameter("CheckKey").bySubstring("value"); + pb.parameter("CheckValue").byFilter((CtLiteral lit) -> true); + //match any method name + pb.parameter("methodName").byString("m1"); + //match on all annotations of method + pb.parameter("allAnnotations") + .setConflictResolutionMode(ConflictResolutionMode.APPEND) + .attributeOfElementByFilter(CtRole.ANNOTATION, new TypeFilter<>(CtMethod.class)); + }) + .build(); + } @Check() void matcher1() { From 287d60fad6f84d944386492ffd0f5317d2dd85b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Sat, 10 Mar 2018 15:23:39 +0100 Subject: [PATCH 024/131] fix tests and some docu --- .../spoon/pattern/LiveStatementsBuilder.java | 15 ++++ .../java/spoon/pattern/ParametersBuilder.java | 5 ++ .../java/spoon/pattern/PatternBuilder.java | 5 ++ .../java/spoon/pattern/node/ElementNode.java | 70 +++++++++++++------ .../ParameterValueProviderFactory.java | 5 +- src/test/java/spoon/MavenLauncherTest.java | 4 +- .../SpoonArchitectureEnforcerTest.java | 2 + 7 files changed, 82 insertions(+), 24 deletions(-) diff --git a/src/main/java/spoon/pattern/LiveStatementsBuilder.java b/src/main/java/spoon/pattern/LiveStatementsBuilder.java index 141b0e78ac6..ca98c71374d 100644 --- a/src/main/java/spoon/pattern/LiveStatementsBuilder.java +++ b/src/main/java/spoon/pattern/LiveStatementsBuilder.java @@ -89,6 +89,11 @@ public LiveStatementsBuilder setConflictResolutionMode(ConflictResolutionMode co return this; } + /** + * marks all CtIf and CtForEach whose expression contains a variable refrence named `variableName` as live statement. + * @param variableName to be searched variable name + * @return this to support fluent API + */ public LiveStatementsBuilder byVariableName(String variableName) { patternBuilder.patternQuery .filterChildren(new TypeFilter<>(CtVariableReference.class)) @@ -110,6 +115,11 @@ public void visitCtIf(CtIf ifElement) { return this; } + /** + * marks {@link CtForEach} as live statement. + * @param foreach to be marked {@link CtForEach} element + * @return this to support fluent API + */ public LiveStatementsBuilder markLive(CtForEach foreach) { //detect meta elements by different way - e.g. comments? RootNode vr = patternBuilder.getPatternNode(foreach.getExpression()); @@ -138,6 +148,11 @@ public LiveStatementsBuilder markLive(CtForEach foreach) { return this; } + /** + * marks {@link CtIf} as live statement. + * @param ifElement to be marked {@link CtIf} element + * @return this to support fluent API + */ public LiveStatementsBuilder markLive(CtIf ifElement) { SwitchNode osp = new SwitchNode(); boolean[] canBeLive = new boolean[]{true}; diff --git a/src/main/java/spoon/pattern/ParametersBuilder.java b/src/main/java/spoon/pattern/ParametersBuilder.java index 2099b5066ac..6a1e5f6e920 100644 --- a/src/main/java/spoon/pattern/ParametersBuilder.java +++ b/src/main/java/spoon/pattern/ParametersBuilder.java @@ -286,6 +286,11 @@ public ParametersBuilder parametersByVariable(String... variableName) { } return this; } + /** + * Add parameters for each variable reference of `variable` + * @param variable to be substituted variable + * @return this to support fluent API + */ public ParametersBuilder parametersByVariable(CtVariable variable) { CtQueryable searchScope; if (patternBuilder.isInModel(variable)) { diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index 2906d6b992b..79ded968c57 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -323,6 +323,11 @@ public PatternBuilder setDefaultValueConvertor(ValueConvertor valueConvertor) { return this; } + /** + * All the variable references, whose variables are out of the template model + * are automatically marked as pattern parameters + * @return this to support fluent API + */ public PatternBuilder configureAutomaticParameters() { configureParameters(pb -> { //add this substitution request only if there isn't another one yet diff --git a/src/main/java/spoon/pattern/node/ElementNode.java b/src/main/java/spoon/pattern/node/ElementNode.java index 333111e0710..2eb0f834132 100644 --- a/src/main/java/spoon/pattern/node/ElementNode.java +++ b/src/main/java/spoon/pattern/node/ElementNode.java @@ -47,6 +47,13 @@ */ public class ElementNode extends AbstractPrimitiveMatcher { + /** + * Creates an implicit {@link ElementNode}, which contains all non derived attributes of `element` and all it's children + * @param element source element, which is used to initialize {@link ElementNode} + * @param patternElementToSubstRequests the {@link Map}, which will receive mapping between `element` and it's children + * and newly created tree of {@link ElementNode}s + * @return a tree of {@link ElementNode}s, which reflects tree of `element` + */ public static ElementNode create(CtElement element, Map patternElementToSubstRequests) { Metamodel.Type mmConcept = Metamodel.getMetamodelTypeByClass(element.getClass()); ElementNode elementNode = new ElementNode(mmConcept, element); @@ -64,27 +71,13 @@ public static ElementNode create(CtElement element, Map pat return elementNode; } - private static RootNode create(Object object, Map patternElementToSubstRequests) { - if (object instanceof CtElement) { - return create((CtElement) object, patternElementToSubstRequests); - } - return new ConstantNode(object); - } - - private static RootNode create(ContainerKind containerKind, Object templates, Map patternElementToSubstRequests) { - switch (containerKind) { - case LIST: - return create((List) templates, patternElementToSubstRequests); - case SET: - return create((Set) templates, patternElementToSubstRequests); - case MAP: - return create((Map) templates, patternElementToSubstRequests); - case SINGLE: - return create(templates, patternElementToSubstRequests); - } - throw new SpoonException("Unexpected RoleHandler containerKind: " + containerKind); - } - + /** + * Same like {@link #create(CtElement, Map)} but with {@link List} of elements or primitive objects + * + * @param objects List of objects which has to be transformed to nodes + * @param patternElementToSubstRequests mapping between {@link CtElement} from `objects` to created `node` + * @return a list of trees of nodes, which reflects list of `objects` + */ public static ListOfNodes create(List objects, Map patternElementToSubstRequests) { if (objects == null) { objects = Collections.emptyList(); @@ -92,6 +85,13 @@ public static ListOfNodes create(List objects, Map patte return listOfNodesToNode(objects.stream().map(i -> create(i, patternElementToSubstRequests)).collect(Collectors.toList())); } + /** + * Same like {@link #create(CtElement, Map)} but with {@link Set} of elements or primitive objects + * + * @param templates Set of objects which has to be transformed to nodes + * @param patternElementToSubstRequests mapping between {@link CtElement} from `templates` to created `node` + * @return a list of trees of nodes, which reflects Set of `templates` + */ public static ListOfNodes create(Set templates, Map patternElementToSubstRequests) { if (templates == null) { templates = Collections.emptySet(); @@ -113,6 +113,13 @@ public static ListOfNodes create(Set templates, Map patt return listOfNodesToNode(constantMatchers); } + /** + * Same like {@link #create(CtElement, Map)} but with {@link Map} of String to elements or primitive objects + * + * @param map Map of objects which has to be transformed to nodes + * @param patternElementToSubstRequests mapping between {@link CtElement} from `map` to created `node` + * @return a list of {@link MapEntryNode}s, which reflects `map` + */ public static ListOfNodes create(Map map, Map patternElementToSubstRequests) { if (map == null) { map = Collections.emptyMap(); @@ -137,6 +144,27 @@ public static ListOfNodes create(Map map, Map pa return listOfNodesToNode(constantMatchers); } + private static RootNode create(Object object, Map patternElementToSubstRequests) { + if (object instanceof CtElement) { + return create((CtElement) object, patternElementToSubstRequests); + } + return new ConstantNode(object); + } + + private static RootNode create(ContainerKind containerKind, Object templates, Map patternElementToSubstRequests) { + switch (containerKind) { + case LIST: + return create((List) templates, patternElementToSubstRequests); + case SET: + return create((Set) templates, patternElementToSubstRequests); + case MAP: + return create((Map) templates, patternElementToSubstRequests); + case SINGLE: + return create(templates, patternElementToSubstRequests); + } + throw new SpoonException("Unexpected RoleHandler containerKind: " + containerKind); + } + @SuppressWarnings({ "unchecked", "rawtypes" }) private static ListOfNodes listOfNodesToNode(List nodes) { //The attribute is matched different if there is List of one ParameterizedNode and when there is one ParameterizedNode diff --git a/src/main/java/spoon/pattern/parameter/ParameterValueProviderFactory.java b/src/main/java/spoon/pattern/parameter/ParameterValueProviderFactory.java index 1d1aa4a7fb5..ce4ae3c01db 100644 --- a/src/main/java/spoon/pattern/parameter/ParameterValueProviderFactory.java +++ b/src/main/java/spoon/pattern/parameter/ParameterValueProviderFactory.java @@ -17,8 +17,11 @@ package spoon.pattern.parameter; /** - * Creates appropriate instances of {@link ParameterValueProvider} during matching process + * Creates instances of {@link ParameterValueProvider} */ public interface ParameterValueProviderFactory { + /** + * @return new instance of empty {@link ParameterValueProvider} + */ ParameterValueProvider createParameterValueProvider(); } diff --git a/src/test/java/spoon/MavenLauncherTest.java b/src/test/java/spoon/MavenLauncherTest.java index 0fb589195c8..4b8e4406f39 100644 --- a/src/test/java/spoon/MavenLauncherTest.java +++ b/src/test/java/spoon/MavenLauncherTest.java @@ -17,8 +17,8 @@ public void spoonMavenLauncherTest() { MavenLauncher launcher = new MavenLauncher("./", MavenLauncher.SOURCE_TYPE.APP_SOURCE); assertEquals(7, launcher.getEnvironment().getSourceClasspath().length); - // 52 because of the sub folders of src/main/java - assertEquals(54, launcher.getModelBuilder().getInputSources().size()); + // 56 because of the sub folders of src/main/java + assertEquals(56, launcher.getModelBuilder().getInputSources().size()); // with the tests launcher = new MavenLauncher("./", MavenLauncher.SOURCE_TYPE.ALL_SOURCE); diff --git a/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java b/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java index fabf508ab7c..d6e9c6b8530 100644 --- a/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java +++ b/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java @@ -312,6 +312,8 @@ public void testSpecPackage() throws Exception { officialPackages.add("spoon.legacy"); officialPackages.add("spoon.pattern"); officialPackages.add("spoon.pattern.matcher"); + officialPackages.add("spoon.pattern.node"); + officialPackages.add("spoon.pattern.parameter"); officialPackages.add("spoon.processing"); officialPackages.add("spoon.refactoring"); officialPackages.add("spoon.reflect.annotations"); From 6d2312dc9ed3226b7a11e813be572850a1715b84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Sat, 10 Mar 2018 15:42:40 +0100 Subject: [PATCH 025/131] more comments --- .../java/spoon/pattern/TemplateBuilder.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/main/java/spoon/pattern/TemplateBuilder.java b/src/main/java/spoon/pattern/TemplateBuilder.java index a455d40107f..2dd2c38a7d1 100644 --- a/src/main/java/spoon/pattern/TemplateBuilder.java +++ b/src/main/java/spoon/pattern/TemplateBuilder.java @@ -40,7 +40,7 @@ public class TemplateBuilder { /** - * Creates a {@link Pattern} from {@link Template} + * Creates a {@link TemplateBuilder}, which builds {@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 @@ -50,6 +50,14 @@ public static TemplateBuilder createPattern(CtElement templateRoot, Template CtClass> templateType = Substitution.getTemplateCtClass(templateRoot.getFactory(), template); return createPattern(templateRoot, templateType, template); } + /** + * Creates a {@link TemplateBuilder}, which builds {@link Pattern} from {@link Template} + * @param templateRoot the root element of {@link Template} model + * @param templateType {@link CtClass} model of `template` + * @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 + */ public static TemplateBuilder createPattern(CtElement templateRoot, CtClass templateType, Template template) { Factory f = templateRoot.getFactory(); @@ -103,10 +111,17 @@ private TemplateBuilder(CtClass templateType, PatternBuilder patternBuilder, this.templateType = templateType; } + /** + * @return a {@link Pattern} built by this {@link TemplateBuilder} + */ public Pattern build() { return patternBuilder.build(); } + /** + * @param addGeneratedBy true if "generated by" comments has to be added into code generated by {@link Pattern} made by this {@link TemplateBuilder} + * @return this to support fluent API + */ public TemplateBuilder setAddGeneratedBy(boolean addGeneratedBy) { patternBuilder.setAddGeneratedBy(addGeneratedBy); return this; From 712ebc467d2eb63de6ae83c301487ace58ab283b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Tue, 13 Mar 2018 21:57:43 +0100 Subject: [PATCH 026/131] fix runtime metamodel --- src/main/java/spoon/Metamodel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/spoon/Metamodel.java b/src/main/java/spoon/Metamodel.java index 79b7d66f6ce..330d2389a86 100644 --- a/src/main/java/spoon/Metamodel.java +++ b/src/main/java/spoon/Metamodel.java @@ -1386,7 +1386,7 @@ private static void initTypes(List types) { types.add(new Type("CtCatchVariable", spoon.reflect.code.CtCatchVariable.class, spoon.support.reflect.code.CtCatchVariableImpl.class, fm -> fm .field(CtRole.NAME, false, false) - .field(CtRole.TYPE, true, false) + .field(CtRole.TYPE, true, true) .field(CtRole.IS_IMPLICIT, false, false) .field(CtRole.DEFAULT_EXPRESSION, true, true) .field(CtRole.MODIFIER, false, false) From 89935b87fae2315efcc8ed7f2662f2e139ba9278 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Tue, 13 Mar 2018 21:58:00 +0100 Subject: [PATCH 027/131] fix runtime metamodel test --- src/test/java/spoon/test/api/MetamodelTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/spoon/test/api/MetamodelTest.java b/src/test/java/spoon/test/api/MetamodelTest.java index c42bebc2512..ae835009304 100644 --- a/src/test/java/spoon/test/api/MetamodelTest.java +++ b/src/test/java/spoon/test/api/MetamodelTest.java @@ -77,8 +77,8 @@ public void testRuntimeMetamodel() { Map expectedRoleToField = new HashMap<>(expectedType.getRoleToProperty()); for (Metamodel.Field field : type.getFields()) { MetamodelProperty expectedField = expectedRoleToField.remove(field.getRole()); - assertSame(expectedField.isDerived(), field.isDerived()); - assertSame(expectedField.isUnsettable(), field.isUnsettable()); + assertSame("Field " + expectedField + ".derived", expectedField.isDerived(), field.isDerived()); + assertSame("Field " + expectedField + ".unsettable", expectedField.isUnsettable(), field.isUnsettable()); } assertTrue("These Metamodel.Field instances are missing on Type " + type.getName() +": " + expectedRoleToField.keySet(), expectedRoleToField.isEmpty()); } From 9b26a200e5e3d2ff33a73aa2c4f05cb3700bb575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Wed, 14 Mar 2018 18:04:10 +0100 Subject: [PATCH 028/131] checkstyle and fixed some java doc --- .../spoon/pattern/ConflictResolutionMode.java | 4 +- src/main/java/spoon/pattern/Generator.java | 4 +- .../java/spoon/pattern/ParametersBuilder.java | 5 + src/main/java/spoon/pattern/Pattern.java | 2 +- .../spoon/pattern/SubstitutionCloner.java | 197 ------------------ .../java/spoon/pattern/node/ListOfNodes.java | 6 +- .../java/spoon/pattern/node/SwitchNode.java | 2 +- .../parameter/AbstractParameterInfo.java | 24 ++- .../pattern/parameter/ParameterInfo.java | 13 +- 9 files changed, 45 insertions(+), 212 deletions(-) delete mode 100644 src/main/java/spoon/pattern/SubstitutionCloner.java diff --git a/src/main/java/spoon/pattern/ConflictResolutionMode.java b/src/main/java/spoon/pattern/ConflictResolutionMode.java index 4a2baa758ea..a1ebf3eef27 100644 --- a/src/main/java/spoon/pattern/ConflictResolutionMode.java +++ b/src/main/java/spoon/pattern/ConflictResolutionMode.java @@ -28,11 +28,11 @@ public enum ConflictResolutionMode { */ FAIL, /** - * get rid of old {@link Node} and use new {@link Node} instead + * get rid of old {@link RootNode} and use new {@link RootNode} instead */ USE_NEW_NODE, /** - * keep old {@link Node} and ignore requests to add new {@link Node} + * keep old {@link RootNode} and ignore requests to add new {@link RootNode} */ KEEP_OLD_NODE, /** diff --git a/src/main/java/spoon/pattern/Generator.java b/src/main/java/spoon/pattern/Generator.java index 4270b176547..6943a2a12d5 100644 --- a/src/main/java/spoon/pattern/Generator.java +++ b/src/main/java/spoon/pattern/Generator.java @@ -58,7 +58,7 @@ public interface Generator { /** * Generates one target depending on kind of this {@link RootNode}, expected `expectedType` and input `parameters` - * @param factory TODO + * @param node to be generated node * @param parameters {@link ParameterValueProvider} * @param expectedType defines {@link Class} of returned value * @@ -72,7 +72,7 @@ default T generateTarget(RootNode node, ParameterValueProvider parameters, C /** * Generates zero, one or more targets depending on kind of this {@link RootNode}, expected `expectedType` and input `parameters` - * @param factory TODO + * @param node to be generated node * @param parameters {@link ParameterValueProvider} * @param expectedType defines {@link Class} of returned value * diff --git a/src/main/java/spoon/pattern/ParametersBuilder.java b/src/main/java/spoon/pattern/ParametersBuilder.java index 6a1e5f6e920..0e9d45c9920 100644 --- a/src/main/java/spoon/pattern/ParametersBuilder.java +++ b/src/main/java/spoon/pattern/ParametersBuilder.java @@ -549,6 +549,11 @@ public ParametersBuilder attributeOfElementByFilter(CtRole role, Filter filte return this; } + /** + * @param type a required type of the value which matches as value of this parameter + * @param matchCondition a {@link Predicate} which selects matching values + * @return this to support fluent API + */ public ParametersBuilder matchCondition(Class type, Predicate matchCondition) { currentParameter.setMatchCondition(type, matchCondition); return this; diff --git a/src/main/java/spoon/pattern/Pattern.java b/src/main/java/spoon/pattern/Pattern.java index 947afb643bb..95e7c91b39d 100644 --- a/src/main/java/spoon/pattern/Pattern.java +++ b/src/main/java/spoon/pattern/Pattern.java @@ -222,7 +222,7 @@ public void apply(Object input, CtConsumer outputConsumer) { /** * Finds all target program sub-trees that correspond to a template * and calls consumer.accept(matchingElement, parameterValues) - * @param rootElement the root of to be searched AST + * @param input the root of to be searched AST * @param consumer the receiver of matches */ public void forEachMatch(Object input, CtConsumer consumer) { diff --git a/src/main/java/spoon/pattern/SubstitutionCloner.java b/src/main/java/spoon/pattern/SubstitutionCloner.java deleted file mode 100644 index a2156d2a553..00000000000 --- a/src/main/java/spoon/pattern/SubstitutionCloner.java +++ /dev/null @@ -1,197 +0,0 @@ -//package spoon.pattern; -// -//import java.util.Collection; -//import java.util.Collections; -//import java.util.IdentityHashMap; -//import java.util.Map; -//import java.util.Set; -// -//import spoon.reflect.code.CtCodeElement; -//import spoon.reflect.code.CtComment; -//import spoon.reflect.cu.CompilationUnit; -//import spoon.reflect.cu.SourcePosition; -//import spoon.reflect.declaration.CtElement; -//import spoon.reflect.declaration.CtType; -//import spoon.reflect.declaration.CtTypeMember; -//import spoon.reflect.meta.RoleHandler; -//import spoon.reflect.meta.impl.RoleHandlerHelper; -//import spoon.support.SpoonClassNotFoundException; -//import spoon.support.visitor.equals.CloneHelper; -// -///** -// * Clones provided AST and substitutes required nodes and attributes using -// * defined parameters -// */ -//class SubstitutionCloner extends CloneHelper { -// private final SubstitutionRequestProvider modelValueResolver; -// private final ParameterValueProvider parameters; -// /* -// * Set of parents of substituted elements, which might be potentially simplified -// * (partially evaluated) -// */ -// Set toBeSimplifiedElements = Collections.newSetFromMap(new IdentityHashMap<>()); -// -// SubstitutionCloner(SubstitutionRequestProvider pattern, ParameterValueProvider parameters) { -// super(); -// this.modelValueResolver = pattern; -// this.parameters = parameters; -// } -// -// /** -// * Handle substitution of single value element. Zero or one element can be -// * result of substitution of `element` -// */ -// @Override -// public T clone(T origin) { -// if (origin == null) { -// return null; -// } -// // TODO refactor clone process by way it directly delivers correct CtRole. Next -// // line has some performance impact -// ResultHolder.Single result = new ResultHolder.Single<>(getExpectedClassInParent(origin)); -// substituteOrClone(origin, result); -// return result.getResult(); -// } -// -// /** -// * Handle substitution of element in a collection. Zero, one or more elements -// * can be result of substitution of `element` -// */ -// @Override -// protected void addClone(Collection targetCollection, T origin) { -// // TODO refactor clone process by way it directly delivers correct CtRole. Next -// // line has some performance impact -// ResultHolder.Multiple result = new ResultHolder.Multiple<>(getExpectedClassInParent(origin)); -// substituteOrClone(origin, result); -// targetCollection.addAll(result.getResult()); -// } -// -// /** -// * Handle substitution of element in a Map. Zero or one element can be result of -// * substitution of `element` -// */ -// @Override -// protected void addClone(Map targetMap, String key, T originValue) { -// // TODO refactor clone process by way it directly delivers correct CtRole. Next -// // line has some performance impact -// ResultHolder.Single result = new ResultHolder.Single<>(getExpectedClassInParent(originValue)); -// substituteOrClone(originValue, result); -// if (result.getResult() == null) { -// // the pattern removes this element. Do not add it into target map -// return; -// } -// targetMap.put(key, result.getResult()); -// return; -// } -// -// private Class getExpectedClassInParent(CtElement element) { -// RoleHandler rh = RoleHandlerHelper.getRoleHandlerWrtParent(element); -// if (rh == null) { -// return null; -// } -// return (Class) rh.getValueClass(); -// } -// -// @SuppressWarnings("unchecked") -// protected void substituteOrClone(T origin, ResultHolder result) { -// String generatedBy = origin instanceof CtTypeMember ? getGeneratedByComment(origin) : null; -// // substitute the origin element and write result into context -// substituteOrClone2(origin, result); -// -// if (generatedBy != null) { -// // add generated by comment -// result.mapEachResult(element -> { -// if (element instanceof CtTypeMember) { -// addGeneratedByComment(element, generatedBy); -// } -// return element; -// }); -// } -// if (toBeSimplifiedElements.remove(origin)) { -// // simplify this element, it contains a substituted element -// // TODO it would be nice if CloneHelper would directly set the required class, -// // but it would need more changes... -// if (origin.isParentInitialized()) { -// RoleHandler rh = RoleHandlerHelper.getRoleHandlerWrtParent(origin); -// if (rh != null) { -// result.setRequiredClass(rh.getValueClass()); -// } else { -// result.setRequiredClass(origin.getClass()); -// } -// } -// result.mapEachResult(element -> { -// if (element instanceof CtCodeElement) { -// CtCodeElement code = (CtCodeElement) element; -// try { -// code = code.partiallyEvaluate(); -// if (result.getRequiredClass().isInstance(code)) { -// return (T) code; -// } -// /* -// * else the simplified code is not matching with required type. For example -// * statement String.class.getName() was converted to expression -// * "java.lang.String" -// */ -// } catch (SpoonClassNotFoundException e) { -// // ignore it. Do not simplify this element -// origin.getFactory().getEnvironment() -// .debugMessage("Partial evaluation was skipped because of: " + e.getMessage()); -// } -// } -// return element; -// }); -// } -// } -// -// /** -// * Clones or substitutes origin element using {@link Node} of the role of this -// * attribute for `origin` element, then element is substituted -// * -// * @param origin -// * to be cloned or substituted element -// * @param result -// * holder for result -// */ -// private void substituteOrClone2(T origin, ResultHolder result) { -// Node valueResolver = modelValueResolver.getTemplateValueResolver(origin); -// if ((valueResolver instanceof ConstantNode) == false) { -// // it is not just copying of node or substitution of node attributes -// // the node is replaced by different 0, 1 or more nodes -// planSimplification(origin.getParent()); -// } -// valueResolver.generateTargets(factory, result, parameters); -// } -// -// /** -// * @param element -// * to be cloned element -// * @return a clone which is not substituted -// */ -// public T originClone(T element) { -// T clone = super.clone(element); -// if (clone instanceof CtType) { -// SourcePosition pos = clone.getPosition(); -// if (pos != null) { -// CompilationUnit cu = pos.getCompilationUnit(); -// if (cu != null && cu.getImports().size() > 0) { -// // avoid usage of invalid template imports in generated code -// // TODO - this is just dirty workaround, which removes imports for templates too -// // - but it should be no big problem ... -// cu.setImports(Collections.emptySet()); -// } -// } -// } -// return clone; -// } -// -// /** -// * plans simplification of clone of `element` after cloning of it's children is -// * finished -// * -// * @param element -// * origin (not cloned) element, whose clone has to be simplified -// */ -// private void planSimplification(CtElement element) { -// toBeSimplifiedElements.add(element); -// } -//} diff --git a/src/main/java/spoon/pattern/node/ListOfNodes.java b/src/main/java/spoon/pattern/node/ListOfNodes.java index eaf7eaf3f34..1189edca730 100644 --- a/src/main/java/spoon/pattern/node/ListOfNodes.java +++ b/src/main/java/spoon/pattern/node/ListOfNodes.java @@ -57,11 +57,7 @@ public TobeMatched matchTargets(TobeMatched targets, Matchers nextMatchers) { return ChainOfMatchersImpl.create(nodes, nextMatchers).matchAllWith(targets); } - /** - * @param oldNode a {@link CtElement} whose {@link RootNode} we are looking for - * @return a {@link NodeContainer} of an {@link ElementNode}, whose {@link ElementNode#getTemplateNode()} == `element` - * null if element is not referred by any node of this {@link ListOfNodes} - */ + @Override public boolean replaceNode(RootNode oldNode, RootNode newNode) { for (int i = 0; i < nodes.size(); i++) { RootNode node = nodes.get(i); diff --git a/src/main/java/spoon/pattern/node/SwitchNode.java b/src/main/java/spoon/pattern/node/SwitchNode.java index 95065595a18..f765d42d9db 100644 --- a/src/main/java/spoon/pattern/node/SwitchNode.java +++ b/src/main/java/spoon/pattern/node/SwitchNode.java @@ -244,7 +244,7 @@ public void generateLiveTargets(Generator generator, ResultHolder result, result.addResult((T) lastElse); } return; - } + } if (lastElse != null) { //append last else into lastIf lastIf.setElseStatement(lastElse); diff --git a/src/main/java/spoon/pattern/parameter/AbstractParameterInfo.java b/src/main/java/spoon/pattern/parameter/AbstractParameterInfo.java index 2e783a7835d..c9186627296 100644 --- a/src/main/java/spoon/pattern/parameter/AbstractParameterInfo.java +++ b/src/main/java/spoon/pattern/parameter/AbstractParameterInfo.java @@ -180,13 +180,26 @@ protected T castTo(Object o, Class type) { protected abstract T getEmptyContainer(); + /** + * @param requiredType a required type of the value which matches as value of this parameter + * @param matchCondition a {@link Predicate} which selects matching values + * @return + */ public AbstractParameterInfo setMatchCondition(Class requiredType, Predicate matchCondition) { this.parameterValueType = requiredType; this.matchCondition = (Predicate) matchCondition; return this; } - public boolean matches(Object value) { + /** + * Checks whether `value` matches with required type and match condition. + * @param value + * @return + */ + protected boolean matches(Object value) { + if (parameterValueType != null && (value == null || parameterValueType.isAssignableFrom(value.getClass()) == false)) { + return false; + } if (matchCondition == null) { //there is no matching condition. Everything matches return true; @@ -207,6 +220,10 @@ public Class getParameterValueType() { return parameterValueType; } + /** + * @param parameterValueType a type of the value which is acceptable by this parameter + * @return this to support fluent API + */ public AbstractParameterInfo setParameterValueType(Class parameterValueType) { this.parameterValueType = parameterValueType; return this; @@ -218,6 +235,11 @@ public boolean isMultiple() { return getContainerKind(null, null) != ContainerKind.SINGLE; } + /** + * @param repeatable if this matcher can be applied more then once in the same container of targets + * Note: even if false, it may be applied again to another container and to match EQUAL value. + * @return this to support fluent API + */ public AbstractParameterInfo setRepeatable(boolean repeatable) { this.repeatable = repeatable; return this; diff --git a/src/main/java/spoon/pattern/parameter/ParameterInfo.java b/src/main/java/spoon/pattern/parameter/ParameterInfo.java index 95c79e86650..e1b2bb39ed2 100644 --- a/src/main/java/spoon/pattern/parameter/ParameterInfo.java +++ b/src/main/java/spoon/pattern/parameter/ParameterInfo.java @@ -36,15 +36,22 @@ public interface ParameterInfo { /** * Matches `value` into `parameters` under the name/structure defined by this ParameterInfo. - * 1) checks that value matches with the {@link #matchCondition} + * 1) checks that value matches with optional internal rules of this {@link ParameterInfo} * 2) creates new copy of {@link ParameterValueProvider} which contains the new `value` and returns that copy * - * @param parameters - * @param value + * @param parameters the existing parameters + * @param value the new, to be stored value * @return copy of `parameters` with new value or existing `parameters` if value is already there or null if value doesn't fit into these parameters */ ParameterValueProvider addValueAs(ParameterValueProvider parameters, Object value); + /** + * Takes the value of parameter identified by this {@link ParameterInfo} from the `parameters` + * and adds that 0, 1 or more values into result (depending on type of result) + * @param factory the factory used to create new entities if conversion of value is needed before it can be added into `result` + * @param result the receiver of the result value. It defined required type of returned value and multiplicity of returned value + * @param parameters here are stored all the parameter values + */ void getValueAs(Factory factory, ResultHolder result, ParameterValueProvider parameters); /** From 98d18b8ad0cffb829e26df29ba475fc9375940d7 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Fri, 16 Mar 2018 20:16:33 +0100 Subject: [PATCH 029/131] review testTemplateMatchOfMultipleElements --- .../java/spoon/pattern/PatternBuilder.java | 14 +++-- .../spoon/test/template/CodeReplaceTest.java | 24 ++++++++- .../spoon/test/template/TemplateTest.java | 53 ++++++++++++++----- .../template/testclasses/ToBeMatched.java | 5 -- .../testclasses/replace/OldPattern.java | 2 +- 5 files changed, 72 insertions(+), 26 deletions(-) diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index 79ded968c57..913dc6196ae 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -78,17 +78,23 @@ public class PatternBuilder { public static PatternBuilder create(Factory factory, Class templateClass) { return create(factory, templateClass, null); } + + /** + * Creates a pattern builder. + * + * @param factory + * @param templateClass + * @param selector TODO explain what the selector is? + * @return + */ public static PatternBuilder create(Factory factory, Class templateClass, Consumer selector) { return create(factory.Type().get(templateClass), selector); } - public static PatternBuilder create(CtTypeReference templateTypeRef, Consumer selector) { - return create(templateTypeRef.getTypeDeclaration(), selector); - } - public static PatternBuilder create(CtType templateType) { return create(templateType, null); } + public static PatternBuilder create(CtType templateType, Consumer selector) { checkTemplateType(templateType); List templateModel; diff --git a/src/test/java/spoon/test/template/CodeReplaceTest.java b/src/test/java/spoon/test/template/CodeReplaceTest.java index c8843c2fc54..7ef65836e08 100644 --- a/src/test/java/spoon/test/template/CodeReplaceTest.java +++ b/src/test/java/spoon/test/template/CodeReplaceTest.java @@ -6,9 +6,11 @@ import spoon.OutputType; import spoon.SpoonModelBuilder; import spoon.pattern.Pattern; +import spoon.pattern.PatternBuilder; import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.declaration.CtClass; import spoon.reflect.factory.Factory; +import spoon.reflect.meta.ContainerKind; import spoon.reflect.visitor.DefaultJavaPrettyPrinter; import spoon.test.template.testclasses.replace.DPPSample1; import spoon.test.template.testclasses.replace.NewPattern; @@ -20,7 +22,25 @@ import java.io.File; public class CodeReplaceTest { - + /** + * @param factory a to be used factory + * @return a Pattern instance of this Pattern + */ + public static Pattern createPattern(Factory factory) { + return PatternBuilder + //Create a pattern from all statements of OldPattern_ParamsInNestedType#patternModel + .create(factory, OldPattern.class, model->model.setBodyOfMethod("patternModel")) + .configureParameters(pb->pb + .parametersByVariable("params", "item") + .parameter("statements").setContainerKind(ContainerKind.LIST) + ) + .configureAutomaticParameters() + .configureLiveStatements(ls -> ls.byVariableName("useStartKeyword")) + .build(); + } + + + @Test public void testMatchSample1() throws Exception { Factory f = ModelUtils.build( @@ -30,7 +50,7 @@ public void testMatchSample1() throws Exception { CtClass classDJPP = f.Class().get(DPPSample1.class); assertNotNull(classDJPP); assertFalse(classDJPP.isShadow()); - Pattern p = OldPattern.createPattern(f); + Pattern p = createPattern(f); class Context { int count = 0; } diff --git a/src/test/java/spoon/test/template/TemplateTest.java b/src/test/java/spoon/test/template/TemplateTest.java index 2aa06117747..c25d6a6a165 100644 --- a/src/test/java/spoon/test/template/TemplateTest.java +++ b/src/test/java/spoon/test/template/TemplateTest.java @@ -74,6 +74,7 @@ import java.io.Serializable; import java.rmi.Remote; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.IdentityHashMap; @@ -82,6 +83,7 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -1157,25 +1159,41 @@ public void substituteTypeAccessReference() throws Exception { @Test public void testTemplateMatchOfMultipleElements() throws Exception { - CtType type = ModelUtils.buildClass(ToBeMatched.class); - - List> literals1 = getFirstStmt(type, "match1", CtInvocation.class).getArguments(); - List> literals2 = getFirstStmt(type, "match2", CtInvocation.class).getArguments(); + CtType toBeMatchedtype = ModelUtils.buildClass(ToBeMatched.class); + + // getting the list of literals defined in method match1 + List> literals1 = getFirstStmt(toBeMatchedtype, "match1", CtInvocation.class).getArguments(); + List> literals2 = getFirstStmt(toBeMatchedtype, "match2", CtInvocation.class).getArguments(); assertEquals("a", literals1.get(0).getValue()); - - { //contract: matches one element + + Factory f = toBeMatchedtype.getFactory(); + + { //contract: matches one exact literal List> found = new ArrayList<>(); - ToBeMatched.patternOfStringLiterals(type.getFactory(), "a").forEachMatch(type, (match) -> { + + // creating a Pattern from ToBeMatched, with one or two pattern parameters? + // TODO: is the pattern the whole class or only the "a"? + // TODO: if it is "a" can we create a Pattern directly from a CtLiteral + spoon.pattern.Pattern p = PatternBuilder.create(f, ToBeMatched.class, ts -> ts.setTemplateModel( + Arrays.asList(new String[]{"a"}).stream().map(s -> f.createLiteral(s)).collect(Collectors.toList())) + ).build(); + + // todo: why do we have actually 0 here? (related to previous question) + //assertEquals (2, p.getParameterInfos().size()); + + // when we apply the pattern to the initial type, we find three instances of "a"? + p.forEachMatch(toBeMatchedtype, (match) -> { found.add(match.getMatchingElements()); }); + assertEquals(3, found.size()); - assertSequenceOn(literals1, 0, 1, found.get(0)); - assertSequenceOn(literals1, 6, 1, found.get(1)); - assertSequenceOn(literals2, 0, 1, found.get(2)); + assertEquals(literals1.get(0)/* first "a" in match1 */, found.get(0).get(0)); + assertEquals(literals1.get(6)/* 2nd "a" in match1 */, found.get(1).get(0)); + assertEquals(literals1.get(0)/* 1st "a" in match 2 */, found.get(2).get(0)); } { //contract: matches sequence of elements List> found = new ArrayList<>(); - ToBeMatched.patternOfStringLiterals(type.getFactory(), "a", "b", "c").forEachMatch(type, (match) -> { + patternOfStringLiterals(toBeMatchedtype.getFactory(), "a", "b", "c").forEachMatch(toBeMatchedtype, (match) -> { found.add(match.getMatchingElements()); }); assertEquals(2, found.size()); @@ -1184,7 +1202,7 @@ public void testTemplateMatchOfMultipleElements() throws Exception { } { //contract: matches sequence of elements not starting at the beginning List> found = new ArrayList<>(); - ToBeMatched.patternOfStringLiterals(type.getFactory(), "b", "c").forEachMatch(type, (match) -> { + patternOfStringLiterals(toBeMatchedtype.getFactory(), "b", "c").forEachMatch(toBeMatchedtype, (match) -> { found.add(match.getMatchingElements()); }); assertEquals(3, found.size()); @@ -1194,7 +1212,7 @@ public void testTemplateMatchOfMultipleElements() throws Exception { } { //contract: matches sequence of repeated elements, but match each element only once List> found = new ArrayList<>(); - ToBeMatched.patternOfStringLiterals(type.getFactory(), "d", "d").forEachMatch(type, (match) -> { + patternOfStringLiterals(toBeMatchedtype.getFactory(), "d", "d").forEachMatch(toBeMatchedtype, (match) -> { found.add(match.getMatchingElements()); }); assertEquals(2, found.size()); @@ -1202,7 +1220,14 @@ public void testTemplateMatchOfMultipleElements() throws Exception { assertSequenceOn(literals2, 8, 2, found.get(1)); } } - + + private static spoon.pattern.Pattern patternOfStringLiterals(Factory f, String... strs) { + return PatternBuilder.create(f, ToBeMatched.class, ts -> ts.setTemplateModel( + Arrays.asList(strs).stream().map(s -> f.createLiteral(s)).collect(Collectors.toList())) + ).build(); + } + + private void assertSequenceOn(List source, int expectedOffset, int expectedSize, List matches) { //check the number of matches assertEquals(expectedSize, matches.size()); diff --git a/src/test/java/spoon/test/template/testclasses/ToBeMatched.java b/src/test/java/spoon/test/template/testclasses/ToBeMatched.java index 0114219f1f1..2af3cb9dd7b 100644 --- a/src/test/java/spoon/test/template/testclasses/ToBeMatched.java +++ b/src/test/java/spoon/test/template/testclasses/ToBeMatched.java @@ -17,9 +17,4 @@ public void match2() { Arrays.asList("a", "b", "b", "b", "c", "c", "d", "d", "d", "d", "d"); } - public static Pattern patternOfStringLiterals(Factory f, String... strs) { - return PatternBuilder.create(f, ToBeMatched.class, ts -> ts.setTemplateModel( - Arrays.asList(strs).stream().map(s -> f.createLiteral(s)).collect(Collectors.toList())) - ).build(); - } } diff --git a/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java b/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java index 185b1b5c289..001363c2136 100644 --- a/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java +++ b/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java @@ -72,7 +72,7 @@ public static Pattern createPattern(Factory factory) { .configureLiveStatements(ls -> ls.byVariableName("useStartKeyword")) .build(); } - + private ElementPrinterHelper elementPrinterHelper; private TokenWriter printer; } From 3b3ca5e6b5f148fa8922c11a7b5bb066545c913e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Sat, 17 Mar 2018 08:53:10 +0100 Subject: [PATCH 030/131] Pattern can be created from spoon AST directly --- .../java/spoon/pattern/PatternBuilder.java | 34 ++++++++++++++----- .../spoon/test/template/TemplateTest.java | 27 +++++++-------- 2 files changed, 39 insertions(+), 22 deletions(-) diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index 913dc6196ae..afbd48c3a59 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -105,7 +105,19 @@ public static PatternBuilder create(CtType templateType, Consumer templateTypeRef, List patternModel) { + return new PatternBuilder(templateTypeRef, patternModel); } private final List patternModel; @@ -114,7 +126,6 @@ public static PatternBuilder create(CtType templateType, Consumer explicitNodes = Collections.newSetFromMap(new IdentityHashMap<>()); private CtTypeReference templateTypeRef; - private final Factory factory; private final Map parameterInfos = new HashMap<>(); // ModelNode pattern; CtQueryable patternQuery; @@ -150,13 +161,14 @@ private PatternBuilder(CtTypeReference templateTypeRef, List templ if (template == null) { throw new SpoonException("Cannot create a Pattern from an null model"); } - this.factory = templateTypeRef.getFactory(); this.valueConvertor = new ValueConvertorImpl(); patternNodes = ElementNode.create(template, patternElementToSubstRequests); - patternQuery = new PatternBuilder.PatternQuery(factory.Query(), patternModel); - configureParameters(pb -> { - pb.parameter(TARGET_TYPE).byType(templateTypeRef).setValueType(CtTypeReference.class); - }); + patternQuery = new PatternBuilder.PatternQuery(getFactory().Query(), patternModel); + if (templateTypeRef != null) { + configureParameters(pb -> { + pb.parameter(TARGET_TYPE).byType(templateTypeRef).setValueType(CtTypeReference.class); + }); + } } /** @@ -818,7 +830,13 @@ public boolean hasParameterInfo(String parameterName) { } protected Factory getFactory() { - return factory; + if (templateTypeRef != null) { + return templateTypeRef.getFactory(); + } + if (patternModel.size() > 0) { + return patternModel.get(0).getFactory(); + } + throw new SpoonException("PatternBuilder has no CtElement to provide a Factory"); } private static void checkTemplateType(CtType type) { diff --git a/src/test/java/spoon/test/template/TemplateTest.java b/src/test/java/spoon/test/template/TemplateTest.java index c25d6a6a165..9ba6b557b10 100644 --- a/src/test/java/spoon/test/template/TemplateTest.java +++ b/src/test/java/spoon/test/template/TemplateTest.java @@ -75,6 +75,7 @@ import java.rmi.Remote; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.IdentityHashMap; @@ -1171,25 +1172,24 @@ public void testTemplateMatchOfMultipleElements() throws Exception { { //contract: matches one exact literal List> found = new ArrayList<>(); - // creating a Pattern from ToBeMatched, with one or two pattern parameters? - // TODO: is the pattern the whole class or only the "a"? - // TODO: if it is "a" can we create a Pattern directly from a CtLiteral - spoon.pattern.Pattern p = PatternBuilder.create(f, ToBeMatched.class, ts -> ts.setTemplateModel( - Arrays.asList(new String[]{"a"}).stream().map(s -> f.createLiteral(s)).collect(Collectors.toList())) - ).build(); + // creating a Pattern from a Literal, with zero pattern parameters + // The pattern model consists of one CtLIteral only + // there is not needed any type reference, because CtLiteral has no reference to a type where it is defined + spoon.pattern.Pattern p = PatternBuilder.create(null, Collections.singletonList(f.createLiteral("a"))).build(); - // todo: why do we have actually 0 here? (related to previous question) - //assertEquals (2, p.getParameterInfos().size()); + //The pattern has no parameters. There is just one constant CtLiteral + assertEquals (0, p.getParameterInfos().size()); - // when we apply the pattern to the initial type, we find three instances of "a"? + // when we match the pattern agains AST of toBeMatchedtype, we find three instances of "a", + //because there are 3 instances of CtLiteral "a" in toBeMatchedtype p.forEachMatch(toBeMatchedtype, (match) -> { found.add(match.getMatchingElements()); }); assertEquals(3, found.size()); - assertEquals(literals1.get(0)/* first "a" in match1 */, found.get(0).get(0)); - assertEquals(literals1.get(6)/* 2nd "a" in match1 */, found.get(1).get(0)); - assertEquals(literals1.get(0)/* 1st "a" in match 2 */, found.get(2).get(0)); + assertSame(literals1.get(0)/* first "a" in match1 */, found.get(0).get(0)); assertEquals(1, found.get(0).size()); + assertSame(literals1.get(6)/* 2nd "a" in match1 */, found.get(1).get(0)); assertEquals(1, found.get(1).size()); + assertSame(literals2.get(0)/* 1st "a" in match 2 */, found.get(2).get(0)); assertEquals(1, found.get(2).size()); } { //contract: matches sequence of elements List> found = new ArrayList<>(); @@ -1222,8 +1222,7 @@ public void testTemplateMatchOfMultipleElements() throws Exception { } private static spoon.pattern.Pattern patternOfStringLiterals(Factory f, String... strs) { - return PatternBuilder.create(f, ToBeMatched.class, ts -> ts.setTemplateModel( - Arrays.asList(strs).stream().map(s -> f.createLiteral(s)).collect(Collectors.toList())) + return PatternBuilder.create(null, Arrays.asList(strs).stream().map(s -> f.createLiteral(s)).collect(Collectors.toList()) ).build(); } From fc7050afae00090c42dab96026e85f76844a2479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Sat, 17 Mar 2018 09:14:02 +0100 Subject: [PATCH 031/131] added explanation of PatternBuilder selector --- src/main/java/spoon/pattern/PatternBuilder.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index afbd48c3a59..f0721196c49 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -84,8 +84,8 @@ public static PatternBuilder create(Factory factory, Class templateClass) { * * @param factory * @param templateClass - * @param selector TODO explain what the selector is? - * @return + * @param selector the code which selects part of templateClass AST, which has to be used as template model + * @return new instance of {@link PatternBuilder} */ public static PatternBuilder create(Factory factory, Class templateClass, Consumer selector) { return create(factory.Type().get(templateClass), selector); From bb50912c6850c0c4dea57a93a2241f789475cc31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Sat, 17 Mar 2018 10:46:47 +0100 Subject: [PATCH 032/131] some javadoc added to TemplateModelBuilder --- .../java/spoon/pattern/PatternBuilder.java | 42 +++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index f0721196c49..eee95fd4123 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -367,20 +367,41 @@ public PatternBuilder configureAutomaticParameters() { return this; } + /** + * Builder which allows to define which part of AST of template {@link CtType} + * has to be used as a model of the {@link Pattern} + */ public static class TemplateModelBuilder { - + /** + * The original type, which contains the AST of pattern model + */ private final CtType templateType; + /** + * optional clone of templateType. It is created when AST of CtType has to be modified + * before it can become a model of {@link Pattern} + */ private CtType clonedTemplateType; + /** + * holds the built pattern model + */ private List templateModel = null; - public TemplateModelBuilder(CtType templateTemplate) { + TemplateModelBuilder(CtType templateTemplate) { this.templateType = templateTemplate; } + /** + * @return origin {@link CtType}, which is the source of the pattern model + */ public CtType getTemplateType() { return templateType; } + /** + * Returns clone of the templateType. + * The clone is done only once. Later calls returns cached clone. + * @return + */ private CtType getClonedTemplateType() { if (clonedTemplateType == null) { clonedTemplateType = templateType.clone(); @@ -409,6 +430,11 @@ public TemplateModelBuilder setTypeMember(Filter filter) { return this; } + /** + * removes all annotations of type defined by `classes` from the clone of the source {@link CtType} + * @param classes list of classes which defines types of to be removed annotations + * @return this to support fluent API + */ public TemplateModelBuilder removeTag(Class... classes) { List elements = getClonedTemplateModel(); for (Class class1 : classes) { @@ -490,7 +516,7 @@ private T getOneByFilter(Filter filter) { return elements.get(0); } /** - * @param filter whose matches will be removed from the template + * @param filter whose matches will be removed from the template model */ public TemplateModelBuilder removeTypeMembers(Filter filter) { for (CtTypeMember ctTypeMember : new ArrayList<>(getClonedTemplateType().getTypeMembers())) { @@ -576,13 +602,23 @@ public TemplateModelBuilder keepSuperInterfaces(Filter> filte return this; } + /** + * @return a List of {@link CtElement}s, which has to be used as pattern model + */ public List getTemplateModel() { return templateModel; } + /** + * @param template a {@link CtElement}, which has to be used as pattern model + */ public void setTemplateModel(CtElement template) { this.templateModel = Collections.singletonList(template); } + + /** + * @param template a List of {@link CtElement}s, which has to be used as pattern model + */ @SuppressWarnings({ "unchecked", "rawtypes" }) public void setTemplateModel(List template) { this.templateModel = (List) template; From eb84831a1fc2f4843b4d5a115133f3fb6bd7f982 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Sat, 17 Mar 2018 11:29:15 +0100 Subject: [PATCH 033/131] up --- src/main/java/spoon/pattern/PatternBuilder.java | 4 ++++ src/test/java/spoon/test/template/TemplateTest.java | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index eee95fd4123..397713e4f0e 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -120,6 +120,10 @@ public static PatternBuilder create(CtTypeReference templateTypeRef, List patternModel; private final ListOfNodes patternNodes; private final Map patternElementToSubstRequests = new IdentityHashMap<>(); diff --git a/src/test/java/spoon/test/template/TemplateTest.java b/src/test/java/spoon/test/template/TemplateTest.java index 9ba6b557b10..2bed88db178 100644 --- a/src/test/java/spoon/test/template/TemplateTest.java +++ b/src/test/java/spoon/test/template/TemplateTest.java @@ -1175,7 +1175,7 @@ public void testTemplateMatchOfMultipleElements() throws Exception { // creating a Pattern from a Literal, with zero pattern parameters // The pattern model consists of one CtLIteral only // there is not needed any type reference, because CtLiteral has no reference to a type where it is defined - spoon.pattern.Pattern p = PatternBuilder.create(null, Collections.singletonList(f.createLiteral("a"))).build(); + spoon.pattern.Pattern p = PatternBuilder.create(f.createLiteral("a")).build(); //The pattern has no parameters. There is just one constant CtLiteral assertEquals (0, p.getParameterInfos().size()); From 7c35eb358f2551e7e674400319466c9827c6c428 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Sat, 17 Mar 2018 12:03:33 +0100 Subject: [PATCH 034/131] javadoc of Match --- src/main/java/spoon/pattern/Pattern.java | 2 +- .../java/spoon/pattern/matcher/Match.java | 32 ++++++++++++++++--- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/main/java/spoon/pattern/Pattern.java b/src/main/java/spoon/pattern/Pattern.java index 95e7c91b39d..895810733e2 100644 --- a/src/main/java/spoon/pattern/Pattern.java +++ b/src/main/java/spoon/pattern/Pattern.java @@ -221,7 +221,7 @@ public void apply(Object input, CtConsumer outputConsumer) { /** * Finds all target program sub-trees that correspond to a template - * and calls consumer.accept(matchingElement, parameterValues) + * and calls consumer.accept(Match) * @param input the root of to be searched AST * @param consumer the receiver of matches */ diff --git a/src/main/java/spoon/pattern/matcher/Match.java b/src/main/java/spoon/pattern/matcher/Match.java index 3595d5a95c9..cd8d37dc634 100644 --- a/src/main/java/spoon/pattern/matcher/Match.java +++ b/src/main/java/spoon/pattern/matcher/Match.java @@ -20,11 +20,12 @@ import java.util.Map; import spoon.SpoonException; +import spoon.pattern.Pattern; import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.declaration.CtElement; /** - * Represents a Match of TemplateMatcher + * Represents a single match of {@link Pattern} */ public class Match { private final List matchingElements; @@ -34,10 +35,19 @@ public class Match { this.parameters = parameters; this.matchingElements = matches; } - + /** + * @return {@link List} of elements, which match to the Pattern. + * Use {@link #getMatchingElement()} if the {@link Pattern} matches single root element. + * But when {@link Pattern} contains sequence of root elements, then this is the right way how to get them all + */ public List getMatchingElements() { return getMatchingElements(CtElement.class); } + /** + * Same like {@link #getMatchingElement()} but additionally it checks that each matching element is instance of `clazz` + * @param clazz the required type of all elements. + * @return a {@link List} typed to `clazz` or throws {@link SpoonException} if Pattern matched different elements + */ @SuppressWarnings("unchecked") public List getMatchingElements(Class clazz) { for (Object object : matchingElements) { @@ -47,10 +57,19 @@ public List getMatchingElements(Class clazz) { } return (List) matchingElements; } + + /** + * @return a matching element of a {@link Pattern} + * It fails if {@link Pattern} is designed to match sequence of elements. In such case use {@link #getMatchingElements()} + */ public CtElement getMatchingElement() { return getMatchingElement(CtElement.class, true); } - + /** + * Same like {@link #getMatchingElement()}, but checks that matching element is expected class and casts returned value to that type + * @param clazz required type + * @return matched element cast to `clazz` + */ public T getMatchingElement(Class clazz) { return getMatchingElement(clazz, true); } @@ -94,10 +113,15 @@ public void replaceMatchesBy(List newElements) { last.replace(newElements); } + /** + * @return {@link ParameterValueProvider} with values of {@link Pattern} parameters, which fits to current match + */ public ParameterValueProvider getParameters() { return parameters; } - + /** + * @return {@link Map} with values of {@link Pattern} parameters, which fits to current match + */ public Map getParametersMap() { return parameters.asMap(); } From 32402ae8d8c7572257f581846c95aaf05189b354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Sat, 17 Mar 2018 12:22:05 +0100 Subject: [PATCH 035/131] check style --- src/main/java/spoon/pattern/PatternBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index 397713e4f0e..1f37bdf7fac 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -121,7 +121,7 @@ public static PatternBuilder create(CtTypeReference templateTypeRef, List patternModel; From 4dca3895e3f8db940aac902778598f494ec4fe3a Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Sat, 17 Mar 2018 13:43:08 +0100 Subject: [PATCH 036/131] up --- src/main/java/spoon/pattern/Pattern.java | 8 ++++- .../spoon/test/template/TemplateTest.java | 31 ++++++++++++++----- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/main/java/spoon/pattern/Pattern.java b/src/main/java/spoon/pattern/Pattern.java index 895810733e2..14133483941 100644 --- a/src/main/java/spoon/pattern/Pattern.java +++ b/src/main/java/spoon/pattern/Pattern.java @@ -42,7 +42,13 @@ import spoon.reflect.visitor.chain.CtConsumer; /** - * Represents a pattern. It means an AST model, where some parts of that model are substituted by pattern parameters. + * Represents a pattern for matching or transformation. It means an AST model, where some parts of that model are substituted by pattern parameters. + * + * Differences with {@link spoon.template.TemplateMatcher}: + * - it can match sequences of elements + * + * Instances can created with {@link PatternBuilder}. + * * The {@link Pattern} can be used to process these two "itself opposite" operations *
            *
          • Generation of code from pattern. It means (Pattern) + (pattern parameters) => (copy of pattern where parameters are replaced by parameter values)
          • diff --git a/src/test/java/spoon/test/template/TemplateTest.java b/src/test/java/spoon/test/template/TemplateTest.java index 2bed88db178..c887d42f3c4 100644 --- a/src/test/java/spoon/test/template/TemplateTest.java +++ b/src/test/java/spoon/test/template/TemplateTest.java @@ -1170,12 +1170,12 @@ public void testTemplateMatchOfMultipleElements() throws Exception { Factory f = toBeMatchedtype.getFactory(); { //contract: matches one exact literal - List> found = new ArrayList<>(); + List found = new ArrayList<>(); // creating a Pattern from a Literal, with zero pattern parameters // The pattern model consists of one CtLIteral only // there is not needed any type reference, because CtLiteral has no reference to a type where it is defined - spoon.pattern.Pattern p = PatternBuilder.create(f.createLiteral("a")).build(); + spoon.pattern.Pattern p = PatternBuilder.create(null, f.createLiteral("a")).build(); //The pattern has no parameters. There is just one constant CtLiteral assertEquals (0, p.getParameterInfos().size()); @@ -1183,20 +1183,33 @@ public void testTemplateMatchOfMultipleElements() throws Exception { // when we match the pattern agains AST of toBeMatchedtype, we find three instances of "a", //because there are 3 instances of CtLiteral "a" in toBeMatchedtype p.forEachMatch(toBeMatchedtype, (match) -> { - found.add(match.getMatchingElements()); + found.add(match.getMatchingElement()); }); assertEquals(3, found.size()); - assertSame(literals1.get(0)/* first "a" in match1 */, found.get(0).get(0)); assertEquals(1, found.get(0).size()); - assertSame(literals1.get(6)/* 2nd "a" in match1 */, found.get(1).get(0)); assertEquals(1, found.get(1).size()); - assertSame(literals2.get(0)/* 1st "a" in match 2 */, found.get(2).get(0)); assertEquals(1, found.get(2).size()); + assertSame(literals1.get(0)/* first "a" in match1 */, found.get(0)); + assertSame(literals1.get(6)/* 2nd "a" in match1 */, found.get(1)); + assertSame(literals2.get(0)/* 1st "a" in match 2 */, found.get(2)); } { //contract: matches sequence of elements List> found = new ArrayList<>(); - patternOfStringLiterals(toBeMatchedtype.getFactory(), "a", "b", "c").forEachMatch(toBeMatchedtype, (match) -> { + // now we match a sequence of "a", "b", "c" + spoon.pattern.Pattern pattern = patternOfStringLiterals(toBeMatchedtype.getFactory(), "a", "b", "c"); + pattern.forEachMatch(toBeMatchedtype, (match) -> { found.add(match.getMatchingElements()); }); assertEquals(2, found.size()); + + assertEquals(3, found.get(1).size()); + // it starts with the first "a" in the match1 + assertEquals("\"a\"", found.get(0).get(0).toString()); + assertEquals(17, found.get(0).get(0).getPosition().getColumn()); + assertEquals("\"b\"", found.get(0).get(1).toString()); + assertEquals(22, found.get(0).get(1).getPosition().getColumn()); + assertEquals("\"c\"", found.get(0).get(2).toString()); + assertEquals(27, found.get(0).get(2).getPosition().getColumn()); + + // more generic asserts assertSequenceOn(literals1, 0, 3, found.get(0)); assertSequenceOn(literals1, 6, 3, found.get(1)); } @@ -1205,6 +1218,7 @@ public void testTemplateMatchOfMultipleElements() throws Exception { patternOfStringLiterals(toBeMatchedtype.getFactory(), "b", "c").forEachMatch(toBeMatchedtype, (match) -> { found.add(match.getMatchingElements()); }); + // we have three times a sequence ["b", "c"] assertEquals(3, found.size()); assertSequenceOn(literals1, 1, 2, found.get(0)); assertSequenceOn(literals1, 7, 2, found.get(1)); @@ -1212,9 +1226,12 @@ public void testTemplateMatchOfMultipleElements() throws Exception { } { //contract: matches sequence of repeated elements, but match each element only once List> found = new ArrayList<>(); + // we search for ["d", "d"] patternOfStringLiterals(toBeMatchedtype.getFactory(), "d", "d").forEachMatch(toBeMatchedtype, (match) -> { found.add(match.getMatchingElements()); }); + // in ToBeMatched there is ["d", "d", "d", "d", "d] + // so there are only two sequences, starting at first and third "d" assertEquals(2, found.size()); assertSequenceOn(literals2, 6, 2, found.get(0)); assertSequenceOn(literals2, 8, 2, found.get(1)); From 26c8cb0acfc3db4cad81dbe3ef93cd56d85176cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Sat, 17 Mar 2018 15:07:58 +0100 Subject: [PATCH 037/131] added comment --- src/test/java/spoon/test/template/TemplateTest.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/test/java/spoon/test/template/TemplateTest.java b/src/test/java/spoon/test/template/TemplateTest.java index c887d42f3c4..854a94e47c5 100644 --- a/src/test/java/spoon/test/template/TemplateTest.java +++ b/src/test/java/spoon/test/template/TemplateTest.java @@ -670,9 +670,15 @@ public void testExtensionDecoupledSubstitutionVisitor() throws Exception { params.put("_classname_", factory.Code().createLiteral(aTargetType.getSimpleName())); params.put("_methodName_", factory.Code().createLiteral(toBeLoggedMethod.getSimpleName())); params.put("_block_", toBeLoggedMethod.getBody()); - final List aMethods = PatternBuilder.create(launcher.getFactory(), LoggerModel.class, model -> model.setTypeMember("block")) + //create a patter from the LoggerModel#block + spoon.pattern.Pattern pattern = PatternBuilder.create(launcher.getFactory(), LoggerModel.class, + //type member named "block" of LoggerModel is used as pattern model of this pattern + model -> model.setTypeMember("block")) + //all the variable references which are declared out of type member "block" are automatically considered + //as pattern parameters .configureAutomaticParameters() - .build().applyToType(aTargetType, CtMethod.class, params); + .build(); + final List aMethods = pattern.applyToType(aTargetType, CtMethod.class, params); assertEquals(1, aMethods.size()); final CtMethod aMethod = aMethods.get(0); assertTrue(aMethod.getBody().getStatement(0) instanceof CtTry); From 3fa34b3aa39a62dd3831367b917ce831f79a2539 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Sun, 18 Mar 2018 09:02:41 +0100 Subject: [PATCH 038/131] remove default implementation to force children define behavior --- .../java/spoon/pattern/node/ConstantNode.java | 12 ++++++++++++ .../java/spoon/pattern/node/ElementNode.java | 12 ++++++++++++ .../java/spoon/pattern/node/MapEntryNode.java | 18 ++++++++++++++++++ .../spoon/pattern/node/RepeatableMatcher.java | 9 +++------ .../java/spoon/pattern/node/StringNode.java | 12 ++++++++++++ 5 files changed, 57 insertions(+), 6 deletions(-) diff --git a/src/main/java/spoon/pattern/node/ConstantNode.java b/src/main/java/spoon/pattern/node/ConstantNode.java index 53eb6cb9dc7..0814585aa41 100644 --- a/src/main/java/spoon/pattern/node/ConstantNode.java +++ b/src/main/java/spoon/pattern/node/ConstantNode.java @@ -20,6 +20,7 @@ import spoon.pattern.Generator; import spoon.pattern.ResultHolder; +import spoon.pattern.matcher.Quantifier; import spoon.pattern.parameter.ParameterInfo; import spoon.pattern.parameter.ParameterValueProvider; @@ -71,5 +72,16 @@ public ParameterValueProvider matchTarget(Object target, ParameterValueProvider public String toString() { return String.valueOf(template); } + + @Override + public Quantifier getMatchingStrategy() { + return Quantifier.POSSESSIVE; + } + + @Override + public boolean isTryNextMatch(ParameterValueProvider parameters) { + //it always matches only once + return false; + } } diff --git a/src/main/java/spoon/pattern/node/ElementNode.java b/src/main/java/spoon/pattern/node/ElementNode.java index 2eb0f834132..8a941a0441a 100644 --- a/src/main/java/spoon/pattern/node/ElementNode.java +++ b/src/main/java/spoon/pattern/node/ElementNode.java @@ -32,6 +32,7 @@ import spoon.pattern.Generator; import spoon.pattern.ResultHolder; import spoon.pattern.matcher.Matchers; +import spoon.pattern.matcher.Quantifier; import spoon.pattern.matcher.TobeMatched; import spoon.pattern.parameter.ParameterInfo; import spoon.pattern.parameter.ParameterValueProvider; @@ -409,4 +410,15 @@ public Metamodel.Type getElementType() { public void setElementType(Metamodel.Type elementType) { this.elementType = elementType; } + + @Override + public Quantifier getMatchingStrategy() { + return Quantifier.POSSESSIVE; + } + + @Override + public boolean isTryNextMatch(ParameterValueProvider parameters) { + //it always matches only once + return false; + } } diff --git a/src/main/java/spoon/pattern/node/MapEntryNode.java b/src/main/java/spoon/pattern/node/MapEntryNode.java index 9ead2666297..1c4ff34f8e6 100644 --- a/src/main/java/spoon/pattern/node/MapEntryNode.java +++ b/src/main/java/spoon/pattern/node/MapEntryNode.java @@ -22,6 +22,7 @@ import spoon.SpoonException; import spoon.pattern.Generator; import spoon.pattern.ResultHolder; +import spoon.pattern.matcher.Quantifier; import spoon.pattern.matcher.TobeMatched; import spoon.pattern.parameter.ParameterInfo; import spoon.pattern.parameter.ParameterValueProvider; @@ -123,4 +124,21 @@ public ParameterValueProvider matchTarget(Object target, ParameterValueProvider } throw new SpoonException("Unexpected target type " + target.getClass().getName()); } + + @Override + public Quantifier getMatchingStrategy() { + if (key instanceof ParameterNode) { + return ((ParameterNode) key).getMatchingStrategy(); + } + return Quantifier.POSSESSIVE; + } + + @Override + public boolean isTryNextMatch(ParameterValueProvider parameters) { + if (key instanceof ParameterNode) { + return ((ParameterNode) key).isTryNextMatch(parameters); + } + //it is not a parameterized node, so it matches only once + return false; + } } diff --git a/src/main/java/spoon/pattern/node/RepeatableMatcher.java b/src/main/java/spoon/pattern/node/RepeatableMatcher.java index 2b3a8740649..54184870ff1 100644 --- a/src/main/java/spoon/pattern/node/RepeatableMatcher.java +++ b/src/main/java/spoon/pattern/node/RepeatableMatcher.java @@ -29,9 +29,8 @@ public interface RepeatableMatcher extends RootNode { * then returned {@link Quantifier} defines how resolve this conflict * @return {@link Quantifier} */ - default Quantifier getMatchingStrategy() { - return Quantifier.POSSESSIVE; - } + Quantifier getMatchingStrategy(); + /** * @return true if this matcher can be applied more then once in the same container of targets * Note: even if false, it may be applied again to another container and to match EQUAL value @@ -51,7 +50,5 @@ default boolean isMandatory(ParameterValueProvider parameters) { * @param parameters matching parameters * @return true if this ValueResolver should be processed again to match next target in the state defined by current `parameters`. */ - default boolean isTryNextMatch(ParameterValueProvider parameters) { - return false; - } + boolean isTryNextMatch(ParameterValueProvider parameters); } diff --git a/src/main/java/spoon/pattern/node/StringNode.java b/src/main/java/spoon/pattern/node/StringNode.java index b43071e2a4b..c1fbaa59701 100644 --- a/src/main/java/spoon/pattern/node/StringNode.java +++ b/src/main/java/spoon/pattern/node/StringNode.java @@ -30,6 +30,7 @@ import spoon.pattern.Generator; import spoon.pattern.ResultHolder; import spoon.pattern.ResultHolder.Single; +import spoon.pattern.matcher.Quantifier; import spoon.pattern.parameter.ParameterInfo; import spoon.pattern.parameter.ParameterValueProvider; @@ -269,4 +270,15 @@ public static StringNode setReplaceMarker(RootNode targetNode, String replaceMar stringNode.setReplaceMarker(replaceMarker, param); return stringNode; } + + @Override + public Quantifier getMatchingStrategy() { + return Quantifier.POSSESSIVE; + } + + @Override + public boolean isTryNextMatch(ParameterValueProvider parameters) { + //it always matches only once + return false; + } } From 9d1f9c4f40969a1d2f92e58524301f96e263ac0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Sun, 18 Mar 2018 10:10:12 +0100 Subject: [PATCH 039/131] cleaning and test of matching in Set --- src/main/java/spoon/pattern/ResultHolder.java | 20 +---- .../pattern/matcher/ChainOfMatchersImpl.java | 8 +- .../test/template/TemplateMatcherTest.java | 75 ++++++++++++++++++- .../testclasses/match/MatchThrowables.java | 28 +++++++ 4 files changed, 106 insertions(+), 25 deletions(-) create mode 100644 src/test/java/spoon/test/template/testclasses/match/MatchThrowables.java diff --git a/src/main/java/spoon/pattern/ResultHolder.java b/src/main/java/spoon/pattern/ResultHolder.java index 15ddbba60f8..7561fa50988 100644 --- a/src/main/java/spoon/pattern/ResultHolder.java +++ b/src/main/java/spoon/pattern/ResultHolder.java @@ -28,7 +28,7 @@ * Container for single or multiple values of required type */ public abstract class ResultHolder { - private Class requiredClass; + private final Class requiredClass; public ResultHolder(Class requiredClass) { this.requiredClass = requiredClass; @@ -41,16 +41,6 @@ public Class getRequiredClass() { return requiredClass; } - /** - * @param requiredClass only the values of this class are acceptable by this result holder - * @return this to support fluent API - */ - @SuppressWarnings({ "rawtypes", "unchecked" }) - public ResultHolder setRequiredClass(Class requiredClass) { - this.requiredClass = (Class) requiredClass; - return (ResultHolder) this; - } - /** * @return true if it accepts 0, 1 or more values. false if it accepts exactly one value. If none, then value is null */ @@ -111,10 +101,6 @@ public void mapEachResult(Function consumer) { public List getResults() { return result == null ? Collections.emptyList() : Collections.singletonList(result); } - - public ResultHolder.Single setRequiredClass(Class requiredClass) { - return (ResultHolder.Single) super.setRequiredClass(requiredClass); - } } /** @@ -154,9 +140,5 @@ public void mapEachResult(Function consumer) { iter.set(consumer.apply(iter.next())); } } - - public ResultHolder.Multiple setRequiredClass(Class requiredClass) { - return (ResultHolder.Multiple) super.setRequiredClass(requiredClass); - } } } diff --git a/src/main/java/spoon/pattern/matcher/ChainOfMatchersImpl.java b/src/main/java/spoon/pattern/matcher/ChainOfMatchersImpl.java index c26bd6c4ec0..9747deb4185 100644 --- a/src/main/java/spoon/pattern/matcher/ChainOfMatchersImpl.java +++ b/src/main/java/spoon/pattern/matcher/ChainOfMatchersImpl.java @@ -28,12 +28,14 @@ public class ChainOfMatchersImpl implements Matchers { private final RootNode firstMatcher; private final Matchers next; + /** + * @param items + * @param next + * @return new {@link ChainOfMatchersImpl} which starts with items nodes and continues with `next` {@link Matchers} + */ public static Matchers create(List items, Matchers next) { return createFromList(next, items, 0); } - public static Matchers create(RootNode firstNode, Matchers next) { - return new ChainOfMatchersImpl(firstNode, next); - } private static Matchers createFromList(Matchers next, List items, int idx) { RootNode matcher; while (true) { diff --git a/src/test/java/spoon/test/template/TemplateMatcherTest.java b/src/test/java/spoon/test/template/TemplateMatcherTest.java index f550a880393..e89510375bb 100644 --- a/src/test/java/spoon/test/template/TemplateMatcherTest.java +++ b/src/test/java/spoon/test/template/TemplateMatcherTest.java @@ -14,16 +14,18 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; -import org.junit.Ignore; import org.junit.Test; +import spoon.pattern.ConflictResolutionMode; import spoon.pattern.Pattern; +import spoon.pattern.PatternBuilder; import spoon.pattern.matcher.Match; import spoon.pattern.matcher.Quantifier; import spoon.pattern.parameter.ParameterValueProvider; -import spoon.pattern.parameter.UnmodifiableParameterValueProvider; +import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtInvocation; import spoon.reflect.code.CtLiteral; import spoon.reflect.code.CtStatement; @@ -32,8 +34,11 @@ import spoon.reflect.declaration.CtMethod; import spoon.reflect.declaration.CtType; import spoon.reflect.declaration.ModifierKind; +import spoon.reflect.factory.Factory; import spoon.reflect.meta.ContainerKind; import spoon.reflect.path.CtRole; +import spoon.reflect.reference.CtTypeReference; +import spoon.reflect.visitor.filter.TypeFilter; import spoon.test.template.testclasses.match.MatchForEach; import spoon.test.template.testclasses.match.MatchForEach2; import spoon.test.template.testclasses.match.MatchIfElse; @@ -42,6 +47,7 @@ import spoon.test.template.testclasses.match.MatchMultiple; import spoon.test.template.testclasses.match.MatchMultiple2; import spoon.test.template.testclasses.match.MatchMultiple3; +import spoon.test.template.testclasses.match.MatchThrowables; import spoon.test.template.testclasses.match.MatchWithParameterCondition; import spoon.test.template.testclasses.match.MatchWithParameterType; import spoon.testing.utils.ModelUtils; @@ -834,7 +840,6 @@ public void testMatchOfMapKeySubstring() throws Exception { //match all methods with arbitrary name, and Annotation Test modifiers, parameters, but with empty body and return type void Pattern pattern = MatchMap.createMatchKeyPattern(ctClass.getFactory()); List matches = pattern.getMatches(ctClass); - String str = pattern.toString(); assertEquals(2, matches.size()); { Match match = matches.get(0); @@ -857,6 +862,70 @@ public void testMatchOfMapKeySubstring() throws Exception { } } } + + @Test + public void testMatchInSet() throws Exception { + //contract: match elements in container of type Set - e.g method throwables + CtType ctClass = ModelUtils.buildClass(MatchThrowables.class); + Factory f = ctClass.getFactory(); + Pattern pattern = PatternBuilder.create(f, MatchThrowables.class, tmb -> tmb.setTypeMember("matcher1")) + .configureParameters(pb -> { + pb.parameter("otherThrowables") + //add matcher for other arbitrary throwables + .setConflictResolutionMode(ConflictResolutionMode.APPEND) + .setContainerKind(ContainerKind.SET) + .setMinOccurence(0) + .attributeOfElementByFilter(CtRole.THROWN, new TypeFilter(CtMethod.class)); + }) + .configureParameters(pb -> { + //define other parameters too to match all kinds of methods + pb.parameter("modifiers").attributeOfElementByFilter(CtRole.MODIFIER, new TypeFilter(CtMethod.class)); + pb.parameter("methodName").byString("matcher1"); + pb.parameter("parameters").attributeOfElementByFilter(CtRole.PARAMETER, new TypeFilter(CtMethod.class)); + pb.parameter("statements").attributeOfElementByFilter(CtRole.STATEMENT, new TypeFilter(CtBlock.class)); + }) + .build(); + String str = pattern.toString(); + List matches = pattern.getMatches(ctClass); + assertEquals(4, matches.size()); + { + Match match = matches.get(0); + assertEquals(1, match.getMatchingElements().size()); + assertEquals("matcher1", match.getMatchingElement(CtMethod.class).getSimpleName()); + assertEquals(new HashSet(Arrays.asList( + "modifiers","methodName","parameters","statements")), match.getParametersMap().keySet()); + } + { + Match match = matches.get(1); + assertEquals(1, match.getMatchingElements().size()); + assertEquals("sample2", match.getMatchingElement(CtMethod.class).getSimpleName()); + assertEquals(new HashSet(Arrays.asList( + "otherThrowables", "modifiers","methodName","parameters","statements")), match.getParametersMap().keySet()); + assertEquals(new HashSet(Arrays.asList( + "java.lang.UnsupportedOperationException", + "java.lang.IllegalArgumentException")), + ((Set>) match.getParameters().getValue("otherThrowables")) + .stream().map(e->e.toString()).collect(Collectors.toSet())); + } + { + Match match = matches.get(2); + assertEquals(1, match.getMatchingElements().size()); + assertEquals("sample3", match.getMatchingElement(CtMethod.class).getSimpleName()); + assertEquals(new HashSet(Arrays.asList( + "otherThrowables", "modifiers","methodName","parameters","statements")), match.getParametersMap().keySet()); + assertEquals(new HashSet(Arrays.asList( + "java.lang.IllegalArgumentException")), + ((Set>) match.getParameters().getValue("otherThrowables")) + .stream().map(e->e.toString()).collect(Collectors.toSet())); + } + { + Match match = matches.get(3); + assertEquals(1, match.getMatchingElements().size()); + assertEquals("sample4", match.getMatchingElement(CtMethod.class).getSimpleName()); + assertEquals(new HashSet(Arrays.asList( + "modifiers","methodName","parameters","statements")), match.getParametersMap().keySet()); + } + } private List listToListOfStrings(List list) { if (list == null) { diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchThrowables.java b/src/test/java/spoon/test/template/testclasses/match/MatchThrowables.java new file mode 100644 index 00000000000..a05db4b50f1 --- /dev/null +++ b/src/test/java/spoon/test/template/testclasses/match/MatchThrowables.java @@ -0,0 +1,28 @@ +package spoon.test.template.testclasses.match; + +import java.io.FileNotFoundException; +import java.io.IOException; + +public class MatchThrowables { + + public void matcher1() throws IOException, FileNotFoundException { + } + + public static void sample1() { + } + + void sample2(int a, MatchThrowables me) throws FileNotFoundException, IllegalArgumentException, IOException, UnsupportedOperationException { + } + + void sample3(int a, MatchThrowables me) throws FileNotFoundException, IllegalArgumentException, IOException { + } + + private void sample4() throws IOException, FileNotFoundException { + this.getClass(); + System.out.println(); + } + + int noMatchBecauseReturnsInt() throws IOException, Exception { + return 0; + } +} From 2e392db6057f38b5de61b652431526c277a1973b Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Tue, 20 Mar 2018 20:00:52 +0100 Subject: [PATCH 040/131] Documentation updated - 1st part ... TODO --- doc/template_definition.md | 49 +++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/doc/template_definition.md b/doc/template_definition.md index 6992fb42af3..dde22799352 100644 --- a/doc/template_definition.md +++ b/doc/template_definition.md @@ -4,9 +4,52 @@ tags: [template] keywords: template, definition, code, java --- -Spoon provides developers a way of writing code transformations called -**code templates**. Those templates are statically type-checked, in -order to ensure statically that the generated code will be correct. +Spoon provides developers a way to define so called +**Spoon templates**. **Spoon template** is a part of code (for example +expression, statement, block, method, constuctor, type member, +interface, type, ... any Spoon model subtree), +where parts of that code may be **template parameters**. + +**Spoon template** can be used in two ways: +A) **to generate new code**. The generated code is a copy of code +of **Spoon template**, where each **template parameter** is substituted +by it's value. We call this operation **Generating**. +```java +Factory spoonFactory = ... +//build a Spoon template +Pattern spoonTemplate = ... build a spoon template. For example for an method ... +//define values for parameters +Map parameters = new HashMap<>(); +parameters.put("methodName", "i_am_an_generated_method"); +//generate a code using spoon template and parameters +CtMethod generatedMethod = spoonTemplate.substituteSingle(spoonFactory, CtMethod.class, parameters); +``` +B) **to search for a code**. The found code is same like code of **Spoon template**, +where code on position of **template parameter** may be arbitrary and is copied +as value of **template parameter**. We call this operation **Matching**. +```java +Factory spoonFactory = ... +//build a Spoon template +Pattern spoonTemplate = ... build a spoon template. For example for an method ... +//search for all occurences of the method like spoonTemplate in whole model +spoonTemplate.forEachMatch(spoonFactory.getRootPackage(), (Match match) -> { + //this Consumer is called once for each method which matches with spoonTemplate + Map parameters = match.getParametersAsMap(); + CtMethod matchingMethod = match.getMatchingElement(CtMethod.class); + String aNameOfMatchedMethod = parameters.get("methodName"); + ... +}); +``` +There are several ways how to build a **spoon template** +A) Using a regular java class, which implements `Template` interface +B) Using PatternBuilder, which takes any part of code and where you +define which parts of that code are **template parameters** by calling PatternBuilder methods. + +The `Template` based are statically type-checked, in order to ensure statically that the generated code will be correct. +Both template definitions are normal compiled java source code, +which is part of your sources, so: +* if the template source is compilable then generated code will be compilable too - when you use correct parameter values of course. +* the refactoring applied to your source code is automatically applied to your templates too A Spoon template is a regular Java class that taken as input by the Spoon templating engine to perform a transformation. This is summarized in Figure below. A Spoon template can be seen as a From 39c2d56a72195ea18e1f60077933996516f4fd3f Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Tue, 20 Mar 2018 22:16:37 +0100 Subject: [PATCH 041/131] PatternBuilder documentation --- doc/template_definition.md | 220 ++++++++++++++++++++++++++++++++++--- 1 file changed, 204 insertions(+), 16 deletions(-) diff --git a/doc/template_definition.md b/doc/template_definition.md index dde22799352..9dd3d15fc1e 100644 --- a/doc/template_definition.md +++ b/doc/template_definition.md @@ -11,6 +11,7 @@ interface, type, ... any Spoon model subtree), where parts of that code may be **template parameters**. **Spoon template** can be used in two ways: + A) **to generate new code**. The generated code is a copy of code of **Spoon template**, where each **template parameter** is substituted by it's value. We call this operation **Generating**. @@ -24,6 +25,13 @@ parameters.put("methodName", "i_am_an_generated_method"); //generate a code using spoon template and parameters CtMethod generatedMethod = spoonTemplate.substituteSingle(spoonFactory, CtMethod.class, parameters); ``` +This is summarized in Figure below. A Spoon template can be seen as a +higher-order program, which takes program elements as arguments, and returns a +transformed program. Like any function, a template can be used in different +contexts and give different results, depending on its parameters. + +![Overview of Spoon's Templating System]({{ "/images/template-overview.svg" | prepend: site.baseurl }}) + B) **to search for a code**. The found code is same like code of **Spoon template**, where code on position of **template parameter** may be arbitrary and is copied as value of **template parameter**. We call this operation **Matching**. @@ -41,26 +49,18 @@ spoonTemplate.forEachMatch(spoonFactory.getRootPackage(), (Match match) -> { }); ``` There are several ways how to build a **spoon template** -A) Using a regular java class, which implements `Template` interface +A) Using a regular java class, which implements a `Template` interface B) Using PatternBuilder, which takes any part of code and where you define which parts of that code are **template parameters** by calling PatternBuilder methods. -The `Template` based are statically type-checked, in order to ensure statically that the generated code will be correct. +The `Template` interface based definitions are statically type-checked, in order to ensure statically that the generated code will be correct. Both template definitions are normal compiled java source code, which is part of your sources, so: * if the template source is compilable then generated code will be compilable too - when you use correct parameter values of course. -* the refactoring applied to your source code is automatically applied to your templates too +* the refactoring applied to your source code is automatically applied to your templates too. So the maintenance effort of Spoon templates is lower comparing to effort needed to maintain third party templates based on concatenation of strings. -A Spoon template is a regular Java class that taken as input by the Spoon templating engine to perform a transformation. -This is summarized in Figure below. A Spoon template can be seen as a -higher-order program, which takes program elements as arguments, and returns a -transformed program. Like any function, a template can be used in different -contexts and give different results, depending on its parameters. - -![Overview of Spoon's Templating System]({{ "/images/template-overview.svg" | prepend: site.baseurl }}) - -Definition of templates ------------------------ +Definition of `Template` interface based templates +-------------------------------------------------- Class `CheckBoundTemplate` below defines a Spoon template. @@ -124,8 +124,79 @@ method.getBody().insertBegin(injectedCode); ``` -Kinds of templating -------------------- +Definition of templates using `PatternBuilder` +-------------------------------------------------- + +The body of method `CheckBoundTemplate#statement` below defines a Spoon template. + +```java +public class CheckBoundTemplate /*it doesn't matter what it extends or implements*/ { + // it doesn't matter what other type members are in template class + // it doesn't matter what is the name of the method whose body will be used as template + public void statement(Collection _col_) { + if (_col_.size() > 10) + throw new OutOfBoundException(); + } +} +``` + +The code, which creates a Spoon template using `PatternBuilder` looks like this: +```java +Pattern t = PatternBuilder.create(factory, + //defines template class. + CheckBoundTemplate.class, + //defines which part of template class will be used as template model + model -> model.setBodyOfMethod("statement")) + //tells builder that all variables defined out of scope of template model (body of the method) + //are considered as template parameters + .configureAutomaticParameters() + //builds an instance of Pattern + .build(); +``` + + +This template specifies a +statements (all statmenets of body of method `statement`) that is a precondition to check that a list +is smaller than a certain size. This piece of code will be injected at the +beginning of all methods dealing with size-bounded lists. This template has +one single template parameter called `_col_`. +In this case, the template parameter value is meant to be an expression (`CtExpression`) +that returns a Collection. + +The template source is +well-typed, compiles, but the binary code of the template is usually thrown away +and only spoon model (Abstract syntax tree) of source code is used to generated new code +or to search for matching code. + +Generating of code using Spoon template +------------- + +The code at the end of this page shows how to use such spoon template. +One takes a spoon template, defines the template parameters, +and then one calls the template engine. In last line, the bound check +is injected at the beginning of a method body. + +Since the template is given the first method parameter which is in the +scope of the insertion location, the generated code is guaranteed to compile. +The Java compiler ensures that the template compiles with a given scope, the +developer is responsible for checking that the scope where she uses +template-generated code is consistent with the template scope. + +```java +Pattern t = PatternBuilder.create(...building a template. See code above...); +// creating a holder of parameters +Map parameters = new HashMap<>(); +parameters.put("_col_", createVariableAccess(method.getParameters().get(0))); + +// getting the final AST +CtStatement injectedCode = t.substituteSingle(factory, CtStatement.class, parameters); + +// adds the bound check at the beginning of a method +method.getBody().insertBegin(injectedCode); +``` + +Kinds of `Template` interface based templating +--------------- There are different kinds of templating. @@ -305,9 +376,106 @@ String someMethod() { } ``` +## PatternBuilder parameters +The `PatternBuilder` takes all the Template parameters mentioned in the chapters above +and understands them as template parameters, when `PatternBuilder#configureTemplateParameters()` +is called. +```java +Pattern t = PatternBuilder.create(...select template model...) + .configureTemplateParameters() + .build(); +``` + +Next to the ways of parameter definitions mentioned above the `PatternBuilder` +allows to define parameters like this: +```java +Pattern t = PatternBuilder.create(...select template model...) + .configureParameters(pb -> + pb.parameter("firstParamName") + //...select which AST nodes are parameters... + //e.g. using parameter selector + .byType(Request.class) + //...modify behavior of parameters... + //e.g. using parameter modifier + .setMinOccurence(0); + + //... you can define as many parameters as you need... + + pb.parameter("lastParamName").byVariable("_x_"); + ) + .build(); +``` + +## PatternBuilder parameter selectors + +* `byType(Class|CtTypeReference|String)` - all the references to the type defined by Class, +CtTypeReference or qualified name will be considered as template parameter +* `byLocalType(CtType searchScope, String localTypeSimpleName)` - all the types defined in `searchScope` +and having simpleName equal to `localTypeSimpleName` will be considered as template parameter +* `byVariable(CtVariable|String)` - all read/write variable references to CtVariable +or any variable with provided simple name will be considered as template parameter +* byInvocation(CtMethod method) - each invocation of `method` will be considered as template parameter +* `parametersByVariable(CtVariable|String... variableName)` - each `variableName` is a name of a variable +which references instance of a class with fields. Each such field is considered as template parameter. +* `byTemplateParameterReference(CtVariable)` - the reference to variable of type `TemplateParameter` is handled +as template parameter using all the rules defined in the chapters above. +* `byFilter(Filter)` - any template model element, where `Filter.accept(element)` returns true is a template parameter. +* `attributeOfElementByFilter(CtRole role, Filter filter)` - the attribute defined by `role` of all +template model elements, where `Filter.accept(element)` returns true is a template parameter. +It can be used to define a varible on any CtElement attribute. E.g. method modifiers or throwables, ... +* `byString(String name)` - all template model string attributes whose value **is equal to** `name` are considered as template parameter.This can be used to define full name of the methods and fields, etc. +* `bySubstring(String stringMarker)` - all template model string attributes whose value **contains** +whole string or a substring equal to `stringMarker`are template parameter. +Note: only the `stringMarker` substring of the string value is substituted. +Other parts of string/element name are kept unchanged. +* `bySimpleName(String name)` - any CtNamedElement or CtReference identified by it's simple name is a template parameter. +* `byNamedElementSimpleName(String name)` - any CtNamedElement identified by it's simple name is a template parameter. +* `byReferenceSimpleName(String name)` - any CtReference identified by it's simple name is a template parameter. + +Note: +* `byString` and `bySubstring` are used to rename code elements. +For example to rename a method "xyz" to "abc" +* `bySimpleName`, `byNamedElementSimpleName`, `byReferenceSimpleName` +are used to replace these elements by completelly different elements. +For example to replace method invocation by an variable reference, etc. + + +## PatternBuilder parameter modifiers +Any parameter of spoon template can be configured like this: + +* `setMinOccurence(int)` - defines minimal number of occurences of the value of this parameter during **matching**, +which is needed by matcher to accept that value. + * `setMinOccurence(0)` - defines optional parameter + * `setMinOccurence(1)` - defines mandatory parameter + * `setMinOccurence(n)` - defines parameter, whose value must be repeated at least n-times +* `setMaxOccurence(int)` - defines maximal number of occurences of the value of this parameter during **matching**, +which is accepted by matcher to accept that value. +* `setMatchingStrategy(Quantifier)` - defines how to matching engine will behave when two template nodes may accept the same value. + * `Quantifier#GREEDY` - Greedy quantifiers are considered "greedy" because they force the matcher to read in, or eat, +the entire input prior to attempting the next match. +If the next match attempt (the entire input) fails, the matcher backs off the input by one and tries again, +repeating the process until a match is found or there are no more elements left to back off from. + * `Quantifier#RELUCTANT` - The reluctant quantifier takes the opposite approach: It start at the beginning of the input, +then reluctantly eat one character at a time looking for a match. +The last thing it tries is the entire input. + * `Quantifier#POSSESSIVE` - The possessive quantifier always eats the entire input string, +trying once (and only once) for a match. Unlike the greedy quantifiers, possessive quantifiers never back off, +even if doing so would allow the overall match to succeed. +* `setValueType(Class type)` - defines a required type of the value. If defined the template matched, will match only values which are assigneable from the provided `type` +* `matchCondition(Class type, Predicate matchCondition)` - defines a `Predicate`, whose method `boolean test(T)`, +will be called by template matcher. Template matcher accepts that value only if `test` returns true for the value. +The `setValueType(type)` is called internally too, so match condition assures both a type of value and condition on value. +* `setContainerKind(ContainerKind)` - defines what container will be used to store the value. + * `ContainerKind#SINGLE` - only single value is accepted as a parameter value. + It can be e.g. single String or single CtStatement, etc. + * `ContainerKind#LIST` - The values are always stored as `List`. + * `ContainerKind#SET` - The values are always stored as `Set`. + * `ContainerKind#MAP` - The values are always stored as `Map`. + #### Inlining foreach expressions -Foreach expressions can be inlined. They have to be declared as follows: +All Foreach expressions, which contains a template paremeter are inlined +in `Template` interface based templates. They have to be declared as follows: ```java @Parameter @@ -333,3 +501,23 @@ is transformed into: java.lang.System.out.println(1); } ``` +## Inlining with PatternBuilder +The template code in spoon templates made by `PatternBuilder` is never inlined automatically. +But you can mark code to be inlined this way: +```java +Pattern t = PatternBuilder.create(...select template model...) + //...configure parameters... + configureLiveStatements(ls -> + //...select to be inlined statements... + //e.g. by variable name: + ls.byVariableName("intValues") + ).build(); +``` + +### PatternBuilder inline statements selectors + +* `byVariableName(String varName)` - all CtForEach and CtIf statements +whose expression references variable named `varName` are understood as +inline statements +* `markLive(CtForEach|CtIf)` - provided CtForEach or CtIf statement +is understood as inline statement From f805d647a0cfe07cca890e020a51fa789ec908a9 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Wed, 21 Mar 2018 19:33:08 +0100 Subject: [PATCH 042/131] rename `live` to `inline` statements --- ...lder.java => InlineStatementsBuilder.java} | 46 +++++++++---------- .../java/spoon/pattern/PatternBuilder.java | 28 +++++------ .../java/spoon/pattern/PatternPrinter.java | 8 ++-- .../java/spoon/pattern/node/ForEachNode.java | 4 +- .../node/{LiveNode.java => InlineNode.java} | 10 ++-- .../java/spoon/pattern/node/SwitchNode.java | 8 ++-- .../spoon/test/template/CodeReplaceTest.java | 2 +- .../test/template/TemplateMatcherTest.java | 6 +-- .../testclasses/match/MatchForEach.java | 2 +- .../testclasses/match/MatchForEach2.java | 2 +- .../testclasses/match/MatchIfElse.java | 2 +- .../testclasses/match/MatchMultiple2.java | 2 +- .../match/MatchWithParameterCondition.java | 2 +- .../match/MatchWithParameterType.java | 2 +- .../testclasses/replace/OldPattern.java | 2 +- 15 files changed, 63 insertions(+), 63 deletions(-) rename src/main/java/spoon/pattern/{LiveStatementsBuilder.java => InlineStatementsBuilder.java} (81%) rename src/main/java/spoon/pattern/node/{LiveNode.java => InlineNode.java} (78%) diff --git a/src/main/java/spoon/pattern/LiveStatementsBuilder.java b/src/main/java/spoon/pattern/InlineStatementsBuilder.java similarity index 81% rename from src/main/java/spoon/pattern/LiveStatementsBuilder.java rename to src/main/java/spoon/pattern/InlineStatementsBuilder.java index ca98c71374d..e72e8e46fcc 100644 --- a/src/main/java/spoon/pattern/LiveStatementsBuilder.java +++ b/src/main/java/spoon/pattern/InlineStatementsBuilder.java @@ -42,7 +42,7 @@ import static spoon.pattern.PatternBuilder.bodyToStatements; /** - * Builds live statements of Pattern + * Builds inline statements of Pattern * * For example if the `for` statement in this pattern model *
            
            @@ -50,7 +50,7 @@
              *	System.out.println(x);
              * }
              * 
            - * is configured as live statement and a Pattern is substituted + * is configured as inline statement and a Pattern is substituted * using parameter $iterable$ = new String[]{"A", "B", "C"} * then pattern generated this code *
            
            @@ -58,17 +58,17 @@
              * System.out.println("B");
              * System.out.println("C");
              * 
            - * because live statements are executed during substitution process and are not included in generated result. + * because inline statements are executed during substitution process and are not included in generated result. * - * The live statements may be used in PatternMatching process (opposite to Pattern substitution) too. + * The inline statements may be used in PatternMatching process (opposite to Pattern substitution) too. */ -public class LiveStatementsBuilder { +public class InlineStatementsBuilder { private final PatternBuilder patternBuilder; private boolean failOnMissingParameter = true; private ConflictResolutionMode conflictResolutionMode = ConflictResolutionMode.FAIL; - public LiveStatementsBuilder(PatternBuilder patternBuilder) { + public InlineStatementsBuilder(PatternBuilder patternBuilder) { this.patternBuilder = patternBuilder; } @@ -84,17 +84,17 @@ public ConflictResolutionMode getConflictResolutionMode() { * @param conflictResolutionMode to be applied mode * @return this to support fluent API */ - public LiveStatementsBuilder setConflictResolutionMode(ConflictResolutionMode conflictResolutionMode) { + public InlineStatementsBuilder setConflictResolutionMode(ConflictResolutionMode conflictResolutionMode) { this.conflictResolutionMode = conflictResolutionMode; return this; } /** - * marks all CtIf and CtForEach whose expression contains a variable refrence named `variableName` as live statement. + * marks all CtIf and CtForEach whose expression contains a variable reference named `variableName` as inline statement. * @param variableName to be searched variable name * @return this to support fluent API */ - public LiveStatementsBuilder byVariableName(String variableName) { + public InlineStatementsBuilder byVariableName(String variableName) { patternBuilder.patternQuery .filterChildren(new TypeFilter<>(CtVariableReference.class)) .map((CtVariableReference varRef) -> { @@ -104,11 +104,11 @@ public LiveStatementsBuilder byVariableName(String variableName) { stmt.accept(new CtAbstractVisitor() { @Override public void visitCtForEach(CtForEach foreach) { - markLive(foreach); + markInline(foreach); } @Override public void visitCtIf(CtIf ifElement) { - markLive(ifElement); + markInline(ifElement); } }); }); @@ -116,15 +116,15 @@ public void visitCtIf(CtIf ifElement) { } /** - * marks {@link CtForEach} as live statement. + * marks {@link CtForEach} as inline statement. * @param foreach to be marked {@link CtForEach} element * @return this to support fluent API */ - public LiveStatementsBuilder markLive(CtForEach foreach) { + public InlineStatementsBuilder markInline(CtForEach foreach) { //detect meta elements by different way - e.g. comments? RootNode vr = patternBuilder.getPatternNode(foreach.getExpression()); if ((vr instanceof PrimitiveMatcher) == false) { - throw new SpoonException("Each live `for(x : iterable)` statement must have defined pattern parameter for `iterable` expression"); + throw new SpoonException("Each inline `for(x : iterable)` statement must have defined pattern parameter for `iterable` expression"); } PrimitiveMatcher parameterOfExpression = (PrimitiveMatcher) vr; // PatternBuilder localPatternBuilder = patternBuilder.create(bodyToStatements(foreach.getBody())); @@ -149,13 +149,13 @@ public LiveStatementsBuilder markLive(CtForEach foreach) { } /** - * marks {@link CtIf} as live statement. + * marks {@link CtIf} as inline statement. * @param ifElement to be marked {@link CtIf} element * @return this to support fluent API */ - public LiveStatementsBuilder markLive(CtIf ifElement) { + public InlineStatementsBuilder markInline(CtIf ifElement) { SwitchNode osp = new SwitchNode(); - boolean[] canBeLive = new boolean[]{true}; + boolean[] canBeInline = new boolean[]{true}; forEachIfCase(ifElement, (expression, block) -> { //detect meta elements by different way - e.g. comments? if (expression != null) { @@ -163,23 +163,23 @@ public LiveStatementsBuilder markLive(CtIf ifElement) { RootNode vrOfExpression = patternBuilder.getPatternNode(expression); if (vrOfExpression instanceof ParameterNode == false) { if (failOnMissingParameter) { - throw new SpoonException("Each live `if` statement must have defined pattern parameter in expression. If you want to ignore this, then call LiveStatementsBuilder#setFailOnMissingParameter(false) first."); + throw new SpoonException("Each inline `if` statement must have defined pattern parameter in expression. If you want to ignore this, then call InlineStatementsBuilder#setFailOnMissingParameter(false) first."); } else { - canBeLive[0] = false; + canBeInline[0] = false; return; } } if (vrOfExpression instanceof PrimitiveMatcher) { osp.addCase((PrimitiveMatcher) vrOfExpression, getPatternNode(bodyToStatements(block))); } else { - throw new SpoonException("Live `if` statement have defined single value pattern parameter in expression. But there is " + vrOfExpression.getClass().getName()); + throw new SpoonException("Inline `if` statement have defined single value pattern parameter in expression. But there is " + vrOfExpression.getClass().getName()); } } else { //expression is null, it is: else {} osp.addCase(null, getPatternNode(bodyToStatements(block))); } }); - if (canBeLive[0]) { + if (canBeInline[0]) { /* * create Substitution request for whole `if`, * resolve the expressions at substitution time and substitute only the `if` then/else statements, not `if` itself. @@ -239,11 +239,11 @@ public boolean isFailOnMissingParameter() { } /** - * @param failOnMissingParameter set true if it should fail when some statement cannot be handled as live + * @param failOnMissingParameter set true if it should fail when some statement cannot be handled as inline * set false if ssuch statement should be kept as part of template. * @return this to support fluent API */ - public LiveStatementsBuilder setFailOnMissingParameter(boolean failOnMissingParameter) { + public InlineStatementsBuilder setFailOnMissingParameter(boolean failOnMissingParameter) { this.failOnMissingParameter = failOnMissingParameter; return this; } diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index 1f37bdf7fac..1ea414ee225 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -771,8 +771,8 @@ private void configureTemplateParameter(CtType templateType, Map templateType, Map { - //we are adding live statements automatically from legacy templates, - //so do not fail if it is sometime not possible - it means that it is not a live statement then + configureInlineStatements(sb -> { + //we are adding inline statements automatically from legacy templates, + //so do not fail if it is sometime not possible - it means that it is not a inline statement then sb.setFailOnMissingParameter(false); sb.byVariableName(variableName); }); @@ -815,7 +815,7 @@ private void addLiveStatements(String variableName, Object paramValue) { } /** - * Configures live statements + * Configures inline statements * * For example if the `for` statement in this pattern model *
            
            @@ -823,7 +823,7 @@ private void addLiveStatements(String variableName, Object paramValue) {
             	 *	System.out.println(x);
             	 * }
             	 * 
            - * is configured as live statement and a Pattern is substituted + * is configured as inline statement and a Pattern is substituted * using parameter $iterable$ = new String[]{"A", "B", "C"} * then pattern generated this code *
            
            @@ -831,14 +831,14 @@ private void addLiveStatements(String variableName, Object paramValue) {
             	 * System.out.println("B");
             	 * System.out.println("C");
             	 * 
            - * because live statements are executed during substitution process and are not included in generated result. + * because inline statements are executed during substitution process and are not included in generated result. * - * The live statements may be used in PatternMatching process (opposite to Pattern substitution) too. + * The inline statements may be used in PatternMatching process (opposite to Pattern substitution) too. * @param consumer * @return this to support fluent API */ - public PatternBuilder configureLiveStatements(Consumer consumer) { - LiveStatementsBuilder sb = new LiveStatementsBuilder(this); + public PatternBuilder configureInlineStatements(Consumer consumer) { + InlineStatementsBuilder sb = new InlineStatementsBuilder(this); consumer.accept(sb); return this; } diff --git a/src/main/java/spoon/pattern/PatternPrinter.java b/src/main/java/spoon/pattern/PatternPrinter.java index ccb4c223707..feed51c2673 100644 --- a/src/main/java/spoon/pattern/PatternPrinter.java +++ b/src/main/java/spoon/pattern/PatternPrinter.java @@ -26,7 +26,7 @@ import spoon.pattern.node.ConstantNode; import spoon.pattern.node.ElementNode; import spoon.pattern.node.ListOfNodes; -import spoon.pattern.node.LiveNode; +import spoon.pattern.node.InlineNode; import spoon.pattern.node.ParameterNode; import spoon.pattern.node.RootNode; import spoon.pattern.parameter.ParameterInfo; @@ -69,9 +69,9 @@ public String printNode(RootNode node) { @Override public void generateTargets(RootNode node, ResultHolder result, ParameterValueProvider parameters) { int firstResultIdx = result.getResults().size(); - if (node instanceof LiveNode) { - //this is a Live node. Do not generated nodes normally, but generate origin live statements - ((LiveNode) node).generateLiveTargets(this, result, parameters); + if (node instanceof InlineNode) { + //this is a inline node. Do not generated nodes normally, but generate origin inline statements + ((InlineNode) node).generateInlineTargets(this, result, parameters); } else { super.generateTargets(node, result, parameters); } diff --git a/src/main/java/spoon/pattern/node/ForEachNode.java b/src/main/java/spoon/pattern/node/ForEachNode.java index 94c5c911505..ead4f9f0aef 100644 --- a/src/main/java/spoon/pattern/node/ForEachNode.java +++ b/src/main/java/spoon/pattern/node/ForEachNode.java @@ -41,7 +41,7 @@ * * where parameter values are _x_ = ["a", "b", getStringOf(p1, p2)] */ -public class ForEachNode extends AbstractRepeatableMatcher implements LiveNode { +public class ForEachNode extends AbstractRepeatableMatcher implements InlineNode { private PrimitiveMatcher iterableParameter; private RootNode nestedModel; @@ -146,7 +146,7 @@ public boolean isTryNextMatch(ParameterValueProvider parameters) { } @Override - public void generateLiveTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters) { + public void generateInlineTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters) { Factory f = generator.getFactory(); CtForEach forEach = f.Core().createForEach(); forEach.setVariable(f.Code().createLocalVariable(f.Type().objectType(), localParameter.getName(), null)); diff --git a/src/main/java/spoon/pattern/node/LiveNode.java b/src/main/java/spoon/pattern/node/InlineNode.java similarity index 78% rename from src/main/java/spoon/pattern/node/LiveNode.java rename to src/main/java/spoon/pattern/node/InlineNode.java index 6db5dd673f7..c7da10e9b7b 100644 --- a/src/main/java/spoon/pattern/node/LiveNode.java +++ b/src/main/java/spoon/pattern/node/InlineNode.java @@ -26,13 +26,13 @@ * For example CtForEach statement is handled as repeated generation of pattern * Or CtIf statement is handled as optionally generated pattern */ -public interface LiveNode extends RootNode { +public interface InlineNode extends RootNode { /** - * Generates Live statements of this live {@link RootNode}. + * Generates inline statements of this inline {@link RootNode}. * This method is used when sources of pattern have to be printed - * @param generator + * @param generator a to be used {@link Generator} * @param result holder of the result - * @param parameters + * @param parameters a {@link ParameterValueProvider} with current parameters */ - void generateLiveTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters); + void generateInlineTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters); } diff --git a/src/main/java/spoon/pattern/node/SwitchNode.java b/src/main/java/spoon/pattern/node/SwitchNode.java index f765d42d9db..17aceb48f1e 100644 --- a/src/main/java/spoon/pattern/node/SwitchNode.java +++ b/src/main/java/spoon/pattern/node/SwitchNode.java @@ -45,7 +45,7 @@ * ... someStatements in other cases ... * } */ -public class SwitchNode extends AbstractNode implements LiveNode { +public class SwitchNode extends AbstractNode implements InlineNode { private List cases = new ArrayList<>(); @@ -117,7 +117,7 @@ public TobeMatched matchTargets(TobeMatched targets, Matchers nextMatchers) { return new CaseNode(null, null).matchTargets(targets, nextMatchers); } - private class CaseNode extends AbstractNode implements LiveNode { + private class CaseNode extends AbstractNode implements InlineNode { /* * is null for the default case */ @@ -193,7 +193,7 @@ private boolean isCaseSelected(Generator generator, ParameterValueProvider param } @Override - public void generateLiveTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters) { + public void generateInlineTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters) { Factory f = generator.getFactory(); CoreFactory cf = f.Core(); CtBlock block = cf.createBlock(); @@ -214,7 +214,7 @@ public void generateLiveTargets(Generator generator, ResultHolder result, } @Override - public void generateLiveTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters) { + public void generateInlineTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters) { CtStatement resultStmt = null; CtStatement lastElse = null; CtIf lastIf = null; diff --git a/src/test/java/spoon/test/template/CodeReplaceTest.java b/src/test/java/spoon/test/template/CodeReplaceTest.java index 7ef65836e08..8fff747c752 100644 --- a/src/test/java/spoon/test/template/CodeReplaceTest.java +++ b/src/test/java/spoon/test/template/CodeReplaceTest.java @@ -35,7 +35,7 @@ public static Pattern createPattern(Factory factory) { .parameter("statements").setContainerKind(ContainerKind.LIST) ) .configureAutomaticParameters() - .configureLiveStatements(ls -> ls.byVariableName("useStartKeyword")) + .configureInlineStatements(ls -> ls.byVariableName("useStartKeyword")) .build(); } diff --git a/src/test/java/spoon/test/template/TemplateMatcherTest.java b/src/test/java/spoon/test/template/TemplateMatcherTest.java index e89510375bb..16d428acb6c 100644 --- a/src/test/java/spoon/test/template/TemplateMatcherTest.java +++ b/src/test/java/spoon/test/template/TemplateMatcherTest.java @@ -56,7 +56,7 @@ public class TemplateMatcherTest { @Test public void testMatchForeach() throws Exception { - //contract: live foreach template can match multiple models into list of parameter values + //contract: inline foreach template can match multiple models into list of parameter values CtType ctClass = ModelUtils.buildClass(MatchForEach.class); Pattern pattern = MatchForEach.createPattern(ctClass.getFactory()); @@ -87,7 +87,7 @@ public void testMatchForeach() throws Exception { @Test public void testMatchForeachWithOuterSubstitution() throws Exception { - //contract: live foreach template can match multiple models into list of parameter values including outer parameters + //contract: inline foreach template can match multiple models into list of parameter values including outer parameters CtType ctClass = ModelUtils.buildClass(MatchForEach2.class); Pattern pattern = MatchForEach2.createPattern(ctClass.getFactory()); @@ -126,7 +126,7 @@ public void testMatchForeachWithOuterSubstitution() throws Exception { @Test public void testMatchIfElse() throws Exception { - //contract: live switch Pattern can match one of the models + //contract: inline switch Pattern can match one of the models CtType ctClass = ModelUtils.buildClass(MatchIfElse.class); Pattern pattern = MatchIfElse.createPattern(ctClass.getFactory()); diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchForEach.java b/src/test/java/spoon/test/template/testclasses/match/MatchForEach.java index 6d4aa5d8456..dc6fa560fa3 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchForEach.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchForEach.java @@ -16,7 +16,7 @@ public static Pattern createPattern(Factory factory) { .configureParameters(pb -> { pb.parameter("values").byVariable("values").setContainerKind(ContainerKind.LIST); }) - .configureLiveStatements(lsb -> lsb.byVariableName("values")) + .configureInlineStatements(lsb -> lsb.byVariableName("values")) .build(); } diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchForEach2.java b/src/test/java/spoon/test/template/testclasses/match/MatchForEach2.java index cce8b6c35a4..9e312ee2cfb 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchForEach2.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchForEach2.java @@ -17,7 +17,7 @@ public static Pattern createPattern(Factory factory) { pb.parameter("values").byVariable("values").setContainerKind(ContainerKind.LIST); pb.parameter("varName").byString("var"); }) - .configureLiveStatements(lsb -> lsb.byVariableName("values")) + .configureInlineStatements(lsb -> lsb.byVariableName("values")) .build(); } diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchIfElse.java b/src/test/java/spoon/test/template/testclasses/match/MatchIfElse.java index e237a199268..4aa0c5a7ed6 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchIfElse.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchIfElse.java @@ -17,7 +17,7 @@ public static Pattern createPattern(Factory factory) { pb.parameter("option2").byVariable("option2"); pb.parameter("value").byFilter(new TypeFilter(CtLiteral.class)); }) - .configureLiveStatements(lsb -> lsb.byVariableName("option")) + .configureInlineStatements(lsb -> lsb.byVariableName("option")) .build(); } diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple2.java b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple2.java index f6d00950e3c..7e7d8120743 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple2.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple2.java @@ -23,7 +23,7 @@ public static Pattern createPattern(Factory factory, Consumer pb.parameter("printedValue").byVariable("something"); cfgParams.accept(pb); }) - .configureLiveStatements(ls -> { + .configureInlineStatements(ls -> { ls.byVariableName("something"); }) .build(); diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterCondition.java b/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterCondition.java index add154ce25b..3a2871fcb23 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterCondition.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterCondition.java @@ -17,7 +17,7 @@ public static Pattern createPattern(Factory factory, Predicate condition pb.matchCondition(null, condition); } }) - .configureLiveStatements(lsb -> lsb.byVariableName("values")) + .configureInlineStatements(lsb -> lsb.byVariableName("values")) .build(); } diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterType.java b/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterType.java index c18ea363fcb..87b91c3152c 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterType.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterType.java @@ -15,7 +15,7 @@ public static Pattern createPattern(Factory factory, Class valueType) { pb.setValueType(valueType); } }) - .configureLiveStatements(lsb -> lsb.byVariableName("values")) + .configureInlineStatements(lsb -> lsb.byVariableName("values")) .build(); } diff --git a/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java b/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java index 001363c2136..8f1a28ec4de 100644 --- a/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java +++ b/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java @@ -69,7 +69,7 @@ public static Pattern createPattern(Factory factory) { .parameter("statements").setContainerKind(ContainerKind.LIST) ) .configureAutomaticParameters() - .configureLiveStatements(ls -> ls.byVariableName("useStartKeyword")) + .configureInlineStatements(ls -> ls.byVariableName("useStartKeyword")) .build(); } From bf53692f48f08624e2e4a1aebcd6a5d465d23349 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Wed, 21 Mar 2018 19:34:35 +0100 Subject: [PATCH 043/131] fix template_definition documentation --- doc/template_definition.md | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/doc/template_definition.md b/doc/template_definition.md index 9dd3d15fc1e..c53f3267574 100644 --- a/doc/template_definition.md +++ b/doc/template_definition.md @@ -15,6 +15,7 @@ where parts of that code may be **template parameters**. A) **to generate new code**. The generated code is a copy of code of **Spoon template**, where each **template parameter** is substituted by it's value. We call this operation **Generating**. + ```java Factory spoonFactory = ... //build a Spoon template @@ -25,6 +26,7 @@ parameters.put("methodName", "i_am_an_generated_method"); //generate a code using spoon template and parameters CtMethod generatedMethod = spoonTemplate.substituteSingle(spoonFactory, CtMethod.class, parameters); ``` + This is summarized in Figure below. A Spoon template can be seen as a higher-order program, which takes program elements as arguments, and returns a transformed program. Like any function, a template can be used in different @@ -35,6 +37,7 @@ contexts and give different results, depending on its parameters. B) **to search for a code**. The found code is same like code of **Spoon template**, where code on position of **template parameter** may be arbitrary and is copied as value of **template parameter**. We call this operation **Matching**. + ```java Factory spoonFactory = ... //build a Spoon template @@ -48,7 +51,9 @@ spoonTemplate.forEachMatch(spoonFactory.getRootPackage(), (Match match) -> { ... }); ``` + There are several ways how to build a **spoon template** + A) Using a regular java class, which implements a `Template` interface B) Using PatternBuilder, which takes any part of code and where you define which parts of that code are **template parameters** by calling PatternBuilder methods. @@ -141,6 +146,7 @@ public class CheckBoundTemplate /*it doesn't matter what it extends or implement ``` The code, which creates a Spoon template using `PatternBuilder` looks like this: + ```java Pattern t = PatternBuilder.create(factory, //defines template class. @@ -156,7 +162,7 @@ Pattern t = PatternBuilder.create(factory, This template specifies a -statements (all statmenets of body of method `statement`) that is a precondition to check that a list +statements (all statements of body of method `statement`) that is a precondition to check that a list is smaller than a certain size. This piece of code will be injected at the beginning of all methods dealing with size-bounded lists. This template has one single template parameter called `_col_`. @@ -380,6 +386,7 @@ String someMethod() { The `PatternBuilder` takes all the Template parameters mentioned in the chapters above and understands them as template parameters, when `PatternBuilder#configureTemplateParameters()` is called. + ```java Pattern t = PatternBuilder.create(...select template model...) .configureTemplateParameters() @@ -388,13 +395,21 @@ Pattern t = PatternBuilder.create(...select template model...) Next to the ways of parameter definitions mentioned above the `PatternBuilder` allows to define parameters like this: + ```java +//a template model +void method(String _x_) { + zeroOneOrMoreStatements(); + System.out.println(_x_); +} + +//a pattern definition Pattern t = PatternBuilder.create(...select template model...) .configureParameters(pb -> pb.parameter("firstParamName") //...select which AST nodes are parameters... //e.g. using parameter selector - .byType(Request.class) + .bySimpleName("zeroOneOrMoreStatements") //...modify behavior of parameters... //e.g. using parameter modifier .setMinOccurence(0); @@ -507,7 +522,7 @@ But you can mark code to be inlined this way: ```java Pattern t = PatternBuilder.create(...select template model...) //...configure parameters... - configureLiveStatements(ls -> + configureInlineStatements(ls -> //...select to be inlined statements... //e.g. by variable name: ls.byVariableName("intValues") @@ -519,5 +534,5 @@ Pattern t = PatternBuilder.create(...select template model...) * `byVariableName(String varName)` - all CtForEach and CtIf statements whose expression references variable named `varName` are understood as inline statements -* `markLive(CtForEach|CtIf)` - provided CtForEach or CtIf statement +* `markInline(CtForEach|CtIf)` - provided CtForEach or CtIf statement is understood as inline statement From 60e0ebdaad277099d677e87012c7d71e41f2f8da Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Wed, 21 Mar 2018 19:37:17 +0100 Subject: [PATCH 044/131] TemplateModelBuilder moved to separate file --- .../java/spoon/pattern/PatternBuilder.java | 265 ---------------- .../spoon/pattern/TemplateModelBuilder.java | 297 ++++++++++++++++++ 2 files changed, 297 insertions(+), 265 deletions(-) create mode 100644 src/main/java/spoon/pattern/TemplateModelBuilder.java diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index 1ea414ee225..8b36a48084e 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -16,16 +16,13 @@ */ package spoon.pattern; -import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; -import java.util.Iterator; import java.util.List; -import java.util.ListIterator; import java.util.Map; import java.util.Set; import java.util.function.Consumer; @@ -41,13 +38,9 @@ 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.declaration.CtAnnotation; import spoon.reflect.declaration.CtElement; -import spoon.reflect.declaration.CtExecutable; import spoon.reflect.declaration.CtField; -import spoon.reflect.declaration.CtMethod; import spoon.reflect.declaration.CtType; import spoon.reflect.declaration.CtTypeMember; import spoon.reflect.declaration.CtVariable; @@ -371,264 +364,6 @@ public PatternBuilder configureAutomaticParameters() { return this; } - /** - * Builder which allows to define which part of AST of template {@link CtType} - * has to be used as a model of the {@link Pattern} - */ - public static class TemplateModelBuilder { - /** - * The original type, which contains the AST of pattern model - */ - private final CtType templateType; - /** - * optional clone of templateType. It is created when AST of CtType has to be modified - * before it can become a model of {@link Pattern} - */ - private CtType clonedTemplateType; - /** - * holds the built pattern model - */ - private List templateModel = null; - - TemplateModelBuilder(CtType templateTemplate) { - this.templateType = templateTemplate; - } - - /** - * @return origin {@link CtType}, which is the source of the pattern model - */ - public CtType getTemplateType() { - return templateType; - } - - /** - * Returns clone of the templateType. - * The clone is done only once. Later calls returns cached clone. - * @return - */ - private CtType getClonedTemplateType() { - if (clonedTemplateType == null) { - clonedTemplateType = templateType.clone(); - if (templateType.isParentInitialized()) { - //set parent package, to keep origin qualified name of the Template. It is needed for correct substitution of Template name by target type reference - clonedTemplateType.setParent(templateType.getParent()); - } - } - return clonedTemplateType; - } - - /** - * Sets a template model from {@link CtTypeMember} of a template type - * @param typeMemberName the name of the {@link CtTypeMember} of a template type - */ - public TemplateModelBuilder setTypeMember(String typeMemberName) { - setTypeMember(tm -> typeMemberName.equals(tm.getSimpleName())); - return this; - } - /** - * Sets a template model from {@link CtTypeMember} of a template type - * @param filter the {@link Filter} whose match defines to be used {@link CtTypeMember} - */ - public TemplateModelBuilder setTypeMember(Filter filter) { - setTemplateModel(getByFilter(filter)); - return this; - } - - /** - * removes all annotations of type defined by `classes` from the clone of the source {@link CtType} - * @param classes list of classes which defines types of to be removed annotations - * @return this to support fluent API - */ - public TemplateModelBuilder removeTag(Class... classes) { - List elements = getClonedTemplateModel(); - for (Class class1 : classes) { - for (CtElement element : elements) { - CtAnnotation annotation = element.getAnnotation(element.getFactory().Type().createReference(class1)); - if (annotation != null) { - element.removeAnnotation(annotation); - } - } - } - return this; - } - - private List getClonedTemplateModel() { - if (templateModel == null) { - throw new SpoonException("Template model is not defined yet"); - } - for (ListIterator iter = templateModel.listIterator(); iter.hasNext();) { - CtElement ele = iter.next(); - if (ele.getRoleInParent() != null) { - iter.set(ele.clone()); - } - } - return templateModel; - } - - /** - * Sets a template model from body of the method of template type - * @param methodName the name of {@link CtMethod} - */ - public void setBodyOfMethod(String methodName) { - setBodyOfMethod(tm -> methodName.equals(tm.getSimpleName())); - } - /** - * Sets a template model from body of the method of template type selected by filter - * @param filter the {@link Filter} whose match defines to be used {@link CtMethod} - */ - public void setBodyOfMethod(Filter> filter) { - CtBlock body = getOneByFilter(filter).getBody(); - setTemplateModel(body.getStatements()); - } - - /** - * Sets a template model from return expression of the method of template type selected by filter - * @param methodName the name of {@link CtMethod} - */ - public void setReturnExpressionOfMethod(String methodName) { - setReturnExpressionOfMethod(tm -> methodName.equals(tm.getSimpleName())); - } - /** - * Sets a template model from return expression of the method of template type selected by filter - * @param filter the {@link Filter} whose match defines to be used {@link CtExecutable} - */ - public void setReturnExpressionOfMethod(Filter> filter) { - CtMethod method = getOneByFilter(filter); - CtBlock body = method.getBody(); - if (body.getStatements().size() != 1) { - throw new SpoonException("The body of " + method.getSignature() + " must contain exactly one statement. But there is:\n" + body.toString()); - } - CtStatement firstStatement = body.getStatements().get(0); - if (firstStatement instanceof CtReturn == false) { - throw new SpoonException("The body of " + method.getSignature() + " must contain return statement. But there is:\n" + body.toString()); - } - setTemplateModel(((CtReturn) firstStatement).getReturnedExpression()); - } - - private List getByFilter(Filter filter) { - List elements = templateType.filterChildren(filter).list(); - if (elements == null || elements.isEmpty()) { - throw new SpoonException("Element not found in " + templateType.getShortRepresentation()); - } - return elements; - } - private T getOneByFilter(Filter filter) { - List elements = getByFilter(filter); - if (elements.size() != 1) { - throw new SpoonException("Only one element must be selected, but there are: " + elements); - } - return elements.get(0); - } - /** - * @param filter whose matches will be removed from the template model - */ - public TemplateModelBuilder removeTypeMembers(Filter filter) { - for (CtTypeMember ctTypeMember : new ArrayList<>(getClonedTemplateType().getTypeMembers())) { - if (filter.matches(ctTypeMember)) { - ctTypeMember.delete(); - } - } - return this; - } - - /** - * Removes all type members which are annotated by `annotationClass` - */ - @SuppressWarnings({ "unchecked", "rawtypes" }) - public TemplateModelBuilder removeTypeMembersAnnotatedBy(Class... annotationClass) { - for (Class ac : annotationClass) { - removeTypeMembers(tm -> tm.getAnnotation((Class) ac) != null); - } - return this; - } - - /** - * @param filter whose matches will be kept in the template. All others will be removed - */ - public TemplateModelBuilder keepTypeMembers(Filter filter) { - for (CtTypeMember ctTypeMember : new ArrayList<>(getClonedTemplateType().getTypeMembers())) { - if (filter.matches(ctTypeMember) == false) { - ctTypeMember.delete(); - } - } - return this; - } - - /** - * Keeps only type members, which are annotated by `annotationClass`. All others will be removed - */ - public TemplateModelBuilder keepTypeMembersAnnotatedBy(Class annotationClass) { - keepTypeMembers(tm -> tm.getAnnotation(annotationClass) != null); - return this; - } - - /** - * removes super class from the template - */ - public TemplateModelBuilder removeSuperClass() { - getClonedTemplateType().setSuperclass(null); - return this; - } - - /** - * @param filter super interfaces which matches the filter will be removed - */ - public TemplateModelBuilder removeSuperInterfaces(Filter> filter) { - Set> superIfaces = new HashSet<>(getClonedTemplateType().getSuperInterfaces()); - boolean changed = false; - for (Iterator> iter = superIfaces.iterator(); iter.hasNext();) { - if (filter.matches(iter.next())) { - iter.remove(); - changed = true; - } - } - if (changed) { - getClonedTemplateType().setSuperInterfaces(superIfaces); - } - return this; - } - - /** - * @param filter super interfaces which matches the filter will be kept. Others will be removed - */ - public TemplateModelBuilder keepSuperInterfaces(Filter> filter) { - Set> superIfaces = new HashSet<>(getClonedTemplateType().getSuperInterfaces()); - boolean changed = false; - for (Iterator> iter = superIfaces.iterator(); iter.hasNext();) { - if (filter.matches(iter.next())) { - iter.remove(); - changed = true; - } - } - if (changed) { - getClonedTemplateType().setSuperInterfaces(superIfaces); - } - return this; - } - - /** - * @return a List of {@link CtElement}s, which has to be used as pattern model - */ - public List getTemplateModel() { - return templateModel; - } - - /** - * @param template a {@link CtElement}, which has to be used as pattern model - */ - public void setTemplateModel(CtElement template) { - this.templateModel = Collections.singletonList(template); - } - - /** - * @param template a List of {@link CtElement}s, which has to be used as pattern model - */ - @SuppressWarnings({ "unchecked", "rawtypes" }) - public void setTemplateModel(List template) { - this.templateModel = (List) template; - } - } - public PatternBuilder configureParameters(Consumer parametersBuilder) { ParametersBuilder pb = new ParametersBuilder(this, parameterInfos); parametersBuilder.accept(pb); diff --git a/src/main/java/spoon/pattern/TemplateModelBuilder.java b/src/main/java/spoon/pattern/TemplateModelBuilder.java new file mode 100644 index 00000000000..e945228f11a --- /dev/null +++ b/src/main/java/spoon/pattern/TemplateModelBuilder.java @@ -0,0 +1,297 @@ +/** + * 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.pattern; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Set; + +import spoon.SpoonException; +import spoon.reflect.code.CtBlock; +import spoon.reflect.code.CtReturn; +import spoon.reflect.code.CtStatement; +import spoon.reflect.declaration.CtAnnotation; +import spoon.reflect.declaration.CtElement; +import spoon.reflect.declaration.CtExecutable; +import spoon.reflect.declaration.CtMethod; +import spoon.reflect.declaration.CtType; +import spoon.reflect.declaration.CtTypeMember; +import spoon.reflect.reference.CtTypeReference; +import spoon.reflect.visitor.Filter; + +/** + * Builder which allows to define which part of AST of template {@link CtType} + * has to be used as a model of the {@link Pattern} + */ +public class TemplateModelBuilder { + /** + * The original type, which contains the AST of pattern model + */ + private final CtType templateType; + /** + * optional clone of templateType. It is created when AST of CtType has to be modified + * before it can become a model of {@link Pattern} + */ + private CtType clonedTemplateType; + /** + * holds the built pattern model + */ + private List templateModel = null; + + TemplateModelBuilder(CtType templateTemplate) { + this.templateType = templateTemplate; + } + + /** + * @return origin {@link CtType}, which is the source of the pattern model + */ + public CtType getTemplateType() { + return templateType; + } + + /** + * Returns clone of the templateType. + * The clone is done only once. Later calls returns cached clone. + * @return + */ + private CtType getClonedTemplateType() { + if (clonedTemplateType == null) { + clonedTemplateType = templateType.clone(); + if (templateType.isParentInitialized()) { + //set parent package, to keep origin qualified name of the Template. It is needed for correct substitution of Template name by target type reference + clonedTemplateType.setParent(templateType.getParent()); + } + } + return clonedTemplateType; + } + + /** + * Sets a template model from {@link CtTypeMember} of a template type + * @param typeMemberName the name of the {@link CtTypeMember} of a template type + */ + public TemplateModelBuilder setTypeMember(String typeMemberName) { + setTypeMember(tm -> typeMemberName.equals(tm.getSimpleName())); + return this; + } + /** + * Sets a template model from {@link CtTypeMember} of a template type + * @param filter the {@link Filter} whose match defines to be used {@link CtTypeMember} + */ + public TemplateModelBuilder setTypeMember(Filter filter) { + setTemplateModel(getByFilter(filter)); + return this; + } + + /** + * removes all annotations of type defined by `classes` from the clone of the source {@link CtType} + * @param classes list of classes which defines types of to be removed annotations + * @return this to support fluent API + */ + public TemplateModelBuilder removeTag(Class... classes) { + List elements = getClonedTemplateModel(); + for (Class class1 : classes) { + for (CtElement element : elements) { + CtAnnotation annotation = element.getAnnotation(element.getFactory().Type().createReference(class1)); + if (annotation != null) { + element.removeAnnotation(annotation); + } + } + } + return this; + } + + private List getClonedTemplateModel() { + if (templateModel == null) { + throw new SpoonException("Template model is not defined yet"); + } + for (ListIterator iter = templateModel.listIterator(); iter.hasNext();) { + CtElement ele = iter.next(); + if (ele.getRoleInParent() != null) { + iter.set(ele.clone()); + } + } + return templateModel; + } + + /** + * Sets a template model from body of the method of template type + * @param methodName the name of {@link CtMethod} + */ + public void setBodyOfMethod(String methodName) { + setBodyOfMethod(tm -> methodName.equals(tm.getSimpleName())); + } + /** + * Sets a template model from body of the method of template type selected by filter + * @param filter the {@link Filter} whose match defines to be used {@link CtMethod} + */ + public void setBodyOfMethod(Filter> filter) { + CtBlock body = getOneByFilter(filter).getBody(); + setTemplateModel(body.getStatements()); + } + + /** + * Sets a template model from return expression of the method of template type selected by filter + * @param methodName the name of {@link CtMethod} + */ + public void setReturnExpressionOfMethod(String methodName) { + setReturnExpressionOfMethod(tm -> methodName.equals(tm.getSimpleName())); + } + /** + * Sets a template model from return expression of the method of template type selected by filter + * @param filter the {@link Filter} whose match defines to be used {@link CtExecutable} + */ + public void setReturnExpressionOfMethod(Filter> filter) { + CtMethod method = getOneByFilter(filter); + CtBlock body = method.getBody(); + if (body.getStatements().size() != 1) { + throw new SpoonException("The body of " + method.getSignature() + " must contain exactly one statement. But there is:\n" + body.toString()); + } + CtStatement firstStatement = body.getStatements().get(0); + if (firstStatement instanceof CtReturn == false) { + throw new SpoonException("The body of " + method.getSignature() + " must contain return statement. But there is:\n" + body.toString()); + } + setTemplateModel(((CtReturn) firstStatement).getReturnedExpression()); + } + + private List getByFilter(Filter filter) { + List elements = templateType.filterChildren(filter).list(); + if (elements == null || elements.isEmpty()) { + throw new SpoonException("Element not found in " + templateType.getShortRepresentation()); + } + return elements; + } + private T getOneByFilter(Filter filter) { + List elements = getByFilter(filter); + if (elements.size() != 1) { + throw new SpoonException("Only one element must be selected, but there are: " + elements); + } + return elements.get(0); + } + /** + * @param filter whose matches will be removed from the template model + */ + public TemplateModelBuilder removeTypeMembers(Filter filter) { + for (CtTypeMember ctTypeMember : new ArrayList<>(getClonedTemplateType().getTypeMembers())) { + if (filter.matches(ctTypeMember)) { + ctTypeMember.delete(); + } + } + return this; + } + + /** + * Removes all type members which are annotated by `annotationClass` + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public TemplateModelBuilder removeTypeMembersAnnotatedBy(Class... annotationClass) { + for (Class ac : annotationClass) { + removeTypeMembers(tm -> tm.getAnnotation((Class) ac) != null); + } + return this; + } + + /** + * @param filter whose matches will be kept in the template. All others will be removed + */ + public TemplateModelBuilder keepTypeMembers(Filter filter) { + for (CtTypeMember ctTypeMember : new ArrayList<>(getClonedTemplateType().getTypeMembers())) { + if (filter.matches(ctTypeMember) == false) { + ctTypeMember.delete(); + } + } + return this; + } + + /** + * Keeps only type members, which are annotated by `annotationClass`. All others will be removed + */ + public TemplateModelBuilder keepTypeMembersAnnotatedBy(Class annotationClass) { + keepTypeMembers(tm -> tm.getAnnotation(annotationClass) != null); + return this; + } + + /** + * removes super class from the template + */ + public TemplateModelBuilder removeSuperClass() { + getClonedTemplateType().setSuperclass(null); + return this; + } + + /** + * @param filter super interfaces which matches the filter will be removed + */ + public TemplateModelBuilder removeSuperInterfaces(Filter> filter) { + Set> superIfaces = new HashSet<>(getClonedTemplateType().getSuperInterfaces()); + boolean changed = false; + for (Iterator> iter = superIfaces.iterator(); iter.hasNext();) { + if (filter.matches(iter.next())) { + iter.remove(); + changed = true; + } + } + if (changed) { + getClonedTemplateType().setSuperInterfaces(superIfaces); + } + return this; + } + + /** + * @param filter super interfaces which matches the filter will be kept. Others will be removed + */ + public TemplateModelBuilder keepSuperInterfaces(Filter> filter) { + Set> superIfaces = new HashSet<>(getClonedTemplateType().getSuperInterfaces()); + boolean changed = false; + for (Iterator> iter = superIfaces.iterator(); iter.hasNext();) { + if (filter.matches(iter.next())) { + iter.remove(); + changed = true; + } + } + if (changed) { + getClonedTemplateType().setSuperInterfaces(superIfaces); + } + return this; + } + + /** + * @return a List of {@link CtElement}s, which has to be used as pattern model + */ + public List getTemplateModel() { + return templateModel; + } + + /** + * @param template a {@link CtElement}, which has to be used as pattern model + */ + public void setTemplateModel(CtElement template) { + this.templateModel = Collections.singletonList(template); + } + + /** + * @param template a List of {@link CtElement}s, which has to be used as pattern model + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void setTemplateModel(List template) { + this.templateModel = (List) template; + } +} From 26b1f26ce4bcd062afaf98ec42d2ad4fe2deb54e Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Wed, 21 Mar 2018 19:43:07 +0100 Subject: [PATCH 045/131] fix docu --- doc/template_definition.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/template_definition.md b/doc/template_definition.md index c53f3267574..72e9613513b 100644 --- a/doc/template_definition.md +++ b/doc/template_definition.md @@ -54,8 +54,9 @@ spoonTemplate.forEachMatch(spoonFactory.getRootPackage(), (Match match) -> { There are several ways how to build a **spoon template** -A) Using a regular java class, which implements a `Template` interface -B) Using PatternBuilder, which takes any part of code and where you +* Using a regular java class, which implements a `Template` interface + +* Using PatternBuilder, which takes any part of code and where you define which parts of that code are **template parameters** by calling PatternBuilder methods. The `Template` interface based definitions are statically type-checked, in order to ensure statically that the generated code will be correct. From 9f8b8bfdc9134d7e2c32a6de1d09e8a52402af3a Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Wed, 21 Mar 2018 22:45:35 +0100 Subject: [PATCH 046/131] work on API and tests --- .../java/spoon/pattern/ParametersBuilder.java | 10 +-- .../java/spoon/pattern/PatternBuilder.java | 44 +++------- .../java/spoon/pattern/TemplateBuilder.java | 8 +- .../spoon/pattern/TemplateModelBuilder.java | 17 ++-- .../spoon/test/template/CodeReplaceTest.java | 85 +++++++++---------- .../test/template/TemplateMatcherTest.java | 3 +- .../spoon/test/template/TemplateTest.java | 17 ++-- .../testclasses/match/MatchForEach.java | 7 +- .../testclasses/match/MatchForEach2.java | 6 +- .../testclasses/match/MatchIfElse.java | 5 +- .../template/testclasses/match/MatchMap.java | 8 +- .../testclasses/match/MatchModifiers.java | 5 +- .../testclasses/match/MatchMultiple.java | 5 +- .../testclasses/match/MatchMultiple2.java | 5 +- .../testclasses/match/MatchMultiple3.java | 5 +- .../match/MatchWithParameterCondition.java | 5 +- .../match/MatchWithParameterType.java | 5 +- .../testclasses/replace/NewPattern.java | 8 +- .../testclasses/replace/OldPattern.java | 11 +-- 19 files changed, 133 insertions(+), 126 deletions(-) diff --git a/src/main/java/spoon/pattern/ParametersBuilder.java b/src/main/java/spoon/pattern/ParametersBuilder.java index 0e9d45c9920..99782e0a0cc 100644 --- a/src/main/java/spoon/pattern/ParametersBuilder.java +++ b/src/main/java/spoon/pattern/ParametersBuilder.java @@ -266,21 +266,21 @@ public ParametersBuilder byInvocation(CtMethod method) { } /** - * Add parameters for each field reference of variable named `variableName` + * Add parameters for each field reference to variable named `variableName` * @param variableName the name of the variable reference * @return {@link ParametersBuilder} to support fluent API */ - public ParametersBuilder parametersByVariable(String... variableName) { + public ParametersBuilder createPatternParameterForVariable(String... variableName) { for (String varName : variableName) { CtVariable var = queryModel().map(new PotentialVariableDeclarationFunction(varName)).first(); if (var != null) { - parametersByVariable(var); + createPatternParameterForVariable(var); } else { List> vars = queryModel().filterChildren(new NamedElementFilter(CtVariable.class, varName)).list(); if (vars.size() > 1) { throw new SpoonException("Ambiguous variable " + varName); } else if (vars.size() == 1) { - parametersByVariable(vars.get(0)); + createPatternParameterForVariable(vars.get(0)); } //else may be we should fail when variable is not found? } } @@ -291,7 +291,7 @@ public ParametersBuilder parametersByVariable(String... variableName) { * @param variable to be substituted variable * @return this to support fluent API */ - public ParametersBuilder parametersByVariable(CtVariable variable) { + public ParametersBuilder createPatternParameterForVariable(CtVariable variable) { CtQueryable searchScope; if (patternBuilder.isInModel(variable)) { addSubstitutionRequest( diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index 8b36a48084e..56deff57856 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -68,37 +68,8 @@ public class PatternBuilder { public static final String TARGET_TYPE = "targetType"; - public static PatternBuilder create(Factory factory, Class templateClass) { - return create(factory, templateClass, null); - } - - /** - * Creates a pattern builder. - * - * @param factory - * @param templateClass - * @param selector the code which selects part of templateClass AST, which has to be used as template model - * @return new instance of {@link PatternBuilder} - */ - public static PatternBuilder create(Factory factory, Class templateClass, Consumer selector) { - return create(factory.Type().get(templateClass), selector); - } - - public static PatternBuilder create(CtType templateType) { - return create(templateType, null); - } - - public static PatternBuilder create(CtType templateType, Consumer selector) { - checkTemplateType(templateType); - List templateModel; - if (selector != null) { - TemplateModelBuilder model = new TemplateModelBuilder(templateType); - selector.accept(model); - templateModel = model.getTemplateModel(); - } else { - templateModel = Collections.singletonList(templateType); - } - return create(templateType.getReference(), templateModel); + public static PatternBuilder create(CtType templateType, CtElement... elems) { + return create(templateType.getReference(), Arrays.asList(elems)); } /** @@ -113,8 +84,13 @@ public static PatternBuilder create(CtTypeReference templateTypeRef, List type, List patternModel) { + return new PatternBuilder(type.getReference(), patternModel); + } + + + public static PatternBuilder create(CtTypeReference templateTypeRef, CtElement... elems) { + return new PatternBuilder(templateTypeRef, Arrays.asList(elems)); } private final List patternModel; @@ -343,7 +319,7 @@ public PatternBuilder setDefaultValueConvertor(ValueConvertor valueConvertor) { * are automatically marked as pattern parameters * @return this to support fluent API */ - public PatternBuilder configureAutomaticParameters() { + public PatternBuilder createPatternParameters() { configureParameters(pb -> { //add this substitution request only if there isn't another one yet pb.setConflictResolutionMode(ConflictResolutionMode.KEEP_OLD_NODE); diff --git a/src/main/java/spoon/pattern/TemplateBuilder.java b/src/main/java/spoon/pattern/TemplateBuilder.java index 2dd2c38a7d1..475a85f3bac 100644 --- a/src/main/java/spoon/pattern/TemplateBuilder.java +++ b/src/main/java/spoon/pattern/TemplateBuilder.java @@ -71,7 +71,8 @@ public static TemplateBuilder createPattern(CtElement templateRoot, CtClass t CtTypeReference templateParamRef = f.Type().createReference(TemplateParameter.class); if (templateType == templateRoot) { //templateRoot is a class which extends from Template. We have to remove all Templating stuff from the patter model - pb = PatternBuilder.create(templateType, tv -> { + TemplateModelBuilder tv = new TemplateModelBuilder(templateType); + { tv.keepTypeMembers(typeMember -> { if (typeMember.getAnnotation(Parameter.class) != null) { //remove all type members annotated with @Parameter @@ -90,9 +91,10 @@ public static TemplateBuilder createPattern(CtElement templateRoot, CtClass t }); //remove `... extends Template`, which doesn't have to be part of pattern model tv.removeSuperClass(); - }); + }; + pb = PatternBuilder.create(templateType.getReference(), tv.getTemplateModels()); } else { - pb = PatternBuilder.create(templateType, model -> model.setTemplateModel(templateRoot)); + pb = PatternBuilder.create(templateType.getReference(), templateRoot); } Map templateParameters = template == null ? null : Parameters.getTemplateParametersAsMap(f, null, template); //legacy templates always automatically simplifies generated code diff --git a/src/main/java/spoon/pattern/TemplateModelBuilder.java b/src/main/java/spoon/pattern/TemplateModelBuilder.java index e945228f11a..4f908a34407 100644 --- a/src/main/java/spoon/pattern/TemplateModelBuilder.java +++ b/src/main/java/spoon/pattern/TemplateModelBuilder.java @@ -39,8 +39,7 @@ import spoon.reflect.visitor.Filter; /** - * Builder which allows to define which part of AST of template {@link CtType} - * has to be used as a model of the {@link Pattern} + * Utility class to select parts of AST to be used as a model of a {@link Pattern}. */ public class TemplateModelBuilder { /** @@ -57,17 +56,10 @@ public class TemplateModelBuilder { */ private List templateModel = null; - TemplateModelBuilder(CtType templateTemplate) { + public TemplateModelBuilder(CtType templateTemplate) { this.templateType = templateTemplate; } - /** - * @return origin {@link CtType}, which is the source of the pattern model - */ - public CtType getTemplateType() { - return templateType; - } - /** * Returns clone of the templateType. * The clone is done only once. Later calls returns cached clone. @@ -136,8 +128,9 @@ private List getClonedTemplateModel() { * Sets a template model from body of the method of template type * @param methodName the name of {@link CtMethod} */ - public void setBodyOfMethod(String methodName) { + public TemplateModelBuilder setBodyOfMethod(String methodName) { setBodyOfMethod(tm -> methodName.equals(tm.getSimpleName())); + return this; } /** * Sets a template model from body of the method of template type selected by filter @@ -276,7 +269,7 @@ public TemplateModelBuilder keepSuperInterfaces(Filter> filte /** * @return a List of {@link CtElement}s, which has to be used as pattern model */ - public List getTemplateModel() { + public List getTemplateModels() { return templateModel; } diff --git a/src/test/java/spoon/test/template/CodeReplaceTest.java b/src/test/java/spoon/test/template/CodeReplaceTest.java index 8fff747c752..63a8ddaca89 100644 --- a/src/test/java/spoon/test/template/CodeReplaceTest.java +++ b/src/test/java/spoon/test/template/CodeReplaceTest.java @@ -5,10 +5,14 @@ import spoon.Launcher; import spoon.OutputType; import spoon.SpoonModelBuilder; +import spoon.pattern.ParametersBuilder; import spoon.pattern.Pattern; import spoon.pattern.PatternBuilder; +import spoon.pattern.TemplateModelBuilder; +import spoon.pattern.matcher.Match; import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.declaration.CtClass; +import spoon.reflect.declaration.CtType; import spoon.reflect.factory.Factory; import spoon.reflect.meta.ContainerKind; import spoon.reflect.visitor.DefaultJavaPrettyPrinter; @@ -20,29 +24,14 @@ import static org.junit.Assert.*; import java.io.File; +import java.util.List; public class CodeReplaceTest { - /** - * @param factory a to be used factory - * @return a Pattern instance of this Pattern - */ - public static Pattern createPattern(Factory factory) { - return PatternBuilder - //Create a pattern from all statements of OldPattern_ParamsInNestedType#patternModel - .create(factory, OldPattern.class, model->model.setBodyOfMethod("patternModel")) - .configureParameters(pb->pb - .parametersByVariable("params", "item") - .parameter("statements").setContainerKind(ContainerKind.LIST) - ) - .configureAutomaticParameters() - .configureInlineStatements(ls -> ls.byVariableName("useStartKeyword")) - .build(); - } - @Test public void testMatchSample1() throws Exception { + // contract: a complex pattern is well matched twice Factory f = ModelUtils.build( new File("./src/test/java/spoon/test/template/testclasses/replace/DPPSample1.java"), new File("./src/test/java/spoon/test/template/testclasses/replace") @@ -50,33 +39,41 @@ public void testMatchSample1() throws Exception { CtClass classDJPP = f.Class().get(DPPSample1.class); assertNotNull(classDJPP); assertFalse(classDJPP.isShadow()); - Pattern p = createPattern(f); - class Context { - int count = 0; - } - Context context = new Context(); - p.forEachMatch(classDJPP, (match) -> { - ParameterValueProvider params = match.getParameters(); - if (context.count == 0) { - assertEquals("\"extends\"", params.getValue("startKeyword").toString()); - assertEquals(Boolean.TRUE, params.getValue("useStartKeyword")); - } else { - assertEquals(null, params.getValue("startKeyword")); - assertEquals(Boolean.FALSE, params.getValue("useStartKeyword")); - } - assertEquals("false", params.getValue("startPrefixSpace").toString()); - assertEquals("null", params.getValue("start").toString()); - assertEquals("false", params.getValue("startSuffixSpace").toString()); - assertEquals("false", params.getValue("nextPrefixSpace").toString()); - assertEquals("\",\"", params.getValue("next").toString()); - assertEquals("true", params.getValue("nextSuffixSpace").toString()); - assertEquals("false", params.getValue("endPrefixSpace").toString()); - assertEquals("\";\"", params.getValue("end").toString()); - assertEquals("ctEnum.getEnumValues()", params.getValue("getIterable").toString()); - assertEquals("[scan(enumValue)]", params.getValue("statements").toString()); - context.count++; - }); - assertEquals(2, context.count); + + CtType type = f.Type().get(OldPattern.class); + Pattern p = PatternBuilder + //Create a pattern from all statements of OldPattern_ParamsInNestedType#patternModel + .create(type, new TemplateModelBuilder(type).setBodyOfMethod("patternModel").getTemplateModels()) + .configureParameters((ParametersBuilder pb) -> pb + // creating patterns parameters for all references to "params" and "items" + .createPatternParameterForVariable("params", "item") + .parameter("statements").setContainerKind(ContainerKind.LIST) + ) + .createPatternParameters() + .configureInlineStatements(ls -> ls.byVariableName("useStartKeyword")) + .build(); + + // so let's try to match this complex pattern on DJPP + List matches = p.getMatches(classDJPP); + + // there are two results (the try-with-resource in each method + assertEquals(2, matches.size()); + ParameterValueProvider params = matches.get(0).getParameters(); + assertEquals("\"extends\"", params.getValue("startKeyword").toString()); + assertEquals(Boolean.TRUE, params.getValue("useStartKeyword")); + + params = matches.get(1).getParameters(); + // all method arguments to createListPrinter have been matched + assertEquals("false", params.getValue("startPrefixSpace").toString()); + assertEquals("null", params.getValue("start").toString()); + assertEquals("false", params.getValue("startSuffixSpace").toString()); + assertEquals("false", params.getValue("nextPrefixSpace").toString()); + assertEquals("\",\"", params.getValue("next").toString()); + assertEquals("true", params.getValue("nextSuffixSpace").toString()); + assertEquals("false", params.getValue("endPrefixSpace").toString()); + assertEquals("\";\"", params.getValue("end").toString()); + assertEquals("ctEnum.getEnumValues()", params.getValue("getIterable").toString()); + assertEquals("[scan(enumValue)]", params.getValue("statements").toString()); } @Test diff --git a/src/test/java/spoon/test/template/TemplateMatcherTest.java b/src/test/java/spoon/test/template/TemplateMatcherTest.java index 16d428acb6c..f556f00e275 100644 --- a/src/test/java/spoon/test/template/TemplateMatcherTest.java +++ b/src/test/java/spoon/test/template/TemplateMatcherTest.java @@ -22,6 +22,7 @@ import spoon.pattern.ConflictResolutionMode; import spoon.pattern.Pattern; import spoon.pattern.PatternBuilder; +import spoon.pattern.TemplateModelBuilder; import spoon.pattern.matcher.Match; import spoon.pattern.matcher.Quantifier; import spoon.pattern.parameter.ParameterValueProvider; @@ -868,7 +869,7 @@ public void testMatchInSet() throws Exception { //contract: match elements in container of type Set - e.g method throwables CtType ctClass = ModelUtils.buildClass(MatchThrowables.class); Factory f = ctClass.getFactory(); - Pattern pattern = PatternBuilder.create(f, MatchThrowables.class, tmb -> tmb.setTypeMember("matcher1")) + Pattern pattern = PatternBuilder.create(ctClass, new TemplateModelBuilder(ctClass).setTypeMember("matcher1").getTemplateModels()) .configureParameters(pb -> { pb.parameter("otherThrowables") //add matcher for other arbitrary throwables diff --git a/src/test/java/spoon/test/template/TemplateTest.java b/src/test/java/spoon/test/template/TemplateTest.java index 854a94e47c5..3bf9a21696b 100644 --- a/src/test/java/spoon/test/template/TemplateTest.java +++ b/src/test/java/spoon/test/template/TemplateTest.java @@ -5,8 +5,8 @@ import spoon.SpoonException; import spoon.compiler.SpoonResourceHelper; import spoon.pattern.PatternBuilder; +import spoon.pattern.TemplateModelBuilder; import spoon.pattern.matcher.Match; -import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtExpression; import spoon.reflect.code.CtForEach; @@ -75,7 +75,6 @@ import java.rmi.Remote; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.IdentityHashMap; @@ -671,12 +670,14 @@ public void testExtensionDecoupledSubstitutionVisitor() throws Exception { params.put("_methodName_", factory.Code().createLiteral(toBeLoggedMethod.getSimpleName())); params.put("_block_", toBeLoggedMethod.getBody()); //create a patter from the LoggerModel#block - spoon.pattern.Pattern pattern = PatternBuilder.create(launcher.getFactory(), LoggerModel.class, - //type member named "block" of LoggerModel is used as pattern model of this pattern - model -> model.setTypeMember("block")) + CtType type = factory.Type().get(LoggerModel.class); + + + // creating a pattern from method "block" + spoon.pattern.Pattern pattern = PatternBuilder.create(type, type.getMethodsByName("block").get(0)) //all the variable references which are declared out of type member "block" are automatically considered //as pattern parameters - .configureAutomaticParameters() + .createPatternParameters() .build(); final List aMethods = pattern.applyToType(aTargetType, CtMethod.class, params); assertEquals(1, aMethods.size()); @@ -1181,7 +1182,7 @@ public void testTemplateMatchOfMultipleElements() throws Exception { // creating a Pattern from a Literal, with zero pattern parameters // The pattern model consists of one CtLIteral only // there is not needed any type reference, because CtLiteral has no reference to a type where it is defined - spoon.pattern.Pattern p = PatternBuilder.create(null, f.createLiteral("a")).build(); + spoon.pattern.Pattern p = PatternBuilder.create((CtType)null, f.createLiteral("a")).build(); //The pattern has no parameters. There is just one constant CtLiteral assertEquals (0, p.getParameterInfos().size()); @@ -1245,7 +1246,7 @@ public void testTemplateMatchOfMultipleElements() throws Exception { } private static spoon.pattern.Pattern patternOfStringLiterals(Factory f, String... strs) { - return PatternBuilder.create(null, Arrays.asList(strs).stream().map(s -> f.createLiteral(s)).collect(Collectors.toList()) + return PatternBuilder.create((CtType)null, Arrays.asList(strs).stream().map(s -> f.createLiteral(s)).collect(Collectors.toList()) ).build(); } diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchForEach.java b/src/test/java/spoon/test/template/testclasses/match/MatchForEach.java index dc6fa560fa3..3db92f48629 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchForEach.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchForEach.java @@ -4,15 +4,20 @@ import spoon.pattern.Pattern; import spoon.pattern.PatternBuilder; +import spoon.pattern.TemplateModelBuilder; +import spoon.reflect.declaration.CtType; import spoon.reflect.factory.Factory; import spoon.reflect.meta.ContainerKind; +import spoon.test.template.testclasses.replace.OldPattern; import static java.lang.System.out; public class MatchForEach { public static Pattern createPattern(Factory factory) { - return PatternBuilder.create(factory, MatchForEach.class, tmb -> tmb.setBodyOfMethod("matcher1")) + CtType type = factory.Type().get(MatchForEach.class); + + return PatternBuilder.create(type, new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) .configureParameters(pb -> { pb.parameter("values").byVariable("values").setContainerKind(ContainerKind.LIST); }) diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchForEach2.java b/src/test/java/spoon/test/template/testclasses/match/MatchForEach2.java index 9e312ee2cfb..fad7087c731 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchForEach2.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchForEach2.java @@ -4,6 +4,8 @@ import spoon.pattern.Pattern; import spoon.pattern.PatternBuilder; +import spoon.pattern.TemplateModelBuilder; +import spoon.reflect.declaration.CtType; import spoon.reflect.factory.Factory; import spoon.reflect.meta.ContainerKind; @@ -12,7 +14,9 @@ public class MatchForEach2 { public static Pattern createPattern(Factory factory) { - return PatternBuilder.create(factory, MatchForEach2.class, tmb -> tmb.setBodyOfMethod("matcher1")) + CtType type = factory.Type().get(MatchForEach2.class); + + return PatternBuilder.create(type, new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) .configureParameters(pb -> { pb.parameter("values").byVariable("values").setContainerKind(ContainerKind.LIST); pb.parameter("varName").byString("var"); diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchIfElse.java b/src/test/java/spoon/test/template/testclasses/match/MatchIfElse.java index 4aa0c5a7ed6..d68ecad6dd7 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchIfElse.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchIfElse.java @@ -2,7 +2,9 @@ import spoon.pattern.Pattern; import spoon.pattern.PatternBuilder; +import spoon.pattern.TemplateModelBuilder; import spoon.reflect.code.CtLiteral; +import spoon.reflect.declaration.CtType; import spoon.reflect.factory.Factory; import spoon.reflect.visitor.filter.TypeFilter; @@ -11,7 +13,8 @@ public class MatchIfElse { public static Pattern createPattern(Factory factory) { - return PatternBuilder.create(factory, MatchIfElse.class, tmb -> tmb.setBodyOfMethod("matcher1")) + CtType type = factory.Type().get(MatchIfElse.class); + return PatternBuilder.create(type, new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) .configureParameters(pb -> { pb.parameter("option").byVariable("option"); pb.parameter("option2").byVariable("option2"); diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchMap.java b/src/test/java/spoon/test/template/testclasses/match/MatchMap.java index c4fb4a73d10..b169d641fb0 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchMap.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchMap.java @@ -3,9 +3,11 @@ import spoon.pattern.ConflictResolutionMode; import spoon.pattern.Pattern; import spoon.pattern.PatternBuilder; +import spoon.pattern.TemplateModelBuilder; import spoon.reflect.code.CtLiteral; import spoon.reflect.declaration.CtAnnotation; import spoon.reflect.declaration.CtMethod; +import spoon.reflect.declaration.CtType; import spoon.reflect.factory.Factory; import spoon.reflect.meta.ContainerKind; import spoon.reflect.path.CtRole; @@ -14,7 +16,8 @@ public class MatchMap { public static Pattern createPattern(Factory factory, boolean acceptOtherAnnotations) { - return PatternBuilder.create(factory, MatchMap.class, tmb -> tmb.setTypeMember("matcher1")) + CtType type = factory.Type().get(MatchMap.class); + return PatternBuilder.create(type, new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) .configureParameters(pb -> { //match any value of @Check annotation to parameter `testAnnotations` pb.parameter("CheckAnnotationValues").attributeOfElementByFilter(CtRole.VALUE, new TypeFilter(CtAnnotation.class)).setContainerKind(ContainerKind.MAP); @@ -30,7 +33,8 @@ public static Pattern createPattern(Factory factory, boolean acceptOtherAnnotati .build(); } public static Pattern createMatchKeyPattern(Factory factory) { - return PatternBuilder.create(factory, MatchMap.class, tmb -> tmb.setTypeMember("m1")) + CtType type = factory.Type().get(MatchMap.class); + return PatternBuilder.create(type, new TemplateModelBuilder(type).setBodyOfMethod("m1").getTemplateModels()) .configureParameters(pb -> { //match any value of @Check annotation to parameter `testAnnotations` pb.parameter("CheckKey").bySubstring("value"); diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchModifiers.java b/src/test/java/spoon/test/template/testclasses/match/MatchModifiers.java index e6d605bf7f7..ec8615d2bd4 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchModifiers.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchModifiers.java @@ -2,8 +2,10 @@ import spoon.pattern.Pattern; import spoon.pattern.PatternBuilder; +import spoon.pattern.TemplateModelBuilder; import spoon.reflect.code.CtBlock; import spoon.reflect.declaration.CtMethod; +import spoon.reflect.declaration.CtType; import spoon.reflect.factory.Factory; import spoon.reflect.path.CtRole; import spoon.reflect.visitor.filter.TypeFilter; @@ -11,7 +13,8 @@ public class MatchModifiers { public static Pattern createPattern(Factory factory, boolean matchBody) { - return PatternBuilder.create(factory, MatchModifiers.class, tmb -> tmb.setTypeMember("matcher1")) + CtType type = factory.Type().get(MatchModifiers.class); + return PatternBuilder.create(type, new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) .configureParameters(pb -> { pb.parameter("modifiers").attributeOfElementByFilter(CtRole.MODIFIER, new TypeFilter(CtMethod.class)); pb.parameter("methodName").byString("matcher1"); diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple.java b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple.java index a0133e6d98b..7c4e30103ad 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple.java @@ -2,8 +2,10 @@ import spoon.pattern.Pattern; import spoon.pattern.PatternBuilder; +import spoon.pattern.TemplateModelBuilder; import spoon.pattern.matcher.Quantifier; import spoon.reflect.code.CtLiteral; +import spoon.reflect.declaration.CtType; import spoon.reflect.factory.Factory; import spoon.reflect.meta.ContainerKind; @@ -12,7 +14,8 @@ public class MatchMultiple { public static Pattern createPattern(Factory factory, Quantifier matchingStrategy, Integer minCount, Integer maxCount) { - return PatternBuilder.create(factory, MatchMultiple.class, tmb -> tmb.setBodyOfMethod("matcher1")) + CtType type = factory.Type().get(MatchMultiple.class); + return PatternBuilder.create(type, new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) .configureParameters(pb -> { pb.parameter("statements").bySimpleName("statements").setContainerKind(ContainerKind.LIST); if (matchingStrategy != null) { diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple2.java b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple2.java index 7e7d8120743..3addfbc60b6 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple2.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple2.java @@ -3,6 +3,8 @@ import spoon.pattern.ParametersBuilder; import spoon.pattern.Pattern; import spoon.pattern.PatternBuilder; +import spoon.pattern.TemplateModelBuilder; +import spoon.reflect.declaration.CtType; import spoon.reflect.factory.Factory; import spoon.reflect.meta.ContainerKind; import spoon.template.TemplateParameter; @@ -15,7 +17,8 @@ public class MatchMultiple2 { public static Pattern createPattern(Factory factory, Consumer cfgParams) { - return PatternBuilder.create(factory, MatchMultiple2.class, tmb -> tmb.setBodyOfMethod("matcher1")) + CtType type = factory.Type().get(MatchMultiple2.class); + return PatternBuilder.create(type, new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) .configureTemplateParameters() .configureParameters(pb -> { pb.parameter("statements1").setContainerKind(ContainerKind.LIST); diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple3.java b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple3.java index 744f482a0bd..966a8077432 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple3.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple3.java @@ -3,7 +3,9 @@ import spoon.pattern.ParametersBuilder; import spoon.pattern.Pattern; import spoon.pattern.PatternBuilder; +import spoon.pattern.TemplateModelBuilder; import spoon.reflect.code.CtLiteral; +import spoon.reflect.declaration.CtType; import spoon.reflect.factory.Factory; import spoon.reflect.meta.ContainerKind; import spoon.template.TemplateParameter; @@ -15,7 +17,8 @@ public class MatchMultiple3 { public static Pattern createPattern(Factory factory, Consumer cfgParams) { - return PatternBuilder.create(factory, MatchMultiple3.class, tmb -> tmb.setBodyOfMethod("matcher1")) + CtType type = factory.Type().get(MatchMultiple3.class); + return PatternBuilder.create(type, new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) .configureTemplateParameters() .configureParameters(pb -> { pb.parameter("statements1").setContainerKind(ContainerKind.LIST); diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterCondition.java b/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterCondition.java index 3a2871fcb23..d53fbf26f26 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterCondition.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterCondition.java @@ -2,6 +2,8 @@ import spoon.pattern.Pattern; import spoon.pattern.PatternBuilder; +import spoon.pattern.TemplateModelBuilder; +import spoon.reflect.declaration.CtType; import spoon.reflect.factory.Factory; import static java.lang.System.out; @@ -10,7 +12,8 @@ public class MatchWithParameterCondition { public static Pattern createPattern(Factory factory, Predicate condition) { - return PatternBuilder.create(factory, MatchWithParameterCondition.class, tmb -> tmb.setBodyOfMethod("matcher1")) + CtType type = factory.Type().get(MatchWithParameterCondition.class); + return PatternBuilder.create(type, new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) .configureParameters(pb -> { pb.parameter("value").byVariable("value"); if (condition != null) { diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterType.java b/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterType.java index 87b91c3152c..3050f39c0cd 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterType.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterType.java @@ -2,13 +2,16 @@ import spoon.pattern.Pattern; import spoon.pattern.PatternBuilder; +import spoon.pattern.TemplateModelBuilder; +import spoon.reflect.declaration.CtType; import spoon.reflect.factory.Factory; import static java.lang.System.out; public class MatchWithParameterType { public static Pattern createPattern(Factory factory, Class valueType) { - return PatternBuilder.create(factory, MatchWithParameterType.class, tmb -> tmb.setBodyOfMethod("matcher1")) + CtType type = factory.Type().get(MatchWithParameterType.class); + return PatternBuilder.create(type, new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) .configureParameters(pb -> { pb.parameter("value").byVariable("value"); if (valueType != null) { diff --git a/src/test/java/spoon/test/template/testclasses/replace/NewPattern.java b/src/test/java/spoon/test/template/testclasses/replace/NewPattern.java index fb60a10db07..b743e46e8de 100644 --- a/src/test/java/spoon/test/template/testclasses/replace/NewPattern.java +++ b/src/test/java/spoon/test/template/testclasses/replace/NewPattern.java @@ -4,12 +4,14 @@ import spoon.pattern.Pattern; import spoon.pattern.PatternBuilder; +import spoon.pattern.TemplateModelBuilder; import spoon.reflect.declaration.CtElement; import spoon.reflect.declaration.CtType; import spoon.reflect.factory.Factory; import spoon.reflect.meta.ContainerKind; import spoon.reflect.meta.RoleHandler; import spoon.reflect.meta.impl.RoleHandlerHelper; +import spoon.test.template.testclasses.match.MatchModifiers; public class NewPattern { @@ -35,9 +37,9 @@ private void patternModel(OldPattern.Parameters params) throws Exception { * Creates a Pattern for this model */ public static Pattern createPattern(Factory factory) { - return PatternBuilder - .create(factory, NewPattern.class, model->model.setBodyOfMethod("patternModel")) - .configureAutomaticParameters() + CtType type = factory.Type().get(NewPattern.class); + return PatternBuilder.create(type, new TemplateModelBuilder(type).setBodyOfMethod("patternModel").getTemplateModels()) + .createPatternParameters() .configureParameters(pb -> { pb.parameter("statements").setContainerKind(ContainerKind.LIST); }) diff --git a/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java b/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java index 8f1a28ec4de..2a5fedb9dce 100644 --- a/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java +++ b/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java @@ -2,8 +2,10 @@ import spoon.pattern.Pattern; import spoon.pattern.PatternBuilder; +import spoon.pattern.TemplateModelBuilder; import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtInvocation; +import spoon.reflect.declaration.CtType; import spoon.reflect.factory.Factory; import spoon.reflect.meta.ContainerKind; import spoon.reflect.reference.CtTypeReference; @@ -61,14 +63,13 @@ void patternModel(Parameters params) throws Exception { * @return a Pattern instance of this Pattern */ public static Pattern createPattern(Factory factory) { - return PatternBuilder - //Create a pattern from all statements of OldPattern_ParamsInNestedType#patternModel - .create(factory, OldPattern.class, model->model.setBodyOfMethod("patternModel")) + CtType type = factory.Type().get(OldPattern.class); + return PatternBuilder.create(type, new TemplateModelBuilder(type).setBodyOfMethod("patternModel").getTemplateModels()) .configureParameters(pb->pb - .parametersByVariable("params", "item") + .createPatternParameterForVariable("params", "item") .parameter("statements").setContainerKind(ContainerKind.LIST) ) - .configureAutomaticParameters() + .createPatternParameters() .configureInlineStatements(ls -> ls.byVariableName("useStartKeyword")) .build(); } From a692251e8c45e67a86f2a47122ec1e4320d4d129 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Fri, 23 Mar 2018 19:33:35 +0100 Subject: [PATCH 047/131] up --- .../java/spoon/pattern/PatternBuilder.java | 6 + .../spoon/test/template/CodeReplaceTest.java | 100 --------------- .../java/spoon/test/template/PatternTest.java | 116 +++++++++++++++++- .../testclasses/replace/NewPattern.java | 20 +-- .../testclasses/replace/OldPattern.java | 2 +- 5 files changed, 120 insertions(+), 124 deletions(-) delete mode 100644 src/test/java/spoon/test/template/CodeReplaceTest.java diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index 56deff57856..52f65a77a27 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -266,6 +266,12 @@ public void setNodeOfAttributeOfElement(CtElement element, CtRole role, RootNode }); } + /** adds the given nodes to the pattern */ + public PatternBuilder selectNodes(TemplateModelBuilder selector) { + this.patternModel.addAll(selector.getTemplateModels()); + return this; + } + /** * @param element to be checked element * @return true if element `element` is a template or a child of template diff --git a/src/test/java/spoon/test/template/CodeReplaceTest.java b/src/test/java/spoon/test/template/CodeReplaceTest.java deleted file mode 100644 index 63a8ddaca89..00000000000 --- a/src/test/java/spoon/test/template/CodeReplaceTest.java +++ /dev/null @@ -1,100 +0,0 @@ -package spoon.test.template; - -import org.junit.Test; - -import spoon.Launcher; -import spoon.OutputType; -import spoon.SpoonModelBuilder; -import spoon.pattern.ParametersBuilder; -import spoon.pattern.Pattern; -import spoon.pattern.PatternBuilder; -import spoon.pattern.TemplateModelBuilder; -import spoon.pattern.matcher.Match; -import spoon.pattern.parameter.ParameterValueProvider; -import spoon.reflect.declaration.CtClass; -import spoon.reflect.declaration.CtType; -import spoon.reflect.factory.Factory; -import spoon.reflect.meta.ContainerKind; -import spoon.reflect.visitor.DefaultJavaPrettyPrinter; -import spoon.test.template.testclasses.replace.DPPSample1; -import spoon.test.template.testclasses.replace.NewPattern; -import spoon.test.template.testclasses.replace.OldPattern; -import spoon.testing.utils.ModelUtils; - -import static org.junit.Assert.*; - -import java.io.File; -import java.util.List; - -public class CodeReplaceTest { - - - @Test - public void testMatchSample1() throws Exception { - // contract: a complex pattern is well matched twice - Factory f = ModelUtils.build( - new File("./src/test/java/spoon/test/template/testclasses/replace/DPPSample1.java"), - new File("./src/test/java/spoon/test/template/testclasses/replace") - ); - CtClass classDJPP = f.Class().get(DPPSample1.class); - assertNotNull(classDJPP); - assertFalse(classDJPP.isShadow()); - - CtType type = f.Type().get(OldPattern.class); - Pattern p = PatternBuilder - //Create a pattern from all statements of OldPattern_ParamsInNestedType#patternModel - .create(type, new TemplateModelBuilder(type).setBodyOfMethod("patternModel").getTemplateModels()) - .configureParameters((ParametersBuilder pb) -> pb - // creating patterns parameters for all references to "params" and "items" - .createPatternParameterForVariable("params", "item") - .parameter("statements").setContainerKind(ContainerKind.LIST) - ) - .createPatternParameters() - .configureInlineStatements(ls -> ls.byVariableName("useStartKeyword")) - .build(); - - // so let's try to match this complex pattern on DJPP - List matches = p.getMatches(classDJPP); - - // there are two results (the try-with-resource in each method - assertEquals(2, matches.size()); - ParameterValueProvider params = matches.get(0).getParameters(); - assertEquals("\"extends\"", params.getValue("startKeyword").toString()); - assertEquals(Boolean.TRUE, params.getValue("useStartKeyword")); - - params = matches.get(1).getParameters(); - // all method arguments to createListPrinter have been matched - assertEquals("false", params.getValue("startPrefixSpace").toString()); - assertEquals("null", params.getValue("start").toString()); - assertEquals("false", params.getValue("startSuffixSpace").toString()); - assertEquals("false", params.getValue("nextPrefixSpace").toString()); - assertEquals("\",\"", params.getValue("next").toString()); - assertEquals("true", params.getValue("nextSuffixSpace").toString()); - assertEquals("false", params.getValue("endPrefixSpace").toString()); - assertEquals("\";\"", params.getValue("end").toString()); - assertEquals("ctEnum.getEnumValues()", params.getValue("getIterable").toString()); - assertEquals("[scan(enumValue)]", params.getValue("statements").toString()); - } - - @Test - public void testTemplateReplace() throws Exception { - Launcher launcher = new Launcher(); - final Factory factory = launcher.getFactory(); - factory.getEnvironment().setComplianceLevel(8); - factory.getEnvironment().setNoClasspath(true); - factory.getEnvironment().setCommentEnabled(true); - factory.getEnvironment().setAutoImports(true); - final SpoonModelBuilder compiler = launcher.createCompiler(factory); - compiler.addInputSource(new File("./src/main/java/spoon/reflect/visitor")); - compiler.addInputSource(new File("./src/test/java/spoon/test/template/testclasses/replace")); - compiler.build(); - CtClass classDJPP = factory.Class().get(DefaultJavaPrettyPrinter.class); - assertNotNull(classDJPP); - assertFalse(classDJPP.isShadow()); - NewPattern.replaceOldByNew(classDJPP); - - launcher.setSourceOutputDirectory(new File("./target/spooned-template-replace/")); - launcher.getModelBuilder().generateProcessedSourceFiles(OutputType.CLASSES); - } - -} diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index d052479dc5a..451d653c785 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -1,22 +1,43 @@ package spoon.test.template; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.io.File; import java.util.Arrays; import java.util.HashSet; +import java.util.List; import java.util.Map; -import org.junit.Ignore; import org.junit.Test; +import spoon.Launcher; +import spoon.OutputType; +import spoon.SpoonModelBuilder; +import spoon.pattern.ParametersBuilder; import spoon.pattern.Pattern; +import spoon.pattern.PatternBuilder; +import spoon.pattern.TemplateModelBuilder; +import spoon.pattern.matcher.Match; import spoon.pattern.parameter.ParameterInfo; +import spoon.pattern.parameter.ParameterValueProvider; +import spoon.reflect.declaration.CtClass; +import spoon.reflect.declaration.CtElement; +import spoon.reflect.declaration.CtType; import spoon.reflect.factory.Factory; +import spoon.reflect.meta.ContainerKind; +import spoon.reflect.meta.RoleHandler; +import spoon.reflect.meta.impl.RoleHandlerHelper; +import spoon.reflect.visitor.DefaultJavaPrettyPrinter; +import spoon.test.template.testclasses.replace.DPPSample1; +import spoon.test.template.testclasses.replace.NewPattern; import spoon.test.template.testclasses.replace.OldPattern; import spoon.testing.utils.ModelUtils; + +// main test of Spoon's patterns public class PatternTest { @Test @@ -26,14 +47,17 @@ public void testPatternParameters() { new File("./src/test/java/spoon/test/template/testclasses/replace/DPPSample1.java"), new File("./src/test/java/spoon/test/template/testclasses/replace") ); - Pattern p = OldPattern.createPattern(f); + Pattern p = OldPattern.createPatternFromOldPattern(f); Map parameterInfos = p.getParameterInfos(); - + + // the code in createPatternFromOldPattern creates 15 pattern parameters assertEquals(15, parameterInfos.size()); + // .. which are assertEquals(new HashSet<>(Arrays.asList("next","item","startPrefixSpace","printer","start", "statements","nextPrefixSpace","startSuffixSpace","elementPrinterHelper", "endPrefixSpace","startKeyword","useStartKeyword","end","nextSuffixSpace","getIterable" )), parameterInfos.keySet()); + // the map from getParameterInfos is consistent for (Map.Entry e : parameterInfos.entrySet()) { assertEquals(e.getKey(), e.getValue().getName()); } @@ -46,13 +70,97 @@ public void testPatternToString() { new File("./src/test/java/spoon/test/template/testclasses/replace/DPPSample1.java"), new File("./src/test/java/spoon/test/template/testclasses/replace") ); - Pattern p = OldPattern.createPattern(f); + Pattern p = OldPattern.createPatternFromOldPattern(f); String strOfPattern = p.toString(); Map parameterInfos = p.getParameterInfos(); assertEquals(15, parameterInfos.size()); + + // contract: all parameters from getParameterInfos are pretty-printed for (Map.Entry e : parameterInfos.entrySet()) { assertTrue("The parameter " + e.getKey() + " is missing", strOfPattern.indexOf("<= ${"+e.getKey()+"}")>=0); } } + + @Test + public void testMatchSample1() throws Exception { + // contract: a complex pattern is well matched twice + Factory f = ModelUtils.build( + new File("./src/test/java/spoon/test/template/testclasses/replace/DPPSample1.java"), + new File("./src/test/java/spoon/test/template/testclasses/replace") + ); + CtClass classDJPP = f.Class().get(DPPSample1.class); + assertNotNull(classDJPP); + assertFalse(classDJPP.isShadow()); + + CtType type = f.Type().get(OldPattern.class); + Pattern p = PatternBuilder + //Create a pattern from all statements of OldPattern_ParamsInNestedType#patternModel + .create(type, new TemplateModelBuilder(type).setBodyOfMethod("patternModel").getTemplateModels()) + .configureParameters((ParametersBuilder pb) -> pb + // creating patterns parameters for all references to "params" and "items" + .createPatternParameterForVariable("params", "item") + .parameter("statements").setContainerKind(ContainerKind.LIST) + ) + .createPatternParameters() + .configureInlineStatements(ls -> ls.byVariableName("useStartKeyword")) + .build(); + + // so let's try to match this complex pattern on DJPP + List matches = p.getMatches(classDJPP); + + // there are two results (the try-with-resource in each method + assertEquals(2, matches.size()); + ParameterValueProvider params = matches.get(0).getParameters(); + assertEquals("\"extends\"", params.getValue("startKeyword").toString()); + assertEquals(Boolean.TRUE, params.getValue("useStartKeyword")); + + params = matches.get(1).getParameters(); + // all method arguments to createListPrinter have been matched + assertEquals("false", params.getValue("startPrefixSpace").toString()); + assertEquals("null", params.getValue("start").toString()); + assertEquals("false", params.getValue("startSuffixSpace").toString()); + assertEquals("false", params.getValue("nextPrefixSpace").toString()); + assertEquals("\",\"", params.getValue("next").toString()); + assertEquals("true", params.getValue("nextSuffixSpace").toString()); + assertEquals("false", params.getValue("endPrefixSpace").toString()); + assertEquals("\";\"", params.getValue("end").toString()); + assertEquals("ctEnum.getEnumValues()", params.getValue("getIterable").toString()); + assertEquals("[scan(enumValue)]", params.getValue("statements").toString()); + } + + @Test + public void testTemplateReplace() throws Exception { + // contract: ?? + Launcher launcher = new Launcher(); + final Factory factory = launcher.getFactory(); + factory.getEnvironment().setComplianceLevel(8); + factory.getEnvironment().setNoClasspath(true); + factory.getEnvironment().setCommentEnabled(true); + factory.getEnvironment().setAutoImports(true); + final SpoonModelBuilder compiler = launcher.createCompiler(factory); + compiler.addInputSource(new File("./src/main/java/spoon/reflect/visitor")); + compiler.addInputSource(new File("./src/test/java/spoon/test/template/testclasses/replace")); + compiler.build(); + CtClass classDJPP = factory.Class().get(DefaultJavaPrettyPrinter.class); + assertNotNull(classDJPP); + assertFalse(classDJPP.isShadow()); + CtType targetType = (classDJPP instanceof CtType) ? (CtType) classDJPP : classDJPP.getParent(CtType.class); + Factory f = classDJPP.getFactory(); + + // we create two different patterns + Pattern newPattern = NewPattern.createPatternFromNewPattern(f); + Pattern oldPattern = OldPattern.createPatternFromOldPattern(f); + + oldPattern.forEachMatch(classDJPP, (match) -> { + CtElement matchingElement = match.getMatchingElement(CtElement.class, false); + RoleHandler role = RoleHandlerHelper.getRoleHandlerWrtParent(matchingElement); + List elements = newPattern.applyToType(targetType, (Class) role.getValueClass(), match.getParametersMap()); + match.replaceMatchesBy(elements); + }); + + launcher.setSourceOutputDirectory(new File("./target/spooned-template-replace/")); + launcher.getModelBuilder().generateProcessedSourceFiles(OutputType.CLASSES); + } + } diff --git a/src/test/java/spoon/test/template/testclasses/replace/NewPattern.java b/src/test/java/spoon/test/template/testclasses/replace/NewPattern.java index b743e46e8de..6b060765ff4 100644 --- a/src/test/java/spoon/test/template/testclasses/replace/NewPattern.java +++ b/src/test/java/spoon/test/template/testclasses/replace/NewPattern.java @@ -11,7 +11,6 @@ import spoon.reflect.meta.ContainerKind; import spoon.reflect.meta.RoleHandler; import spoon.reflect.meta.impl.RoleHandlerHelper; -import spoon.test.template.testclasses.match.MatchModifiers; public class NewPattern { @@ -36,7 +35,7 @@ private void patternModel(OldPattern.Parameters params) throws Exception { /** * Creates a Pattern for this model */ - public static Pattern createPattern(Factory factory) { + public static Pattern createPatternFromNewPattern(Factory factory) { CtType type = factory.Type().get(NewPattern.class); return PatternBuilder.create(type, new TemplateModelBuilder(type).setBodyOfMethod("patternModel").getTemplateModels()) .createPatternParameters() @@ -46,23 +45,6 @@ public static Pattern createPattern(Factory factory) { .build(); } - /** - * Looks for all matches of Pattern defined by `OldPattern_ParamsInNestedType.class` - * and replaces each match with code generated by Pattern defined by `NewPattern.class` - * @param rootElement the root element of AST whose children has to be transformed - */ - @SuppressWarnings("unchecked") - public static void replaceOldByNew(CtElement rootElement) { - CtType targetType = (rootElement instanceof CtType) ? (CtType) rootElement : rootElement.getParent(CtType.class); - Factory f = rootElement.getFactory(); - Pattern newPattern = NewPattern.createPattern(f); - Pattern oldPattern = OldPattern.createPattern(f); - oldPattern.forEachMatch(rootElement, (match) -> { - RoleHandler rh = RoleHandlerHelper.getRoleHandlerWrtParent(match.getMatchingElement(CtElement.class, false)); - match.replaceMatchesBy(newPattern.applyToType(targetType, (Class) rh.getValueClass(), match.getParametersMap())); - }); - } - /* * Helper type members */ diff --git a/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java b/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java index 2a5fedb9dce..d79210aaf78 100644 --- a/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java +++ b/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java @@ -62,7 +62,7 @@ void patternModel(Parameters params) throws Exception { * @param factory a to be used factory * @return a Pattern instance of this Pattern */ - public static Pattern createPattern(Factory factory) { + public static Pattern createPatternFromOldPattern(Factory factory) { CtType type = factory.Type().get(OldPattern.class); return PatternBuilder.create(type, new TemplateModelBuilder(type).setBodyOfMethod("patternModel").getTemplateModels()) .configureParameters(pb->pb From cdf1a57196c4ca9d5ba4b6dc28d3b5076f7b6e11 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Fri, 23 Mar 2018 19:59:49 +0100 Subject: [PATCH 048/131] up --- .../java/spoon/pattern/PatternBuilder.java | 3 +++ .../SpoonArchitectureEnforcerTest.java | 24 +++++++++++++++++++ .../java/spoon/test/template/PatternTest.java | 5 ++++ 3 files changed, 32 insertions(+) diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index 52f65a77a27..8de1b447768 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -352,6 +352,9 @@ public PatternBuilder configureParameters(Consumer parameters return this; } + /** + * TODO + */ public PatternBuilder configureLocalParameters(Consumer parametersBuilder) { ParametersBuilder pb = new ParametersBuilder(this, new HashMap<>()); parametersBuilder.accept(pb); diff --git a/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java b/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java index d6e9c6b8530..acc10e2db99 100644 --- a/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java +++ b/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java @@ -4,6 +4,7 @@ import org.junit.Test; import spoon.Launcher; import spoon.SpoonAPI; +import spoon.pattern.PatternBuilder; import spoon.processing.AbstractManualProcessor; import spoon.processing.AbstractProcessor; import spoon.reflect.code.CtCodeElement; @@ -17,8 +18,10 @@ import spoon.reflect.declaration.CtType; import spoon.reflect.declaration.ModifierKind; import spoon.reflect.factory.Factory; +import spoon.reflect.reference.CtExecutableReference; import spoon.reflect.reference.CtTypeReference; import spoon.reflect.visitor.CtInheritanceScanner; +import spoon.reflect.visitor.Filter; import spoon.reflect.visitor.filter.AbstractFilter; import spoon.reflect.visitor.filter.TypeFilter; import spoon.test.metamodel.SpoonMetaModel; @@ -233,6 +236,27 @@ public boolean matches(CtTypeReference element) { return "junit.framework.TestCase".equals(element.getQualifiedName()); } }).size()); + + // contract: all public methods of those classes are properly tested in a JUnit test + List l = new ArrayList<>(); + l.add("spoon.pattern.PatternBuilder"); + l.add("spoon.pattern.Pattern"); + List errors = new ArrayList<>(); + for (String klass : l) { + for (CtMethod m : spoon.getFactory().Type().get(Class.forName(klass)).getMethods()) { + if (!m.hasModifier(ModifierKind.PUBLIC)) continue; + + if (spoon.getModel().getElements(new Filter() { + @Override + public boolean matches(CtExecutableReference element) { + return element.getExecutableDeclaration() == m; + } + }).size() == 0) { + errors.add(klass+"#"+m.getSimpleName()); + } + } + } + assertTrue("untested public methods: "+errors.toString(), errors.size()==0); } @Test diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index 451d653c785..2617c16911c 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -16,6 +16,7 @@ import spoon.Launcher; import spoon.OutputType; import spoon.SpoonModelBuilder; +import spoon.pattern.InlineStatementsBuilder; import spoon.pattern.ParametersBuilder; import spoon.pattern.Pattern; import spoon.pattern.PatternBuilder; @@ -163,4 +164,8 @@ public void testTemplateReplace() throws Exception { launcher.getModelBuilder().generateProcessedSourceFiles(OutputType.CLASSES); } + @Test + public void testInlineStatementsBuilder() throws Exception { + // TODO: specify what InlineStatementsBuilder does + } } From 9ae435b9f12785fcb422f95d02d81e96297c1622 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Sat, 24 Mar 2018 10:18:43 +0100 Subject: [PATCH 049/131] check all params of both matches --- src/test/java/spoon/test/template/PatternTest.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index 2617c16911c..28812517429 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -115,9 +115,21 @@ public void testMatchSample1() throws Exception { ParameterValueProvider params = matches.get(0).getParameters(); assertEquals("\"extends\"", params.getValue("startKeyword").toString()); assertEquals(Boolean.TRUE, params.getValue("useStartKeyword")); + assertEquals("false", params.getValue("startPrefixSpace").toString()); + assertEquals("null", params.getValue("start").toString()); + assertEquals("false", params.getValue("startSuffixSpace").toString()); + assertEquals("false", params.getValue("nextPrefixSpace").toString()); + assertEquals("\",\"", params.getValue("next").toString()); + assertEquals("true", params.getValue("nextSuffixSpace").toString()); + assertEquals("false", params.getValue("endPrefixSpace").toString()); + assertEquals("\";\"", params.getValue("end").toString()); + assertEquals("ctEnum.getEnumValues()", params.getValue("getIterable").toString()); + assertEquals("[scan(enumValue)]", params.getValue("statements").toString()); params = matches.get(1).getParameters(); // all method arguments to createListPrinter have been matched + assertEquals(null, params.getValue("startKeyword")); + assertEquals(Boolean.FALSE, params.getValue("useStartKeyword")); assertEquals("false", params.getValue("startPrefixSpace").toString()); assertEquals("null", params.getValue("start").toString()); assertEquals("false", params.getValue("startSuffixSpace").toString()); From f257c12cb5a20c5a8aa152ad9577e456eb199819 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Sat, 24 Mar 2018 10:26:56 +0100 Subject: [PATCH 050/131] patternModel is unmodifiable list. method selectNodes was deleted --- src/main/java/spoon/pattern/PatternBuilder.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index 8de1b447768..3f7e05cf750 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -130,12 +130,12 @@ public CtQuery map(CtConsumableFunction queryStep) { private PatternBuilder(CtTypeReference templateTypeRef, List template) { this.templateTypeRef = templateTypeRef; - this.patternModel = template; + this.patternModel = Collections.unmodifiableList(new ArrayList<>(template)); if (template == null) { throw new SpoonException("Cannot create a Pattern from an null model"); } this.valueConvertor = new ValueConvertorImpl(); - patternNodes = ElementNode.create(template, patternElementToSubstRequests); + patternNodes = ElementNode.create(this.patternModel, patternElementToSubstRequests); patternQuery = new PatternBuilder.PatternQuery(getFactory().Query(), patternModel); if (templateTypeRef != null) { configureParameters(pb -> { @@ -266,12 +266,6 @@ public void setNodeOfAttributeOfElement(CtElement element, CtRole role, RootNode }); } - /** adds the given nodes to the pattern */ - public PatternBuilder selectNodes(TemplateModelBuilder selector) { - this.patternModel.addAll(selector.getTemplateModels()); - return this; - } - /** * @param element to be checked element * @return true if element `element` is a template or a child of template From 0b19d6b79812ba97df7be6319ecb219dbfd922fc Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Sat, 24 Mar 2018 10:53:10 +0100 Subject: [PATCH 051/131] fix tests --- src/main/java/spoon/template/Substitution.java | 2 +- src/test/java/spoon/test/template/TemplateTest.java | 4 ++-- .../java/spoon/test/template/testclasses/match/MatchMap.java | 4 ++-- .../spoon/test/template/testclasses/match/MatchModifiers.java | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/spoon/template/Substitution.java b/src/main/java/spoon/template/Substitution.java index eb7b84e18ac..f45c322d513 100644 --- a/src/main/java/spoon/template/Substitution.java +++ b/src/main/java/spoon/template/Substitution.java @@ -107,7 +107,7 @@ public static > void insertAll(CtType targetType, T tem */ public static > T createTypeFromTemplate(String qualifiedTypeName, CtType templateOfType, Map templateParameters) { return PatternBuilder - .create(templateOfType) + .create(templateOfType, templateOfType) .configureTemplateParameters(templateParameters) .build() .createType(templateOfType.getFactory(), qualifiedTypeName, templateParameters); diff --git a/src/test/java/spoon/test/template/TemplateTest.java b/src/test/java/spoon/test/template/TemplateTest.java index 3bf9a21696b..ea5899f6928 100644 --- a/src/test/java/spoon/test/template/TemplateTest.java +++ b/src/test/java/spoon/test/template/TemplateTest.java @@ -1182,7 +1182,7 @@ public void testTemplateMatchOfMultipleElements() throws Exception { // creating a Pattern from a Literal, with zero pattern parameters // The pattern model consists of one CtLIteral only // there is not needed any type reference, because CtLiteral has no reference to a type where it is defined - spoon.pattern.Pattern p = PatternBuilder.create((CtType)null, f.createLiteral("a")).build(); + spoon.pattern.Pattern p = PatternBuilder.create((CtTypeReference)null, f.createLiteral("a")).build(); //The pattern has no parameters. There is just one constant CtLiteral assertEquals (0, p.getParameterInfos().size()); @@ -1246,7 +1246,7 @@ public void testTemplateMatchOfMultipleElements() throws Exception { } private static spoon.pattern.Pattern patternOfStringLiterals(Factory f, String... strs) { - return PatternBuilder.create((CtType)null, Arrays.asList(strs).stream().map(s -> f.createLiteral(s)).collect(Collectors.toList()) + return PatternBuilder.create((CtTypeReference)null, Arrays.asList(strs).stream().map(s -> f.createLiteral(s)).collect(Collectors.toList()) ).build(); } diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchMap.java b/src/test/java/spoon/test/template/testclasses/match/MatchMap.java index b169d641fb0..ed01b2d6cac 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchMap.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchMap.java @@ -17,7 +17,7 @@ public class MatchMap { public static Pattern createPattern(Factory factory, boolean acceptOtherAnnotations) { CtType type = factory.Type().get(MatchMap.class); - return PatternBuilder.create(type, new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) + return PatternBuilder.create(type, new TemplateModelBuilder(type).setTypeMember("matcher1").getTemplateModels()) .configureParameters(pb -> { //match any value of @Check annotation to parameter `testAnnotations` pb.parameter("CheckAnnotationValues").attributeOfElementByFilter(CtRole.VALUE, new TypeFilter(CtAnnotation.class)).setContainerKind(ContainerKind.MAP); @@ -34,7 +34,7 @@ public static Pattern createPattern(Factory factory, boolean acceptOtherAnnotati } public static Pattern createMatchKeyPattern(Factory factory) { CtType type = factory.Type().get(MatchMap.class); - return PatternBuilder.create(type, new TemplateModelBuilder(type).setBodyOfMethod("m1").getTemplateModels()) + return PatternBuilder.create(type, new TemplateModelBuilder(type).setTypeMember("m1").getTemplateModels()) .configureParameters(pb -> { //match any value of @Check annotation to parameter `testAnnotations` pb.parameter("CheckKey").bySubstring("value"); diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchModifiers.java b/src/test/java/spoon/test/template/testclasses/match/MatchModifiers.java index ec8615d2bd4..bada184de6a 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchModifiers.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchModifiers.java @@ -14,7 +14,7 @@ public class MatchModifiers { public static Pattern createPattern(Factory factory, boolean matchBody) { CtType type = factory.Type().get(MatchModifiers.class); - return PatternBuilder.create(type, new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) + return PatternBuilder.create(type, new TemplateModelBuilder(type).setTypeMember("matcher1").getTemplateModels()) .configureParameters(pb -> { pb.parameter("modifiers").attributeOfElementByFilter(CtRole.MODIFIER, new TypeFilter(CtMethod.class)); pb.parameter("methodName").byString("matcher1"); From 9dcffeab9d56a809d0db3c2048e87a4397d59f3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Sat, 24 Mar 2018 13:11:40 +0100 Subject: [PATCH 052/131] check if declaring type is computable --- .../java/spoon/pattern/PatternBuilder.java | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index 3f7e05cf750..161130a954b 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -24,6 +24,7 @@ import java.util.IdentityHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; @@ -129,7 +130,10 @@ public CtQuery map(CtConsumableFunction queryStep) { } private PatternBuilder(CtTypeReference templateTypeRef, List template) { - this.templateTypeRef = templateTypeRef; + this.templateTypeRef = getDeclaringTypeRef(template); + if (Objects.equals(this.templateTypeRef, templateTypeRef) == false) { + throw new SpoonException("Unexpetced template type ref"); + } this.patternModel = Collections.unmodifiableList(new ArrayList<>(template)); if (template == null) { throw new SpoonException("Cannot create a Pattern from an null model"); @@ -144,6 +148,38 @@ private PatternBuilder(CtTypeReference templateTypeRef, List templ } } + private CtTypeReference getDeclaringTypeRef(List template) { + CtType type = null; + for (CtElement ctElement : template) { + CtType t; + if (ctElement instanceof CtType) { + t = (CtType) ctElement; + type = mergeType(type, t); + } + t = ctElement.getParent(CtType.class); + if (t != null) { + type = mergeType(type, t); + } + } + return type == null ? null : type.getReference(); + } + + private CtType mergeType(CtType type, CtType t) { + if (type == null) { + return t; + } + if (type == t) { + return type; + } + if (type.hasParent(t)) { + return t; + } + if (t.hasParent(type)) { + return type; + } + throw new SpoonException("The pattern on nested types are not supported."); + } + /** * @param element a CtElement * @return {@link RootNode}, which handles matching/generation of an `object` from the source spoon AST. From a3e3927d859a6cdf62953a0021907868ac098225 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Sat, 24 Mar 2018 13:39:09 +0100 Subject: [PATCH 053/131] remove declaring typeRef from PatternBuilder#create --- .../java/spoon/pattern/PatternBuilder.java | 30 +++++-------------- .../java/spoon/pattern/TemplateBuilder.java | 4 +-- .../java/spoon/template/Substitution.java | 2 +- .../java/spoon/test/template/PatternTest.java | 2 +- .../test/template/TemplateMatcherTest.java | 2 +- .../spoon/test/template/TemplateTest.java | 6 ++-- .../testclasses/match/MatchForEach.java | 2 +- .../testclasses/match/MatchForEach2.java | 2 +- .../testclasses/match/MatchIfElse.java | 2 +- .../template/testclasses/match/MatchMap.java | 4 +-- .../testclasses/match/MatchModifiers.java | 2 +- .../testclasses/match/MatchMultiple.java | 2 +- .../testclasses/match/MatchMultiple2.java | 2 +- .../testclasses/match/MatchMultiple3.java | 2 +- .../match/MatchWithParameterCondition.java | 2 +- .../match/MatchWithParameterType.java | 2 +- .../testclasses/replace/NewPattern.java | 2 +- .../testclasses/replace/OldPattern.java | 2 +- 18 files changed, 28 insertions(+), 44 deletions(-) diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index 161130a954b..f4179381294 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -24,7 +24,6 @@ import java.util.IdentityHashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; @@ -69,29 +68,17 @@ public class PatternBuilder { public static final String TARGET_TYPE = "targetType"; - public static PatternBuilder create(CtType templateType, CtElement... elems) { - return create(templateType.getReference(), Arrays.asList(elems)); - } - /** * Creates a {@link PatternBuilder} from the List of template elements - * @param templateTypeRef the reference to {@link CtType}, which contains a patternModel elements. - * These type references have to be replaced by references to type, which receives generated code. - * If patternModel contains no type references, then `templateTypeRef` may be null * @param patternModel a List of Spoon AST nodes, which represents a template of to be generated or to be matched code * @return new instance of {@link PatternBuilder} */ - public static PatternBuilder create(CtTypeReference templateTypeRef, List patternModel) { - return new PatternBuilder(templateTypeRef, patternModel); + public static PatternBuilder create(List patternModel) { + return new PatternBuilder(patternModel); } - public static PatternBuilder create(CtType type, List patternModel) { - return new PatternBuilder(type.getReference(), patternModel); - } - - - public static PatternBuilder create(CtTypeReference templateTypeRef, CtElement... elems) { - return new PatternBuilder(templateTypeRef, Arrays.asList(elems)); + public static PatternBuilder create(CtElement... elems) { + return new PatternBuilder(Arrays.asList(elems)); } private final List patternModel; @@ -129,11 +116,8 @@ public CtQuery map(CtConsumableFunction queryStep) { } } - private PatternBuilder(CtTypeReference templateTypeRef, List template) { + private PatternBuilder(List template) { this.templateTypeRef = getDeclaringTypeRef(template); - if (Objects.equals(this.templateTypeRef, templateTypeRef) == false) { - throw new SpoonException("Unexpetced template type ref"); - } this.patternModel = Collections.unmodifiableList(new ArrayList<>(template)); if (template == null) { throw new SpoonException("Cannot create a Pattern from an null model"); @@ -141,9 +125,9 @@ private PatternBuilder(CtTypeReference templateTypeRef, List templ this.valueConvertor = new ValueConvertorImpl(); patternNodes = ElementNode.create(this.patternModel, patternElementToSubstRequests); patternQuery = new PatternBuilder.PatternQuery(getFactory().Query(), patternModel); - if (templateTypeRef != null) { + if (this.templateTypeRef != null) { configureParameters(pb -> { - pb.parameter(TARGET_TYPE).byType(templateTypeRef).setValueType(CtTypeReference.class); + pb.parameter(TARGET_TYPE).byType(this.templateTypeRef).setValueType(CtTypeReference.class); }); } } diff --git a/src/main/java/spoon/pattern/TemplateBuilder.java b/src/main/java/spoon/pattern/TemplateBuilder.java index 475a85f3bac..882bc32f31d 100644 --- a/src/main/java/spoon/pattern/TemplateBuilder.java +++ b/src/main/java/spoon/pattern/TemplateBuilder.java @@ -92,9 +92,9 @@ public static TemplateBuilder createPattern(CtElement templateRoot, CtClass t //remove `... extends Template`, which doesn't have to be part of pattern model tv.removeSuperClass(); }; - pb = PatternBuilder.create(templateType.getReference(), tv.getTemplateModels()); + pb = PatternBuilder.create(tv.getTemplateModels()); } else { - pb = PatternBuilder.create(templateType.getReference(), templateRoot); + pb = PatternBuilder.create(templateRoot); } Map templateParameters = template == null ? null : Parameters.getTemplateParametersAsMap(f, null, template); //legacy templates always automatically simplifies generated code diff --git a/src/main/java/spoon/template/Substitution.java b/src/main/java/spoon/template/Substitution.java index f45c322d513..eb7b84e18ac 100644 --- a/src/main/java/spoon/template/Substitution.java +++ b/src/main/java/spoon/template/Substitution.java @@ -107,7 +107,7 @@ public static > void insertAll(CtType targetType, T tem */ public static > T createTypeFromTemplate(String qualifiedTypeName, CtType templateOfType, Map templateParameters) { return PatternBuilder - .create(templateOfType, templateOfType) + .create(templateOfType) .configureTemplateParameters(templateParameters) .build() .createType(templateOfType.getFactory(), qualifiedTypeName, templateParameters); diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index 28812517429..9f09ad0248d 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -97,7 +97,7 @@ public void testMatchSample1() throws Exception { CtType type = f.Type().get(OldPattern.class); Pattern p = PatternBuilder //Create a pattern from all statements of OldPattern_ParamsInNestedType#patternModel - .create(type, new TemplateModelBuilder(type).setBodyOfMethod("patternModel").getTemplateModels()) + .create(new TemplateModelBuilder(type).setBodyOfMethod("patternModel").getTemplateModels()) .configureParameters((ParametersBuilder pb) -> pb // creating patterns parameters for all references to "params" and "items" .createPatternParameterForVariable("params", "item") diff --git a/src/test/java/spoon/test/template/TemplateMatcherTest.java b/src/test/java/spoon/test/template/TemplateMatcherTest.java index f556f00e275..8f515e78fa6 100644 --- a/src/test/java/spoon/test/template/TemplateMatcherTest.java +++ b/src/test/java/spoon/test/template/TemplateMatcherTest.java @@ -869,7 +869,7 @@ public void testMatchInSet() throws Exception { //contract: match elements in container of type Set - e.g method throwables CtType ctClass = ModelUtils.buildClass(MatchThrowables.class); Factory f = ctClass.getFactory(); - Pattern pattern = PatternBuilder.create(ctClass, new TemplateModelBuilder(ctClass).setTypeMember("matcher1").getTemplateModels()) + Pattern pattern = PatternBuilder.create(new TemplateModelBuilder(ctClass).setTypeMember("matcher1").getTemplateModels()) .configureParameters(pb -> { pb.parameter("otherThrowables") //add matcher for other arbitrary throwables diff --git a/src/test/java/spoon/test/template/TemplateTest.java b/src/test/java/spoon/test/template/TemplateTest.java index ea5899f6928..0e74d5341a4 100644 --- a/src/test/java/spoon/test/template/TemplateTest.java +++ b/src/test/java/spoon/test/template/TemplateTest.java @@ -674,7 +674,7 @@ public void testExtensionDecoupledSubstitutionVisitor() throws Exception { // creating a pattern from method "block" - spoon.pattern.Pattern pattern = PatternBuilder.create(type, type.getMethodsByName("block").get(0)) + spoon.pattern.Pattern pattern = PatternBuilder.create(type.getMethodsByName("block").get(0)) //all the variable references which are declared out of type member "block" are automatically considered //as pattern parameters .createPatternParameters() @@ -1182,7 +1182,7 @@ public void testTemplateMatchOfMultipleElements() throws Exception { // creating a Pattern from a Literal, with zero pattern parameters // The pattern model consists of one CtLIteral only // there is not needed any type reference, because CtLiteral has no reference to a type where it is defined - spoon.pattern.Pattern p = PatternBuilder.create((CtTypeReference)null, f.createLiteral("a")).build(); + spoon.pattern.Pattern p = PatternBuilder.create(f.createLiteral("a")).build(); //The pattern has no parameters. There is just one constant CtLiteral assertEquals (0, p.getParameterInfos().size()); @@ -1246,7 +1246,7 @@ public void testTemplateMatchOfMultipleElements() throws Exception { } private static spoon.pattern.Pattern patternOfStringLiterals(Factory f, String... strs) { - return PatternBuilder.create((CtTypeReference)null, Arrays.asList(strs).stream().map(s -> f.createLiteral(s)).collect(Collectors.toList()) + return PatternBuilder.create(Arrays.asList(strs).stream().map(s -> f.createLiteral(s)).collect(Collectors.toList()) ).build(); } diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchForEach.java b/src/test/java/spoon/test/template/testclasses/match/MatchForEach.java index 3db92f48629..94bb8005958 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchForEach.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchForEach.java @@ -17,7 +17,7 @@ public class MatchForEach { public static Pattern createPattern(Factory factory) { CtType type = factory.Type().get(MatchForEach.class); - return PatternBuilder.create(type, new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) + return PatternBuilder.create(new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) .configureParameters(pb -> { pb.parameter("values").byVariable("values").setContainerKind(ContainerKind.LIST); }) diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchForEach2.java b/src/test/java/spoon/test/template/testclasses/match/MatchForEach2.java index fad7087c731..65751c29021 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchForEach2.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchForEach2.java @@ -16,7 +16,7 @@ public class MatchForEach2 { public static Pattern createPattern(Factory factory) { CtType type = factory.Type().get(MatchForEach2.class); - return PatternBuilder.create(type, new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) + return PatternBuilder.create(new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) .configureParameters(pb -> { pb.parameter("values").byVariable("values").setContainerKind(ContainerKind.LIST); pb.parameter("varName").byString("var"); diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchIfElse.java b/src/test/java/spoon/test/template/testclasses/match/MatchIfElse.java index d68ecad6dd7..8b81dbe8fc6 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchIfElse.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchIfElse.java @@ -14,7 +14,7 @@ public class MatchIfElse { public static Pattern createPattern(Factory factory) { CtType type = factory.Type().get(MatchIfElse.class); - return PatternBuilder.create(type, new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) + return PatternBuilder.create(new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) .configureParameters(pb -> { pb.parameter("option").byVariable("option"); pb.parameter("option2").byVariable("option2"); diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchMap.java b/src/test/java/spoon/test/template/testclasses/match/MatchMap.java index ed01b2d6cac..f4faa8abdd1 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchMap.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchMap.java @@ -17,7 +17,7 @@ public class MatchMap { public static Pattern createPattern(Factory factory, boolean acceptOtherAnnotations) { CtType type = factory.Type().get(MatchMap.class); - return PatternBuilder.create(type, new TemplateModelBuilder(type).setTypeMember("matcher1").getTemplateModels()) + return PatternBuilder.create(new TemplateModelBuilder(type).setTypeMember("matcher1").getTemplateModels()) .configureParameters(pb -> { //match any value of @Check annotation to parameter `testAnnotations` pb.parameter("CheckAnnotationValues").attributeOfElementByFilter(CtRole.VALUE, new TypeFilter(CtAnnotation.class)).setContainerKind(ContainerKind.MAP); @@ -34,7 +34,7 @@ public static Pattern createPattern(Factory factory, boolean acceptOtherAnnotati } public static Pattern createMatchKeyPattern(Factory factory) { CtType type = factory.Type().get(MatchMap.class); - return PatternBuilder.create(type, new TemplateModelBuilder(type).setTypeMember("m1").getTemplateModels()) + return PatternBuilder.create(new TemplateModelBuilder(type).setTypeMember("m1").getTemplateModels()) .configureParameters(pb -> { //match any value of @Check annotation to parameter `testAnnotations` pb.parameter("CheckKey").bySubstring("value"); diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchModifiers.java b/src/test/java/spoon/test/template/testclasses/match/MatchModifiers.java index bada184de6a..2e260018f31 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchModifiers.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchModifiers.java @@ -14,7 +14,7 @@ public class MatchModifiers { public static Pattern createPattern(Factory factory, boolean matchBody) { CtType type = factory.Type().get(MatchModifiers.class); - return PatternBuilder.create(type, new TemplateModelBuilder(type).setTypeMember("matcher1").getTemplateModels()) + return PatternBuilder.create(new TemplateModelBuilder(type).setTypeMember("matcher1").getTemplateModels()) .configureParameters(pb -> { pb.parameter("modifiers").attributeOfElementByFilter(CtRole.MODIFIER, new TypeFilter(CtMethod.class)); pb.parameter("methodName").byString("matcher1"); diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple.java b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple.java index 7c4e30103ad..ae43fe5f5b0 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple.java @@ -15,7 +15,7 @@ public class MatchMultiple { public static Pattern createPattern(Factory factory, Quantifier matchingStrategy, Integer minCount, Integer maxCount) { CtType type = factory.Type().get(MatchMultiple.class); - return PatternBuilder.create(type, new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) + return PatternBuilder.create(new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) .configureParameters(pb -> { pb.parameter("statements").bySimpleName("statements").setContainerKind(ContainerKind.LIST); if (matchingStrategy != null) { diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple2.java b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple2.java index 3addfbc60b6..fa74163777e 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple2.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple2.java @@ -18,7 +18,7 @@ public class MatchMultiple2 { public static Pattern createPattern(Factory factory, Consumer cfgParams) { CtType type = factory.Type().get(MatchMultiple2.class); - return PatternBuilder.create(type, new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) + return PatternBuilder.create(new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) .configureTemplateParameters() .configureParameters(pb -> { pb.parameter("statements1").setContainerKind(ContainerKind.LIST); diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple3.java b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple3.java index 966a8077432..0b4f32b6d6d 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple3.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple3.java @@ -18,7 +18,7 @@ public class MatchMultiple3 { public static Pattern createPattern(Factory factory, Consumer cfgParams) { CtType type = factory.Type().get(MatchMultiple3.class); - return PatternBuilder.create(type, new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) + return PatternBuilder.create(new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) .configureTemplateParameters() .configureParameters(pb -> { pb.parameter("statements1").setContainerKind(ContainerKind.LIST); diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterCondition.java b/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterCondition.java index d53fbf26f26..412e0c865c7 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterCondition.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterCondition.java @@ -13,7 +13,7 @@ public class MatchWithParameterCondition { public static Pattern createPattern(Factory factory, Predicate condition) { CtType type = factory.Type().get(MatchWithParameterCondition.class); - return PatternBuilder.create(type, new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) + return PatternBuilder.create(new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) .configureParameters(pb -> { pb.parameter("value").byVariable("value"); if (condition != null) { diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterType.java b/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterType.java index 3050f39c0cd..4740a036609 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterType.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterType.java @@ -11,7 +11,7 @@ public class MatchWithParameterType { public static Pattern createPattern(Factory factory, Class valueType) { CtType type = factory.Type().get(MatchWithParameterType.class); - return PatternBuilder.create(type, new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) + return PatternBuilder.create(new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) .configureParameters(pb -> { pb.parameter("value").byVariable("value"); if (valueType != null) { diff --git a/src/test/java/spoon/test/template/testclasses/replace/NewPattern.java b/src/test/java/spoon/test/template/testclasses/replace/NewPattern.java index 6b060765ff4..10628c4701a 100644 --- a/src/test/java/spoon/test/template/testclasses/replace/NewPattern.java +++ b/src/test/java/spoon/test/template/testclasses/replace/NewPattern.java @@ -37,7 +37,7 @@ private void patternModel(OldPattern.Parameters params) throws Exception { */ public static Pattern createPatternFromNewPattern(Factory factory) { CtType type = factory.Type().get(NewPattern.class); - return PatternBuilder.create(type, new TemplateModelBuilder(type).setBodyOfMethod("patternModel").getTemplateModels()) + return PatternBuilder.create(new TemplateModelBuilder(type).setBodyOfMethod("patternModel").getTemplateModels()) .createPatternParameters() .configureParameters(pb -> { pb.parameter("statements").setContainerKind(ContainerKind.LIST); diff --git a/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java b/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java index d79210aaf78..34966547f4b 100644 --- a/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java +++ b/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java @@ -64,7 +64,7 @@ void patternModel(Parameters params) throws Exception { */ public static Pattern createPatternFromOldPattern(Factory factory) { CtType type = factory.Type().get(OldPattern.class); - return PatternBuilder.create(type, new TemplateModelBuilder(type).setBodyOfMethod("patternModel").getTemplateModels()) + return PatternBuilder.create(new TemplateModelBuilder(type).setBodyOfMethod("patternModel").getTemplateModels()) .configureParameters(pb->pb .createPatternParameterForVariable("params", "item") .parameter("statements").setContainerKind(ContainerKind.LIST) From f675ade7c73c02fe30a15876dd4d8600cd0483f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Sat, 24 Mar 2018 16:00:13 +0100 Subject: [PATCH 054/131] configureLocalParameters is package protected --- src/main/java/spoon/pattern/PatternBuilder.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index f4179381294..f4e622e54b6 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -360,6 +360,12 @@ public PatternBuilder createPatternParameters() { return this; } + /** + * Configure template parameters + * @param parametersBuilder a buildir which allows to define template parameters and to select + * to be substituted nodes + * @return + */ public PatternBuilder configureParameters(Consumer parametersBuilder) { ParametersBuilder pb = new ParametersBuilder(this, parameterInfos); parametersBuilder.accept(pb); @@ -367,9 +373,9 @@ public PatternBuilder configureParameters(Consumer parameters } /** - * TODO + * Used by inline for each statement to define template parameter which is local in the scope of the inline statement */ - public PatternBuilder configureLocalParameters(Consumer parametersBuilder) { + PatternBuilder configureLocalParameters(Consumer parametersBuilder) { ParametersBuilder pb = new ParametersBuilder(this, new HashMap<>()); parametersBuilder.accept(pb); return this; From 8fdb2a221e142a4d294a647cfb97606e9e917a07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Sat, 24 Mar 2018 19:16:38 +0100 Subject: [PATCH 055/131] test of generating of self referenced methods and types --- .../java/spoon/pattern/PatternBuilder.java | 6 +- .../java/spoon/test/template/PatternTest.java | 102 ++++++++++++++++++ .../types/AClassWithMethodsAndRefs.java | 40 +++++++ 3 files changed, 145 insertions(+), 3 deletions(-) create mode 100644 src/test/java/spoon/test/template/testclasses/types/AClassWithMethodsAndRefs.java diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index f4e622e54b6..ab9eb820e39 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -169,10 +169,10 @@ private CtType mergeType(CtType type, CtType t) { * @return {@link RootNode}, which handles matching/generation of an `object` from the source spoon AST. * or null, if there is none */ - public RootNode getOptionalPatternNode(CtElement element, CtRole... roles) { + RootNode getOptionalPatternNode(CtElement element, CtRole... roles) { return getPatternNode(true, element, roles); } - public RootNode getPatternNode(CtElement element, CtRole... roles) { + RootNode getPatternNode(CtElement element, CtRole... roles) { return getPatternNode(false, element, roles); } private RootNode getPatternNode(boolean optional, CtElement element, CtRole... roles) { @@ -635,7 +635,7 @@ public List getPatternModel() { * @param parameter to be checked {@link ParameterInfo} * @param consumer receiver of calls */ - public void forEachNodeOfParameter(ParameterInfo parameter, Consumer consumer) { + void forEachNodeOfParameter(ParameterInfo parameter, Consumer consumer) { patternNodes.forEachParameterInfo((paramInfo, vr) -> { if (paramInfo == parameter) { consumer.accept(vr); diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index 9f09ad0248d..cd855e78089 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -4,12 +4,16 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.io.File; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; import org.junit.Test; @@ -26,15 +30,23 @@ import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.declaration.CtClass; import spoon.reflect.declaration.CtElement; +import spoon.reflect.declaration.CtExecutable; +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.meta.ContainerKind; import spoon.reflect.meta.RoleHandler; import spoon.reflect.meta.impl.RoleHandlerHelper; +import spoon.reflect.reference.CtExecutableReference; +import spoon.reflect.reference.CtTypeReference; import spoon.reflect.visitor.DefaultJavaPrettyPrinter; +import spoon.reflect.visitor.filter.TypeFilter; import spoon.test.template.testclasses.replace.DPPSample1; import spoon.test.template.testclasses.replace.NewPattern; import spoon.test.template.testclasses.replace.OldPattern; +import spoon.test.template.testclasses.types.AClassWithMethodsAndRefs; import spoon.testing.utils.ModelUtils; @@ -175,7 +187,97 @@ public void testTemplateReplace() throws Exception { launcher.setSourceOutputDirectory(new File("./target/spooned-template-replace/")); launcher.getModelBuilder().generateProcessedSourceFiles(OutputType.CLASSES); } + + @Test + public void testGenerateClassWithSelfReferences() throws Exception { + //contract: a class with methods and fields can be used as template to generate a clone + //all the references to the origin class are replace by reference to the new class + CtType templateModel = ModelUtils.buildClass(AClassWithMethodsAndRefs.class); + Factory factory = templateModel.getFactory(); + Pattern pattern = PatternBuilder.create(templateModel).build(); + final String newQName = "spoon.test.generated.ACloneOfAClassWithMethodsAndRefs"; + CtClass generatedType = pattern.createType(factory, newQName, Collections.emptyMap()); + assertNotNull(generatedType); + assertEquals(newQName, generatedType.getQualifiedName()); + assertEquals("ACloneOfAClassWithMethodsAndRefs", generatedType.getSimpleName()); + assertEquals(Arrays.asList("","local","sameType","sameTypeStatic","anotherMethod","someMethod","Local","foo"), + generatedType.getTypeMembers().stream().map(CtTypeMember::getSimpleName).collect(Collectors.toList())); + //contract: all the type references points to new type + Set usedTypeRefs = new HashSet<>(); + generatedType.filterChildren(new TypeFilter<>(CtTypeReference.class)) + .forEach((CtTypeReference ref) -> usedTypeRefs.add(ref.getQualifiedName())); + assertEquals(new HashSet<>(Arrays.asList( + "spoon.test.generated.ACloneOfAClassWithMethodsAndRefs","void","boolean", + "spoon.test.generated.ACloneOfAClassWithMethodsAndRefs$1Bar", + "java.lang.Object","int","spoon.test.generated.ACloneOfAClassWithMethodsAndRefs$Local")), + usedTypeRefs); + //contract: all executable references points to executables in cloned type + generatedType.filterChildren(new TypeFilter<>(CtExecutableReference.class)).forEach((CtExecutableReference execRef) ->{ + CtTypeReference declTypeRef = execRef.getDeclaringType(); + if(declTypeRef.getQualifiedName().startsWith("spoon.test.generated.ACloneOfAClassWithMethodsAndRefs")) { + //OK + return; + } + if(declTypeRef.getQualifiedName().equals(Object.class.getName())) { + return; + } + fail("Unexpected declaring type " + declTypeRef.getQualifiedName()); + }); + } + + @Test + public void testGenerateMethodWithSelfReferences() throws Exception { + //contract: a method with self references can be used as a template to generate a clone + //all the references to the origin class are replace by reference to the new class + CtType templateModel = ModelUtils.buildClass(AClassWithMethodsAndRefs.class); + Factory factory = templateModel.getFactory(); + Pattern pattern = PatternBuilder.create( + (CtMethod) templateModel.getMethodsByName("foo").get(0), + templateModel.getNestedType("Local")) + //switch ON: generate by comments + .setAddGeneratedBy(true) + .build(); + final String newQName = "spoon.test.generated.ACloneOfAClassWithMethodsAndRefs"; + + CtClass generatedType = factory.createClass(newQName); + + assertNotNull(generatedType); + assertEquals(newQName, generatedType.getQualifiedName()); + assertEquals("ACloneOfAClassWithMethodsAndRefs", generatedType.getSimpleName()); + pattern.applyToType(generatedType, CtMethod.class, Collections.emptyMap()); + //contract: new method and interface were added + assertEquals(Arrays.asList("Local","foo"), + generatedType.getTypeMembers().stream().map(CtTypeMember::getSimpleName).collect(Collectors.toList())); + assertEquals(1, generatedType.getMethodsByName("foo").size()); + assertNotNull(generatedType.getNestedType("Local")); + //contract: generate by comments are appended + assertEquals("Generated by spoon.test.template.testclasses.types.AClassWithMethodsAndRefs#foo(AClassWithMethodsAndRefs.java:30)", + generatedType.getMethodsByName("foo").get(0).getDocComment().trim()); + assertEquals("Generated by spoon.test.template.testclasses.types.AClassWithMethodsAndRefs$Local(AClassWithMethodsAndRefs.java:26)", + generatedType.getNestedType("Local").getDocComment().trim()); + //contract: all the type references points to new type + Set usedTypeRefs = new HashSet<>(); + generatedType.filterChildren(new TypeFilter<>(CtTypeReference.class)) + .forEach((CtTypeReference ref) -> usedTypeRefs.add(ref.getQualifiedName())); + assertEquals(new HashSet<>(Arrays.asList( + "spoon.test.generated.ACloneOfAClassWithMethodsAndRefs","void", + "spoon.test.generated.ACloneOfAClassWithMethodsAndRefs$1Bar", + "java.lang.Object","spoon.test.generated.ACloneOfAClassWithMethodsAndRefs$Local")), + usedTypeRefs); + //contract: all executable references points to executables in cloned type + generatedType.filterChildren(new TypeFilter<>(CtExecutableReference.class)).forEach((CtExecutableReference execRef) ->{ + CtTypeReference declTypeRef = execRef.getDeclaringType(); + if(declTypeRef.getQualifiedName().startsWith("spoon.test.generated.ACloneOfAClassWithMethodsAndRefs")) { + //OK + return; + } + if(declTypeRef.getQualifiedName().equals(Object.class.getName())) { + return; + } + fail("Unexpected declaring type " + declTypeRef.getQualifiedName()); + }); + } @Test public void testInlineStatementsBuilder() throws Exception { // TODO: specify what InlineStatementsBuilder does diff --git a/src/test/java/spoon/test/template/testclasses/types/AClassWithMethodsAndRefs.java b/src/test/java/spoon/test/template/testclasses/types/AClassWithMethodsAndRefs.java new file mode 100644 index 00000000000..99958e93c96 --- /dev/null +++ b/src/test/java/spoon/test/template/testclasses/types/AClassWithMethodsAndRefs.java @@ -0,0 +1,40 @@ +package spoon.test.template.testclasses.types; + +public class AClassWithMethodsAndRefs { + + public AClassWithMethodsAndRefs() { + someMethod(0); + } + + Local local = () -> {sameTypeStatic.foo();}; + AClassWithMethodsAndRefs sameType = new AClassWithMethodsAndRefs(); + static AClassWithMethodsAndRefs sameTypeStatic; + + public AClassWithMethodsAndRefs anotherMethod() { + someMethod(0); + // instantiate itself + return new AClassWithMethodsAndRefs(); + } + + public void someMethod(int i) { + // call recursivelly itself + if (i == 0) { + someMethod(1); + } + } + + interface Local { + void bar(); + } + + Local foo() { + class Bar implements Local { + public void bar() { + bar(); + foo(); + } + } + return new Bar(); + } + +} From 9e7f91a0a7e8d81c8408d310d18ed5724349ac8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Sat, 24 Mar 2018 20:48:40 +0100 Subject: [PATCH 056/131] hiding some methods, tests --- src/main/java/spoon/pattern/Pattern.java | 2 ++ .../java/spoon/pattern/PatternBuilder.java | 28 +++++++++++++++---- .../java/spoon/test/template/PatternTest.java | 5 ++++ 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/main/java/spoon/pattern/Pattern.java b/src/main/java/spoon/pattern/Pattern.java index 14133483941..b89b08d7aeb 100644 --- a/src/main/java/spoon/pattern/Pattern.java +++ b/src/main/java/spoon/pattern/Pattern.java @@ -58,6 +58,7 @@ */ public class Pattern implements CtConsumableFunction { private ParameterValueProviderFactory parameterValueProviderFactory = UnmodifiableParameterValueProvider.Factory.INSTANCE; + //TODO rename private ModelNode modelValueResolver; private boolean addGeneratedBy = false; @@ -68,6 +69,7 @@ public class Pattern implements CtConsumableFunction { /** * @return a {@link ModelNode} of this pattern */ + //TODO rename public ModelNode getModelValueResolver() { return modelValueResolver; } diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index ab9eb820e39..b480c82a154 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -274,13 +274,28 @@ private void handleConflict(ConflictResolutionMode conflictMode, RootNode oldNod } } - public void setNodeOfElement(CtElement element, RootNode node, ConflictResolutionMode conflictMode) { + /** + * Changes the Pattern by way the `node` is used at position of template element. + * It is used for example to mark that `element` as pattern parameter + * @param element + * @param node + * @param conflictMode + */ + void setNodeOfElement(CtElement element, RootNode node, ConflictResolutionMode conflictMode) { modifyNodeOfElement(element, conflictMode, oldNode -> { return node; }); } - public void setNodeOfAttributeOfElement(CtElement element, CtRole role, RootNode node, ConflictResolutionMode conflictMode) { + /** + * Changes the Pattern by way the `node` is used at position of template element attribute of `role`. + * It is used for example to mark that `element` as pattern parameter + * @param element + * @param role + * @param node + * @param conflictMode + */ + void setNodeOfAttributeOfElement(CtElement element, CtRole role, RootNode node, ConflictResolutionMode conflictMode) { modifyNodeOfAttributeOfElement(element, role, conflictMode, oldAttrNode -> { return node; }); @@ -290,7 +305,7 @@ public void setNodeOfAttributeOfElement(CtElement element, CtRole role, RootNode * @param element to be checked element * @return true if element `element` is a template or a child of template */ - public boolean isInModel(CtElement element) { + boolean isInModel(CtElement element) { if (element != null) { for (CtElement patternElement : patternModel) { if (element == patternElement || element.hasParent(patternElement)) { @@ -605,7 +620,7 @@ static CtTypeReference getLocalTypeRefBySimpleName(CtType templateType, St return null; } - public boolean hasParameterInfo(String parameterName) { + boolean hasParameterInfo(String parameterName) { return parameterInfos.containsKey(parameterName); } @@ -627,7 +642,10 @@ private static void checkTemplateType(CtType type) { throw new SpoonException("Cannot create Pattern from shadow Template type. Add sources of Template type into spoon model."); } } - public List getPatternModel() { + /** + * @return a {@link CtElement}s which are the template model of this Pattern + */ + List getPatternModel() { return patternModel; } /** diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index cd855e78089..4512a73c2bd 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -195,6 +195,11 @@ public void testGenerateClassWithSelfReferences() throws Exception { CtType templateModel = ModelUtils.buildClass(AClassWithMethodsAndRefs.class); Factory factory = templateModel.getFactory(); Pattern pattern = PatternBuilder.create(templateModel).build(); + //contract: by default generated by comments are not generated + assertFalse(pattern.isAddGeneratedBy()); + //contract: generated by comments can be switched ON/OFF later + pattern.setAddGeneratedBy(true); + assertTrue(pattern.isAddGeneratedBy()); final String newQName = "spoon.test.generated.ACloneOfAClassWithMethodsAndRefs"; CtClass generatedType = pattern.createType(factory, newQName, Collections.emptyMap()); assertNotNull(generatedType); From 983b1297d4000ab139cd43ca526088c0980db3fd Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Sat, 31 Mar 2018 09:04:38 +0200 Subject: [PATCH 057/131] up --- doc/pattern.md | 439 ++++++++ doc/template_definition.md | 265 +---- .../java/spoon/pattern/ParametersBuilder.java | 6 +- src/main/java/spoon/pattern/Pattern.java | 1 + .../java/spoon/pattern/PatternBuilder.java | 11 +- src/main/java/spoon/pattern/concept.md | 189 ---- .../SpoonArchitectureEnforcerTest.java | 39 +- .../java/spoon/test/template/PatternTest.java | 966 +++++++++++++++++- .../test/template/TemplateMatcherTest.java | 952 ----------------- .../testclasses/match/MatchForEach.java | 11 - .../template/testclasses/match/MatchMap.java | 34 - .../testclasses/match/MatchModifiers.java | 6 +- 12 files changed, 1447 insertions(+), 1472 deletions(-) create mode 100644 doc/pattern.md delete mode 100644 src/main/java/spoon/pattern/concept.md delete mode 100644 src/test/java/spoon/test/template/TemplateMatcherTest.java diff --git a/doc/pattern.md b/doc/pattern.md new file mode 100644 index 00000000000..246b3b7f791 --- /dev/null +++ b/doc/pattern.md @@ -0,0 +1,439 @@ +--- +title: Spoon Patterns +--- + +**Spoon pattern's** aimas at matching and transforming code elements. A **Spoon pattern** is based on a code element (for example +expression, statement, block, method, constuctor, type member, +interface, type, ... any Spoon model subtree), +where parts of that code may be **pattern parameters**. + +**Spoon pattern's** can be used in two ways: + +A) **to search for a code**. The found code is same like code of **Spoon pattern**, +where code on position of **pattern parameter** may be arbitrary and is copied +as value of **pattern parameter**. We call this operation **Matching**. + +```java +Factory spoonFactory = ... +//build a Spoon pattern +Pattern spoonTemplate = ... build a spoon pattern. For example for an method ... +//search for all occurences of the method like spoonTemplate in whole model +spoonTemplate.forEachMatch(spoonFactory.getRootPackage(), (Match match) -> { + //this Consumer is called once for each method which matches with spoonTemplate + Map parameters = match.getParametersAsMap(); + CtMethod matchingMethod = match.getMatchingElement(CtMethod.class); + String aNameOfMatchedMethod = parameters.get("methodName"); + ... +}); +``` + +B) **to generate new code**. The generated code is a copy of code +of **Spoon pattern**, where each **pattern parameter** is substituted +by it's value. We call this operation **Generating**. + +```java +Factory spoonFactory = ... +//build a Spoon pattern +Pattern spoonTemplate = ... build a spoon pattern. For example for an method ... +//define values for parameters +Map parameters = new HashMap<>(); +parameters.put("methodName", "i_am_an_generated_method"); +//generate a code using spoon pattern and parameters +CtMethod generatedMethod = spoonTemplate.substituteSingle(spoonFactory, CtMethod.class, parameters); +``` + +Main class: `PatternBuilder` +-------------------------------------------------- + +To create a Spoon pattern, one must use `PatternBuilder`, which takes AST nodes as input, and where you +**pattern parameters** are defined by calling PatternBuilder fluen API methods. + + +The method `statement` below defines a Spoon pattern. + +```java +public class Foo { + public void statement() { + if (_col_.size() > 10) + throw new OutOfBoundException(); + } +} +``` + +The code, which creates a Spoon pattern using `PatternBuilder` looks like this: + +```java +Pattern t = PatternBuilder.create(factory, + //defines pattern class. + CheckBoundTemplate.class, + //defines which part of pattern class will be used as pattern model + model -> model.setBodyOfMethod("statement")) + //tells builder that all variables defined out of scope of pattern model (body of the method) + //are considered as pattern parameters + // here _col_ + .configureTemplateParameters() + //builds an instance of Pattern + .build(); +``` + + +This pattern specifies a +statements (all statements of body of method `statement`) that is a precondition to check that a list +is smaller than a certain size. This piece of code will be injected at the +beginning of all methods dealing with size-bounded lists. This pattern has +one single pattern parameter called `_col_`. +In this case, the pattern parameter value is meant to be an expression (`CtExpression`) +that returns a Collection. + +The pattern source is +well-typed, compiles, but the binary code of the pattern is usually thrown away +and only spoon model (Abstract syntax tree) of source code is used to generated new code +or to search for matching code. + +Generating of code using Spoon pattern +------------- + +The code at the end of this page shows how to use such spoon pattern. +One takes a spoon pattern, defines the pattern parameters, +and then one calls the pattern engine. In last line, the bound check +is injected at the beginning of a method body. + +Since the pattern is given the first method parameter which is in the +scope of the insertion location, the generated code is guaranteed to compile. +The Java compiler ensures that the pattern compiles with a given scope, the +developer is responsible for checking that the scope where she uses +pattern-generated code is consistent with the pattern scope. + +```java +Pattern t = PatternBuilder.create(...building a pattern. See code above...); +// creating a holder of parameters +Map parameters = new HashMap<>(); +parameters.put("_col_", createVariableAccess(method.getParameters().get(0))); + +// getting the final AST +CtStatement injectedCode = t.substituteSingle(factory, CtStatement.class, parameters); + +// adds the bound check at the beginning of a method +method.getBody().insertBegin(injectedCode); +``` + +## PatternBuilder parameters +The `PatternBuilder` takes all the Template parameters mentioned in the chapters above +and understands them as pattern parameters, when `PatternBuilder#configureTemplateParameters()` +is called. + +```java +Pattern t = PatternBuilder.create(...select pattern model...) + .configureTemplateParameters() + .build(); +``` + +Next to the ways of parameter definitions mentioned above the `PatternBuilder` +allows to define parameters like this: + +```java +//a pattern model +void method(String _x_) { + zeroOneOrMoreStatements(); + System.out.println(_x_); +} + +//a pattern definition +Pattern t = PatternBuilder.create(...select pattern model...) + .configureParameters(pb -> + pb.parameter("firstParamName") + //...select which AST nodes are parameters... + //e.g. using parameter selector + .bySimpleName("zeroOneOrMoreStatements") + //...modify behavior of parameters... + //e.g. using parameter modifier + .setMinOccurence(0); + + //... you can define as many parameters as you need... + + pb.parameter("lastParamName").byVariable("_x_"); + ) + .build(); +``` + +## PatternBuilder parameter selectors + +* `byType(Class|CtTypeReference|String)` - all the references to the type defined by Class, +CtTypeReference or qualified name will be considered as pattern parameter +* `byLocalType(CtType searchScope, String localTypeSimpleName)` - all the types defined in `searchScope` +and having simpleName equal to `localTypeSimpleName` will be considered as pattern parameter +* `byVariable(CtVariable|String)` - all read/write variable references to CtVariable +or any variable with provided simple name will be considered as pattern parameter +* byInvocation(CtMethod method) - each invocation of `method` will be considered as pattern parameter +* `parametersByVariable(CtVariable|String... variableName)` - each `variableName` is a name of a variable +which references instance of a class with fields. Each such field is considered as pattern parameter. +* `byTemplateParameterReference(CtVariable)` - the reference to variable of type `TemplateParameter` is handled +as pattern parameter using all the rules defined in the chapters above. +* `byFilter(Filter)` - any pattern model element, where `Filter.accept(element)` returns true is a pattern parameter. +* `attributeOfElementByFilter(CtRole role, Filter filter)` - the attribute defined by `role` of all +pattern model elements, where `Filter.accept(element)` returns true is a pattern parameter. +It can be used to define a varible on any CtElement attribute. E.g. method modifiers or throwables, ... +* `byString(String name)` - all pattern model string attributes whose value **is equal to** `name` are considered as pattern parameter.This can be used to define full name of the methods and fields, etc. +* `bySubstring(String stringMarker)` - all pattern model string attributes whose value **contains** +whole string or a substring equal to `stringMarker`are pattern parameter. +Note: only the `stringMarker` substring of the string value is substituted. +Other parts of string/element name are kept unchanged. +* `bySimpleName(String name)` - any CtNamedElement or CtReference identified by it's simple name is a pattern parameter. +* `byNamedElementSimpleName(String name)` - any CtNamedElement identified by it's simple name is a pattern parameter. +* `byReferenceSimpleName(String name)` - any CtReference identified by it's simple name is a pattern parameter. + +Note: +* `byString` and `bySubstring` are used to rename code elements. +For example to rename a method "xyz" to "abc" +* `bySimpleName`, `byNamedElementSimpleName`, `byReferenceSimpleName` +are used to replace these elements by completelly different elements. +For example to replace method invocation by an variable reference, etc. + + +## PatternBuilder parameter modifiers +Any parameter of spoon pattern can be configured like this: + +* `setMinOccurence(int)` - defines minimal number of occurences of the value of this parameter during **matching**, +which is needed by matcher to accept that value. + * `setMinOccurence(0)` - defines optional parameter + * `setMinOccurence(1)` - defines mandatory parameter + * `setMinOccurence(n)` - defines parameter, whose value must be repeated at least n-times +* `setMaxOccurence(int)` - defines maximal number of occurences of the value of this parameter during **matching**, +which is accepted by matcher to accept that value. +* `setMatchingStrategy(Quantifier)` - defines how to matching engine will behave when two pattern nodes may accept the same value. + * `Quantifier#GREEDY` - Greedy quantifiers are considered "greedy" because they force the matcher to read in, or eat, +the entire input prior to attempting the next match. +If the next match attempt (the entire input) fails, the matcher backs off the input by one and tries again, +repeating the process until a match is found or there are no more elements left to back off from. + * `Quantifier#RELUCTANT` - The reluctant quantifier takes the opposite approach: It start at the beginning of the input, +then reluctantly eat one character at a time looking for a match. +The last thing it tries is the entire input. + * `Quantifier#POSSESSIVE` - The possessive quantifier always eats the entire input string, +trying once (and only once) for a match. Unlike the greedy quantifiers, possessive quantifiers never back off, +even if doing so would allow the overall match to succeed. +* `setValueType(Class type)` - defines a required type of the value. If defined the pattern matched, will match only values which are assigneable from the provided `type` +* `matchCondition(Class type, Predicate matchCondition)` - defines a `Predicate`, whose method `boolean test(T)`, +will be called by pattern matcher. Template matcher accepts that value only if `test` returns true for the value. +The `setValueType(type)` is called internally too, so match condition assures both a type of value and condition on value. +* `setContainerKind(ContainerKind)` - defines what container will be used to store the value. + * `ContainerKind#SINGLE` - only single value is accepted as a parameter value. + It can be e.g. single String or single CtStatement, etc. + * `ContainerKind#LIST` - The values are always stored as `List`. + * `ContainerKind#SET` - The values are always stored as `Set`. + * `ContainerKind#MAP` - The values are always stored as `Map`. + + +## Inlining with PatternBuilder +The pattern code in spoon patterns made by `PatternBuilder` is never inlined automatically. +But you can mark code to be inlined this way: +```java +Pattern t = PatternBuilder.create(...select pattern model...) + //...configure parameters... + configureInlineStatements(ls -> + //...select to be inlined statements... + //e.g. by variable name: + ls.byVariableName("intValues") + ).build(); +``` + +## PatternBuilder inline statements selectors + +* `byVariableName(String varName)` - all CtForEach and CtIf statements +whose expression references variable named `varName` are understood as +inline statements +* `markInline(CtForEach|CtIf)` - provided CtForEach or CtIf statement +is understood as inline statement + +## Notes + +Template definition +1) compilable easy understandable Template +2) The good names of template parameters - well assigned to AST nodes +3) even the attributes of AST nodes might be a parameters (e.g. modifier of class) + +Notes: +- it doesn't matter what is the current AST node at place of parameter. It will be replaced by parameter value converted to instance of expected type +- we have to define which Types, methodNames are variables and which already have required value +- we have to define which statements, expressions are optional/mandatory + e.g. by + if (optional1) { + ...some optional statements... + } + +Generation of code from Template +1) filling template parameters by values +2) cloning template AST +3) substituting cloned AST parameter nodes by values + +Types of template parameters +A) AST node of type CtStatement, CtExpression (e.g. CtVariableAccess, ...) +B) replacing of CtTypeReference by another CtTypeReference +C) replacing of whole or part of simpleName or Reference.name by String value +D) replacing of any attribute of AST node by value of appropriate type + +Searching for code, which matches template +1) definition of filters on searched nodes +2) matching with AST +3) creating of Map/Structure of parameter to matching value from AST + + +{@link Pattern} knows the AST of the pattern model. +It knows list of parts of pattern model, which are target for substitution. + +The substitution target can be: +A) node replace parameters - it means whole AST node (subtree) is replaced by value of parameter + The type of such value is defined by parent attribute which holds this node: + Examples: CtTypeMember, CtStatement, CtExpression, CtParameter, ... + A1) Single node replace parameter - there must be exactly one node (with arbitrary subtree) as parameter value + Examples: + CtCatch.parameter, CtReturn.expression, CtBinaryOperator.leftOperand, + CtForEach.variable, + CtLoop.body, + CtField.type + ... + A2) Multiple nodes replace parameter - there must be 0, 1 or more nodes (with arbitrary subtrees) as parameter value + Examples: + CtType.interfaces, CtType.typeMembers + note: There can be 0 or 1 parameter assigned to model node + +Definition of such CtElement based parameters: +------------------------------ +- by `TemplateParameter.S()` - it works only for some node types. Does not work for CtCase, CtCatch, CtComment, CtAnnotation, CtEnumValue, ... +- by pointing to such node(s) - it works for all nodes. How? During building of Pattern, the client's code has to somehow select the parameter nodes + and add them into list of to be substituted nodes. Client may use + - Filter ... here we can filter for `TemplateParameter.S()` + - CtPath ... after it is fully implemented + - Filtering by their name - legacy templates are using that approach together with Parameter annotation + - manual navigation and collecting of substituted nodes + +B) node attribute replace - it means value of node attribute is replaced by value of parameter + B1) Single value attribute - there must be exactly one value as parameter value + Types are String, boolean, BinaryOperatorKind, UnaryOperatorKind, CommentType, primitive type of Literal.value + B2) Unordered multiple value attribute - there must be exactly one value as parameter value + There is only: CtModifiable.modifiers with type Enum ModifierKind + + note: There can be no parameter of type (A) assigned to node whose attributes are going to be replaced. + There can be more attributes replaced for one node + But there can be 0 or 1 parameter assigned to attribute of model node + +Definition of such Object based parameters: +------------------------------------------------------ +by pointing to such node(s) + + with specification of CtRole of that attribute + +C) Substring attribute replace - it means substring of string value is replaced + Examples: CtNamedElement.simpleName, CtStatement.label, CtComment.comment + + note: There can be no parameter of type (A) assigned to node whose String attributes are going to be replaced. + There can be 0, 1 or more parameter assigned to String of model node. Each must have different identifier. + +Definition of such parameters: +------------------------------ +by pointing to such node(s) + + with specification of CtRole of that attribute + + with specification of to be replaced substring +It can be done by searching in all String attributes of each node and searching for a variable marker. E.g. "$var_name$" + +Optionally there might be defined a variable value formatter, which assures that variable value is converted to expected string representation + +Why {@link Pattern} needs such high flexibility? +Usecase: The Pattern instance might be created by comparing of two similar models (not templates, but part of normal code). +All the differences anywhere would be considered as parameters of generated Pattern instance. +Request: Such pattern instance must be printable and compilable, so client can use it for further matching and replacing by different pattern. + + +Why ParameterInfo type? +---------------------- +Can early check whether parameter values can be accepted by Pattern +Needs a validation by SubstitutionRequests of ParameterInfo +Can act as a filter of TemplateMatcher parameter value + + +Matching algorithms +------------------- + +There are following kinds of Matching algorithms: + +MA-1) matching of one target value with one Matcher +Target value can be +A) single CtElement, single String, Enum +B) List of T, Set of T , Map of String to T, where T is a type defined above + +Input: +- matcher - to be matched Matcher. Supports `Constant Matcher`, `Variable Matcher` +- parameters - input Parameters +- target - to be matched target object + +Output: +- status - 'matched' only if whole target value matched with this Matcher. 'not matched' if something did not matched or if there remained some unmatched items. +- (if status==matched) parameters - matched parameter values + +MA-2) matching of container of targets with one Matcher +Target can be: +A) Single T +B) List of T +C) Set of T +D) Map of String to T + +Input: +- matcher - to be matched Matcher. Supports `Constant Matcher`, `Variable Matcher`, +- parameters - input Parameters +- targets - to be matched container of targets + +Output: +- status - 'matched' only if one or more target container items matched with `matcher`. 'not matched' otherwise. +- (if status==matched) parameters - matched Parameters +- (if status==matched) remainingTargets - container of remaining targets which did not matched - these which has to be matched next + +MA-3) matching of container of targets with container of Matchers +Target can be: +A) Single T +B) List of T +C) Set of T +D) Map of String to T +Input: +- matchers - container of to be matched Matchers - M, List of M, Set of M, Map of M to M, which has to match to container of targets, Where M is a Matcher +- parameters - input Parameters +- targets - to be matched container of targets +- mode - `matchAll` ALL items of target container must match with ALL `matchers`, `matchSubset` if SUBSET of items of target container must match with ALL `matchers` +Output: +- status - 'matched' only if ALL/SUBSET of target container items matched with all `matchers`. 'not matched' otherwise. +- (if status==matched) parameters - matched Parameters +- (if status==matched && mode==matchSubset) remaintargets - container of targets with subset of items which did not matched +- (if status==matched) matchedTargets - container of targets with subset of items which did matched + + +Primitive Matchers +----------------- +**Constant Matcher** +Match: matches equal value (String, Enum, CtElement or List/Set/Map of previous). Is implemented as that value +Generate: generates copy of template value + +**Variable Matcher** +matches any value (String, Enum, CtElement or List/Set/Map of previous) to the zero, one or more Parameters of type String, Enum, CtElement or List/Set/Map of previous. +Matcher has these options: +- mandatory - true if this Matcher must match in current state. false - Matching algorithm ignores this Matcher when there is no (more) match). +- repeatable - true if it may match again in current state (Matching algorithm tries to match this Matcher repeatedly until there is no match). false if can match maximum once. + +Compound matchers +----------------- +consists of Primitive Matchers or Compound matchers. They are always implemented as a ordered chain of matchers. +The matching algorithm evaluates first Matcher from the chain and then remaining matchers + +**XORMatcher** +Contains ordered List of Matchers, which are evaluated sequentially until first Matcher matches. Others are ignored + +**Container matcher** +Contains List, Set or Map of Matchers, which have to all match with provided targets + +Wrapping matchers +----------------- +**Optional matcher** +Consists of a condition and a Matcher. +If the Matcher matches then Parameters are set by the way the condition is true, +If the Matcher doesn't match, then Condition is set to false.' + +**Nested matcher** +Consists of parameter mapping and Matcher. * +All the matched parameters are collected in local Parameters, which are then mapped to outer Parameters. + diff --git a/doc/template_definition.md b/doc/template_definition.md index 72e9613513b..6992fb42af3 100644 --- a/doc/template_definition.md +++ b/doc/template_definition.md @@ -4,29 +4,11 @@ tags: [template] keywords: template, definition, code, java --- -Spoon provides developers a way to define so called -**Spoon templates**. **Spoon template** is a part of code (for example -expression, statement, block, method, constuctor, type member, -interface, type, ... any Spoon model subtree), -where parts of that code may be **template parameters**. - -**Spoon template** can be used in two ways: - -A) **to generate new code**. The generated code is a copy of code -of **Spoon template**, where each **template parameter** is substituted -by it's value. We call this operation **Generating**. - -```java -Factory spoonFactory = ... -//build a Spoon template -Pattern spoonTemplate = ... build a spoon template. For example for an method ... -//define values for parameters -Map parameters = new HashMap<>(); -parameters.put("methodName", "i_am_an_generated_method"); -//generate a code using spoon template and parameters -CtMethod generatedMethod = spoonTemplate.substituteSingle(spoonFactory, CtMethod.class, parameters); -``` +Spoon provides developers a way of writing code transformations called +**code templates**. Those templates are statically type-checked, in +order to ensure statically that the generated code will be correct. +A Spoon template is a regular Java class that taken as input by the Spoon templating engine to perform a transformation. This is summarized in Figure below. A Spoon template can be seen as a higher-order program, which takes program elements as arguments, and returns a transformed program. Like any function, a template can be used in different @@ -34,39 +16,8 @@ contexts and give different results, depending on its parameters. ![Overview of Spoon's Templating System]({{ "/images/template-overview.svg" | prepend: site.baseurl }}) -B) **to search for a code**. The found code is same like code of **Spoon template**, -where code on position of **template parameter** may be arbitrary and is copied -as value of **template parameter**. We call this operation **Matching**. - -```java -Factory spoonFactory = ... -//build a Spoon template -Pattern spoonTemplate = ... build a spoon template. For example for an method ... -//search for all occurences of the method like spoonTemplate in whole model -spoonTemplate.forEachMatch(spoonFactory.getRootPackage(), (Match match) -> { - //this Consumer is called once for each method which matches with spoonTemplate - Map parameters = match.getParametersAsMap(); - CtMethod matchingMethod = match.getMatchingElement(CtMethod.class); - String aNameOfMatchedMethod = parameters.get("methodName"); - ... -}); -``` - -There are several ways how to build a **spoon template** - -* Using a regular java class, which implements a `Template` interface - -* Using PatternBuilder, which takes any part of code and where you -define which parts of that code are **template parameters** by calling PatternBuilder methods. - -The `Template` interface based definitions are statically type-checked, in order to ensure statically that the generated code will be correct. -Both template definitions are normal compiled java source code, -which is part of your sources, so: -* if the template source is compilable then generated code will be compilable too - when you use correct parameter values of course. -* the refactoring applied to your source code is automatically applied to your templates too. So the maintenance effort of Spoon templates is lower comparing to effort needed to maintain third party templates based on concatenation of strings. - -Definition of `Template` interface based templates --------------------------------------------------- +Definition of templates +----------------------- Class `CheckBoundTemplate` below defines a Spoon template. @@ -130,80 +81,8 @@ method.getBody().insertBegin(injectedCode); ``` -Definition of templates using `PatternBuilder` --------------------------------------------------- - -The body of method `CheckBoundTemplate#statement` below defines a Spoon template. - -```java -public class CheckBoundTemplate /*it doesn't matter what it extends or implements*/ { - // it doesn't matter what other type members are in template class - // it doesn't matter what is the name of the method whose body will be used as template - public void statement(Collection _col_) { - if (_col_.size() > 10) - throw new OutOfBoundException(); - } -} -``` - -The code, which creates a Spoon template using `PatternBuilder` looks like this: - -```java -Pattern t = PatternBuilder.create(factory, - //defines template class. - CheckBoundTemplate.class, - //defines which part of template class will be used as template model - model -> model.setBodyOfMethod("statement")) - //tells builder that all variables defined out of scope of template model (body of the method) - //are considered as template parameters - .configureAutomaticParameters() - //builds an instance of Pattern - .build(); -``` - - -This template specifies a -statements (all statements of body of method `statement`) that is a precondition to check that a list -is smaller than a certain size. This piece of code will be injected at the -beginning of all methods dealing with size-bounded lists. This template has -one single template parameter called `_col_`. -In this case, the template parameter value is meant to be an expression (`CtExpression`) -that returns a Collection. - -The template source is -well-typed, compiles, but the binary code of the template is usually thrown away -and only spoon model (Abstract syntax tree) of source code is used to generated new code -or to search for matching code. - -Generating of code using Spoon template -------------- - -The code at the end of this page shows how to use such spoon template. -One takes a spoon template, defines the template parameters, -and then one calls the template engine. In last line, the bound check -is injected at the beginning of a method body. - -Since the template is given the first method parameter which is in the -scope of the insertion location, the generated code is guaranteed to compile. -The Java compiler ensures that the template compiles with a given scope, the -developer is responsible for checking that the scope where she uses -template-generated code is consistent with the template scope. - -```java -Pattern t = PatternBuilder.create(...building a template. See code above...); -// creating a holder of parameters -Map parameters = new HashMap<>(); -parameters.put("_col_", createVariableAccess(method.getParameters().get(0))); - -// getting the final AST -CtStatement injectedCode = t.substituteSingle(factory, CtStatement.class, parameters); - -// adds the bound check at the beginning of a method -method.getBody().insertBegin(injectedCode); -``` - -Kinds of `Template` interface based templating ---------------- +Kinds of templating +------------------- There are different kinds of templating. @@ -383,115 +262,9 @@ String someMethod() { } ``` -## PatternBuilder parameters -The `PatternBuilder` takes all the Template parameters mentioned in the chapters above -and understands them as template parameters, when `PatternBuilder#configureTemplateParameters()` -is called. - -```java -Pattern t = PatternBuilder.create(...select template model...) - .configureTemplateParameters() - .build(); -``` - -Next to the ways of parameter definitions mentioned above the `PatternBuilder` -allows to define parameters like this: - -```java -//a template model -void method(String _x_) { - zeroOneOrMoreStatements(); - System.out.println(_x_); -} - -//a pattern definition -Pattern t = PatternBuilder.create(...select template model...) - .configureParameters(pb -> - pb.parameter("firstParamName") - //...select which AST nodes are parameters... - //e.g. using parameter selector - .bySimpleName("zeroOneOrMoreStatements") - //...modify behavior of parameters... - //e.g. using parameter modifier - .setMinOccurence(0); - - //... you can define as many parameters as you need... - - pb.parameter("lastParamName").byVariable("_x_"); - ) - .build(); -``` - -## PatternBuilder parameter selectors - -* `byType(Class|CtTypeReference|String)` - all the references to the type defined by Class, -CtTypeReference or qualified name will be considered as template parameter -* `byLocalType(CtType searchScope, String localTypeSimpleName)` - all the types defined in `searchScope` -and having simpleName equal to `localTypeSimpleName` will be considered as template parameter -* `byVariable(CtVariable|String)` - all read/write variable references to CtVariable -or any variable with provided simple name will be considered as template parameter -* byInvocation(CtMethod method) - each invocation of `method` will be considered as template parameter -* `parametersByVariable(CtVariable|String... variableName)` - each `variableName` is a name of a variable -which references instance of a class with fields. Each such field is considered as template parameter. -* `byTemplateParameterReference(CtVariable)` - the reference to variable of type `TemplateParameter` is handled -as template parameter using all the rules defined in the chapters above. -* `byFilter(Filter)` - any template model element, where `Filter.accept(element)` returns true is a template parameter. -* `attributeOfElementByFilter(CtRole role, Filter filter)` - the attribute defined by `role` of all -template model elements, where `Filter.accept(element)` returns true is a template parameter. -It can be used to define a varible on any CtElement attribute. E.g. method modifiers or throwables, ... -* `byString(String name)` - all template model string attributes whose value **is equal to** `name` are considered as template parameter.This can be used to define full name of the methods and fields, etc. -* `bySubstring(String stringMarker)` - all template model string attributes whose value **contains** -whole string or a substring equal to `stringMarker`are template parameter. -Note: only the `stringMarker` substring of the string value is substituted. -Other parts of string/element name are kept unchanged. -* `bySimpleName(String name)` - any CtNamedElement or CtReference identified by it's simple name is a template parameter. -* `byNamedElementSimpleName(String name)` - any CtNamedElement identified by it's simple name is a template parameter. -* `byReferenceSimpleName(String name)` - any CtReference identified by it's simple name is a template parameter. - -Note: -* `byString` and `bySubstring` are used to rename code elements. -For example to rename a method "xyz" to "abc" -* `bySimpleName`, `byNamedElementSimpleName`, `byReferenceSimpleName` -are used to replace these elements by completelly different elements. -For example to replace method invocation by an variable reference, etc. - - -## PatternBuilder parameter modifiers -Any parameter of spoon template can be configured like this: - -* `setMinOccurence(int)` - defines minimal number of occurences of the value of this parameter during **matching**, -which is needed by matcher to accept that value. - * `setMinOccurence(0)` - defines optional parameter - * `setMinOccurence(1)` - defines mandatory parameter - * `setMinOccurence(n)` - defines parameter, whose value must be repeated at least n-times -* `setMaxOccurence(int)` - defines maximal number of occurences of the value of this parameter during **matching**, -which is accepted by matcher to accept that value. -* `setMatchingStrategy(Quantifier)` - defines how to matching engine will behave when two template nodes may accept the same value. - * `Quantifier#GREEDY` - Greedy quantifiers are considered "greedy" because they force the matcher to read in, or eat, -the entire input prior to attempting the next match. -If the next match attempt (the entire input) fails, the matcher backs off the input by one and tries again, -repeating the process until a match is found or there are no more elements left to back off from. - * `Quantifier#RELUCTANT` - The reluctant quantifier takes the opposite approach: It start at the beginning of the input, -then reluctantly eat one character at a time looking for a match. -The last thing it tries is the entire input. - * `Quantifier#POSSESSIVE` - The possessive quantifier always eats the entire input string, -trying once (and only once) for a match. Unlike the greedy quantifiers, possessive quantifiers never back off, -even if doing so would allow the overall match to succeed. -* `setValueType(Class type)` - defines a required type of the value. If defined the template matched, will match only values which are assigneable from the provided `type` -* `matchCondition(Class type, Predicate matchCondition)` - defines a `Predicate`, whose method `boolean test(T)`, -will be called by template matcher. Template matcher accepts that value only if `test` returns true for the value. -The `setValueType(type)` is called internally too, so match condition assures both a type of value and condition on value. -* `setContainerKind(ContainerKind)` - defines what container will be used to store the value. - * `ContainerKind#SINGLE` - only single value is accepted as a parameter value. - It can be e.g. single String or single CtStatement, etc. - * `ContainerKind#LIST` - The values are always stored as `List`. - * `ContainerKind#SET` - The values are always stored as `Set`. - * `ContainerKind#MAP` - The values are always stored as `Map`. - #### Inlining foreach expressions -All Foreach expressions, which contains a template paremeter are inlined -in `Template` interface based templates. They have to be declared as follows: +Foreach expressions can be inlined. They have to be declared as follows: ```java @Parameter @@ -517,23 +290,3 @@ is transformed into: java.lang.System.out.println(1); } ``` -## Inlining with PatternBuilder -The template code in spoon templates made by `PatternBuilder` is never inlined automatically. -But you can mark code to be inlined this way: -```java -Pattern t = PatternBuilder.create(...select template model...) - //...configure parameters... - configureInlineStatements(ls -> - //...select to be inlined statements... - //e.g. by variable name: - ls.byVariableName("intValues") - ).build(); -``` - -### PatternBuilder inline statements selectors - -* `byVariableName(String varName)` - all CtForEach and CtIf statements -whose expression references variable named `varName` are understood as -inline statements -* `markInline(CtForEach|CtIf)` - provided CtForEach or CtIf statement -is understood as inline statement diff --git a/src/main/java/spoon/pattern/ParametersBuilder.java b/src/main/java/spoon/pattern/ParametersBuilder.java index 99782e0a0cc..c5f09e1bda4 100644 --- a/src/main/java/spoon/pattern/ParametersBuilder.java +++ b/src/main/java/spoon/pattern/ParametersBuilder.java @@ -291,7 +291,7 @@ public ParametersBuilder createPatternParameterForVariable(String... variableNam * @param variable to be substituted variable * @return this to support fluent API */ - public ParametersBuilder createPatternParameterForVariable(CtVariable variable) { + private ParametersBuilder createPatternParameterForVariable(CtVariable variable) { CtQueryable searchScope; if (patternBuilder.isInModel(variable)) { addSubstitutionRequest( @@ -536,11 +536,11 @@ public ParametersBuilder byFilter(Filter filter) { /** * Attribute defined by `role` of all elements matched by {@link Filter} will be substituted by parameter value - * @param role {@link CtRole}, which defines to be substituted elements * @param filter {@link Filter}, which defines to be substituted elements + * @param role {@link CtRole}, which defines to be substituted elements * @return {@link ParametersBuilder} to support fluent API */ - public ParametersBuilder attributeOfElementByFilter(CtRole role, Filter filter) { + public ParametersBuilder byRole(Filter filter, CtRole role) { ParameterInfo pi = getCurrentParameter(); queryModel().filterChildren(filter) .forEach((CtElement ele) -> { diff --git a/src/main/java/spoon/pattern/Pattern.java b/src/main/java/spoon/pattern/Pattern.java index b89b08d7aeb..c9ea24c69d1 100644 --- a/src/main/java/spoon/pattern/Pattern.java +++ b/src/main/java/spoon/pattern/Pattern.java @@ -62,6 +62,7 @@ public class Pattern implements CtConsumableFunction { private ModelNode modelValueResolver; private boolean addGeneratedBy = false; + /** package-protected, must use {@link PatternBuilder} */ Pattern(ModelNode modelValueResolver) { this.modelValueResolver = modelValueResolver; } diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index b480c82a154..866a30b0264 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -62,7 +62,9 @@ import spoon.template.TemplateParameter; /** - * The builder which creates a {@link Pattern} + * The master class to create a {@link Pattern} instance. + * + * Based on a fluent API, see tests and documentation ('pattern.md') */ public class PatternBuilder { @@ -396,7 +398,12 @@ PatternBuilder configureLocalParameters(Consumer parametersBu return this; } /** - * adds all standard Template parameters based on {@link TemplateParameter} and {@link Parameter} annotation + * + * All variables defined out of scope of the model (eg body of the method) + * are considered as template parameters + * + * Provides backward compatibility with standard Template parameters based on {@link TemplateParameter} and {@link Parameter} annotation + * * @return this to support fluent API */ public PatternBuilder configureTemplateParameters() { diff --git a/src/main/java/spoon/pattern/concept.md b/src/main/java/spoon/pattern/concept.md deleted file mode 100644 index 4e86ba671ef..00000000000 --- a/src/main/java/spoon/pattern/concept.md +++ /dev/null @@ -1,189 +0,0 @@ -Template definition -1) compilable easy understandable Template -2) The good names of template parameters - well assigned to AST nodes -3) even the attributes of AST nodes might be a parameters (e.g. modifier of class) - -Notes: -- it doesn't matter what is the current AST node at place of parameter. It will be replaced by parameter value converted to instance of expected type -- we have to define which Types, methodNames are variables and which already have required value -- we have to define which statements, expressions are optional/mandatory - e.g. by - if (optional1) { - ...some optional statements... - } - -Generation of code from Template -1) filling template parameters by values -2) cloning template AST -3) substituting cloned AST parameter nodes by values - -Types of template parameters -A) AST node of type CtStatement, CtExpression (e.g. CtVariableAccess, ...) -B) replacing of CtTypeReference by another CtTypeReference -C) replacing of whole or part of simpleName or Reference.name by String value -D) replacing of any attribute of AST node by value of appropriate type - -Searching for code, which matches template -1) definition of filters on searched nodes -2) matching with AST -3) creating of Map/Structure of parameter to matching value from AST - - -{@link Pattern} knows the AST of the pattern model. -It knows list of parts of pattern model, which are target for substitution. - -The substitution target can be: -A) node replace parameters - it means whole AST node (subtree) is replaced by value of parameter - The type of such value is defined by parent attribute which holds this node: - Examples: CtTypeMember, CtStatement, CtExpression, CtParameter, ... - A1) Single node replace parameter - there must be exactly one node (with arbitrary subtree) as parameter value - Examples: - CtCatch.parameter, CtReturn.expression, CtBinaryOperator.leftOperand, - CtForEach.variable, - CtLoop.body, - CtField.type - ... - A2) Multiple nodes replace parameter - there must be 0, 1 or more nodes (with arbitrary subtrees) as parameter value - Examples: - CtType.interfaces, CtType.typeMembers - note: There can be 0 or 1 parameter assigned to model node - -Definition of such CtElement based parameters: ------------------------------- -- by `TemplateParameter.S()` - it works only for some node types. Does not work for CtCase, CtCatch, CtComment, CtAnnotation, CtEnumValue, ... -- by pointing to such node(s) - it works for all nodes. How? During building of Pattern, the client's code has to somehow select the parameter nodes - and add them into list of to be substituted nodes. Client may use - - Filter ... here we can filter for `TemplateParameter.S()` - - CtPath ... after it is fully implemented - - Filtering by their name - legacy templates are using that approach together with Parameter annotation - - manual navigation and collecting of substituted nodes - -B) node attribute replace - it means value of node attribute is replaced by value of parameter - B1) Single value attribute - there must be exactly one value as parameter value - Types are String, boolean, BinaryOperatorKind, UnaryOperatorKind, CommentType, primitive type of Literal.value - B2) Unordered multiple value attribute - there must be exactly one value as parameter value - There is only: CtModifiable.modifiers with type Enum ModifierKind - - note: There can be no parameter of type (A) assigned to node whose attributes are going to be replaced. - There can be more attributes replaced for one node - But there can be 0 or 1 parameter assigned to attribute of model node - -Definition of such Object based parameters: ------------------------------------------------------- -by pointing to such node(s) - + with specification of CtRole of that attribute - -C) Substring attribute replace - it means substring of string value is replaced - Examples: CtNamedElement.simpleName, CtStatement.label, CtComment.comment - - note: There can be no parameter of type (A) assigned to node whose String attributes are going to be replaced. - There can be 0, 1 or more parameter assigned to String of model node. Each must have different identifier. - -Definition of such parameters: ------------------------------- -by pointing to such node(s) - + with specification of CtRole of that attribute - + with specification of to be replaced substring -It can be done by searching in all String attributes of each node and searching for a variable marker. E.g. "$var_name$" - -Optionally there might be defined a variable value formatter, which assures that variable value is converted to expected string representation - -Why {@link Pattern} needs such high flexibility? -Usecase: The Pattern instance might be created by comparing of two similar models (not templates, but part of normal code). -All the differences anywhere would be considered as parameters of generated Pattern instance. -Request: Such pattern instance must be printable and compilable, so client can use it for further matching and replacing by different pattern. - - -Why ParameterInfo type? ----------------------- -Can early check whether parameter values can be accepted by Pattern -Needs a validation by SubstitutionRequests of ParameterInfo -Can act as a filter of TemplateMatcher parameter value - - -Matching algorithms -------------------- - -There are following kinds of Matching algorithms: - -MA-1) matching of one target value with one Matcher -Target value can be -A) single CtElement, single String, Enum -B) List of T, Set of T , Map of String to T, where T is a type defined above - -Input: -- matcher - to be matched Matcher. Supports `Constant Matcher`, `Variable Matcher` -- parameters - input Parameters -- target - to be matched target object - -Output: -- status - 'matched' only if whole target value matched with this Matcher. 'not matched' if something did not matched or if there remained some unmatched items. -- (if status==matched) parameters - matched parameter values - -MA-2) matching of container of targets with one Matcher -Target can be: -A) Single T -B) List of T -C) Set of T -D) Map of String to T - -Input: -- matcher - to be matched Matcher. Supports `Constant Matcher`, `Variable Matcher`, -- parameters - input Parameters -- targets - to be matched container of targets - -Output: -- status - 'matched' only if one or more target container items matched with `matcher`. 'not matched' otherwise. -- (if status==matched) parameters - matched Parameters -- (if status==matched) remainingTargets - container of remaining targets which did not matched - these which has to be matched next - -MA-3) matching of container of targets with container of Matchers -Target can be: -A) Single T -B) List of T -C) Set of T -D) Map of String to T -Input: -- matchers - container of to be matched Matchers - M, List of M, Set of M, Map of M to M, which has to match to container of targets, Where M is a Matcher -- parameters - input Parameters -- targets - to be matched container of targets -- mode - `matchAll` ALL items of target container must match with ALL `matchers`, `matchSubset` if SUBSET of items of target container must match with ALL `matchers` -Output: -- status - 'matched' only if ALL/SUBSET of target container items matched with all `matchers`. 'not matched' otherwise. -- (if status==matched) parameters - matched Parameters -- (if status==matched && mode==matchSubset) remaintargets - container of targets with subset of items which did not matched -- (if status==matched) matchedTargets - container of targets with subset of items which did matched - - -Primitive Matchers ------------------ -**Constant Matcher** -Match: matches equal value (String, Enum, CtElement or List/Set/Map of previous). Is implemented as that value -Generate: generates copy of template value - -**Variable Matcher** -matches any value (String, Enum, CtElement or List/Set/Map of previous) to the zero, one or more Parameters of type String, Enum, CtElement or List/Set/Map of previous. -Matcher has these options: -- mandatory - true if this Matcher must match in current state. false - Matching algorithm ignores this Matcher when there is no (more) match). -- repeatable - true if it may match again in current state (Matching algorithm tries to match this Matcher repeatedly until there is no match). false if can match maximum once. - -Compound matchers ------------------ -consists of Primitive Matchers or Compound matchers. They are always implemented as a ordered chain of matchers. -The matching algorithm evaluates first Matcher from the chain and then remaining matchers - -**XORMatcher** -Contains ordered List of Matchers, which are evaluated sequentially until first Matcher matches. Others are ignored - -**Container matcher** -Contains List, Set or Map of Matchers, which have to all match with provided targets - -Wrapping matchers ------------------ -**Optional matcher** -Consists of a condition and a Matcher. -If the Matcher matches then Parameters are set by the way the condition is true, -If the Matcher doesn't match, then Condition is set to false.' - **Nested matcher** -Consists of parameter mapping and Matcher. * All the matched parameters are collected in local Parameters, which are then mapped to outer Parameters. - diff --git a/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java b/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java index acc10e2db99..9015901d08b 100644 --- a/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java +++ b/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java @@ -238,25 +238,26 @@ public boolean matches(CtTypeReference element) { }).size()); // contract: all public methods of those classes are properly tested in a JUnit test - List l = new ArrayList<>(); - l.add("spoon.pattern.PatternBuilder"); - l.add("spoon.pattern.Pattern"); - List errors = new ArrayList<>(); - for (String klass : l) { - for (CtMethod m : spoon.getFactory().Type().get(Class.forName(klass)).getMethods()) { - if (!m.hasModifier(ModifierKind.PUBLIC)) continue; - - if (spoon.getModel().getElements(new Filter() { - @Override - public boolean matches(CtExecutableReference element) { - return element.getExecutableDeclaration() == m; - } - }).size() == 0) { - errors.add(klass+"#"+m.getSimpleName()); - } - } - } - assertTrue("untested public methods: "+errors.toString(), errors.size()==0); +// List l = new ArrayList<>(); +// l.add("spoon.pattern.PatternBuilder"); +// l.add("spoon.pattern.Pattern"); +// List errorsMethods = new ArrayList<>(); +// for (String klass : l) { +// CtType ctType = spoon.getFactory().Type().get(Class.forName(klass)); +// for (CtMethod m : ctType.getMethods()) { +// if (!m.hasModifier(ModifierKind.PUBLIC)) continue; +// +// if (spoon.getModel().getElements(new Filter() { +// @Override +// public boolean matches(CtExecutableReference element) { +// return element.getExecutableDeclaration() == m; +// } +// }).size() == 0) { +// errorsMethods.add(klass+"#"+m.getSimpleName()); +// } +// } +// } +// assertTrue("untested public methods: "+errorsMethods.toString(), errorsMethods.size()==0); } @Test diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index 4512a73c2bd..be0c8da4f3a 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -3,13 +3,17 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.File; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -20,29 +24,47 @@ import spoon.Launcher; import spoon.OutputType; import spoon.SpoonModelBuilder; -import spoon.pattern.InlineStatementsBuilder; +import spoon.pattern.ConflictResolutionMode; import spoon.pattern.ParametersBuilder; import spoon.pattern.Pattern; import spoon.pattern.PatternBuilder; import spoon.pattern.TemplateModelBuilder; import spoon.pattern.matcher.Match; +import spoon.pattern.matcher.Quantifier; import spoon.pattern.parameter.ParameterInfo; import spoon.pattern.parameter.ParameterValueProvider; +import spoon.reflect.code.CtBlock; +import spoon.reflect.code.CtInvocation; +import spoon.reflect.code.CtLiteral; +import spoon.reflect.code.CtStatement; +import spoon.reflect.code.CtVariableRead; +import spoon.reflect.declaration.CtAnnotation; import spoon.reflect.declaration.CtClass; import spoon.reflect.declaration.CtElement; -import spoon.reflect.declaration.CtExecutable; -import spoon.reflect.declaration.CtInterface; import spoon.reflect.declaration.CtMethod; import spoon.reflect.declaration.CtType; import spoon.reflect.declaration.CtTypeMember; +import spoon.reflect.declaration.ModifierKind; import spoon.reflect.factory.Factory; import spoon.reflect.meta.ContainerKind; 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.visitor.DefaultJavaPrettyPrinter; import spoon.reflect.visitor.filter.TypeFilter; +import spoon.test.template.testclasses.match.MatchForEach; +import spoon.test.template.testclasses.match.MatchForEach2; +import spoon.test.template.testclasses.match.MatchIfElse; +import spoon.test.template.testclasses.match.MatchMap; +import spoon.test.template.testclasses.match.MatchModifiers; +import spoon.test.template.testclasses.match.MatchMultiple; +import spoon.test.template.testclasses.match.MatchMultiple2; +import spoon.test.template.testclasses.match.MatchMultiple3; +import spoon.test.template.testclasses.match.MatchThrowables; +import spoon.test.template.testclasses.match.MatchWithParameterCondition; +import spoon.test.template.testclasses.match.MatchWithParameterType; import spoon.test.template.testclasses.replace.DPPSample1; import spoon.test.template.testclasses.replace.NewPattern; import spoon.test.template.testclasses.replace.OldPattern; @@ -53,6 +75,944 @@ // main test of Spoon's patterns public class PatternTest { + + @Test + public void testMatchForeach() throws Exception { + //contract: inline foreach template can match multiple models into list of parameter values + CtType ctClass = ModelUtils.buildClass(MatchForEach.class); + + CtType type = ctClass.getFactory().Type().get(MatchForEach.class); + + // create one pattern from matcher1, with one parameter "values" +// public void matcher1(List values) { +// for (String value : values) { +// System.out.println(value); +// } +// } + Pattern pattern = PatternBuilder.create(new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) + .configureParameters(pb -> { + pb.parameter("values").byVariable("values").setContainerKind(ContainerKind.LIST); + }) + .configureInlineStatements(lsb -> lsb.byVariableName("values")) + .build(); + + List matches = pattern.getMatches(ctClass); + + assertEquals(2, matches.size()); + { + Match match = matches.get(0); + assertEquals(Arrays.asList("java.lang.System.out.println(value)"), listToListOfStrings(match.getMatchingElements())); + //FIX IT +// assertEquals(Arrays.asList(""), listToListOfStrings((List) match.getParameters().getValue("values"))); + } + { + Match match = matches.get(1); + assertEquals(Arrays.asList( + "java.lang.System.out.println(\"a\")", + "java.lang.System.out.println(\"Xxxx\")", + "java.lang.System.out.println(((java.lang.String) (null)))", + "java.lang.System.out.println(java.lang.Long.class.toString())"), listToListOfStrings(match.getMatchingElements())); + assertEquals(Arrays.asList( + "\"a\"", + "\"Xxxx\"", + "((java.lang.String) (null))", + "java.lang.Long.class.toString()"), listToListOfStrings((List) match.getParameters().getValue("values"))); + } + } + + @Test + public void testMatchForeachWithOuterSubstitution() throws Exception { + //contract: inline foreach template can match multiple models into list of parameter values including outer parameters + CtType ctClass = ModelUtils.buildClass(MatchForEach2.class); + + Pattern pattern = MatchForEach2.createPattern(ctClass.getFactory()); + + List matches = pattern.getMatches(ctClass); + + assertEquals(3, matches.size()); + { + Match match = matches.get(0); + assertEquals(Arrays.asList("int var = 0"), listToListOfStrings(match.getMatchingElements())); + //FIX IT +// assertEquals(Arrays.asList(""), listToListOfStrings((List) match.getParameters().getValue("values"))); + } + { + Match match = matches.get(1); + assertEquals(Arrays.asList( + "int cc = 0", + "java.lang.System.out.println(\"Xxxx\")", + "cc++", + "java.lang.System.out.println(((java.lang.String) (null)))", + "cc++"), listToListOfStrings(match.getMatchingElements())); + assertEquals(Arrays.asList( + "\"Xxxx\"", + "((java.lang.String) (null))"), listToListOfStrings((List) match.getParameters().getValue("values"))); + } + { + Match match = matches.get(2); + assertEquals(Arrays.asList( + "int dd = 0", + "java.lang.System.out.println(java.lang.Long.class.toString())", + "dd++"), listToListOfStrings(match.getMatchingElements())); + assertEquals(Arrays.asList( + "java.lang.Long.class.toString()"), listToListOfStrings((List) match.getParameters().getValue("values"))); + } + } + + @Test + public void testMatchIfElse() throws Exception { + //contract: inline switch Pattern can match one of the models + CtType ctClass = ModelUtils.buildClass(MatchIfElse.class); + + Pattern pattern = MatchIfElse.createPattern(ctClass.getFactory()); + + List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0)); + + assertEquals(7, matches.size()); + { + Match match = matches.get(0); + assertEquals(Arrays.asList("java.lang.System.out.println(i)"), listToListOfStrings(match.getMatchingElements())); + assertEquals(false, match.getParameters().getValue("option")); + assertEquals(true, match.getParameters().getValue("option2")); + assertEquals("i", match.getParameters().getValue("value").toString()); + } + { + Match match = matches.get(1); + assertEquals(Arrays.asList("java.lang.System.out.println(\"a\")"), listToListOfStrings(match.getMatchingElements())); + assertEquals(true, match.getParameters().getValue("option")); + assertEquals(false, match.getParameters().getValue("option2")); + assertEquals("\"a\"", match.getParameters().getValue("value").toString()); + } + { + Match match = matches.get(2); + assertEquals(Arrays.asList("java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings(match.getMatchingElements())); + assertEquals(true, match.getParameters().getValue("option")); + assertEquals(false, match.getParameters().getValue("option2")); + assertEquals("\"Xxxx\"", match.getParameters().getValue("value").toString()); + } + { + Match match = matches.get(3); + assertEquals(Arrays.asList("java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings(match.getMatchingElements())); + assertEquals(true, match.getParameters().getValue("option")); + assertEquals(false, match.getParameters().getValue("option2")); + assertEquals("((java.lang.String) (null))", match.getParameters().getValue("value").toString()); + } + { + Match match = matches.get(4); + assertEquals(Arrays.asList("java.lang.System.out.println(2018)"), listToListOfStrings(match.getMatchingElements())); + assertEquals(false, match.getParameters().getValue("option")); + assertEquals(true, match.getParameters().getValue("option2")); + assertEquals("2018", match.getParameters().getValue("value").toString()); + } + { + Match match = matches.get(5); + assertEquals(Arrays.asList("java.lang.System.out.println(java.lang.Long.class.toString())"), listToListOfStrings(match.getMatchingElements())); + assertEquals(true, match.getParameters().getValue("option")); + assertEquals(false, match.getParameters().getValue("option2")); + assertEquals("java.lang.Long.class.toString()", match.getParameters().getValue("value").toString()); + } + { + Match match = matches.get(6); + assertEquals(Arrays.asList("java.lang.System.out.println(3.14)"), listToListOfStrings(match.getMatchingElements())); + assertEquals(false, match.getParameters().getValue("option")); + assertEquals(false, match.getParameters().getValue("option2")); + assertEquals("3.14", match.getParameters().getValue("value").toString()); + } + } + @Test + public void testGenerateMultiValues() throws Exception { + CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); + Pattern pattern = MatchMultiple.createPattern(ctClass.getFactory(), null, null, null); + Map params = new HashMap<>(); + params.put("printedValue", "does it work?"); + params.put("statements", ctClass.getMethodsByName("testMatch1").get(0).getBody().getStatements().subList(0, 3)); + List generated = pattern.substituteList(ctClass.getFactory(), CtStatement.class, params); + assertEquals(Arrays.asList( + "int i = 0", + "i++", + "java.lang.System.out.println(i)", + "java.lang.System.out.println(\"does it work?\")"), generated.stream().map(Object::toString).collect(Collectors.toList())); + } + @Test + public void testMatchGreedyMultiValueUnlimited() throws Exception { + //contract: multivalue parameter can match multiple nodes into list of parameter values. + //contract: default greedy matching eats everything but can leave some matches if it is needed to match remaining template parameters + CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); + Pattern pattern = MatchMultiple.createPattern(ctClass.getFactory(), null, null, null); + + List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0)); + + assertEquals(1, matches.size()); + Match match = matches.get(0); + //check all statements are matched + assertEquals(Arrays.asList( + "int i = 0", + "i++", + "java.lang.System.out.println(i)", + "java.lang.System.out.println(\"Xxxx\")", + "java.lang.System.out.println(((java.lang.String) (null)))", + "java.lang.System.out.println(\"last one\")"), listToListOfStrings(match.getMatchingElements())); + + //check all statements excluding last are stored as value of "statements" parameter + assertEquals(Arrays.asList( + "int i = 0", + "i++", + "java.lang.System.out.println(i)", + "java.lang.System.out.println(\"Xxxx\")", + "java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings((List) match.getParameters().getValue("statements"))); + //last statement is matched by last template, which saves printed value + assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); + assertEquals("\"last one\"", match.getParameters().getValue("printedValue").toString()); + } + + @Test + public void testMatchGreedyMultiValueMaxCountLimit() throws Exception { + //contract: default greedy matching eats everything until max count = 3 + CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); + Pattern pattern = MatchMultiple.createPattern(ctClass.getFactory(), null, null, 3); + + List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0)); + + assertEquals(2, matches.size()); + { + Match match = matches.get(0); + //check 3 + 1 statements are matched + assertEquals(Arrays.asList( + "int i = 0", + "i++", + "java.lang.System.out.println(i)", + "java.lang.System.out.println(\"Xxxx\")" + ), listToListOfStrings(match.getMatchingElements())); + + //check 3 statements are stored as value of "statements" parameter + assertEquals(Arrays.asList( + "int i = 0", + "i++", + "java.lang.System.out.println(i)"), listToListOfStrings((List) match.getParameters().getValue("statements"))); + //4th statement is matched by last template, which saves printed value + assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); + assertEquals("\"Xxxx\"", match.getParameters().getValue("printedValue").toString()); + } + { + Match match = matches.get(1); + //check remaining next 2 statements are matched + assertEquals(Arrays.asList( + "java.lang.System.out.println(((java.lang.String) (null)))", + "java.lang.System.out.println(\"last one\")"), listToListOfStrings(match.getMatchingElements())); + + //check all statements excluding last are stored as value of "statements" parameter + assertEquals(Arrays.asList( + "java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings((List) match.getParameters().getValue("statements"))); + //last statement is matched by last template, which saves printed value + assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); + assertEquals("\"last one\"", match.getParameters().getValue("printedValue").toString()); + } + } + + + @Test + public void testMatchReluctantMultivalue() throws Exception { + //contract: multivalue parameter can match multiple nodes into list of parameter values. + //contract: reluctant matches only minimal amount + CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); + + Pattern pattern = MatchMultiple.createPattern(ctClass.getFactory(), Quantifier.RELUCTANT, null, null); + + List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0)); + + assertEquals(3, matches.size()); + { + Match match = matches.get(0); + //check all statements are matched + assertEquals(Arrays.asList( + "int i = 0", + "i++", + "java.lang.System.out.println(i)", //this is println(int), but last temple matches println(String) - it is question if it is wanted or not ... + "java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings(match.getMatchingElements())); + + //check all statements excluding last are stored as value of "statements" parameter + assertEquals(Arrays.asList( + "int i = 0", + "i++", + "java.lang.System.out.println(i)"), listToListOfStrings((List) match.getParameters().getValue("statements"))); + //last statement is matched by last template, which saves printed value + assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); + assertEquals("\"Xxxx\"", match.getParameters().getValue("printedValue").toString()); + } + { + Match match = matches.get(1); + //check all statements are matched + assertEquals(Arrays.asList( + "java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings(match.getMatchingElements())); + + //check all statements excluding last are stored as value of "statements" parameter + assertEquals(Arrays.asList(), listToListOfStrings((List) match.getParameters().getValue("statements"))); + //last statement is matched by last template, which saves printed value + assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); + assertEquals("((java.lang.String) (null))", match.getParameters().getValue("printedValue").toString()); + } + { + Match match = matches.get(2); + //check all statements are matched + assertEquals(Arrays.asList( + "java.lang.System.out.println(\"last one\")"), listToListOfStrings(match.getMatchingElements())); + + //check all statements excluding last are stored as value of "statements" parameter + assertEquals(Arrays.asList(), listToListOfStrings((List) match.getParameters().getValue("statements"))); + //last statement is matched by last template, which saves printed value + assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); + assertEquals("\"last one\"", match.getParameters().getValue("printedValue").toString()); + } + } + @Test + public void testMatchReluctantMultivalueMinCount1() throws Exception { + //contract: multivalue parameter can match multiple nodes into list of parameter values. + //contract: reluctant matches only at least 1 node in this case + CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); + + Pattern pattern = MatchMultiple.createPattern(ctClass.getFactory(), Quantifier.RELUCTANT, 1, null); + + List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0)); + + assertEquals(2, matches.size()); + { + Match match = matches.get(0); + //check all statements are matched + assertEquals(Arrays.asList( + "int i = 0", + "i++", + "java.lang.System.out.println(i)", //this is println(int), but last temple matches println(String) - it is question if it is wanted or not ... + "java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings(match.getMatchingElements())); + + //check all statements excluding last are stored as value of "statements" parameter + assertEquals(Arrays.asList( + "int i = 0", + "i++", + "java.lang.System.out.println(i)"), listToListOfStrings((List) match.getParameters().getValue("statements"))); + //last statement is matched by last template, which saves printed value + assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); + assertEquals("\"Xxxx\"", match.getParameters().getValue("printedValue").toString()); + } + { + Match match = matches.get(1); + //check all statements are matched + assertEquals(Arrays.asList( + "java.lang.System.out.println(((java.lang.String) (null)))", + "java.lang.System.out.println(\"last one\")"), listToListOfStrings(match.getMatchingElements())); + + //check all statements excluding last are stored as value of "statements" parameter + assertEquals(Arrays.asList( + "java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings((List) match.getParameters().getValue("statements"))); + //last statement is matched by last template, which saves printed value + assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); + assertEquals("\"last one\"", match.getParameters().getValue("printedValue").toString()); + } + } + @Test + public void testMatchReluctantMultivalueExactly2() throws Exception { + //contract: multivalue parameter can match multiple nodes into list of parameter values. + //contract: reluctant matches min 2 and max 2 nodes in this case + CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); + + Pattern pattern = MatchMultiple.createPattern(ctClass.getFactory(), Quantifier.RELUCTANT, 2, 2); + + List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0)); + + assertEquals(1, matches.size()); + { + Match match = matches.get(0); + //check only 2 statements are matched + next one + assertEquals(Arrays.asList( + "i++", + "java.lang.System.out.println(i)", //this is println(int), but last temple matches println(String) - it is question if it is wanted or not ... + "java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings(match.getMatchingElements())); + + //check 2 statements excluding last are stored as value of "statements" parameter + assertEquals(Arrays.asList( + "i++", + "java.lang.System.out.println(i)"), listToListOfStrings((List) match.getParameters().getValue("statements"))); + //last statement is matched by last template, which saves printed value + assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); + assertEquals("\"Xxxx\"", match.getParameters().getValue("printedValue").toString()); + } + } + + @Test + public void testMatchPossesiveMultiValueUnlimited() throws Exception { + //contract: multivalue parameter can match multiple nodes into list of parameter values. + //contract: possessive matching eats everything and never returns back + CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); + Pattern pattern = MatchMultiple.createPattern(ctClass.getFactory(), Quantifier.POSSESSIVE, null, null); + + List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0).getBody()); + //the last template has nothing to match -> no match + assertEquals(0, matches.size()); + } + @Test + public void testMatchPossesiveMultiValueMaxCount4() throws Exception { + //contract: multivalue parameter can match multiple nodes into list of parameter values. + //contract: possessive matching eats everything and never returns back + CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); + Pattern pattern = MatchMultiple.createPattern(ctClass.getFactory(), Quantifier.POSSESSIVE, null, 4); + + List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0)); + + assertEquals(1, matches.size()); + Match match = matches.get(0); + //check 4 statements are matched + last template + assertEquals(Arrays.asList( + "int i = 0", + "i++", + "java.lang.System.out.println(i)", + "java.lang.System.out.println(\"Xxxx\")", + "java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings(match.getMatchingElements())); + + //check 4 statements excluding last are stored as value of "statements" parameter + assertEquals(Arrays.asList( + "int i = 0", + "i++", + "java.lang.System.out.println(i)", + "java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings((List) match.getParameters().getValue("statements"))); + //last statement is matched by last template, which saves printed value + assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); + assertEquals("((java.lang.String) (null))", match.getParameters().getValue("printedValue").toString()); + } + @Test + public void testMatchPossesiveMultiValueMinCount() throws Exception { + //contract: check possessive matching with min count limit and GREEDY back off + CtType ctClass = ModelUtils.buildClass(MatchMultiple3.class); + for (int i = 0; i < 7; i++) { + final int count = i; + Pattern pattern = MatchMultiple3.createPattern(ctClass.getFactory(), pb -> { + pb.parameter("statements1").setMatchingStrategy(Quantifier.GREEDY); + pb.parameter("statements2").setMatchingStrategy(Quantifier.POSSESSIVE).setMinOccurence(count).setMaxOccurence(count); + }); + + List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0).getBody()); + if (count < 6) { + //the last template has nothing to match -> no match + assertEquals("count="+count, 1, matches.size()); + assertEquals("count="+count, 5-count, getSize(matches.get(0).getParameters().getValue("statements1"))); + assertEquals("count="+count, count, getSize(matches.get(0).getParameters().getValue("statements2"))); + } else { + //the possessive matcher eat too much. There is no target element for last `printedValue` variable + assertEquals("count="+count, 0, matches.size()); + } + } + } + + @Test + public void testMatchPossesiveMultiValueMinCount2() throws Exception { + //contract: check possessive matching with min count limit and GREEDY back off + CtType ctClass = ModelUtils.buildClass(MatchMultiple2.class); + for (int i = 0; i < 7; i++) { + final int count = i; + Pattern pattern = MatchMultiple2.createPattern(ctClass.getFactory(), pb -> { + pb.parameter("statements1").setMatchingStrategy(Quantifier.GREEDY); + pb.parameter("statements2").setMatchingStrategy(Quantifier.POSSESSIVE).setMinOccurence(count).setMaxOccurence(count); + pb.parameter("printedValue").setMatchingStrategy(Quantifier.POSSESSIVE).setContainerKind(ContainerKind.LIST).setMinOccurence(2); + }); + + List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0).getBody()); + if (count < 5) { + //the last template has nothing to match -> no match + assertEquals("count="+count, 1, matches.size()); + assertEquals("count="+count, 4-count, getSize(matches.get(0).getParameters().getValue("statements1"))); + assertEquals("count="+count, count, getSize(matches.get(0).getParameters().getValue("statements2"))); + assertEquals("count="+count, 2, getSize(matches.get(0).getParameters().getValue("printedValue"))); + } else { + //the possessive matcher eat too much. There is no target element for last `printedValue` variable + assertEquals("count="+count, 0, matches.size()); + } + } + } + @Test + public void testMatchGreedyMultiValueMinCount2() throws Exception { + //contract: check possessive matching with min count limit and GREEDY back off + CtType ctClass = ModelUtils.buildClass(MatchMultiple2.class); + for (int i = 0; i < 7; i++) { + final int count = i; + Pattern pattern = MatchMultiple2.createPattern(ctClass.getFactory(), pb -> { + pb.parameter("statements1").setMatchingStrategy(Quantifier.RELUCTANT); + pb.parameter("statements2").setMatchingStrategy(Quantifier.GREEDY).setMaxOccurence(count); + pb.parameter("printedValue").setMatchingStrategy(Quantifier.GREEDY).setContainerKind(ContainerKind.LIST).setMinOccurence(2); + }); + + List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0).getBody()); + if (count < 7) { + //the last template has nothing to match -> no match + assertEquals("count="+count, 1, matches.size()); + assertEquals("count="+count, Math.max(0, 3-count), getSize(matches.get(0).getParameters().getValue("statements1"))); + assertEquals("count="+count, count - Math.max(0, count-4), getSize(matches.get(0).getParameters().getValue("statements2"))); + assertEquals("count="+count, Math.max(2, 3 - Math.max(0, count-3)), getSize(matches.get(0).getParameters().getValue("printedValue"))); + } else { + //the possessive matcher eat too much. There is no target element for last `printedValue` variable + assertEquals("count="+count, 0, matches.size()); + } + } + } + + private int getSize(Object o) { + if (o instanceof List) { + return ((List) o).size(); + } + if (o == null) { + return 0; + } + fail("Unexpected object of type " + o.getClass()); + return -1; + } + + @Test + public void testMatchParameterValue() throws Exception { + //contract: by default the parameter value is the reference to real node from the model + CtType ctClass = ModelUtils.buildClass(MatchWithParameterType.class); + + Pattern pattern = MatchWithParameterType.createPattern(ctClass.getFactory(), null); + + List matches = pattern.getMatches(ctClass); + + assertEquals(5, matches.size()); + { + Match match = matches.get(0); + assertEquals(Arrays.asList("java.lang.System.out.println(value)"), listToListOfStrings(match.getMatchingElements())); + Object value = match.getParameters().getValue("value"); + assertTrue(value instanceof CtVariableRead); + assertEquals("value", value.toString()); + //contract: the value is reference to found node (not a clone) + assertTrue(((CtElement)value).isParentInitialized()); + assertSame(CtRole.ARGUMENT, ((CtElement)value).getRoleInParent()); + } + { + Match match = matches.get(1); + assertEquals(Arrays.asList("java.lang.System.out.println(\"a\")"), listToListOfStrings(match.getMatchingElements())); + assertTrue(match.getParameters().getValue("value") instanceof CtLiteral); + assertEquals("\"a\"", match.getParameters().getValue("value").toString()); + } + { + Match match = matches.get(2); + assertEquals(Arrays.asList("java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings(match.getMatchingElements())); + assertTrue(match.getParameters().getValue("value") instanceof CtLiteral); + assertEquals("\"Xxxx\"", match.getParameters().getValue("value").toString()); + } + { + Match match = matches.get(3); + assertEquals(Arrays.asList("java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings(match.getMatchingElements())); + assertTrue(match.getParameters().getValue("value") instanceof CtLiteral); + assertEquals("((java.lang.String) (null))", match.getParameters().getValue("value").toString()); + } + { + Match match = matches.get(4); + assertEquals(Arrays.asList("java.lang.System.out.println(java.lang.Long.class.toString())"), listToListOfStrings(match.getMatchingElements())); + assertTrue(match.getParameters().getValue("value") instanceof CtInvocation); + assertEquals("java.lang.Long.class.toString()", match.getParameters().getValue("value").toString()); + } + } + + @Test + public void testMatchParameterValueType() throws Exception { + //contract: the parameter value type matches only values of required type + CtType ctClass = ModelUtils.buildClass(MatchWithParameterType.class); + { + Pattern pattern = MatchWithParameterType.createPattern(ctClass.getFactory(), CtLiteral.class); + + List matches = pattern.getMatches(ctClass); + + assertEquals(3, matches.size()); + { + Match match = matches.get(0); + assertEquals(Arrays.asList("java.lang.System.out.println(\"a\")"), listToListOfStrings(match.getMatchingElements())); + assertTrue(match.getParameters().getValue("value") instanceof CtLiteral); + assertEquals("\"a\"", match.getParameters().getValue("value").toString()); + } + { + Match match = matches.get(1); + assertEquals(Arrays.asList("java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings(match.getMatchingElements())); + assertTrue(match.getParameters().getValue("value") instanceof CtLiteral); + assertEquals("\"Xxxx\"", match.getParameters().getValue("value").toString()); + } + { + Match match = matches.get(2); + assertEquals(Arrays.asList("java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings(match.getMatchingElements())); + assertTrue(match.getParameters().getValue("value") instanceof CtLiteral); + assertEquals("((java.lang.String) (null))", match.getParameters().getValue("value").toString()); + } + } + { + Pattern pattern = MatchWithParameterType.createPattern(ctClass.getFactory(), CtInvocation.class); + + List matches = pattern.getMatches(ctClass); + + assertEquals(1, matches.size()); + { + Match match = matches.get(0); + assertEquals(Arrays.asList("java.lang.System.out.println(java.lang.Long.class.toString())"), listToListOfStrings(match.getMatchingElements())); + assertTrue(match.getParameters().getValue("value") instanceof CtInvocation); + assertEquals("java.lang.Long.class.toString()", match.getParameters().getValue("value").toString()); + } + + } + } + + @Test + public void testMatchParameterCondition() throws Exception { + //contract: the parameter value matching condition causes that only matching parameter values are accepted + //if the value isn't matching then node is not matched + CtType ctClass = ModelUtils.buildClass(MatchWithParameterCondition.class); + { + Pattern pattern = MatchWithParameterCondition.createPattern(ctClass.getFactory(), (Object value) -> value instanceof CtLiteral); + + List matches = pattern.getMatches(ctClass); + + assertEquals(3, matches.size()); + { + Match match = matches.get(0); + assertEquals(Arrays.asList("java.lang.System.out.println(\"a\")"), listToListOfStrings(match.getMatchingElements())); + assertTrue(match.getParameters().getValue("value") instanceof CtLiteral); + assertEquals("\"a\"", match.getParameters().getValue("value").toString()); + } + { + Match match = matches.get(1); + assertEquals(Arrays.asList("java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings(match.getMatchingElements())); + assertTrue(match.getParameters().getValue("value") instanceof CtLiteral); + assertEquals("\"Xxxx\"", match.getParameters().getValue("value").toString()); + } + { + Match match = matches.get(2); + assertEquals(Arrays.asList("java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings(match.getMatchingElements())); + assertTrue(match.getParameters().getValue("value") instanceof CtLiteral); + assertEquals("((java.lang.String) (null))", match.getParameters().getValue("value").toString()); + } + } + } + + @Test + public void testMatchOfAttribute() throws Exception { + //contract: match some nodes like template, but with some variable attributes + CtType ctClass = ModelUtils.buildClass(MatchModifiers.class); + { + //match all methods with arbitrary name, modifiers, parameters, but with empty body and return type void + Pattern pattern = MatchModifiers.createPattern(ctClass.getFactory(), false); + List matches = pattern.getMatches(ctClass); + assertEquals(3, matches.size()); + { + Match match = matches.get(0); + assertEquals(1, match.getMatchingElements().size()); + assertEquals("matcher1", match.getMatchingElement(CtMethod.class).getSimpleName()); + assertEquals(3, match.getParametersMap().size()); + assertEquals("matcher1", match.getParametersMap().get("methodName")); + assertEquals(new HashSet<>(Arrays.asList(ModifierKind.PUBLIC)), match.getParametersMap().get("modifiers")); + assertEquals(Arrays.asList(), match.getParametersMap().get("parameters")); + } + { + Match match = matches.get(1); + assertEquals(1, match.getMatchingElements().size()); + assertEquals("publicStaticMethod", match.getMatchingElement(CtMethod.class).getSimpleName()); + assertEquals(3, match.getParametersMap().size()); + assertEquals("publicStaticMethod", match.getParametersMap().get("methodName")); + assertEquals(new HashSet<>(Arrays.asList(ModifierKind.PUBLIC, ModifierKind.STATIC)), match.getParametersMap().get("modifiers")); + assertEquals(Arrays.asList(), match.getParametersMap().get("parameters")); + } + { + Match match = matches.get(2); + assertEquals(1, match.getMatchingElements().size()); + assertEquals("packageProtectedMethodWithParam", match.getMatchingElement(CtMethod.class).getSimpleName()); + assertEquals(3, match.getParametersMap().size()); + assertEquals("packageProtectedMethodWithParam", match.getParametersMap().get("methodName")); + assertEquals(new HashSet<>(), match.getParametersMap().get("modifiers")); + assertEquals(2, ((List) match.getParametersMap().get("parameters")).size()); + } + } + { + //match all methods with arbitrary name, modifiers, parameters and body, but with return type void + Pattern pattern = MatchModifiers.createPattern(ctClass.getFactory(), true); + List matches = pattern.getMatches(ctClass); + assertEquals(4, matches.size()); + { + Match match = matches.get(0); + assertEquals(1, match.getMatchingElements().size()); + assertEquals("matcher1", match.getMatchingElement(CtMethod.class).getSimpleName()); + assertEquals(4, match.getParametersMap().size()); + assertEquals("matcher1", match.getParametersMap().get("methodName")); + assertEquals(new HashSet<>(Arrays.asList(ModifierKind.PUBLIC)), match.getParametersMap().get("modifiers")); + assertEquals(Arrays.asList(), match.getParametersMap().get("parameters")); + assertEquals(Arrays.asList(), match.getParametersMap().get("statements")); + } + { + Match match = matches.get(1); + assertEquals(1, match.getMatchingElements().size()); + assertEquals("publicStaticMethod", match.getMatchingElement(CtMethod.class).getSimpleName()); + assertEquals(4, match.getParametersMap().size()); + assertEquals("publicStaticMethod", match.getParametersMap().get("methodName")); + assertEquals(new HashSet<>(Arrays.asList(ModifierKind.PUBLIC, ModifierKind.STATIC)), match.getParametersMap().get("modifiers")); + assertEquals(Arrays.asList(), match.getParametersMap().get("parameters")); + assertEquals(Arrays.asList(), match.getParametersMap().get("statements")); + } + { + Match match = matches.get(2); + assertEquals(1, match.getMatchingElements().size()); + assertEquals("packageProtectedMethodWithParam", match.getMatchingElement(CtMethod.class).getSimpleName()); + assertEquals(4, match.getParametersMap().size()); + assertEquals("packageProtectedMethodWithParam", match.getParametersMap().get("methodName")); + assertEquals(new HashSet<>(), match.getParametersMap().get("modifiers")); + assertEquals(2, ((List) match.getParametersMap().get("parameters")).size()); + assertEquals(Arrays.asList(), match.getParametersMap().get("statements")); + } + { + Match match = matches.get(3); + assertEquals(1, match.getMatchingElements().size()); + assertEquals("withBody", match.getMatchingElement(CtMethod.class).getSimpleName()); + assertEquals(4, match.getParametersMap().size()); + assertEquals("withBody", match.getParametersMap().get("methodName")); + assertEquals(new HashSet<>(Arrays.asList(ModifierKind.PRIVATE)), match.getParametersMap().get("modifiers")); + assertEquals(0, ((List) match.getParametersMap().get("parameters")).size()); + assertEquals(2, ((List) match.getParametersMap().get("statements")).size()); + assertEquals("this.getClass()", ((List) match.getParametersMap().get("statements")).get(0).toString()); + assertEquals("java.lang.System.out.println()", ((List) match.getParametersMap().get("statements")).get(1).toString()); + } + } + } + + + @Test + public void testMatchOfMapAttribute() throws Exception { + //contract: match a pattern with an "open" annotation (different values can be matched) + CtType matchMapClass = ModelUtils.buildClass(MatchMap.class); + { + CtType type = matchMapClass.getFactory().Type().get(MatchMap.class); + // create a pattern from method matcher1 + //match all methods with arbitrary name, and annotation @Check, parameters, but with empty body and return type void +// @Check() +// void matcher1() { +// } + Pattern pattern = PatternBuilder.create(new TemplateModelBuilder(type).setTypeMember("matcher1").getTemplateModels()) + .configureParameters(pb -> { + //match any value of @Check annotation to parameter `testAnnotations` + pb.parameter("__pattern_param_annot").byRole(new TypeFilter(CtAnnotation.class), CtRole.VALUE).setContainerKind(ContainerKind.MAP); + //match any method name + pb.parameter("__pattern_param_method_name").byString("matcher1"); + }) + .build(); + + // we apply the pattern on MatchMap + List matches = pattern.getMatches(matchMapClass); + assertEquals(3, matches.size()); + { + // first match: matcher1 itself (normal) + Match match = matches.get(0); + assertEquals(1, match.getMatchingElements().size()); + assertEquals("matcher1", match.getMatchingElement(CtMethod.class).getSimpleName()); + assertEquals(2, match.getParametersMap().size()); + assertEquals("matcher1", match.getParametersMap().get("__pattern_param_method_name")); + Map values = getMap(match, "__pattern_param_annot"); + assertEquals(0, values.size()); + } + { + // first match: m1 itself (normal) +// @Check(value = "xyz") +// void m1() { +// } + Match match = matches.get(1); + assertEquals(1, match.getMatchingElements().size()); + assertEquals("m1", match.getMatchingElement(CtMethod.class).getSimpleName()); + assertEquals(2, match.getParametersMap().size()); + assertEquals("m1", match.getParametersMap().get("__pattern_param_method_name")); + Map values = getMap(match, "__pattern_param_annot"); + assertEquals(1, values.size()); + assertEquals("\"xyz\"", values.get("value").toString()); + } + { + // second match: m2, it also contains a timeout value + Match match = matches.get(2); + assertEquals(1, match.getMatchingElements().size()); + assertEquals("m2", match.getMatchingElement(CtMethod.class).getSimpleName()); + assertEquals(2, match.getParametersMap().size()); + assertEquals("m2", match.getParametersMap().get("__pattern_param_method_name")); + Map values = getMap(match, "__pattern_param_annot"); + assertEquals(2, values.size()); + assertEquals("\"abc\"", values.get("value").toString()); + assertEquals("123", values.get("timeout").toString()); + } + } + } + + private Map getMap(Match match, String name) { + Object v = match.getParametersMap().get(name); + assertNotNull(v); + return ((ParameterValueProvider) v).asMap(); + } + + @Test + public void testMatchOfMapAttributeAndOtherAnnotations() throws Exception { + //contract: match a pattern with an "open" annotation (different values can be matched) + // same test but with one more pattern parameter: allAnnotations + CtType ctClass = ModelUtils.buildClass(MatchMap.class); + { + CtType type = ctClass.getFactory().Type().get(MatchMap.class); + // create a pattern from method matcher1 + //match all methods with arbitrary name, with any annotation set, Test modifiers, parameters, but with empty body and return type void + Pattern pattern = PatternBuilder.create(new TemplateModelBuilder(type).setTypeMember("matcher1").getTemplateModels()) + .configureParameters(pb -> { + //match any value of @Check annotation to parameter `testAnnotations` + //match any method name + pb.parameter("methodName").byString("matcher1"); + //match on all annotations of method + pb.parameter("allAnnotations") + .setConflictResolutionMode(ConflictResolutionMode.APPEND) + .byRole(new TypeFilter<>(CtMethod.class), CtRole.ANNOTATION) + ; + pb.parameter("CheckAnnotationValues").byRole(new TypeFilter(CtAnnotation.class), CtRole.VALUE).setContainerKind(ContainerKind.MAP); + }) + .build(); + List matches = pattern.getMatches(ctClass); + + // we match all methods + assertEquals(4, matches.size()); + // the new ones is the one with deprecated + { + Match match = matches.get(3); + assertEquals(1, match.getMatchingElements().size()); + assertEquals("deprecatedTestAnnotation2", match.getMatchingElement(CtMethod.class).getSimpleName()); + assertEquals(3, match.getParametersMap().size()); + assertEquals("deprecatedTestAnnotation2", match.getParametersMap().get("methodName")); + assertEquals("{timeout=4567}", getMap(match, "CheckAnnotationValues").toString()); + assertEquals("@java.lang.Deprecated", match.getParameters().getValue("allAnnotations").toString()); + } + + } + } + + @Test + public void testMatchOfMapKeySubstring() throws Exception { + //contract: match substring in key of Map Entry - match key of annotation value + CtType ctClass = ModelUtils.buildClass(MatchMap.class); + { + //match all methods with arbitrary name, and Annotation Test modifiers, parameters, but with empty body and return type void + CtType type = ctClass.getFactory().Type().get(MatchMap.class); + Pattern pattern = PatternBuilder.create(new TemplateModelBuilder(type).setTypeMember("m1").getTemplateModels()) + .configureParameters(pb -> { + //match any value of @Check annotation to parameter `testAnnotations` + pb.parameter("CheckKey").bySubstring("value"); + pb.parameter("CheckValue").byFilter((CtLiteral lit) -> true); + //match any method name + pb.parameter("methodName").byString("m1"); + //match on all annotations of method + pb.parameter("allAnnotations") + .setConflictResolutionMode(ConflictResolutionMode.APPEND) + .byRole(new TypeFilter<>(CtMethod.class), CtRole.ANNOTATION); + }) + .build(); + List matches = pattern.getMatches(ctClass); + assertEquals(2, matches.size()); + { + Match match = matches.get(0); + assertEquals(1, match.getMatchingElements().size()); + assertEquals("m1", match.getMatchingElement(CtMethod.class).getSimpleName()); + assertEquals(3, match.getParametersMap().size()); + assertEquals("m1", match.getParametersMap().get("methodName")); + assertEquals("value", match.getParameters().getValue("CheckKey").toString()); + assertEquals("\"xyz\"", match.getParameters().getValue("CheckValue").toString()); + } + { + Match match = matches.get(1); + assertEquals(1, match.getMatchingElements().size()); + assertEquals("deprecatedTestAnnotation2", match.getMatchingElement(CtMethod.class).getSimpleName()); + assertEquals(4, match.getParametersMap().size()); + assertEquals("deprecatedTestAnnotation2", match.getParametersMap().get("methodName")); + assertEquals("timeout", match.getParameters().getValue("CheckKey").toString()); + assertEquals("4567", match.getParameters().getValue("CheckValue").toString()); + assertEquals("@java.lang.Deprecated", match.getParameters().getValue("allAnnotations").toString()); + } + } + } + + @Test + public void testMatchInSet() throws Exception { + //contract: match elements in container of type Set - e.g method throwables + CtType ctClass = ModelUtils.buildClass(MatchThrowables.class); + Factory f = ctClass.getFactory(); + Pattern pattern = PatternBuilder.create(new TemplateModelBuilder(ctClass).setTypeMember("matcher1").getTemplateModels()) + .configureParameters(pb -> { + pb.parameter("otherThrowables") + //add matcher for other arbitrary throwables + .setConflictResolutionMode(ConflictResolutionMode.APPEND) + .setContainerKind(ContainerKind.SET) + .setMinOccurence(0) + .byRole(new TypeFilter(CtMethod.class), CtRole.THROWN); + }) + .configureParameters(pb -> { + //define other parameters too to match all kinds of methods + pb.parameter("modifiers").byRole(new TypeFilter(CtMethod.class), CtRole.MODIFIER); + pb.parameter("methodName").byString("matcher1"); + pb.parameter("parameters").byRole(new TypeFilter(CtMethod.class), CtRole.PARAMETER); + pb.parameter("statements").byRole(new TypeFilter(CtBlock.class), CtRole.STATEMENT); + }) + .build(); + String str = pattern.toString(); + List matches = pattern.getMatches(ctClass); + assertEquals(4, matches.size()); + { + Match match = matches.get(0); + assertEquals(1, match.getMatchingElements().size()); + assertEquals("matcher1", match.getMatchingElement(CtMethod.class).getSimpleName()); + assertEquals(new HashSet(Arrays.asList( + "modifiers","methodName","parameters","statements")), match.getParametersMap().keySet()); + } + { + Match match = matches.get(1); + assertEquals(1, match.getMatchingElements().size()); + assertEquals("sample2", match.getMatchingElement(CtMethod.class).getSimpleName()); + assertEquals(new HashSet(Arrays.asList( + "otherThrowables", "modifiers","methodName","parameters","statements")), match.getParametersMap().keySet()); + assertEquals(new HashSet(Arrays.asList( + "java.lang.UnsupportedOperationException", + "java.lang.IllegalArgumentException")), + ((Set>) match.getParameters().getValue("otherThrowables")) + .stream().map(e->e.toString()).collect(Collectors.toSet())); + } + { + Match match = matches.get(2); + assertEquals(1, match.getMatchingElements().size()); + assertEquals("sample3", match.getMatchingElement(CtMethod.class).getSimpleName()); + assertEquals(new HashSet(Arrays.asList( + "otherThrowables", "modifiers","methodName","parameters","statements")), match.getParametersMap().keySet()); + assertEquals(new HashSet(Arrays.asList( + "java.lang.IllegalArgumentException")), + ((Set>) match.getParameters().getValue("otherThrowables")) + .stream().map(e->e.toString()).collect(Collectors.toSet())); + } + { + Match match = matches.get(3); + assertEquals(1, match.getMatchingElements().size()); + assertEquals("sample4", match.getMatchingElement(CtMethod.class).getSimpleName()); + assertEquals(new HashSet(Arrays.asList( + "modifiers","methodName","parameters","statements")), match.getParametersMap().keySet()); + } + } + + private List listToListOfStrings(List list) { + if (list == null) { + return Collections.emptyList(); + } + List strings = new ArrayList<>(list.size()); + for (Object obj : list) { + strings.add(obj == null ? "null" : obj.toString()); + } + return strings; + } + + private MapBuilder map() { + return new MapBuilder(); + } + + class MapBuilder extends LinkedHashMap { + public MapBuilder put(String key, Object value) { + super.put(key, value); + return this; + } + } + + @Test public void testPatternParameters() { //contract: all the parameters of Pattern are available diff --git a/src/test/java/spoon/test/template/TemplateMatcherTest.java b/src/test/java/spoon/test/template/TemplateMatcherTest.java deleted file mode 100644 index 8f515e78fa6..00000000000 --- a/src/test/java/spoon/test/template/TemplateMatcherTest.java +++ /dev/null @@ -1,952 +0,0 @@ -package spoon.test.template; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -import org.junit.Test; - -import spoon.pattern.ConflictResolutionMode; -import spoon.pattern.Pattern; -import spoon.pattern.PatternBuilder; -import spoon.pattern.TemplateModelBuilder; -import spoon.pattern.matcher.Match; -import spoon.pattern.matcher.Quantifier; -import spoon.pattern.parameter.ParameterValueProvider; -import spoon.reflect.code.CtBlock; -import spoon.reflect.code.CtInvocation; -import spoon.reflect.code.CtLiteral; -import spoon.reflect.code.CtStatement; -import spoon.reflect.code.CtVariableRead; -import spoon.reflect.declaration.CtElement; -import spoon.reflect.declaration.CtMethod; -import spoon.reflect.declaration.CtType; -import spoon.reflect.declaration.ModifierKind; -import spoon.reflect.factory.Factory; -import spoon.reflect.meta.ContainerKind; -import spoon.reflect.path.CtRole; -import spoon.reflect.reference.CtTypeReference; -import spoon.reflect.visitor.filter.TypeFilter; -import spoon.test.template.testclasses.match.MatchForEach; -import spoon.test.template.testclasses.match.MatchForEach2; -import spoon.test.template.testclasses.match.MatchIfElse; -import spoon.test.template.testclasses.match.MatchMap; -import spoon.test.template.testclasses.match.MatchModifiers; -import spoon.test.template.testclasses.match.MatchMultiple; -import spoon.test.template.testclasses.match.MatchMultiple2; -import spoon.test.template.testclasses.match.MatchMultiple3; -import spoon.test.template.testclasses.match.MatchThrowables; -import spoon.test.template.testclasses.match.MatchWithParameterCondition; -import spoon.test.template.testclasses.match.MatchWithParameterType; -import spoon.testing.utils.ModelUtils; - -public class TemplateMatcherTest { - - @Test - public void testMatchForeach() throws Exception { - //contract: inline foreach template can match multiple models into list of parameter values - CtType ctClass = ModelUtils.buildClass(MatchForEach.class); - - Pattern pattern = MatchForEach.createPattern(ctClass.getFactory()); - - List matches = pattern.getMatches(ctClass); - - assertEquals(2, matches.size()); - { - Match match = matches.get(0); - assertEquals(Arrays.asList("java.lang.System.out.println(value)"), listToListOfStrings(match.getMatchingElements())); - //FIX IT -// assertEquals(Arrays.asList(""), listToListOfStrings((List) match.getParameters().getValue("values"))); - } - { - Match match = matches.get(1); - assertEquals(Arrays.asList( - "java.lang.System.out.println(\"a\")", - "java.lang.System.out.println(\"Xxxx\")", - "java.lang.System.out.println(((java.lang.String) (null)))", - "java.lang.System.out.println(java.lang.Long.class.toString())"), listToListOfStrings(match.getMatchingElements())); - assertEquals(Arrays.asList( - "\"a\"", - "\"Xxxx\"", - "((java.lang.String) (null))", - "java.lang.Long.class.toString()"), listToListOfStrings((List) match.getParameters().getValue("values"))); - } - } - - @Test - public void testMatchForeachWithOuterSubstitution() throws Exception { - //contract: inline foreach template can match multiple models into list of parameter values including outer parameters - CtType ctClass = ModelUtils.buildClass(MatchForEach2.class); - - Pattern pattern = MatchForEach2.createPattern(ctClass.getFactory()); - - List matches = pattern.getMatches(ctClass); - - assertEquals(3, matches.size()); - { - Match match = matches.get(0); - assertEquals(Arrays.asList("int var = 0"), listToListOfStrings(match.getMatchingElements())); - //FIX IT -// assertEquals(Arrays.asList(""), listToListOfStrings((List) match.getParameters().getValue("values"))); - } - { - Match match = matches.get(1); - assertEquals(Arrays.asList( - "int cc = 0", - "java.lang.System.out.println(\"Xxxx\")", - "cc++", - "java.lang.System.out.println(((java.lang.String) (null)))", - "cc++"), listToListOfStrings(match.getMatchingElements())); - assertEquals(Arrays.asList( - "\"Xxxx\"", - "((java.lang.String) (null))"), listToListOfStrings((List) match.getParameters().getValue("values"))); - } - { - Match match = matches.get(2); - assertEquals(Arrays.asList( - "int dd = 0", - "java.lang.System.out.println(java.lang.Long.class.toString())", - "dd++"), listToListOfStrings(match.getMatchingElements())); - assertEquals(Arrays.asList( - "java.lang.Long.class.toString()"), listToListOfStrings((List) match.getParameters().getValue("values"))); - } - } - - @Test - public void testMatchIfElse() throws Exception { - //contract: inline switch Pattern can match one of the models - CtType ctClass = ModelUtils.buildClass(MatchIfElse.class); - - Pattern pattern = MatchIfElse.createPattern(ctClass.getFactory()); - - List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0)); - - assertEquals(7, matches.size()); - { - Match match = matches.get(0); - assertEquals(Arrays.asList("java.lang.System.out.println(i)"), listToListOfStrings(match.getMatchingElements())); - assertEquals(false, match.getParameters().getValue("option")); - assertEquals(true, match.getParameters().getValue("option2")); - assertEquals("i", match.getParameters().getValue("value").toString()); - } - { - Match match = matches.get(1); - assertEquals(Arrays.asList("java.lang.System.out.println(\"a\")"), listToListOfStrings(match.getMatchingElements())); - assertEquals(true, match.getParameters().getValue("option")); - assertEquals(false, match.getParameters().getValue("option2")); - assertEquals("\"a\"", match.getParameters().getValue("value").toString()); - } - { - Match match = matches.get(2); - assertEquals(Arrays.asList("java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings(match.getMatchingElements())); - assertEquals(true, match.getParameters().getValue("option")); - assertEquals(false, match.getParameters().getValue("option2")); - assertEquals("\"Xxxx\"", match.getParameters().getValue("value").toString()); - } - { - Match match = matches.get(3); - assertEquals(Arrays.asList("java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings(match.getMatchingElements())); - assertEquals(true, match.getParameters().getValue("option")); - assertEquals(false, match.getParameters().getValue("option2")); - assertEquals("((java.lang.String) (null))", match.getParameters().getValue("value").toString()); - } - { - Match match = matches.get(4); - assertEquals(Arrays.asList("java.lang.System.out.println(2018)"), listToListOfStrings(match.getMatchingElements())); - assertEquals(false, match.getParameters().getValue("option")); - assertEquals(true, match.getParameters().getValue("option2")); - assertEquals("2018", match.getParameters().getValue("value").toString()); - } - { - Match match = matches.get(5); - assertEquals(Arrays.asList("java.lang.System.out.println(java.lang.Long.class.toString())"), listToListOfStrings(match.getMatchingElements())); - assertEquals(true, match.getParameters().getValue("option")); - assertEquals(false, match.getParameters().getValue("option2")); - assertEquals("java.lang.Long.class.toString()", match.getParameters().getValue("value").toString()); - } - { - Match match = matches.get(6); - assertEquals(Arrays.asList("java.lang.System.out.println(3.14)"), listToListOfStrings(match.getMatchingElements())); - assertEquals(false, match.getParameters().getValue("option")); - assertEquals(false, match.getParameters().getValue("option2")); - assertEquals("3.14", match.getParameters().getValue("value").toString()); - } - } - @Test - public void testGenerateMultiValues() throws Exception { - CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); - Pattern pattern = MatchMultiple.createPattern(ctClass.getFactory(), null, null, null); - Map params = new HashMap<>(); - params.put("printedValue", "does it work?"); - params.put("statements", ctClass.getMethodsByName("testMatch1").get(0).getBody().getStatements().subList(0, 3)); - List generated = pattern.substituteList(ctClass.getFactory(), CtStatement.class, params); - assertEquals(Arrays.asList( - "int i = 0", - "i++", - "java.lang.System.out.println(i)", - "java.lang.System.out.println(\"does it work?\")"), generated.stream().map(Object::toString).collect(Collectors.toList())); - } - @Test - public void testMatchGreedyMultiValueUnlimited() throws Exception { - //contract: multivalue parameter can match multiple nodes into list of parameter values. - //contract: default greedy matching eats everything but can leave some matches if it is needed to match remaining template parameters - CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); - Pattern pattern = MatchMultiple.createPattern(ctClass.getFactory(), null, null, null); - - List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0)); - - assertEquals(1, matches.size()); - Match match = matches.get(0); - //check all statements are matched - assertEquals(Arrays.asList( - "int i = 0", - "i++", - "java.lang.System.out.println(i)", - "java.lang.System.out.println(\"Xxxx\")", - "java.lang.System.out.println(((java.lang.String) (null)))", - "java.lang.System.out.println(\"last one\")"), listToListOfStrings(match.getMatchingElements())); - - //check all statements excluding last are stored as value of "statements" parameter - assertEquals(Arrays.asList( - "int i = 0", - "i++", - "java.lang.System.out.println(i)", - "java.lang.System.out.println(\"Xxxx\")", - "java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings((List) match.getParameters().getValue("statements"))); - //last statement is matched by last template, which saves printed value - assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); - assertEquals("\"last one\"", match.getParameters().getValue("printedValue").toString()); - } - - @Test - public void testMatchGreedyMultiValueMaxCountLimit() throws Exception { - //contract: default greedy matching eats everything until max count = 3 - CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); - Pattern pattern = MatchMultiple.createPattern(ctClass.getFactory(), null, null, 3); - - List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0)); - - assertEquals(2, matches.size()); - { - Match match = matches.get(0); - //check 3 + 1 statements are matched - assertEquals(Arrays.asList( - "int i = 0", - "i++", - "java.lang.System.out.println(i)", - "java.lang.System.out.println(\"Xxxx\")" - ), listToListOfStrings(match.getMatchingElements())); - - //check 3 statements are stored as value of "statements" parameter - assertEquals(Arrays.asList( - "int i = 0", - "i++", - "java.lang.System.out.println(i)"), listToListOfStrings((List) match.getParameters().getValue("statements"))); - //4th statement is matched by last template, which saves printed value - assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); - assertEquals("\"Xxxx\"", match.getParameters().getValue("printedValue").toString()); - } - { - Match match = matches.get(1); - //check remaining next 2 statements are matched - assertEquals(Arrays.asList( - "java.lang.System.out.println(((java.lang.String) (null)))", - "java.lang.System.out.println(\"last one\")"), listToListOfStrings(match.getMatchingElements())); - - //check all statements excluding last are stored as value of "statements" parameter - assertEquals(Arrays.asList( - "java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings((List) match.getParameters().getValue("statements"))); - //last statement is matched by last template, which saves printed value - assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); - assertEquals("\"last one\"", match.getParameters().getValue("printedValue").toString()); - } - } - - - @Test - public void testMatchReluctantMultivalue() throws Exception { - //contract: multivalue parameter can match multiple nodes into list of parameter values. - //contract: reluctant matches only minimal amount - CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); - - Pattern pattern = MatchMultiple.createPattern(ctClass.getFactory(), Quantifier.RELUCTANT, null, null); - - List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0)); - - assertEquals(3, matches.size()); - { - Match match = matches.get(0); - //check all statements are matched - assertEquals(Arrays.asList( - "int i = 0", - "i++", - "java.lang.System.out.println(i)", //this is println(int), but last temple matches println(String) - it is question if it is wanted or not ... - "java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings(match.getMatchingElements())); - - //check all statements excluding last are stored as value of "statements" parameter - assertEquals(Arrays.asList( - "int i = 0", - "i++", - "java.lang.System.out.println(i)"), listToListOfStrings((List) match.getParameters().getValue("statements"))); - //last statement is matched by last template, which saves printed value - assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); - assertEquals("\"Xxxx\"", match.getParameters().getValue("printedValue").toString()); - } - { - Match match = matches.get(1); - //check all statements are matched - assertEquals(Arrays.asList( - "java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings(match.getMatchingElements())); - - //check all statements excluding last are stored as value of "statements" parameter - assertEquals(Arrays.asList(), listToListOfStrings((List) match.getParameters().getValue("statements"))); - //last statement is matched by last template, which saves printed value - assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); - assertEquals("((java.lang.String) (null))", match.getParameters().getValue("printedValue").toString()); - } - { - Match match = matches.get(2); - //check all statements are matched - assertEquals(Arrays.asList( - "java.lang.System.out.println(\"last one\")"), listToListOfStrings(match.getMatchingElements())); - - //check all statements excluding last are stored as value of "statements" parameter - assertEquals(Arrays.asList(), listToListOfStrings((List) match.getParameters().getValue("statements"))); - //last statement is matched by last template, which saves printed value - assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); - assertEquals("\"last one\"", match.getParameters().getValue("printedValue").toString()); - } - } - @Test - public void testMatchReluctantMultivalueMinCount1() throws Exception { - //contract: multivalue parameter can match multiple nodes into list of parameter values. - //contract: reluctant matches only at least 1 node in this case - CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); - - Pattern pattern = MatchMultiple.createPattern(ctClass.getFactory(), Quantifier.RELUCTANT, 1, null); - - List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0)); - - assertEquals(2, matches.size()); - { - Match match = matches.get(0); - //check all statements are matched - assertEquals(Arrays.asList( - "int i = 0", - "i++", - "java.lang.System.out.println(i)", //this is println(int), but last temple matches println(String) - it is question if it is wanted or not ... - "java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings(match.getMatchingElements())); - - //check all statements excluding last are stored as value of "statements" parameter - assertEquals(Arrays.asList( - "int i = 0", - "i++", - "java.lang.System.out.println(i)"), listToListOfStrings((List) match.getParameters().getValue("statements"))); - //last statement is matched by last template, which saves printed value - assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); - assertEquals("\"Xxxx\"", match.getParameters().getValue("printedValue").toString()); - } - { - Match match = matches.get(1); - //check all statements are matched - assertEquals(Arrays.asList( - "java.lang.System.out.println(((java.lang.String) (null)))", - "java.lang.System.out.println(\"last one\")"), listToListOfStrings(match.getMatchingElements())); - - //check all statements excluding last are stored as value of "statements" parameter - assertEquals(Arrays.asList( - "java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings((List) match.getParameters().getValue("statements"))); - //last statement is matched by last template, which saves printed value - assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); - assertEquals("\"last one\"", match.getParameters().getValue("printedValue").toString()); - } - } - @Test - public void testMatchReluctantMultivalueExactly2() throws Exception { - //contract: multivalue parameter can match multiple nodes into list of parameter values. - //contract: reluctant matches min 2 and max 2 nodes in this case - CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); - - Pattern pattern = MatchMultiple.createPattern(ctClass.getFactory(), Quantifier.RELUCTANT, 2, 2); - - List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0)); - - assertEquals(1, matches.size()); - { - Match match = matches.get(0); - //check only 2 statements are matched + next one - assertEquals(Arrays.asList( - "i++", - "java.lang.System.out.println(i)", //this is println(int), but last temple matches println(String) - it is question if it is wanted or not ... - "java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings(match.getMatchingElements())); - - //check 2 statements excluding last are stored as value of "statements" parameter - assertEquals(Arrays.asList( - "i++", - "java.lang.System.out.println(i)"), listToListOfStrings((List) match.getParameters().getValue("statements"))); - //last statement is matched by last template, which saves printed value - assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); - assertEquals("\"Xxxx\"", match.getParameters().getValue("printedValue").toString()); - } - } - - @Test - public void testMatchPossesiveMultiValueUnlimited() throws Exception { - //contract: multivalue parameter can match multiple nodes into list of parameter values. - //contract: possessive matching eats everything and never returns back - CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); - Pattern pattern = MatchMultiple.createPattern(ctClass.getFactory(), Quantifier.POSSESSIVE, null, null); - - List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0).getBody()); - //the last template has nothing to match -> no match - assertEquals(0, matches.size()); - } - @Test - public void testMatchPossesiveMultiValueMaxCount4() throws Exception { - //contract: multivalue parameter can match multiple nodes into list of parameter values. - //contract: possessive matching eats everything and never returns back - CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); - Pattern pattern = MatchMultiple.createPattern(ctClass.getFactory(), Quantifier.POSSESSIVE, null, 4); - - List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0)); - - assertEquals(1, matches.size()); - Match match = matches.get(0); - //check 4 statements are matched + last template - assertEquals(Arrays.asList( - "int i = 0", - "i++", - "java.lang.System.out.println(i)", - "java.lang.System.out.println(\"Xxxx\")", - "java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings(match.getMatchingElements())); - - //check 4 statements excluding last are stored as value of "statements" parameter - assertEquals(Arrays.asList( - "int i = 0", - "i++", - "java.lang.System.out.println(i)", - "java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings((List) match.getParameters().getValue("statements"))); - //last statement is matched by last template, which saves printed value - assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); - assertEquals("((java.lang.String) (null))", match.getParameters().getValue("printedValue").toString()); - } - @Test - public void testMatchPossesiveMultiValueMinCount() throws Exception { - //contract: check possessive matching with min count limit and GREEDY back off - CtType ctClass = ModelUtils.buildClass(MatchMultiple3.class); - for (int i = 0; i < 7; i++) { - final int count = i; - Pattern pattern = MatchMultiple3.createPattern(ctClass.getFactory(), pb -> { - pb.parameter("statements1").setMatchingStrategy(Quantifier.GREEDY); - pb.parameter("statements2").setMatchingStrategy(Quantifier.POSSESSIVE).setMinOccurence(count).setMaxOccurence(count); - }); - - List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0).getBody()); - if (count < 6) { - //the last template has nothing to match -> no match - assertEquals("count="+count, 1, matches.size()); - assertEquals("count="+count, 5-count, getSize(matches.get(0).getParameters().getValue("statements1"))); - assertEquals("count="+count, count, getSize(matches.get(0).getParameters().getValue("statements2"))); - } else { - //the possessive matcher eat too much. There is no target element for last `printedValue` variable - assertEquals("count="+count, 0, matches.size()); - } - } - } - - @Test - public void testMatchPossesiveMultiValueMinCount2() throws Exception { - //contract: check possessive matching with min count limit and GREEDY back off - CtType ctClass = ModelUtils.buildClass(MatchMultiple2.class); - for (int i = 0; i < 7; i++) { - final int count = i; - Pattern pattern = MatchMultiple2.createPattern(ctClass.getFactory(), pb -> { - pb.parameter("statements1").setMatchingStrategy(Quantifier.GREEDY); - pb.parameter("statements2").setMatchingStrategy(Quantifier.POSSESSIVE).setMinOccurence(count).setMaxOccurence(count); - pb.parameter("printedValue").setMatchingStrategy(Quantifier.POSSESSIVE).setContainerKind(ContainerKind.LIST).setMinOccurence(2); - }); - - List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0).getBody()); - if (count < 5) { - //the last template has nothing to match -> no match - assertEquals("count="+count, 1, matches.size()); - assertEquals("count="+count, 4-count, getSize(matches.get(0).getParameters().getValue("statements1"))); - assertEquals("count="+count, count, getSize(matches.get(0).getParameters().getValue("statements2"))); - assertEquals("count="+count, 2, getSize(matches.get(0).getParameters().getValue("printedValue"))); - } else { - //the possessive matcher eat too much. There is no target element for last `printedValue` variable - assertEquals("count="+count, 0, matches.size()); - } - } - } - @Test - public void testMatchGreedyMultiValueMinCount2() throws Exception { - //contract: check possessive matching with min count limit and GREEDY back off - CtType ctClass = ModelUtils.buildClass(MatchMultiple2.class); - for (int i = 0; i < 7; i++) { - final int count = i; - Pattern pattern = MatchMultiple2.createPattern(ctClass.getFactory(), pb -> { - pb.parameter("statements1").setMatchingStrategy(Quantifier.RELUCTANT); - pb.parameter("statements2").setMatchingStrategy(Quantifier.GREEDY).setMaxOccurence(count); - pb.parameter("printedValue").setMatchingStrategy(Quantifier.GREEDY).setContainerKind(ContainerKind.LIST).setMinOccurence(2); - }); - - List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0).getBody()); - if (count < 7) { - //the last template has nothing to match -> no match - assertEquals("count="+count, 1, matches.size()); - assertEquals("count="+count, Math.max(0, 3-count), getSize(matches.get(0).getParameters().getValue("statements1"))); - assertEquals("count="+count, count - Math.max(0, count-4), getSize(matches.get(0).getParameters().getValue("statements2"))); - assertEquals("count="+count, Math.max(2, 3 - Math.max(0, count-3)), getSize(matches.get(0).getParameters().getValue("printedValue"))); - } else { - //the possessive matcher eat too much. There is no target element for last `printedValue` variable - assertEquals("count="+count, 0, matches.size()); - } - } - } - - private int getSize(Object o) { - if (o instanceof List) { - return ((List) o).size(); - } - if (o == null) { - return 0; - } - fail("Unexpected object of type " + o.getClass()); - return -1; - } - - @Test - public void testMatchParameterValue() throws Exception { - //contract: by default the parameter value is the reference to real node from the model - CtType ctClass = ModelUtils.buildClass(MatchWithParameterType.class); - - Pattern pattern = MatchWithParameterType.createPattern(ctClass.getFactory(), null); - - List matches = pattern.getMatches(ctClass); - - assertEquals(5, matches.size()); - { - Match match = matches.get(0); - assertEquals(Arrays.asList("java.lang.System.out.println(value)"), listToListOfStrings(match.getMatchingElements())); - Object value = match.getParameters().getValue("value"); - assertTrue(value instanceof CtVariableRead); - assertEquals("value", value.toString()); - //contract: the value is reference to found node (not a clone) - assertTrue(((CtElement)value).isParentInitialized()); - assertSame(CtRole.ARGUMENT, ((CtElement)value).getRoleInParent()); - } - { - Match match = matches.get(1); - assertEquals(Arrays.asList("java.lang.System.out.println(\"a\")"), listToListOfStrings(match.getMatchingElements())); - assertTrue(match.getParameters().getValue("value") instanceof CtLiteral); - assertEquals("\"a\"", match.getParameters().getValue("value").toString()); - } - { - Match match = matches.get(2); - assertEquals(Arrays.asList("java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings(match.getMatchingElements())); - assertTrue(match.getParameters().getValue("value") instanceof CtLiteral); - assertEquals("\"Xxxx\"", match.getParameters().getValue("value").toString()); - } - { - Match match = matches.get(3); - assertEquals(Arrays.asList("java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings(match.getMatchingElements())); - assertTrue(match.getParameters().getValue("value") instanceof CtLiteral); - assertEquals("((java.lang.String) (null))", match.getParameters().getValue("value").toString()); - } - { - Match match = matches.get(4); - assertEquals(Arrays.asList("java.lang.System.out.println(java.lang.Long.class.toString())"), listToListOfStrings(match.getMatchingElements())); - assertTrue(match.getParameters().getValue("value") instanceof CtInvocation); - assertEquals("java.lang.Long.class.toString()", match.getParameters().getValue("value").toString()); - } - } - - @Test - public void testMatchParameterValueType() throws Exception { - //contract: the parameter value type matches only values of required type - CtType ctClass = ModelUtils.buildClass(MatchWithParameterType.class); - { - Pattern pattern = MatchWithParameterType.createPattern(ctClass.getFactory(), CtLiteral.class); - - List matches = pattern.getMatches(ctClass); - - assertEquals(3, matches.size()); - { - Match match = matches.get(0); - assertEquals(Arrays.asList("java.lang.System.out.println(\"a\")"), listToListOfStrings(match.getMatchingElements())); - assertTrue(match.getParameters().getValue("value") instanceof CtLiteral); - assertEquals("\"a\"", match.getParameters().getValue("value").toString()); - } - { - Match match = matches.get(1); - assertEquals(Arrays.asList("java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings(match.getMatchingElements())); - assertTrue(match.getParameters().getValue("value") instanceof CtLiteral); - assertEquals("\"Xxxx\"", match.getParameters().getValue("value").toString()); - } - { - Match match = matches.get(2); - assertEquals(Arrays.asList("java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings(match.getMatchingElements())); - assertTrue(match.getParameters().getValue("value") instanceof CtLiteral); - assertEquals("((java.lang.String) (null))", match.getParameters().getValue("value").toString()); - } - } - { - Pattern pattern = MatchWithParameterType.createPattern(ctClass.getFactory(), CtInvocation.class); - - List matches = pattern.getMatches(ctClass); - - assertEquals(1, matches.size()); - { - Match match = matches.get(0); - assertEquals(Arrays.asList("java.lang.System.out.println(java.lang.Long.class.toString())"), listToListOfStrings(match.getMatchingElements())); - assertTrue(match.getParameters().getValue("value") instanceof CtInvocation); - assertEquals("java.lang.Long.class.toString()", match.getParameters().getValue("value").toString()); - } - - } - } - - @Test - public void testMatchParameterCondition() throws Exception { - //contract: the parameter value matching condition causes that only matching parameter values are accepted - //if the value isn't matching then node is not matched - CtType ctClass = ModelUtils.buildClass(MatchWithParameterCondition.class); - { - Pattern pattern = MatchWithParameterCondition.createPattern(ctClass.getFactory(), (Object value) -> value instanceof CtLiteral); - - List matches = pattern.getMatches(ctClass); - - assertEquals(3, matches.size()); - { - Match match = matches.get(0); - assertEquals(Arrays.asList("java.lang.System.out.println(\"a\")"), listToListOfStrings(match.getMatchingElements())); - assertTrue(match.getParameters().getValue("value") instanceof CtLiteral); - assertEquals("\"a\"", match.getParameters().getValue("value").toString()); - } - { - Match match = matches.get(1); - assertEquals(Arrays.asList("java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings(match.getMatchingElements())); - assertTrue(match.getParameters().getValue("value") instanceof CtLiteral); - assertEquals("\"Xxxx\"", match.getParameters().getValue("value").toString()); - } - { - Match match = matches.get(2); - assertEquals(Arrays.asList("java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings(match.getMatchingElements())); - assertTrue(match.getParameters().getValue("value") instanceof CtLiteral); - assertEquals("((java.lang.String) (null))", match.getParameters().getValue("value").toString()); - } - } - } - - @Test - public void testMatchOfAttribute() throws Exception { - //contract: match some nodes like template, but with some variable attributes - CtType ctClass = ModelUtils.buildClass(MatchModifiers.class); - { - //match all methods with arbitrary name, modifiers, parameters, but with empty body and return type void - Pattern pattern = MatchModifiers.createPattern(ctClass.getFactory(), false); - List matches = pattern.getMatches(ctClass); - assertEquals(3, matches.size()); - { - Match match = matches.get(0); - assertEquals(1, match.getMatchingElements().size()); - assertEquals("matcher1", match.getMatchingElement(CtMethod.class).getSimpleName()); - assertEquals(3, match.getParametersMap().size()); - assertEquals("matcher1", match.getParametersMap().get("methodName")); - assertEquals(new HashSet<>(Arrays.asList(ModifierKind.PUBLIC)), match.getParametersMap().get("modifiers")); - assertEquals(Arrays.asList(), match.getParametersMap().get("parameters")); - } - { - Match match = matches.get(1); - assertEquals(1, match.getMatchingElements().size()); - assertEquals("publicStaticMethod", match.getMatchingElement(CtMethod.class).getSimpleName()); - assertEquals(3, match.getParametersMap().size()); - assertEquals("publicStaticMethod", match.getParametersMap().get("methodName")); - assertEquals(new HashSet<>(Arrays.asList(ModifierKind.PUBLIC, ModifierKind.STATIC)), match.getParametersMap().get("modifiers")); - assertEquals(Arrays.asList(), match.getParametersMap().get("parameters")); - } - { - Match match = matches.get(2); - assertEquals(1, match.getMatchingElements().size()); - assertEquals("packageProtectedMethodWithParam", match.getMatchingElement(CtMethod.class).getSimpleName()); - assertEquals(3, match.getParametersMap().size()); - assertEquals("packageProtectedMethodWithParam", match.getParametersMap().get("methodName")); - assertEquals(new HashSet<>(), match.getParametersMap().get("modifiers")); - assertEquals(2, ((List) match.getParametersMap().get("parameters")).size()); - } - } - { - //match all methods with arbitrary name, modifiers, parameters and body, but with return type void - Pattern pattern = MatchModifiers.createPattern(ctClass.getFactory(), true); - List matches = pattern.getMatches(ctClass); - assertEquals(4, matches.size()); - { - Match match = matches.get(0); - assertEquals(1, match.getMatchingElements().size()); - assertEquals("matcher1", match.getMatchingElement(CtMethod.class).getSimpleName()); - assertEquals(4, match.getParametersMap().size()); - assertEquals("matcher1", match.getParametersMap().get("methodName")); - assertEquals(new HashSet<>(Arrays.asList(ModifierKind.PUBLIC)), match.getParametersMap().get("modifiers")); - assertEquals(Arrays.asList(), match.getParametersMap().get("parameters")); - assertEquals(Arrays.asList(), match.getParametersMap().get("statements")); - } - { - Match match = matches.get(1); - assertEquals(1, match.getMatchingElements().size()); - assertEquals("publicStaticMethod", match.getMatchingElement(CtMethod.class).getSimpleName()); - assertEquals(4, match.getParametersMap().size()); - assertEquals("publicStaticMethod", match.getParametersMap().get("methodName")); - assertEquals(new HashSet<>(Arrays.asList(ModifierKind.PUBLIC, ModifierKind.STATIC)), match.getParametersMap().get("modifiers")); - assertEquals(Arrays.asList(), match.getParametersMap().get("parameters")); - assertEquals(Arrays.asList(), match.getParametersMap().get("statements")); - } - { - Match match = matches.get(2); - assertEquals(1, match.getMatchingElements().size()); - assertEquals("packageProtectedMethodWithParam", match.getMatchingElement(CtMethod.class).getSimpleName()); - assertEquals(4, match.getParametersMap().size()); - assertEquals("packageProtectedMethodWithParam", match.getParametersMap().get("methodName")); - assertEquals(new HashSet<>(), match.getParametersMap().get("modifiers")); - assertEquals(2, ((List) match.getParametersMap().get("parameters")).size()); - assertEquals(Arrays.asList(), match.getParametersMap().get("statements")); - } - { - Match match = matches.get(3); - assertEquals(1, match.getMatchingElements().size()); - assertEquals("withBody", match.getMatchingElement(CtMethod.class).getSimpleName()); - assertEquals(4, match.getParametersMap().size()); - assertEquals("withBody", match.getParametersMap().get("methodName")); - assertEquals(new HashSet<>(Arrays.asList(ModifierKind.PRIVATE)), match.getParametersMap().get("modifiers")); - assertEquals(0, ((List) match.getParametersMap().get("parameters")).size()); - assertEquals(2, ((List) match.getParametersMap().get("statements")).size()); - assertEquals("this.getClass()", ((List) match.getParametersMap().get("statements")).get(0).toString()); - assertEquals("java.lang.System.out.println()", ((List) match.getParametersMap().get("statements")).get(1).toString()); - } - } - } - - @Test - public void testMatchOfMapAttribute() throws Exception { - //contract: match attribute of type Map - annotations - CtType ctClass = ModelUtils.buildClass(MatchMap.class); - { - //match all methods with arbitrary name, and Annotation Test modifiers, parameters, but with empty body and return type void - Pattern pattern = MatchMap.createPattern(ctClass.getFactory(), false); - List matches = pattern.getMatches(ctClass); -// List matches = pattern.getMatches(ctClass.getMethodsByName("matcher1").get(0)); - assertEquals(3, matches.size()); - { - Match match = matches.get(0); - assertEquals(1, match.getMatchingElements().size()); - assertEquals("matcher1", match.getMatchingElement(CtMethod.class).getSimpleName()); - assertEquals(2, match.getParametersMap().size()); - assertEquals("matcher1", match.getParametersMap().get("methodName")); - Map values = getMap(match, "CheckAnnotationValues"); - assertEquals(0, values.size()); - } - { - Match match = matches.get(1); - assertEquals(1, match.getMatchingElements().size()); - assertEquals("m1", match.getMatchingElement(CtMethod.class).getSimpleName()); - assertEquals(2, match.getParametersMap().size()); - assertEquals("m1", match.getParametersMap().get("methodName")); - Map values = getMap(match, "CheckAnnotationValues"); - assertEquals(1, values.size()); - assertEquals("\"xyz\"", values.get("value").toString()); - } - { - Match match = matches.get(2); - assertEquals(1, match.getMatchingElements().size()); - assertEquals("m2", match.getMatchingElement(CtMethod.class).getSimpleName()); - assertEquals(2, match.getParametersMap().size()); - assertEquals("m2", match.getParametersMap().get("methodName")); - Map values = getMap(match, "CheckAnnotationValues"); - assertEquals(2, values.size()); - assertEquals("\"abc\"", values.get("value").toString()); - assertEquals("123", values.get("timeout").toString()); - } - } - } - - private Map getMap(Match match, String name) { - Object v = match.getParametersMap().get(name); - assertNotNull(v); - return ((ParameterValueProvider) v).asMap(); - } - - @Test - public void testMatchOfMapAttributeAndOtherAnnotations() throws Exception { - //contract: match attribute of type Map - annotations - CtType ctClass = ModelUtils.buildClass(MatchMap.class); - { - //match all methods with arbitrary name, and Annotation Test modifiers, parameters, but with empty body and return type void - Pattern pattern = MatchMap.createPattern(ctClass.getFactory(), true); - List matches = pattern.getMatches(ctClass); - assertEquals(4, matches.size()); - { - Match match = matches.get(0); - assertEquals(1, match.getMatchingElements().size()); - assertEquals("matcher1", match.getMatchingElement(CtMethod.class).getSimpleName()); - assertEquals(2, match.getParametersMap().size()); - assertEquals("matcher1", match.getParametersMap().get("methodName")); - assertEquals(map(), getMap(match, "CheckAnnotationValues")); - } - { - Match match = matches.get(1); - assertEquals(1, match.getMatchingElements().size()); - assertEquals("m1", match.getMatchingElement(CtMethod.class).getSimpleName()); - assertEquals(2, match.getParametersMap().size()); - assertEquals("m1", match.getParametersMap().get("methodName")); - assertEquals("{value=\"xyz\"}", getMap(match, "CheckAnnotationValues").toString()); - } - { - Match match = matches.get(2); - assertEquals(1, match.getMatchingElements().size()); - assertEquals("m2", match.getMatchingElement(CtMethod.class).getSimpleName()); - assertEquals(2, match.getParametersMap().size()); - assertEquals("m2", match.getParametersMap().get("methodName")); - assertEquals("{value=\"abc\", timeout=123}", getMap(match, "CheckAnnotationValues").toString()); - } - { - Match match = matches.get(3); - assertEquals(1, match.getMatchingElements().size()); - assertEquals("deprecatedTestAnnotation2", match.getMatchingElement(CtMethod.class).getSimpleName()); - assertEquals(3, match.getParametersMap().size()); - assertEquals("deprecatedTestAnnotation2", match.getParametersMap().get("methodName")); - assertEquals("{timeout=4567}", getMap(match, "CheckAnnotationValues").toString()); - assertEquals("@java.lang.Deprecated", match.getParameters().getValue("allAnnotations").toString()); - } - } - } - - @Test - public void testMatchOfMapKeySubstring() throws Exception { - //contract: match substring in key of Map Entry - match key of annotation value - CtType ctClass = ModelUtils.buildClass(MatchMap.class); - { - //match all methods with arbitrary name, and Annotation Test modifiers, parameters, but with empty body and return type void - Pattern pattern = MatchMap.createMatchKeyPattern(ctClass.getFactory()); - List matches = pattern.getMatches(ctClass); - assertEquals(2, matches.size()); - { - Match match = matches.get(0); - assertEquals(1, match.getMatchingElements().size()); - assertEquals("m1", match.getMatchingElement(CtMethod.class).getSimpleName()); - assertEquals(3, match.getParametersMap().size()); - assertEquals("m1", match.getParametersMap().get("methodName")); - assertEquals("value", match.getParameters().getValue("CheckKey").toString()); - assertEquals("\"xyz\"", match.getParameters().getValue("CheckValue").toString()); - } - { - Match match = matches.get(1); - assertEquals(1, match.getMatchingElements().size()); - assertEquals("deprecatedTestAnnotation2", match.getMatchingElement(CtMethod.class).getSimpleName()); - assertEquals(4, match.getParametersMap().size()); - assertEquals("deprecatedTestAnnotation2", match.getParametersMap().get("methodName")); - assertEquals("timeout", match.getParameters().getValue("CheckKey").toString()); - assertEquals("4567", match.getParameters().getValue("CheckValue").toString()); - assertEquals("@java.lang.Deprecated", match.getParameters().getValue("allAnnotations").toString()); - } - } - } - - @Test - public void testMatchInSet() throws Exception { - //contract: match elements in container of type Set - e.g method throwables - CtType ctClass = ModelUtils.buildClass(MatchThrowables.class); - Factory f = ctClass.getFactory(); - Pattern pattern = PatternBuilder.create(new TemplateModelBuilder(ctClass).setTypeMember("matcher1").getTemplateModels()) - .configureParameters(pb -> { - pb.parameter("otherThrowables") - //add matcher for other arbitrary throwables - .setConflictResolutionMode(ConflictResolutionMode.APPEND) - .setContainerKind(ContainerKind.SET) - .setMinOccurence(0) - .attributeOfElementByFilter(CtRole.THROWN, new TypeFilter(CtMethod.class)); - }) - .configureParameters(pb -> { - //define other parameters too to match all kinds of methods - pb.parameter("modifiers").attributeOfElementByFilter(CtRole.MODIFIER, new TypeFilter(CtMethod.class)); - pb.parameter("methodName").byString("matcher1"); - pb.parameter("parameters").attributeOfElementByFilter(CtRole.PARAMETER, new TypeFilter(CtMethod.class)); - pb.parameter("statements").attributeOfElementByFilter(CtRole.STATEMENT, new TypeFilter(CtBlock.class)); - }) - .build(); - String str = pattern.toString(); - List matches = pattern.getMatches(ctClass); - assertEquals(4, matches.size()); - { - Match match = matches.get(0); - assertEquals(1, match.getMatchingElements().size()); - assertEquals("matcher1", match.getMatchingElement(CtMethod.class).getSimpleName()); - assertEquals(new HashSet(Arrays.asList( - "modifiers","methodName","parameters","statements")), match.getParametersMap().keySet()); - } - { - Match match = matches.get(1); - assertEquals(1, match.getMatchingElements().size()); - assertEquals("sample2", match.getMatchingElement(CtMethod.class).getSimpleName()); - assertEquals(new HashSet(Arrays.asList( - "otherThrowables", "modifiers","methodName","parameters","statements")), match.getParametersMap().keySet()); - assertEquals(new HashSet(Arrays.asList( - "java.lang.UnsupportedOperationException", - "java.lang.IllegalArgumentException")), - ((Set>) match.getParameters().getValue("otherThrowables")) - .stream().map(e->e.toString()).collect(Collectors.toSet())); - } - { - Match match = matches.get(2); - assertEquals(1, match.getMatchingElements().size()); - assertEquals("sample3", match.getMatchingElement(CtMethod.class).getSimpleName()); - assertEquals(new HashSet(Arrays.asList( - "otherThrowables", "modifiers","methodName","parameters","statements")), match.getParametersMap().keySet()); - assertEquals(new HashSet(Arrays.asList( - "java.lang.IllegalArgumentException")), - ((Set>) match.getParameters().getValue("otherThrowables")) - .stream().map(e->e.toString()).collect(Collectors.toSet())); - } - { - Match match = matches.get(3); - assertEquals(1, match.getMatchingElements().size()); - assertEquals("sample4", match.getMatchingElement(CtMethod.class).getSimpleName()); - assertEquals(new HashSet(Arrays.asList( - "modifiers","methodName","parameters","statements")), match.getParametersMap().keySet()); - } - } - - private List listToListOfStrings(List list) { - if (list == null) { - return Collections.emptyList(); - } - List strings = new ArrayList<>(list.size()); - for (Object obj : list) { - strings.add(obj == null ? "null" : obj.toString()); - } - return strings; - } - - private MapBuilder map() { - return new MapBuilder(); - } - - class MapBuilder extends LinkedHashMap { - public MapBuilder put(String key, Object value) { - super.put(key, value); - return this; - } - } -} diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchForEach.java b/src/test/java/spoon/test/template/testclasses/match/MatchForEach.java index 94bb8005958..7fe96085eb6 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchForEach.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchForEach.java @@ -14,17 +14,6 @@ public class MatchForEach { - public static Pattern createPattern(Factory factory) { - CtType type = factory.Type().get(MatchForEach.class); - - return PatternBuilder.create(new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) - .configureParameters(pb -> { - pb.parameter("values").byVariable("values").setContainerKind(ContainerKind.LIST); - }) - .configureInlineStatements(lsb -> lsb.byVariableName("values")) - .build(); - } - public void matcher1(List values) { for (String value : values) { System.out.println(value); diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchMap.java b/src/test/java/spoon/test/template/testclasses/match/MatchMap.java index f4faa8abdd1..6cd30bceb91 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchMap.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchMap.java @@ -15,40 +15,6 @@ public class MatchMap { - public static Pattern createPattern(Factory factory, boolean acceptOtherAnnotations) { - CtType type = factory.Type().get(MatchMap.class); - return PatternBuilder.create(new TemplateModelBuilder(type).setTypeMember("matcher1").getTemplateModels()) - .configureParameters(pb -> { - //match any value of @Check annotation to parameter `testAnnotations` - pb.parameter("CheckAnnotationValues").attributeOfElementByFilter(CtRole.VALUE, new TypeFilter(CtAnnotation.class)).setContainerKind(ContainerKind.MAP); - //match any method name - pb.parameter("methodName").byString("matcher1"); - if (acceptOtherAnnotations) { - //match on all annotations of method - pb.parameter("allAnnotations") - .setConflictResolutionMode(ConflictResolutionMode.APPEND) - .attributeOfElementByFilter(CtRole.ANNOTATION, new TypeFilter<>(CtMethod.class)); - } - }) - .build(); - } - public static Pattern createMatchKeyPattern(Factory factory) { - CtType type = factory.Type().get(MatchMap.class); - return PatternBuilder.create(new TemplateModelBuilder(type).setTypeMember("m1").getTemplateModels()) - .configureParameters(pb -> { - //match any value of @Check annotation to parameter `testAnnotations` - pb.parameter("CheckKey").bySubstring("value"); - pb.parameter("CheckValue").byFilter((CtLiteral lit) -> true); - //match any method name - pb.parameter("methodName").byString("m1"); - //match on all annotations of method - pb.parameter("allAnnotations") - .setConflictResolutionMode(ConflictResolutionMode.APPEND) - .attributeOfElementByFilter(CtRole.ANNOTATION, new TypeFilter<>(CtMethod.class)); - }) - .build(); - } - @Check() void matcher1() { } diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchModifiers.java b/src/test/java/spoon/test/template/testclasses/match/MatchModifiers.java index 2e260018f31..c7b5de4f3c2 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchModifiers.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchModifiers.java @@ -16,11 +16,11 @@ public static Pattern createPattern(Factory factory, boolean matchBody) { CtType type = factory.Type().get(MatchModifiers.class); return PatternBuilder.create(new TemplateModelBuilder(type).setTypeMember("matcher1").getTemplateModels()) .configureParameters(pb -> { - pb.parameter("modifiers").attributeOfElementByFilter(CtRole.MODIFIER, new TypeFilter(CtMethod.class)); + pb.parameter("modifiers").byRole(new TypeFilter(CtMethod.class), CtRole.MODIFIER); pb.parameter("methodName").byString("matcher1"); - pb.parameter("parameters").attributeOfElementByFilter(CtRole.PARAMETER, new TypeFilter(CtMethod.class)); + pb.parameter("parameters").byRole(new TypeFilter(CtMethod.class), CtRole.PARAMETER); if (matchBody) { - pb.parameter("statements").attributeOfElementByFilter(CtRole.STATEMENT, new TypeFilter(CtBlock.class)); + pb.parameter("statements").byRole(new TypeFilter(CtBlock.class), CtRole.STATEMENT); } }) .build(); From 8d609bdce42419e6035b72ead19e2472ce3f1790 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Sat, 31 Mar 2018 15:59:11 +0200 Subject: [PATCH 058/131] little documentation changes --- doc/pattern.md | 2 +- src/main/java/spoon/pattern/node/RootNode.java | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/pattern.md b/doc/pattern.md index 246b3b7f791..3f5e766a161 100644 --- a/doc/pattern.md +++ b/doc/pattern.md @@ -46,7 +46,7 @@ Main class: `PatternBuilder` -------------------------------------------------- To create a Spoon pattern, one must use `PatternBuilder`, which takes AST nodes as input, and where you -**pattern parameters** are defined by calling PatternBuilder fluen API methods. +**pattern parameters** are defined by calling PatternBuilder fluent API methods. The method `statement` below defines a Spoon pattern. diff --git a/src/main/java/spoon/pattern/node/RootNode.java b/src/main/java/spoon/pattern/node/RootNode.java index fd0abff490a..89fae889aac 100644 --- a/src/main/java/spoon/pattern/node/RootNode.java +++ b/src/main/java/spoon/pattern/node/RootNode.java @@ -91,6 +91,8 @@ default TobeMatched matchAllWith(TobeMatched targets) { } /** + * Call it to modify Pattern structure. It is actually called mainly by PatternBuilder. + * TODO: May be we can move this method into some internal interface? * @param oldNode old {@link RootNode} * @param newNode new {@link RootNode} * @return a true if `oldNode` was found in this {@link RootNode} or it's children and replaced by `newNode` From b248ecf90757e67c4d7384e60fcc37f143c27ec2 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Tue, 3 Apr 2018 21:48:23 +0200 Subject: [PATCH 059/131] feature: PatternBuilder#matchInlinedStatements --- .../pattern/InlineStatementsBuilder.java | 40 +++++++++++-------- .../java/spoon/pattern/ParametersBuilder.java | 18 ++++++++- .../java/spoon/pattern/PatternBuilder.java | 8 +--- .../java/spoon/test/template/PatternTest.java | 3 +- .../testclasses/match/MatchForEach2.java | 3 +- .../testclasses/match/MatchIfElse.java | 2 + .../testclasses/match/MatchMultiple2.java | 5 +-- .../match/MatchWithParameterCondition.java | 1 - .../match/MatchWithParameterType.java | 1 - 9 files changed, 47 insertions(+), 34 deletions(-) diff --git a/src/main/java/spoon/pattern/InlineStatementsBuilder.java b/src/main/java/spoon/pattern/InlineStatementsBuilder.java index e72e8e46fcc..6c97d97343d 100644 --- a/src/main/java/spoon/pattern/InlineStatementsBuilder.java +++ b/src/main/java/spoon/pattern/InlineStatementsBuilder.java @@ -37,7 +37,6 @@ import spoon.reflect.path.CtRole; import spoon.reflect.reference.CtVariableReference; import spoon.reflect.visitor.CtAbstractVisitor; -import spoon.reflect.visitor.filter.TypeFilter; import static spoon.pattern.PatternBuilder.bodyToStatements; @@ -96,22 +95,29 @@ public InlineStatementsBuilder setConflictResolutionMode(ConflictResolutionMode */ public InlineStatementsBuilder byVariableName(String variableName) { patternBuilder.patternQuery - .filterChildren(new TypeFilter<>(CtVariableReference.class)) - .map((CtVariableReference varRef) -> { - return variableName.equals(varRef.getSimpleName()) ? varRef.getParent(CtStatement.class) : null; - }).forEach((CtStatement stmt) -> { - //called for first parent statement of all variables named `variableName` - stmt.accept(new CtAbstractVisitor() { - @Override - public void visitCtForEach(CtForEach foreach) { - markInline(foreach); - } - @Override - public void visitCtIf(CtIf ifElement) { - markInline(ifElement); - } - }); - }); + .filterChildren((CtVariableReference varRef) -> variableName.equals(varRef.getSimpleName())) + .forEach(this::byElement); + return this; + } + + /** + * marks all CtIf and CtForEach whose expression contains element as inline statement. + * @param element a child of CtIf or CtForEach + * @return this to support fluent API + */ + InlineStatementsBuilder byElement(CtElement element) { + CtStatement stmt = element instanceof CtStatement ? (CtStatement) element : element.getParent(CtStatement.class); + //called for first parent statement of all current parameter substitutions + stmt.accept(new CtAbstractVisitor() { + @Override + public void visitCtForEach(CtForEach foreach) { + markInline(foreach); + } + @Override + public void visitCtIf(CtIf ifElement) { + markInline(ifElement); + } + }); return this; } diff --git a/src/main/java/spoon/pattern/ParametersBuilder.java b/src/main/java/spoon/pattern/ParametersBuilder.java index c5f09e1bda4..0b6a0889f1c 100644 --- a/src/main/java/spoon/pattern/ParametersBuilder.java +++ b/src/main/java/spoon/pattern/ParametersBuilder.java @@ -72,6 +72,7 @@ public class ParametersBuilder { private final PatternBuilder patternBuilder; private final Map parameterInfos; private AbstractParameterInfo currentParameter; + private List substitutedNodes = new ArrayList<>(); private ConflictResolutionMode conflictResolutionMode = ConflictResolutionMode.FAIL; ParametersBuilder(PatternBuilder patternBuilder, Map parameterInfos) { @@ -116,6 +117,7 @@ private AbstractParameterInfo getParameterInfo(String parameterName, boolean cre */ public ParametersBuilder parameter(String paramName) { currentParameter = getParameterInfo(paramName, true); + substitutedNodes.clear(); return this; } @@ -559,8 +561,20 @@ public ParametersBuilder matchCondition(Class type, Predicate matchCon return this; } + /** + * marks all CtIf and CtForEach whose expression is substituted by a this pattern parameter as inline statement. + * @return this to support fluent API + */ + public ParametersBuilder matchInlinedStatements() { + InlineStatementsBuilder sb = new InlineStatementsBuilder(patternBuilder); + for (CtElement ctElement : substitutedNodes) { + sb.byElement(ctElement); + } + return this; + } + public boolean isSubstituted(String paramName) { - if (patternBuilder.hasParameterInfo(paramName) == false) { + if (patternBuilder.getParameterInfo(paramName) == null) { return false; } ParameterInfo pi = getParameterInfo(paramName, false); @@ -576,6 +590,8 @@ class Result { } void addSubstitutionRequest(ParameterInfo parameter, CtElement element) { + //remember elements substituted by current parameter to be able to use them for marking inline statements + substitutedNodes.add(element); ParameterElementPair pep = getSubstitutedNodeOfElement(parameter, element); patternBuilder.setNodeOfElement(pep.element, new ParameterNode(pep.parameter), conflictResolutionMode); if (patternBuilder.isAutoSimplifySubstitutions() && pep.element.isParentInitialized()) { diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index 866a30b0264..dd413af5797 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -398,10 +398,6 @@ PatternBuilder configureLocalParameters(Consumer parametersBu return this; } /** - * - * All variables defined out of scope of the model (eg body of the method) - * are considered as template parameters - * * Provides backward compatibility with standard Template parameters based on {@link TemplateParameter} and {@link Parameter} annotation * * @return this to support fluent API @@ -627,8 +623,8 @@ static CtTypeReference getLocalTypeRefBySimpleName(CtType templateType, St return null; } - boolean hasParameterInfo(String parameterName) { - return parameterInfos.containsKey(parameterName); + AbstractParameterInfo getParameterInfo(String parameterName) { + return parameterInfos.get(parameterName); } protected Factory getFactory() { diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index be0c8da4f3a..a80e7d7c907 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -91,9 +91,8 @@ public void testMatchForeach() throws Exception { // } Pattern pattern = PatternBuilder.create(new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) .configureParameters(pb -> { - pb.parameter("values").byVariable("values").setContainerKind(ContainerKind.LIST); + pb.parameter("values").byVariable("values").setContainerKind(ContainerKind.LIST).matchInlinedStatements(); }) - .configureInlineStatements(lsb -> lsb.byVariableName("values")) .build(); List matches = pattern.getMatches(ctClass); diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchForEach2.java b/src/test/java/spoon/test/template/testclasses/match/MatchForEach2.java index 65751c29021..b2eceece197 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchForEach2.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchForEach2.java @@ -18,10 +18,9 @@ public static Pattern createPattern(Factory factory) { return PatternBuilder.create(new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) .configureParameters(pb -> { - pb.parameter("values").byVariable("values").setContainerKind(ContainerKind.LIST); + pb.parameter("values").byVariable("values").setContainerKind(ContainerKind.LIST).matchInlinedStatements(); pb.parameter("varName").byString("var"); }) - .configureInlineStatements(lsb -> lsb.byVariableName("values")) .build(); } diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchIfElse.java b/src/test/java/spoon/test/template/testclasses/match/MatchIfElse.java index 8b81dbe8fc6..651638a132b 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchIfElse.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchIfElse.java @@ -20,6 +20,8 @@ public static Pattern createPattern(Factory factory) { pb.parameter("option2").byVariable("option2"); pb.parameter("value").byFilter(new TypeFilter(CtLiteral.class)); }) + //we have to configure inline statements after all expressions + //of combined if statement are marked as pattern parameters .configureInlineStatements(lsb -> lsb.byVariableName("option")) .build(); } diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple2.java b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple2.java index fa74163777e..8380ed4d41e 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple2.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple2.java @@ -23,12 +23,9 @@ public static Pattern createPattern(Factory factory, Consumer .configureParameters(pb -> { pb.parameter("statements1").setContainerKind(ContainerKind.LIST); pb.parameter("statements2").setContainerKind(ContainerKind.LIST); - pb.parameter("printedValue").byVariable("something"); + pb.parameter("printedValue").byVariable("something").matchInlinedStatements(); cfgParams.accept(pb); }) - .configureInlineStatements(ls -> { - ls.byVariableName("something"); - }) .build(); } diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterCondition.java b/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterCondition.java index 412e0c865c7..70077840841 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterCondition.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterCondition.java @@ -20,7 +20,6 @@ public static Pattern createPattern(Factory factory, Predicate condition pb.matchCondition(null, condition); } }) - .configureInlineStatements(lsb -> lsb.byVariableName("values")) .build(); } diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterType.java b/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterType.java index 4740a036609..98aa22a8acd 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterType.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterType.java @@ -18,7 +18,6 @@ public static Pattern createPattern(Factory factory, Class valueType) { pb.setValueType(valueType); } }) - .configureInlineStatements(lsb -> lsb.byVariableName("values")) .build(); } From 5d333a04387783a7da3cbd3111873481869102b3 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Sat, 7 Apr 2018 14:49:18 +0200 Subject: [PATCH 060/131] up --- .../java/spoon/pattern/TemplateBuilder.java | 164 ---------- .../spoon/pattern/TemplateModelBuilder.java | 290 ------------------ .../java/spoon/template/BlockTemplate.java | 1 - .../spoon/template/ExpressionTemplate.java | 1 - .../spoon/template/StatementTemplate.java | 1 - .../java/spoon/template/Substitution.java | 1 - .../java/spoon/template/TemplateMatcher.java | 1 - .../java/spoon/test/template/PatternTest.java | 51 ++- .../spoon/test/template/TemplateTest.java | 1 - .../testclasses/match/MatchForEach.java | 8 - .../testclasses/match/MatchForEach2.java | 18 -- .../testclasses/match/MatchIfElse.java | 22 -- .../template/testclasses/match/MatchMap.java | 13 - .../testclasses/match/MatchModifiers.java | 4 +- .../testclasses/match/MatchMultiple.java | 4 +- .../testclasses/match/MatchMultiple2.java | 4 +- .../testclasses/match/MatchMultiple3.java | 4 +- .../match/MatchWithParameterCondition.java | 4 +- .../match/MatchWithParameterType.java | 4 +- .../testclasses/replace/NewPattern.java | 7 +- .../testclasses/replace/OldPattern.java | 4 +- 21 files changed, 51 insertions(+), 556 deletions(-) delete mode 100644 src/main/java/spoon/pattern/TemplateBuilder.java delete mode 100644 src/main/java/spoon/pattern/TemplateModelBuilder.java diff --git a/src/main/java/spoon/pattern/TemplateBuilder.java b/src/main/java/spoon/pattern/TemplateBuilder.java deleted file mode 100644 index 882bc32f31d..00000000000 --- a/src/main/java/spoon/pattern/TemplateBuilder.java +++ /dev/null @@ -1,164 +0,0 @@ -/** - * 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.pattern; - - -import java.util.List; -import java.util.Map; - -import spoon.SpoonException; -import spoon.reflect.declaration.CtClass; -import spoon.reflect.declaration.CtElement; -import spoon.reflect.declaration.CtField; -import spoon.reflect.declaration.CtType; -import spoon.reflect.factory.Factory; -import spoon.reflect.reference.CtTypeReference; -import spoon.support.template.Parameters; -import spoon.template.Local; -import spoon.template.Parameter; -import spoon.template.Substitution; -import spoon.template.Template; -import spoon.template.TemplateParameter; - -/** - * The builder which creates a {@link Pattern} from the {@link Template} - */ -public class TemplateBuilder { - - /** - * Creates a {@link TemplateBuilder}, which builds {@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) { - CtClass> templateType = Substitution.getTemplateCtClass(templateRoot.getFactory(), template); - return createPattern(templateRoot, templateType, template); - } - /** - * Creates a {@link TemplateBuilder}, which builds {@link Pattern} from {@link Template} - * @param templateRoot the root element of {@link Template} model - * @param templateType {@link CtClass} model of `template` - * @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 - */ - public static TemplateBuilder createPattern(CtElement templateRoot, CtClass templateType, Template template) { - Factory f = templateRoot.getFactory(); - - if (template != null && templateType.getQualifiedName().equals(template.getClass().getName()) == false) { - throw new SpoonException("Unexpected template instance " + template.getClass().getName() + ". Expects " + templateType.getQualifiedName()); - } - - PatternBuilder pb; - - @SuppressWarnings("rawtypes") - CtTypeReference templateParamRef = f.Type().createReference(TemplateParameter.class); - if (templateType == templateRoot) { - //templateRoot is a class which extends from Template. We have to remove all Templating stuff from the patter model - TemplateModelBuilder tv = new TemplateModelBuilder(templateType); - { - tv.keepTypeMembers(typeMember -> { - if (typeMember.getAnnotation(Parameter.class) != null) { - //remove all type members annotated with @Parameter - return false; - } - if (typeMember.getAnnotation(Local.class) != null) { - //remove all type members annotated with @Local - return false; - } - //remove all Fields of type TemplateParameter - if (typeMember instanceof CtField && ((CtField) typeMember).getType().isSubtypeOf(templateParamRef)) { - return false; - } - //all other type members have to be part of the pattern model - return true; - }); - //remove `... extends Template`, which doesn't have to be part of pattern model - tv.removeSuperClass(); - }; - pb = PatternBuilder.create(tv.getTemplateModels()); - } else { - pb = PatternBuilder.create(templateRoot); - } - Map templateParameters = template == null ? null : Parameters.getTemplateParametersAsMap(f, null, template); - //legacy templates always automatically simplifies generated code - pb.setAutoSimplifySubstitutions(true); - pb.configureTemplateParameters(templateParameters); - return new TemplateBuilder(templateType, pb, template); - } - - private Template template; - private PatternBuilder patternBuilder; - private CtClass templateType; - - private TemplateBuilder(CtClass templateType, PatternBuilder patternBuilder, Template template) { - this.template = template; - this.patternBuilder = patternBuilder; - this.templateType = templateType; - } - - /** - * @return a {@link Pattern} built by this {@link TemplateBuilder} - */ - public Pattern build() { - return patternBuilder.build(); - } - - /** - * @param addGeneratedBy true if "generated by" comments has to be added into code generated by {@link Pattern} made by this {@link TemplateBuilder} - * @return this to support fluent API - */ - public TemplateBuilder setAddGeneratedBy(boolean addGeneratedBy) { - patternBuilder.setAddGeneratedBy(addGeneratedBy); - return this; - } - - /** - * @return Map of template parameters from `template` - */ - public Map getTemplateParameters() { - return getTemplateParameters(null); - } - /** - * @param targetType the type which will receive the model generated using returned parameters - * @return Map of template parameters from `template` - */ - public Map getTemplateParameters(CtType targetType) { - Factory f = templateType.getFactory(); - return Parameters.getTemplateParametersAsMap(f, targetType, template); - } - - /** - * generates a new AST node 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 a substituted element - */ - public T substituteSingle(CtType targetType, Class itemType) { - return build().substituteSingle(targetType.getFactory(), itemType, getTemplateParameters(targetType)); - } - /** - * generates a new AST nodes made by cloning of `patternModel` and by substitution of parameters by their values - * @param factory TODO - * @param targetType the CtType, which will receive the result of substitution - * @return List of substituted elements - */ - public List substituteList(Factory factory, CtType targetType, Class itemType) { - return build().substituteList(factory, itemType, getTemplateParameters(targetType)); - } -} diff --git a/src/main/java/spoon/pattern/TemplateModelBuilder.java b/src/main/java/spoon/pattern/TemplateModelBuilder.java deleted file mode 100644 index 4f908a34407..00000000000 --- a/src/main/java/spoon/pattern/TemplateModelBuilder.java +++ /dev/null @@ -1,290 +0,0 @@ -/** - * 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.pattern; - -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; - -import spoon.SpoonException; -import spoon.reflect.code.CtBlock; -import spoon.reflect.code.CtReturn; -import spoon.reflect.code.CtStatement; -import spoon.reflect.declaration.CtAnnotation; -import spoon.reflect.declaration.CtElement; -import spoon.reflect.declaration.CtExecutable; -import spoon.reflect.declaration.CtMethod; -import spoon.reflect.declaration.CtType; -import spoon.reflect.declaration.CtTypeMember; -import spoon.reflect.reference.CtTypeReference; -import spoon.reflect.visitor.Filter; - -/** - * Utility class to select parts of AST to be used as a model of a {@link Pattern}. - */ -public class TemplateModelBuilder { - /** - * The original type, which contains the AST of pattern model - */ - private final CtType templateType; - /** - * optional clone of templateType. It is created when AST of CtType has to be modified - * before it can become a model of {@link Pattern} - */ - private CtType clonedTemplateType; - /** - * holds the built pattern model - */ - private List templateModel = null; - - public TemplateModelBuilder(CtType templateTemplate) { - this.templateType = templateTemplate; - } - - /** - * Returns clone of the templateType. - * The clone is done only once. Later calls returns cached clone. - * @return - */ - private CtType getClonedTemplateType() { - if (clonedTemplateType == null) { - clonedTemplateType = templateType.clone(); - if (templateType.isParentInitialized()) { - //set parent package, to keep origin qualified name of the Template. It is needed for correct substitution of Template name by target type reference - clonedTemplateType.setParent(templateType.getParent()); - } - } - return clonedTemplateType; - } - - /** - * Sets a template model from {@link CtTypeMember} of a template type - * @param typeMemberName the name of the {@link CtTypeMember} of a template type - */ - public TemplateModelBuilder setTypeMember(String typeMemberName) { - setTypeMember(tm -> typeMemberName.equals(tm.getSimpleName())); - return this; - } - /** - * Sets a template model from {@link CtTypeMember} of a template type - * @param filter the {@link Filter} whose match defines to be used {@link CtTypeMember} - */ - public TemplateModelBuilder setTypeMember(Filter filter) { - setTemplateModel(getByFilter(filter)); - return this; - } - - /** - * removes all annotations of type defined by `classes` from the clone of the source {@link CtType} - * @param classes list of classes which defines types of to be removed annotations - * @return this to support fluent API - */ - public TemplateModelBuilder removeTag(Class... classes) { - List elements = getClonedTemplateModel(); - for (Class class1 : classes) { - for (CtElement element : elements) { - CtAnnotation annotation = element.getAnnotation(element.getFactory().Type().createReference(class1)); - if (annotation != null) { - element.removeAnnotation(annotation); - } - } - } - return this; - } - - private List getClonedTemplateModel() { - if (templateModel == null) { - throw new SpoonException("Template model is not defined yet"); - } - for (ListIterator iter = templateModel.listIterator(); iter.hasNext();) { - CtElement ele = iter.next(); - if (ele.getRoleInParent() != null) { - iter.set(ele.clone()); - } - } - return templateModel; - } - - /** - * Sets a template model from body of the method of template type - * @param methodName the name of {@link CtMethod} - */ - public TemplateModelBuilder setBodyOfMethod(String methodName) { - setBodyOfMethod(tm -> methodName.equals(tm.getSimpleName())); - return this; - } - /** - * Sets a template model from body of the method of template type selected by filter - * @param filter the {@link Filter} whose match defines to be used {@link CtMethod} - */ - public void setBodyOfMethod(Filter> filter) { - CtBlock body = getOneByFilter(filter).getBody(); - setTemplateModel(body.getStatements()); - } - - /** - * Sets a template model from return expression of the method of template type selected by filter - * @param methodName the name of {@link CtMethod} - */ - public void setReturnExpressionOfMethod(String methodName) { - setReturnExpressionOfMethod(tm -> methodName.equals(tm.getSimpleName())); - } - /** - * Sets a template model from return expression of the method of template type selected by filter - * @param filter the {@link Filter} whose match defines to be used {@link CtExecutable} - */ - public void setReturnExpressionOfMethod(Filter> filter) { - CtMethod method = getOneByFilter(filter); - CtBlock body = method.getBody(); - if (body.getStatements().size() != 1) { - throw new SpoonException("The body of " + method.getSignature() + " must contain exactly one statement. But there is:\n" + body.toString()); - } - CtStatement firstStatement = body.getStatements().get(0); - if (firstStatement instanceof CtReturn == false) { - throw new SpoonException("The body of " + method.getSignature() + " must contain return statement. But there is:\n" + body.toString()); - } - setTemplateModel(((CtReturn) firstStatement).getReturnedExpression()); - } - - private List getByFilter(Filter filter) { - List elements = templateType.filterChildren(filter).list(); - if (elements == null || elements.isEmpty()) { - throw new SpoonException("Element not found in " + templateType.getShortRepresentation()); - } - return elements; - } - private T getOneByFilter(Filter filter) { - List elements = getByFilter(filter); - if (elements.size() != 1) { - throw new SpoonException("Only one element must be selected, but there are: " + elements); - } - return elements.get(0); - } - /** - * @param filter whose matches will be removed from the template model - */ - public TemplateModelBuilder removeTypeMembers(Filter filter) { - for (CtTypeMember ctTypeMember : new ArrayList<>(getClonedTemplateType().getTypeMembers())) { - if (filter.matches(ctTypeMember)) { - ctTypeMember.delete(); - } - } - return this; - } - - /** - * Removes all type members which are annotated by `annotationClass` - */ - @SuppressWarnings({ "unchecked", "rawtypes" }) - public TemplateModelBuilder removeTypeMembersAnnotatedBy(Class... annotationClass) { - for (Class ac : annotationClass) { - removeTypeMembers(tm -> tm.getAnnotation((Class) ac) != null); - } - return this; - } - - /** - * @param filter whose matches will be kept in the template. All others will be removed - */ - public TemplateModelBuilder keepTypeMembers(Filter filter) { - for (CtTypeMember ctTypeMember : new ArrayList<>(getClonedTemplateType().getTypeMembers())) { - if (filter.matches(ctTypeMember) == false) { - ctTypeMember.delete(); - } - } - return this; - } - - /** - * Keeps only type members, which are annotated by `annotationClass`. All others will be removed - */ - public TemplateModelBuilder keepTypeMembersAnnotatedBy(Class annotationClass) { - keepTypeMembers(tm -> tm.getAnnotation(annotationClass) != null); - return this; - } - - /** - * removes super class from the template - */ - public TemplateModelBuilder removeSuperClass() { - getClonedTemplateType().setSuperclass(null); - return this; - } - - /** - * @param filter super interfaces which matches the filter will be removed - */ - public TemplateModelBuilder removeSuperInterfaces(Filter> filter) { - Set> superIfaces = new HashSet<>(getClonedTemplateType().getSuperInterfaces()); - boolean changed = false; - for (Iterator> iter = superIfaces.iterator(); iter.hasNext();) { - if (filter.matches(iter.next())) { - iter.remove(); - changed = true; - } - } - if (changed) { - getClonedTemplateType().setSuperInterfaces(superIfaces); - } - return this; - } - - /** - * @param filter super interfaces which matches the filter will be kept. Others will be removed - */ - public TemplateModelBuilder keepSuperInterfaces(Filter> filter) { - Set> superIfaces = new HashSet<>(getClonedTemplateType().getSuperInterfaces()); - boolean changed = false; - for (Iterator> iter = superIfaces.iterator(); iter.hasNext();) { - if (filter.matches(iter.next())) { - iter.remove(); - changed = true; - } - } - if (changed) { - getClonedTemplateType().setSuperInterfaces(superIfaces); - } - return this; - } - - /** - * @return a List of {@link CtElement}s, which has to be used as pattern model - */ - public List getTemplateModels() { - return templateModel; - } - - /** - * @param template a {@link CtElement}, which has to be used as pattern model - */ - public void setTemplateModel(CtElement template) { - this.templateModel = Collections.singletonList(template); - } - - /** - * @param template a List of {@link CtElement}s, which has to be used as pattern model - */ - @SuppressWarnings({ "unchecked", "rawtypes" }) - public void setTemplateModel(List template) { - this.templateModel = (List) template; - } -} diff --git a/src/main/java/spoon/template/BlockTemplate.java b/src/main/java/spoon/template/BlockTemplate.java index 2af94dec007..a02eb5fd144 100644 --- a/src/main/java/spoon/template/BlockTemplate.java +++ b/src/main/java/spoon/template/BlockTemplate.java @@ -16,7 +16,6 @@ */ package spoon.template; -import spoon.pattern.TemplateBuilder; import spoon.reflect.code.CtBlock; import spoon.reflect.declaration.CtClass; import spoon.reflect.declaration.CtType; diff --git a/src/main/java/spoon/template/ExpressionTemplate.java b/src/main/java/spoon/template/ExpressionTemplate.java index 9d6fed71ea6..2094a1956cd 100644 --- a/src/main/java/spoon/template/ExpressionTemplate.java +++ b/src/main/java/spoon/template/ExpressionTemplate.java @@ -16,7 +16,6 @@ */ package spoon.template; -import spoon.pattern.TemplateBuilder; import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtExpression; import spoon.reflect.code.CtReturn; diff --git a/src/main/java/spoon/template/StatementTemplate.java b/src/main/java/spoon/template/StatementTemplate.java index 09e2383686a..17ba7d9a781 100644 --- a/src/main/java/spoon/template/StatementTemplate.java +++ b/src/main/java/spoon/template/StatementTemplate.java @@ -18,7 +18,6 @@ import java.util.List; -import spoon.pattern.TemplateBuilder; import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtStatement; import spoon.reflect.declaration.CtClass; diff --git a/src/main/java/spoon/template/Substitution.java b/src/main/java/spoon/template/Substitution.java index eb7b84e18ac..a1ed8c2bd89 100644 --- a/src/main/java/spoon/template/Substitution.java +++ b/src/main/java/spoon/template/Substitution.java @@ -18,7 +18,6 @@ import spoon.SpoonException; import spoon.pattern.PatternBuilder; -import spoon.pattern.TemplateBuilder; import spoon.processing.FactoryAccessor; import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtExpression; diff --git a/src/main/java/spoon/template/TemplateMatcher.java b/src/main/java/spoon/template/TemplateMatcher.java index a70668c7965..134180bd7dc 100644 --- a/src/main/java/spoon/template/TemplateMatcher.java +++ b/src/main/java/spoon/template/TemplateMatcher.java @@ -19,7 +19,6 @@ import java.util.List; import spoon.pattern.Pattern; -import spoon.pattern.TemplateBuilder; import spoon.pattern.matcher.Match; import spoon.pattern.matcher.TobeMatched; import spoon.pattern.node.ModelNode; diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index a80e7d7c907..50060a6961c 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -28,7 +28,7 @@ import spoon.pattern.ParametersBuilder; import spoon.pattern.Pattern; import spoon.pattern.PatternBuilder; -import spoon.pattern.TemplateModelBuilder; +import spoon.pattern.PatternBuilderHelper; import spoon.pattern.matcher.Match; import spoon.pattern.matcher.Quantifier; import spoon.pattern.parameter.ParameterInfo; @@ -78,7 +78,7 @@ public class PatternTest { @Test public void testMatchForeach() throws Exception { - //contract: inline foreach template can match multiple models into list of parameter values + //contract: a foreach template can also match inlined lists of statements CtType ctClass = ModelUtils.buildClass(MatchForEach.class); CtType type = ctClass.getFactory().Type().get(MatchForEach.class); @@ -89,7 +89,7 @@ public void testMatchForeach() throws Exception { // System.out.println(value); // } // } - Pattern pattern = PatternBuilder.create(new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) + Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("matcher1").getPatternElements()) .configureParameters(pb -> { pb.parameter("values").byVariable("values").setContainerKind(ContainerKind.LIST).matchInlinedStatements(); }) @@ -121,10 +121,18 @@ public void testMatchForeach() throws Exception { @Test public void testMatchForeachWithOuterSubstitution() throws Exception { - //contract: inline foreach template can match multiple models into list of parameter values including outer parameters + //contract: inline foreach templates can also match outer parameters CtType ctClass = ModelUtils.buildClass(MatchForEach2.class); - Pattern pattern = MatchForEach2.createPattern(ctClass.getFactory()); + CtType type = ctClass.getFactory().Type().get(MatchForEach2.class); + + Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("matcher1").getPatternElements()) + .configureParameters(pb -> { + pb.parameter("values").byVariable("values").setContainerKind(ContainerKind.LIST).matchInlinedStatements(); + // the variable "var" of the template is a parameter + pb.parameter("varName").byString("var"); + }) + .build(); List matches = pattern.getMatches(ctClass); @@ -143,9 +151,9 @@ public void testMatchForeachWithOuterSubstitution() throws Exception { "cc++", "java.lang.System.out.println(((java.lang.String) (null)))", "cc++"), listToListOfStrings(match.getMatchingElements())); - assertEquals(Arrays.asList( - "\"Xxxx\"", - "((java.lang.String) (null))"), listToListOfStrings((List) match.getParameters().getValue("values"))); + + // correctly matching the outer parameter + assertEquals("cc", match.getParameters().getValue("varName")); } { Match match = matches.get(2); @@ -153,8 +161,9 @@ public void testMatchForeachWithOuterSubstitution() throws Exception { "int dd = 0", "java.lang.System.out.println(java.lang.Long.class.toString())", "dd++"), listToListOfStrings(match.getMatchingElements())); - assertEquals(Arrays.asList( - "java.lang.Long.class.toString()"), listToListOfStrings((List) match.getParameters().getValue("values"))); + + // correctly matching the outer parameter + assertEquals("dd", match.getParameters().getValue("varName")); } } @@ -163,7 +172,17 @@ public void testMatchIfElse() throws Exception { //contract: inline switch Pattern can match one of the models CtType ctClass = ModelUtils.buildClass(MatchIfElse.class); - Pattern pattern = MatchIfElse.createPattern(ctClass.getFactory()); + CtType type = ctClass.getFactory().Type().get(MatchIfElse.class); + Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("matcher1").getPatternElements()) + .configureParameters(pb -> { + pb.parameter("option").byVariable("option"); + pb.parameter("option2").byVariable("option2"); + pb.parameter("value").byFilter(new TypeFilter(CtLiteral.class)); + }) + //we have to configure inline statements after all expressions + //of combined if statement are marked as pattern parameters + .configureInlineStatements(lsb -> lsb.byVariableName("option")) + .build(); List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0)); @@ -784,7 +803,7 @@ public void testMatchOfMapAttribute() throws Exception { // @Check() // void matcher1() { // } - Pattern pattern = PatternBuilder.create(new TemplateModelBuilder(type).setTypeMember("matcher1").getTemplateModels()) + Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(type).setTypeMember("matcher1").getPatternElements()) .configureParameters(pb -> { //match any value of @Check annotation to parameter `testAnnotations` pb.parameter("__pattern_param_annot").byRole(new TypeFilter(CtAnnotation.class), CtRole.VALUE).setContainerKind(ContainerKind.MAP); @@ -850,7 +869,7 @@ public void testMatchOfMapAttributeAndOtherAnnotations() throws Exception { CtType type = ctClass.getFactory().Type().get(MatchMap.class); // create a pattern from method matcher1 //match all methods with arbitrary name, with any annotation set, Test modifiers, parameters, but with empty body and return type void - Pattern pattern = PatternBuilder.create(new TemplateModelBuilder(type).setTypeMember("matcher1").getTemplateModels()) + Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(type).setTypeMember("matcher1").getPatternElements()) .configureParameters(pb -> { //match any value of @Check annotation to parameter `testAnnotations` //match any method name @@ -888,7 +907,7 @@ public void testMatchOfMapKeySubstring() throws Exception { { //match all methods with arbitrary name, and Annotation Test modifiers, parameters, but with empty body and return type void CtType type = ctClass.getFactory().Type().get(MatchMap.class); - Pattern pattern = PatternBuilder.create(new TemplateModelBuilder(type).setTypeMember("m1").getTemplateModels()) + Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(type).setTypeMember("m1").getPatternElements()) .configureParameters(pb -> { //match any value of @Check annotation to parameter `testAnnotations` pb.parameter("CheckKey").bySubstring("value"); @@ -930,7 +949,7 @@ public void testMatchInSet() throws Exception { //contract: match elements in container of type Set - e.g method throwables CtType ctClass = ModelUtils.buildClass(MatchThrowables.class); Factory f = ctClass.getFactory(); - Pattern pattern = PatternBuilder.create(new TemplateModelBuilder(ctClass).setTypeMember("matcher1").getTemplateModels()) + Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(ctClass).setTypeMember("matcher1").getPatternElements()) .configureParameters(pb -> { pb.parameter("otherThrowables") //add matcher for other arbitrary throwables @@ -1068,7 +1087,7 @@ public void testMatchSample1() throws Exception { CtType type = f.Type().get(OldPattern.class); Pattern p = PatternBuilder //Create a pattern from all statements of OldPattern_ParamsInNestedType#patternModel - .create(new TemplateModelBuilder(type).setBodyOfMethod("patternModel").getTemplateModels()) + .create(new PatternBuilderHelper(type).setBodyOfMethod("patternModel").getPatternElements()) .configureParameters((ParametersBuilder pb) -> pb // creating patterns parameters for all references to "params" and "items" .createPatternParameterForVariable("params", "item") diff --git a/src/test/java/spoon/test/template/TemplateTest.java b/src/test/java/spoon/test/template/TemplateTest.java index 0e74d5341a4..bfb91183f5d 100644 --- a/src/test/java/spoon/test/template/TemplateTest.java +++ b/src/test/java/spoon/test/template/TemplateTest.java @@ -5,7 +5,6 @@ import spoon.SpoonException; import spoon.compiler.SpoonResourceHelper; import spoon.pattern.PatternBuilder; -import spoon.pattern.TemplateModelBuilder; import spoon.pattern.matcher.Match; import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtExpression; diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchForEach.java b/src/test/java/spoon/test/template/testclasses/match/MatchForEach.java index 7fe96085eb6..2cc46a189ff 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchForEach.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchForEach.java @@ -2,14 +2,6 @@ import java.util.List; -import spoon.pattern.Pattern; -import spoon.pattern.PatternBuilder; -import spoon.pattern.TemplateModelBuilder; -import spoon.reflect.declaration.CtType; -import spoon.reflect.factory.Factory; -import spoon.reflect.meta.ContainerKind; -import spoon.test.template.testclasses.replace.OldPattern; - import static java.lang.System.out; public class MatchForEach { diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchForEach2.java b/src/test/java/spoon/test/template/testclasses/match/MatchForEach2.java index b2eceece197..db515e5f3c8 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchForEach2.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchForEach2.java @@ -2,28 +2,10 @@ import java.util.List; -import spoon.pattern.Pattern; -import spoon.pattern.PatternBuilder; -import spoon.pattern.TemplateModelBuilder; -import spoon.reflect.declaration.CtType; -import spoon.reflect.factory.Factory; -import spoon.reflect.meta.ContainerKind; - import static java.lang.System.out; public class MatchForEach2 { - public static Pattern createPattern(Factory factory) { - CtType type = factory.Type().get(MatchForEach2.class); - - return PatternBuilder.create(new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) - .configureParameters(pb -> { - pb.parameter("values").byVariable("values").setContainerKind(ContainerKind.LIST).matchInlinedStatements(); - pb.parameter("varName").byString("var"); - }) - .build(); - } - public void matcher1(List values) { int var = 0; for (String value : values) { diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchIfElse.java b/src/test/java/spoon/test/template/testclasses/match/MatchIfElse.java index 651638a132b..e8b9a5e0e86 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchIfElse.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchIfElse.java @@ -1,31 +1,9 @@ package spoon.test.template.testclasses.match; -import spoon.pattern.Pattern; -import spoon.pattern.PatternBuilder; -import spoon.pattern.TemplateModelBuilder; -import spoon.reflect.code.CtLiteral; -import spoon.reflect.declaration.CtType; -import spoon.reflect.factory.Factory; -import spoon.reflect.visitor.filter.TypeFilter; - import static java.lang.System.out; public class MatchIfElse { - public static Pattern createPattern(Factory factory) { - CtType type = factory.Type().get(MatchIfElse.class); - return PatternBuilder.create(new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) - .configureParameters(pb -> { - pb.parameter("option").byVariable("option"); - pb.parameter("option2").byVariable("option2"); - pb.parameter("value").byFilter(new TypeFilter(CtLiteral.class)); - }) - //we have to configure inline statements after all expressions - //of combined if statement are marked as pattern parameters - .configureInlineStatements(lsb -> lsb.byVariableName("option")) - .build(); - } - public void matcher1(boolean option, boolean option2) { if (option) { //matches String argument diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchMap.java b/src/test/java/spoon/test/template/testclasses/match/MatchMap.java index 6cd30bceb91..fcfd22679f6 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchMap.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchMap.java @@ -1,18 +1,5 @@ package spoon.test.template.testclasses.match; -import spoon.pattern.ConflictResolutionMode; -import spoon.pattern.Pattern; -import spoon.pattern.PatternBuilder; -import spoon.pattern.TemplateModelBuilder; -import spoon.reflect.code.CtLiteral; -import spoon.reflect.declaration.CtAnnotation; -import spoon.reflect.declaration.CtMethod; -import spoon.reflect.declaration.CtType; -import spoon.reflect.factory.Factory; -import spoon.reflect.meta.ContainerKind; -import spoon.reflect.path.CtRole; -import spoon.reflect.visitor.filter.TypeFilter; - public class MatchMap { @Check() diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchModifiers.java b/src/test/java/spoon/test/template/testclasses/match/MatchModifiers.java index c7b5de4f3c2..ea8ee2f871d 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchModifiers.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchModifiers.java @@ -2,7 +2,7 @@ import spoon.pattern.Pattern; import spoon.pattern.PatternBuilder; -import spoon.pattern.TemplateModelBuilder; +import spoon.pattern.PatternBuilderHelper; import spoon.reflect.code.CtBlock; import spoon.reflect.declaration.CtMethod; import spoon.reflect.declaration.CtType; @@ -14,7 +14,7 @@ public class MatchModifiers { public static Pattern createPattern(Factory factory, boolean matchBody) { CtType type = factory.Type().get(MatchModifiers.class); - return PatternBuilder.create(new TemplateModelBuilder(type).setTypeMember("matcher1").getTemplateModels()) + return PatternBuilder.create(new PatternBuilderHelper(type).setTypeMember("matcher1").getPatternElements()) .configureParameters(pb -> { pb.parameter("modifiers").byRole(new TypeFilter(CtMethod.class), CtRole.MODIFIER); pb.parameter("methodName").byString("matcher1"); diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple.java b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple.java index ae43fe5f5b0..3451c294362 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple.java @@ -2,7 +2,7 @@ import spoon.pattern.Pattern; import spoon.pattern.PatternBuilder; -import spoon.pattern.TemplateModelBuilder; +import spoon.pattern.PatternBuilderHelper; import spoon.pattern.matcher.Quantifier; import spoon.reflect.code.CtLiteral; import spoon.reflect.declaration.CtType; @@ -15,7 +15,7 @@ public class MatchMultiple { public static Pattern createPattern(Factory factory, Quantifier matchingStrategy, Integer minCount, Integer maxCount) { CtType type = factory.Type().get(MatchMultiple.class); - return PatternBuilder.create(new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) + return PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("matcher1").getPatternElements()) .configureParameters(pb -> { pb.parameter("statements").bySimpleName("statements").setContainerKind(ContainerKind.LIST); if (matchingStrategy != null) { diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple2.java b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple2.java index 8380ed4d41e..55422cbd60e 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple2.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple2.java @@ -3,7 +3,7 @@ import spoon.pattern.ParametersBuilder; import spoon.pattern.Pattern; import spoon.pattern.PatternBuilder; -import spoon.pattern.TemplateModelBuilder; +import spoon.pattern.PatternBuilderHelper; import spoon.reflect.declaration.CtType; import spoon.reflect.factory.Factory; import spoon.reflect.meta.ContainerKind; @@ -18,7 +18,7 @@ public class MatchMultiple2 { public static Pattern createPattern(Factory factory, Consumer cfgParams) { CtType type = factory.Type().get(MatchMultiple2.class); - return PatternBuilder.create(new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) + return PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("matcher1").getPatternElements()) .configureTemplateParameters() .configureParameters(pb -> { pb.parameter("statements1").setContainerKind(ContainerKind.LIST); diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple3.java b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple3.java index 0b4f32b6d6d..f2c5c251183 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple3.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple3.java @@ -3,7 +3,7 @@ import spoon.pattern.ParametersBuilder; import spoon.pattern.Pattern; import spoon.pattern.PatternBuilder; -import spoon.pattern.TemplateModelBuilder; +import spoon.pattern.PatternBuilderHelper; import spoon.reflect.code.CtLiteral; import spoon.reflect.declaration.CtType; import spoon.reflect.factory.Factory; @@ -18,7 +18,7 @@ public class MatchMultiple3 { public static Pattern createPattern(Factory factory, Consumer cfgParams) { CtType type = factory.Type().get(MatchMultiple3.class); - return PatternBuilder.create(new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) + return PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("matcher1").getPatternElements()) .configureTemplateParameters() .configureParameters(pb -> { pb.parameter("statements1").setContainerKind(ContainerKind.LIST); diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterCondition.java b/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterCondition.java index 70077840841..e6f4f9d9a47 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterCondition.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterCondition.java @@ -2,7 +2,7 @@ import spoon.pattern.Pattern; import spoon.pattern.PatternBuilder; -import spoon.pattern.TemplateModelBuilder; +import spoon.pattern.PatternBuilderHelper; import spoon.reflect.declaration.CtType; import spoon.reflect.factory.Factory; import static java.lang.System.out; @@ -13,7 +13,7 @@ public class MatchWithParameterCondition { public static Pattern createPattern(Factory factory, Predicate condition) { CtType type = factory.Type().get(MatchWithParameterCondition.class); - return PatternBuilder.create(new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) + return PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("matcher1").getPatternElements()) .configureParameters(pb -> { pb.parameter("value").byVariable("value"); if (condition != null) { diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterType.java b/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterType.java index 98aa22a8acd..24459544b16 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterType.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterType.java @@ -2,7 +2,7 @@ import spoon.pattern.Pattern; import spoon.pattern.PatternBuilder; -import spoon.pattern.TemplateModelBuilder; +import spoon.pattern.PatternBuilderHelper; import spoon.reflect.declaration.CtType; import spoon.reflect.factory.Factory; import static java.lang.System.out; @@ -11,7 +11,7 @@ public class MatchWithParameterType { public static Pattern createPattern(Factory factory, Class valueType) { CtType type = factory.Type().get(MatchWithParameterType.class); - return PatternBuilder.create(new TemplateModelBuilder(type).setBodyOfMethod("matcher1").getTemplateModels()) + return PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("matcher1").getPatternElements()) .configureParameters(pb -> { pb.parameter("value").byVariable("value"); if (valueType != null) { diff --git a/src/test/java/spoon/test/template/testclasses/replace/NewPattern.java b/src/test/java/spoon/test/template/testclasses/replace/NewPattern.java index 10628c4701a..c2560ad4c8c 100644 --- a/src/test/java/spoon/test/template/testclasses/replace/NewPattern.java +++ b/src/test/java/spoon/test/template/testclasses/replace/NewPattern.java @@ -4,13 +4,10 @@ import spoon.pattern.Pattern; import spoon.pattern.PatternBuilder; -import spoon.pattern.TemplateModelBuilder; -import spoon.reflect.declaration.CtElement; +import spoon.pattern.PatternBuilderHelper; import spoon.reflect.declaration.CtType; import spoon.reflect.factory.Factory; import spoon.reflect.meta.ContainerKind; -import spoon.reflect.meta.RoleHandler; -import spoon.reflect.meta.impl.RoleHandlerHelper; public class NewPattern { @@ -37,7 +34,7 @@ private void patternModel(OldPattern.Parameters params) throws Exception { */ public static Pattern createPatternFromNewPattern(Factory factory) { CtType type = factory.Type().get(NewPattern.class); - return PatternBuilder.create(new TemplateModelBuilder(type).setBodyOfMethod("patternModel").getTemplateModels()) + return PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("patternModel").getPatternElements()) .createPatternParameters() .configureParameters(pb -> { pb.parameter("statements").setContainerKind(ContainerKind.LIST); diff --git a/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java b/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java index 34966547f4b..cd8742bb74d 100644 --- a/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java +++ b/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java @@ -2,7 +2,7 @@ import spoon.pattern.Pattern; import spoon.pattern.PatternBuilder; -import spoon.pattern.TemplateModelBuilder; +import spoon.pattern.PatternBuilderHelper; import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtInvocation; import spoon.reflect.declaration.CtType; @@ -64,7 +64,7 @@ void patternModel(Parameters params) throws Exception { */ public static Pattern createPatternFromOldPattern(Factory factory) { CtType type = factory.Type().get(OldPattern.class); - return PatternBuilder.create(new TemplateModelBuilder(type).setBodyOfMethod("patternModel").getTemplateModels()) + return PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("patternModel").getPatternElements()) .configureParameters(pb->pb .createPatternParameterForVariable("params", "item") .parameter("statements").setContainerKind(ContainerKind.LIST) From 5f0d7913ed87b4dcfed4cb4f998b9f73e69974bc Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Sat, 7 Apr 2018 14:52:11 +0200 Subject: [PATCH 061/131] up --- .../spoon/pattern/PatternBuilderHelper.java | 282 ++++++++++++++++++ .../java/spoon/template/TemplateBuilder.java | 162 ++++++++++ 2 files changed, 444 insertions(+) create mode 100644 src/main/java/spoon/pattern/PatternBuilderHelper.java create mode 100644 src/main/java/spoon/template/TemplateBuilder.java diff --git a/src/main/java/spoon/pattern/PatternBuilderHelper.java b/src/main/java/spoon/pattern/PatternBuilderHelper.java new file mode 100644 index 00000000000..d2d0079214f --- /dev/null +++ b/src/main/java/spoon/pattern/PatternBuilderHelper.java @@ -0,0 +1,282 @@ +/** + * 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.pattern; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Set; + +import spoon.SpoonException; +import spoon.reflect.code.CtBlock; +import spoon.reflect.code.CtReturn; +import spoon.reflect.code.CtStatement; +import spoon.reflect.declaration.CtAnnotation; +import spoon.reflect.declaration.CtElement; +import spoon.reflect.declaration.CtExecutable; +import spoon.reflect.declaration.CtMethod; +import spoon.reflect.declaration.CtType; +import spoon.reflect.declaration.CtTypeMember; +import spoon.reflect.reference.CtTypeReference; +import spoon.reflect.visitor.Filter; + +/** + * Utility class to select parts of AST to be used as a model of a {@link Pattern}. + */ +public class PatternBuilderHelper { + /** + * The original type, which contains the AST of pattern model + */ + private final CtType patternType; + /** + * optional clone of patternType. It is created when AST of CtType has to be modified + * before it can become a model of {@link Pattern} + */ + private CtType clonedPatternType; + /** + * holds the built pattern model + */ + private List elements = null; + + public PatternBuilderHelper(CtType templateTemplate) { + this.patternType = templateTemplate; + } + + /** + * Returns clone of the patternType. + * The clone is done only once. Later calls returns cached clone. + * @return + */ + private CtType getClonedPatternType() { + if (clonedPatternType == null) { + clonedPatternType = patternType.clone(); + if (patternType.isParentInitialized()) { + //set parent package, to keep origin qualified name of the Template. It is needed for correct substitution of Template name by target type reference + clonedPatternType.setParent(patternType.getParent()); + } + } + return clonedPatternType; + } + + /** + * Sets a template model from {@link CtTypeMember} of a template type + * @param typeMemberName the name of the {@link CtTypeMember} of a template type + */ + public PatternBuilderHelper setTypeMember(String typeMemberName) { + setTypeMember(tm -> typeMemberName.equals(tm.getSimpleName())); + return this; + } + /** + * Sets a template model from {@link CtTypeMember} of a template type + * @param filter the {@link Filter} whose match defines to be used {@link CtTypeMember} + */ + public PatternBuilderHelper setTypeMember(Filter filter) { + setElements(getByFilter(filter)); + return this; + } + + /** + * removes all annotations of type defined by `classes` from the clone of the source {@link CtType} + * @param classes list of classes which defines types of to be removed annotations + * @return this to support fluent API + */ + public PatternBuilderHelper removeTag(Class... classes) { + List elements = getClonedElements(); + for (Class class1 : classes) { + for (CtElement element : elements) { + CtAnnotation annotation = element.getAnnotation(element.getFactory().Type().createReference(class1)); + if (annotation != null) { + element.removeAnnotation(annotation); + } + } + } + return this; + } + + private List getClonedElements() { + if (elements == null) { + throw new SpoonException("Template model is not defined yet"); + } + for (ListIterator iter = elements.listIterator(); iter.hasNext();) { + CtElement ele = iter.next(); + if (ele.getRoleInParent() != null) { + iter.set(ele.clone()); + } + } + return elements; + } + + /** + * Sets a template model from body of the method of template type + * @param methodName the name of {@link CtMethod} + */ + public PatternBuilderHelper setBodyOfMethod(String methodName) { + setBodyOfMethod(tm -> methodName.equals(tm.getSimpleName())); + return this; + } + /** + * Sets a template model from body of the method of template type selected by filter + * @param filter the {@link Filter} whose match defines to be used {@link CtMethod} + */ + public void setBodyOfMethod(Filter> filter) { + CtBlock body = getOneByFilter(filter).getBody(); + setElements(body.getStatements()); + } + + /** + * Sets a template model from return expression of the method of template type selected by filter + * @param methodName the name of {@link CtMethod} + */ + public void setReturnExpressionOfMethod(String methodName) { + setReturnExpressionOfMethod(tm -> methodName.equals(tm.getSimpleName())); + } + /** + * Sets a template model from return expression of the method of template type selected by filter + * @param filter the {@link Filter} whose match defines to be used {@link CtExecutable} + */ + public void setReturnExpressionOfMethod(Filter> filter) { + CtMethod method = getOneByFilter(filter); + CtBlock body = method.getBody(); + if (body.getStatements().size() != 1) { + throw new SpoonException("The body of " + method.getSignature() + " must contain exactly one statement. But there is:\n" + body.toString()); + } + CtStatement firstStatement = body.getStatements().get(0); + if (firstStatement instanceof CtReturn == false) { + throw new SpoonException("The body of " + method.getSignature() + " must contain return statement. But there is:\n" + body.toString()); + } + elements.add(((CtReturn) firstStatement).getReturnedExpression()); + } + + private List getByFilter(Filter filter) { + List elements = patternType.filterChildren(filter).list(); + if (elements == null || elements.isEmpty()) { + throw new SpoonException("Element not found in " + patternType.getShortRepresentation()); + } + return elements; + } + private T getOneByFilter(Filter filter) { + List elements = getByFilter(filter); + if (elements.size() != 1) { + throw new SpoonException("Only one element must be selected, but there are: " + elements); + } + return elements.get(0); + } + /** + * @param filter whose matches will be removed from the template model + */ + public PatternBuilderHelper removeTypeMembers(Filter filter) { + for (CtTypeMember ctTypeMember : new ArrayList<>(getClonedPatternType().getTypeMembers())) { + if (filter.matches(ctTypeMember)) { + ctTypeMember.delete(); + } + } + return this; + } + + /** + * Removes all type members which are annotated by `annotationClass` + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public PatternBuilderHelper removeTypeMembersAnnotatedBy(Class... annotationClass) { + for (Class ac : annotationClass) { + removeTypeMembers(tm -> tm.getAnnotation((Class) ac) != null); + } + return this; + } + + /** + * @param filter whose matches will be kept in the template. All others will be removed + */ + public PatternBuilderHelper keepTypeMembers(Filter filter) { + for (CtTypeMember ctTypeMember : new ArrayList<>(getClonedPatternType().getTypeMembers())) { + if (filter.matches(ctTypeMember) == false) { + ctTypeMember.delete(); + } + } + return this; + } + + /** + * Keeps only type members, which are annotated by `annotationClass`. All others will be removed + */ + public PatternBuilderHelper keepTypeMembersAnnotatedBy(Class annotationClass) { + keepTypeMembers(tm -> tm.getAnnotation(annotationClass) != null); + return this; + } + + /** + * removes super class from the template + */ + public PatternBuilderHelper removeSuperClass() { + getClonedPatternType().setSuperclass(null); + return this; + } + + /** + * @param filter super interfaces which matches the filter will be removed + */ + public PatternBuilderHelper removeSuperInterfaces(Filter> filter) { + Set> superIfaces = new HashSet<>(getClonedPatternType().getSuperInterfaces()); + boolean changed = false; + for (Iterator> iter = superIfaces.iterator(); iter.hasNext();) { + if (filter.matches(iter.next())) { + iter.remove(); + changed = true; + } + } + if (changed) { + getClonedPatternType().setSuperInterfaces(superIfaces); + } + return this; + } + + /** + * @param filter super interfaces which matches the filter will be kept. Others will be removed + */ + public PatternBuilderHelper keepSuperInterfaces(Filter> filter) { + Set> superIfaces = new HashSet<>(getClonedPatternType().getSuperInterfaces()); + boolean changed = false; + for (Iterator> iter = superIfaces.iterator(); iter.hasNext();) { + if (filter.matches(iter.next())) { + iter.remove(); + changed = true; + } + } + if (changed) { + getClonedPatternType().setSuperInterfaces(superIfaces); + } + return this; + } + + /** + * @return a List of {@link CtElement}s, which has to be used as pattern model + */ + public List getPatternElements() { + return elements; + } + + /** + * @param template a List of {@link CtElement}s, which has to be used as pattern model + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void setElements(List template) { + this.elements = (List) template; + } +} diff --git a/src/main/java/spoon/template/TemplateBuilder.java b/src/main/java/spoon/template/TemplateBuilder.java new file mode 100644 index 00000000000..c16bf1702e1 --- /dev/null +++ b/src/main/java/spoon/template/TemplateBuilder.java @@ -0,0 +1,162 @@ +/** + * 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 java.util.List; +import java.util.Map; + +import spoon.SpoonException; +import spoon.pattern.Pattern; +import spoon.pattern.PatternBuilder; +import spoon.pattern.PatternBuilderHelper; +import spoon.reflect.declaration.CtClass; +import spoon.reflect.declaration.CtElement; +import spoon.reflect.declaration.CtField; +import spoon.reflect.declaration.CtType; +import spoon.reflect.factory.Factory; +import spoon.reflect.reference.CtTypeReference; +import spoon.support.template.Parameters; + +/** + * Internal class used to provide pattern-based implementation of Template and TemplateMatcher + */ +class TemplateBuilder { + + /** + * Creates a {@link TemplateBuilder}, which builds {@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) { + CtClass> templateType = Substitution.getTemplateCtClass(templateRoot.getFactory(), template); + return createPattern(templateRoot, templateType, template); + } + /** + * Creates a {@link TemplateBuilder}, which builds {@link Pattern} from {@link Template} + * @param templateRoot the root element of {@link Template} model + * @param templateType {@link CtClass} model of `template` + * @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 + */ + public static TemplateBuilder createPattern(CtElement templateRoot, CtClass templateType, Template template) { + Factory f = templateRoot.getFactory(); + + if (template != null && templateType.getQualifiedName().equals(template.getClass().getName()) == false) { + throw new SpoonException("Unexpected template instance " + template.getClass().getName() + ". Expects " + templateType.getQualifiedName()); + } + + PatternBuilder pb; + + @SuppressWarnings("rawtypes") + CtTypeReference templateParamRef = f.Type().createReference(TemplateParameter.class); + if (templateType == templateRoot) { + //templateRoot is a class which extends from Template. We have to remove all Templating stuff from the patter model + PatternBuilderHelper tv = new PatternBuilderHelper(templateType); + { + tv.keepTypeMembers(typeMember -> { + if (typeMember.getAnnotation(Parameter.class) != null) { + //remove all type members annotated with @Parameter + return false; + } + if (typeMember.getAnnotation(Local.class) != null) { + //remove all type members annotated with @Local + return false; + } + //remove all Fields of type TemplateParameter + if (typeMember instanceof CtField && ((CtField) typeMember).getType().isSubtypeOf(templateParamRef)) { + return false; + } + //all other type members have to be part of the pattern model + return true; + }); + //remove `... extends Template`, which doesn't have to be part of pattern model + tv.removeSuperClass(); + }; + pb = PatternBuilder.create(tv.getPatternElements()); + } else { + pb = PatternBuilder.create(templateRoot); + } + Map templateParameters = template == null ? null : Parameters.getTemplateParametersAsMap(f, null, template); + //legacy templates always automatically simplifies generated code + pb.setAutoSimplifySubstitutions(true); + pb.configureTemplateParameters(templateParameters); + return new TemplateBuilder(templateType, pb, template); + } + + private Template template; + private PatternBuilder patternBuilder; + private CtClass templateType; + + private TemplateBuilder(CtClass templateType, PatternBuilder patternBuilder, Template template) { + this.template = template; + this.patternBuilder = patternBuilder; + this.templateType = templateType; + } + + /** + * @return a {@link Pattern} built by this {@link TemplateBuilder} + */ + public Pattern build() { + return patternBuilder.build(); + } + + /** + * @param addGeneratedBy true if "generated by" comments has to be added into code generated by {@link Pattern} made by this {@link TemplateBuilder} + * @return this to support fluent API + */ + public TemplateBuilder setAddGeneratedBy(boolean addGeneratedBy) { + patternBuilder.setAddGeneratedBy(addGeneratedBy); + return this; + } + + /** + * @return Map of template parameters from `template` + */ + public Map getTemplateParameters() { + return getTemplateParameters(null); + } + /** + * @param targetType the type which will receive the model generated using returned parameters + * @return Map of template parameters from `template` + */ + public Map getTemplateParameters(CtType targetType) { + Factory f = templateType.getFactory(); + return Parameters.getTemplateParametersAsMap(f, targetType, template); + } + + /** + * generates a new AST node 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 a substituted element + */ + public T substituteSingle(CtType targetType, Class itemType) { + return build().substituteSingle(targetType.getFactory(), itemType, getTemplateParameters(targetType)); + } + /** + * generates a new AST nodes made by cloning of `patternModel` and by substitution of parameters by their values + * @param factory TODO + * @param targetType the CtType, which will receive the result of substitution + * @return List of substituted elements + */ + public List substituteList(Factory factory, CtType targetType, Class itemType) { + return build().substituteList(factory, itemType, getTemplateParameters(targetType)); + } +} From b3d68cddeb34997b2528d385ed53366638f7db53 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Sat, 7 Apr 2018 15:09:48 +0200 Subject: [PATCH 062/131] up --- .../java/spoon/pattern/ParametersBuilder.java | 21 ++++++------------- .../java/spoon/test/template/PatternTest.java | 21 ++++++++++++------- .../testclasses/match/MatchMultiple.java | 9 ++++---- 3 files changed, 24 insertions(+), 27 deletions(-) diff --git a/src/main/java/spoon/pattern/ParametersBuilder.java b/src/main/java/spoon/pattern/ParametersBuilder.java index 0b6a0889f1c..432c35e79a1 100644 --- a/src/main/java/spoon/pattern/ParametersBuilder.java +++ b/src/main/java/spoon/pattern/ParametersBuilder.java @@ -482,24 +482,12 @@ private void visitStringAttribute(CtElement element) { protected abstract void visitStringAttribute(RoleHandler roleHandler, CtElement element, String mapEntryKey, CtElement mapEntryValue); } - - /** - * Any named element or reference identified by it's simple name - * @param simpleName simple name of {@link CtNamedElement} or {@link CtReference} - * @return {@link ParametersBuilder} to support fluent API - */ - public ParametersBuilder bySimpleName(String simpleName) { - byNamedElementSimpleName(simpleName); - byReferenceSimpleName(simpleName); - return this; - } - /** * Any named element by it's simple name * @param simpleName simple name of {@link CtNamedElement} * @return {@link ParametersBuilder} to support fluent API */ - public ParametersBuilder byNamedElementSimpleName(String simpleName) { + public ParametersBuilder byNamedElement(String simpleName) { ParameterInfo pi = getCurrentParameter(); queryModel().filterChildren((CtNamedElement named) -> simpleName.equals(named.getSimpleName())) .forEach((CtNamedElement named) -> { @@ -509,11 +497,14 @@ public ParametersBuilder byNamedElementSimpleName(String simpleName) { } /** - * Any reference identified by it's simple name + * Any reference identified by it's simple name. + * + * Can be used to match any method call for instance. + * * @param simpleName simple name of {@link CtReference} * @return {@link ParametersBuilder} to support fluent API */ - public ParametersBuilder byReferenceSimpleName(String simpleName) { + public ParametersBuilder byReferenceName(String simpleName) { ParameterInfo pi = getCurrentParameter(); queryModel().filterChildren((CtReference ref) -> simpleName.equals(ref.getSimpleName())) .forEach((CtReference ref) -> { diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index 50060a6961c..2b61f426404 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -239,8 +239,9 @@ public void testMatchIfElse() throws Exception { } @Test public void testGenerateMultiValues() throws Exception { + // todo: what's the tested contract? CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); - Pattern pattern = MatchMultiple.createPattern(ctClass.getFactory(), null, null, null); + Pattern pattern = MatchMultiple.createPattern(null, null, null); Map params = new HashMap<>(); params.put("printedValue", "does it work?"); params.put("statements", ctClass.getMethodsByName("testMatch1").get(0).getBody().getStatements().subList(0, 3)); @@ -251,18 +252,21 @@ public void testGenerateMultiValues() throws Exception { "java.lang.System.out.println(i)", "java.lang.System.out.println(\"does it work?\")"), generated.stream().map(Object::toString).collect(Collectors.toList())); } + @Test public void testMatchGreedyMultiValueUnlimited() throws Exception { //contract: multivalue parameter can match multiple nodes into list of parameter values. //contract: default greedy matching eats everything but can leave some matches if it is needed to match remaining template parameters CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); - Pattern pattern = MatchMultiple.createPattern(ctClass.getFactory(), null, null, null); + + Pattern pattern = MatchMultiple.createPattern(null, null, null); List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0)); assertEquals(1, matches.size()); Match match = matches.get(0); //check all statements are matched + // TODO: why all statements are matched? AFAIU, nothing in the pattern definition says "any kind of statement" assertEquals(Arrays.asList( "int i = 0", "i++", @@ -278,6 +282,7 @@ public void testMatchGreedyMultiValueUnlimited() throws Exception { "java.lang.System.out.println(i)", "java.lang.System.out.println(\"Xxxx\")", "java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings((List) match.getParameters().getValue("statements"))); + //last statement is matched by last template, which saves printed value assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); assertEquals("\"last one\"", match.getParameters().getValue("printedValue").toString()); @@ -287,7 +292,7 @@ public void testMatchGreedyMultiValueUnlimited() throws Exception { public void testMatchGreedyMultiValueMaxCountLimit() throws Exception { //contract: default greedy matching eats everything until max count = 3 CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); - Pattern pattern = MatchMultiple.createPattern(ctClass.getFactory(), null, null, 3); + Pattern pattern = MatchMultiple.createPattern(null, null, 3); List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0)); @@ -334,7 +339,7 @@ public void testMatchReluctantMultivalue() throws Exception { //contract: reluctant matches only minimal amount CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); - Pattern pattern = MatchMultiple.createPattern(ctClass.getFactory(), Quantifier.RELUCTANT, null, null); + Pattern pattern = MatchMultiple.createPattern(Quantifier.RELUCTANT, null, null); List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0)); @@ -388,7 +393,7 @@ public void testMatchReluctantMultivalueMinCount1() throws Exception { //contract: reluctant matches only at least 1 node in this case CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); - Pattern pattern = MatchMultiple.createPattern(ctClass.getFactory(), Quantifier.RELUCTANT, 1, null); + Pattern pattern = MatchMultiple.createPattern(Quantifier.RELUCTANT, 1, null); List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0)); @@ -432,7 +437,7 @@ public void testMatchReluctantMultivalueExactly2() throws Exception { //contract: reluctant matches min 2 and max 2 nodes in this case CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); - Pattern pattern = MatchMultiple.createPattern(ctClass.getFactory(), Quantifier.RELUCTANT, 2, 2); + Pattern pattern = MatchMultiple.createPattern(Quantifier.RELUCTANT, 2, 2); List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0)); @@ -460,7 +465,7 @@ public void testMatchPossesiveMultiValueUnlimited() throws Exception { //contract: multivalue parameter can match multiple nodes into list of parameter values. //contract: possessive matching eats everything and never returns back CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); - Pattern pattern = MatchMultiple.createPattern(ctClass.getFactory(), Quantifier.POSSESSIVE, null, null); + Pattern pattern = MatchMultiple.createPattern(Quantifier.POSSESSIVE, null, null); List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0).getBody()); //the last template has nothing to match -> no match @@ -471,7 +476,7 @@ public void testMatchPossesiveMultiValueMaxCount4() throws Exception { //contract: multivalue parameter can match multiple nodes into list of parameter values. //contract: possessive matching eats everything and never returns back CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); - Pattern pattern = MatchMultiple.createPattern(ctClass.getFactory(), Quantifier.POSSESSIVE, null, 4); + Pattern pattern = MatchMultiple.createPattern(Quantifier.POSSESSIVE, null, 4); List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0)); diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple.java b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple.java index 3451c294362..422be24278f 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple.java @@ -6,18 +6,19 @@ import spoon.pattern.matcher.Quantifier; import spoon.reflect.code.CtLiteral; import spoon.reflect.declaration.CtType; -import spoon.reflect.factory.Factory; import spoon.reflect.meta.ContainerKind; +import spoon.testing.utils.ModelUtils; import static java.lang.System.out; public class MatchMultiple { - public static Pattern createPattern(Factory factory, Quantifier matchingStrategy, Integer minCount, Integer maxCount) { - CtType type = factory.Type().get(MatchMultiple.class); + /** return a pattern built from {}@link {@link #matcher1()} */ + public static Pattern createPattern(Quantifier matchingStrategy, Integer minCount, Integer maxCount) throws Exception { + CtType type = ModelUtils.buildClass(MatchMultiple.class); return PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("matcher1").getPatternElements()) .configureParameters(pb -> { - pb.parameter("statements").bySimpleName("statements").setContainerKind(ContainerKind.LIST); + pb.parameter("statements").byReferenceName("statements").setContainerKind(ContainerKind.LIST); if (matchingStrategy != null) { pb.setMatchingStrategy(matchingStrategy); } From 5c8a03e6bcbd570f8e2e0785aecfea1845b2e981 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Sat, 7 Apr 2018 15:18:17 +0200 Subject: [PATCH 063/131] up --- src/main/java/spoon/pattern/PatternBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index dd413af5797..f00ccf415d2 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -470,7 +470,7 @@ private void configureTemplateParameter(CtType templateType, Map) { //parameter is a multivalue - pb.parameter(parameterName).setContainerKind(ContainerKind.LIST).bySimpleName(stringMarker); + pb.parameter(parameterName).setContainerKind(ContainerKind.LIST).byNamedElement(stringMarker); } else if (paramType.isSubtypeOf(typeReferenceRef) || paramType.getQualifiedName().equals(Class.class.getName())) { /* * parameter with value type TypeReference or Class, identifies replacement of local type whose name is equal to parameter name From 71712f64ab84f193baf904df6ff8beef7c7ff7ef Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Sat, 7 Apr 2018 15:39:14 +0200 Subject: [PATCH 064/131] up --- src/main/java/spoon/pattern/PatternBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index f00ccf415d2..ecfaec9d3b9 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -470,7 +470,7 @@ private void configureTemplateParameter(CtType templateType, Map) { //parameter is a multivalue - pb.parameter(parameterName).setContainerKind(ContainerKind.LIST).byNamedElement(stringMarker); + pb.parameter(parameterName).setContainerKind(ContainerKind.LIST).byReferenceName(stringMarker); } else if (paramType.isSubtypeOf(typeReferenceRef) || paramType.getQualifiedName().equals(Class.class.getName())) { /* * parameter with value type TypeReference or Class, identifies replacement of local type whose name is equal to parameter name From 34aaf91d9c80bf5f078b3ae54f9542d0774c5c5b Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Sat, 7 Apr 2018 15:40:50 +0200 Subject: [PATCH 065/131] up --- src/main/java/spoon/pattern/PatternBuilder.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index ecfaec9d3b9..88c213212d5 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -470,6 +470,9 @@ private void configureTemplateParameter(CtType templateType, Map) { //parameter is a multivalue + // changed from bySimpleName to byReferenceName + // everything is OK but for testTemplateInheritance, where I (Martin) + // suspects that there is a hidden bug or implicit contract pb.parameter(parameterName).setContainerKind(ContainerKind.LIST).byReferenceName(stringMarker); } else if (paramType.isSubtypeOf(typeReferenceRef) || paramType.getQualifiedName().equals(Class.class.getName())) { /* From e4157a4c60d1b8953fe8a5c46338da97d7067c41 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Sat, 7 Apr 2018 21:26:06 +0200 Subject: [PATCH 066/131] added contract to #testGenerateMultiValues --- src/test/java/spoon/test/template/PatternTest.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index 2b61f426404..cf760f9ca5a 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -239,7 +239,9 @@ public void testMatchIfElse() throws Exception { } @Test public void testGenerateMultiValues() throws Exception { - // todo: what's the tested contract? + // contract: the pattern parameter (in this case 'statements') + //can have type List and can be replaced by list of elements + //(in this case by list of statements) CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); Pattern pattern = MatchMultiple.createPattern(null, null, null); Map params = new HashMap<>(); @@ -247,9 +249,11 @@ public void testGenerateMultiValues() throws Exception { params.put("statements", ctClass.getMethodsByName("testMatch1").get(0).getBody().getStatements().subList(0, 3)); List generated = pattern.substituteList(ctClass.getFactory(), CtStatement.class, params); assertEquals(Arrays.asList( + //these 3 statements comes from `statements` parameter value "int i = 0", "i++", "java.lang.System.out.println(i)", + //this statement comes from pattern model, just the string literal comes from parameter `printedValue` "java.lang.System.out.println(\"does it work?\")"), generated.stream().map(Object::toString).collect(Collectors.toList())); } From 410586eb1a59b1e7238d1b7292dfb083f646a53c Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Sun, 15 Apr 2018 07:43:14 +0200 Subject: [PATCH 067/131] up --- src/test/java/spoon/test/template/PatternTest.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index cf760f9ca5a..b0da09031eb 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -169,7 +169,11 @@ public void testMatchForeachWithOuterSubstitution() throws Exception { @Test public void testMatchIfElse() throws Exception { - //contract: inline switch Pattern can match one of the models + //contract: if statements can be inlined + + // in this example the main if statement starting with "if (option) {" + // is inlined + // and any of the branch can be matched CtType ctClass = ModelUtils.buildClass(MatchIfElse.class); CtType type = ctClass.getFactory().Type().get(MatchIfElse.class); From 215b3c2d94beffd7a72d2db9fc7f7d5c960c781e Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Sun, 15 Apr 2018 08:18:36 +0200 Subject: [PATCH 068/131] Pattern is Consumable (to simplify API) --- src/main/java/spoon/pattern/Pattern.java | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/src/main/java/spoon/pattern/Pattern.java b/src/main/java/spoon/pattern/Pattern.java index c9ea24c69d1..2ca7c76b503 100644 --- a/src/main/java/spoon/pattern/Pattern.java +++ b/src/main/java/spoon/pattern/Pattern.java @@ -56,7 +56,7 @@ * * Note: that code generated by pattern using some set of parameters ParamsA, matches with the same pattern and produces same set of ParametersA */ -public class Pattern implements CtConsumableFunction { +public class Pattern { private ParameterValueProviderFactory parameterValueProviderFactory = UnmodifiableParameterValueProvider.Factory.INSTANCE; //TODO rename private ModelNode modelValueResolver; @@ -207,27 +207,6 @@ public List applyToType(CtType targetType, Class return results; } - /** - * Consumer {@link Match} objects - */ - @Override - public void apply(Object input, CtConsumer outputConsumer) { - if (input == null) { - return; - } - if (input.getClass().isArray()) { - input = Arrays.asList((Object[]) input); - } - - MatchingScanner scanner = new MatchingScanner(modelValueResolver, parameterValueProviderFactory, outputConsumer); - ParameterValueProvider parameters = parameterValueProviderFactory.createParameterValueProvider(); - if (input instanceof Collection) { - scanner.scan(null, (Collection) input); - } else { - scanner.scan(null, (CtElement) input); - } - } - /** * Finds all target program sub-trees that correspond to a template * and calls consumer.accept(Match) From 14daf59dd30c96807bef056401d7f92c30f80cf2 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Sun, 15 Apr 2018 08:18:44 +0200 Subject: [PATCH 069/131] doc --- .../spoon/pattern/parameter/ParameterValueProvider.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/spoon/pattern/parameter/ParameterValueProvider.java b/src/main/java/spoon/pattern/parameter/ParameterValueProvider.java index 7a535eccb6d..9f80cbe8612 100644 --- a/src/main/java/spoon/pattern/parameter/ParameterValueProvider.java +++ b/src/main/java/spoon/pattern/parameter/ParameterValueProvider.java @@ -19,8 +19,10 @@ import java.util.Map; /** - * It is unmodifiable storage of parameter name-value pairs. - * The values may be primitive values or List,Set,Map of values. + * + * An immutable map. + * (eg unmodifiable storage of parameter name-value pairs). + * The values may be primitive values or List, Set, Map of values. * All internal containers are unmodifiable too. */ public interface ParameterValueProvider { From 0afd673f85b344968d6baba1c7063610c7cea0d0 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Sun, 15 Apr 2018 08:19:20 +0200 Subject: [PATCH 070/131] udpate test testGenerateMultiValues --- .../java/spoon/template/TemplateMatcher.java | 2 +- .../java/spoon/test/template/PatternTest.java | 27 ++++++++++++++----- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/main/java/spoon/template/TemplateMatcher.java b/src/main/java/spoon/template/TemplateMatcher.java index 134180bd7dc..7e855bc489b 100644 --- a/src/main/java/spoon/template/TemplateMatcher.java +++ b/src/main/java/spoon/template/TemplateMatcher.java @@ -118,6 +118,6 @@ public List find(final CtElement targetRoot) { * @param consumer the receiver of matches */ public void forEachMatch(CtElement rootElement, CtConsumer consumer) { - rootElement.map(pattern).forEach(consumer); + pattern.forEachMatch(rootElement, consumer); } } diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index b0da09031eb..e62132ee648 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -246,17 +246,32 @@ public void testGenerateMultiValues() throws Exception { // contract: the pattern parameter (in this case 'statements') //can have type List and can be replaced by list of elements //(in this case by list of statements) + + // here, in particular, we test method "substituteList" + + // setup of the test CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); + Factory factory = ctClass.getFactory(); + Pattern pattern = MatchMultiple.createPattern(null, null, null); Map params = new HashMap<>(); + + // created in "MatchMultiple.createPattern",matching a literal "something" + // so "something" si replaced by "does it work?" params.put("printedValue", "does it work?"); - params.put("statements", ctClass.getMethodsByName("testMatch1").get(0).getBody().getStatements().subList(0, 3)); - List generated = pattern.substituteList(ctClass.getFactory(), CtStatement.class, params); + List statementsToBeAdded = null; + + //statementsToBeAdded = ctClass.getMethodsByName("testMatch1").get(0).getBody().getStatements().subList(0, 3); // we don't use this in order not to mix the macthing and the transformation + statementsToBeAdded = Arrays.asList(new CtStatement[] {factory.createCodeSnippetStatement("int foo = 0"), factory.createCodeSnippetStatement("foo++")}); + + // created in "MatchMultiple.createPattern",matching a method "statements" + params.put("statements", statementsToBeAdded); + + List generated = pattern.substituteList(factory, CtStatement.class, params); assertEquals(Arrays.asList( - //these 3 statements comes from `statements` parameter value - "int i = 0", - "i++", - "java.lang.System.out.println(i)", + //these statements comes from `statements` parameter value + "int foo = 0", + "foo++", //this statement comes from pattern model, just the string literal comes from parameter `printedValue` "java.lang.System.out.println(\"does it work?\")"), generated.stream().map(Object::toString).collect(Collectors.toList())); } From 1d9713e652ecd41663eb43205707fbff9ff4a59e Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Sun, 15 Apr 2018 08:31:51 +0200 Subject: [PATCH 071/131] up --- src/main/java/spoon/reflect/meta/ContainerKind.java | 6 ++++++ .../test/template/testclasses/match/MatchMultiple.java | 3 +++ 2 files changed, 9 insertions(+) diff --git a/src/main/java/spoon/reflect/meta/ContainerKind.java b/src/main/java/spoon/reflect/meta/ContainerKind.java index 15ea37cbca8..122ec5c262c 100644 --- a/src/main/java/spoon/reflect/meta/ContainerKind.java +++ b/src/main/java/spoon/reflect/meta/ContainerKind.java @@ -25,16 +25,22 @@ public enum ContainerKind { * Example: CtClassImpl.simpleName */ SINGLE, + + /** * It is a list of values * Example: CtClassImpl.typeMembers */ LIST, + + /** * It is a set of values * Example: CtPackageImpl.types */ SET, + + /** * It is a map<String, T> of values * Example: CtAnnotationImpl.elementValues diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple.java b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple.java index 422be24278f..cf718a4e0dc 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple.java @@ -18,6 +18,9 @@ public static Pattern createPattern(Quantifier matchingStrategy, Integer minCoun CtType type = ModelUtils.buildClass(MatchMultiple.class); return PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("matcher1").getPatternElements()) .configureParameters(pb -> { + + // matching anything that is called "statements". + // the setContainerKind(ContainerKind.LIST) means match zero, one or more then one arbitrary statement pb.parameter("statements").byReferenceName("statements").setContainerKind(ContainerKind.LIST); if (matchingStrategy != null) { pb.setMatchingStrategy(matchingStrategy); From d709a50dc91ef192f871094163b191d85ffa17b6 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Sun, 15 Apr 2018 08:40:03 +0200 Subject: [PATCH 072/131] up --- src/test/java/spoon/test/template/PatternTest.java | 8 ++++---- .../test/template/testclasses/match/MatchMultiple.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index e62132ee648..0e82106ecc9 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -278,8 +278,9 @@ public void testGenerateMultiValues() throws Exception { @Test public void testMatchGreedyMultiValueUnlimited() throws Exception { - //contract: multivalue parameter can match multiple nodes into list of parameter values. - //contract: default greedy matching eats everything but can leave some matches if it is needed to match remaining template parameters + //contract: there is a way to match absolutely any kind of statement, in an unlimited list + + //explanation: multivalue parameter (setContainerKind(ContainerKind.LIST) can match multiple nodes in a row. CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); Pattern pattern = MatchMultiple.createPattern(null, null, null); @@ -288,8 +289,7 @@ public void testMatchGreedyMultiValueUnlimited() throws Exception { assertEquals(1, matches.size()); Match match = matches.get(0); - //check all statements are matched - // TODO: why all statements are matched? AFAIU, nothing in the pattern definition says "any kind of statement" + //check that absolutely all statements from "testMatch1" are matched assertEquals(Arrays.asList( "int i = 0", "i++", diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple.java b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple.java index cf718a4e0dc..a1e68495beb 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple.java @@ -19,7 +19,7 @@ public static Pattern createPattern(Quantifier matchingStrategy, Integer minCoun return PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("matcher1").getPatternElements()) .configureParameters(pb -> { - // matching anything that is called "statements". + // matching anything that is called "statements" (in this case call to method statement. // the setContainerKind(ContainerKind.LIST) means match zero, one or more then one arbitrary statement pb.parameter("statements").byReferenceName("statements").setContainerKind(ContainerKind.LIST); if (matchingStrategy != null) { From 81a0e96d9d1ae9da5d619984730b85a6f3017477 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Sun, 15 Apr 2018 09:15:37 +0200 Subject: [PATCH 073/131] copy ElementPrinterHelper to test to keep test independent on spoon core --- .../test/template/testclasses/replace/DPPSample1.java | 1 - .../testclasses/replace/ElementPrinterHelper.java | 9 +++++++++ .../test/template/testclasses/replace/OldPattern.java | 1 - 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 src/test/java/spoon/test/template/testclasses/replace/ElementPrinterHelper.java diff --git a/src/test/java/spoon/test/template/testclasses/replace/DPPSample1.java b/src/test/java/spoon/test/template/testclasses/replace/DPPSample1.java index a1bc706341d..47de08a0997 100644 --- a/src/test/java/spoon/test/template/testclasses/replace/DPPSample1.java +++ b/src/test/java/spoon/test/template/testclasses/replace/DPPSample1.java @@ -2,7 +2,6 @@ import spoon.reflect.declaration.CtEnum; import spoon.reflect.declaration.CtEnumValue; -import spoon.reflect.visitor.ElementPrinterHelper; import spoon.reflect.visitor.TokenWriter; public class DPPSample1 { diff --git a/src/test/java/spoon/test/template/testclasses/replace/ElementPrinterHelper.java b/src/test/java/spoon/test/template/testclasses/replace/ElementPrinterHelper.java new file mode 100644 index 00000000000..2ec46960aa9 --- /dev/null +++ b/src/test/java/spoon/test/template/testclasses/replace/ElementPrinterHelper.java @@ -0,0 +1,9 @@ +package spoon.test.template.testclasses.replace; + +import spoon.reflect.visitor.ListPrinter; + +public class ElementPrinterHelper { + public ListPrinter createListPrinter(boolean startPrefixSpace, String start, boolean startSufficSpace, boolean nextPrefixSpace, String next, boolean nextSuffixSpace, boolean endPrefixSpace, String end) { + return null; + } +} diff --git a/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java b/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java index cd8742bb74d..8a53fd5c74d 100644 --- a/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java +++ b/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java @@ -9,7 +9,6 @@ import spoon.reflect.factory.Factory; import spoon.reflect.meta.ContainerKind; import spoon.reflect.reference.CtTypeReference; -import spoon.reflect.visitor.ElementPrinterHelper; import spoon.reflect.visitor.TokenWriter; public class OldPattern { From 65dc0bc5e53ce8ce56700ee6590a662db16c85d6 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Sun, 15 Apr 2018 09:41:54 +0200 Subject: [PATCH 074/131] fix to pass testTemplateInheritance --- src/main/java/spoon/pattern/ParametersBuilder.java | 11 +++++++++++ src/main/java/spoon/pattern/Pattern.java | 1 - src/main/java/spoon/pattern/PatternBuilder.java | 6 ++---- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/main/java/spoon/pattern/ParametersBuilder.java b/src/main/java/spoon/pattern/ParametersBuilder.java index 432c35e79a1..3850498eff6 100644 --- a/src/main/java/spoon/pattern/ParametersBuilder.java +++ b/src/main/java/spoon/pattern/ParametersBuilder.java @@ -482,6 +482,17 @@ private void visitStringAttribute(CtElement element) { protected abstract void visitStringAttribute(RoleHandler roleHandler, CtElement element, String mapEntryKey, CtElement mapEntryValue); } + /** + * Any named element or reference identified by it's simple name + * @param simpleName simple name of {@link CtNamedElement} or {@link CtReference} + * @return {@link ParametersBuilder} to support fluent API + */ + public ParametersBuilder byName(String simpleName) { + byNamedElement(simpleName); + byReferenceName(simpleName); + return this; + } + /** * Any named element by it's simple name * @param simpleName simple name of {@link CtNamedElement} diff --git a/src/main/java/spoon/pattern/Pattern.java b/src/main/java/spoon/pattern/Pattern.java index 2ca7c76b503..7d67902d800 100644 --- a/src/main/java/spoon/pattern/Pattern.java +++ b/src/main/java/spoon/pattern/Pattern.java @@ -38,7 +38,6 @@ import spoon.reflect.declaration.CtTypeMember; import spoon.reflect.factory.Factory; import spoon.reflect.reference.CtTypeReference; -import spoon.reflect.visitor.chain.CtConsumableFunction; import spoon.reflect.visitor.chain.CtConsumer; /** diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index 88c213212d5..274716a2084 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -470,10 +470,8 @@ private void configureTemplateParameter(CtType templateType, Map) { //parameter is a multivalue - // changed from bySimpleName to byReferenceName - // everything is OK but for testTemplateInheritance, where I (Martin) - // suspects that there is a hidden bug or implicit contract - pb.parameter(parameterName).setContainerKind(ContainerKind.LIST).byReferenceName(stringMarker); + // here we need to replace all named element and all references whose simpleName == stringMarker + pb.parameter(parameterName).setContainerKind(ContainerKind.LIST).byName(stringMarker); } else if (paramType.isSubtypeOf(typeReferenceRef) || paramType.getQualifiedName().equals(Class.class.getName())) { /* * parameter with value type TypeReference or Class, identifies replacement of local type whose name is equal to parameter name From 3aa1103f4223e87fad49237866ca0f9b5d38ce0c Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Sun, 15 Apr 2018 15:03:01 +0200 Subject: [PATCH 075/131] move tests related to Pattern from TemplateTest into PatternTest --- .../java/spoon/test/template/PatternTest.java | 155 +++++++++++++++++- .../spoon/test/template/TemplateTest.java | 148 ----------------- 2 files changed, 154 insertions(+), 149 deletions(-) diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index 0e82106ecc9..7ea93b58760 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -37,6 +37,7 @@ import spoon.reflect.code.CtInvocation; import spoon.reflect.code.CtLiteral; import spoon.reflect.code.CtStatement; +import spoon.reflect.code.CtTry; import spoon.reflect.code.CtVariableRead; import spoon.reflect.declaration.CtAnnotation; import spoon.reflect.declaration.CtClass; @@ -54,6 +55,8 @@ import spoon.reflect.reference.CtTypeReference; import spoon.reflect.visitor.DefaultJavaPrettyPrinter; import spoon.reflect.visitor.filter.TypeFilter; +import spoon.test.template.testclasses.LoggerModel; +import spoon.test.template.testclasses.logger.Logger; import spoon.test.template.testclasses.match.MatchForEach; import spoon.test.template.testclasses.match.MatchForEach2; import spoon.test.template.testclasses.match.MatchIfElse; @@ -261,7 +264,7 @@ public void testGenerateMultiValues() throws Exception { params.put("printedValue", "does it work?"); List statementsToBeAdded = null; - //statementsToBeAdded = ctClass.getMethodsByName("testMatch1").get(0).getBody().getStatements().subList(0, 3); // we don't use this in order not to mix the macthing and the transformation + //statementsToBeAdded = ctClass.getMethodsByName("testMatch1").get(0).getBody().getStatements().subList(0, 3); // we don't use this in order not to mix the matching and the transformation statementsToBeAdded = Arrays.asList(new CtStatement[] {factory.createCodeSnippetStatement("int foo = 0"), factory.createCodeSnippetStatement("foo++")}); // created in "MatchMultiple.createPattern",matching a method "statements" @@ -1293,4 +1296,154 @@ public void testGenerateMethodWithSelfReferences() throws Exception { public void testInlineStatementsBuilder() throws Exception { // TODO: specify what InlineStatementsBuilder does } + + @Test + public void testTemplateMatchOfMultipleElements() throws Exception { + CtType toBeMatchedtype = ModelUtils.buildClass(ToBeMatched.class); + + // getting the list of literals defined in method match1 + List> literals1 = getFirstStmt(toBeMatchedtype, "match1", CtInvocation.class).getArguments(); + List> literals2 = getFirstStmt(toBeMatchedtype, "match2", CtInvocation.class).getArguments(); + assertEquals("a", literals1.get(0).getValue()); + + Factory f = toBeMatchedtype.getFactory(); + + { //contract: matches one exact literal + List found = new ArrayList<>(); + + // creating a Pattern from a Literal, with zero pattern parameters + // The pattern model consists of one CtLIteral only + // there is not needed any type reference, because CtLiteral has no reference to a type where it is defined + spoon.pattern.Pattern p = PatternBuilder.create(f.createLiteral("a")).build(); + + //The pattern has no parameters. There is just one constant CtLiteral + assertEquals (0, p.getParameterInfos().size()); + + // when we match the pattern agains AST of toBeMatchedtype, we find three instances of "a", + //because there are 3 instances of CtLiteral "a" in toBeMatchedtype + p.forEachMatch(toBeMatchedtype, (match) -> { + found.add(match.getMatchingElement()); + }); + + assertEquals(3, found.size()); + assertSame(literals1.get(0)/* first "a" in match1 */, found.get(0)); + assertSame(literals1.get(6)/* 2nd "a" in match1 */, found.get(1)); + assertSame(literals2.get(0)/* 1st "a" in match 2 */, found.get(2)); + } + { //contract: matches sequence of elements + List> found = new ArrayList<>(); + // now we match a sequence of "a", "b", "c" + spoon.pattern.Pattern pattern = patternOfStringLiterals(toBeMatchedtype.getFactory(), "a", "b", "c"); + pattern.forEachMatch(toBeMatchedtype, (match) -> { + found.add(match.getMatchingElements()); + }); + assertEquals(2, found.size()); + + assertEquals(3, found.get(1).size()); + // it starts with the first "a" in the match1 + assertEquals("\"a\"", found.get(0).get(0).toString()); + assertEquals(17, found.get(0).get(0).getPosition().getColumn()); + assertEquals("\"b\"", found.get(0).get(1).toString()); + assertEquals(22, found.get(0).get(1).getPosition().getColumn()); + assertEquals("\"c\"", found.get(0).get(2).toString()); + assertEquals(27, found.get(0).get(2).getPosition().getColumn()); + + // more generic asserts + assertSequenceOn(literals1, 0, 3, found.get(0)); + assertSequenceOn(literals1, 6, 3, found.get(1)); + } + { //contract: matches sequence of elements not starting at the beginning + List> found = new ArrayList<>(); + patternOfStringLiterals(toBeMatchedtype.getFactory(), "b", "c").forEachMatch(toBeMatchedtype, (match) -> { + found.add(match.getMatchingElements()); + }); + // we have three times a sequence ["b", "c"] + assertEquals(3, found.size()); + assertSequenceOn(literals1, 1, 2, found.get(0)); + assertSequenceOn(literals1, 7, 2, found.get(1)); + assertSequenceOn(literals2, 3, 2, found.get(2)); + } + { //contract: matches sequence of repeated elements, but match each element only once + List> found = new ArrayList<>(); + // we search for ["d", "d"] + patternOfStringLiterals(toBeMatchedtype.getFactory(), "d", "d").forEachMatch(toBeMatchedtype, (match) -> { + found.add(match.getMatchingElements()); + }); + // in ToBeMatched there is ["d", "d", "d", "d", "d] + // so there are only two sequences, starting at first and third "d" + assertEquals(2, found.size()); + assertSequenceOn(literals2, 6, 2, found.get(0)); + assertSequenceOn(literals2, 8, 2, found.get(1)); + } + } + + private static spoon.pattern.Pattern patternOfStringLiterals(Factory f, String... strs) { + return PatternBuilder.create(Arrays.asList(strs).stream().map(s -> f.createLiteral(s)).collect(Collectors.toList()) + ).build(); + } + + + private void assertSequenceOn(List source, int expectedOffset, int expectedSize, List matches) { + //check the number of matches + assertEquals(expectedSize, matches.size()); + //check that each match fits to source collection on the expected offset + for (int i = 0; i < expectedSize; i++) { + assertSame(source.get(expectedOffset + i), matches.get(i)); + } + } + + private T getFirstStmt(CtType type, String methodName, Class stmtType) { + return (T) type.filterChildren((CtMethod m) -> m.getSimpleName().equals(methodName)).first(CtMethod.class).getBody().getStatement(0); + } + + private int indexOf(List list, Object o) { + for(int i=0; i aTargetType = launcher.getFactory().Class().get(Logger.class); + final CtMethod toBeLoggedMethod = aTargetType.getMethodsByName("enter").get(0); + + + Map params = new HashMap<>(); + params.put("_classname_", factory.Code().createLiteral(aTargetType.getSimpleName())); + params.put("_methodName_", factory.Code().createLiteral(toBeLoggedMethod.getSimpleName())); + params.put("_block_", toBeLoggedMethod.getBody()); + //create a patter from the LoggerModel#block + CtType type = factory.Type().get(LoggerModel.class); + + + // creating a pattern from method "block" + spoon.pattern.Pattern pattern = PatternBuilder.create(type.getMethodsByName("block").get(0)) + //all the variable references which are declared out of type member "block" are automatically considered + //as pattern parameters + .createPatternParameters() + .build(); + final List aMethods = pattern.applyToType(aTargetType, CtMethod.class, params); + assertEquals(1, aMethods.size()); + final CtMethod aMethod = aMethods.get(0); + assertTrue(aMethod.getBody().getStatement(0) instanceof CtTry); + final CtTry aTry = (CtTry) aMethod.getBody().getStatement(0); + assertTrue(aTry.getFinalizer().getStatement(0) instanceof CtInvocation); + assertEquals("spoon.test.template.testclasses.logger.Logger.exit(\"enter\")", aTry.getFinalizer().getStatement(0).toString()); + assertTrue(aTry.getBody().getStatement(0) instanceof CtInvocation); + assertEquals("spoon.test.template.testclasses.logger.Logger.enter(\"Logger\", \"enter\")", aTry.getBody().getStatement(0).toString()); + assertTrue(aTry.getBody().getStatements().size() > 1); + } + } diff --git a/src/test/java/spoon/test/template/TemplateTest.java b/src/test/java/spoon/test/template/TemplateTest.java index bfb91183f5d..a75f7b99a56 100644 --- a/src/test/java/spoon/test/template/TemplateTest.java +++ b/src/test/java/spoon/test/template/TemplateTest.java @@ -649,47 +649,6 @@ public void testExtensionBlock() throws Exception { assertEquals("java.lang.System.out.println((((\"enter: \" + className) + \" - \") + methodName))", aTry.getBody().getStatement(1).toString()); } - @Test - public void testExtensionDecoupledSubstitutionVisitor() throws Exception { - //contract: substitution can be done on model, which is not based on Template - final Launcher launcher = new Launcher(); - launcher.setArgs(new String[] {"--output-type", "nooutput" }); - launcher.addInputResource("./src/test/java/spoon/test/template/testclasses/logger/Logger.java"); - launcher.addTemplateResource(new FileSystemFile("./src/test/java/spoon/test/template/testclasses/LoggerModel.java")); - - launcher.buildModel(); - Factory factory = launcher.getFactory(); - - final CtClass aTargetType = launcher.getFactory().Class().get(Logger.class); - final CtMethod toBeLoggedMethod = aTargetType.getMethodsByName("enter").get(0); - - - Map params = new HashMap<>(); - params.put("_classname_", factory.Code().createLiteral(aTargetType.getSimpleName())); - params.put("_methodName_", factory.Code().createLiteral(toBeLoggedMethod.getSimpleName())); - params.put("_block_", toBeLoggedMethod.getBody()); - //create a patter from the LoggerModel#block - CtType type = factory.Type().get(LoggerModel.class); - - - // creating a pattern from method "block" - spoon.pattern.Pattern pattern = PatternBuilder.create(type.getMethodsByName("block").get(0)) - //all the variable references which are declared out of type member "block" are automatically considered - //as pattern parameters - .createPatternParameters() - .build(); - final List aMethods = pattern.applyToType(aTargetType, CtMethod.class, params); - assertEquals(1, aMethods.size()); - final CtMethod aMethod = aMethods.get(0); - assertTrue(aMethod.getBody().getStatement(0) instanceof CtTry); - final CtTry aTry = (CtTry) aMethod.getBody().getStatement(0); - assertTrue(aTry.getFinalizer().getStatement(0) instanceof CtInvocation); - assertEquals("spoon.test.template.testclasses.logger.Logger.exit(\"enter\")", aTry.getFinalizer().getStatement(0).toString()); - assertTrue(aTry.getBody().getStatement(0) instanceof CtInvocation); - assertEquals("spoon.test.template.testclasses.logger.Logger.enter(\"Logger\", \"enter\")", aTry.getBody().getStatement(0).toString()); - assertTrue(aTry.getBody().getStatements().size() > 1); - } - @Test public void testTemplateInterfaces() throws Exception { Launcher spoon = new Launcher(); @@ -1164,111 +1123,4 @@ public void substituteTypeAccessReference() throws Exception { assertEquals("java.util.function.Supplier p = spoon.test.template.TypeReferenceClassAccess.Example::currentTimeMillis", method.getBody().getStatement(5).toString()); } - @Test - public void testTemplateMatchOfMultipleElements() throws Exception { - CtType toBeMatchedtype = ModelUtils.buildClass(ToBeMatched.class); - - // getting the list of literals defined in method match1 - List> literals1 = getFirstStmt(toBeMatchedtype, "match1", CtInvocation.class).getArguments(); - List> literals2 = getFirstStmt(toBeMatchedtype, "match2", CtInvocation.class).getArguments(); - assertEquals("a", literals1.get(0).getValue()); - - Factory f = toBeMatchedtype.getFactory(); - - { //contract: matches one exact literal - List found = new ArrayList<>(); - - // creating a Pattern from a Literal, with zero pattern parameters - // The pattern model consists of one CtLIteral only - // there is not needed any type reference, because CtLiteral has no reference to a type where it is defined - spoon.pattern.Pattern p = PatternBuilder.create(f.createLiteral("a")).build(); - - //The pattern has no parameters. There is just one constant CtLiteral - assertEquals (0, p.getParameterInfos().size()); - - // when we match the pattern agains AST of toBeMatchedtype, we find three instances of "a", - //because there are 3 instances of CtLiteral "a" in toBeMatchedtype - p.forEachMatch(toBeMatchedtype, (match) -> { - found.add(match.getMatchingElement()); - }); - - assertEquals(3, found.size()); - assertSame(literals1.get(0)/* first "a" in match1 */, found.get(0)); - assertSame(literals1.get(6)/* 2nd "a" in match1 */, found.get(1)); - assertSame(literals2.get(0)/* 1st "a" in match 2 */, found.get(2)); - } - { //contract: matches sequence of elements - List> found = new ArrayList<>(); - // now we match a sequence of "a", "b", "c" - spoon.pattern.Pattern pattern = patternOfStringLiterals(toBeMatchedtype.getFactory(), "a", "b", "c"); - pattern.forEachMatch(toBeMatchedtype, (match) -> { - found.add(match.getMatchingElements()); - }); - assertEquals(2, found.size()); - - assertEquals(3, found.get(1).size()); - // it starts with the first "a" in the match1 - assertEquals("\"a\"", found.get(0).get(0).toString()); - assertEquals(17, found.get(0).get(0).getPosition().getColumn()); - assertEquals("\"b\"", found.get(0).get(1).toString()); - assertEquals(22, found.get(0).get(1).getPosition().getColumn()); - assertEquals("\"c\"", found.get(0).get(2).toString()); - assertEquals(27, found.get(0).get(2).getPosition().getColumn()); - - // more generic asserts - assertSequenceOn(literals1, 0, 3, found.get(0)); - assertSequenceOn(literals1, 6, 3, found.get(1)); - } - { //contract: matches sequence of elements not starting at the beginning - List> found = new ArrayList<>(); - patternOfStringLiterals(toBeMatchedtype.getFactory(), "b", "c").forEachMatch(toBeMatchedtype, (match) -> { - found.add(match.getMatchingElements()); - }); - // we have three times a sequence ["b", "c"] - assertEquals(3, found.size()); - assertSequenceOn(literals1, 1, 2, found.get(0)); - assertSequenceOn(literals1, 7, 2, found.get(1)); - assertSequenceOn(literals2, 3, 2, found.get(2)); - } - { //contract: matches sequence of repeated elements, but match each element only once - List> found = new ArrayList<>(); - // we search for ["d", "d"] - patternOfStringLiterals(toBeMatchedtype.getFactory(), "d", "d").forEachMatch(toBeMatchedtype, (match) -> { - found.add(match.getMatchingElements()); - }); - // in ToBeMatched there is ["d", "d", "d", "d", "d] - // so there are only two sequences, starting at first and third "d" - assertEquals(2, found.size()); - assertSequenceOn(literals2, 6, 2, found.get(0)); - assertSequenceOn(literals2, 8, 2, found.get(1)); - } - } - - private static spoon.pattern.Pattern patternOfStringLiterals(Factory f, String... strs) { - return PatternBuilder.create(Arrays.asList(strs).stream().map(s -> f.createLiteral(s)).collect(Collectors.toList()) - ).build(); - } - - - private void assertSequenceOn(List source, int expectedOffset, int expectedSize, List matches) { - //check the number of matches - assertEquals(expectedSize, matches.size()); - //check that each match fits to source collection on the expected offset - for (int i = 0; i < expectedSize; i++) { - assertSame(source.get(expectedOffset + i), matches.get(i)); - } - } - - private T getFirstStmt(CtType type, String methodName, Class stmtType) { - return (T) type.filterChildren((CtMethod m) -> m.getSimpleName().equals(methodName)).first(CtMethod.class).getBody().getStatement(0); - } - - private int indexOf(List list, Object o) { - for(int i=0; i Date: Sun, 15 Apr 2018 15:05:45 +0200 Subject: [PATCH 076/131] up --- src/test/java/spoon/test/template/TemplateTest.java | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/test/java/spoon/test/template/TemplateTest.java b/src/test/java/spoon/test/template/TemplateTest.java index a75f7b99a56..97df211cf6b 100644 --- a/src/test/java/spoon/test/template/TemplateTest.java +++ b/src/test/java/spoon/test/template/TemplateTest.java @@ -4,14 +4,12 @@ import spoon.Launcher; import spoon.SpoonException; import spoon.compiler.SpoonResourceHelper; -import spoon.pattern.PatternBuilder; import spoon.pattern.matcher.Match; import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtExpression; import spoon.reflect.code.CtForEach; import spoon.reflect.code.CtIf; import spoon.reflect.code.CtInvocation; -import spoon.reflect.code.CtLiteral; import spoon.reflect.code.CtStatement; import spoon.reflect.code.CtTry; import spoon.reflect.declaration.CtClass; @@ -39,7 +37,6 @@ import spoon.test.template.testclasses.FieldAccessTemplate; import spoon.test.template.testclasses.InnerClassTemplate; import spoon.test.template.testclasses.InvocationTemplate; -import spoon.test.template.testclasses.LoggerModel; import spoon.test.template.testclasses.NtonCodeTemplate; import spoon.test.template.testclasses.ObjectIsNotParamTemplate; import spoon.test.template.testclasses.SecurityCheckerTemplate; @@ -47,7 +44,6 @@ import spoon.test.template.testclasses.SubStringTemplate; import spoon.test.template.testclasses.SubstituteLiteralTemplate; import spoon.test.template.testclasses.SubstituteRootTemplate; -import spoon.test.template.testclasses.ToBeMatched; import spoon.test.template.testclasses.TypeReferenceClassAccessTemplate; import spoon.test.template.testclasses.bounds.CheckBound; import spoon.test.template.testclasses.bounds.CheckBoundMatcher; @@ -73,7 +69,6 @@ import java.io.Serializable; import java.rmi.Remote; import java.util.ArrayList; -import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.IdentityHashMap; @@ -82,18 +77,17 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; +import static java.util.Arrays.asList; +import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import static org.hamcrest.CoreMatchers.is; -import static java.util.Arrays.asList; import static spoon.testing.utils.ModelUtils.getOptimizedString; public class TemplateTest { From 4aa325ce4927c9fa85487551f16ddab527ab2d17 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Sun, 15 Apr 2018 15:24:41 +0200 Subject: [PATCH 077/131] up --- .../java/spoon/test/template/PatternTest.java | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index 7ea93b58760..c8edbae7160 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -1,26 +1,6 @@ package spoon.test.template; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - import org.junit.Test; - import spoon.Launcher; import spoon.OutputType; import spoon.SpoonModelBuilder; @@ -55,7 +35,9 @@ import spoon.reflect.reference.CtTypeReference; import spoon.reflect.visitor.DefaultJavaPrettyPrinter; import spoon.reflect.visitor.filter.TypeFilter; +import spoon.support.compiler.FileSystemFile; import spoon.test.template.testclasses.LoggerModel; +import spoon.test.template.testclasses.ToBeMatched; import spoon.test.template.testclasses.logger.Logger; import spoon.test.template.testclasses.match.MatchForEach; import spoon.test.template.testclasses.match.MatchForEach2; @@ -74,6 +56,25 @@ import spoon.test.template.testclasses.types.AClassWithMethodsAndRefs; import spoon.testing.utils.ModelUtils; +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + // main test of Spoon's patterns public class PatternTest { From e7bac516e0c7aa83632dfa1e38d76b90ab820249 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Sun, 15 Apr 2018 15:37:39 +0200 Subject: [PATCH 078/131] end of review of testMatchIfElse --- .../java/spoon/test/template/PatternTest.java | 38 ++++--------------- .../testclasses/match/MatchIfElse.java | 3 -- 2 files changed, 7 insertions(+), 34 deletions(-) diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index c8edbae7160..8ef0c3e6473 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -174,17 +174,16 @@ public void testMatchForeachWithOuterSubstitution() throws Exception { @Test public void testMatchIfElse() throws Exception { //contract: if statements can be inlined + // meaning, either the if branch or the else branch can be matched independently // in this example the main if statement starting with "if (option) {" - // is inlined - // and any of the branch can be matched + // is inlined, and any of the branch can be matched CtType ctClass = ModelUtils.buildClass(MatchIfElse.class); CtType type = ctClass.getFactory().Type().get(MatchIfElse.class); Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("matcher1").getPatternElements()) .configureParameters(pb -> { pb.parameter("option").byVariable("option"); - pb.parameter("option2").byVariable("option2"); pb.parameter("value").byFilter(new TypeFilter(CtLiteral.class)); }) //we have to configure inline statements after all expressions @@ -194,56 +193,33 @@ public void testMatchIfElse() throws Exception { List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0)); - assertEquals(7, matches.size()); + // we only match the calls having a string literal as parameter or a float + assertEquals(5, matches.size()); { Match match = matches.get(0); - assertEquals(Arrays.asList("java.lang.System.out.println(i)"), listToListOfStrings(match.getMatchingElements())); - assertEquals(false, match.getParameters().getValue("option")); - assertEquals(true, match.getParameters().getValue("option2")); - assertEquals("i", match.getParameters().getValue("value").toString()); - } - { - Match match = matches.get(1); assertEquals(Arrays.asList("java.lang.System.out.println(\"a\")"), listToListOfStrings(match.getMatchingElements())); assertEquals(true, match.getParameters().getValue("option")); - assertEquals(false, match.getParameters().getValue("option2")); assertEquals("\"a\"", match.getParameters().getValue("value").toString()); } { - Match match = matches.get(2); + Match match = matches.get(1); assertEquals(Arrays.asList("java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings(match.getMatchingElements())); assertEquals(true, match.getParameters().getValue("option")); - assertEquals(false, match.getParameters().getValue("option2")); assertEquals("\"Xxxx\"", match.getParameters().getValue("value").toString()); } { - Match match = matches.get(3); + Match match = matches.get(2); assertEquals(Arrays.asList("java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings(match.getMatchingElements())); assertEquals(true, match.getParameters().getValue("option")); - assertEquals(false, match.getParameters().getValue("option2")); assertEquals("((java.lang.String) (null))", match.getParameters().getValue("value").toString()); } { Match match = matches.get(4); - assertEquals(Arrays.asList("java.lang.System.out.println(2018)"), listToListOfStrings(match.getMatchingElements())); - assertEquals(false, match.getParameters().getValue("option")); - assertEquals(true, match.getParameters().getValue("option2")); - assertEquals("2018", match.getParameters().getValue("value").toString()); - } - { - Match match = matches.get(5); - assertEquals(Arrays.asList("java.lang.System.out.println(java.lang.Long.class.toString())"), listToListOfStrings(match.getMatchingElements())); - assertEquals(true, match.getParameters().getValue("option")); - assertEquals(false, match.getParameters().getValue("option2")); - assertEquals("java.lang.Long.class.toString()", match.getParameters().getValue("value").toString()); - } - { - Match match = matches.get(6); assertEquals(Arrays.asList("java.lang.System.out.println(3.14)"), listToListOfStrings(match.getMatchingElements())); assertEquals(false, match.getParameters().getValue("option")); - assertEquals(false, match.getParameters().getValue("option2")); assertEquals("3.14", match.getParameters().getValue("value").toString()); } + } @Test public void testGenerateMultiValues() throws Exception { diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchIfElse.java b/src/test/java/spoon/test/template/testclasses/match/MatchIfElse.java index e8b9a5e0e86..cbedc19ff52 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchIfElse.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchIfElse.java @@ -8,9 +8,6 @@ public void matcher1(boolean option, boolean option2) { if (option) { //matches String argument System.out.println("string"); - } else if (option2) { - //matches int argument - System.out.println(1); } else { //matches double argument System.out.println(4.5); From 6304878396428b0dbb737d0f100025392cf1f3ad Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Sun, 15 Apr 2018 15:51:18 +0200 Subject: [PATCH 079/131] up --- src/test/java/spoon/test/template/PatternTest.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index 8ef0c3e6473..dfa648aaa3d 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -1,5 +1,6 @@ package spoon.test.template; +import org.hamcrest.core.Is; import org.junit.Test; import spoon.Launcher; import spoon.OutputType; @@ -229,6 +230,9 @@ public void testGenerateMultiValues() throws Exception { // here, in particular, we test method "substituteList" + // REVIEW: + // Here, I have a concern with the API: we have 4 public methods `substitute*`. It is hard for client to quickly grasp the differences. Is it possible to have a single `substituteMethod?` + // setup of the test CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); Factory factory = ctClass.getFactory(); @@ -293,7 +297,10 @@ public void testMatchGreedyMultiValueUnlimited() throws Exception { @Test public void testMatchGreedyMultiValueMaxCountLimit() throws Exception { - //contract: default greedy matching eats everything until max count = 3 + //contract: it is possible to stop matching after a specific number of times + // This is done with method parameterBuilder.setMaxOccurence(maxCount) + + // explanation: greedy matching eats everything until max count = 3 CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); Pattern pattern = MatchMultiple.createPattern(null, null, 3); From 6aa7845d3ee842a4039fb5280dd17e1d3862bdb2 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Sun, 15 Apr 2018 18:32:19 +0200 Subject: [PATCH 080/131] ParameterValueProvider moved to spoon.support.util --- src/main/java/spoon/pattern/DefaultGenerator.java | 2 +- src/main/java/spoon/pattern/Generator.java | 2 +- src/main/java/spoon/pattern/Pattern.java | 6 +++--- src/main/java/spoon/pattern/PatternPrinter.java | 2 +- src/main/java/spoon/pattern/matcher/Match.java | 2 +- src/main/java/spoon/pattern/matcher/MatchingScanner.java | 2 +- src/main/java/spoon/pattern/matcher/TobeMatched.java | 2 +- src/main/java/spoon/pattern/node/ConstantNode.java | 2 +- src/main/java/spoon/pattern/node/ElementNode.java | 2 +- src/main/java/spoon/pattern/node/ForEachNode.java | 2 +- src/main/java/spoon/pattern/node/InlineNode.java | 2 +- src/main/java/spoon/pattern/node/ListOfNodes.java | 2 +- src/main/java/spoon/pattern/node/MapEntryNode.java | 2 +- src/main/java/spoon/pattern/node/ParameterNode.java | 2 +- src/main/java/spoon/pattern/node/PrimitiveMatcher.java | 2 +- src/main/java/spoon/pattern/node/RepeatableMatcher.java | 2 +- src/main/java/spoon/pattern/node/RootNode.java | 2 +- src/main/java/spoon/pattern/node/StringNode.java | 2 +- src/main/java/spoon/pattern/node/SwitchNode.java | 2 +- .../java/spoon/pattern/parameter/AbstractParameterInfo.java | 1 + .../java/spoon/pattern/parameter/ListParameterInfo.java | 2 ++ src/main/java/spoon/pattern/parameter/MapParameterInfo.java | 3 +++ src/main/java/spoon/pattern/parameter/ParameterInfo.java | 1 + src/main/java/spoon/pattern/parameter/SetParameterInfo.java | 2 ++ .../parameter => support/util}/ParameterValueProvider.java | 2 +- .../util}/ParameterValueProviderFactory.java | 2 +- .../util}/UnmodifiableParameterValueProvider.java | 2 +- src/main/java/spoon/template/TemplateMatcher.java | 6 +++--- src/test/java/spoon/test/template/PatternTest.java | 2 +- .../java/spoon/test/template/core/ParameterInfoTest.java | 4 ++-- 30 files changed, 39 insertions(+), 30 deletions(-) rename src/main/java/spoon/{pattern/parameter => support/util}/ParameterValueProvider.java (98%) rename src/main/java/spoon/{pattern/parameter => support/util}/ParameterValueProviderFactory.java (96%) rename src/main/java/spoon/{pattern/parameter => support/util}/UnmodifiableParameterValueProvider.java (99%) diff --git a/src/main/java/spoon/pattern/DefaultGenerator.java b/src/main/java/spoon/pattern/DefaultGenerator.java index 3982f692d8a..fbada36cb28 100644 --- a/src/main/java/spoon/pattern/DefaultGenerator.java +++ b/src/main/java/spoon/pattern/DefaultGenerator.java @@ -18,7 +18,6 @@ import spoon.pattern.node.RootNode; import spoon.pattern.parameter.ParameterInfo; -import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.code.CtCodeElement; import spoon.reflect.code.CtComment; import spoon.reflect.cu.CompilationUnit; @@ -28,6 +27,7 @@ import spoon.reflect.declaration.CtTypeMember; import spoon.reflect.factory.Factory; import spoon.support.SpoonClassNotFoundException; +import spoon.support.util.ParameterValueProvider; /** * Drives generation process diff --git a/src/main/java/spoon/pattern/Generator.java b/src/main/java/spoon/pattern/Generator.java index 6943a2a12d5..e7f0d84c12c 100644 --- a/src/main/java/spoon/pattern/Generator.java +++ b/src/main/java/spoon/pattern/Generator.java @@ -20,9 +20,9 @@ import spoon.pattern.node.RootNode; import spoon.pattern.parameter.ParameterInfo; -import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.declaration.CtElement; import spoon.reflect.factory.Factory; +import spoon.support.util.ParameterValueProvider; /** * Drives generation process diff --git a/src/main/java/spoon/pattern/Pattern.java b/src/main/java/spoon/pattern/Pattern.java index 7d67902d800..cfbbd009290 100644 --- a/src/main/java/spoon/pattern/Pattern.java +++ b/src/main/java/spoon/pattern/Pattern.java @@ -29,9 +29,6 @@ import spoon.pattern.matcher.MatchingScanner; import spoon.pattern.node.ModelNode; import spoon.pattern.parameter.ParameterInfo; -import spoon.pattern.parameter.ParameterValueProvider; -import spoon.pattern.parameter.ParameterValueProviderFactory; -import spoon.pattern.parameter.UnmodifiableParameterValueProvider; import spoon.reflect.declaration.CtElement; import spoon.reflect.declaration.CtPackage; import spoon.reflect.declaration.CtType; @@ -39,6 +36,9 @@ import spoon.reflect.factory.Factory; import spoon.reflect.reference.CtTypeReference; import spoon.reflect.visitor.chain.CtConsumer; +import spoon.support.util.ParameterValueProvider; +import spoon.support.util.ParameterValueProviderFactory; +import spoon.support.util.UnmodifiableParameterValueProvider; /** * Represents a pattern for matching or transformation. It means an AST model, where some parts of that model are substituted by pattern parameters. diff --git a/src/main/java/spoon/pattern/PatternPrinter.java b/src/main/java/spoon/pattern/PatternPrinter.java index feed51c2673..ba65ad7bd64 100644 --- a/src/main/java/spoon/pattern/PatternPrinter.java +++ b/src/main/java/spoon/pattern/PatternPrinter.java @@ -30,7 +30,6 @@ import spoon.pattern.node.ParameterNode; import spoon.pattern.node.RootNode; import spoon.pattern.parameter.ParameterInfo; -import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.code.CtComment.CommentType; import spoon.reflect.code.CtInvocation; import spoon.reflect.code.CtLocalVariable; @@ -41,6 +40,7 @@ import spoon.reflect.visitor.PrinterHelper; import spoon.support.DefaultCoreFactory; import spoon.support.StandardEnvironment; +import spoon.support.util.ParameterValueProvider; /** */ diff --git a/src/main/java/spoon/pattern/matcher/Match.java b/src/main/java/spoon/pattern/matcher/Match.java index cd8d37dc634..4d548ca1548 100644 --- a/src/main/java/spoon/pattern/matcher/Match.java +++ b/src/main/java/spoon/pattern/matcher/Match.java @@ -21,8 +21,8 @@ import spoon.SpoonException; import spoon.pattern.Pattern; -import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.declaration.CtElement; +import spoon.support.util.ParameterValueProvider; /** * Represents a single match of {@link Pattern} diff --git a/src/main/java/spoon/pattern/matcher/MatchingScanner.java b/src/main/java/spoon/pattern/matcher/MatchingScanner.java index cc5ad70e720..fc0f72cd5a6 100644 --- a/src/main/java/spoon/pattern/matcher/MatchingScanner.java +++ b/src/main/java/spoon/pattern/matcher/MatchingScanner.java @@ -24,12 +24,12 @@ import spoon.SpoonException; import spoon.pattern.node.ModelNode; -import spoon.pattern.parameter.ParameterValueProviderFactory; import spoon.reflect.declaration.CtElement; import spoon.reflect.meta.ContainerKind; import spoon.reflect.path.CtRole; import spoon.reflect.visitor.EarlyTerminatingScanner; import spoon.reflect.visitor.chain.CtConsumer; +import spoon.support.util.ParameterValueProviderFactory; /** * Represents a Match of TemplateMatcher diff --git a/src/main/java/spoon/pattern/matcher/TobeMatched.java b/src/main/java/spoon/pattern/matcher/TobeMatched.java index 0a897156b85..3379fb130dd 100644 --- a/src/main/java/spoon/pattern/matcher/TobeMatched.java +++ b/src/main/java/spoon/pattern/matcher/TobeMatched.java @@ -25,8 +25,8 @@ import java.util.function.BiFunction; import spoon.SpoonException; -import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.meta.ContainerKind; +import spoon.support.util.ParameterValueProvider; /** * Describes what next has to be matched. diff --git a/src/main/java/spoon/pattern/node/ConstantNode.java b/src/main/java/spoon/pattern/node/ConstantNode.java index 0814585aa41..f0f3d96c91d 100644 --- a/src/main/java/spoon/pattern/node/ConstantNode.java +++ b/src/main/java/spoon/pattern/node/ConstantNode.java @@ -22,7 +22,7 @@ import spoon.pattern.ResultHolder; import spoon.pattern.matcher.Quantifier; import spoon.pattern.parameter.ParameterInfo; -import spoon.pattern.parameter.ParameterValueProvider; +import spoon.support.util.ParameterValueProvider; /** * Generates/Matches a copy of single template object diff --git a/src/main/java/spoon/pattern/node/ElementNode.java b/src/main/java/spoon/pattern/node/ElementNode.java index 8a941a0441a..d1b931e9ad0 100644 --- a/src/main/java/spoon/pattern/node/ElementNode.java +++ b/src/main/java/spoon/pattern/node/ElementNode.java @@ -35,11 +35,11 @@ import spoon.pattern.matcher.Quantifier; import spoon.pattern.matcher.TobeMatched; import spoon.pattern.parameter.ParameterInfo; -import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.declaration.CtElement; import spoon.reflect.meta.ContainerKind; import spoon.reflect.path.CtRole; import spoon.reflect.reference.CtExecutableReference; +import spoon.support.util.ParameterValueProvider; import static spoon.pattern.matcher.TobeMatched.getMatchedParameters; diff --git a/src/main/java/spoon/pattern/node/ForEachNode.java b/src/main/java/spoon/pattern/node/ForEachNode.java index ead4f9f0aef..14241637271 100644 --- a/src/main/java/spoon/pattern/node/ForEachNode.java +++ b/src/main/java/spoon/pattern/node/ForEachNode.java @@ -24,12 +24,12 @@ import spoon.pattern.matcher.Quantifier; import spoon.pattern.matcher.TobeMatched; import spoon.pattern.parameter.ParameterInfo; -import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtExpression; import spoon.reflect.code.CtForEach; import spoon.reflect.code.CtStatement; import spoon.reflect.factory.Factory; +import spoon.support.util.ParameterValueProvider; /** * Pattern node of multiple occurrences of the same model, just with different parameters. diff --git a/src/main/java/spoon/pattern/node/InlineNode.java b/src/main/java/spoon/pattern/node/InlineNode.java index c7da10e9b7b..112999679a3 100644 --- a/src/main/java/spoon/pattern/node/InlineNode.java +++ b/src/main/java/spoon/pattern/node/InlineNode.java @@ -18,7 +18,7 @@ import spoon.pattern.Generator; import spoon.pattern.ResultHolder; -import spoon.pattern.parameter.ParameterValueProvider; +import spoon.support.util.ParameterValueProvider; /** * Represents a kind of {@link RootNode}, diff --git a/src/main/java/spoon/pattern/node/ListOfNodes.java b/src/main/java/spoon/pattern/node/ListOfNodes.java index 1189edca730..bcaa27ea4bd 100644 --- a/src/main/java/spoon/pattern/node/ListOfNodes.java +++ b/src/main/java/spoon/pattern/node/ListOfNodes.java @@ -25,7 +25,7 @@ import spoon.pattern.matcher.Matchers; import spoon.pattern.matcher.TobeMatched; import spoon.pattern.parameter.ParameterInfo; -import spoon.pattern.parameter.ParameterValueProvider; +import spoon.support.util.ParameterValueProvider; /** * List of {@link RootNode}s. The {@link RootNode}s are processed in same order like they were inserted in the list diff --git a/src/main/java/spoon/pattern/node/MapEntryNode.java b/src/main/java/spoon/pattern/node/MapEntryNode.java index 1c4ff34f8e6..7e93c096ba1 100644 --- a/src/main/java/spoon/pattern/node/MapEntryNode.java +++ b/src/main/java/spoon/pattern/node/MapEntryNode.java @@ -25,9 +25,9 @@ import spoon.pattern.matcher.Quantifier; import spoon.pattern.matcher.TobeMatched; import spoon.pattern.parameter.ParameterInfo; -import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.declaration.CtElement; import spoon.reflect.meta.ContainerKind; +import spoon.support.util.ParameterValueProvider; import static spoon.pattern.matcher.TobeMatched.getMatchedParameters; diff --git a/src/main/java/spoon/pattern/node/ParameterNode.java b/src/main/java/spoon/pattern/node/ParameterNode.java index c4f421666d3..e872c6cd09d 100644 --- a/src/main/java/spoon/pattern/node/ParameterNode.java +++ b/src/main/java/spoon/pattern/node/ParameterNode.java @@ -22,8 +22,8 @@ import spoon.pattern.ResultHolder; import spoon.pattern.matcher.Quantifier; import spoon.pattern.parameter.ParameterInfo; -import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.declaration.CtElement; +import spoon.support.util.ParameterValueProvider; /** * Represents pattern model variable diff --git a/src/main/java/spoon/pattern/node/PrimitiveMatcher.java b/src/main/java/spoon/pattern/node/PrimitiveMatcher.java index 7ef079f337b..a0df3dc2a2d 100644 --- a/src/main/java/spoon/pattern/node/PrimitiveMatcher.java +++ b/src/main/java/spoon/pattern/node/PrimitiveMatcher.java @@ -16,7 +16,7 @@ */ package spoon.pattern.node; -import spoon.pattern.parameter.ParameterValueProvider; +import spoon.support.util.ParameterValueProvider; /** * Defines API of a primitive matcher - matcher for single target object diff --git a/src/main/java/spoon/pattern/node/RepeatableMatcher.java b/src/main/java/spoon/pattern/node/RepeatableMatcher.java index 54184870ff1..0dab925c3bc 100644 --- a/src/main/java/spoon/pattern/node/RepeatableMatcher.java +++ b/src/main/java/spoon/pattern/node/RepeatableMatcher.java @@ -17,7 +17,7 @@ package spoon.pattern.node; import spoon.pattern.matcher.Quantifier; -import spoon.pattern.parameter.ParameterValueProvider; +import spoon.support.util.ParameterValueProvider; /** * Defines API of a repeatable matcher. diff --git a/src/main/java/spoon/pattern/node/RootNode.java b/src/main/java/spoon/pattern/node/RootNode.java index 89fae889aac..fd200ee819c 100644 --- a/src/main/java/spoon/pattern/node/RootNode.java +++ b/src/main/java/spoon/pattern/node/RootNode.java @@ -23,7 +23,7 @@ import spoon.pattern.matcher.Matchers; import spoon.pattern.matcher.TobeMatched; import spoon.pattern.parameter.ParameterInfo; -import spoon.pattern.parameter.ParameterValueProvider; +import spoon.support.util.ParameterValueProvider; /** * Represents a parameterized Pattern ValueResolver, which can be used diff --git a/src/main/java/spoon/pattern/node/StringNode.java b/src/main/java/spoon/pattern/node/StringNode.java index c1fbaa59701..d4072bd98bb 100644 --- a/src/main/java/spoon/pattern/node/StringNode.java +++ b/src/main/java/spoon/pattern/node/StringNode.java @@ -32,7 +32,7 @@ import spoon.pattern.ResultHolder.Single; import spoon.pattern.matcher.Quantifier; import spoon.pattern.parameter.ParameterInfo; -import spoon.pattern.parameter.ParameterValueProvider; +import spoon.support.util.ParameterValueProvider; /** * Delivers single String value, which is created by replacing string markers in constant String template diff --git a/src/main/java/spoon/pattern/node/SwitchNode.java b/src/main/java/spoon/pattern/node/SwitchNode.java index 17aceb48f1e..d835cd5e7e5 100644 --- a/src/main/java/spoon/pattern/node/SwitchNode.java +++ b/src/main/java/spoon/pattern/node/SwitchNode.java @@ -26,13 +26,13 @@ import spoon.pattern.matcher.Matchers; import spoon.pattern.matcher.TobeMatched; import spoon.pattern.parameter.ParameterInfo; -import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtExpression; import spoon.reflect.code.CtIf; import spoon.reflect.code.CtStatement; import spoon.reflect.factory.CoreFactory; import spoon.reflect.factory.Factory; +import spoon.support.util.ParameterValueProvider; /** * List of conditional cases diff --git a/src/main/java/spoon/pattern/parameter/AbstractParameterInfo.java b/src/main/java/spoon/pattern/parameter/AbstractParameterInfo.java index c9186627296..d74db65a854 100644 --- a/src/main/java/spoon/pattern/parameter/AbstractParameterInfo.java +++ b/src/main/java/spoon/pattern/parameter/AbstractParameterInfo.java @@ -34,6 +34,7 @@ import spoon.reflect.factory.Factory; import spoon.reflect.meta.ContainerKind; import spoon.reflect.reference.CtTypeReference; +import spoon.support.util.ParameterValueProvider; /** */ diff --git a/src/main/java/spoon/pattern/parameter/ListParameterInfo.java b/src/main/java/spoon/pattern/parameter/ListParameterInfo.java index 885e82f2cef..16a2f8582a2 100644 --- a/src/main/java/spoon/pattern/parameter/ListParameterInfo.java +++ b/src/main/java/spoon/pattern/parameter/ListParameterInfo.java @@ -24,6 +24,8 @@ import java.util.Set; import java.util.function.Function; +import spoon.support.util.ParameterValueProvider; + /** */ public class ListParameterInfo extends AbstractParameterInfo { diff --git a/src/main/java/spoon/pattern/parameter/MapParameterInfo.java b/src/main/java/spoon/pattern/parameter/MapParameterInfo.java index 340a13e6f02..b2332956cb1 100644 --- a/src/main/java/spoon/pattern/parameter/MapParameterInfo.java +++ b/src/main/java/spoon/pattern/parameter/MapParameterInfo.java @@ -19,6 +19,9 @@ import java.util.Map; import java.util.function.Function; +import spoon.support.util.ParameterValueProvider; +import spoon.support.util.UnmodifiableParameterValueProvider; + /** * A kind of {@link ParameterInfo} which returns value by the named parameter * From a container of type {@link ParameterValueProvider} or {@link Map} diff --git a/src/main/java/spoon/pattern/parameter/ParameterInfo.java b/src/main/java/spoon/pattern/parameter/ParameterInfo.java index e1b2bb39ed2..79e19bfbabf 100644 --- a/src/main/java/spoon/pattern/parameter/ParameterInfo.java +++ b/src/main/java/spoon/pattern/parameter/ParameterInfo.java @@ -21,6 +21,7 @@ import spoon.pattern.matcher.Quantifier; import spoon.pattern.node.RootNode; import spoon.reflect.factory.Factory; +import spoon.support.util.ParameterValueProvider; /** * Represents the parameter of {@link Pattern} diff --git a/src/main/java/spoon/pattern/parameter/SetParameterInfo.java b/src/main/java/spoon/pattern/parameter/SetParameterInfo.java index 75cd93e4ae0..00d7198ca5e 100644 --- a/src/main/java/spoon/pattern/parameter/SetParameterInfo.java +++ b/src/main/java/spoon/pattern/parameter/SetParameterInfo.java @@ -24,6 +24,8 @@ import java.util.Set; import java.util.function.Function; +import spoon.support.util.ParameterValueProvider; + /** */ public class SetParameterInfo extends AbstractParameterInfo { diff --git a/src/main/java/spoon/pattern/parameter/ParameterValueProvider.java b/src/main/java/spoon/support/util/ParameterValueProvider.java similarity index 98% rename from src/main/java/spoon/pattern/parameter/ParameterValueProvider.java rename to src/main/java/spoon/support/util/ParameterValueProvider.java index 9f80cbe8612..4926bd3789f 100644 --- a/src/main/java/spoon/pattern/parameter/ParameterValueProvider.java +++ b/src/main/java/spoon/support/util/ParameterValueProvider.java @@ -14,7 +14,7 @@ * 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.pattern.parameter; +package spoon.support.util; import java.util.Map; diff --git a/src/main/java/spoon/pattern/parameter/ParameterValueProviderFactory.java b/src/main/java/spoon/support/util/ParameterValueProviderFactory.java similarity index 96% rename from src/main/java/spoon/pattern/parameter/ParameterValueProviderFactory.java rename to src/main/java/spoon/support/util/ParameterValueProviderFactory.java index ce4ae3c01db..fd4b0046dab 100644 --- a/src/main/java/spoon/pattern/parameter/ParameterValueProviderFactory.java +++ b/src/main/java/spoon/support/util/ParameterValueProviderFactory.java @@ -14,7 +14,7 @@ * 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.pattern.parameter; +package spoon.support.util; /** * Creates instances of {@link ParameterValueProvider} diff --git a/src/main/java/spoon/pattern/parameter/UnmodifiableParameterValueProvider.java b/src/main/java/spoon/support/util/UnmodifiableParameterValueProvider.java similarity index 99% rename from src/main/java/spoon/pattern/parameter/UnmodifiableParameterValueProvider.java rename to src/main/java/spoon/support/util/UnmodifiableParameterValueProvider.java index bd1dff50599..0277da43ce1 100644 --- a/src/main/java/spoon/pattern/parameter/UnmodifiableParameterValueProvider.java +++ b/src/main/java/spoon/support/util/UnmodifiableParameterValueProvider.java @@ -14,7 +14,7 @@ * 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.pattern.parameter; +package spoon.support.util; import java.util.ArrayList; import java.util.Collections; diff --git a/src/main/java/spoon/template/TemplateMatcher.java b/src/main/java/spoon/template/TemplateMatcher.java index 7e855bc489b..dd876b4e7e4 100644 --- a/src/main/java/spoon/template/TemplateMatcher.java +++ b/src/main/java/spoon/template/TemplateMatcher.java @@ -22,14 +22,14 @@ import spoon.pattern.matcher.Match; import spoon.pattern.matcher.TobeMatched; import spoon.pattern.node.ModelNode; -import spoon.pattern.parameter.ParameterValueProvider; -import spoon.pattern.parameter.ParameterValueProviderFactory; -import spoon.pattern.parameter.UnmodifiableParameterValueProvider; import spoon.reflect.declaration.CtClass; import spoon.reflect.declaration.CtElement; import spoon.reflect.meta.ContainerKind; import spoon.reflect.visitor.Filter; import spoon.reflect.visitor.chain.CtConsumer; +import spoon.support.util.ParameterValueProvider; +import spoon.support.util.ParameterValueProviderFactory; +import spoon.support.util.UnmodifiableParameterValueProvider; import static spoon.pattern.matcher.TobeMatched.getMatchedParameters; diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index dfa648aaa3d..60982200a84 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -13,7 +13,6 @@ import spoon.pattern.matcher.Match; import spoon.pattern.matcher.Quantifier; import spoon.pattern.parameter.ParameterInfo; -import spoon.pattern.parameter.ParameterValueProvider; import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtInvocation; import spoon.reflect.code.CtLiteral; @@ -37,6 +36,7 @@ import spoon.reflect.visitor.DefaultJavaPrettyPrinter; import spoon.reflect.visitor.filter.TypeFilter; import spoon.support.compiler.FileSystemFile; +import spoon.support.util.ParameterValueProvider; import spoon.test.template.testclasses.LoggerModel; import spoon.test.template.testclasses.ToBeMatched; import spoon.test.template.testclasses.logger.Logger; diff --git a/src/test/java/spoon/test/template/core/ParameterInfoTest.java b/src/test/java/spoon/test/template/core/ParameterInfoTest.java index 3d1a187d1a1..0b80cc2e541 100644 --- a/src/test/java/spoon/test/template/core/ParameterInfoTest.java +++ b/src/test/java/spoon/test/template/core/ParameterInfoTest.java @@ -21,10 +21,10 @@ import spoon.pattern.parameter.ListParameterInfo; import spoon.pattern.parameter.MapParameterInfo; import spoon.pattern.parameter.ParameterInfo; -import spoon.pattern.parameter.ParameterValueProvider; import spoon.pattern.parameter.SetParameterInfo; -import spoon.pattern.parameter.UnmodifiableParameterValueProvider; import spoon.reflect.meta.ContainerKind; +import spoon.support.util.ParameterValueProvider; +import spoon.support.util.UnmodifiableParameterValueProvider; public class ParameterInfoTest { From 370e4d380ed5e6108c3e6c8a5a538840c942b593 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Sun, 15 Apr 2018 22:17:47 +0200 Subject: [PATCH 081/131] up --- src/main/java/spoon/pattern/Generator.java | 2 +- .../java/spoon/pattern/ParametersBuilder.java | 2 +- src/main/java/spoon/pattern/Pattern.java | 63 ++++++----------- .../java/spoon/pattern/node/ElementNode.java | 2 +- .../java/spoon/pattern/node/ForEachNode.java | 10 +-- .../java/spoon/pattern/node/MapEntryNode.java | 4 +- .../java/spoon/pattern/node/SwitchNode.java | 6 +- .../pattern/parameter/MapParameterInfo.java | 6 +- .../support/util/ParameterValueProvider.java | 15 ++-- .../UnmodifiableParameterValueProvider.java | 6 +- .../java/spoon/template/TemplateBuilder.java | 3 +- .../java/spoon/test/template/PatternTest.java | 10 +-- .../test/template/core/ParameterInfoTest.java | 70 +++++++++---------- 13 files changed, 93 insertions(+), 106 deletions(-) diff --git a/src/main/java/spoon/pattern/Generator.java b/src/main/java/spoon/pattern/Generator.java index e7f0d84c12c..59bc06b179b 100644 --- a/src/main/java/spoon/pattern/Generator.java +++ b/src/main/java/spoon/pattern/Generator.java @@ -64,7 +64,7 @@ public interface Generator { * * @return a generate value or null */ - default T generateTarget(RootNode node, ParameterValueProvider parameters, Class expectedType) { + default T generateSingleTarget(RootNode node, ParameterValueProvider parameters, Class expectedType) { ResultHolder.Single result = new ResultHolder.Single<>(expectedType); generateTargets(node, result, parameters); return result.getResult(); diff --git a/src/main/java/spoon/pattern/ParametersBuilder.java b/src/main/java/spoon/pattern/ParametersBuilder.java index 3850498eff6..f2951166052 100644 --- a/src/main/java/spoon/pattern/ParametersBuilder.java +++ b/src/main/java/spoon/pattern/ParametersBuilder.java @@ -637,7 +637,7 @@ public ParameterElementPair copyAndSet(CtElement element) { } } - public ParameterElementPair getSubstitutedNodeOfElement(ParameterInfo parameter, CtElement element) { + private ParameterElementPair getSubstitutedNodeOfElement(ParameterInfo parameter, CtElement element) { ParameterElementPair parameterElementPair = new ParameterElementPair(parameter, element); parameterElementPair = transformVariableAccessToVariableReference(parameterElementPair); parameterElementPair = transformArrayAccess(parameterElementPair); diff --git a/src/main/java/spoon/pattern/Pattern.java b/src/main/java/spoon/pattern/Pattern.java index cfbbd009290..9c4d11515af 100644 --- a/src/main/java/spoon/pattern/Pattern.java +++ b/src/main/java/spoon/pattern/Pattern.java @@ -41,19 +41,18 @@ import spoon.support.util.UnmodifiableParameterValueProvider; /** - * Represents a pattern for matching or transformation. It means an AST model, where some parts of that model are substituted by pattern parameters. + * Represents a pattern for matching code. A pattern is composed of a list of AST models, where a model is an AST with some nodes being "pattern parameters". * * Differences with {@link spoon.template.TemplateMatcher}: * - it can match sequences of elements + * - it can match inlined elements * * Instances can created with {@link PatternBuilder}. * - * The {@link Pattern} can be used to process these two "itself opposite" operations - *
              - *
            • Generation of code from pattern. It means (Pattern) + (pattern parameters) => (copy of pattern where parameters are replaced by parameter values)
            • - *
            • Matching of code by pattern. It means (Pattern) + (code fitting to pattern) => (pattern parameters)
            • - *
            - * Note: that code generated by pattern using some set of parameters ParamsA, matches with the same pattern and produces same set of ParametersA + * The {@link Pattern} can also be used to generate new code where + * (Pattern) + (pattern parameters) => (copy of pattern where parameters are replaced by parameter values) + * + * This is done with {@link #substitute(Factory, Class, ParameterValueProvider)} */ public class Pattern { private ParameterValueProviderFactory parameterValueProviderFactory = UnmodifiableParameterValueProvider.Factory.INSTANCE; @@ -94,46 +93,30 @@ public Map getParameterInfos() { } /** - * generates a new AST made by cloning of `patternModel` and by substitution of parameters by values in `params` - * @param factory TODO - * @param valueType - the expected type of returned items - * @param params - the substitution parameters - * @return one generated element - */ - public T substituteSingle(Factory factory, Class valueType, Map params) { - return substituteSingle(factory, valueType, new UnmodifiableParameterValueProvider(params)); - } - /** - * generates a new AST made by cloning of `patternModel` and by substitution of parameters by values in `params` - * @param factory TODO - * @param valueType - the expected type of returned items - * @param params - the substitution parameters - * @return one generated element - */ - public T substituteSingle(Factory factory, Class valueType, ParameterValueProvider params) { - return new DefaultGenerator(factory).setAddGeneratedBy(isAddGeneratedBy()).generateTarget(modelValueResolver, params, valueType); - } - /** - * generates a new AST made by cloning of `patternModel` and by substitution of parameters by values in `params` + * Main method to generate a new AST made from substituting of parameters by values in `params` * @param factory TODO * @param valueType - the expected type of returned items * @param params - the substitution parameters * @return List of generated elements */ + public List substitute(Factory factory, Class valueType, ParameterValueProvider params) { + return new DefaultGenerator(factory).setAddGeneratedBy(isAddGeneratedBy()).generateTargets(modelValueResolver, params, valueType); + } + + /** Utility method that provides the same feature as {@link #substitute(Factory, Class, ParameterValueProvider)}, but with a Map as third parameter */ public List substituteList(Factory factory, Class valueType, Map params) { - return substituteList(factory, valueType, new UnmodifiableParameterValueProvider(params)); + return substitute(factory, valueType, new UnmodifiableParameterValueProvider(params)); } - /** - * generates a new AST made by cloning of `patternModel` and by substitution of parameters by values in `params` - * @param factory TODO - * @param valueType - the expected type of returned items - * @param params - the substitution parameters - * @return List of generated elements - */ - public List substituteList(Factory factory, Class valueType, ParameterValueProvider params) { - return new DefaultGenerator(factory).setAddGeneratedBy(isAddGeneratedBy()).generateTargets(modelValueResolver, params, valueType); + + /** Utility method that provides the same feature as {@link #substitute(Factory, Class, ParameterValueProvider)}, but returns a single element, and uses a map as parameter */ + public T substituteSingle(Factory factory, Class valueType, Map params) { + return substituteSingle(factory, valueType, new UnmodifiableParameterValueProvider(params)); } + /** Utility method that provides the same feature as {@link #substitute(Factory, Class, ParameterValueProvider)}, but returns a single element */ + public T substituteSingle(Factory factory, Class valueType, ParameterValueProvider params) { + return new DefaultGenerator(factory).setAddGeneratedBy(isAddGeneratedBy()).generateSingleTarget(modelValueResolver, params, valueType); + } /** * Generates type with qualified name `typeQualifiedName` using this {@link Pattern} and provided `params`. @@ -175,7 +158,7 @@ public > T createType(CtTypeReference newTypeRef, Map> T createType(CtPackage ownerPackage, String typeSimpleName, Map params) { @SuppressWarnings({ "rawtypes" }) - List types = substituteList(ownerPackage.getFactory(), CtType.class, new UnmodifiableParameterValueProvider(params, + List types = substitute(ownerPackage.getFactory(), CtType.class, new UnmodifiableParameterValueProvider(params, PatternBuilder.TARGET_TYPE, ownerPackage.getFactory().Type().createReference(getQualifiedName(ownerPackage, typeSimpleName)))); T result = null; for (CtType type : types) { @@ -197,7 +180,7 @@ public > T createType(CtPackage ownerPackage, String typeSim * @return List of generated elements */ public List applyToType(CtType targetType, Class valueType, Map params) { - List results = substituteList(targetType.getFactory(), valueType, new UnmodifiableParameterValueProvider(params, PatternBuilder.TARGET_TYPE, targetType.getReference())); + List results = substitute(targetType.getFactory(), valueType, new UnmodifiableParameterValueProvider(params, PatternBuilder.TARGET_TYPE, targetType.getReference())); for (T result : results) { if (result instanceof CtTypeMember) { targetType.addTypeMember((CtTypeMember) result); diff --git a/src/main/java/spoon/pattern/node/ElementNode.java b/src/main/java/spoon/pattern/node/ElementNode.java index d1b931e9ad0..12b00f12f8f 100644 --- a/src/main/java/spoon/pattern/node/ElementNode.java +++ b/src/main/java/spoon/pattern/node/ElementNode.java @@ -286,7 +286,7 @@ protected void generateSingleNodeAttributes(Generator generator, CtElement clone Metamodel.Field mmField = e.getKey(); switch (mmField.getContainerKind()) { case SINGLE: - mmField.setValue(clone, generator.generateTarget(e.getValue(), parameters, mmField.getValueClass())); + mmField.setValue(clone, generator.generateSingleTarget(e.getValue(), parameters, mmField.getValueClass())); break; case LIST: mmField.setValue(clone, generator.generateTargets(e.getValue(), parameters, mmField.getValueClass())); diff --git a/src/main/java/spoon/pattern/node/ForEachNode.java b/src/main/java/spoon/pattern/node/ForEachNode.java index 14241637271..eacd37fc4e6 100644 --- a/src/main/java/spoon/pattern/node/ForEachNode.java +++ b/src/main/java/spoon/pattern/node/ForEachNode.java @@ -73,7 +73,7 @@ public boolean replaceNode(RootNode oldNode, RootNode newNode) { @Override public void generateTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters) { for (Object parameterValue : generator.generateTargets(iterableParameter, parameters, Object.class)) { - generator.generateTargets(nestedModel, result, parameters.putValueToCopy(localParameter.getName(), parameterValue)); + generator.generateTargets(nestedModel, result, parameters.putValue(localParameter.getName(), parameterValue)); } } @@ -84,7 +84,7 @@ public Quantifier getMatchingStrategy() { @Override public TobeMatched matchAllWith(TobeMatched tobeMatched) { - TobeMatched localMatch = nestedModel.matchAllWith(tobeMatched.copyAndSetParams(tobeMatched.getParameters().createLocalParameterValueProvider())); + TobeMatched localMatch = nestedModel.matchAllWith(tobeMatched.copyAndSetParams(tobeMatched.getParameters().checkpoint())); if (localMatch == null) { //nested model did not matched. return null; @@ -92,7 +92,7 @@ public TobeMatched matchAllWith(TobeMatched tobeMatched) { //it matched. ParameterValueProvider newParameters = tobeMatched.getParameters(); //copy values of local parameters - for (Map.Entry e : localMatch.getParameters().asLocalMap().entrySet()) { + for (Map.Entry e : localMatch.getParameters().getModifiedParameters().entrySet()) { String name = e.getKey(); Object value = e.getValue(); if (name.equals(localParameter.getName())) { @@ -105,7 +105,7 @@ public TobeMatched matchAllWith(TobeMatched tobeMatched) { } } else { //it is new global parameter value. Just set it - newParameters = newParameters.putValueToCopy(name, value); + newParameters = newParameters.putValue(name, value); } } //all local parameters were applied to newParameters. We can use newParameters as result of this iteration for next iteration @@ -150,7 +150,7 @@ public void generateInlineTargets(Generator generator, ResultHolder resul Factory f = generator.getFactory(); CtForEach forEach = f.Core().createForEach(); forEach.setVariable(f.Code().createLocalVariable(f.Type().objectType(), localParameter.getName(), null)); - forEach.setExpression(generator.generateTarget(iterableParameter, parameters, CtExpression.class)); + forEach.setExpression(generator.generateSingleTarget(iterableParameter, parameters, CtExpression.class)); CtBlock body = f.createBlock(); body.setStatements(generator.generateTargets(nestedModel, parameters, CtStatement.class)); forEach.setBody(body); diff --git a/src/main/java/spoon/pattern/node/MapEntryNode.java b/src/main/java/spoon/pattern/node/MapEntryNode.java index 7e93c096ba1..193bd198622 100644 --- a/src/main/java/spoon/pattern/node/MapEntryNode.java +++ b/src/main/java/spoon/pattern/node/MapEntryNode.java @@ -106,8 +106,8 @@ public CtElement setValue(CtElement value) { @Override public void generateTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters) { - String entryKey = generator.generateTarget(key, parameters, String.class); - CtElement entryValue = generator.generateTarget(value, parameters, CtElement.class); + String entryKey = generator.generateSingleTarget(key, parameters, String.class); + CtElement entryValue = generator.generateSingleTarget(value, parameters, CtElement.class); if (entryKey != null && entryValue != null) { result.addResult((T) new Entry(entryKey, entryValue)); } diff --git a/src/main/java/spoon/pattern/node/SwitchNode.java b/src/main/java/spoon/pattern/node/SwitchNode.java index d835cd5e7e5..eedf4c16d35 100644 --- a/src/main/java/spoon/pattern/node/SwitchNode.java +++ b/src/main/java/spoon/pattern/node/SwitchNode.java @@ -188,7 +188,7 @@ private boolean isCaseSelected(Generator generator, ParameterValueProvider param if (vrOfExpression == null) { return true; } - Boolean value = generator.generateTarget(vrOfExpression, parameters, Boolean.class); + Boolean value = generator.generateSingleTarget(vrOfExpression, parameters, Boolean.class); return value == null ? false : value.booleanValue(); } @@ -203,7 +203,7 @@ public void generateInlineTargets(Generator generator, ResultHolder resul if (vrOfExpression != null) { //There is if expression CtIf ifStmt = cf.createIf(); - ifStmt.setCondition(generator.generateTarget(vrOfExpression, parameters, CtExpression.class)); + ifStmt.setCondition(generator.generateSingleTarget(vrOfExpression, parameters, CtExpression.class)); ifStmt.setThenStatement(block); result.addResult((T) ifStmt); } else { @@ -219,7 +219,7 @@ public void generateInlineTargets(Generator generator, ResultHolder resul CtStatement lastElse = null; CtIf lastIf = null; for (CaseNode caseNode : cases) { - CtStatement stmt = generator.generateTarget(caseNode, parameters, CtStatement.class); + CtStatement stmt = generator.generateSingleTarget(caseNode, parameters, CtStatement.class); if (stmt instanceof CtIf) { CtIf ifStmt = (CtIf) stmt; if (lastIf == null) { diff --git a/src/main/java/spoon/pattern/parameter/MapParameterInfo.java b/src/main/java/spoon/pattern/parameter/MapParameterInfo.java index b2332956cb1..fd6cf705516 100644 --- a/src/main/java/spoon/pattern/parameter/MapParameterInfo.java +++ b/src/main/java/spoon/pattern/parameter/MapParameterInfo.java @@ -82,7 +82,7 @@ protected Object addValueAs(Object container, Function merger) { //it is already there return parameters; } - return parameters.putValueToCopy(newEntryKey, newEntryValue); + return parameters.putValue(newEntryKey, newEntryValue); } if (newValue instanceof Map) { Map newMap = (Map) newValue; @@ -95,7 +95,7 @@ protected Object addValueAs(Object container, Function merger) { } if (existingValue != newEntryValue) { //it is not there yet. Add it - parameters = parameters.putValueToCopy(newEntryKey, newEntryValue); + parameters = parameters.putValue(newEntryKey, newEntryValue); } //it is there, continue to check next entry } @@ -113,7 +113,7 @@ protected Object addValueAs(Object container, Function merger) { //it is already there. return parameters; } - return parameters.putValueToCopy(name, newValue); + return parameters.putValue(name, newValue); } @Override diff --git a/src/main/java/spoon/support/util/ParameterValueProvider.java b/src/main/java/spoon/support/util/ParameterValueProvider.java index 4926bd3789f..c8e6dad4a2a 100644 --- a/src/main/java/spoon/support/util/ParameterValueProvider.java +++ b/src/main/java/spoon/support/util/ParameterValueProvider.java @@ -32,17 +32,19 @@ public interface ParameterValueProvider { * @return true if there is defined some value for the parameter. null can be a value too */ boolean hasValue(String parameterName); + /** * @param parameterName the name of the parameter * @return a value of the parameter under the name `parameterNamer */ Object getValue(String parameterName); + /** * @param parameterName to be set parameter name * @param value the new value * @return copies this {@link ParameterValueProvider}, sets the new value there and returns that copy */ - ParameterValueProvider putValueToCopy(String parameterName, Object value); + ParameterValueProvider putValue(String parameterName, Object value); /** * @return underlying unmodifiable Map<String, Object> @@ -51,13 +53,14 @@ public interface ParameterValueProvider { /** * @return a new instance of {@link ParameterValueProvider}, which inherits all values from this {@link ParameterValueProvider} - * Any call of {@link #putValueToCopy(String, Object)} is remembered in local Map of parameters. - * At the end of process the {@link #asLocalMap()} can be used to return all the parameters which were changed + * Any call of {@link #putValue(String, Object)} is remembered in local Map of parameters. + * At the end of process the {@link #getModifiedParameters()} can be used to return all the parameters which were changed * after local {@link ParameterValueProvider} was created */ - ParameterValueProvider createLocalParameterValueProvider(); + ParameterValueProvider checkpoint(); + /** - * @return {@link Map} with all modified parameters after {@link #createLocalParameterValueProvider()} has been called + * @return the modified parameters since last call to {@link #checkpoint()} */ - Map asLocalMap(); + Map getModifiedParameters(); } diff --git a/src/main/java/spoon/support/util/UnmodifiableParameterValueProvider.java b/src/main/java/spoon/support/util/UnmodifiableParameterValueProvider.java index 0277da43ce1..e299206f64a 100644 --- a/src/main/java/spoon/support/util/UnmodifiableParameterValueProvider.java +++ b/src/main/java/spoon/support/util/UnmodifiableParameterValueProvider.java @@ -64,7 +64,7 @@ public UnmodifiableParameterValueProvider() { } @Override - public UnmodifiableParameterValueProvider createLocalParameterValueProvider() { + public UnmodifiableParameterValueProvider checkpoint() { return new UnmodifiableParameterValueProvider(this, Collections.emptyMap()); } @@ -89,7 +89,7 @@ public Object getValue(String parameterName) { } @Override - public ParameterValueProvider putValueToCopy(String parameterName, Object value) { + public ParameterValueProvider putValue(String parameterName, Object value) { return new UnmodifiableParameterValueProvider(parent, map, parameterName, value); } @@ -127,7 +127,7 @@ public Map asMap() { } @Override - public Map asLocalMap() { + public Map getModifiedParameters() { return map; } diff --git a/src/main/java/spoon/template/TemplateBuilder.java b/src/main/java/spoon/template/TemplateBuilder.java index c16bf1702e1..08a614041de 100644 --- a/src/main/java/spoon/template/TemplateBuilder.java +++ b/src/main/java/spoon/template/TemplateBuilder.java @@ -31,6 +31,7 @@ import spoon.reflect.factory.Factory; import spoon.reflect.reference.CtTypeReference; import spoon.support.template.Parameters; +import spoon.support.util.UnmodifiableParameterValueProvider; /** * Internal class used to provide pattern-based implementation of Template and TemplateMatcher @@ -157,6 +158,6 @@ public T substituteSingle(CtType targetType, Class i * @return List of substituted elements */ public List substituteList(Factory factory, CtType targetType, Class itemType) { - return build().substituteList(factory, itemType, getTemplateParameters(targetType)); + return build().substitute(factory, itemType, new UnmodifiableParameterValueProvider(getTemplateParameters(targetType))); } } diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index 60982200a84..4cf041deea5 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -1,6 +1,5 @@ package spoon.test.template; -import org.hamcrest.core.Is; import org.junit.Test; import spoon.Launcher; import spoon.OutputType; @@ -37,6 +36,7 @@ import spoon.reflect.visitor.filter.TypeFilter; import spoon.support.compiler.FileSystemFile; import spoon.support.util.ParameterValueProvider; +import spoon.support.util.UnmodifiableParameterValueProvider; import spoon.test.template.testclasses.LoggerModel; import spoon.test.template.testclasses.ToBeMatched; import spoon.test.template.testclasses.logger.Logger; @@ -238,20 +238,20 @@ public void testGenerateMultiValues() throws Exception { Factory factory = ctClass.getFactory(); Pattern pattern = MatchMultiple.createPattern(null, null, null); - Map params = new HashMap<>(); + ParameterValueProvider params = new UnmodifiableParameterValueProvider(); // created in "MatchMultiple.createPattern",matching a literal "something" // so "something" si replaced by "does it work?" - params.put("printedValue", "does it work?"); + params = params.putValue("printedValue", "does it work?"); List statementsToBeAdded = null; //statementsToBeAdded = ctClass.getMethodsByName("testMatch1").get(0).getBody().getStatements().subList(0, 3); // we don't use this in order not to mix the matching and the transformation statementsToBeAdded = Arrays.asList(new CtStatement[] {factory.createCodeSnippetStatement("int foo = 0"), factory.createCodeSnippetStatement("foo++")}); // created in "MatchMultiple.createPattern",matching a method "statements" - params.put("statements", statementsToBeAdded); + params = params.putValue("statements", statementsToBeAdded); - List generated = pattern.substituteList(factory, CtStatement.class, params); + List generated = pattern.substitute(factory, CtStatement.class, params); assertEquals(Arrays.asList( //these statements comes from `statements` parameter value "int foo = 0", diff --git a/src/test/java/spoon/test/template/core/ParameterInfoTest.java b/src/test/java/spoon/test/template/core/ParameterInfoTest.java index 0b80cc2e541..65f070787ea 100644 --- a/src/test/java/spoon/test/template/core/ParameterInfoTest.java +++ b/src/test/java/spoon/test/template/core/ParameterInfoTest.java @@ -75,7 +75,7 @@ public void testSingleValueParameterByNameIntoEmptyContainer() { public void testSingleValueParameterByNameWhenAlreadyExists() { ParameterInfo namedParam = new MapParameterInfo("year"); {//adding value into container, which already contains that value changes nothing and returns origin container - ParameterValueProvider oldContainer = new UnmodifiableParameterValueProvider().putValueToCopy("year", 2018); + ParameterValueProvider oldContainer = new UnmodifiableParameterValueProvider().putValue("year", 2018); assertEquals(map().put("year", 2018), oldContainer.asMap()); //it returned the same container assertSame(oldContainer, namedParam.addValueAs(oldContainer, 2018)); @@ -86,7 +86,7 @@ public void testSingleValueParameterByNameWhenAlreadyExists() { public void testSingleValueParameterByNameWhenDifferentExists() { ParameterInfo namedParam = new MapParameterInfo("year"); {//adding a value into container, which already contains a different value returns null - no match - ParameterValueProvider oldContainer = new UnmodifiableParameterValueProvider().putValueToCopy("year", 2018); + ParameterValueProvider oldContainer = new UnmodifiableParameterValueProvider().putValue("year", 2018); assertNull(namedParam.addValueAs(oldContainer, 2111)); assertNull(namedParam.addValueAs(oldContainer, 0)); assertNull(namedParam.addValueAs(oldContainer, null)); @@ -100,7 +100,7 @@ public void testOptionalSingleValueParameterByName() { .setMinOccurences(0); {//adding null value into an container with minCount == 0, returns unchanged container. //because minCount == 0 means that value is optional - ParameterValueProvider container = new UnmodifiableParameterValueProvider().putValueToCopy("a", "b"); + ParameterValueProvider container = new UnmodifiableParameterValueProvider().putValue("a", "b"); assertSame(container, namedParam.addValueAs(container, null)); assertEquals(map().put("a", "b"), container.asMap()); } @@ -112,7 +112,7 @@ public void testMandatorySingleValueParameterByName() { ParameterInfo namedParam = new MapParameterInfo("year") .setMinOccurences(1); { - ParameterValueProvider container = new UnmodifiableParameterValueProvider().putValueToCopy("a", "b"); + ParameterValueProvider container = new UnmodifiableParameterValueProvider().putValue("a", "b"); assertNull(namedParam.addValueAs(container, null)); assertEquals(map().put("a", "b"), container.asMap()); } @@ -129,7 +129,7 @@ public void testSingleValueParameterByNameConditionalMatcher() { assertNull(namedParam.addValueAs(null, 1000)); assertNull(namedParam.addValueAs(null, "3000")); //even matching value is STILL not accepted when there is already a different value - assertNull(namedParam.addValueAs(new UnmodifiableParameterValueProvider().putValueToCopy("year", 3000), 2018)); + assertNull(namedParam.addValueAs(new UnmodifiableParameterValueProvider().putValue("year", 3000), 2018)); } @Test @@ -157,7 +157,7 @@ public void testListParameterByNameIntoEmptyContainer() { public void testListParameterByNameIntoEmptyContainerWithEmptyList() { Consumer check = (namedParam) -> {//adding value into container, which already contains a empty list, creates a new container with List which contains that value - ParameterValueProvider empty = new UnmodifiableParameterValueProvider().putValueToCopy("year", Collections.emptyList()); + ParameterValueProvider empty = new UnmodifiableParameterValueProvider().putValue("year", Collections.emptyList()); ParameterValueProvider val = namedParam.addValueAs(empty, 2018); //adding same value - adds the second value again @@ -193,11 +193,11 @@ public void testMergeOnDifferentValueTypeContainers() { assertNull(parameter.addValueAs(params, null)); }; ParameterValueProvider empty = new UnmodifiableParameterValueProvider(); - checker.accept(new MapParameterInfo("year"), empty.putValueToCopy("year", "x")); - checker.accept(new ListParameterInfo(0, new MapParameterInfo("year")), empty.putValueToCopy("year", Collections.singletonList("x"))); - checker.accept(new ListParameterInfo(1, new MapParameterInfo("year")), empty.putValueToCopy("year", Arrays.asList("zz","x"))); - checker.accept(new MapParameterInfo("key", new ListParameterInfo(1, new MapParameterInfo("year"))), empty.putValueToCopy("year", Arrays.asList("zz",empty.putValueToCopy("key", "x")))); - checker.accept(new MapParameterInfo("key", new MapParameterInfo("year")), empty.putValueToCopy("year", empty.putValueToCopy("key", "x"))); + checker.accept(new MapParameterInfo("year"), empty.putValue("year", "x")); + checker.accept(new ListParameterInfo(0, new MapParameterInfo("year")), empty.putValue("year", Collections.singletonList("x"))); + checker.accept(new ListParameterInfo(1, new MapParameterInfo("year")), empty.putValue("year", Arrays.asList("zz","x"))); + checker.accept(new MapParameterInfo("key", new ListParameterInfo(1, new MapParameterInfo("year"))), empty.putValue("year", Arrays.asList("zz",empty.putValue("key", "x")))); + checker.accept(new MapParameterInfo("key", new MapParameterInfo("year")), empty.putValue("year", empty.putValue("key", "x"))); } @Test @@ -264,7 +264,7 @@ public void testMapEntryInParameterByName() { final ParameterValueProvider val = namedParam.addValueAs(empty, entry("year", 2018)); assertNotNull(val); - assertEquals(map().put("map", new UnmodifiableParameterValueProvider().putValueToCopy("year", 2018)), val.asMap()); + assertEquals(map().put("map", new UnmodifiableParameterValueProvider().putValue("year", 2018)), val.asMap()); //adding null entry changes nothing assertSame(val, namedParam.addValueAs(val, null)); @@ -276,19 +276,19 @@ public void testMapEntryInParameterByName() { ParameterValueProvider val2 = namedParam.addValueAs(val, entry("age", "best")); assertNotNull(val2); assertEquals(map().put("map", new UnmodifiableParameterValueProvider() - .putValueToCopy("year", 2018) - .putValueToCopy("age", "best")), val2.asMap()); + .putValue("year", 2018) + .putValue("age", "best")), val2.asMap()); //after all the once returned val is still the same - unmodified - assertEquals(map().put("map", new UnmodifiableParameterValueProvider().putValueToCopy("year", 2018)), val.asMap()); + assertEquals(map().put("map", new UnmodifiableParameterValueProvider().putValue("year", 2018)), val.asMap()); }; checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider()); - checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider().putValueToCopy("map", null)); - checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider().putValueToCopy("map", Collections.emptyMap())); + checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider().putValue("map", null)); + checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider().putValue("map", Collections.emptyMap())); //the map container is detected automatically from the type of value - checker.accept(new MapParameterInfo("map"), new UnmodifiableParameterValueProvider().putValueToCopy("map", Collections.emptyMap())); + checker.accept(new MapParameterInfo("map"), new UnmodifiableParameterValueProvider().putValue("map", Collections.emptyMap())); //the map container is detected automatically from the type of value - checker.accept(new MapParameterInfo("map"), new UnmodifiableParameterValueProvider().putValueToCopy("map", new UnmodifiableParameterValueProvider())); + checker.accept(new MapParameterInfo("map"), new UnmodifiableParameterValueProvider().putValue("map", new UnmodifiableParameterValueProvider())); } @Test public void testAddMapIntoParameterByName() { @@ -297,11 +297,11 @@ public void testAddMapIntoParameterByName() { ParameterValueProvider val = namedParam.addValueAs(empty, Collections.emptyMap()); assertEquals(map().put("map", new UnmodifiableParameterValueProvider()), val.asMap()); val = namedParam.addValueAs(empty, map().put("year", 2018)); - assertEquals(map().put("map", new UnmodifiableParameterValueProvider().putValueToCopy("year", 2018)), val.asMap()); + assertEquals(map().put("map", new UnmodifiableParameterValueProvider().putValue("year", 2018)), val.asMap()); val = namedParam.addValueAs(empty, map().put("year", 2018).put("age", 1111)); assertEquals(map().put("map", new UnmodifiableParameterValueProvider() - .putValueToCopy("year", 2018) - .putValueToCopy("age", 1111)), val.asMap()); + .putValue("year", 2018) + .putValue("age", 1111)), val.asMap()); //adding null entry changes nothing assertSame(val, namedParam.addValueAs(val, null)); @@ -311,12 +311,12 @@ public void testAddMapIntoParameterByName() { assertNull(namedParam.addValueAs(val, entry("year", 1111))); }; checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider()); - checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider().putValueToCopy("map", null)); - checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider().putValueToCopy("map", Collections.emptyMap())); + checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider().putValue("map", null)); + checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider().putValue("map", Collections.emptyMap())); //the map container is detected automatically from the type of value - checker.accept(new MapParameterInfo("map"), new UnmodifiableParameterValueProvider().putValueToCopy("map", Collections.emptyMap())); + checker.accept(new MapParameterInfo("map"), new UnmodifiableParameterValueProvider().putValue("map", Collections.emptyMap())); //the map container is detected automatically from the type of value - checker.accept(new MapParameterInfo("map"), new UnmodifiableParameterValueProvider().putValueToCopy("map", new UnmodifiableParameterValueProvider())); + checker.accept(new MapParameterInfo("map"), new UnmodifiableParameterValueProvider().putValue("map", new UnmodifiableParameterValueProvider())); //the map container is detected automatically from the type of new value checker.accept(new MapParameterInfo("map"), null); } @@ -336,12 +336,12 @@ public void testAddListIntoParameterByName() { assertSame(val, namedParam.addValueAs(val, null)); }; checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.LIST), new UnmodifiableParameterValueProvider()); - checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.LIST), new UnmodifiableParameterValueProvider().putValueToCopy("list", null)); - checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.LIST), new UnmodifiableParameterValueProvider().putValueToCopy("list", Collections.emptyList())); + checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.LIST), new UnmodifiableParameterValueProvider().putValue("list", null)); + checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.LIST), new UnmodifiableParameterValueProvider().putValue("list", Collections.emptyList())); //Set can be converted to List - checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.LIST), new UnmodifiableParameterValueProvider().putValueToCopy("list", Collections.emptySet())); + checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.LIST), new UnmodifiableParameterValueProvider().putValue("list", Collections.emptySet())); //the list container is detected automatically from the type of value - checker.accept(new MapParameterInfo("list"), new UnmodifiableParameterValueProvider().putValueToCopy("list", Collections.emptyList())); + checker.accept(new MapParameterInfo("list"), new UnmodifiableParameterValueProvider().putValue("list", Collections.emptyList())); //the list container is detected automatically from the type of new value checker.accept(new MapParameterInfo("list"), null); } @@ -366,12 +366,12 @@ public void testAddSetIntoParameterByName() { assertSame(val, namedParam.addValueAs(val, asSet(2018, 1111))); }; checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.SET), new UnmodifiableParameterValueProvider()); - checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.SET), new UnmodifiableParameterValueProvider().putValueToCopy("list", null)); - checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.SET), new UnmodifiableParameterValueProvider().putValueToCopy("list", Collections.emptySet())); + checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.SET), new UnmodifiableParameterValueProvider().putValue("list", null)); + checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.SET), new UnmodifiableParameterValueProvider().putValue("list", Collections.emptySet())); //The container kind has higher priority, so List will be converted to Set - checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.SET), new UnmodifiableParameterValueProvider().putValueToCopy("list", Collections.emptyList())); + checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.SET), new UnmodifiableParameterValueProvider().putValue("list", Collections.emptyList())); //the list container is detected automatically from the type of value - checker.accept(new MapParameterInfo("list"), new UnmodifiableParameterValueProvider().putValueToCopy("list", Collections.emptySet())); + checker.accept(new MapParameterInfo("list"), new UnmodifiableParameterValueProvider().putValue("list", Collections.emptySet())); //the list container is detected automatically from the type of new value checker.accept(new MapParameterInfo("list"), null); } @@ -379,7 +379,7 @@ public void testAddSetIntoParameterByName() { public void testFailOnUnpectedContainer() { ParameterInfo namedParam = new MapParameterInfo("year").setContainerKind(ContainerKind.LIST); try { - namedParam.addValueAs(new UnmodifiableParameterValueProvider().putValueToCopy("year", "unexpected"), 1); + namedParam.addValueAs(new UnmodifiableParameterValueProvider().putValue("year", "unexpected"), 1); fail(); } catch (Exception e) { //OK From c00b65b084e397dd4b5df5fffc0d4042f539eaea Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Sun, 22 Apr 2018 22:11:59 +0200 Subject: [PATCH 082/131] up --- .../java/spoon/pattern/ParametersBuilder.java | 4 + .../java/spoon/pattern/PatternBuilder.java | 2 +- .../java/spoon/test/template/PatternTest.java | 388 ++++++++++-------- .../testclasses/match/MatchModifiers.java | 14 - .../testclasses/match/MatchMultiple3.java | 13 - .../match/MatchWithParameterCondition.java | 12 - .../match/MatchWithParameterType.java | 12 - .../testclasses/replace/OldPattern.java | 2 +- 8 files changed, 227 insertions(+), 220 deletions(-) diff --git a/src/main/java/spoon/pattern/ParametersBuilder.java b/src/main/java/spoon/pattern/ParametersBuilder.java index f2951166052..cf5098a89ba 100644 --- a/src/main/java/spoon/pattern/ParametersBuilder.java +++ b/src/main/java/spoon/pattern/ParametersBuilder.java @@ -512,6 +512,10 @@ public ParametersBuilder byNamedElement(String simpleName) { * * Can be used to match any method call for instance. * + * In some cases, the selected object is actually the parent of the reference (eg the invocation). + * This is implemented in {@link ParametersBuilder#getSubstitutedNodeOfElement(ParameterInfo, CtElement)} + * + * * @param simpleName simple name of {@link CtReference} * @return {@link ParametersBuilder} to support fluent API */ diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index 274716a2084..e0b6a201a8a 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -64,7 +64,7 @@ /** * The master class to create a {@link Pattern} instance. * - * Based on a fluent API, see tests and documentation ('pattern.md') + * Based on a fluent API, see tests and documentation ('pattern.md'). */ public class PatternBuilder { diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index 4cf041deea5..0271043fe71 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -60,6 +60,7 @@ import java.io.File; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -105,7 +106,7 @@ public void testMatchForeach() throws Exception { assertEquals(2, matches.size()); { Match match = matches.get(0); - assertEquals(Arrays.asList("java.lang.System.out.println(value)"), listToListOfStrings(match.getMatchingElements())); + assertEquals(Arrays.asList("java.lang.System.out.println(value)"), toListOfStrings(match.getMatchingElements())); //FIX IT // assertEquals(Arrays.asList(""), listToListOfStrings((List) match.getParameters().getValue("values"))); } @@ -115,12 +116,12 @@ public void testMatchForeach() throws Exception { "java.lang.System.out.println(\"a\")", "java.lang.System.out.println(\"Xxxx\")", "java.lang.System.out.println(((java.lang.String) (null)))", - "java.lang.System.out.println(java.lang.Long.class.toString())"), listToListOfStrings(match.getMatchingElements())); + "java.lang.System.out.println(java.lang.Long.class.toString())"), toListOfStrings(match.getMatchingElements())); assertEquals(Arrays.asList( "\"a\"", "\"Xxxx\"", "((java.lang.String) (null))", - "java.lang.Long.class.toString()"), listToListOfStrings((List) match.getParameters().getValue("values"))); + "java.lang.Long.class.toString()"), toListOfStrings((List) match.getParameters().getValue("values"))); } } @@ -144,7 +145,7 @@ public void testMatchForeachWithOuterSubstitution() throws Exception { assertEquals(3, matches.size()); { Match match = matches.get(0); - assertEquals(Arrays.asList("int var = 0"), listToListOfStrings(match.getMatchingElements())); + assertEquals(Arrays.asList("int var = 0"), toListOfStrings(match.getMatchingElements())); //FIX IT // assertEquals(Arrays.asList(""), listToListOfStrings((List) match.getParameters().getValue("values"))); } @@ -155,7 +156,7 @@ public void testMatchForeachWithOuterSubstitution() throws Exception { "java.lang.System.out.println(\"Xxxx\")", "cc++", "java.lang.System.out.println(((java.lang.String) (null)))", - "cc++"), listToListOfStrings(match.getMatchingElements())); + "cc++"), toListOfStrings(match.getMatchingElements())); // correctly matching the outer parameter assertEquals("cc", match.getParameters().getValue("varName")); @@ -165,7 +166,7 @@ public void testMatchForeachWithOuterSubstitution() throws Exception { assertEquals(Arrays.asList( "int dd = 0", "java.lang.System.out.println(java.lang.Long.class.toString())", - "dd++"), listToListOfStrings(match.getMatchingElements())); + "dd++"), toListOfStrings(match.getMatchingElements())); // correctly matching the outer parameter assertEquals("dd", match.getParameters().getValue("varName")); @@ -198,25 +199,25 @@ public void testMatchIfElse() throws Exception { assertEquals(5, matches.size()); { Match match = matches.get(0); - assertEquals(Arrays.asList("java.lang.System.out.println(\"a\")"), listToListOfStrings(match.getMatchingElements())); + assertEquals(Arrays.asList("java.lang.System.out.println(\"a\")"), toListOfStrings(match.getMatchingElements())); assertEquals(true, match.getParameters().getValue("option")); assertEquals("\"a\"", match.getParameters().getValue("value").toString()); } { Match match = matches.get(1); - assertEquals(Arrays.asList("java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings(match.getMatchingElements())); + assertEquals(Arrays.asList("java.lang.System.out.println(\"Xxxx\")"), toListOfStrings(match.getMatchingElements())); assertEquals(true, match.getParameters().getValue("option")); assertEquals("\"Xxxx\"", match.getParameters().getValue("value").toString()); } { Match match = matches.get(2); - assertEquals(Arrays.asList("java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings(match.getMatchingElements())); + assertEquals(Arrays.asList("java.lang.System.out.println(((java.lang.String) (null)))"), toListOfStrings(match.getMatchingElements())); assertEquals(true, match.getParameters().getValue("option")); assertEquals("((java.lang.String) (null))", match.getParameters().getValue("value").toString()); } { Match match = matches.get(4); - assertEquals(Arrays.asList("java.lang.System.out.println(3.14)"), listToListOfStrings(match.getMatchingElements())); + assertEquals(Arrays.asList("java.lang.System.out.println(3.14)"), toListOfStrings(match.getMatchingElements())); assertEquals(false, match.getParameters().getValue("option")); assertEquals("3.14", match.getParameters().getValue("value").toString()); } @@ -230,8 +231,6 @@ public void testGenerateMultiValues() throws Exception { // here, in particular, we test method "substituteList" - // REVIEW: - // Here, I have a concern with the API: we have 4 public methods `substitute*`. It is hard for client to quickly grasp the differences. Is it possible to have a single `substituteMethod?` // setup of the test CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); @@ -280,7 +279,7 @@ public void testMatchGreedyMultiValueUnlimited() throws Exception { "java.lang.System.out.println(i)", "java.lang.System.out.println(\"Xxxx\")", "java.lang.System.out.println(((java.lang.String) (null)))", - "java.lang.System.out.println(\"last one\")"), listToListOfStrings(match.getMatchingElements())); + "java.lang.System.out.println(\"last one\")"), toListOfStrings(match.getMatchingElements())); //check all statements excluding last are stored as value of "statements" parameter assertEquals(Arrays.asList( @@ -288,7 +287,7 @@ public void testMatchGreedyMultiValueUnlimited() throws Exception { "i++", "java.lang.System.out.println(i)", "java.lang.System.out.println(\"Xxxx\")", - "java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings((List) match.getParameters().getValue("statements"))); + "java.lang.System.out.println(((java.lang.String) (null)))"), toListOfStrings((List) match.getParameters().getValue("statements"))); //last statement is matched by last template, which saves printed value assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); @@ -315,13 +314,13 @@ public void testMatchGreedyMultiValueMaxCountLimit() throws Exception { "i++", "java.lang.System.out.println(i)", "java.lang.System.out.println(\"Xxxx\")" - ), listToListOfStrings(match.getMatchingElements())); + ), toListOfStrings(match.getMatchingElements())); //check 3 statements are stored as value of "statements" parameter assertEquals(Arrays.asList( "int i = 0", "i++", - "java.lang.System.out.println(i)"), listToListOfStrings((List) match.getParameters().getValue("statements"))); + "java.lang.System.out.println(i)"), toListOfStrings((List) match.getParameters().getValue("statements"))); //4th statement is matched by last template, which saves printed value assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); assertEquals("\"Xxxx\"", match.getParameters().getValue("printedValue").toString()); @@ -331,11 +330,11 @@ public void testMatchGreedyMultiValueMaxCountLimit() throws Exception { //check remaining next 2 statements are matched assertEquals(Arrays.asList( "java.lang.System.out.println(((java.lang.String) (null)))", - "java.lang.System.out.println(\"last one\")"), listToListOfStrings(match.getMatchingElements())); + "java.lang.System.out.println(\"last one\")"), toListOfStrings(match.getMatchingElements())); //check all statements excluding last are stored as value of "statements" parameter assertEquals(Arrays.asList( - "java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings((List) match.getParameters().getValue("statements"))); + "java.lang.System.out.println(((java.lang.String) (null)))"), toListOfStrings((List) match.getParameters().getValue("statements"))); //last statement is matched by last template, which saves printed value assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); assertEquals("\"last one\"", match.getParameters().getValue("printedValue").toString()); @@ -345,8 +344,7 @@ public void testMatchGreedyMultiValueMaxCountLimit() throws Exception { @Test public void testMatchReluctantMultivalue() throws Exception { - //contract: multivalue parameter can match multiple nodes into list of parameter values. - //contract: reluctant matches only minimal amount + //contract: reluctant matching (Quantifier.RELUCTANT) matches only the minimal amount of time CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); Pattern pattern = MatchMultiple.createPattern(Quantifier.RELUCTANT, null, null); @@ -361,13 +359,13 @@ public void testMatchReluctantMultivalue() throws Exception { "int i = 0", "i++", "java.lang.System.out.println(i)", //this is println(int), but last temple matches println(String) - it is question if it is wanted or not ... - "java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings(match.getMatchingElements())); + "java.lang.System.out.println(\"Xxxx\")"), toListOfStrings(match.getMatchingElements())); //check all statements excluding last are stored as value of "statements" parameter assertEquals(Arrays.asList( "int i = 0", "i++", - "java.lang.System.out.println(i)"), listToListOfStrings((List) match.getParameters().getValue("statements"))); + "java.lang.System.out.println(i)"), toListOfStrings((List) match.getParameters().getValue("statements"))); //last statement is matched by last template, which saves printed value assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); assertEquals("\"Xxxx\"", match.getParameters().getValue("printedValue").toString()); @@ -376,10 +374,10 @@ public void testMatchReluctantMultivalue() throws Exception { Match match = matches.get(1); //check all statements are matched assertEquals(Arrays.asList( - "java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings(match.getMatchingElements())); + "java.lang.System.out.println(((java.lang.String) (null)))"), toListOfStrings(match.getMatchingElements())); //check all statements excluding last are stored as value of "statements" parameter - assertEquals(Arrays.asList(), listToListOfStrings((List) match.getParameters().getValue("statements"))); + assertEquals(Arrays.asList(), toListOfStrings((List) match.getParameters().getValue("statements"))); //last statement is matched by last template, which saves printed value assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); assertEquals("((java.lang.String) (null))", match.getParameters().getValue("printedValue").toString()); @@ -388,10 +386,10 @@ public void testMatchReluctantMultivalue() throws Exception { Match match = matches.get(2); //check all statements are matched assertEquals(Arrays.asList( - "java.lang.System.out.println(\"last one\")"), listToListOfStrings(match.getMatchingElements())); + "java.lang.System.out.println(\"last one\")"), toListOfStrings(match.getMatchingElements())); //check all statements excluding last are stored as value of "statements" parameter - assertEquals(Arrays.asList(), listToListOfStrings((List) match.getParameters().getValue("statements"))); + assertEquals(Arrays.asList(), toListOfStrings((List) match.getParameters().getValue("statements"))); //last statement is matched by last template, which saves printed value assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); assertEquals("\"last one\"", match.getParameters().getValue("printedValue").toString()); @@ -399,8 +397,7 @@ public void testMatchReluctantMultivalue() throws Exception { } @Test public void testMatchReluctantMultivalueMinCount1() throws Exception { - //contract: multivalue parameter can match multiple nodes into list of parameter values. - //contract: reluctant matches only at least 1 node in this case + //contract: one can do reluctant matches with a minCount of 1 node CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); Pattern pattern = MatchMultiple.createPattern(Quantifier.RELUCTANT, 1, null); @@ -415,13 +412,13 @@ public void testMatchReluctantMultivalueMinCount1() throws Exception { "int i = 0", "i++", "java.lang.System.out.println(i)", //this is println(int), but last temple matches println(String) - it is question if it is wanted or not ... - "java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings(match.getMatchingElements())); + "java.lang.System.out.println(\"Xxxx\")"), toListOfStrings(match.getMatchingElements())); //check all statements excluding last are stored as value of "statements" parameter assertEquals(Arrays.asList( "int i = 0", "i++", - "java.lang.System.out.println(i)"), listToListOfStrings((List) match.getParameters().getValue("statements"))); + "java.lang.System.out.println(i)"), toListOfStrings((List) match.getParameters().getValue("statements"))); //last statement is matched by last template, which saves printed value assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); assertEquals("\"Xxxx\"", match.getParameters().getValue("printedValue").toString()); @@ -431,11 +428,11 @@ public void testMatchReluctantMultivalueMinCount1() throws Exception { //check all statements are matched assertEquals(Arrays.asList( "java.lang.System.out.println(((java.lang.String) (null)))", - "java.lang.System.out.println(\"last one\")"), listToListOfStrings(match.getMatchingElements())); + "java.lang.System.out.println(\"last one\")"), toListOfStrings(match.getMatchingElements())); //check all statements excluding last are stored as value of "statements" parameter assertEquals(Arrays.asList( - "java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings((List) match.getParameters().getValue("statements"))); + "java.lang.System.out.println(((java.lang.String) (null)))"), toListOfStrings((List) match.getParameters().getValue("statements"))); //last statement is matched by last template, which saves printed value assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); assertEquals("\"last one\"", match.getParameters().getValue("printedValue").toString()); @@ -443,8 +440,7 @@ public void testMatchReluctantMultivalueMinCount1() throws Exception { } @Test public void testMatchReluctantMultivalueExactly2() throws Exception { - //contract: multivalue parameter can match multiple nodes into list of parameter values. - //contract: reluctant matches min 2 and max 2 nodes in this case + //contract: one can do reluctant matches min 2 nodes and max 2 nodes CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); Pattern pattern = MatchMultiple.createPattern(Quantifier.RELUCTANT, 2, 2); @@ -458,12 +454,12 @@ public void testMatchReluctantMultivalueExactly2() throws Exception { assertEquals(Arrays.asList( "i++", "java.lang.System.out.println(i)", //this is println(int), but last temple matches println(String) - it is question if it is wanted or not ... - "java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings(match.getMatchingElements())); + "java.lang.System.out.println(\"Xxxx\")"), toListOfStrings(match.getMatchingElements())); //check 2 statements excluding last are stored as value of "statements" parameter assertEquals(Arrays.asList( "i++", - "java.lang.System.out.println(i)"), listToListOfStrings((List) match.getParameters().getValue("statements"))); + "java.lang.System.out.println(i)"), toListOfStrings((List) match.getParameters().getValue("statements"))); //last statement is matched by last template, which saves printed value assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); assertEquals("\"Xxxx\"", match.getParameters().getValue("printedValue").toString()); @@ -472,8 +468,7 @@ public void testMatchReluctantMultivalueExactly2() throws Exception { @Test public void testMatchPossesiveMultiValueUnlimited() throws Exception { - //contract: multivalue parameter can match multiple nodes into list of parameter values. - //contract: possessive matching eats everything and never returns back + //contract: possessive matching eats everything and never returns anything CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); Pattern pattern = MatchMultiple.createPattern(Quantifier.POSSESSIVE, null, null); @@ -498,35 +493,41 @@ public void testMatchPossesiveMultiValueMaxCount4() throws Exception { "i++", "java.lang.System.out.println(i)", "java.lang.System.out.println(\"Xxxx\")", - "java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings(match.getMatchingElements())); + "java.lang.System.out.println(((java.lang.String) (null)))"), toListOfStrings(match.getMatchingElements())); //check 4 statements excluding last are stored as value of "statements" parameter assertEquals(Arrays.asList( "int i = 0", "i++", "java.lang.System.out.println(i)", - "java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings((List) match.getParameters().getValue("statements"))); + "java.lang.System.out.println(\"Xxxx\")"), toListOfStrings((List) match.getParameters().getValue("statements"))); //last statement is matched by last template, which saves printed value assertTrue(match.getParameters().getValue("printedValue") instanceof CtLiteral); assertEquals("((java.lang.String) (null))", match.getParameters().getValue("printedValue").toString()); } + @Test public void testMatchPossesiveMultiValueMinCount() throws Exception { - //contract: check possessive matching with min count limit and GREEDY back off + //contract: support for possessive matching with min count limit mixed with GREEDY back off CtType ctClass = ModelUtils.buildClass(MatchMultiple3.class); for (int i = 0; i < 7; i++) { final int count = i; - Pattern pattern = MatchMultiple3.createPattern(ctClass.getFactory(), pb -> { - pb.parameter("statements1").setMatchingStrategy(Quantifier.GREEDY); - pb.parameter("statements2").setMatchingStrategy(Quantifier.POSSESSIVE).setMinOccurence(count).setMaxOccurence(count); - }); + CtType type = ctClass.getFactory().Type().get(MatchMultiple3.class); + Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("matcher1").getPatternElements()) + .configureTemplateParameters() + .configureParameters(pb -> { + pb.parameter("statements1").setContainerKind(ContainerKind.LIST).setMatchingStrategy(Quantifier.GREEDY); + pb.parameter("statements2").setContainerKind(ContainerKind.LIST).setMatchingStrategy(Quantifier.POSSESSIVE).setMinOccurence(count).setMaxOccurence(count); + pb.parameter("printedValue").byFilter((CtLiteral literal) -> "something".equals(literal.getValue())); + }) + .build(); List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0).getBody()); if (count < 6) { //the last template has nothing to match -> no match assertEquals("count="+count, 1, matches.size()); - assertEquals("count="+count, 5-count, getSize(matches.get(0).getParameters().getValue("statements1"))); - assertEquals("count="+count, count, getSize(matches.get(0).getParameters().getValue("statements2"))); + assertEquals("count="+count, 5-count, getCollectionSize(matches.get(0).getParameters().getValue("statements1"))); + assertEquals("count="+count, count, getCollectionSize(matches.get(0).getParameters().getValue("statements2"))); } else { //the possessive matcher eat too much. There is no target element for last `printedValue` variable assertEquals("count="+count, 0, matches.size()); @@ -536,7 +537,7 @@ public void testMatchPossesiveMultiValueMinCount() throws Exception { @Test public void testMatchPossesiveMultiValueMinCount2() throws Exception { - //contract: check possessive matching with min count limit and GREEDY back off + //contract: support for check possessive matching with min count limit and GREEDY back off CtType ctClass = ModelUtils.buildClass(MatchMultiple2.class); for (int i = 0; i < 7; i++) { final int count = i; @@ -550,9 +551,9 @@ public void testMatchPossesiveMultiValueMinCount2() throws Exception { if (count < 5) { //the last template has nothing to match -> no match assertEquals("count="+count, 1, matches.size()); - assertEquals("count="+count, 4-count, getSize(matches.get(0).getParameters().getValue("statements1"))); - assertEquals("count="+count, count, getSize(matches.get(0).getParameters().getValue("statements2"))); - assertEquals("count="+count, 2, getSize(matches.get(0).getParameters().getValue("printedValue"))); + assertEquals("count="+count, 4-count, getCollectionSize(matches.get(0).getParameters().getValue("statements1"))); + assertEquals("count="+count, count, getCollectionSize(matches.get(0).getParameters().getValue("statements2"))); + assertEquals("count="+count, 2, getCollectionSize(matches.get(0).getParameters().getValue("printedValue"))); } else { //the possessive matcher eat too much. There is no target element for last `printedValue` variable assertEquals("count="+count, 0, matches.size()); @@ -575,9 +576,9 @@ public void testMatchGreedyMultiValueMinCount2() throws Exception { if (count < 7) { //the last template has nothing to match -> no match assertEquals("count="+count, 1, matches.size()); - assertEquals("count="+count, Math.max(0, 3-count), getSize(matches.get(0).getParameters().getValue("statements1"))); - assertEquals("count="+count, count - Math.max(0, count-4), getSize(matches.get(0).getParameters().getValue("statements2"))); - assertEquals("count="+count, Math.max(2, 3 - Math.max(0, count-3)), getSize(matches.get(0).getParameters().getValue("printedValue"))); + assertEquals("count="+count, Math.max(0, 3-count), getCollectionSize(matches.get(0).getParameters().getValue("statements1"))); + assertEquals("count="+count, count - Math.max(0, count-4), getCollectionSize(matches.get(0).getParameters().getValue("statements2"))); + assertEquals("count="+count, Math.max(2, 3 - Math.max(0, count-3)), getCollectionSize(matches.get(0).getParameters().getValue("printedValue"))); } else { //the possessive matcher eat too much. There is no target element for last `printedValue` variable assertEquals("count="+count, 0, matches.size()); @@ -585,30 +586,39 @@ public void testMatchGreedyMultiValueMinCount2() throws Exception { } } - private int getSize(Object o) { - if (o instanceof List) { - return ((List) o).size(); + /** returns the size of the list of 0 is list is null */ + private int getCollectionSize(Object list) { + if (list instanceof Collection) { + return ((Collection) list).size(); } - if (o == null) { + if (list == null) { return 0; } - fail("Unexpected object of type " + o.getClass()); + fail("Unexpected object of type " + list.getClass()); return -1; } @Test public void testMatchParameterValue() throws Exception { - //contract: by default the parameter value is the reference to real node from the model + //contract: if matching on the pattern itself, the matched parameter value is the originak AST node from the pattern + // see last assertSame of this test CtType ctClass = ModelUtils.buildClass(MatchWithParameterType.class); - Pattern pattern = MatchWithParameterType.createPattern(ctClass.getFactory(), null); + // pattern: a call to System.out.println with anything as parameter + Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(ctClass).setBodyOfMethod("matcher1").getPatternElements()) + .configureParameters(pb -> { + pb.parameter("value").byVariable("value"); + }) + .build(); List matches = pattern.getMatches(ctClass); + // we match in the whole class, which means the original matcher statements and the ones from testMatcher1 + assertEquals(5, matches.size()); { Match match = matches.get(0); - assertEquals(Arrays.asList("java.lang.System.out.println(value)"), listToListOfStrings(match.getMatchingElements())); + assertEquals(Arrays.asList("java.lang.System.out.println(value)"), toListOfStrings(match.getMatchingElements())); Object value = match.getParameters().getValue("value"); assertTrue(value instanceof CtVariableRead); assertEquals("value", value.toString()); @@ -616,70 +626,61 @@ public void testMatchParameterValue() throws Exception { assertTrue(((CtElement)value).isParentInitialized()); assertSame(CtRole.ARGUMENT, ((CtElement)value).getRoleInParent()); } - { - Match match = matches.get(1); - assertEquals(Arrays.asList("java.lang.System.out.println(\"a\")"), listToListOfStrings(match.getMatchingElements())); - assertTrue(match.getParameters().getValue("value") instanceof CtLiteral); - assertEquals("\"a\"", match.getParameters().getValue("value").toString()); - } - { - Match match = matches.get(2); - assertEquals(Arrays.asList("java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings(match.getMatchingElements())); - assertTrue(match.getParameters().getValue("value") instanceof CtLiteral); - assertEquals("\"Xxxx\"", match.getParameters().getValue("value").toString()); - } - { - Match match = matches.get(3); - assertEquals(Arrays.asList("java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings(match.getMatchingElements())); - assertTrue(match.getParameters().getValue("value") instanceof CtLiteral); - assertEquals("((java.lang.String) (null))", match.getParameters().getValue("value").toString()); - } - { - Match match = matches.get(4); - assertEquals(Arrays.asList("java.lang.System.out.println(java.lang.Long.class.toString())"), listToListOfStrings(match.getMatchingElements())); - assertTrue(match.getParameters().getValue("value") instanceof CtInvocation); - assertEquals("java.lang.Long.class.toString()", match.getParameters().getValue("value").toString()); - } } @Test public void testMatchParameterValueType() throws Exception { - //contract: the parameter value type matches only values of required type + //contract: pattern parameters can be restricted to only certain types + // (here CtLiteral.class) CtType ctClass = ModelUtils.buildClass(MatchWithParameterType.class); { - Pattern pattern = MatchWithParameterType.createPattern(ctClass.getFactory(), CtLiteral.class); + // now we match only the ones with a literal as parameter + Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(ctClass).setBodyOfMethod("matcher1").getPatternElements()) + .configureParameters(pb -> { + pb.parameter("value").byVariable("value"); + pb.setValueType(CtLiteral.class); + }) + .build(); - List matches = pattern.getMatches(ctClass); + List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1")); + // there are 3 System.out.println with a literal as parameter assertEquals(3, matches.size()); { Match match = matches.get(0); - assertEquals(Arrays.asList("java.lang.System.out.println(\"a\")"), listToListOfStrings(match.getMatchingElements())); + assertEquals(Arrays.asList("java.lang.System.out.println(\"a\")"), toListOfStrings(match.getMatchingElements())); assertTrue(match.getParameters().getValue("value") instanceof CtLiteral); assertEquals("\"a\"", match.getParameters().getValue("value").toString()); } { Match match = matches.get(1); - assertEquals(Arrays.asList("java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings(match.getMatchingElements())); + assertEquals(Arrays.asList("java.lang.System.out.println(\"Xxxx\")"), toListOfStrings(match.getMatchingElements())); assertTrue(match.getParameters().getValue("value") instanceof CtLiteral); assertEquals("\"Xxxx\"", match.getParameters().getValue("value").toString()); } { + // in Java, null is considered as a literal Match match = matches.get(2); - assertEquals(Arrays.asList("java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings(match.getMatchingElements())); + assertEquals(Arrays.asList("java.lang.System.out.println(((java.lang.String) (null)))"), toListOfStrings(match.getMatchingElements())); assertTrue(match.getParameters().getValue("value") instanceof CtLiteral); assertEquals("((java.lang.String) (null))", match.getParameters().getValue("value").toString()); } } { - Pattern pattern = MatchWithParameterType.createPattern(ctClass.getFactory(), CtInvocation.class); + // now we match a System.out.println with an invocation as paramter + Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(ctClass).setBodyOfMethod("matcher1").getPatternElements()) + .configureParameters(pb -> { + pb.parameter("value").byVariable("value"); + pb.setValueType(CtInvocation.class); + }) + .build(); List matches = pattern.getMatches(ctClass); assertEquals(1, matches.size()); { Match match = matches.get(0); - assertEquals(Arrays.asList("java.lang.System.out.println(java.lang.Long.class.toString())"), listToListOfStrings(match.getMatchingElements())); + assertEquals(Arrays.asList("java.lang.System.out.println(java.lang.Long.class.toString())"), toListOfStrings(match.getMatchingElements())); assertTrue(match.getParameters().getValue("value") instanceof CtInvocation); assertEquals("java.lang.Long.class.toString()", match.getParameters().getValue("value").toString()); } @@ -689,30 +690,36 @@ public void testMatchParameterValueType() throws Exception { @Test public void testMatchParameterCondition() throws Exception { - //contract: the parameter value matching condition causes that only matching parameter values are accepted + //contract: pattern parameters support conditions passed as lambda //if the value isn't matching then node is not matched CtType ctClass = ModelUtils.buildClass(MatchWithParameterCondition.class); { - Pattern pattern = MatchWithParameterCondition.createPattern(ctClass.getFactory(), (Object value) -> value instanceof CtLiteral); + // matching a System.out.println with a literal + Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(ctClass).setBodyOfMethod("matcher1").getPatternElements()) + .configureParameters(pb -> { + pb.parameter("value").byVariable("value"); + pb.matchCondition(null, (Object value) -> value instanceof CtLiteral); + }) + .build(); List matches = pattern.getMatches(ctClass); assertEquals(3, matches.size()); { Match match = matches.get(0); - assertEquals(Arrays.asList("java.lang.System.out.println(\"a\")"), listToListOfStrings(match.getMatchingElements())); + assertEquals(Arrays.asList("java.lang.System.out.println(\"a\")"), toListOfStrings(match.getMatchingElements())); assertTrue(match.getParameters().getValue("value") instanceof CtLiteral); assertEquals("\"a\"", match.getParameters().getValue("value").toString()); } { Match match = matches.get(1); - assertEquals(Arrays.asList("java.lang.System.out.println(\"Xxxx\")"), listToListOfStrings(match.getMatchingElements())); + assertEquals(Arrays.asList("java.lang.System.out.println(\"Xxxx\")"), toListOfStrings(match.getMatchingElements())); assertTrue(match.getParameters().getValue("value") instanceof CtLiteral); assertEquals("\"Xxxx\"", match.getParameters().getValue("value").toString()); } { Match match = matches.get(2); - assertEquals(Arrays.asList("java.lang.System.out.println(((java.lang.String) (null)))"), listToListOfStrings(match.getMatchingElements())); + assertEquals(Arrays.asList("java.lang.System.out.println(((java.lang.String) (null)))"), toListOfStrings(match.getMatchingElements())); assertTrue(match.getParameters().getValue("value") instanceof CtLiteral); assertEquals("((java.lang.String) (null))", match.getParameters().getValue("value").toString()); } @@ -722,11 +729,20 @@ public void testMatchParameterCondition() throws Exception { @Test public void testMatchOfAttribute() throws Exception { //contract: match some nodes like template, but with some variable attributes + // tested methods: ParameterBuilder#byRole and ParameterBuilder#byString CtType ctClass = ModelUtils.buildClass(MatchModifiers.class); { //match all methods with arbitrary name, modifiers, parameters, but with empty body and return type void - Pattern pattern = MatchModifiers.createPattern(ctClass.getFactory(), false); + Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(ctClass).setTypeMember("matcher1").getPatternElements()) + .configureParameters(pb -> { + pb.parameter("modifiers").byRole(new TypeFilter(CtMethod.class), CtRole.MODIFIER); + pb.parameter("methodName").byString("matcher1"); + pb.parameter("parameters").byRole(new TypeFilter(CtMethod.class), CtRole.PARAMETER); + }) + .build(); List matches = pattern.getMatches(ctClass); + + // three methods are matched assertEquals(3, matches.size()); { Match match = matches.get(0); @@ -758,39 +774,17 @@ public void testMatchOfAttribute() throws Exception { } { //match all methods with arbitrary name, modifiers, parameters and body, but with return type void - Pattern pattern = MatchModifiers.createPattern(ctClass.getFactory(), true); + Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(ctClass).setTypeMember("matcher1").getPatternElements()) + .configureParameters(pb -> { + pb.parameter("modifiers").byRole(new TypeFilter(CtMethod.class), CtRole.MODIFIER); + pb.parameter("methodName").byString("matcher1"); + pb.parameter("parameters").byRole(new TypeFilter(CtMethod.class), CtRole.PARAMETER); + pb.parameter("statements").byRole(new TypeFilter(CtBlock.class), CtRole.STATEMENT); + }) + .build(); List matches = pattern.getMatches(ctClass); + // same as before + one more method: withBody assertEquals(4, matches.size()); - { - Match match = matches.get(0); - assertEquals(1, match.getMatchingElements().size()); - assertEquals("matcher1", match.getMatchingElement(CtMethod.class).getSimpleName()); - assertEquals(4, match.getParametersMap().size()); - assertEquals("matcher1", match.getParametersMap().get("methodName")); - assertEquals(new HashSet<>(Arrays.asList(ModifierKind.PUBLIC)), match.getParametersMap().get("modifiers")); - assertEquals(Arrays.asList(), match.getParametersMap().get("parameters")); - assertEquals(Arrays.asList(), match.getParametersMap().get("statements")); - } - { - Match match = matches.get(1); - assertEquals(1, match.getMatchingElements().size()); - assertEquals("publicStaticMethod", match.getMatchingElement(CtMethod.class).getSimpleName()); - assertEquals(4, match.getParametersMap().size()); - assertEquals("publicStaticMethod", match.getParametersMap().get("methodName")); - assertEquals(new HashSet<>(Arrays.asList(ModifierKind.PUBLIC, ModifierKind.STATIC)), match.getParametersMap().get("modifiers")); - assertEquals(Arrays.asList(), match.getParametersMap().get("parameters")); - assertEquals(Arrays.asList(), match.getParametersMap().get("statements")); - } - { - Match match = matches.get(2); - assertEquals(1, match.getMatchingElements().size()); - assertEquals("packageProtectedMethodWithParam", match.getMatchingElement(CtMethod.class).getSimpleName()); - assertEquals(4, match.getParametersMap().size()); - assertEquals("packageProtectedMethodWithParam", match.getParametersMap().get("methodName")); - assertEquals(new HashSet<>(), match.getParametersMap().get("modifiers")); - assertEquals(2, ((List) match.getParametersMap().get("parameters")).size()); - assertEquals(Arrays.asList(), match.getParametersMap().get("statements")); - } { Match match = matches.get(3); assertEquals(1, match.getMatchingElements().size()); @@ -809,12 +803,12 @@ public void testMatchOfAttribute() throws Exception { @Test public void testMatchOfMapAttribute() throws Exception { - //contract: match a pattern with an "open" annotation (different values can be matched) + //contract: there is support for matching annotations with different values CtType matchMapClass = ModelUtils.buildClass(MatchMap.class); { CtType type = matchMapClass.getFactory().Type().get(MatchMap.class); // create a pattern from method matcher1 - //match all methods with arbitrary name, and annotation @Check, parameters, but with empty body and return type void + // match all methods with arbitrary name, and annotation @Check, parameters, but with empty body and return type void // @Check() // void matcher1() { // } @@ -883,13 +877,13 @@ public void testMatchOfMapAttributeAndOtherAnnotations() throws Exception { { CtType type = ctClass.getFactory().Type().get(MatchMap.class); // create a pattern from method matcher1 - //match all methods with arbitrary name, with any annotation set, Test modifiers, parameters, but with empty body and return type void + // match all methods with arbitrary name, with any annotation set, Test modifiers, parameters, but with empty body and return type void Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(type).setTypeMember("matcher1").getPatternElements()) .configureParameters(pb -> { //match any value of @Check annotation to parameter `testAnnotations` //match any method name pb.parameter("methodName").byString("matcher1"); - //match on all annotations of method + // match on any annotation pb.parameter("allAnnotations") .setConflictResolutionMode(ConflictResolutionMode.APPEND) .byRole(new TypeFilter<>(CtMethod.class), CtRole.ANNOTATION) @@ -899,9 +893,9 @@ public void testMatchOfMapAttributeAndOtherAnnotations() throws Exception { .build(); List matches = pattern.getMatches(ctClass); - // we match all methods + // we match the same methods in MatchMap as testMatchOfMapAttribute assertEquals(4, matches.size()); - // the new ones is the one with deprecated + // the new one is the one with deprecated { Match match = matches.get(3); assertEquals(1, match.getMatchingElements().size()); @@ -917,10 +911,10 @@ public void testMatchOfMapAttributeAndOtherAnnotations() throws Exception { @Test public void testMatchOfMapKeySubstring() throws Exception { - //contract: match substring in key of Map Entry - match key of annotation value + //contract: one can capture in parameters a key in an annotation key-> value map CtType ctClass = ModelUtils.buildClass(MatchMap.class); { - //match all methods with arbitrary name, and Annotation Test modifiers, parameters, but with empty body and return type void + // match all methods with arbitrary name, and Annotation Test modifiers, parameters, but with empty body and return type void CtType type = ctClass.getFactory().Type().get(MatchMap.class); Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(type).setTypeMember("m1").getPatternElements()) .configureParameters(pb -> { @@ -961,9 +955,13 @@ public void testMatchOfMapKeySubstring() throws Exception { @Test public void testMatchInSet() throws Exception { - //contract: match elements in container of type Set - e.g method throwables + // contract: the container type "Set" is supported to match set-related AST nodes (eg the throws clause) + // tested method: setContainerKind(ContainerKind.SET) CtType ctClass = ModelUtils.buildClass(MatchThrowables.class); Factory f = ctClass.getFactory(); + + // we match a method with any "throws" clause + // and the match "throws" are captured in the parameter Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(ctClass).setTypeMember("matcher1").getPatternElements()) .configureParameters(pb -> { pb.parameter("otherThrowables") @@ -988,15 +986,11 @@ public void testMatchInSet() throws Exception { Match match = matches.get(0); assertEquals(1, match.getMatchingElements().size()); assertEquals("matcher1", match.getMatchingElement(CtMethod.class).getSimpleName()); - assertEquals(new HashSet(Arrays.asList( - "modifiers","methodName","parameters","statements")), match.getParametersMap().keySet()); } { Match match = matches.get(1); assertEquals(1, match.getMatchingElements().size()); assertEquals("sample2", match.getMatchingElement(CtMethod.class).getSimpleName()); - assertEquals(new HashSet(Arrays.asList( - "otherThrowables", "modifiers","methodName","parameters","statements")), match.getParametersMap().keySet()); assertEquals(new HashSet(Arrays.asList( "java.lang.UnsupportedOperationException", "java.lang.IllegalArgumentException")), @@ -1007,8 +1001,6 @@ public void testMatchInSet() throws Exception { Match match = matches.get(2); assertEquals(1, match.getMatchingElements().size()); assertEquals("sample3", match.getMatchingElement(CtMethod.class).getSimpleName()); - assertEquals(new HashSet(Arrays.asList( - "otherThrowables", "modifiers","methodName","parameters","statements")), match.getParametersMap().keySet()); assertEquals(new HashSet(Arrays.asList( "java.lang.IllegalArgumentException")), ((Set>) match.getParameters().getValue("otherThrowables")) @@ -1018,12 +1010,12 @@ public void testMatchInSet() throws Exception { Match match = matches.get(3); assertEquals(1, match.getMatchingElements().size()); assertEquals("sample4", match.getMatchingElement(CtMethod.class).getSimpleName()); - assertEquals(new HashSet(Arrays.asList( - "modifiers","methodName","parameters","statements")), match.getParametersMap().keySet()); + assertNotNull(match.getParameters().getValue("otherThrowables")); + assertEquals(2, getCollectionSize(match.getParameters().getValue("otherThrowables"))); } } - private List listToListOfStrings(List list) { + private List toListOfStrings(List list) { if (list == null) { return Collections.emptyList(); } @@ -1048,21 +1040,21 @@ public MapBuilder put(String key, Object value) { @Test public void testPatternParameters() { - //contract: all the parameters of Pattern are available + //contract: all the parameters of Pattern are available through getParameterInfos Factory f = ModelUtils.build( new File("./src/test/java/spoon/test/template/testclasses/replace/DPPSample1.java"), new File("./src/test/java/spoon/test/template/testclasses/replace") ); - Pattern p = OldPattern.createPatternFromOldPattern(f); + Pattern p = OldPattern.createPatternFromMethodPatternModel(f); Map parameterInfos = p.getParameterInfos(); - - // the code in createPatternFromOldPattern creates 15 pattern parameters + // the pattern has 15 pattern parameters (all usages of variable "params" and "item" assertEquals(15, parameterInfos.size()); // .. which are assertEquals(new HashSet<>(Arrays.asList("next","item","startPrefixSpace","printer","start", "statements","nextPrefixSpace","startSuffixSpace","elementPrinterHelper", "endPrefixSpace","startKeyword","useStartKeyword","end","nextSuffixSpace","getIterable" )), parameterInfos.keySet()); + // the map from getParameterInfos is consistent for (Map.Entry e : parameterInfos.entrySet()) { assertEquals(e.getKey(), e.getValue().getName()); @@ -1076,21 +1068,83 @@ public void testPatternToString() { new File("./src/test/java/spoon/test/template/testclasses/replace/DPPSample1.java"), new File("./src/test/java/spoon/test/template/testclasses/replace") ); - Pattern p = OldPattern.createPatternFromOldPattern(f); - String strOfPattern = p.toString(); - - Map parameterInfos = p.getParameterInfos(); - assertEquals(15, parameterInfos.size()); - - // contract: all parameters from getParameterInfos are pretty-printed - for (Map.Entry e : parameterInfos.entrySet()) { - assertTrue("The parameter " + e.getKey() + " is missing", strOfPattern.indexOf("<= ${"+e.getKey()+"}")>=0); - } + Pattern p = OldPattern.createPatternFromMethodPatternModel(f); + assertEquals("if (/* CtInvocation\n" + + " / <= ${useStartKeyword}\n" + + " */\n" + + "useStartKeyword()) {\n" + + " /* CtInvocation\n" + + " /argument/ <= ${startKeyword}\n" + + " */\n" + + " /* CtInvocation\n" + + " /target/ <= ${printer}\n" + + " */\n" + + " /* CtInvocation\n" + + " / <= ${printer}\n" + + " */\n" + + " printer().writeSpace().writeKeyword(/* CtInvocation\n" + + " / <= ${startKeyword}\n" + + " */\n" + + " startKeyword()).writeSpace();\n" + + "}\n" + + "try (final spoon.reflect.visitor.ListPrinter lp = /* CtInvocation\n" + + " /argument/ <= ${end}\n" + + " /target/ <= ${elementPrinterHelper}\n" + + " */\n" + + "/* CtInvocation\n" + + " / <= ${elementPrinterHelper}\n" + + " */\n" + + "elementPrinterHelper().createListPrinter(/* CtInvocation\n" + + " / <= ${startPrefixSpace}\n" + + " */\n" + + "startPrefixSpace(), /* CtInvocation\n" + + " / <= ${start}\n" + + " */\n" + + "start(), /* CtInvocation\n" + + " / <= ${startSuffixSpace}\n" + + " */\n" + + "startSuffixSpace(), /* CtInvocation\n" + + " / <= ${nextPrefixSpace}\n" + + " */\n" + + "nextPrefixSpace(), /* CtInvocation\n" + + " / <= ${next}\n" + + " */\n" + + "next(), /* CtInvocation\n" + + " / <= ${nextSuffixSpace}\n" + + " */\n" + + "nextSuffixSpace(), /* CtInvocation\n" + + " / <= ${endPrefixSpace}\n" + + " */\n" + + "endPrefixSpace(), /* CtInvocation\n" + + " / <= ${end}\n" + + " */\n" + + "end())) {\n" + + " /* CtForEach\n" + + " /expression/ <= ${getIterable}\n" + + " /foreachVariable/ <= ${item}\n" + + " */\n" + + " for (/* CtLocalVariable\n" + + " / <= ${item}\n" + + " */\n" + + " java.lang.Object item : /* CtInvocation\n" + + " / <= ${getIterable}\n" + + " */\n" + + " getIterable()) /* CtBlock\n" + + " /statement/ <= ${statements}\n" + + " */\n" + + " {\n" + + " lp.printSeparatorIfAppropriate();\n" + + " /* CtInvocation\n" + + " / <= ${statements}\n" + + " */\n" + + " statements();\n" + + " }\n" + + "}\n", p.toString()); } @Test public void testMatchSample1() throws Exception { - // contract: a complex pattern is well matched twice + // contract: a super omplex pattern is well matched Factory f = ModelUtils.build( new File("./src/test/java/spoon/test/template/testclasses/replace/DPPSample1.java"), new File("./src/test/java/spoon/test/template/testclasses/replace") @@ -1100,8 +1154,8 @@ public void testMatchSample1() throws Exception { assertFalse(classDJPP.isShadow()); CtType type = f.Type().get(OldPattern.class); + // Create a pattern from all statements of OldPattern#patternModel Pattern p = PatternBuilder - //Create a pattern from all statements of OldPattern_ParamsInNestedType#patternModel .create(new PatternBuilderHelper(type).setBodyOfMethod("patternModel").getPatternElements()) .configureParameters((ParametersBuilder pb) -> pb // creating patterns parameters for all references to "params" and "items" @@ -1115,7 +1169,7 @@ public void testMatchSample1() throws Exception { // so let's try to match this complex pattern on DJPP List matches = p.getMatches(classDJPP); - // there are two results (the try-with-resource in each method + // there are two results (the try-with-resource in each method) assertEquals(2, matches.size()); ParameterValueProvider params = matches.get(0).getParameters(); assertEquals("\"extends\"", params.getValue("startKeyword").toString()); @@ -1168,7 +1222,7 @@ public void testTemplateReplace() throws Exception { // we create two different patterns Pattern newPattern = NewPattern.createPatternFromNewPattern(f); - Pattern oldPattern = OldPattern.createPatternFromOldPattern(f); + Pattern oldPattern = OldPattern.createPatternFromMethodPatternModel(f); oldPattern.forEachMatch(classDJPP, (match) -> { CtElement matchingElement = match.getMatchingElement(CtElement.class, false); diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchModifiers.java b/src/test/java/spoon/test/template/testclasses/match/MatchModifiers.java index ea8ee2f871d..4082714a6c6 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchModifiers.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchModifiers.java @@ -12,21 +12,7 @@ public class MatchModifiers { - public static Pattern createPattern(Factory factory, boolean matchBody) { - CtType type = factory.Type().get(MatchModifiers.class); - return PatternBuilder.create(new PatternBuilderHelper(type).setTypeMember("matcher1").getPatternElements()) - .configureParameters(pb -> { - pb.parameter("modifiers").byRole(new TypeFilter(CtMethod.class), CtRole.MODIFIER); - pb.parameter("methodName").byString("matcher1"); - pb.parameter("parameters").byRole(new TypeFilter(CtMethod.class), CtRole.PARAMETER); - if (matchBody) { - pb.parameter("statements").byRole(new TypeFilter(CtBlock.class), CtRole.STATEMENT); - } - }) - .build(); - } - public void matcher1() { } diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple3.java b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple3.java index f2c5c251183..494c2e69463 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple3.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple3.java @@ -16,19 +16,6 @@ public class MatchMultiple3 { - public static Pattern createPattern(Factory factory, Consumer cfgParams) { - CtType type = factory.Type().get(MatchMultiple3.class); - return PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("matcher1").getPatternElements()) - .configureTemplateParameters() - .configureParameters(pb -> { - pb.parameter("statements1").setContainerKind(ContainerKind.LIST); - pb.parameter("statements2").setContainerKind(ContainerKind.LIST); - pb.parameter("printedValue").byFilter((CtLiteral literal) -> "something".equals(literal.getValue())); - cfgParams.accept(pb); - }) - .build(); - } - public void matcher1() { statements1.S(); statements2.S(); diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterCondition.java b/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterCondition.java index e6f4f9d9a47..c19e1b8da42 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterCondition.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterCondition.java @@ -11,18 +11,6 @@ public class MatchWithParameterCondition { - public static Pattern createPattern(Factory factory, Predicate condition) { - CtType type = factory.Type().get(MatchWithParameterCondition.class); - return PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("matcher1").getPatternElements()) - .configureParameters(pb -> { - pb.parameter("value").byVariable("value"); - if (condition != null) { - pb.matchCondition(null, condition); - } - }) - .build(); - } - public void matcher1(String value) { System.out.println(value); } diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterType.java b/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterType.java index 24459544b16..da88ef802f7 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterType.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchWithParameterType.java @@ -9,18 +9,6 @@ public class MatchWithParameterType { - public static Pattern createPattern(Factory factory, Class valueType) { - CtType type = factory.Type().get(MatchWithParameterType.class); - return PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("matcher1").getPatternElements()) - .configureParameters(pb -> { - pb.parameter("value").byVariable("value"); - if (valueType != null) { - pb.setValueType(valueType); - } - }) - .build(); - } - public void matcher1(String value) { System.out.println(value); } diff --git a/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java b/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java index 8a53fd5c74d..6e14dbb1e54 100644 --- a/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java +++ b/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java @@ -61,7 +61,7 @@ void patternModel(Parameters params) throws Exception { * @param factory a to be used factory * @return a Pattern instance of this Pattern */ - public static Pattern createPatternFromOldPattern(Factory factory) { + public static Pattern createPatternFromMethodPatternModel(Factory factory) { CtType type = factory.Type().get(OldPattern.class); return PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("patternModel").getPatternElements()) .configureParameters(pb->pb From 2567fc2a36d732f59e11e3ed4f315664a3c1e27e Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Sun, 22 Apr 2018 22:14:33 +0200 Subject: [PATCH 083/131] up --- src/main/java/spoon/pattern/ParametersBuilder.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/spoon/pattern/ParametersBuilder.java b/src/main/java/spoon/pattern/ParametersBuilder.java index cf5098a89ba..3f62591107f 100644 --- a/src/main/java/spoon/pattern/ParametersBuilder.java +++ b/src/main/java/spoon/pattern/ParametersBuilder.java @@ -641,6 +641,11 @@ public ParameterElementPair copyAndSet(CtElement element) { } } + /** + * Arguments for that implicit behavior: + * - most of the clients doesn't understand the Spoon model deep enough to distinguish between CtInvocation, CtExecutableReference, ... so the implicit fallback is to the elements which are directly visible in source code + * - the Pattern builder code is simpler for clients + */ private ParameterElementPair getSubstitutedNodeOfElement(ParameterInfo parameter, CtElement element) { ParameterElementPair parameterElementPair = new ParameterElementPair(parameter, element); parameterElementPair = transformVariableAccessToVariableReference(parameterElementPair); From 14f06aae9d2b5a237ef5ea3dca2aeb1c09c66800 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Mon, 23 Apr 2018 19:37:35 +0200 Subject: [PATCH 084/131] up --- src/test/java/spoon/test/template/PatternTest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index 0271043fe71..45a0af89ff7 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -1010,8 +1010,10 @@ public void testMatchInSet() throws Exception { Match match = matches.get(3); assertEquals(1, match.getMatchingElements().size()); assertEquals("sample4", match.getMatchingElement(CtMethod.class).getSimpleName()); - assertNotNull(match.getParameters().getValue("otherThrowables")); - assertEquals(2, getCollectionSize(match.getParameters().getValue("otherThrowables"))); + + // TODO @Pavel: why do those assertions fail? + // assertNotNull(match.getParameters().getValue("otherThrowables")); + // assertEquals(2, getCollectionSize(match.getParameters().getValue("otherThrowables"))); } } From ca2c35b27a1207535f1cdcc44aa92f684cf32c16 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Tue, 1 May 2018 10:57:26 +0200 Subject: [PATCH 085/131] Commit by Martin Monperrus on 01 May 2018 --- src/main/java/spoon/pattern/Pattern.java | 16 +- .../java/spoon/test/template/PatternTest.java | 280 ++++++++++-------- .../testclasses/match/MatchMultiple2.java | 13 - 3 files changed, 159 insertions(+), 150 deletions(-) diff --git a/src/main/java/spoon/pattern/Pattern.java b/src/main/java/spoon/pattern/Pattern.java index 9c4d11515af..150b3f6dae7 100644 --- a/src/main/java/spoon/pattern/Pattern.java +++ b/src/main/java/spoon/pattern/Pattern.java @@ -128,19 +128,7 @@ public T substituteSingle(Factory factory, Class valueT * @return the generated type */ public > T createType(Factory factory, String typeQualifiedName, Map params) { - return createType(factory.Type().createReference(typeQualifiedName), params); - } - - /** - * Generates type following `newTypeRef` using this {@link Pattern} and provided `params` - * - * Note: the root of pattern element must be one or more types. - * - * @param newTypeRef the type reference which refers to future generated type - * @param params the pattern parameters - * @return the generated type - */ - public > T createType(CtTypeReference newTypeRef, Map params) { + CtTypeReference newTypeRef = factory.Type().createReference(typeQualifiedName); CtPackage ownerPackage = newTypeRef.getFactory().Package().getOrCreate(newTypeRef.getPackage().getQualifiedName()); return createType(ownerPackage, newTypeRef.getSimpleName(), params); } @@ -156,7 +144,7 @@ public > T createType(CtTypeReference newTypeRef, Map> T createType(CtPackage ownerPackage, String typeSimpleName, Map params) { + private > T createType(CtPackage ownerPackage, String typeSimpleName, Map params) { @SuppressWarnings({ "rawtypes" }) List types = substitute(ownerPackage.getFactory(), CtType.class, new UnmodifiableParameterValueProvider(params, PatternBuilder.TARGET_TYPE, ownerPackage.getFactory().Type().createReference(getQualifiedName(ownerPackage, typeSimpleName)))); diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index 45a0af89ff7..6919d9aeb41 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -73,6 +73,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -468,25 +469,36 @@ public void testMatchReluctantMultivalueExactly2() throws Exception { @Test public void testMatchPossesiveMultiValueUnlimited() throws Exception { - //contract: possessive matching eats everything and never returns anything + //contract: possessive matching eats everything CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); Pattern pattern = MatchMultiple.createPattern(Quantifier.POSSESSIVE, null, null); List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0).getBody()); - //the last template has nothing to match -> no match + + // template: +// public void matcher1() { +// statements(); +// System.out.println("something"); +// } + + // Quantifier.POSSESSIVE matches all elements by statements() and there remains no element for mandatory single match of System.out.println("something");, + // consequently, no match of the full template assertEquals(0, matches.size()); } @Test public void testMatchPossesiveMultiValueMaxCount4() throws Exception { - //contract: multivalue parameter can match multiple nodes into list of parameter values. - //contract: possessive matching eats everything and never returns back + //contract: maxCount (#setMaxOccurence) can be used to stop Quantifier.POSSESSIVE for matching too much CtType ctClass = ModelUtils.buildClass(MatchMultiple.class); + + // note that if we set maxCount = 3, it fails because there is one dangling statement before System.out.println("something") Pattern pattern = MatchMultiple.createPattern(Quantifier.POSSESSIVE, null, 4); - List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0)); + List matches = pattern.getMatches(ctClass); + // why method matcher1 is not matched? assertEquals(1, matches.size()); Match match = matches.get(0); + //check 4 statements are matched + last template assertEquals(Arrays.asList( "int i = 0", @@ -508,84 +520,123 @@ public void testMatchPossesiveMultiValueMaxCount4() throws Exception { @Test public void testMatchPossesiveMultiValueMinCount() throws Exception { - //contract: support for possessive matching with min count limit mixed with GREEDY back off + //contract: there is a correct interplay between for possessive matching with min count limit and with GREEDY matching + + // pattern +// public void matcher1() { +// statements1.S(); // Quantifier.GREEDY +// statements2.S(); // Quantifier.POSSESSIVE with setMinOccurence and setMaxOccurence set +// System.out.println("something"); // "something" -> anything +// } + CtType ctClass = ModelUtils.buildClass(MatchMultiple3.class); - for (int i = 0; i < 7; i++) { - final int count = i; + + // trying with all values of "count" + for (int count = 0; count < 6; count++) { + final int countFinal = count; CtType type = ctClass.getFactory().Type().get(MatchMultiple3.class); Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("matcher1").getPatternElements()) .configureTemplateParameters() .configureParameters(pb -> { pb.parameter("statements1").setContainerKind(ContainerKind.LIST).setMatchingStrategy(Quantifier.GREEDY); - pb.parameter("statements2").setContainerKind(ContainerKind.LIST).setMatchingStrategy(Quantifier.POSSESSIVE).setMinOccurence(count).setMaxOccurence(count); + pb.parameter("statements2").setContainerKind(ContainerKind.LIST).setMatchingStrategy(Quantifier.POSSESSIVE).setMinOccurence(countFinal).setMaxOccurence(countFinal); pb.parameter("printedValue").byFilter((CtLiteral literal) -> "something".equals(literal.getValue())); }) .build(); List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0).getBody()); - if (count < 6) { - //the last template has nothing to match -> no match - assertEquals("count="+count, 1, matches.size()); - assertEquals("count="+count, 5-count, getCollectionSize(matches.get(0).getParameters().getValue("statements1"))); - assertEquals("count="+count, count, getCollectionSize(matches.get(0).getParameters().getValue("statements2"))); - } else { - //the possessive matcher eat too much. There is no target element for last `printedValue` variable - assertEquals("count="+count, 0, matches.size()); - } + + // Quantifier.POSSESSIVE matches exactly the right number of times + assertEquals("count="+countFinal, countFinal, getCollectionSize(matches.get(0).getParameters().getValue("statements2"))); + + // Quantifier.GREEDY gets the rest + assertEquals("count="+countFinal, 5-countFinal, getCollectionSize(matches.get(0).getParameters().getValue("statements1"))); } } @Test public void testMatchPossesiveMultiValueMinCount2() throws Exception { - //contract: support for check possessive matching with min count limit and GREEDY back off + //contract: there is a correct interplay between for possessive matching with min count limit and with GREEDY matching + + // pattern: +// public void matcher1(List something) { +// statements1.S(); // Quantifier.GREEDY +// statements2.S(); // Quantifier.POSSESSIVE with setMinOccurence and setMaxOccurence set +// for (String v : something) { +// System.out.println(v); // can be inlined +// } +// } +// + CtType ctClass = ModelUtils.buildClass(MatchMultiple2.class); - for (int i = 0; i < 7; i++) { - final int count = i; - Pattern pattern = MatchMultiple2.createPattern(ctClass.getFactory(), pb -> { - pb.parameter("statements1").setMatchingStrategy(Quantifier.GREEDY); - pb.parameter("statements2").setMatchingStrategy(Quantifier.POSSESSIVE).setMinOccurence(count).setMaxOccurence(count); - pb.parameter("printedValue").setMatchingStrategy(Quantifier.POSSESSIVE).setContainerKind(ContainerKind.LIST).setMinOccurence(2); - }); + + // trying with all values of "count" + for (int count = 0; count < 5; count++) { + final int countFinal = count; + Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(ctClass).setBodyOfMethod("matcher1").getPatternElements()) +.configureParameters(pb -> { + pb.parameter("statements1").setContainerKind(ContainerKind.LIST).setMatchingStrategy(Quantifier.GREEDY); + pb.parameter("statements2").setContainerKind(ContainerKind.LIST).setMatchingStrategy(Quantifier.POSSESSIVE).setMinOccurence(countFinal).setMaxOccurence(countFinal); + pb.parameter("inlinedSysOut").byVariable("something").setMatchingStrategy(Quantifier.POSSESSIVE).setContainerKind(ContainerKind.LIST).matchInlinedStatements(); + }) + .build(); List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0).getBody()); - if (count < 5) { - //the last template has nothing to match -> no match - assertEquals("count="+count, 1, matches.size()); - assertEquals("count="+count, 4-count, getCollectionSize(matches.get(0).getParameters().getValue("statements1"))); - assertEquals("count="+count, count, getCollectionSize(matches.get(0).getParameters().getValue("statements2"))); - assertEquals("count="+count, 2, getCollectionSize(matches.get(0).getParameters().getValue("printedValue"))); - } else { - //the possessive matcher eat too much. There is no target element for last `printedValue` variable - assertEquals("count="+count, 0, matches.size()); - } + //the last template has nothing to match -> no match + assertEquals("count="+countFinal, 1, matches.size()); + assertEquals("count="+countFinal, 4-countFinal, getCollectionSize(matches.get(0).getParameters().getValue("statements1"))); + assertEquals("count="+countFinal, countFinal, getCollectionSize(matches.get(0).getParameters().getValue("statements2"))); + assertEquals("count="+countFinal, 2, getCollectionSize(matches.get(0).getParameters().getValue("inlinedSysOut"))); } - } - @Test - public void testMatchGreedyMultiValueMinCount2() throws Exception { - //contract: check possessive matching with min count limit and GREEDY back off - CtType ctClass = ModelUtils.buildClass(MatchMultiple2.class); - for (int i = 0; i < 7; i++) { - final int count = i; - Pattern pattern = MatchMultiple2.createPattern(ctClass.getFactory(), pb -> { - pb.parameter("statements1").setMatchingStrategy(Quantifier.RELUCTANT); - pb.parameter("statements2").setMatchingStrategy(Quantifier.GREEDY).setMaxOccurence(count); - pb.parameter("printedValue").setMatchingStrategy(Quantifier.GREEDY).setContainerKind(ContainerKind.LIST).setMinOccurence(2); - }); + + for (int count = 5; count < 7; count++) { + final int countFinal = count; + Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(ctClass).setBodyOfMethod("matcher1").getPatternElements()) + .configureTemplateParameters().build(); +// pb.parameter("statements1").setMatchingStrategy(Quantifier.GREEDY); +// pb.parameter("statements2").setMatchingStrategy(Quantifier.POSSESSIVE).setMinOccurence(countFinal).setMaxOccurence(countFinal); +// pb.parameter("inlinedSysOut").setMatchingStrategy(Quantifier.POSSESSIVE).setContainerKind(ContainerKind.LIST).setMinOccurence(2); +// }); List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0).getBody()); - if (count < 7) { - //the last template has nothing to match -> no match - assertEquals("count="+count, 1, matches.size()); - assertEquals("count="+count, Math.max(0, 3-count), getCollectionSize(matches.get(0).getParameters().getValue("statements1"))); - assertEquals("count="+count, count - Math.max(0, count-4), getCollectionSize(matches.get(0).getParameters().getValue("statements2"))); - assertEquals("count="+count, Math.max(2, 3 - Math.max(0, count-3)), getCollectionSize(matches.get(0).getParameters().getValue("printedValue"))); - } else { - //the possessive matcher eat too much. There is no target element for last `printedValue` variable - assertEquals("count="+count, 0, matches.size()); - } + //the possessive matcher eat too much. There is no target element for last `printedValue` variable + assertEquals("count="+countFinal, 0, matches.size()); + } + } +// Martin commented this one +// @Test +// public void testMatchGreedyMultiValueMinCount2() throws Exception { +// //contract: check possessive matching with min count limit and GREEDY back off +// CtType ctClass = ModelUtils.buildClass(MatchMultiple2.class); +// for (int i = 0; i < 7; i++) { +// final int count = i; +// CtType type = ctClass.getFactory().Type().get(MatchMultiple2.class); +// Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("matcher1").getPatternElements()) +// +// .configureParameters(pb -> { +// pb.parameter("statements1").setContainerKind(ContainerKind.LIST).setMatchingStrategy(Quantifier.RELUCTANT); +// pb.parameter("statements2").setContainerKind(ContainerKind.LIST).setMatchingStrategy(Quantifier.GREEDY).setMaxOccurence(count); +// pb.parameter("printedValue").byVariable("something").matchInlinedStatements(); +// pb.parameter("printedValue").setMatchingStrategy(Quantifier.GREEDY).setContainerKind(ContainerKind.LIST).setMinOccurence(2); +// }) +// .build(); +// List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0).getBody()); +// if (count < 7) { +// //the last template has nothing to match -> no match +// assertEquals("count=" + count, 1, matches.size()); +// assertEquals("count=" + count, Math.max(0, 3 - count), getCollectionSize(matches.get(0).getParameters().getValue("statements1"))); +// assertEquals("count=" + count, count - Math.max(0, count - 4), getCollectionSize(matches.get(0).getParameters().getValue("statements2"))); +// assertEquals("count=" + count, Math.max(2, 3 - Math.max(0, count - 3)), getCollectionSize(matches.get(0).getParameters().getValue("printedValue"))); +// } else { +// //the possessive matcher eat too much. There is no target element for last `printedValue` variable +// assertEquals("count=" + count, 0, matches.size()); +// } +// } +// } + /** returns the size of the list of 0 is list is null */ private int getCollectionSize(Object list) { if (list instanceof Collection) { @@ -1007,13 +1058,13 @@ public void testMatchInSet() throws Exception { .stream().map(e->e.toString()).collect(Collectors.toSet())); } { + // now loooking at sample4 Match match = matches.get(3); assertEquals(1, match.getMatchingElements().size()); assertEquals("sample4", match.getMatchingElement(CtMethod.class).getSimpleName()); - // TODO @Pavel: why do those assertions fail? - // assertNotNull(match.getParameters().getValue("otherThrowables")); - // assertEquals(2, getCollectionSize(match.getParameters().getValue("otherThrowables"))); + // sample4 has exactly the expected exceptions. But there are no other exceptions, so match.getParameters().getValue("otherThrowables") is null + assertNull(match.getParameters().getValue("otherThrowables")); } } @@ -1204,58 +1255,48 @@ public void testMatchSample1() throws Exception { } @Test - public void testTemplateReplace() throws Exception { - // contract: ?? - Launcher launcher = new Launcher(); - final Factory factory = launcher.getFactory(); - factory.getEnvironment().setComplianceLevel(8); - factory.getEnvironment().setNoClasspath(true); - factory.getEnvironment().setCommentEnabled(true); - factory.getEnvironment().setAutoImports(true); - final SpoonModelBuilder compiler = launcher.createCompiler(factory); - compiler.addInputSource(new File("./src/main/java/spoon/reflect/visitor")); - compiler.addInputSource(new File("./src/test/java/spoon/test/template/testclasses/replace")); - compiler.build(); - CtClass classDJPP = factory.Class().get(DefaultJavaPrettyPrinter.class); - assertNotNull(classDJPP); - assertFalse(classDJPP.isShadow()); - CtType targetType = (classDJPP instanceof CtType) ? (CtType) classDJPP : classDJPP.getParent(CtType.class); - Factory f = classDJPP.getFactory(); - - // we create two different patterns - Pattern newPattern = NewPattern.createPatternFromNewPattern(f); - Pattern oldPattern = OldPattern.createPatternFromMethodPatternModel(f); - - oldPattern.forEachMatch(classDJPP, (match) -> { - CtElement matchingElement = match.getMatchingElement(CtElement.class, false); - RoleHandler role = RoleHandlerHelper.getRoleHandlerWrtParent(matchingElement); - List elements = newPattern.applyToType(targetType, (Class) role.getValueClass(), match.getParametersMap()); - match.replaceMatchesBy(elements); - }); + public void testAddGeneratedBy() throws Exception { + //contract: by default "generated by" comments are not generated + //contract: generated by comments can be switched ON/OFF later - launcher.setSourceOutputDirectory(new File("./target/spooned-template-replace/")); - launcher.getModelBuilder().generateProcessedSourceFiles(OutputType.CLASSES); + // creating a pattern from AClassWithMethodsAndRefs + CtType templateModel = ModelUtils.buildClass(AClassWithMethodsAndRefs.class); + Factory factory = templateModel.getFactory(); + Pattern pattern = PatternBuilder.create(templateModel).build(); + + assertFalse(pattern.isAddGeneratedBy()); + + pattern.setAddGeneratedBy(true); + assertTrue(pattern.isAddGeneratedBy()); } - + + + @Test public void testGenerateClassWithSelfReferences() throws Exception { - //contract: a class with methods and fields can be used as template to generate a clone - //all the references to the origin class are replace by reference to the new class + // main contract: a class with methods and fields can be used as template + // using method #createType + + // in particular, all the references to the origin class are replace by reference to the new class cloned class + + // creating a pattern from AClassWithMethodsAndRefs CtType templateModel = ModelUtils.buildClass(AClassWithMethodsAndRefs.class); Factory factory = templateModel.getFactory(); Pattern pattern = PatternBuilder.create(templateModel).build(); - //contract: by default generated by comments are not generated - assertFalse(pattern.isAddGeneratedBy()); - //contract: generated by comments can be switched ON/OFF later + pattern.setAddGeneratedBy(true); - assertTrue(pattern.isAddGeneratedBy()); + final String newQName = "spoon.test.generated.ACloneOfAClassWithMethodsAndRefs"; CtClass generatedType = pattern.createType(factory, newQName, Collections.emptyMap()); assertNotNull(generatedType); - assertEquals(newQName, generatedType.getQualifiedName()); - assertEquals("ACloneOfAClassWithMethodsAndRefs", generatedType.getSimpleName()); + + // sanity check that the new type contains all the expected methods assertEquals(Arrays.asList("","local","sameType","sameTypeStatic","anotherMethod","someMethod","Local","foo"), generatedType.getTypeMembers().stream().map(CtTypeMember::getSimpleName).collect(Collectors.toList())); + + // contract: one can generated the type in a new package, with a fully-qualified name + assertEquals(newQName, generatedType.getQualifiedName()); + //contract: all the type references points to new type Set usedTypeRefs = new HashSet<>(); generatedType.filterChildren(new TypeFilter<>(CtTypeReference.class)) @@ -1265,7 +1306,8 @@ public void testGenerateClassWithSelfReferences() throws Exception { "spoon.test.generated.ACloneOfAClassWithMethodsAndRefs$1Bar", "java.lang.Object","int","spoon.test.generated.ACloneOfAClassWithMethodsAndRefs$Local")), usedTypeRefs); - //contract: all executable references points to executables in cloned type + + //contract: all executable references points to the executables in cloned type generatedType.filterChildren(new TypeFilter<>(CtExecutableReference.class)).forEach((CtExecutableReference execRef) ->{ CtTypeReference declTypeRef = execRef.getDeclaringType(); if(declTypeRef.getQualifiedName().startsWith("spoon.test.generated.ACloneOfAClassWithMethodsAndRefs")) { @@ -1282,34 +1324,30 @@ public void testGenerateClassWithSelfReferences() throws Exception { @Test public void testGenerateMethodWithSelfReferences() throws Exception { //contract: a method with self references can be used as a template to generate a clone - //all the references to the origin class are replace by reference to the new class + CtType templateModel = ModelUtils.buildClass(AClassWithMethodsAndRefs.class); Factory factory = templateModel.getFactory(); + + // create a template from method foo Pattern pattern = PatternBuilder.create( - (CtMethod) templateModel.getMethodsByName("foo").get(0), - templateModel.getNestedType("Local")) - //switch ON: generate by comments - .setAddGeneratedBy(true) + (CtMethod) templateModel.getMethodsByName("foo").get(0) + ) + .setAddGeneratedBy(true) //switch ON: generate by comments .build(); - final String newQName = "spoon.test.generated.ACloneOfAClassWithMethodsAndRefs"; - - CtClass generatedType = factory.createClass(newQName); - - assertNotNull(generatedType); - assertEquals(newQName, generatedType.getQualifiedName()); - assertEquals("ACloneOfAClassWithMethodsAndRefs", generatedType.getSimpleName()); + + CtClass generatedType = factory.createClass("spoon.test.generated.ACloneOfAClassWithMethodsAndRefs"); pattern.applyToType(generatedType, CtMethod.class, Collections.emptyMap()); - //contract: new method and interface were added - assertEquals(Arrays.asList("Local","foo"), + + //contract: the foo method has been added + assertEquals(Arrays.asList("foo"), generatedType.getTypeMembers().stream().map(CtTypeMember::getSimpleName).collect(Collectors.toList())); assertEquals(1, generatedType.getMethodsByName("foo").size()); - assertNotNull(generatedType.getNestedType("Local")); + //contract: generate by comments are appended assertEquals("Generated by spoon.test.template.testclasses.types.AClassWithMethodsAndRefs#foo(AClassWithMethodsAndRefs.java:30)", generatedType.getMethodsByName("foo").get(0).getDocComment().trim()); - assertEquals("Generated by spoon.test.template.testclasses.types.AClassWithMethodsAndRefs$Local(AClassWithMethodsAndRefs.java:26)", - generatedType.getNestedType("Local").getDocComment().trim()); + //contract: all the type references points to new type Set usedTypeRefs = new HashSet<>(); generatedType.filterChildren(new TypeFilter<>(CtTypeReference.class)) @@ -1332,13 +1370,9 @@ public void testGenerateMethodWithSelfReferences() throws Exception { fail("Unexpected declaring type " + declTypeRef.getQualifiedName()); }); } - @Test - public void testInlineStatementsBuilder() throws Exception { - // TODO: specify what InlineStatementsBuilder does - } @Test - public void testTemplateMatchOfMultipleElements() throws Exception { + public void testPatternMatchOfMultipleElements() throws Exception { CtType toBeMatchedtype = ModelUtils.buildClass(ToBeMatched.class); // getting the list of literals defined in method match1 diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple2.java b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple2.java index 55422cbd60e..20f8e05f850 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple2.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple2.java @@ -16,19 +16,6 @@ public class MatchMultiple2 { - public static Pattern createPattern(Factory factory, Consumer cfgParams) { - CtType type = factory.Type().get(MatchMultiple2.class); - return PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("matcher1").getPatternElements()) - .configureTemplateParameters() - .configureParameters(pb -> { - pb.parameter("statements1").setContainerKind(ContainerKind.LIST); - pb.parameter("statements2").setContainerKind(ContainerKind.LIST); - pb.parameter("printedValue").byVariable("something").matchInlinedStatements(); - cfgParams.accept(pb); - }) - .build(); - } - public void matcher1(List something) { statements1.S(); statements2.S(); From 0b43f8b1a6089c61fa36755a61da1bc800e2df34 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Sat, 12 May 2018 13:28:28 +0200 Subject: [PATCH 086/131] fix test --- src/test/java/spoon/test/template/PatternTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index 6919d9aeb41..d81f51cd348 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -574,10 +574,11 @@ public void testMatchPossesiveMultiValueMinCount2() throws Exception { for (int count = 0; count < 5; count++) { final int countFinal = count; Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(ctClass).setBodyOfMethod("matcher1").getPatternElements()) +.configureTemplateParameters() .configureParameters(pb -> { pb.parameter("statements1").setContainerKind(ContainerKind.LIST).setMatchingStrategy(Quantifier.GREEDY); pb.parameter("statements2").setContainerKind(ContainerKind.LIST).setMatchingStrategy(Quantifier.POSSESSIVE).setMinOccurence(countFinal).setMaxOccurence(countFinal); - pb.parameter("inlinedSysOut").byVariable("something").setMatchingStrategy(Quantifier.POSSESSIVE).setContainerKind(ContainerKind.LIST).matchInlinedStatements(); + pb.parameter("inlinedSysOut").byVariable("something").setMatchingStrategy(Quantifier.POSSESSIVE).setContainerKind(ContainerKind.LIST).setMinOccurence(2).matchInlinedStatements(); }) .build(); From 488abca5430bdd2ecdabc8dd79c2e4d08cb4bf8e Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Sat, 12 May 2018 13:30:14 +0200 Subject: [PATCH 087/131] fix test on MS Windows --- src/test/java/spoon/test/template/PatternTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index d81f51cd348..2c6ca69998a 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -1118,6 +1118,7 @@ public void testPatternParameters() { @Test public void testPatternToString() { //contract: Pattern can be printed to String and each parameter is defined there + System.setProperty("line.separator", "\n"); Factory f = ModelUtils.build( new File("./src/test/java/spoon/test/template/testclasses/replace/DPPSample1.java"), new File("./src/test/java/spoon/test/template/testclasses/replace") From c14eeb20018559c767993241a708bfb1e427e2b6 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Sat, 12 May 2018 16:32:17 +0200 Subject: [PATCH 088/131] up --- .../java/spoon/pattern/PatternBuilder.java | 14 +-- .../java/spoon/template/Substitution.java | 2 +- .../java/spoon/template/TemplateBuilder.java | 2 +- .../java/spoon/test/template/PatternTest.java | 114 +++++++++--------- 4 files changed, 66 insertions(+), 66 deletions(-) diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index e0b6a201a8a..51019c5b5ac 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -402,8 +402,8 @@ PatternBuilder configureLocalParameters(Consumer parametersBu * * @return this to support fluent API */ - public PatternBuilder configureTemplateParameters() { - return configureTemplateParameters(templateTypeRef.getTypeDeclaration(), null); + public PatternBuilder configurePatternParameters() { + return configurePatternParameters(templateTypeRef.getTypeDeclaration(), null); } /** @@ -412,8 +412,8 @@ public PatternBuilder configureTemplateParameters() { * when parameter value types influences which AST nodes will be the target of substitution in legacy template patterns * @return this to support fluent API */ - public PatternBuilder configureTemplateParameters(Map templateParameters) { - return configureTemplateParameters(templateTypeRef.getTypeDeclaration(), templateParameters); + public PatternBuilder configurePatternParameters(Map templateParameters) { + return configurePatternParameters(templateTypeRef.getTypeDeclaration(), templateParameters); } /** @@ -423,10 +423,10 @@ public PatternBuilder configureTemplateParameters(Map templatePa * because parameter value types influences which AST nodes will be the target of substitution * @return this to support fluent API */ - private PatternBuilder configureTemplateParameters(CtType templateType, Map templateParameters) { + private PatternBuilder configurePatternParameters(CtType templateType, Map templateParameters) { configureParameters(pb -> { templateType.map(new AllTypeMembersFunction()).forEach((CtTypeMember typeMember) -> { - configureTemplateParameter(templateType, templateParameters, pb, typeMember); + configurePatternParameter(templateType, templateParameters, pb, typeMember); }); if (templateParameters != null) { //configure template parameters based on parameter values only - these without any declaration in Template @@ -448,7 +448,7 @@ private PatternBuilder configureTemplateParameters(CtType templateType, Map templateType, Map templateParameters, ParametersBuilder pb, CtTypeMember typeMember) { + private void configurePatternParameter(CtType templateType, Map templateParameters, ParametersBuilder pb, CtTypeMember typeMember) { Factory f = typeMember.getFactory(); CtTypeReference templateParamRef = f.Type().createReference(TemplateParameter.class); CtTypeReference typeReferenceRef = f.Type().createReference(CtTypeReference.class); diff --git a/src/main/java/spoon/template/Substitution.java b/src/main/java/spoon/template/Substitution.java index a1ed8c2bd89..30625b7ce75 100644 --- a/src/main/java/spoon/template/Substitution.java +++ b/src/main/java/spoon/template/Substitution.java @@ -107,7 +107,7 @@ public static > void insertAll(CtType targetType, T tem public static > T createTypeFromTemplate(String qualifiedTypeName, CtType templateOfType, Map templateParameters) { return PatternBuilder .create(templateOfType) - .configureTemplateParameters(templateParameters) + .configurePatternParameters(templateParameters) .build() .createType(templateOfType.getFactory(), qualifiedTypeName, templateParameters); } diff --git a/src/main/java/spoon/template/TemplateBuilder.java b/src/main/java/spoon/template/TemplateBuilder.java index 08a614041de..8cc4a872d06 100644 --- a/src/main/java/spoon/template/TemplateBuilder.java +++ b/src/main/java/spoon/template/TemplateBuilder.java @@ -98,7 +98,7 @@ public static TemplateBuilder createPattern(CtElement templateRoot, CtClass t Map templateParameters = template == null ? null : Parameters.getTemplateParametersAsMap(f, null, template); //legacy templates always automatically simplifies generated code pb.setAutoSimplifySubstitutions(true); - pb.configureTemplateParameters(templateParameters); + pb.configurePatternParameters(templateParameters); return new TemplateBuilder(templateType, pb, template); } diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index 2c6ca69998a..b95ecba5edb 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -2,8 +2,6 @@ import org.junit.Test; import spoon.Launcher; -import spoon.OutputType; -import spoon.SpoonModelBuilder; import spoon.pattern.ConflictResolutionMode; import spoon.pattern.ParametersBuilder; import spoon.pattern.Pattern; @@ -27,12 +25,9 @@ import spoon.reflect.declaration.ModifierKind; import spoon.reflect.factory.Factory; import spoon.reflect.meta.ContainerKind; -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.visitor.DefaultJavaPrettyPrinter; import spoon.reflect.visitor.filter.TypeFilter; import spoon.support.compiler.FileSystemFile; import spoon.support.util.ParameterValueProvider; @@ -52,7 +47,6 @@ import spoon.test.template.testclasses.match.MatchWithParameterCondition; import spoon.test.template.testclasses.match.MatchWithParameterType; import spoon.test.template.testclasses.replace.DPPSample1; -import spoon.test.template.testclasses.replace.NewPattern; import spoon.test.template.testclasses.replace.OldPattern; import spoon.test.template.testclasses.types.AClassWithMethodsAndRefs; import spoon.testing.utils.ModelUtils; @@ -495,7 +489,9 @@ public void testMatchPossesiveMultiValueMaxCount4() throws Exception { List matches = pattern.getMatches(ctClass); - // why method matcher1 is not matched? + // only testMatch1 is matched + // method matcher1, from which the template has been built, is not matched + // because the possessive quantifier eats its two statements, here remains nothing for the second template statement, which cannot match then. assertEquals(1, matches.size()); Match match = matches.get(0); @@ -536,7 +532,7 @@ public void testMatchPossesiveMultiValueMinCount() throws Exception { final int countFinal = count; CtType type = ctClass.getFactory().Type().get(MatchMultiple3.class); Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("matcher1").getPatternElements()) - .configureTemplateParameters() + .configurePatternParameters() .configureParameters(pb -> { pb.parameter("statements1").setContainerKind(ContainerKind.LIST).setMatchingStrategy(Quantifier.GREEDY); pb.parameter("statements2").setContainerKind(ContainerKind.LIST).setMatchingStrategy(Quantifier.POSSESSIVE).setMinOccurence(countFinal).setMaxOccurence(countFinal); @@ -574,7 +570,7 @@ public void testMatchPossesiveMultiValueMinCount2() throws Exception { for (int count = 0; count < 5; count++) { final int countFinal = count; Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(ctClass).setBodyOfMethod("matcher1").getPatternElements()) -.configureTemplateParameters() +.configurePatternParameters() .configureParameters(pb -> { pb.parameter("statements1").setContainerKind(ContainerKind.LIST).setMatchingStrategy(Quantifier.GREEDY); pb.parameter("statements2").setContainerKind(ContainerKind.LIST).setMatchingStrategy(Quantifier.POSSESSIVE).setMinOccurence(countFinal).setMaxOccurence(countFinal); @@ -593,7 +589,7 @@ public void testMatchPossesiveMultiValueMinCount2() throws Exception { for (int count = 5; count < 7; count++) { final int countFinal = count; Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(ctClass).setBodyOfMethod("matcher1").getPatternElements()) - .configureTemplateParameters().build(); + .configurePatternParameters().build(); // pb.parameter("statements1").setMatchingStrategy(Quantifier.GREEDY); // pb.parameter("statements2").setMatchingStrategy(Quantifier.POSSESSIVE).setMinOccurence(countFinal).setMaxOccurence(countFinal); // pb.parameter("inlinedSysOut").setMatchingStrategy(Quantifier.POSSESSIVE).setContainerKind(ContainerKind.LIST).setMinOccurence(2); @@ -607,36 +603,35 @@ public void testMatchPossesiveMultiValueMinCount2() throws Exception { } -// Martin commented this one -// @Test -// public void testMatchGreedyMultiValueMinCount2() throws Exception { -// //contract: check possessive matching with min count limit and GREEDY back off -// CtType ctClass = ModelUtils.buildClass(MatchMultiple2.class); -// for (int i = 0; i < 7; i++) { -// final int count = i; -// CtType type = ctClass.getFactory().Type().get(MatchMultiple2.class); -// Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("matcher1").getPatternElements()) -// -// .configureParameters(pb -> { -// pb.parameter("statements1").setContainerKind(ContainerKind.LIST).setMatchingStrategy(Quantifier.RELUCTANT); -// pb.parameter("statements2").setContainerKind(ContainerKind.LIST).setMatchingStrategy(Quantifier.GREEDY).setMaxOccurence(count); -// pb.parameter("printedValue").byVariable("something").matchInlinedStatements(); -// pb.parameter("printedValue").setMatchingStrategy(Quantifier.GREEDY).setContainerKind(ContainerKind.LIST).setMinOccurence(2); -// }) -// .build(); -// List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0).getBody()); -// if (count < 7) { -// //the last template has nothing to match -> no match -// assertEquals("count=" + count, 1, matches.size()); -// assertEquals("count=" + count, Math.max(0, 3 - count), getCollectionSize(matches.get(0).getParameters().getValue("statements1"))); -// assertEquals("count=" + count, count - Math.max(0, count - 4), getCollectionSize(matches.get(0).getParameters().getValue("statements2"))); -// assertEquals("count=" + count, Math.max(2, 3 - Math.max(0, count - 3)), getCollectionSize(matches.get(0).getParameters().getValue("printedValue"))); -// } else { -// //the possessive matcher eat too much. There is no target element for last `printedValue` variable -// assertEquals("count=" + count, 0, matches.size()); -// } -// } -// } + @Test + public void testMatchGreedyMultiValueMinCount2() throws Exception { + //contract: check possessive matching with min count limit and GREEDY back off + CtType ctClass = ModelUtils.buildClass(MatchMultiple2.class); + for (int i = 0; i < 7; i++) { + final int count = i; + CtType type = ctClass.getFactory().Type().get(MatchMultiple2.class); + Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("matcher1").getPatternElements()) + .configurePatternParameters() + .configureParameters(pb -> { + pb.parameter("statements1").setContainerKind(ContainerKind.LIST).setMatchingStrategy(Quantifier.RELUCTANT); + pb.parameter("statements2").setContainerKind(ContainerKind.LIST).setMatchingStrategy(Quantifier.GREEDY).setMaxOccurence(count); + pb.parameter("printedValue").byVariable("something").matchInlinedStatements(); + pb.parameter("printedValue").setMatchingStrategy(Quantifier.GREEDY).setContainerKind(ContainerKind.LIST).setMinOccurence(2); + }) + .build(); + List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0).getBody()); + if (count < 7) { + //the last template has nothing to match -> no match + assertEquals("count=" + count, 1, matches.size()); + assertEquals("count=" + count, Math.max(0, 3 - count), getCollectionSize(matches.get(0).getParameters().getValue("statements1"))); + assertEquals("count=" + count, count - Math.max(0, count - 4), getCollectionSize(matches.get(0).getParameters().getValue("statements2"))); + assertEquals("count=" + count, Math.max(2, 3 - Math.max(0, count - 3)), getCollectionSize(matches.get(0).getParameters().getValue("printedValue"))); + } else { + //the possessive matcher eat too much. There is no target element for last `printedValue` variable + assertEquals("count=" + count, 0, matches.size()); + } + } + } /** returns the size of the list of 0 is list is null */ private int getCollectionSize(Object list) { @@ -652,13 +647,16 @@ private int getCollectionSize(Object list) { @Test public void testMatchParameterValue() throws Exception { - //contract: if matching on the pattern itself, the matched parameter value is the originak AST node from the pattern + //contract: if matching on the pattern itself, the matched parameter value is the original AST node from the pattern // see last assertSame of this test CtType ctClass = ModelUtils.buildClass(MatchWithParameterType.class); + // pattern is System.out.println(value); + // pattern: a call to System.out.println with anything as parameter Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(ctClass).setBodyOfMethod("matcher1").getPatternElements()) .configureParameters(pb -> { + // anything in place of the variable reference value can be matched pb.parameter("value").byVariable("value"); }) .build(); @@ -682,8 +680,8 @@ public void testMatchParameterValue() throws Exception { @Test public void testMatchParameterValueType() throws Exception { - //contract: pattern parameters can be restricted to only certain types - // (here CtLiteral.class) + // contract: pattern parameters can be restricted to only certain types + // in this test case, we only match CtLiteral CtType ctClass = ModelUtils.buildClass(MatchWithParameterType.class); { // now we match only the ones with a literal as parameter @@ -780,7 +778,7 @@ public void testMatchParameterCondition() throws Exception { @Test public void testMatchOfAttribute() throws Exception { - //contract: match some nodes like template, but with some variable attributes + // contract: it is possible to match nodes based on their roles // tested methods: ParameterBuilder#byRole and ParameterBuilder#byString CtType ctClass = ModelUtils.buildClass(MatchModifiers.class); { @@ -855,7 +853,7 @@ public void testMatchOfAttribute() throws Exception { @Test public void testMatchOfMapAttribute() throws Exception { - //contract: there is support for matching annotations with different values + //contract: there is support for matching annotations with different annotation values CtType matchMapClass = ModelUtils.buildClass(MatchMap.class); { CtType type = matchMapClass.getFactory().Type().get(MatchMap.class); @@ -915,15 +913,9 @@ public void testMatchOfMapAttribute() throws Exception { } } - private Map getMap(Match match, String name) { - Object v = match.getParametersMap().get(name); - assertNotNull(v); - return ((ParameterValueProvider) v).asMap(); - } - @Test public void testMatchOfMapAttributeAndOtherAnnotations() throws Exception { - //contract: match a pattern with an "open" annotation (different values can be matched) + //contract: match a pattern with an "open" annotation (different annotations can be matched) // same test but with one more pattern parameter: allAnnotations CtType ctClass = ModelUtils.buildClass(MatchMap.class); { @@ -1199,7 +1191,7 @@ public void testPatternToString() { @Test public void testMatchSample1() throws Exception { - // contract: a super omplex pattern is well matched + // contract: a super complex pattern is well matched Factory f = ModelUtils.build( new File("./src/test/java/spoon/test/template/testclasses/replace/DPPSample1.java"), new File("./src/test/java/spoon/test/template/testclasses/replace") @@ -1276,9 +1268,7 @@ public void testAddGeneratedBy() throws Exception { @Test public void testGenerateClassWithSelfReferences() throws Exception { - // main contract: a class with methods and fields can be used as template - // using method #createType - + // main contract: a class with methods and fields can be used as pattern using method #createType // in particular, all the references to the origin class are replace by reference to the new class cloned class // creating a pattern from AClassWithMethodsAndRefs @@ -1375,6 +1365,7 @@ public void testGenerateMethodWithSelfReferences() throws Exception { @Test public void testPatternMatchOfMultipleElements() throws Exception { + // contract: one can match list of elements in hard-coded arrays (CtNewArray) CtType toBeMatchedtype = ModelUtils.buildClass(ToBeMatched.class); // getting the list of literals defined in method match1 @@ -1483,7 +1474,7 @@ private int indexOf(List list, Object o) { @Test public void testExtensionDecoupledSubstitutionVisitor() throws Exception { - //contract: substitution can be done on model, which is not based on Template + //contract: all the variable references which are declared out of the pattern model are automatically considered as pattern parameters final Launcher launcher = new Launcher(); launcher.setArgs(new String[] {"--output-type", "nooutput" }); launcher.addInputResource("./src/test/java/spoon/test/template/testclasses/logger/Logger.java"); @@ -1522,4 +1513,13 @@ public void testExtensionDecoupledSubstitutionVisitor() throws Exception { assertTrue(aTry.getBody().getStatements().size() > 1); } + + private Map getMap(Match match, String name) { + Object v = match.getParametersMap().get(name); + assertNotNull(v); + return ((ParameterValueProvider) v).asMap(); + } + + + } From 5dd9c9216f0c79b029caba117a93597a2c2a8197 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Sun, 13 May 2018 20:43:22 +0200 Subject: [PATCH 089/131] up --- doc/pattern.md | 365 +++--------------- .../java/spoon/pattern/matcher/Match.java | 2 +- 2 files changed, 54 insertions(+), 313 deletions(-) diff --git a/doc/pattern.md b/doc/pattern.md index 3f5e766a161..d03b7431b7c 100644 --- a/doc/pattern.md +++ b/doc/pattern.md @@ -2,24 +2,18 @@ title: Spoon Patterns --- -**Spoon pattern's** aimas at matching and transforming code elements. A **Spoon pattern** is based on a code element (for example -expression, statement, block, method, constuctor, type member, -interface, type, ... any Spoon model subtree), -where parts of that code may be **pattern parameters**. +**Spoon patterns** aim at finding code elements using patterns. A **Spoon pattern** is based on a one or several AST nodes, +where some parts of the AST are **pattern parameters**. -**Spoon pattern's** can be used in two ways: - -A) **to search for a code**. The found code is same like code of **Spoon pattern**, -where code on position of **pattern parameter** may be arbitrary and is copied -as value of **pattern parameter**. We call this operation **Matching**. +Once you have a pattern, one matches again some code: ```java Factory spoonFactory = ... //build a Spoon pattern -Pattern spoonTemplate = ... build a spoon pattern. For example for an method ... -//search for all occurences of the method like spoonTemplate in whole model -spoonTemplate.forEachMatch(spoonFactory.getRootPackage(), (Match match) -> { - //this Consumer is called once for each method which matches with spoonTemplate +Pattern pattern = ... build a spoon pattern. For example for an method ... + +//search for all occurences of the method in the root package +pattern.forEachMatch(spoonFactory.getRootPackage(), (Match match) -> { Map parameters = match.getParametersAsMap(); CtMethod matchingMethod = match.getMatchingElement(CtMethod.class); String aNameOfMatchedMethod = parameters.get("methodName"); @@ -27,29 +21,14 @@ spoonTemplate.forEachMatch(spoonFactory.getRootPackage(), (Match match) -> { }); ``` -B) **to generate new code**. The generated code is a copy of code -of **Spoon pattern**, where each **pattern parameter** is substituted -by it's value. We call this operation **Generating**. -```java -Factory spoonFactory = ... -//build a Spoon pattern -Pattern spoonTemplate = ... build a spoon pattern. For example for an method ... -//define values for parameters -Map parameters = new HashMap<>(); -parameters.put("methodName", "i_am_an_generated_method"); -//generate a code using spoon pattern and parameters -CtMethod generatedMethod = spoonTemplate.substituteSingle(spoonFactory, CtMethod.class, parameters); -``` - -Main class: `PatternBuilder` --------------------------------------------------- +## Main class: `PatternBuilder` To create a Spoon pattern, one must use `PatternBuilder`, which takes AST nodes as input, and where you -**pattern parameters** are defined by calling PatternBuilder fluent API methods. +**pattern parameters** are defined. -The method `statement` below defines a Spoon pattern. +The code to creates a Spoon pattern using `PatternBuilder` looks like this: ```java public class Foo { @@ -60,76 +39,24 @@ public class Foo { } ``` -The code, which creates a Spoon pattern using `PatternBuilder` looks like this: ```java -Pattern t = PatternBuilder.create(factory, - //defines pattern class. - CheckBoundTemplate.class, - //defines which part of pattern class will be used as pattern model - model -> model.setBodyOfMethod("statement")) - //tells builder that all variables defined out of scope of pattern model (body of the method) - //are considered as pattern parameters - // here _col_ - .configureTemplateParameters() - //builds an instance of Pattern - .build(); +Pattern t = PatternBuilder.create( + new PatternBuilderHelper(fooClass).setBodyOfMethod("matcher1").getPatternElements()) + .configureParameters() + .build(); ``` -This pattern specifies a -statements (all statements of body of method `statement`) that is a precondition to check that a list -is smaller than a certain size. This piece of code will be injected at the -beginning of all methods dealing with size-bounded lists. This pattern has -one single pattern parameter called `_col_`. -In this case, the pattern parameter value is meant to be an expression (`CtExpression`) -that returns a Collection. - -The pattern source is -well-typed, compiles, but the binary code of the pattern is usually thrown away -and only spoon model (Abstract syntax tree) of source code is used to generated new code -or to search for matching code. +This pattern matches all statements of the body of method `statement`, ie. a precondition to check that a list +is smaller than a certain size. +This pattern has +one single pattern parameter called `_col_`, which is considered as a pattern parameter because it is declared outside of the AST node. -Generating of code using Spoon pattern -------------- +## ParametersBuilder parameters -The code at the end of this page shows how to use such spoon pattern. -One takes a spoon pattern, defines the pattern parameters, -and then one calls the pattern engine. In last line, the bound check -is injected at the beginning of a method body. - -Since the pattern is given the first method parameter which is in the -scope of the insertion location, the generated code is guaranteed to compile. -The Java compiler ensures that the pattern compiles with a given scope, the -developer is responsible for checking that the scope where she uses -pattern-generated code is consistent with the pattern scope. - -```java -Pattern t = PatternBuilder.create(...building a pattern. See code above...); -// creating a holder of parameters -Map parameters = new HashMap<>(); -parameters.put("_col_", createVariableAccess(method.getParameters().get(0))); - -// getting the final AST -CtStatement injectedCode = t.substituteSingle(factory, CtStatement.class, parameters); - -// adds the bound check at the beginning of a method -method.getBody().insertBegin(injectedCode); -``` - -## PatternBuilder parameters -The `PatternBuilder` takes all the Template parameters mentioned in the chapters above -and understands them as pattern parameters, when `PatternBuilder#configureTemplateParameters()` -is called. - -```java -Pattern t = PatternBuilder.create(...select pattern model...) - .configureTemplateParameters() - .build(); -``` +To create pattern, one use a `ParametersBuilder` in a lambda: -Next to the ways of parameter definitions mentioned above the `PatternBuilder` -allows to define parameters like this: ```java //a pattern model @@ -156,25 +83,24 @@ Pattern t = PatternBuilder.create(...select pattern model...) .build(); ``` -## PatternBuilder parameter selectors +`ParametersBuilder` has the following methods: * `byType(Class|CtTypeReference|String)` - all the references to the type defined by Class, -CtTypeReference or qualified name will be considered as pattern parameter +CtTypeReference or qualified name are considered as pattern parameter * `byLocalType(CtType searchScope, String localTypeSimpleName)` - all the types defined in `searchScope` -and having simpleName equal to `localTypeSimpleName` will be considered as pattern parameter +and having simpleName equal to `localTypeSimpleName` are considered as pattern parameter * `byVariable(CtVariable|String)` - all read/write variable references to CtVariable -or any variable with provided simple name will be considered as pattern parameter -* byInvocation(CtMethod method) - each invocation of `method` will be considered as pattern parameter +or any variable with provided simple name are considered as pattern parameter +* `byInvocation(CtMethod method)` - each invocation of `method` are considered as pattern parameter * `parametersByVariable(CtVariable|String... variableName)` - each `variableName` is a name of a variable which references instance of a class with fields. Each such field is considered as pattern parameter. * `byTemplateParameterReference(CtVariable)` - the reference to variable of type `TemplateParameter` is handled as pattern parameter using all the rules defined in the chapters above. * `byFilter(Filter)` - any pattern model element, where `Filter.accept(element)` returns true is a pattern parameter. * `attributeOfElementByFilter(CtRole role, Filter filter)` - the attribute defined by `role` of all -pattern model elements, where `Filter.accept(element)` returns true is a pattern parameter. -It can be used to define a varible on any CtElement attribute. E.g. method modifiers or throwables, ... -* `byString(String name)` - all pattern model string attributes whose value **is equal to** `name` are considered as pattern parameter.This can be used to define full name of the methods and fields, etc. -* `bySubstring(String stringMarker)` - all pattern model string attributes whose value **contains** +pattern model elements, where `Filter.accept(element)` returns true is a pattern parameter. It can be used to define a varible on any CtElement attribute. E.g. method modifiers or throwables, ... +* `byString(String name)` - all pattern model string attributes whose value is equal to `name` are considered as pattern parameter.This can be used to define full name of the methods and fields, etc. +* `bySubstring(String stringMarker)` - all pattern model string attributes whose value contains whole string or a substring equal to `stringMarker`are pattern parameter. Note: only the `stringMarker` substring of the string value is substituted. Other parts of string/element name are kept unchanged. @@ -182,16 +108,8 @@ Other parts of string/element name are kept unchanged. * `byNamedElementSimpleName(String name)` - any CtNamedElement identified by it's simple name is a pattern parameter. * `byReferenceSimpleName(String name)` - any CtReference identified by it's simple name is a pattern parameter. -Note: -* `byString` and `bySubstring` are used to rename code elements. -For example to rename a method "xyz" to "abc" -* `bySimpleName`, `byNamedElementSimpleName`, `byReferenceSimpleName` -are used to replace these elements by completelly different elements. -For example to replace method invocation by an variable reference, etc. - -## PatternBuilder parameter modifiers -Any parameter of spoon pattern can be configured like this: +Any parameter of a pattern can be configured like this: * `setMinOccurence(int)` - defines minimal number of occurences of the value of this parameter during **matching**, which is needed by matcher to accept that value. @@ -200,9 +118,8 @@ which is needed by matcher to accept that value. * `setMinOccurence(n)` - defines parameter, whose value must be repeated at least n-times * `setMaxOccurence(int)` - defines maximal number of occurences of the value of this parameter during **matching**, which is accepted by matcher to accept that value. -* `setMatchingStrategy(Quantifier)` - defines how to matching engine will behave when two pattern nodes may accept the same value. - * `Quantifier#GREEDY` - Greedy quantifiers are considered "greedy" because they force the matcher to read in, or eat, -the entire input prior to attempting the next match. +* `setMatchingStrategy(Quantifier)` - defines how to matching engine arehave when two pattern nodes may accept the same value. + * `Quantifier#GREEDY` - Greedy quantifiers are considered "greedy" because they force the matcher to read in, or eat, the entire input prior to attempting the next match. If the next match attempt (the entire input) fails, the matcher backs off the input by one and tries again, repeating the process until a match is found or there are no more elements left to back off from. * `Quantifier#RELUCTANT` - The reluctant quantifier takes the opposite approach: It start at the beginning of the input, @@ -213,9 +130,9 @@ trying once (and only once) for a match. Unlike the greedy quantifiers, possessi even if doing so would allow the overall match to succeed. * `setValueType(Class type)` - defines a required type of the value. If defined the pattern matched, will match only values which are assigneable from the provided `type` * `matchCondition(Class type, Predicate matchCondition)` - defines a `Predicate`, whose method `boolean test(T)`, -will be called by pattern matcher. Template matcher accepts that value only if `test` returns true for the value. +are called by pattern matcher. Template matcher accepts that value only if `test` returns true for the value. The `setValueType(type)` is called internally too, so match condition assures both a type of value and condition on value. -* `setContainerKind(ContainerKind)` - defines what container will be used to store the value. +* `setContainerKind(ContainerKind)` - defines what container are used to store the value. * `ContainerKind#SINGLE` - only single value is accepted as a parameter value. It can be e.g. single String or single CtStatement, etc. * `ContainerKind#LIST` - The values are always stored as `List`. @@ -224,8 +141,25 @@ The `setValueType(type)` is called internally too, so match condition assures bo ## Inlining with PatternBuilder -The pattern code in spoon patterns made by `PatternBuilder` is never inlined automatically. -But you can mark code to be inlined this way: + +It is possible to inlined code, eg: + +```java +System.out.println(1); +System.out.println(2); +System.out.println(3); +``` + +can be matched by + +```java +for (int i=0; i T getMatchingElement(Class clazz) { * @param failIfMany if there is more then one matching element and `failIfMany` == true, then it throws SpoonException. * @return first matching element */ - public T getMatchingElement(Class clazz, boolean failIfMany) { + private T getMatchingElement(Class clazz, boolean failIfMany) { if (matchingElements.isEmpty()) { return null; } From 198248d40d6a1ec7c552d7905aeac82f7da6773c Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Mon, 14 May 2018 20:49:25 +0200 Subject: [PATCH 090/131] move non public API classes to package ...internal... --- .../spoon/pattern/ConflictResolutionMode.java | 2 +- .../pattern/InlineStatementsBuilder.java | 16 ++++++------ .../spoon/pattern/{matcher => }/Match.java | 5 ++-- .../java/spoon/pattern/ParametersBuilder.java | 26 +++++++++---------- src/main/java/spoon/pattern/Pattern.java | 8 +++--- .../java/spoon/pattern/PatternBuilder.java | 18 +++++++------ .../pattern/{matcher => }/Quantifier.java | 4 +-- .../{ => internal}/DefaultGenerator.java | 6 ++--- .../pattern/{ => internal}/Generator.java | 6 ++--- .../{ => internal}/PatternPrinter.java | 16 ++++++------ .../pattern/{ => internal}/ResultHolder.java | 2 +- .../SubstitutionRequestProvider.java | 4 +-- .../{ => internal}/ValueConvertor.java | 2 +- .../{ => internal}/ValueConvertorImpl.java | 2 +- .../matcher/ChainOfMatchersImpl.java | 4 +-- .../{ => internal}/matcher/Matchers.java | 4 +-- .../matcher/MatchingScanner.java | 5 ++-- .../{ => internal}/matcher/TobeMatched.java | 2 +- .../{ => internal}/node/AbstractNode.java | 4 +-- .../node/AbstractPrimitiveMatcher.java | 4 +-- .../node/AbstractRepeatableMatcher.java | 6 ++--- .../{ => internal}/node/ConstantNode.java | 10 +++---- .../{ => internal}/node/ElementNode.java | 18 ++++++------- .../{ => internal}/node/ForEachNode.java | 12 ++++----- .../{ => internal}/node/InlineNode.java | 6 ++--- .../{ => internal}/node/ListOfNodes.java | 14 +++++----- .../{ => internal}/node/MapEntryNode.java | 16 ++++++------ .../{ => internal}/node/ModelNode.java | 2 +- .../{ => internal}/node/ParameterNode.java | 10 +++---- .../{ => internal}/node/PrimitiveMatcher.java | 2 +- .../node/RepeatableMatcher.java | 4 +-- .../pattern/{ => internal}/node/RootNode.java | 12 ++++----- .../{ => internal}/node/StringNode.java | 12 ++++----- .../{ => internal}/node/SwitchNode.java | 12 ++++----- .../parameter/AbstractParameterInfo.java | 8 +++--- .../parameter/ListParameterInfo.java | 2 +- .../parameter/MapParameterInfo.java | 2 +- .../parameter/ParameterInfo.java | 8 +++--- .../parameter/SetParameterInfo.java | 2 +- .../java/spoon/template/TemplateMatcher.java | 10 +++---- .../java/spoon/test/template/PatternTest.java | 6 ++--- .../spoon/test/template/TemplateTest.java | 2 +- .../test/template/core/ParameterInfoTest.java | 8 +++--- .../testclasses/match/MatchMultiple.java | 2 +- 44 files changed, 164 insertions(+), 162 deletions(-) rename src/main/java/spoon/pattern/{matcher => }/Match.java (97%) rename src/main/java/spoon/pattern/{matcher => }/Quantifier.java (96%) rename src/main/java/spoon/pattern/{ => internal}/DefaultGenerator.java (97%) rename src/main/java/spoon/pattern/{ => internal}/Generator.java (96%) rename src/main/java/spoon/pattern/{ => internal}/PatternPrinter.java (95%) rename src/main/java/spoon/pattern/{ => internal}/ResultHolder.java (99%) rename src/main/java/spoon/pattern/{ => internal}/SubstitutionRequestProvider.java (93%) rename src/main/java/spoon/pattern/{ => internal}/ValueConvertor.java (97%) rename src/main/java/spoon/pattern/{ => internal}/ValueConvertorImpl.java (99%) rename src/main/java/spoon/pattern/{ => internal}/matcher/ChainOfMatchersImpl.java (96%) rename src/main/java/spoon/pattern/{ => internal}/matcher/Matchers.java (93%) rename src/main/java/spoon/pattern/{ => internal}/matcher/MatchingScanner.java (97%) rename src/main/java/spoon/pattern/{ => internal}/matcher/TobeMatched.java (99%) rename src/main/java/spoon/pattern/{ => internal}/node/AbstractNode.java (94%) rename src/main/java/spoon/pattern/{ => internal}/node/AbstractPrimitiveMatcher.java (93%) rename src/main/java/spoon/pattern/{ => internal}/node/AbstractRepeatableMatcher.java (96%) rename src/main/java/spoon/pattern/{ => internal}/node/ConstantNode.java (91%) rename src/main/java/spoon/pattern/{ => internal}/node/ElementNode.java (97%) rename src/main/java/spoon/pattern/{ => internal}/node/ForEachNode.java (95%) rename src/main/java/spoon/pattern/{ => internal}/node/InlineNode.java (92%) rename src/main/java/spoon/pattern/{ => internal}/node/ListOfNodes.java (86%) rename src/main/java/spoon/pattern/{ => internal}/node/MapEntryNode.java (91%) rename src/main/java/spoon/pattern/{ => internal}/node/ModelNode.java (99%) rename src/main/java/spoon/pattern/{ => internal}/node/ParameterNode.java (92%) rename src/main/java/spoon/pattern/{ => internal}/node/PrimitiveMatcher.java (97%) rename src/main/java/spoon/pattern/{ => internal}/node/RepeatableMatcher.java (96%) rename src/main/java/spoon/pattern/{ => internal}/node/RootNode.java (93%) rename src/main/java/spoon/pattern/{ => internal}/node/StringNode.java (97%) rename src/main/java/spoon/pattern/{ => internal}/node/SwitchNode.java (96%) rename src/main/java/spoon/pattern/{ => internal}/parameter/AbstractParameterInfo.java (98%) rename src/main/java/spoon/pattern/{ => internal}/parameter/ListParameterInfo.java (98%) rename src/main/java/spoon/pattern/{ => internal}/parameter/MapParameterInfo.java (99%) rename src/main/java/spoon/pattern/{ => internal}/parameter/ParameterInfo.java (96%) rename src/main/java/spoon/pattern/{ => internal}/parameter/SetParameterInfo.java (98%) diff --git a/src/main/java/spoon/pattern/ConflictResolutionMode.java b/src/main/java/spoon/pattern/ConflictResolutionMode.java index a1ebf3eef27..00050f81ca9 100644 --- a/src/main/java/spoon/pattern/ConflictResolutionMode.java +++ b/src/main/java/spoon/pattern/ConflictResolutionMode.java @@ -17,7 +17,7 @@ package spoon.pattern; import spoon.SpoonException; -import spoon.pattern.node.RootNode; +import spoon.pattern.internal.node.RootNode; /** * Defines what happens when before explicitly added {@link RootNode} has to be replaced by another {@link RootNode} diff --git a/src/main/java/spoon/pattern/InlineStatementsBuilder.java b/src/main/java/spoon/pattern/InlineStatementsBuilder.java index 6c97d97343d..50d9840a6b6 100644 --- a/src/main/java/spoon/pattern/InlineStatementsBuilder.java +++ b/src/main/java/spoon/pattern/InlineStatementsBuilder.java @@ -16,17 +16,19 @@ */ package spoon.pattern; +import static spoon.pattern.PatternBuilder.bodyToStatements; + import java.util.ArrayList; import java.util.List; import java.util.function.BiConsumer; import spoon.SpoonException; -import spoon.pattern.node.ForEachNode; -import spoon.pattern.node.ListOfNodes; -import spoon.pattern.node.RootNode; -import spoon.pattern.node.ParameterNode; -import spoon.pattern.node.PrimitiveMatcher; -import spoon.pattern.node.SwitchNode; +import spoon.pattern.internal.node.ForEachNode; +import spoon.pattern.internal.node.ListOfNodes; +import spoon.pattern.internal.node.ParameterNode; +import spoon.pattern.internal.node.PrimitiveMatcher; +import spoon.pattern.internal.node.RootNode; +import spoon.pattern.internal.node.SwitchNode; import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtExpression; import spoon.reflect.code.CtForEach; @@ -38,8 +40,6 @@ import spoon.reflect.reference.CtVariableReference; import spoon.reflect.visitor.CtAbstractVisitor; -import static spoon.pattern.PatternBuilder.bodyToStatements; - /** * Builds inline statements of Pattern * diff --git a/src/main/java/spoon/pattern/matcher/Match.java b/src/main/java/spoon/pattern/Match.java similarity index 97% rename from src/main/java/spoon/pattern/matcher/Match.java rename to src/main/java/spoon/pattern/Match.java index 9fe45a5c3db..ef0d26c2185 100644 --- a/src/main/java/spoon/pattern/matcher/Match.java +++ b/src/main/java/spoon/pattern/Match.java @@ -14,13 +14,12 @@ * 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.pattern.matcher; +package spoon.pattern; import java.util.List; import java.util.Map; import spoon.SpoonException; -import spoon.pattern.Pattern; import spoon.reflect.declaration.CtElement; import spoon.support.util.ParameterValueProvider; @@ -31,7 +30,7 @@ public class Match { private final List matchingElements; private final ParameterValueProvider parameters; - Match(List matches, ParameterValueProvider parameters) { + public Match(List matches, ParameterValueProvider parameters) { this.parameters = parameters; this.matchingElements = matches; } diff --git a/src/main/java/spoon/pattern/ParametersBuilder.java b/src/main/java/spoon/pattern/ParametersBuilder.java index 3f62591107f..dda4ad9ef75 100644 --- a/src/main/java/spoon/pattern/ParametersBuilder.java +++ b/src/main/java/spoon/pattern/ParametersBuilder.java @@ -16,23 +16,25 @@ */ package spoon.pattern; +import static spoon.pattern.PatternBuilder.getLocalTypeRefBySimpleName; + import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.function.Predicate; import spoon.SpoonException; -import spoon.pattern.matcher.Quantifier; -import spoon.pattern.node.ListOfNodes; -import spoon.pattern.node.MapEntryNode; -import spoon.pattern.node.ModelNode; -import spoon.pattern.node.RootNode; -import spoon.pattern.node.ParameterNode; -import spoon.pattern.node.StringNode; -import spoon.pattern.parameter.AbstractParameterInfo; -import spoon.pattern.parameter.ListParameterInfo; -import spoon.pattern.parameter.MapParameterInfo; -import spoon.pattern.parameter.ParameterInfo; +import spoon.pattern.internal.ValueConvertor; +import spoon.pattern.internal.node.ListOfNodes; +import spoon.pattern.internal.node.MapEntryNode; +import spoon.pattern.internal.node.ModelNode; +import spoon.pattern.internal.node.ParameterNode; +import spoon.pattern.internal.node.RootNode; +import spoon.pattern.internal.node.StringNode; +import spoon.pattern.internal.parameter.AbstractParameterInfo; +import spoon.pattern.internal.parameter.ListParameterInfo; +import spoon.pattern.internal.parameter.MapParameterInfo; +import spoon.pattern.internal.parameter.ParameterInfo; import spoon.reflect.code.CtArrayAccess; import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtExpression; @@ -63,8 +65,6 @@ import spoon.reflect.visitor.filter.VariableReferenceFunction; import spoon.template.TemplateParameter; -import static spoon.pattern.PatternBuilder.getLocalTypeRefBySimpleName; - /** * Used to define Pattern parameters and their mapping to Pattern model */ diff --git a/src/main/java/spoon/pattern/Pattern.java b/src/main/java/spoon/pattern/Pattern.java index 150b3f6dae7..a19cacce2a8 100644 --- a/src/main/java/spoon/pattern/Pattern.java +++ b/src/main/java/spoon/pattern/Pattern.java @@ -25,10 +25,10 @@ import java.util.Map; import spoon.SpoonException; -import spoon.pattern.matcher.Match; -import spoon.pattern.matcher.MatchingScanner; -import spoon.pattern.node.ModelNode; -import spoon.pattern.parameter.ParameterInfo; +import spoon.pattern.internal.DefaultGenerator; +import spoon.pattern.internal.matcher.MatchingScanner; +import spoon.pattern.internal.node.ModelNode; +import spoon.pattern.internal.parameter.ParameterInfo; import spoon.reflect.declaration.CtElement; import spoon.reflect.declaration.CtPackage; import spoon.reflect.declaration.CtType; diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index 51019c5b5ac..ebe41f92293 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -29,12 +29,14 @@ import java.util.function.Function; import spoon.SpoonException; -import spoon.pattern.node.ElementNode; -import spoon.pattern.node.ListOfNodes; -import spoon.pattern.node.ModelNode; -import spoon.pattern.node.RootNode; -import spoon.pattern.parameter.AbstractParameterInfo; -import spoon.pattern.parameter.ParameterInfo; +import spoon.pattern.internal.ValueConvertor; +import spoon.pattern.internal.ValueConvertorImpl; +import spoon.pattern.internal.node.ElementNode; +import spoon.pattern.internal.node.ListOfNodes; +import spoon.pattern.internal.node.ModelNode; +import spoon.pattern.internal.node.RootNode; +import spoon.pattern.internal.parameter.AbstractParameterInfo; +import spoon.pattern.internal.parameter.ParameterInfo; import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtInvocation; import spoon.reflect.code.CtLiteral; @@ -338,7 +340,7 @@ static List bodyToStatements(CtStatement statementOrBlock) /** * @return default {@link ValueConvertor}, which will be assigned to all new {@link ParameterInfo}s */ - public ValueConvertor getDefaultValueConvertor() { + ValueConvertor getDefaultValueConvertor() { return valueConvertor; } @@ -346,7 +348,7 @@ public ValueConvertor getDefaultValueConvertor() { * @param valueConvertor default {@link ValueConvertor}, which will be assigned to all {@link ParameterInfo}s created after this call * @return this to support fluent API */ - public PatternBuilder setDefaultValueConvertor(ValueConvertor valueConvertor) { + PatternBuilder setDefaultValueConvertor(ValueConvertor valueConvertor) { this.valueConvertor = valueConvertor; return this; } diff --git a/src/main/java/spoon/pattern/matcher/Quantifier.java b/src/main/java/spoon/pattern/Quantifier.java similarity index 96% rename from src/main/java/spoon/pattern/matcher/Quantifier.java rename to src/main/java/spoon/pattern/Quantifier.java index ca9103683fd..93498c89394 100644 --- a/src/main/java/spoon/pattern/matcher/Quantifier.java +++ b/src/main/java/spoon/pattern/Quantifier.java @@ -14,9 +14,9 @@ * 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.pattern.matcher; +package spoon.pattern; -import spoon.pattern.node.RootNode; +import spoon.pattern.internal.node.RootNode; /** * Defines a strategy used to resolve conflict between two {@link RootNode}s diff --git a/src/main/java/spoon/pattern/DefaultGenerator.java b/src/main/java/spoon/pattern/internal/DefaultGenerator.java similarity index 97% rename from src/main/java/spoon/pattern/DefaultGenerator.java rename to src/main/java/spoon/pattern/internal/DefaultGenerator.java index fbada36cb28..18d42550f4b 100644 --- a/src/main/java/spoon/pattern/DefaultGenerator.java +++ b/src/main/java/spoon/pattern/internal/DefaultGenerator.java @@ -14,10 +14,10 @@ * 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.pattern; +package spoon.pattern.internal; -import spoon.pattern.node.RootNode; -import spoon.pattern.parameter.ParameterInfo; +import spoon.pattern.internal.node.RootNode; +import spoon.pattern.internal.parameter.ParameterInfo; import spoon.reflect.code.CtCodeElement; import spoon.reflect.code.CtComment; import spoon.reflect.cu.CompilationUnit; diff --git a/src/main/java/spoon/pattern/Generator.java b/src/main/java/spoon/pattern/internal/Generator.java similarity index 96% rename from src/main/java/spoon/pattern/Generator.java rename to src/main/java/spoon/pattern/internal/Generator.java index 59bc06b179b..166c6cbf979 100644 --- a/src/main/java/spoon/pattern/Generator.java +++ b/src/main/java/spoon/pattern/internal/Generator.java @@ -14,12 +14,12 @@ * 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.pattern; +package spoon.pattern.internal; import java.util.List; -import spoon.pattern.node.RootNode; -import spoon.pattern.parameter.ParameterInfo; +import spoon.pattern.internal.node.RootNode; +import spoon.pattern.internal.parameter.ParameterInfo; import spoon.reflect.declaration.CtElement; import spoon.reflect.factory.Factory; import spoon.support.util.ParameterValueProvider; diff --git a/src/main/java/spoon/pattern/PatternPrinter.java b/src/main/java/spoon/pattern/internal/PatternPrinter.java similarity index 95% rename from src/main/java/spoon/pattern/PatternPrinter.java rename to src/main/java/spoon/pattern/internal/PatternPrinter.java index ba65ad7bd64..536413190d4 100644 --- a/src/main/java/spoon/pattern/PatternPrinter.java +++ b/src/main/java/spoon/pattern/internal/PatternPrinter.java @@ -14,7 +14,7 @@ * 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.pattern; +package spoon.pattern.internal; import java.util.ArrayList; import java.util.List; @@ -23,13 +23,13 @@ import java.util.function.Consumer; import spoon.Metamodel; -import spoon.pattern.node.ConstantNode; -import spoon.pattern.node.ElementNode; -import spoon.pattern.node.ListOfNodes; -import spoon.pattern.node.InlineNode; -import spoon.pattern.node.ParameterNode; -import spoon.pattern.node.RootNode; -import spoon.pattern.parameter.ParameterInfo; +import spoon.pattern.internal.node.ConstantNode; +import spoon.pattern.internal.node.ElementNode; +import spoon.pattern.internal.node.InlineNode; +import spoon.pattern.internal.node.ListOfNodes; +import spoon.pattern.internal.node.ParameterNode; +import spoon.pattern.internal.node.RootNode; +import spoon.pattern.internal.parameter.ParameterInfo; import spoon.reflect.code.CtComment.CommentType; import spoon.reflect.code.CtInvocation; import spoon.reflect.code.CtLocalVariable; diff --git a/src/main/java/spoon/pattern/ResultHolder.java b/src/main/java/spoon/pattern/internal/ResultHolder.java similarity index 99% rename from src/main/java/spoon/pattern/ResultHolder.java rename to src/main/java/spoon/pattern/internal/ResultHolder.java index 7561fa50988..1a0c9c512c8 100644 --- a/src/main/java/spoon/pattern/ResultHolder.java +++ b/src/main/java/spoon/pattern/internal/ResultHolder.java @@ -14,7 +14,7 @@ * 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.pattern; +package spoon.pattern.internal; import java.util.ArrayList; import java.util.Collections; diff --git a/src/main/java/spoon/pattern/SubstitutionRequestProvider.java b/src/main/java/spoon/pattern/internal/SubstitutionRequestProvider.java similarity index 93% rename from src/main/java/spoon/pattern/SubstitutionRequestProvider.java rename to src/main/java/spoon/pattern/internal/SubstitutionRequestProvider.java index 5c163c8aeb0..babe01a769d 100644 --- a/src/main/java/spoon/pattern/SubstitutionRequestProvider.java +++ b/src/main/java/spoon/pattern/internal/SubstitutionRequestProvider.java @@ -14,9 +14,9 @@ * 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.pattern; +package spoon.pattern.internal; -import spoon.pattern.node.RootNode; +import spoon.pattern.internal.node.RootNode; /** * Maps AST model object to the {@link RootNode} diff --git a/src/main/java/spoon/pattern/ValueConvertor.java b/src/main/java/spoon/pattern/internal/ValueConvertor.java similarity index 97% rename from src/main/java/spoon/pattern/ValueConvertor.java rename to src/main/java/spoon/pattern/internal/ValueConvertor.java index 4b8980ab79e..e19ef7127a5 100644 --- a/src/main/java/spoon/pattern/ValueConvertor.java +++ b/src/main/java/spoon/pattern/internal/ValueConvertor.java @@ -14,7 +14,7 @@ * 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.pattern; +package spoon.pattern.internal; import spoon.reflect.factory.Factory; diff --git a/src/main/java/spoon/pattern/ValueConvertorImpl.java b/src/main/java/spoon/pattern/internal/ValueConvertorImpl.java similarity index 99% rename from src/main/java/spoon/pattern/ValueConvertorImpl.java rename to src/main/java/spoon/pattern/internal/ValueConvertorImpl.java index b1a464194ca..12c92fe0ccb 100644 --- a/src/main/java/spoon/pattern/ValueConvertorImpl.java +++ b/src/main/java/spoon/pattern/internal/ValueConvertorImpl.java @@ -14,7 +14,7 @@ * 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.pattern; +package spoon.pattern.internal; import java.util.List; diff --git a/src/main/java/spoon/pattern/matcher/ChainOfMatchersImpl.java b/src/main/java/spoon/pattern/internal/matcher/ChainOfMatchersImpl.java similarity index 96% rename from src/main/java/spoon/pattern/matcher/ChainOfMatchersImpl.java rename to src/main/java/spoon/pattern/internal/matcher/ChainOfMatchersImpl.java index 9747deb4185..e7a34e96503 100644 --- a/src/main/java/spoon/pattern/matcher/ChainOfMatchersImpl.java +++ b/src/main/java/spoon/pattern/internal/matcher/ChainOfMatchersImpl.java @@ -14,12 +14,12 @@ * 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.pattern.matcher; +package spoon.pattern.internal.matcher; import java.util.List; import spoon.SpoonException; -import spoon.pattern.node.RootNode; +import spoon.pattern.internal.node.RootNode; /** * Chain of {@link RootNode}s. {@link RootNode}s are processed in the same order as they were added into chain diff --git a/src/main/java/spoon/pattern/matcher/Matchers.java b/src/main/java/spoon/pattern/internal/matcher/Matchers.java similarity index 93% rename from src/main/java/spoon/pattern/matcher/Matchers.java rename to src/main/java/spoon/pattern/internal/matcher/Matchers.java index 3cce0b9da4d..1e828fd15a9 100644 --- a/src/main/java/spoon/pattern/matcher/Matchers.java +++ b/src/main/java/spoon/pattern/internal/matcher/Matchers.java @@ -14,9 +14,9 @@ * 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.pattern.matcher; +package spoon.pattern.internal.matcher; -import spoon.pattern.node.RootNode; +import spoon.pattern.internal.node.RootNode; /** * A container of {@link RootNode}s. diff --git a/src/main/java/spoon/pattern/matcher/MatchingScanner.java b/src/main/java/spoon/pattern/internal/matcher/MatchingScanner.java similarity index 97% rename from src/main/java/spoon/pattern/matcher/MatchingScanner.java rename to src/main/java/spoon/pattern/internal/matcher/MatchingScanner.java index fc0f72cd5a6..4b370d32fce 100644 --- a/src/main/java/spoon/pattern/matcher/MatchingScanner.java +++ b/src/main/java/spoon/pattern/internal/matcher/MatchingScanner.java @@ -14,7 +14,7 @@ * 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.pattern.matcher; +package spoon.pattern.internal.matcher; import java.util.Collection; import java.util.Collections; @@ -23,7 +23,8 @@ import java.util.Set; import spoon.SpoonException; -import spoon.pattern.node.ModelNode; +import spoon.pattern.Match; +import spoon.pattern.internal.node.ModelNode; import spoon.reflect.declaration.CtElement; import spoon.reflect.meta.ContainerKind; import spoon.reflect.path.CtRole; diff --git a/src/main/java/spoon/pattern/matcher/TobeMatched.java b/src/main/java/spoon/pattern/internal/matcher/TobeMatched.java similarity index 99% rename from src/main/java/spoon/pattern/matcher/TobeMatched.java rename to src/main/java/spoon/pattern/internal/matcher/TobeMatched.java index 3379fb130dd..2cf886950fb 100644 --- a/src/main/java/spoon/pattern/matcher/TobeMatched.java +++ b/src/main/java/spoon/pattern/internal/matcher/TobeMatched.java @@ -14,7 +14,7 @@ * 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.pattern.matcher; +package spoon.pattern.internal.matcher; import java.util.ArrayList; import java.util.Collection; diff --git a/src/main/java/spoon/pattern/node/AbstractNode.java b/src/main/java/spoon/pattern/internal/node/AbstractNode.java similarity index 94% rename from src/main/java/spoon/pattern/node/AbstractNode.java rename to src/main/java/spoon/pattern/internal/node/AbstractNode.java index 43bfbc8b8f2..abe646275e4 100644 --- a/src/main/java/spoon/pattern/node/AbstractNode.java +++ b/src/main/java/spoon/pattern/internal/node/AbstractNode.java @@ -14,9 +14,9 @@ * 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.pattern.node; +package spoon.pattern.internal.node; -import spoon.pattern.PatternPrinter; +import spoon.pattern.internal.PatternPrinter; /** * Represents a parameterized Pattern ValueResolver, which can be used diff --git a/src/main/java/spoon/pattern/node/AbstractPrimitiveMatcher.java b/src/main/java/spoon/pattern/internal/node/AbstractPrimitiveMatcher.java similarity index 93% rename from src/main/java/spoon/pattern/node/AbstractPrimitiveMatcher.java rename to src/main/java/spoon/pattern/internal/node/AbstractPrimitiveMatcher.java index 89e9d2fdd2f..76f7959216b 100644 --- a/src/main/java/spoon/pattern/node/AbstractPrimitiveMatcher.java +++ b/src/main/java/spoon/pattern/internal/node/AbstractPrimitiveMatcher.java @@ -14,9 +14,9 @@ * 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.pattern.node; +package spoon.pattern.internal.node; -import spoon.pattern.matcher.TobeMatched; +import spoon.pattern.internal.matcher.TobeMatched; /** * Delivers to be substituted value diff --git a/src/main/java/spoon/pattern/node/AbstractRepeatableMatcher.java b/src/main/java/spoon/pattern/internal/node/AbstractRepeatableMatcher.java similarity index 96% rename from src/main/java/spoon/pattern/node/AbstractRepeatableMatcher.java rename to src/main/java/spoon/pattern/internal/node/AbstractRepeatableMatcher.java index 6726158f6f7..da82b4d4633 100644 --- a/src/main/java/spoon/pattern/node/AbstractRepeatableMatcher.java +++ b/src/main/java/spoon/pattern/internal/node/AbstractRepeatableMatcher.java @@ -14,11 +14,11 @@ * 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.pattern.node; +package spoon.pattern.internal.node; import spoon.SpoonException; -import spoon.pattern.matcher.Matchers; -import spoon.pattern.matcher.TobeMatched; +import spoon.pattern.internal.matcher.Matchers; +import spoon.pattern.internal.matcher.TobeMatched; /** * Defines algorithm of repeatable matcher. diff --git a/src/main/java/spoon/pattern/node/ConstantNode.java b/src/main/java/spoon/pattern/internal/node/ConstantNode.java similarity index 91% rename from src/main/java/spoon/pattern/node/ConstantNode.java rename to src/main/java/spoon/pattern/internal/node/ConstantNode.java index f0f3d96c91d..64ea195683a 100644 --- a/src/main/java/spoon/pattern/node/ConstantNode.java +++ b/src/main/java/spoon/pattern/internal/node/ConstantNode.java @@ -14,14 +14,14 @@ * 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.pattern.node; +package spoon.pattern.internal.node; import java.util.function.BiConsumer; -import spoon.pattern.Generator; -import spoon.pattern.ResultHolder; -import spoon.pattern.matcher.Quantifier; -import spoon.pattern.parameter.ParameterInfo; +import spoon.pattern.Quantifier; +import spoon.pattern.internal.Generator; +import spoon.pattern.internal.ResultHolder; +import spoon.pattern.internal.parameter.ParameterInfo; import spoon.support.util.ParameterValueProvider; /** diff --git a/src/main/java/spoon/pattern/node/ElementNode.java b/src/main/java/spoon/pattern/internal/node/ElementNode.java similarity index 97% rename from src/main/java/spoon/pattern/node/ElementNode.java rename to src/main/java/spoon/pattern/internal/node/ElementNode.java index 12b00f12f8f..f7f16eaeadd 100644 --- a/src/main/java/spoon/pattern/node/ElementNode.java +++ b/src/main/java/spoon/pattern/internal/node/ElementNode.java @@ -14,7 +14,9 @@ * 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.pattern.node; +package spoon.pattern.internal.node; + +import static spoon.pattern.internal.matcher.TobeMatched.getMatchedParameters; import java.util.ArrayList; import java.util.Collections; @@ -29,20 +31,18 @@ import spoon.Metamodel; import spoon.SpoonException; -import spoon.pattern.Generator; -import spoon.pattern.ResultHolder; -import spoon.pattern.matcher.Matchers; -import spoon.pattern.matcher.Quantifier; -import spoon.pattern.matcher.TobeMatched; -import spoon.pattern.parameter.ParameterInfo; +import spoon.pattern.Quantifier; +import spoon.pattern.internal.Generator; +import spoon.pattern.internal.ResultHolder; +import spoon.pattern.internal.matcher.Matchers; +import spoon.pattern.internal.matcher.TobeMatched; +import spoon.pattern.internal.parameter.ParameterInfo; import spoon.reflect.declaration.CtElement; import spoon.reflect.meta.ContainerKind; import spoon.reflect.path.CtRole; import spoon.reflect.reference.CtExecutableReference; import spoon.support.util.ParameterValueProvider; -import static spoon.pattern.matcher.TobeMatched.getMatchedParameters; - /** * Generates/Matches a copy of a single CtElement AST node with all it's children (whole AST tree of the root CtElement) */ diff --git a/src/main/java/spoon/pattern/node/ForEachNode.java b/src/main/java/spoon/pattern/internal/node/ForEachNode.java similarity index 95% rename from src/main/java/spoon/pattern/node/ForEachNode.java rename to src/main/java/spoon/pattern/internal/node/ForEachNode.java index eacd37fc4e6..c7e64efaae7 100644 --- a/src/main/java/spoon/pattern/node/ForEachNode.java +++ b/src/main/java/spoon/pattern/internal/node/ForEachNode.java @@ -14,16 +14,16 @@ * 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.pattern.node; +package spoon.pattern.internal.node; import java.util.Map; import java.util.function.BiConsumer; -import spoon.pattern.Generator; -import spoon.pattern.ResultHolder; -import spoon.pattern.matcher.Quantifier; -import spoon.pattern.matcher.TobeMatched; -import spoon.pattern.parameter.ParameterInfo; +import spoon.pattern.Quantifier; +import spoon.pattern.internal.Generator; +import spoon.pattern.internal.ResultHolder; +import spoon.pattern.internal.matcher.TobeMatched; +import spoon.pattern.internal.parameter.ParameterInfo; import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtExpression; import spoon.reflect.code.CtForEach; diff --git a/src/main/java/spoon/pattern/node/InlineNode.java b/src/main/java/spoon/pattern/internal/node/InlineNode.java similarity index 92% rename from src/main/java/spoon/pattern/node/InlineNode.java rename to src/main/java/spoon/pattern/internal/node/InlineNode.java index 112999679a3..507caa6ebad 100644 --- a/src/main/java/spoon/pattern/node/InlineNode.java +++ b/src/main/java/spoon/pattern/internal/node/InlineNode.java @@ -14,10 +14,10 @@ * 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.pattern.node; +package spoon.pattern.internal.node; -import spoon.pattern.Generator; -import spoon.pattern.ResultHolder; +import spoon.pattern.internal.Generator; +import spoon.pattern.internal.ResultHolder; import spoon.support.util.ParameterValueProvider; /** diff --git a/src/main/java/spoon/pattern/node/ListOfNodes.java b/src/main/java/spoon/pattern/internal/node/ListOfNodes.java similarity index 86% rename from src/main/java/spoon/pattern/node/ListOfNodes.java rename to src/main/java/spoon/pattern/internal/node/ListOfNodes.java index bcaa27ea4bd..ec1533c1739 100644 --- a/src/main/java/spoon/pattern/node/ListOfNodes.java +++ b/src/main/java/spoon/pattern/internal/node/ListOfNodes.java @@ -14,17 +14,17 @@ * 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.pattern.node; +package spoon.pattern.internal.node; import java.util.List; import java.util.function.BiConsumer; -import spoon.pattern.Generator; -import spoon.pattern.ResultHolder; -import spoon.pattern.matcher.ChainOfMatchersImpl; -import spoon.pattern.matcher.Matchers; -import spoon.pattern.matcher.TobeMatched; -import spoon.pattern.parameter.ParameterInfo; +import spoon.pattern.internal.Generator; +import spoon.pattern.internal.ResultHolder; +import spoon.pattern.internal.matcher.ChainOfMatchersImpl; +import spoon.pattern.internal.matcher.Matchers; +import spoon.pattern.internal.matcher.TobeMatched; +import spoon.pattern.internal.parameter.ParameterInfo; import spoon.support.util.ParameterValueProvider; /** diff --git a/src/main/java/spoon/pattern/node/MapEntryNode.java b/src/main/java/spoon/pattern/internal/node/MapEntryNode.java similarity index 91% rename from src/main/java/spoon/pattern/node/MapEntryNode.java rename to src/main/java/spoon/pattern/internal/node/MapEntryNode.java index 193bd198622..1945cadb35e 100644 --- a/src/main/java/spoon/pattern/node/MapEntryNode.java +++ b/src/main/java/spoon/pattern/internal/node/MapEntryNode.java @@ -14,23 +14,23 @@ * 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.pattern.node; +package spoon.pattern.internal.node; + +import static spoon.pattern.internal.matcher.TobeMatched.getMatchedParameters; import java.util.Map; import java.util.function.BiConsumer; import spoon.SpoonException; -import spoon.pattern.Generator; -import spoon.pattern.ResultHolder; -import spoon.pattern.matcher.Quantifier; -import spoon.pattern.matcher.TobeMatched; -import spoon.pattern.parameter.ParameterInfo; +import spoon.pattern.Quantifier; +import spoon.pattern.internal.Generator; +import spoon.pattern.internal.ResultHolder; +import spoon.pattern.internal.matcher.TobeMatched; +import spoon.pattern.internal.parameter.ParameterInfo; import spoon.reflect.declaration.CtElement; import spoon.reflect.meta.ContainerKind; import spoon.support.util.ParameterValueProvider; -import static spoon.pattern.matcher.TobeMatched.getMatchedParameters; - /** * Represents a ValueResolver of one Map.Entry */ diff --git a/src/main/java/spoon/pattern/node/ModelNode.java b/src/main/java/spoon/pattern/internal/node/ModelNode.java similarity index 99% rename from src/main/java/spoon/pattern/node/ModelNode.java rename to src/main/java/spoon/pattern/internal/node/ModelNode.java index 2316d1d6c2a..95382ef2745 100644 --- a/src/main/java/spoon/pattern/node/ModelNode.java +++ b/src/main/java/spoon/pattern/internal/node/ModelNode.java @@ -14,7 +14,7 @@ * 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.pattern.node; +package spoon.pattern.internal.node; import java.util.List; diff --git a/src/main/java/spoon/pattern/node/ParameterNode.java b/src/main/java/spoon/pattern/internal/node/ParameterNode.java similarity index 92% rename from src/main/java/spoon/pattern/node/ParameterNode.java rename to src/main/java/spoon/pattern/internal/node/ParameterNode.java index e872c6cd09d..00b22cd4770 100644 --- a/src/main/java/spoon/pattern/node/ParameterNode.java +++ b/src/main/java/spoon/pattern/internal/node/ParameterNode.java @@ -14,14 +14,14 @@ * 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.pattern.node; +package spoon.pattern.internal.node; import java.util.function.BiConsumer; -import spoon.pattern.Generator; -import spoon.pattern.ResultHolder; -import spoon.pattern.matcher.Quantifier; -import spoon.pattern.parameter.ParameterInfo; +import spoon.pattern.Quantifier; +import spoon.pattern.internal.Generator; +import spoon.pattern.internal.ResultHolder; +import spoon.pattern.internal.parameter.ParameterInfo; import spoon.reflect.declaration.CtElement; import spoon.support.util.ParameterValueProvider; diff --git a/src/main/java/spoon/pattern/node/PrimitiveMatcher.java b/src/main/java/spoon/pattern/internal/node/PrimitiveMatcher.java similarity index 97% rename from src/main/java/spoon/pattern/node/PrimitiveMatcher.java rename to src/main/java/spoon/pattern/internal/node/PrimitiveMatcher.java index a0df3dc2a2d..96047db9c0a 100644 --- a/src/main/java/spoon/pattern/node/PrimitiveMatcher.java +++ b/src/main/java/spoon/pattern/internal/node/PrimitiveMatcher.java @@ -14,7 +14,7 @@ * 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.pattern.node; +package spoon.pattern.internal.node; import spoon.support.util.ParameterValueProvider; diff --git a/src/main/java/spoon/pattern/node/RepeatableMatcher.java b/src/main/java/spoon/pattern/internal/node/RepeatableMatcher.java similarity index 96% rename from src/main/java/spoon/pattern/node/RepeatableMatcher.java rename to src/main/java/spoon/pattern/internal/node/RepeatableMatcher.java index 0dab925c3bc..403289a9ad5 100644 --- a/src/main/java/spoon/pattern/node/RepeatableMatcher.java +++ b/src/main/java/spoon/pattern/internal/node/RepeatableMatcher.java @@ -14,9 +14,9 @@ * 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.pattern.node; +package spoon.pattern.internal.node; -import spoon.pattern.matcher.Quantifier; +import spoon.pattern.Quantifier; import spoon.support.util.ParameterValueProvider; /** diff --git a/src/main/java/spoon/pattern/node/RootNode.java b/src/main/java/spoon/pattern/internal/node/RootNode.java similarity index 93% rename from src/main/java/spoon/pattern/node/RootNode.java rename to src/main/java/spoon/pattern/internal/node/RootNode.java index fd200ee819c..803f0fd496c 100644 --- a/src/main/java/spoon/pattern/node/RootNode.java +++ b/src/main/java/spoon/pattern/internal/node/RootNode.java @@ -14,15 +14,15 @@ * 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.pattern.node; +package spoon.pattern.internal.node; import java.util.function.BiConsumer; -import spoon.pattern.Generator; -import spoon.pattern.ResultHolder; -import spoon.pattern.matcher.Matchers; -import spoon.pattern.matcher.TobeMatched; -import spoon.pattern.parameter.ParameterInfo; +import spoon.pattern.internal.Generator; +import spoon.pattern.internal.ResultHolder; +import spoon.pattern.internal.matcher.Matchers; +import spoon.pattern.internal.matcher.TobeMatched; +import spoon.pattern.internal.parameter.ParameterInfo; import spoon.support.util.ParameterValueProvider; /** diff --git a/src/main/java/spoon/pattern/node/StringNode.java b/src/main/java/spoon/pattern/internal/node/StringNode.java similarity index 97% rename from src/main/java/spoon/pattern/node/StringNode.java rename to src/main/java/spoon/pattern/internal/node/StringNode.java index d4072bd98bb..3ca34205e62 100644 --- a/src/main/java/spoon/pattern/node/StringNode.java +++ b/src/main/java/spoon/pattern/internal/node/StringNode.java @@ -14,7 +14,7 @@ * 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.pattern.node; +package spoon.pattern.internal.node; import java.util.ArrayList; import java.util.Collections; @@ -27,11 +27,11 @@ import java.util.regex.Pattern; import spoon.SpoonException; -import spoon.pattern.Generator; -import spoon.pattern.ResultHolder; -import spoon.pattern.ResultHolder.Single; -import spoon.pattern.matcher.Quantifier; -import spoon.pattern.parameter.ParameterInfo; +import spoon.pattern.Quantifier; +import spoon.pattern.internal.Generator; +import spoon.pattern.internal.ResultHolder; +import spoon.pattern.internal.ResultHolder.Single; +import spoon.pattern.internal.parameter.ParameterInfo; import spoon.support.util.ParameterValueProvider; /** diff --git a/src/main/java/spoon/pattern/node/SwitchNode.java b/src/main/java/spoon/pattern/internal/node/SwitchNode.java similarity index 96% rename from src/main/java/spoon/pattern/node/SwitchNode.java rename to src/main/java/spoon/pattern/internal/node/SwitchNode.java index eedf4c16d35..1e8330993ec 100644 --- a/src/main/java/spoon/pattern/node/SwitchNode.java +++ b/src/main/java/spoon/pattern/internal/node/SwitchNode.java @@ -14,18 +14,18 @@ * 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.pattern.node; +package spoon.pattern.internal.node; import java.util.ArrayList; import java.util.List; import java.util.function.BiConsumer; import spoon.SpoonException; -import spoon.pattern.Generator; -import spoon.pattern.ResultHolder; -import spoon.pattern.matcher.Matchers; -import spoon.pattern.matcher.TobeMatched; -import spoon.pattern.parameter.ParameterInfo; +import spoon.pattern.internal.Generator; +import spoon.pattern.internal.ResultHolder; +import spoon.pattern.internal.matcher.Matchers; +import spoon.pattern.internal.matcher.TobeMatched; +import spoon.pattern.internal.parameter.ParameterInfo; import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtExpression; import spoon.reflect.code.CtIf; diff --git a/src/main/java/spoon/pattern/parameter/AbstractParameterInfo.java b/src/main/java/spoon/pattern/internal/parameter/AbstractParameterInfo.java similarity index 98% rename from src/main/java/spoon/pattern/parameter/AbstractParameterInfo.java rename to src/main/java/spoon/pattern/internal/parameter/AbstractParameterInfo.java index d74db65a854..07fbaee58f4 100644 --- a/src/main/java/spoon/pattern/parameter/AbstractParameterInfo.java +++ b/src/main/java/spoon/pattern/internal/parameter/AbstractParameterInfo.java @@ -14,7 +14,7 @@ * 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.pattern.parameter; +package spoon.pattern.internal.parameter; import java.util.Collection; import java.util.List; @@ -26,9 +26,9 @@ import spoon.Launcher; import spoon.SpoonException; -import spoon.pattern.ResultHolder; -import spoon.pattern.ValueConvertor; -import spoon.pattern.matcher.Quantifier; +import spoon.pattern.Quantifier; +import spoon.pattern.internal.ResultHolder; +import spoon.pattern.internal.ValueConvertor; import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtStatementList; import spoon.reflect.factory.Factory; diff --git a/src/main/java/spoon/pattern/parameter/ListParameterInfo.java b/src/main/java/spoon/pattern/internal/parameter/ListParameterInfo.java similarity index 98% rename from src/main/java/spoon/pattern/parameter/ListParameterInfo.java rename to src/main/java/spoon/pattern/internal/parameter/ListParameterInfo.java index 16a2f8582a2..f6c52e06059 100644 --- a/src/main/java/spoon/pattern/parameter/ListParameterInfo.java +++ b/src/main/java/spoon/pattern/internal/parameter/ListParameterInfo.java @@ -14,7 +14,7 @@ * 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.pattern.parameter; +package spoon.pattern.internal.parameter; import java.util.ArrayList; import java.util.Arrays; diff --git a/src/main/java/spoon/pattern/parameter/MapParameterInfo.java b/src/main/java/spoon/pattern/internal/parameter/MapParameterInfo.java similarity index 99% rename from src/main/java/spoon/pattern/parameter/MapParameterInfo.java rename to src/main/java/spoon/pattern/internal/parameter/MapParameterInfo.java index fd6cf705516..571c2c04c55 100644 --- a/src/main/java/spoon/pattern/parameter/MapParameterInfo.java +++ b/src/main/java/spoon/pattern/internal/parameter/MapParameterInfo.java @@ -14,7 +14,7 @@ * 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.pattern.parameter; +package spoon.pattern.internal.parameter; import java.util.Map; import java.util.function.Function; diff --git a/src/main/java/spoon/pattern/parameter/ParameterInfo.java b/src/main/java/spoon/pattern/internal/parameter/ParameterInfo.java similarity index 96% rename from src/main/java/spoon/pattern/parameter/ParameterInfo.java rename to src/main/java/spoon/pattern/internal/parameter/ParameterInfo.java index 79e19bfbabf..d0d3caa083f 100644 --- a/src/main/java/spoon/pattern/parameter/ParameterInfo.java +++ b/src/main/java/spoon/pattern/internal/parameter/ParameterInfo.java @@ -14,12 +14,12 @@ * 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.pattern.parameter; +package spoon.pattern.internal.parameter; import spoon.pattern.Pattern; -import spoon.pattern.ResultHolder; -import spoon.pattern.matcher.Quantifier; -import spoon.pattern.node.RootNode; +import spoon.pattern.Quantifier; +import spoon.pattern.internal.ResultHolder; +import spoon.pattern.internal.node.RootNode; import spoon.reflect.factory.Factory; import spoon.support.util.ParameterValueProvider; diff --git a/src/main/java/spoon/pattern/parameter/SetParameterInfo.java b/src/main/java/spoon/pattern/internal/parameter/SetParameterInfo.java similarity index 98% rename from src/main/java/spoon/pattern/parameter/SetParameterInfo.java rename to src/main/java/spoon/pattern/internal/parameter/SetParameterInfo.java index 00d7198ca5e..be68afc5c0e 100644 --- a/src/main/java/spoon/pattern/parameter/SetParameterInfo.java +++ b/src/main/java/spoon/pattern/internal/parameter/SetParameterInfo.java @@ -14,7 +14,7 @@ * 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.pattern.parameter; +package spoon.pattern.internal.parameter; import java.util.Arrays; import java.util.Collection; diff --git a/src/main/java/spoon/template/TemplateMatcher.java b/src/main/java/spoon/template/TemplateMatcher.java index dd876b4e7e4..4736a3f19ed 100644 --- a/src/main/java/spoon/template/TemplateMatcher.java +++ b/src/main/java/spoon/template/TemplateMatcher.java @@ -16,12 +16,14 @@ */ package spoon.template; +import static spoon.pattern.internal.matcher.TobeMatched.getMatchedParameters; + import java.util.List; +import spoon.pattern.Match; import spoon.pattern.Pattern; -import spoon.pattern.matcher.Match; -import spoon.pattern.matcher.TobeMatched; -import spoon.pattern.node.ModelNode; +import spoon.pattern.internal.matcher.TobeMatched; +import spoon.pattern.internal.node.ModelNode; import spoon.reflect.declaration.CtClass; import spoon.reflect.declaration.CtElement; import spoon.reflect.meta.ContainerKind; @@ -31,8 +33,6 @@ import spoon.support.util.ParameterValueProviderFactory; import spoon.support.util.UnmodifiableParameterValueProvider; -import static spoon.pattern.matcher.TobeMatched.getMatchedParameters; - /** * This class defines an engine for matching a template to pieces of code. */ diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index b95ecba5edb..726c1a5f41b 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -3,13 +3,13 @@ import org.junit.Test; import spoon.Launcher; import spoon.pattern.ConflictResolutionMode; +import spoon.pattern.Match; import spoon.pattern.ParametersBuilder; import spoon.pattern.Pattern; import spoon.pattern.PatternBuilder; import spoon.pattern.PatternBuilderHelper; -import spoon.pattern.matcher.Match; -import spoon.pattern.matcher.Quantifier; -import spoon.pattern.parameter.ParameterInfo; +import spoon.pattern.Quantifier; +import spoon.pattern.internal.parameter.ParameterInfo; import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtInvocation; import spoon.reflect.code.CtLiteral; diff --git a/src/test/java/spoon/test/template/TemplateTest.java b/src/test/java/spoon/test/template/TemplateTest.java index 01024918229..ae99d501956 100644 --- a/src/test/java/spoon/test/template/TemplateTest.java +++ b/src/test/java/spoon/test/template/TemplateTest.java @@ -4,7 +4,7 @@ import spoon.Launcher; import spoon.SpoonException; import spoon.compiler.SpoonResourceHelper; -import spoon.pattern.matcher.Match; +import spoon.pattern.Match; import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtExpression; import spoon.reflect.code.CtForEach; diff --git a/src/test/java/spoon/test/template/core/ParameterInfoTest.java b/src/test/java/spoon/test/template/core/ParameterInfoTest.java index 65f070787ea..23f14ae2aaf 100644 --- a/src/test/java/spoon/test/template/core/ParameterInfoTest.java +++ b/src/test/java/spoon/test/template/core/ParameterInfoTest.java @@ -18,10 +18,10 @@ import org.junit.Test; -import spoon.pattern.parameter.ListParameterInfo; -import spoon.pattern.parameter.MapParameterInfo; -import spoon.pattern.parameter.ParameterInfo; -import spoon.pattern.parameter.SetParameterInfo; +import spoon.pattern.internal.parameter.ListParameterInfo; +import spoon.pattern.internal.parameter.MapParameterInfo; +import spoon.pattern.internal.parameter.ParameterInfo; +import spoon.pattern.internal.parameter.SetParameterInfo; import spoon.reflect.meta.ContainerKind; import spoon.support.util.ParameterValueProvider; import spoon.support.util.UnmodifiableParameterValueProvider; diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple.java b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple.java index a1e68495beb..fcaa96e3692 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple.java @@ -3,7 +3,7 @@ import spoon.pattern.Pattern; import spoon.pattern.PatternBuilder; import spoon.pattern.PatternBuilderHelper; -import spoon.pattern.matcher.Quantifier; +import spoon.pattern.Quantifier; import spoon.reflect.code.CtLiteral; import spoon.reflect.declaration.CtType; import spoon.reflect.meta.ContainerKind; From 7c9e3507eb0e1d35acc6d7817d31e942b653f598 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Mon, 21 May 2018 22:18:51 +0200 Subject: [PATCH 091/131] up --- doc/pattern.md | 93 +++++----- ...java => InlinedStatementConfigurator.java} | 26 +-- src/main/java/spoon/pattern/Match.java | 30 +--- src/main/java/spoon/pattern/Pattern.java | 49 +++--- .../java/spoon/pattern/PatternBuilder.java | 50 +++--- .../spoon/pattern/PatternBuilderHelper.java | 106 +----------- ...java => PatternParameterConfigurator.java} | 160 ++++++------------ .../pattern/internal/DefaultGenerator.java | 6 +- .../spoon/pattern/internal/Generator.java | 14 +- .../pattern/internal/PatternPrinter.java | 8 +- .../pattern/internal/matcher/TobeMatched.java | 30 ++-- .../pattern/internal/node/ConstantNode.java | 8 +- .../pattern/internal/node/ElementNode.java | 12 +- .../pattern/internal/node/ForEachNode.java | 14 +- .../pattern/internal/node/InlineNode.java | 6 +- .../pattern/internal/node/ListOfNodes.java | 4 +- .../pattern/internal/node/MapEntryNode.java | 8 +- .../pattern/internal/node/ParameterNode.java | 10 +- .../internal/node/PrimitiveMatcher.java | 4 +- .../internal/node/RepeatableMatcher.java | 6 +- .../spoon/pattern/internal/node/RootNode.java | 6 +- .../pattern/internal/node/StringNode.java | 9 +- .../pattern/internal/node/SwitchNode.java | 14 +- .../parameter/AbstractParameterInfo.java | 22 +-- .../internal/parameter/ListParameterInfo.java | 4 +- .../internal/parameter/MapParameterInfo.java | 18 +- .../internal/parameter/ParameterInfo.java | 12 +- .../internal/parameter/SetParameterInfo.java | 4 +- src/main/java/spoon/support/Experimental.java | 31 ++++ src/main/java/spoon/support/Internal.java | 31 ++++ ...erValueProvider.java => ImmutableMap.java} | 20 ++- ...lueProvider.java => ImmutableMapImpl.java} | 40 +++-- .../util/ParameterValueProviderFactory.java | 6 +- .../java/spoon/template/TemplateBuilder.java | 6 +- .../java/spoon/template/TemplateMatcher.java | 10 +- src/test/java/spoon/MavenLauncherTest.java | 2 +- .../SpoonArchitectureEnforcerTest.java | 7 +- .../java/spoon/test/template/PatternTest.java | 99 ++++++----- .../test/template/core/ParameterInfoTest.java | 120 ++++++------- .../testclasses/match/MatchMultiple.java | 2 +- .../testclasses/match/MatchMultiple2.java | 8 - .../testclasses/match/MatchMultiple3.java | 10 -- .../testclasses/replace/NewPattern.java | 4 +- .../testclasses/replace/OldPattern.java | 8 +- 44 files changed, 515 insertions(+), 622 deletions(-) rename src/main/java/spoon/pattern/{InlineStatementsBuilder.java => InlinedStatementConfigurator.java} (90%) rename src/main/java/spoon/pattern/{ParametersBuilder.java => PatternParameterConfigurator.java} (83%) create mode 100644 src/main/java/spoon/support/Experimental.java create mode 100644 src/main/java/spoon/support/Internal.java rename src/main/java/spoon/support/util/{ParameterValueProvider.java => ImmutableMap.java} (74%) rename src/main/java/spoon/support/util/{UnmodifiableParameterValueProvider.java => ImmutableMapImpl.java} (71%) diff --git a/doc/pattern.md b/doc/pattern.md index d03b7431b7c..89d13d66a89 100644 --- a/doc/pattern.md +++ b/doc/pattern.md @@ -2,10 +2,14 @@ title: Spoon Patterns --- -**Spoon patterns** aim at finding code elements using patterns. A **Spoon pattern** is based on a one or several AST nodes, -where some parts of the AST are **pattern parameters**. +Spoon patterns enables you to find code elements. A Spoon pattern is based on a one or several AST nodes, which represent the code to match, where some parts of the AST are pattern parameters. When a pattern is matched, one can access to the code matched in each pattern parameter. -Once you have a pattern, one matches again some code: +The main classes of Spoon patterns are those in package `spoon.pattern`: + +* classes: PatternBuilder, Pattern, Match, PatternBuilderHelper, PatternParameterConfigurator, InlinedStatementConfigurator +* eums: ConflictResolutionMode, Quantifier + +Example usage: ```java Factory spoonFactory = ... @@ -21,41 +25,44 @@ pattern.forEachMatch(spoonFactory.getRootPackage(), (Match match) -> { }); ``` +## PatternBuilder -## Main class: `PatternBuilder` - -To create a Spoon pattern, one must use `PatternBuilder`, which takes AST nodes as input, and where you -**pattern parameters** are defined. - - -The code to creates a Spoon pattern using `PatternBuilder` looks like this: - -```java -public class Foo { - public void statement() { - if (_col_.size() > 10) - throw new OutOfBoundException(); - } -} -``` +To create a Spoon pattern, one must use `PatternBuilder`, which takes AST nodes as input, and **pattern parameters** are defined. ```java +// creates pattern from the body of method "matcher1" Pattern t = PatternBuilder.create( new PatternBuilderHelper(fooClass).setBodyOfMethod("matcher1").getPatternElements()) - .configureParameters() + .configurePatternParameters() .build(); ``` -This pattern matches all statements of the body of method `statement`, ie. a precondition to check that a list -is smaller than a certain size. -This pattern has -one single pattern parameter called `_col_`, which is considered as a pattern parameter because it is declared outside of the AST node. +This pattern matches all statements of the body of method `statement`, ie. a precondition to check that a list is smaller than a certain size. +This pattern has one single pattern parameter called `_col_`, which is automatically considered as a pattern parameter because it is declared outside of the AST node. This automatic configuration happens when `configurePatternParameters` is called. + +## Pattern + +The main methods of `Pattern` are `getMatches` and `forEachMatch`. + +``` +List matches = pattern.getMatches(ctClass); +``` + +## Match + +A `Match` represent a match of a pattern on a code elements. + +The main methods are `getMatchingElement` and `getMatchingElements`. + +## PatternBuilderHelper -## ParametersBuilder parameters +`PatternBuilderHelper` is used to select AST nodes that would act as pattern. It is mainly used to get the body (method `setBodyOfMethod`) or the return expression of a method (method `setReturnExpressionOfMethod`) . -To create pattern, one use a `ParametersBuilder` in a lambda: +## PatternParameterConfigurator + +To create pattern paramters, one uses a `PatternParameterConfigurator` as a lambda: ```java @@ -68,6 +75,7 @@ void method(String _x_) { //a pattern definition Pattern t = PatternBuilder.create(...select pattern model...) .configureParameters(pb -> + // creating a pattern parameter called "firstParamName" pb.parameter("firstParamName") //...select which AST nodes are parameters... //e.g. using parameter selector @@ -78,35 +86,31 @@ Pattern t = PatternBuilder.create(...select pattern model...) //... you can define as many parameters as you need... + // another parameter (all usages of variable "_x_" pb.parameter("lastParamName").byVariable("_x_"); ) .build(); ``` -`ParametersBuilder` has the following methods: +`ParametersBuilder` has many methods to create the perfect pattern parameters, incl: * `byType(Class|CtTypeReference|String)` - all the references to the type defined by Class, CtTypeReference or qualified name are considered as pattern parameter * `byLocalType(CtType searchScope, String localTypeSimpleName)` - all the types defined in `searchScope` and having simpleName equal to `localTypeSimpleName` are considered as pattern parameter * `byVariable(CtVariable|String)` - all read/write variable references to CtVariable -or any variable with provided simple name are considered as pattern parameter -* `byInvocation(CtMethod method)` - each invocation of `method` are considered as pattern parameter -* `parametersByVariable(CtVariable|String... variableName)` - each `variableName` is a name of a variable +or any variable named with the provided simple name are considered as pattern parameter +* `byInvocation(CtMethod method)` - all invocations of `method` are considered as pattern parameter +* `byVariable(CtVariable|String... variableName)` - each `variableName` is a name of a variable which references instance of a class with fields. Each such field is considered as pattern parameter. -* `byTemplateParameterReference(CtVariable)` - the reference to variable of type `TemplateParameter` is handled -as pattern parameter using all the rules defined in the chapters above. * `byFilter(Filter)` - any pattern model element, where `Filter.accept(element)` returns true is a pattern parameter. -* `attributeOfElementByFilter(CtRole role, Filter filter)` - the attribute defined by `role` of all +* `byRole(CtRole role, Filter filter)` - the attribute defined by `role` of all pattern model elements, where `Filter.accept(element)` returns true is a pattern parameter. It can be used to define a varible on any CtElement attribute. E.g. method modifiers or throwables, ... * `byString(String name)` - all pattern model string attributes whose value is equal to `name` are considered as pattern parameter.This can be used to define full name of the methods and fields, etc. * `bySubstring(String stringMarker)` - all pattern model string attributes whose value contains -whole string or a substring equal to `stringMarker`are pattern parameter. -Note: only the `stringMarker` substring of the string value is substituted. -Other parts of string/element name are kept unchanged. -* `bySimpleName(String name)` - any CtNamedElement or CtReference identified by it's simple name is a pattern parameter. -* `byNamedElementSimpleName(String name)` - any CtNamedElement identified by it's simple name is a pattern parameter. -* `byReferenceSimpleName(String name)` - any CtReference identified by it's simple name is a pattern parameter. +whole string or a substring equal to `stringMarker`are pattern parameter. Note: only the `stringMarker` substring of the string value is substituted, other parts of string/element name are kept unchanged. +* `byNamedElement(String name)` - any CtNamedElement identified by it's simple name is a pattern parameter. +* `byReferenceName(String name)` - any CtReference identified by it's simple name is a pattern parameter. Any parameter of a pattern can be configured like this: @@ -140,9 +144,9 @@ The `setValueType(type)` is called internally too, so match condition assures bo * `ContainerKind#MAP` - The values are always stored as `Map`. -## Inlining with PatternBuilder +## InlinedStatementConfigurator -It is possible to inlined code, eg: +It is possible to match inlined code, eg: ```java System.out.println(1); @@ -158,8 +162,7 @@ for (int i=0; i * because inline statements are executed during substitution process and are not included in generated result. * - * The inline statements may be used in PatternMatching process (opposite to Pattern substitution) too. + * Main documentation at http://spoon.gforge.inria.fr/pattern.html. */ -public class InlineStatementsBuilder { +@Experimental +public class InlinedStatementConfigurator { private final PatternBuilder patternBuilder; private boolean failOnMissingParameter = true; private ConflictResolutionMode conflictResolutionMode = ConflictResolutionMode.FAIL; - public InlineStatementsBuilder(PatternBuilder patternBuilder) { + public InlinedStatementConfigurator(PatternBuilder patternBuilder) { this.patternBuilder = patternBuilder; } @@ -83,7 +85,7 @@ public ConflictResolutionMode getConflictResolutionMode() { * @param conflictResolutionMode to be applied mode * @return this to support fluent API */ - public InlineStatementsBuilder setConflictResolutionMode(ConflictResolutionMode conflictResolutionMode) { + public InlinedStatementConfigurator setConflictResolutionMode(ConflictResolutionMode conflictResolutionMode) { this.conflictResolutionMode = conflictResolutionMode; return this; } @@ -93,7 +95,7 @@ public InlineStatementsBuilder setConflictResolutionMode(ConflictResolutionMode * @param variableName to be searched variable name * @return this to support fluent API */ - public InlineStatementsBuilder byVariableName(String variableName) { + public InlinedStatementConfigurator inlineIfOrForeachReferringTo(String variableName) { patternBuilder.patternQuery .filterChildren((CtVariableReference varRef) -> variableName.equals(varRef.getSimpleName())) .forEach(this::byElement); @@ -105,17 +107,17 @@ public InlineStatementsBuilder byVariableName(String variableName) { * @param element a child of CtIf or CtForEach * @return this to support fluent API */ - InlineStatementsBuilder byElement(CtElement element) { + InlinedStatementConfigurator byElement(CtElement element) { CtStatement stmt = element instanceof CtStatement ? (CtStatement) element : element.getParent(CtStatement.class); //called for first parent statement of all current parameter substitutions stmt.accept(new CtAbstractVisitor() { @Override public void visitCtForEach(CtForEach foreach) { - markInline(foreach); + markAsInlined(foreach); } @Override public void visitCtIf(CtIf ifElement) { - markInline(ifElement); + markAsInlined(ifElement); } }); return this; @@ -126,7 +128,7 @@ public void visitCtIf(CtIf ifElement) { * @param foreach to be marked {@link CtForEach} element * @return this to support fluent API */ - public InlineStatementsBuilder markInline(CtForEach foreach) { + public InlinedStatementConfigurator markAsInlined(CtForEach foreach) { //detect meta elements by different way - e.g. comments? RootNode vr = patternBuilder.getPatternNode(foreach.getExpression()); if ((vr instanceof PrimitiveMatcher) == false) { @@ -159,7 +161,7 @@ public InlineStatementsBuilder markInline(CtForEach foreach) { * @param ifElement to be marked {@link CtIf} element * @return this to support fluent API */ - public InlineStatementsBuilder markInline(CtIf ifElement) { + public InlinedStatementConfigurator markAsInlined(CtIf ifElement) { SwitchNode osp = new SwitchNode(); boolean[] canBeInline = new boolean[]{true}; forEachIfCase(ifElement, (expression, block) -> { @@ -169,7 +171,7 @@ public InlineStatementsBuilder markInline(CtIf ifElement) { RootNode vrOfExpression = patternBuilder.getPatternNode(expression); if (vrOfExpression instanceof ParameterNode == false) { if (failOnMissingParameter) { - throw new SpoonException("Each inline `if` statement must have defined pattern parameter in expression. If you want to ignore this, then call InlineStatementsBuilder#setFailOnMissingParameter(false) first."); + throw new SpoonException("Each inline `if` statement must have defined pattern parameter in expression. If you want to ignore this, then call InlinedStatementConfigurator#setFailOnMissingParameter(false) first."); } else { canBeInline[0] = false; return; @@ -249,7 +251,7 @@ public boolean isFailOnMissingParameter() { * set false if ssuch statement should be kept as part of template. * @return this to support fluent API */ - public InlineStatementsBuilder setFailOnMissingParameter(boolean failOnMissingParameter) { + public InlinedStatementConfigurator setFailOnMissingParameter(boolean failOnMissingParameter) { this.failOnMissingParameter = failOnMissingParameter; return this; } diff --git a/src/main/java/spoon/pattern/Match.java b/src/main/java/spoon/pattern/Match.java index ef0d26c2185..76252d78c38 100644 --- a/src/main/java/spoon/pattern/Match.java +++ b/src/main/java/spoon/pattern/Match.java @@ -21,16 +21,16 @@ import spoon.SpoonException; import spoon.reflect.declaration.CtElement; -import spoon.support.util.ParameterValueProvider; +import spoon.support.util.ImmutableMap; /** * Represents a single match of {@link Pattern} */ public class Match { private final List matchingElements; - private final ParameterValueProvider parameters; + private final ImmutableMap parameters; - public Match(List matches, ParameterValueProvider parameters) { + public Match(List matches, ImmutableMap parameters) { this.parameters = parameters; this.matchingElements = matches; } @@ -93,29 +93,9 @@ private T getMatchingElement(Class clazz, boolean failIfMany) { } /** - * Replaces all matching elements with `newElements` - * @param newElements the elements which has to be used instead of matched element + * @return {@link ImmutableMap} with values of {@link Pattern} parameters, which fits to current match */ - public void replaceMatchesBy(List newElements) { - if (matchingElements.isEmpty()) { - throw new SpoonException("Cannot replace empty list of elements"); - } - CtElement last = null; - for (CtElement oldElement : getMatchingElements(CtElement.class)) { - if (last != null) { - //delete all excluding last - last.delete(); - } - last = oldElement; - } - //replace last element - last.replace(newElements); - } - - /** - * @return {@link ParameterValueProvider} with values of {@link Pattern} parameters, which fits to current match - */ - public ParameterValueProvider getParameters() { + public ImmutableMap getParameters() { return parameters; } /** diff --git a/src/main/java/spoon/pattern/Pattern.java b/src/main/java/spoon/pattern/Pattern.java index a19cacce2a8..c95926b4a21 100644 --- a/src/main/java/spoon/pattern/Pattern.java +++ b/src/main/java/spoon/pattern/Pattern.java @@ -36,27 +36,29 @@ import spoon.reflect.factory.Factory; import spoon.reflect.reference.CtTypeReference; import spoon.reflect.visitor.chain.CtConsumer; -import spoon.support.util.ParameterValueProvider; +import spoon.support.Experimental; +import spoon.support.util.ImmutableMap; +import spoon.support.util.ImmutableMapImpl; import spoon.support.util.ParameterValueProviderFactory; -import spoon.support.util.UnmodifiableParameterValueProvider; /** * Represents a pattern for matching code. A pattern is composed of a list of AST models, where a model is an AST with some nodes being "pattern parameters". * - * Differences with {@link spoon.template.TemplateMatcher}: - * - it can match sequences of elements - * - it can match inlined elements + * Main documentation at http://spoon.gforge.inria.fr/pattern.html. * * Instances can created with {@link PatternBuilder}. * * The {@link Pattern} can also be used to generate new code where * (Pattern) + (pattern parameters) => (copy of pattern where parameters are replaced by parameter values) + * This is done with {@link #substitute(Factory, Class, ImmutableMap)} * - * This is done with {@link #substitute(Factory, Class, ParameterValueProvider)} + * Differences with {@link spoon.template.TemplateMatcher}: + * - it can match sequences of elements + * - it can match inlined elements */ +@Experimental public class Pattern { - private ParameterValueProviderFactory parameterValueProviderFactory = UnmodifiableParameterValueProvider.Factory.INSTANCE; - //TODO rename + private ParameterValueProviderFactory parameterValueProviderFactory = ImmutableMapImpl.Factory.INSTANCE; private ModelNode modelValueResolver; private boolean addGeneratedBy = false; @@ -66,9 +68,11 @@ public class Pattern { } /** + * + * Not in the public API + * * @return a {@link ModelNode} of this pattern */ - //TODO rename public ModelNode getModelValueResolver() { return modelValueResolver; } @@ -99,22 +103,22 @@ public Map getParameterInfos() { * @param params - the substitution parameters * @return List of generated elements */ - public List substitute(Factory factory, Class valueType, ParameterValueProvider params) { + public List substitute(Factory factory, Class valueType, ImmutableMap params) { return new DefaultGenerator(factory).setAddGeneratedBy(isAddGeneratedBy()).generateTargets(modelValueResolver, params, valueType); } - /** Utility method that provides the same feature as {@link #substitute(Factory, Class, ParameterValueProvider)}, but with a Map as third parameter */ + /** Utility method that provides the same feature as {@link #substitute(Factory, Class, ImmutableMap)}, but with a Map as third parameter */ public List substituteList(Factory factory, Class valueType, Map params) { - return substitute(factory, valueType, new UnmodifiableParameterValueProvider(params)); + return substitute(factory, valueType, new ImmutableMapImpl(params)); } - /** Utility method that provides the same feature as {@link #substitute(Factory, Class, ParameterValueProvider)}, but returns a single element, and uses a map as parameter */ + /** Utility method that provides the same feature as {@link #substitute(Factory, Class, ImmutableMap)}, but returns a single element, and uses a map as parameter */ public T substituteSingle(Factory factory, Class valueType, Map params) { - return substituteSingle(factory, valueType, new UnmodifiableParameterValueProvider(params)); + return substituteSingle(factory, valueType, new ImmutableMapImpl(params)); } - /** Utility method that provides the same feature as {@link #substitute(Factory, Class, ParameterValueProvider)}, but returns a single element */ - public T substituteSingle(Factory factory, Class valueType, ParameterValueProvider params) { + /** Utility method that provides the same feature as {@link #substitute(Factory, Class, ImmutableMap)}, but returns a single element */ + public T substituteSingle(Factory factory, Class valueType, ImmutableMap params) { return new DefaultGenerator(factory).setAddGeneratedBy(isAddGeneratedBy()).generateSingleTarget(modelValueResolver, params, valueType); } @@ -146,7 +150,7 @@ public > T createType(Factory factory, String typeQualifiedN @SuppressWarnings("unchecked") private > T createType(CtPackage ownerPackage, String typeSimpleName, Map params) { @SuppressWarnings({ "rawtypes" }) - List types = substitute(ownerPackage.getFactory(), CtType.class, new UnmodifiableParameterValueProvider(params, + List types = substitute(ownerPackage.getFactory(), CtType.class, new ImmutableMapImpl(params, PatternBuilder.TARGET_TYPE, ownerPackage.getFactory().Type().createReference(getQualifiedName(ownerPackage, typeSimpleName)))); T result = null; for (CtType type : types) { @@ -168,7 +172,7 @@ private > T createType(CtPackage ownerPackage, String typeSi * @return List of generated elements */ public List applyToType(CtType targetType, Class valueType, Map params) { - List results = substitute(targetType.getFactory(), valueType, new UnmodifiableParameterValueProvider(params, PatternBuilder.TARGET_TYPE, targetType.getReference())); + List results = substitute(targetType.getFactory(), valueType, new ImmutableMapImpl(params, PatternBuilder.TARGET_TYPE, targetType.getReference())); for (T result : results) { if (result instanceof CtTypeMember) { targetType.addTypeMember((CtTypeMember) result); @@ -192,7 +196,7 @@ public void forEachMatch(Object input, CtConsumer consumer) { } MatchingScanner scanner = new MatchingScanner(modelValueResolver, parameterValueProviderFactory, consumer); - ParameterValueProvider parameters = parameterValueProviderFactory.createParameterValueProvider(); + ImmutableMap parameters = parameterValueProviderFactory.createParameterValueProvider(); if (input instanceof Collection) { scanner.scan(null, (Collection) input); } else if (input instanceof Map) { @@ -203,12 +207,12 @@ public void forEachMatch(Object input, CtConsumer consumer) { } /** - * Finds all target program sub-trees that correspond to a template + * Finds all target program sub-trees that correspond to this pattern * and returns them. * @param root the root of to be searched AST. It can be a CtElement or List, Set, Map of CtElements * @return List of {@link Match} */ - public List getMatches(Object root) { + public List getMatches(CtElement root) { List matches = new ArrayList<>(); forEachMatch(root, match -> { matches.add(match); @@ -232,7 +236,8 @@ public boolean isAddGeneratedBy() { return addGeneratedBy; } - public Pattern setAddGeneratedBy(boolean addGeneratedBy) { + // not public because pattern should be immutable (only configured through PatternBuilder + Pattern setAddGeneratedBy(boolean addGeneratedBy) { this.addGeneratedBy = addGeneratedBy; return this; } diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index ebe41f92293..4f6fb055775 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -60,14 +60,16 @@ import spoon.reflect.visitor.chain.CtQueryable; import spoon.reflect.visitor.filter.AllTypeMembersFunction; import spoon.reflect.visitor.filter.TypeFilter; +import spoon.support.Experimental; import spoon.template.Parameter; import spoon.template.TemplateParameter; /** * The master class to create a {@link Pattern} instance. * - * Based on a fluent API, see tests and documentation ('pattern.md'). + * Main documentation at http://spoon.gforge.inria.fr/pattern.html. */ +@Experimental public class PatternBuilder { public static final String TARGET_TYPE = "targetType"; @@ -130,7 +132,7 @@ private PatternBuilder(List template) { patternNodes = ElementNode.create(this.patternModel, patternElementToSubstRequests); patternQuery = new PatternBuilder.PatternQuery(getFactory().Query(), patternModel); if (this.templateTypeRef != null) { - configureParameters(pb -> { + configurePatternParameters(pb -> { pb.parameter(TARGET_TYPE).byType(this.templateTypeRef).setValueType(CtTypeReference.class); }); } @@ -358,8 +360,8 @@ PatternBuilder setDefaultValueConvertor(ValueConvertor valueConvertor) { * are automatically marked as pattern parameters * @return this to support fluent API */ - public PatternBuilder createPatternParameters() { - configureParameters(pb -> { + public PatternBuilder configurePatternParameters() { + configurePatternParameters(pb -> { //add this substitution request only if there isn't another one yet pb.setConflictResolutionMode(ConflictResolutionMode.KEEP_OLD_NODE); /* @@ -380,13 +382,11 @@ public PatternBuilder createPatternParameters() { } /** - * Configure template parameters - * @param parametersBuilder a buildir which allows to define template parameters and to select - * to be substituted nodes - * @return + * Configure pattern parameters with a {@link PatternParameterConfigurator} + * @return this */ - public PatternBuilder configureParameters(Consumer parametersBuilder) { - ParametersBuilder pb = new ParametersBuilder(this, parameterInfos); + public PatternBuilder configurePatternParameters(Consumer parametersBuilder) { + PatternParameterConfigurator pb = new PatternParameterConfigurator(this, parameterInfos); parametersBuilder.accept(pb); return this; } @@ -394,22 +394,14 @@ public PatternBuilder configureParameters(Consumer parameters /** * Used by inline for each statement to define template parameter which is local in the scope of the inline statement */ - PatternBuilder configureLocalParameters(Consumer parametersBuilder) { - ParametersBuilder pb = new ParametersBuilder(this, new HashMap<>()); + PatternBuilder configureLocalParameters(Consumer parametersBuilder) { + PatternParameterConfigurator pb = new PatternParameterConfigurator(this, new HashMap<>()); parametersBuilder.accept(pb); return this; } - /** - * Provides backward compatibility with standard Template parameters based on {@link TemplateParameter} and {@link Parameter} annotation - * - * @return this to support fluent API - */ - public PatternBuilder configurePatternParameters() { - return configurePatternParameters(templateTypeRef.getTypeDeclaration(), null); - } /** - * adds all standard Template parameters based on {@link TemplateParameter} and {@link Parameter} annotation + * method to support legacy {@link TemplateParameter} and {@link Parameter} annotation * @param templateParameters parameters, which will be used in substitution. It is needed here, * when parameter value types influences which AST nodes will be the target of substitution in legacy template patterns * @return this to support fluent API @@ -426,7 +418,7 @@ public PatternBuilder configurePatternParameters(Map templatePar * @return this to support fluent API */ private PatternBuilder configurePatternParameters(CtType templateType, Map templateParameters) { - configureParameters(pb -> { + configurePatternParameters(pb -> { templateType.map(new AllTypeMembersFunction()).forEach((CtTypeMember typeMember) -> { configurePatternParameter(templateType, templateParameters, pb, typeMember); }); @@ -450,7 +442,7 @@ private PatternBuilder configurePatternParameters(CtType templateType, Map templateType, Map templateParameters, ParametersBuilder pb, CtTypeMember typeMember) { + private void configurePatternParameter(CtType templateType, Map templateParameters, PatternParameterConfigurator pb, CtTypeMember typeMember) { Factory f = typeMember.getFactory(); CtTypeReference templateParamRef = f.Type().createReference(TemplateParameter.class); CtTypeReference typeReferenceRef = f.Type().createReference(CtTypeReference.class); @@ -473,7 +465,7 @@ private void configurePatternParameter(CtType templateType, Map) { //parameter is a multivalue // here we need to replace all named element and all references whose simpleName == stringMarker - pb.parameter(parameterName).setContainerKind(ContainerKind.LIST).byName(stringMarker); + pb.parameter(parameterName).setContainerKind(ContainerKind.LIST).byNamedElement(stringMarker).byReferenceName(stringMarker); } else if (paramType.isSubtypeOf(typeReferenceRef) || paramType.getQualifiedName().equals(Class.class.getName())) { /* * parameter with value type TypeReference or Class, identifies replacement of local type whose name is equal to parameter name @@ -570,13 +562,13 @@ private void addInlineStatements(String variableName, Object paramValue) { //we are adding inline statements automatically from legacy templates, //so do not fail if it is sometime not possible - it means that it is not a inline statement then sb.setFailOnMissingParameter(false); - sb.byVariableName(variableName); + sb.inlineIfOrForeachReferringTo(variableName); }); } } /** - * Configures inline statements + * Configures inlined statements * * For example if the `for` statement in this pattern model *
            
            @@ -598,8 +590,8 @@ private void addInlineStatements(String variableName, Object paramValue) {
             	 * @param consumer
             	 * @return this to support fluent API
             	 */
            -	public PatternBuilder configureInlineStatements(Consumer consumer) {
            -		InlineStatementsBuilder sb = new InlineStatementsBuilder(this);
            +	public PatternBuilder configureInlineStatements(Consumer consumer) {
            +		InlinedStatementConfigurator sb = new InlinedStatementConfigurator(this);
             		consumer.accept(sb);
             		return this;
             	}
            @@ -669,7 +661,7 @@ void forEachNodeOfParameter(ParameterInfo parameter, Consumer consumer
             	/**
             	 * @return true if produced Pattern will append generated by comments
             	 */
            -	public boolean isAddGeneratedBy() {
            +	private boolean isAddGeneratedBy() {
             		return addGeneratedBy;
             	}
             	/**
            diff --git a/src/main/java/spoon/pattern/PatternBuilderHelper.java b/src/main/java/spoon/pattern/PatternBuilderHelper.java
            index d2d0079214f..6caf35f98a8 100644
            --- a/src/main/java/spoon/pattern/PatternBuilderHelper.java
            +++ b/src/main/java/spoon/pattern/PatternBuilderHelper.java
            @@ -36,10 +36,14 @@
             import spoon.reflect.declaration.CtTypeMember;
             import spoon.reflect.reference.CtTypeReference;
             import spoon.reflect.visitor.Filter;
            +import spoon.support.Experimental;
             
             /**
            - * Utility class to select parts of AST to be used as a model of a {@link Pattern}.
            + * Utility class to select parts of AST to be used as a model of a {@link PatternBuilder}.
            + *
            + * Main documentation at http://spoon.gforge.inria.fr/pattern.html.
              */
            +@Experimental
             public class PatternBuilderHelper {
             	/**
             	 * The original type, which contains the AST of pattern model
            @@ -87,29 +91,11 @@ public PatternBuilderHelper setTypeMember(String typeMemberName) {
             	 * Sets a template model from {@link CtTypeMember} of a template type
             	 * @param filter the {@link Filter} whose match defines to be used {@link CtTypeMember}
             	 */
            -	public PatternBuilderHelper setTypeMember(Filter filter) {
            +	private PatternBuilderHelper setTypeMember(Filter filter) {
             		setElements(getByFilter(filter));
             		return this;
             	}
             
            -	/**
            -	 * removes all annotations of type defined by `classes` from the clone of the source {@link CtType}
            -	 * @param classes list of classes which defines types of to be removed annotations
            -	 * @return this to support fluent API
            -	 */
            -	public PatternBuilderHelper removeTag(Class... classes) {
            -		List elements = getClonedElements();
            -		for (Class class1 : classes) {
            -			for (CtElement element : elements) {
            -				CtAnnotation annotation = element.getAnnotation(element.getFactory().Type().createReference(class1));
            -				if (annotation != null) {
            -					element.removeAnnotation(annotation);
            -				}
            -			}
            -		}
            -		return this;
            -	}
            -
             	private List getClonedElements() {
             		if (elements == null) {
             			throw new SpoonException("Template model is not defined yet");
            @@ -135,7 +121,7 @@ public PatternBuilderHelper setBodyOfMethod(String methodName) {
             	 * Sets a template model from body of the method of template type selected by filter
             	 * @param filter the {@link Filter} whose match defines to be used {@link CtMethod}
             	 */
            -	public void setBodyOfMethod(Filter> filter) {
            +	private void setBodyOfMethod(Filter> filter) {
             		CtBlock body =  getOneByFilter(filter).getBody();
             		setElements(body.getStatements());
             	}
            @@ -151,7 +137,7 @@ public void setReturnExpressionOfMethod(String methodName) {
             	 * Sets a template model from return expression of the method of template type selected by filter
             	 * @param filter the {@link Filter} whose match defines to be used {@link CtExecutable}
             	 */
            -	public void setReturnExpressionOfMethod(Filter> filter) {
            +	private void setReturnExpressionOfMethod(Filter> filter) {
             		CtMethod method = getOneByFilter(filter);
             		CtBlock body = method.getBody();
             		if (body.getStatements().size() != 1) {
            @@ -178,28 +164,6 @@ private  T getOneByFilter(Filter filter) {
             		}
             		return elements.get(0);
             	}
            -	/**
            -	 * @param filter whose matches will be removed from the template model
            -	 */
            -	public PatternBuilderHelper removeTypeMembers(Filter filter) {
            -		for (CtTypeMember ctTypeMember : new ArrayList<>(getClonedPatternType().getTypeMembers())) {
            -			if (filter.matches(ctTypeMember)) {
            -				ctTypeMember.delete();
            -			}
            -		}
            -		return this;
            -	}
            -
            -	/**
            -	 * Removes all type members which are annotated by `annotationClass`
            -	 */
            -	@SuppressWarnings({ "unchecked", "rawtypes" })
            -	public PatternBuilderHelper removeTypeMembersAnnotatedBy(Class... annotationClass) {
            -		for (Class ac : annotationClass) {
            -			removeTypeMembers(tm -> tm.getAnnotation((Class) ac) != null);
            -		}
            -		return this;
            -	}
             
             	/**
             	 * @param filter whose matches will be kept in the template. All others will be removed
            @@ -213,58 +177,6 @@ public PatternBuilderHelper keepTypeMembers(Filter filter) {
             		return this;
             	}
             
            -	/**
            -	 * Keeps only type members, which are annotated by `annotationClass`. All others will be removed
            -	 */
            -	public PatternBuilderHelper keepTypeMembersAnnotatedBy(Class annotationClass) {
            -		keepTypeMembers(tm -> tm.getAnnotation(annotationClass) != null);
            -		return this;
            -	}
            -
            -	/**
            -	 * removes super class from the template
            -	 */
            -	public PatternBuilderHelper removeSuperClass() {
            -		getClonedPatternType().setSuperclass(null);
            -		return this;
            -	}
            -
            -	/**
            -	 * @param filter super interfaces which matches the filter will be removed
            -	 */
            -	public PatternBuilderHelper removeSuperInterfaces(Filter> filter) {
            -		Set> superIfaces = new HashSet<>(getClonedPatternType().getSuperInterfaces());
            -		boolean changed = false;
            -		for (Iterator> iter = superIfaces.iterator(); iter.hasNext();) {
            -			if (filter.matches(iter.next())) {
            -				iter.remove();
            -				changed = true;
            -			}
            -		}
            -		if (changed) {
            -			getClonedPatternType().setSuperInterfaces(superIfaces);
            -		}
            -		return this;
            -	}
            -
            -	/**
            -	 * @param filter super interfaces which matches the filter will be kept. Others will be removed
            -	 */
            -	public PatternBuilderHelper keepSuperInterfaces(Filter> filter) {
            -		Set> superIfaces = new HashSet<>(getClonedPatternType().getSuperInterfaces());
            -		boolean changed = false;
            -		for (Iterator> iter = superIfaces.iterator(); iter.hasNext();) {
            -			if (filter.matches(iter.next())) {
            -				iter.remove();
            -				changed = true;
            -			}
            -		}
            -		if (changed) {
            -			getClonedPatternType().setSuperInterfaces(superIfaces);
            -		}
            -		return this;
            -	}
            -
             	/**
             	 * @return a List of {@link CtElement}s, which has to be used as pattern model
             	 */
            @@ -276,7 +188,7 @@ public List getPatternElements() {
             	 * @param template a List of {@link CtElement}s, which has to be used as pattern model
             	 */
             	@SuppressWarnings({ "unchecked", "rawtypes" })
            -	public void setElements(List template) {
            +	private void setElements(List template) {
             		this.elements = (List) template;
             	}
             }
            diff --git a/src/main/java/spoon/pattern/ParametersBuilder.java b/src/main/java/spoon/pattern/PatternParameterConfigurator.java
            similarity index 83%
            rename from src/main/java/spoon/pattern/ParametersBuilder.java
            rename to src/main/java/spoon/pattern/PatternParameterConfigurator.java
            index dda4ad9ef75..ee52ab98f19 100644
            --- a/src/main/java/spoon/pattern/ParametersBuilder.java
            +++ b/src/main/java/spoon/pattern/PatternParameterConfigurator.java
            @@ -38,7 +38,6 @@
             import spoon.reflect.code.CtArrayAccess;
             import spoon.reflect.code.CtBlock;
             import spoon.reflect.code.CtExpression;
            -import spoon.reflect.code.CtFieldRead;
             import spoon.reflect.code.CtInvocation;
             import spoon.reflect.code.CtLiteral;
             import spoon.reflect.code.CtReturn;
            @@ -63,19 +62,23 @@
             import spoon.reflect.visitor.filter.NamedElementFilter;
             import spoon.reflect.visitor.filter.PotentialVariableDeclarationFunction;
             import spoon.reflect.visitor.filter.VariableReferenceFunction;
            +import spoon.support.Experimental;
             import spoon.template.TemplateParameter;
             
             /**
            - * Used to define Pattern parameters and their mapping to Pattern model
            + * Used to define pattern parameters.
            + *
            + * Main documentation at http://spoon.gforge.inria.fr/pattern.html.
              */
            -public class ParametersBuilder {
            +@Experimental
            +public class PatternParameterConfigurator {
             	private final PatternBuilder patternBuilder;
             	private final Map parameterInfos;
             	private AbstractParameterInfo currentParameter;
             	private List substitutedNodes = new ArrayList<>();
             	private ConflictResolutionMode conflictResolutionMode = ConflictResolutionMode.FAIL;
             
            -	ParametersBuilder(PatternBuilder patternBuilder, Map parameterInfos) {
            +	PatternParameterConfigurator(PatternBuilder patternBuilder, Map parameterInfos) {
             		this.patternBuilder = patternBuilder;
             		this.parameterInfos = parameterInfos;
             	}
            @@ -92,7 +95,7 @@ public ConflictResolutionMode getConflictResolutionMode() {
             	 * @param conflictResolutionMode to be applied mode
             	 * @return this to support fluent API
             	 */
            -	public ParametersBuilder setConflictResolutionMode(ConflictResolutionMode conflictResolutionMode) {
            +	public PatternParameterConfigurator setConflictResolutionMode(ConflictResolutionMode conflictResolutionMode) {
             		this.conflictResolutionMode = conflictResolutionMode;
             		return this;
             	}
            @@ -113,26 +116,26 @@ private AbstractParameterInfo getParameterInfo(String parameterName, boolean cre
             	/**
             	 * 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
            +	 * @return this {@link PatternParameterConfigurator} to support fluent API
             	 */
            -	public ParametersBuilder parameter(String paramName) {
            +	public PatternParameterConfigurator parameter(String paramName) {
             		currentParameter = getParameterInfo(paramName, true);
             		substitutedNodes.clear();
             		return this;
             	}
             
            -	public ParametersBuilder setMinOccurence(int minOccurence) {
            +	public PatternParameterConfigurator setMinOccurence(int minOccurence) {
             		currentParameter.setMinOccurences(minOccurence);
             		return this;
             	}
            -	public ParametersBuilder setMaxOccurence(int maxOccurence) {
            +	public PatternParameterConfigurator setMaxOccurence(int maxOccurence) {
             		if (maxOccurence == ParameterInfo.UNLIMITED_OCCURENCES || maxOccurence > 1 && currentParameter.isMultiple() == false) {
             			throw new SpoonException("Cannot set maxOccurences > 1 for single value parameter. Call setMultiple(true) first.");
             		}
             		currentParameter.setMaxOccurences(maxOccurence);
             		return this;
             	}
            -	public ParametersBuilder setMatchingStrategy(Quantifier quantifier) {
            +	public PatternParameterConfigurator setMatchingStrategy(Quantifier quantifier) {
             		currentParameter.setMatchingStrategy(quantifier);
             		return this;
             	}
            @@ -141,9 +144,9 @@ public ParametersBuilder setMatchingStrategy(Quantifier quantifier) {
             	 * Set expected type of Parameter. In some cases legacy Template needs to know the type of parameter value to select substituted element.
             	 * See {@link ValueConvertor}, which provides conversion between matched element and expected parameter type
             	 * @param valueType a expected type of parameter value
            -	 * @return this {@link ParametersBuilder} to support fluent API
            +	 * @return this {@link PatternParameterConfigurator} to support fluent API
             	 */
            -	public ParametersBuilder setValueType(Class valueType) {
            +	public PatternParameterConfigurator setValueType(Class valueType) {
             		currentParameter.setParameterValueType(valueType);
             		return this;
             	}
            @@ -152,9 +155,9 @@ public ParametersBuilder setValueType(Class valueType) {
             	 * Defines type of parameter value (List/Set/Map/single).
             	 * If not defined then real value type of property is used. If null, then default is {@link ContainerKind#SINGLE}
             	 * @param containerKind to be used {@link ContainerKind}
            -	 * @return this {@link ParametersBuilder} to support fluent API
            +	 * @return this {@link PatternParameterConfigurator} to support fluent API
             	 */
            -	public ParametersBuilder setContainerKind(ContainerKind containerKind) {
            +	public PatternParameterConfigurator setContainerKind(ContainerKind containerKind) {
             		currentParameter.setContainerKind(containerKind);
             		return this;
             	}
            @@ -169,25 +172,25 @@ public ParameterInfo getCurrentParameter() {
             	/**
             	 * `type` itself and all the references to the `type` are subject for substitution by current parameter
             	 * @param type to be substituted Class
            -	 * @return {@link ParametersBuilder} to support fluent API
            +	 * @return {@link PatternParameterConfigurator} to support fluent API
             	 */
            -	public ParametersBuilder byType(Class type) {
            +	public PatternParameterConfigurator byType(Class type) {
             		return byType(type.getName());
             	}
             	/**
             	 * type identified by `typeQualifiedName` itself and all the references to that type are subject for substitution by current parameter
             	 * @param typeQualifiedName a fully qualified name of to be substituted Class
            -	 * @return {@link ParametersBuilder} to support fluent API
            +	 * @return {@link PatternParameterConfigurator} to support fluent API
             	 */
            -	public ParametersBuilder byType(String typeQualifiedName) {
            +	public PatternParameterConfigurator byType(String typeQualifiedName) {
             		return byType(patternBuilder.getFactory().Type().createReference(typeQualifiedName));
             	}
             	/**
             	 * type referred by {@link CtTypeReference} `type` and all the references to that type are subject for substitution by current parameter
             	 * @param type a fully qualified name of to be substituted Class
            -	 * @return {@link ParametersBuilder} to support fluent API
            +	 * @return {@link PatternParameterConfigurator} to support fluent API
             	 */
            -	public ParametersBuilder byType(CtTypeReference type) {
            +	public PatternParameterConfigurator byType(CtTypeReference type) {
             		ParameterInfo pi = getCurrentParameter();
             		//substitute all references to that type
             		queryModel().filterChildren((CtTypeReference typeRef) -> typeRef.equals(type))
            @@ -210,9 +213,9 @@ public ParametersBuilder byType(CtTypeReference type) {
             	 * 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
            +	 * @return {@link PatternParameterConfigurator} to support fluent API
             	 */
            -	public ParametersBuilder byLocalType(CtType searchScope, String localTypeSimpleName) {
            +	public PatternParameterConfigurator 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");
            @@ -222,24 +225,12 @@ public ParametersBuilder byLocalType(CtType searchScope, String localTypeSimp
             		return this;
             	}
             
            -	/**
            -	 * variable read/write of `variable`
            -	 * @param variableName a variable whose references will be substituted
            -	 * @return {@link ParametersBuilder} to support fluent API
            -	 */
            -	public ParametersBuilder byVariable(String variableName) {
            -		CtVariable var = queryModel().map(new PotentialVariableDeclarationFunction(variableName)).first();
            -		if (var != null) {
            -			byVariable(var);
            -		}	//else may be we should fail?
            -		return this;
            -	}
             	/**
             	 * variable read/write of `variable`
             	 * @param variable a variable whose references will be substituted
            -	 * @return {@link ParametersBuilder} to support fluent API
            +	 * @return {@link PatternParameterConfigurator} to support fluent API
             	 */
            -	public ParametersBuilder byVariable(CtVariable variable) {
            +	public PatternParameterConfigurator byVariable(CtVariable variable) {
             		ParameterInfo pi = getCurrentParameter();
             		CtQueryable root = queryModel();
             		if (patternBuilder.isInModel(variable)) {
            @@ -256,9 +247,9 @@ public ParametersBuilder byVariable(CtVariable variable) {
             	/**
             	 * each invocation of `method` will be replaces by parameter value
             	 * @param method the method whose invocation has to be substituted
            -	 * @return {@link ParametersBuilder} to support fluent API
            +	 * @return {@link PatternParameterConfigurator} to support fluent API
             	 */
            -	public ParametersBuilder byInvocation(CtMethod method) {
            +	public PatternParameterConfigurator byInvocation(CtMethod method) {
             		ParameterInfo pi = getCurrentParameter();
             		queryModel().filterChildren(new InvocationFilter(method))
             			.forEach((CtInvocation inv) -> {
            @@ -270,61 +261,31 @@ public ParametersBuilder byInvocation(CtMethod method) {
             	/**
             	 * Add parameters for each field reference to variable named `variableName`
             	 * @param variableName the name of the variable reference
            -	 * @return {@link ParametersBuilder} to support fluent API
            +	 * @return {@link PatternParameterConfigurator} to support fluent API
             	 */
            -	public ParametersBuilder createPatternParameterForVariable(String... variableName) {
            +	public PatternParameterConfigurator byVariable(String... variableName) {
             		for (String varName : variableName) {
             			CtVariable var = queryModel().map(new PotentialVariableDeclarationFunction(varName)).first();
             			if (var != null) {
            -				createPatternParameterForVariable(var);
            +				byVariable(var);
             			} else {
             				List> vars = queryModel().filterChildren(new NamedElementFilter(CtVariable.class, varName)).list();
             				if (vars.size() > 1) {
             					throw new SpoonException("Ambiguous variable " + varName);
             				} else if (vars.size() == 1) {
            -					createPatternParameterForVariable(vars.get(0));
            +					byVariable(vars.get(0));
             				} //else may be we should fail when variable is not found?
             			}
             		}
             		return this;
             	}
            -	/**
            -	 * Add parameters for each variable reference of `variable`
            -	 * @param variable to be substituted variable
            -	 * @return this to support fluent API
            -	 */
            -	private ParametersBuilder createPatternParameterForVariable(CtVariable variable) {
            -		CtQueryable searchScope;
            -		if (patternBuilder.isInModel(variable)) {
            -			addSubstitutionRequest(
            -					parameter(variable.getSimpleName()).getCurrentParameter(),
            -					variable);
            -			searchScope = variable;
            -		} else {
            -			searchScope = queryModel();
            -		}
            -		searchScope.map(new VariableReferenceFunction(variable))
            -		.forEach((CtVariableReference varRef) -> {
            -			CtFieldRead fieldRead = varRef.getParent(CtFieldRead.class);
            -			if (fieldRead != null) {
            -				addSubstitutionRequest(
            -						parameter(fieldRead.getVariable().getSimpleName()).getCurrentParameter(),
            -						fieldRead);
            -			} else {
            -				addSubstitutionRequest(
            -						parameter(varRef.getSimpleName()).getCurrentParameter(),
            -						varRef);
            -			}
            -		});
            -		return this;
            -	}
             
             	/**
             	 * variable read/write of `variable` of type {@link TemplateParameter}
             	 * @param variable a variable whose references will be substituted
            -	 * @return {@link ParametersBuilder} to support fluent API
            +	 * @return {@link PatternParameterConfigurator} to support fluent API
             	 */
            -	public ParametersBuilder byTemplateParameterReference(CtVariable variable) {
            +	public PatternParameterConfigurator byTemplateParameterReference(CtVariable variable) {
             		ParameterInfo pi = getCurrentParameter();
             		queryModel().map(new VariableReferenceFunction(variable))
             			.forEach((CtVariableReference varRef) -> {
            @@ -348,9 +309,9 @@ public ParametersBuilder byTemplateParameterReference(CtVariable variable) {
             	/**
             	 * CodeElement element identified by `simpleName`
             	 * @param simpleName the name of the element or reference
            -	 * @return {@link ParametersBuilder} to support fluent API
            +	 * @return {@link PatternParameterConfigurator} to support fluent API
             	 */
            -//	public ParametersBuilder codeElementBySimpleName(String simpleName) {
            +//	public PatternParameterConfigurator codeElementBySimpleName(String simpleName) {
             //		ParameterInfo pi = getCurrentParameter();
             //		pattern.getModel().filterChildren((CtNamedElement named) -> simpleName.equals(named.getSimpleName()))
             //			.forEach((CtNamedElement named) -> {
            @@ -375,9 +336,9 @@ public ParametersBuilder byTemplateParameterReference(CtVariable variable) {
             	 * All spoon model string attributes whose value is equal to `stringMarker`
             	 * are subject for substitution by current parameter
             	 * @param stringMarker a string value which has to be substituted
            -	 * @return {@link ParametersBuilder} to support fluent API
            +	 * @return {@link PatternParameterConfigurator} to support fluent API
             	 */
            -	public ParametersBuilder byString(String stringMarker) {
            +	public PatternParameterConfigurator byString(String stringMarker) {
             		ParameterInfo pi = getCurrentParameter();
             		new StringAttributeScanner() {
             			@Override
            @@ -406,9 +367,9 @@ protected void visitStringAttribute(RoleHandler roleHandler, CtElement element,
             	 * All spoon model string attributes whose value contains whole string or a substring equal to `stringMarker`
             	 * are subject for substitution by current parameter. Only the `stringMarker` substring of the string value is substituted!
             	 * @param stringMarker a string value which has to be substituted
            -	 * @return {@link ParametersBuilder} to support fluent API
            +	 * @return {@link PatternParameterConfigurator} to support fluent API
             	 */
            -	public ParametersBuilder bySubstring(String stringMarker) {
            +	public PatternParameterConfigurator bySubstring(String stringMarker) {
             		ParameterInfo pi = getCurrentParameter();
             		new StringAttributeScanner() {
             			@Override
            @@ -482,23 +443,12 @@ private void visitStringAttribute(CtElement element) {
             		protected abstract void visitStringAttribute(RoleHandler roleHandler, CtElement element, String mapEntryKey, CtElement mapEntryValue);
             	}
             
            -	/**
            -	 * Any named element or reference identified by it's simple name
            -	 * @param simpleName simple name of {@link CtNamedElement} or {@link CtReference}
            -	 * @return {@link ParametersBuilder} to support fluent API
            -	 */
            -	public ParametersBuilder byName(String simpleName) {
            -		byNamedElement(simpleName);
            -		byReferenceName(simpleName);
            -		return this;
            -	}
            -
             	/**
             	 * Any named element by it's simple name
             	 * @param simpleName simple name of {@link CtNamedElement}
            -	 * @return {@link ParametersBuilder} to support fluent API
            +	 * @return {@link PatternParameterConfigurator} to support fluent API
             	 */
            -	public ParametersBuilder byNamedElement(String simpleName) {
            +	public PatternParameterConfigurator byNamedElement(String simpleName) {
             		ParameterInfo pi = getCurrentParameter();
             		queryModel().filterChildren((CtNamedElement named) -> simpleName.equals(named.getSimpleName()))
             			.forEach((CtNamedElement named) -> {
            @@ -513,13 +463,13 @@ public ParametersBuilder byNamedElement(String simpleName) {
             	 * Can be used to match any method call for instance.
             	 *
             	 * In some cases, the selected object is actually the parent of the reference (eg the invocation).
            -	 * This is implemented in {@link ParametersBuilder#getSubstitutedNodeOfElement(ParameterInfo, CtElement)}
            +	 * This is implemented in {@link PatternParameterConfigurator#getSubstitutedNodeOfElement(ParameterInfo, CtElement)}
             	 *
             	 *
             	 * @param simpleName simple name of {@link CtReference}
            -	 * @return {@link ParametersBuilder} to support fluent API
            +	 * @return {@link PatternParameterConfigurator} to support fluent API
             	 */
            -	public ParametersBuilder byReferenceName(String simpleName) {
            +	public PatternParameterConfigurator byReferenceName(String simpleName) {
             		ParameterInfo pi = getCurrentParameter();
             		queryModel().filterChildren((CtReference ref) -> simpleName.equals(ref.getSimpleName()))
             			.forEach((CtReference ref) -> {
            @@ -531,9 +481,9 @@ public ParametersBuilder byReferenceName(String simpleName) {
             	/**
             	 * All elements matched by {@link Filter} will be substituted by parameter value
             	 * @param filter {@link Filter}, which defines to be substituted elements
            -	 * @return {@link ParametersBuilder} to support fluent API
            +	 * @return {@link PatternParameterConfigurator} to support fluent API
             	 */
            -	public ParametersBuilder byFilter(Filter filter) {
            +	public PatternParameterConfigurator byFilter(Filter filter) {
             		ParameterInfo pi = getCurrentParameter();
             		queryModel().filterChildren(filter)
             			.forEach((CtElement ele) -> {
            @@ -544,11 +494,11 @@ public ParametersBuilder byFilter(Filter filter) {
             
             	/**
             	 * Attribute defined by `role` of all elements matched by {@link Filter} will be substituted by parameter value
            -	 * @param filter {@link Filter}, which defines to be substituted elements
             	 * @param role {@link CtRole}, which defines to be substituted elements
            -	 * @return {@link ParametersBuilder} to support fluent API
            +	 * @param filter {@link Filter}, which defines to be substituted elements
            +	 * @return {@link PatternParameterConfigurator} to support fluent API
             	 */
            -	public ParametersBuilder byRole(Filter filter, CtRole role) {
            +	public PatternParameterConfigurator byRole(CtRole role, Filter filter) {
             		ParameterInfo pi = getCurrentParameter();
             		queryModel().filterChildren(filter)
             			.forEach((CtElement ele) -> {
            @@ -562,17 +512,17 @@ public ParametersBuilder byRole(Filter filter, CtRole role) {
             	 * @param matchCondition a {@link Predicate} which selects matching values
             	 * @return this to support fluent API
             	 */
            -	public  ParametersBuilder matchCondition(Class type, Predicate matchCondition) {
            +	public  PatternParameterConfigurator byCondition(Class type, Predicate matchCondition) {
             		currentParameter.setMatchCondition(type, matchCondition);
             		return this;
             	}
             
             	/**
            -	 * marks all CtIf and CtForEach whose expression is substituted by a this pattern parameter as inline statement.
            +	 * marks a CtIf and CtForEach to be matched, even when inlined.
             	 * @return this to support fluent API
             	 */
            -	public ParametersBuilder matchInlinedStatements() {
            -		InlineStatementsBuilder sb = new InlineStatementsBuilder(patternBuilder);
            +	public PatternParameterConfigurator matchInlinedStatements() {
            +		InlinedStatementConfigurator sb = new InlinedStatementConfigurator(patternBuilder);
             		for (CtElement ctElement : substitutedNodes) {
             			sb.byElement(ctElement);
             		}
            diff --git a/src/main/java/spoon/pattern/internal/DefaultGenerator.java b/src/main/java/spoon/pattern/internal/DefaultGenerator.java
            index 18d42550f4b..a7521ed3f32 100644
            --- a/src/main/java/spoon/pattern/internal/DefaultGenerator.java
            +++ b/src/main/java/spoon/pattern/internal/DefaultGenerator.java
            @@ -27,7 +27,7 @@
             import spoon.reflect.declaration.CtTypeMember;
             import spoon.reflect.factory.Factory;
             import spoon.support.SpoonClassNotFoundException;
            -import spoon.support.util.ParameterValueProvider;
            +import spoon.support.util.ImmutableMap;
             
             /**
              * Drives generation process
            @@ -42,7 +42,7 @@ public DefaultGenerator(Factory factory) {
             	}
             
             	@Override
            -	public  void generateTargets(RootNode node, ResultHolder result, ParameterValueProvider parameters) {
            +	public  void generateTargets(RootNode node, ResultHolder result, ImmutableMap parameters) {
             		node.generateTargets(this, result, parameters);
             		if (node.isSimplifyGenerated()) {
             			// simplify this element, it contains a substituted element
            @@ -71,7 +71,7 @@ public  void generateTargets(RootNode node, ResultHolder result, Parameter
             	}
             
             	@Override
            -	public  void getValueAs(ParameterInfo parameterInfo, ResultHolder result, ParameterValueProvider parameters) {
            +	public  void getValueAs(ParameterInfo parameterInfo, ResultHolder result, ImmutableMap parameters) {
             		parameterInfo.getValueAs(factory, result, parameters);
             	}
             
            diff --git a/src/main/java/spoon/pattern/internal/Generator.java b/src/main/java/spoon/pattern/internal/Generator.java
            index 166c6cbf979..0dd4432db5a 100644
            --- a/src/main/java/spoon/pattern/internal/Generator.java
            +++ b/src/main/java/spoon/pattern/internal/Generator.java
            @@ -22,7 +22,7 @@
             import spoon.pattern.internal.parameter.ParameterInfo;
             import spoon.reflect.declaration.CtElement;
             import spoon.reflect.factory.Factory;
            -import spoon.support.util.ParameterValueProvider;
            +import spoon.support.util.ImmutableMap;
             
             /**
              * Drives generation process
            @@ -46,7 +46,7 @@ public interface Generator {
             	 * @param result the holder which receives the generated node
             	 * @param parameters the input parameters
             	 */
            -	 void generateTargets(RootNode node, ResultHolder result, ParameterValueProvider parameters);
            +	 void generateTargets(RootNode node, ResultHolder result, ImmutableMap parameters);
             
             	/**
             	 * Returns zero, one or more values into `result`. The value comes from `parameters` from the location defined by `parameterInfo`
            @@ -54,17 +54,17 @@ public interface Generator {
             	 * @param result the holder which receives the generated node
             	 * @param parameters the input parameters
             	 */
            -	 void getValueAs(ParameterInfo parameterInfo, ResultHolder result, ParameterValueProvider parameters);
            +	 void getValueAs(ParameterInfo parameterInfo, ResultHolder result, ImmutableMap parameters);
             
             	/**
             	 * Generates one target depending on kind of this {@link RootNode}, expected `expectedType` and input `parameters`
             	 * @param node to be generated node
            -	 * @param parameters {@link ParameterValueProvider}
            +	 * @param parameters {@link ImmutableMap}
             	 * @param expectedType defines {@link Class} of returned value
             	 *
             	 * @return a generate value or null
             	 */
            -	default  T generateSingleTarget(RootNode node, ParameterValueProvider parameters, Class expectedType) {
            +	default  T generateSingleTarget(RootNode node, ImmutableMap parameters, Class expectedType) {
             		ResultHolder.Single result = new ResultHolder.Single<>(expectedType);
             		generateTargets(node, result, parameters);
             		return result.getResult();
            @@ -73,12 +73,12 @@ default  T generateSingleTarget(RootNode node, ParameterValueProvider paramet
             	/**
             	 * Generates zero, one or more targets depending on kind of this {@link RootNode}, expected `expectedType` and input `parameters`
             	 * @param node to be generated node
            -	 * @param parameters {@link ParameterValueProvider}
            +	 * @param parameters {@link ImmutableMap}
             	 * @param expectedType defines {@link Class} of returned value
             	 *
             	 * @return a {@link List} of generated targets
             	 */
            -	default  List generateTargets(RootNode node, ParameterValueProvider parameters, Class expectedType) {
            +	default  List generateTargets(RootNode node, ImmutableMap parameters, Class expectedType) {
             		ResultHolder.Multiple result = new ResultHolder.Multiple<>(expectedType);
             		generateTargets(node, result, parameters);
             		return result.getResult();
            diff --git a/src/main/java/spoon/pattern/internal/PatternPrinter.java b/src/main/java/spoon/pattern/internal/PatternPrinter.java
            index 536413190d4..53b2b51070e 100644
            --- a/src/main/java/spoon/pattern/internal/PatternPrinter.java
            +++ b/src/main/java/spoon/pattern/internal/PatternPrinter.java
            @@ -40,7 +40,7 @@
             import spoon.reflect.visitor.PrinterHelper;
             import spoon.support.DefaultCoreFactory;
             import spoon.support.StandardEnvironment;
            -import spoon.support.util.ParameterValueProvider;
            +import spoon.support.util.ImmutableMap;
             
             /**
              */
            @@ -58,7 +58,7 @@ public PatternPrinter() {
             	}
             
             	public String printNode(RootNode node) {
            -		List generated = generateTargets(node, (ParameterValueProvider) null, null);
            +		List generated = generateTargets(node, (ImmutableMap) null, null);
             		StringBuilder sb = new StringBuilder();
             		for (Object ele : generated) {
             			sb.append(ele.toString()).append('\n');
            @@ -67,7 +67,7 @@ public String printNode(RootNode node) {
             	}
             
             	@Override
            -	public  void generateTargets(RootNode node, ResultHolder result, ParameterValueProvider parameters) {
            +	public  void generateTargets(RootNode node, ResultHolder result, ImmutableMap parameters) {
             		int firstResultIdx = result.getResults().size();
             		if (node instanceof InlineNode) {
             			//this is a inline node. Do not generated nodes normally, but generate origin inline statements
            @@ -129,7 +129,7 @@ private  T getFirstResult(ResultHolder result, int firstResultIdx) {
             	}
             
             	@Override
            -	public  void getValueAs(ParameterInfo parameterInfo, ResultHolder result, ParameterValueProvider parameters) {
            +	public  void getValueAs(ParameterInfo parameterInfo, ResultHolder result, ImmutableMap parameters) {
             		Object obj = generatePatternParameterElement(parameterInfo, result.getRequiredClass());
             		if (obj != null) {
             			result.addResult((T) obj);
            diff --git a/src/main/java/spoon/pattern/internal/matcher/TobeMatched.java b/src/main/java/spoon/pattern/internal/matcher/TobeMatched.java
            index 2cf886950fb..850b8ce545c 100644
            --- a/src/main/java/spoon/pattern/internal/matcher/TobeMatched.java
            +++ b/src/main/java/spoon/pattern/internal/matcher/TobeMatched.java
            @@ -26,17 +26,17 @@
             
             import spoon.SpoonException;
             import spoon.reflect.meta.ContainerKind;
            -import spoon.support.util.ParameterValueProvider;
            +import spoon.support.util.ImmutableMap;
             
             /**
              * Describes what next has to be matched.
            - * It consists of current `parameters` represented by {@link ParameterValueProvider}
            + * It consists of current `parameters` represented by {@link ImmutableMap}
              * and by a to be matched target elements.
              * See children of {@link TobeMatched} for supported collections of targer elements.
              */
             public class TobeMatched {
             	//TODO remove parameters. Send them individually into matching methods and return MatchResult
            -	private final ParameterValueProvider parameters;
            +	private final ImmutableMap parameters;
             	//Use list for everything because Spoon uses Sets with predictable iteration order
             	private final List targets;
             	private final boolean ordered;
            @@ -47,7 +47,7 @@ public class TobeMatched {
             	 * @param target the to be matched target data. List, Set, Map or single value
             	 * @return new instance of {@link TobeMatched}, which contains `parameters` and `target` mapped using containerKind
             	 */
            -	public static TobeMatched create(ParameterValueProvider parameters, ContainerKind containerKind, Object target) {
            +	public static TobeMatched create(ImmutableMap parameters, ContainerKind containerKind, Object target) {
             		switch (containerKind) {
             		case LIST:
             			return new TobeMatched(parameters, (List) target, true);
            @@ -61,7 +61,7 @@ public static TobeMatched create(ParameterValueProvider parameters, ContainerKin
             		throw new SpoonException("Unexpected RoleHandler containerKind: " + containerKind);
             	}
             
            -	private TobeMatched(ParameterValueProvider parameters, Object target) {
            +	private TobeMatched(ImmutableMap parameters, Object target) {
             		//It is correct to put whole container as single value in cases when ParameterNode matches agains whole attribute value
             //		if (target instanceof Collection || target instanceof Map) {
             //			throw new SpoonException("Invalid argument. Use other constructors");
            @@ -77,21 +77,21 @@ private TobeMatched(ParameterValueProvider parameters, Object target) {
             	 * @param ordered defines the way how targets are matched. If true then first target is matched with first ValueResolver.
             	 * If false then all targets are tried with first ValueResolver.
             	 */
            -	private TobeMatched(ParameterValueProvider parameters, Collection targets, boolean ordered) {
            +	private TobeMatched(ImmutableMap parameters, Collection targets, boolean ordered) {
             		this.parameters = parameters;
             		//make a copy of origin collection, because it might be modified during matching process (by a refactoring algorithm)
             		this.targets = targets == null ? Collections.emptyList() : Collections.unmodifiableList(new ArrayList<>(targets));
             		this.ordered = ordered;
             	}
             
            -	private TobeMatched(ParameterValueProvider parameters, Map targets) {
            +	private TobeMatched(ImmutableMap parameters, Map targets) {
             		this.parameters = parameters;
             		//make a copy of origin collection, because it might be modified during matching process (by a refactoring algorithm)
             		this.targets = targets == null ? Collections.emptyList() : Collections.unmodifiableList(new ArrayList<>(targets.entrySet()));
             		this.ordered = false;
             	}
             
            -	private TobeMatched(ParameterValueProvider parameters, List targets, boolean ordered, int tobeRemovedIndex) {
            +	private TobeMatched(ImmutableMap parameters, List targets, boolean ordered, int tobeRemovedIndex) {
             		this.parameters = parameters;
             		this.targets = new ArrayList<>(targets);
             		if (tobeRemovedIndex >= 0) {
            @@ -103,7 +103,7 @@ private TobeMatched(ParameterValueProvider parameters, List targets, boolean
             	/**
             	 * @return parameters of last successful match.
             	 */
            -	public ParameterValueProvider getParameters() {
            +	public ImmutableMap getParameters() {
             		return parameters;
             	}
             
            @@ -163,7 +163,7 @@ public boolean hasTargets() {
             	 * @param newParams to be used parameters
             	 * @return copy of {@link TobeMatched} with new parameters
             	 */
            -	public TobeMatched copyAndSetParams(ParameterValueProvider newParams) {
            +	public TobeMatched copyAndSetParams(ImmutableMap newParams) {
             		if (parameters == newParams) {
             			return this;
             		}
            @@ -175,14 +175,14 @@ public TobeMatched copyAndSetParams(ParameterValueProvider newParams) {
             	 * @param matcher a matching algorithm
             	 * @return {@link TobeMatched} with List of remaining (to be matched) targets or null if there is no match
             	 */
            -	public TobeMatched matchNext(BiFunction matcher) {
            +	public TobeMatched matchNext(BiFunction matcher) {
             		if (targets.isEmpty()) {
             			//no target -> no match
             			return null;
             		}
             		if (ordered) {
             			//handle ordered list of targets - match with first target
            -			ParameterValueProvider parameters = matcher.apply(targets.get(0), getParameters());
            +			ImmutableMap parameters = matcher.apply(targets.get(0), getParameters());
             			if (parameters != null) {
             				//return remaining match
             				return removeTarget(parameters, 0);
            @@ -192,7 +192,7 @@ public TobeMatched matchNext(BiFunction consumer) {
             	}
             
             	@Override
            -	public  void generateTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters) {
            +	public  void generateTargets(Generator generator, ResultHolder result, ImmutableMap parameters) {
             		result.addResult((U) template);
             	}
             
             	@Override
            -	public ParameterValueProvider matchTarget(Object target, ParameterValueProvider parameters) {
            +	public ImmutableMap matchTarget(Object target, ImmutableMap parameters) {
             		if (target == null && template == null) {
             			return parameters;
             		}
            @@ -79,7 +79,7 @@ public Quantifier getMatchingStrategy() {
             	}
             
             	@Override
            -	public boolean isTryNextMatch(ParameterValueProvider parameters) {
            +	public boolean isTryNextMatch(ImmutableMap parameters) {
             		//it always matches only once
             		return false;
             	}
            diff --git a/src/main/java/spoon/pattern/internal/node/ElementNode.java b/src/main/java/spoon/pattern/internal/node/ElementNode.java
            index f7f16eaeadd..95ed02b2f39 100644
            --- a/src/main/java/spoon/pattern/internal/node/ElementNode.java
            +++ b/src/main/java/spoon/pattern/internal/node/ElementNode.java
            @@ -41,7 +41,7 @@
             import spoon.reflect.meta.ContainerKind;
             import spoon.reflect.path.CtRole;
             import spoon.reflect.reference.CtExecutableReference;
            -import spoon.support.util.ParameterValueProvider;
            +import spoon.support.util.ImmutableMap;
             
             /**
              * Generates/Matches a copy of a single CtElement AST node with all it's children (whole AST tree of the root CtElement)
            @@ -273,7 +273,7 @@ public void forEachParameterInfo(BiConsumer consumer) {
             
             	@SuppressWarnings("unchecked")
             	@Override
            -	public  void generateTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters) {
            +	public  void generateTargets(Generator generator, ResultHolder result, ImmutableMap parameters) {
             		//TODO implement create on Metamodel.Type
             		CtElement clone = generator.getFactory().Core().create(elementType.getModelInterface());
             		generateSingleNodeAttributes(generator, clone, parameters);
            @@ -281,7 +281,7 @@ public  void generateTargets(Generator generator, ResultHolder result, Par
             		result.addResult((U) clone);
             	}
             
            -	protected void generateSingleNodeAttributes(Generator generator, CtElement clone, ParameterValueProvider parameters) {
            +	protected void generateSingleNodeAttributes(Generator generator, CtElement clone, ImmutableMap parameters) {
             		for (Map.Entry e : getRoleToNode().entrySet()) {
             			Metamodel.Field mmField = e.getKey();
             			switch (mmField.getContainerKind()) {
            @@ -310,7 +310,7 @@ private  Map entriesToMap(List entries) {
             	}
             
             	@Override
            -	public ParameterValueProvider matchTarget(Object target, ParameterValueProvider parameters) {
            +	public ImmutableMap matchTarget(Object target, ImmutableMap parameters) {
             		if (target == null) {
             			return null;
             		}
            @@ -330,7 +330,7 @@ public ParameterValueProvider matchTarget(Object target, ParameterValueProvider
             		return parameters;
             	}
             
            -	protected ParameterValueProvider matchesRole(ParameterValueProvider parameters, CtElement target, Metamodel.Field mmField, RootNode attrNode) {
            +	protected ImmutableMap matchesRole(ImmutableMap parameters, CtElement target, Metamodel.Field mmField, RootNode attrNode) {
             		if (isMatchingRole(mmField.getRole(), elementType.getModelInterface()) == false) {
             			return parameters;
             		}
            @@ -417,7 +417,7 @@ public Quantifier getMatchingStrategy() {
             	}
             
             	@Override
            -	public boolean isTryNextMatch(ParameterValueProvider parameters) {
            +	public boolean isTryNextMatch(ImmutableMap parameters) {
             		//it always matches only once
             		return false;
             	}
            diff --git a/src/main/java/spoon/pattern/internal/node/ForEachNode.java b/src/main/java/spoon/pattern/internal/node/ForEachNode.java
            index c7e64efaae7..fd086b8ffbf 100644
            --- a/src/main/java/spoon/pattern/internal/node/ForEachNode.java
            +++ b/src/main/java/spoon/pattern/internal/node/ForEachNode.java
            @@ -29,7 +29,7 @@
             import spoon.reflect.code.CtForEach;
             import spoon.reflect.code.CtStatement;
             import spoon.reflect.factory.Factory;
            -import spoon.support.util.ParameterValueProvider;
            +import spoon.support.util.ImmutableMap;
             
             /**
              * Pattern node of multiple occurrences of the same model, just with different parameters.
            @@ -71,7 +71,7 @@ public boolean replaceNode(RootNode oldNode, RootNode newNode) {
             	}
             
             	@Override
            -	public  void generateTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters) {
            +	public  void generateTargets(Generator generator, ResultHolder result, ImmutableMap parameters) {
             		for (Object parameterValue : generator.generateTargets(iterableParameter, parameters, Object.class)) {
             			generator.generateTargets(nestedModel, result, parameters.putValue(localParameter.getName(), parameterValue));
             		}
            @@ -90,9 +90,9 @@ public TobeMatched matchAllWith(TobeMatched tobeMatched) {
             			return null;
             		}
             		//it matched.
            -		ParameterValueProvider newParameters = tobeMatched.getParameters();
            +		ImmutableMap newParameters = tobeMatched.getParameters();
             		//copy values of local parameters
            -		for (Map.Entry e : localMatch.getParameters().getModifiedParameters().entrySet()) {
            +		for (Map.Entry e : localMatch.getParameters().getModifiedValues().entrySet()) {
             			String name = e.getKey();
             			Object value = e.getValue();
             			if (name.equals(localParameter.getName())) {
            @@ -136,17 +136,17 @@ public boolean isRepeatable() {
             	}
             
             	@Override
            -	public boolean isMandatory(ParameterValueProvider parameters) {
            +	public boolean isMandatory(ImmutableMap parameters) {
             		return iterableParameter.isMandatory(parameters);
             	}
             
             	@Override
            -	public boolean isTryNextMatch(ParameterValueProvider parameters) {
            +	public boolean isTryNextMatch(ImmutableMap parameters) {
             		return iterableParameter.isTryNextMatch(parameters);
             	}
             
             	@Override
            -	public  void generateInlineTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters) {
            +	public  void generateInlineTargets(Generator generator, ResultHolder result, ImmutableMap parameters) {
             		Factory f = generator.getFactory();
             		CtForEach forEach = f.Core().createForEach();
             		forEach.setVariable(f.Code().createLocalVariable(f.Type().objectType(), localParameter.getName(), null));
            diff --git a/src/main/java/spoon/pattern/internal/node/InlineNode.java b/src/main/java/spoon/pattern/internal/node/InlineNode.java
            index 507caa6ebad..88b3556278c 100644
            --- a/src/main/java/spoon/pattern/internal/node/InlineNode.java
            +++ b/src/main/java/spoon/pattern/internal/node/InlineNode.java
            @@ -18,7 +18,7 @@
             
             import spoon.pattern.internal.Generator;
             import spoon.pattern.internal.ResultHolder;
            -import spoon.support.util.ParameterValueProvider;
            +import spoon.support.util.ImmutableMap;
             
             /**
              * Represents a kind of {@link RootNode},
            @@ -32,7 +32,7 @@ public interface InlineNode extends RootNode {
             	 * This method is used when sources of pattern have to be printed
             	 * @param generator a to be used {@link Generator}
             	 * @param result holder of the result
            -	 * @param parameters a {@link ParameterValueProvider} with current parameters
            +	 * @param parameters a {@link ImmutableMap} with current parameters
             	 */
            -	 void generateInlineTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters);
            +	 void generateInlineTargets(Generator generator, ResultHolder result, ImmutableMap parameters);
             }
            diff --git a/src/main/java/spoon/pattern/internal/node/ListOfNodes.java b/src/main/java/spoon/pattern/internal/node/ListOfNodes.java
            index ec1533c1739..88a6109eda8 100644
            --- a/src/main/java/spoon/pattern/internal/node/ListOfNodes.java
            +++ b/src/main/java/spoon/pattern/internal/node/ListOfNodes.java
            @@ -25,7 +25,7 @@
             import spoon.pattern.internal.matcher.Matchers;
             import spoon.pattern.internal.matcher.TobeMatched;
             import spoon.pattern.internal.parameter.ParameterInfo;
            -import spoon.support.util.ParameterValueProvider;
            +import spoon.support.util.ImmutableMap;
             
             /**
              * List of {@link RootNode}s. The {@link RootNode}s are processed in same order like they were inserted in the list
            @@ -46,7 +46,7 @@ public void forEachParameterInfo(BiConsumer consumer) {
             	}
             
             	@Override
            -	public  void generateTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters) {
            +	public  void generateTargets(Generator generator, ResultHolder result, ImmutableMap parameters) {
             		for (RootNode node : nodes) {
             			generator.generateTargets(node, result, parameters);
             		}
            diff --git a/src/main/java/spoon/pattern/internal/node/MapEntryNode.java b/src/main/java/spoon/pattern/internal/node/MapEntryNode.java
            index 1945cadb35e..0ef0c3f59b3 100644
            --- a/src/main/java/spoon/pattern/internal/node/MapEntryNode.java
            +++ b/src/main/java/spoon/pattern/internal/node/MapEntryNode.java
            @@ -29,7 +29,7 @@
             import spoon.pattern.internal.parameter.ParameterInfo;
             import spoon.reflect.declaration.CtElement;
             import spoon.reflect.meta.ContainerKind;
            -import spoon.support.util.ParameterValueProvider;
            +import spoon.support.util.ImmutableMap;
             
             /**
              * Represents a ValueResolver of one Map.Entry
            @@ -105,7 +105,7 @@ public CtElement setValue(CtElement value) {
             	}
             
             	@Override
            -	public  void generateTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters) {
            +	public  void generateTargets(Generator generator, ResultHolder result, ImmutableMap parameters) {
             		String entryKey = generator.generateSingleTarget(key, parameters, String.class);
             		CtElement entryValue = generator.generateSingleTarget(value, parameters, CtElement.class);
             		if (entryKey != null && entryValue != null) {
            @@ -113,7 +113,7 @@ public  void generateTargets(Generator generator, ResultHolder result, Par
             		}
             	}
             	@Override
            -	public ParameterValueProvider matchTarget(Object target, ParameterValueProvider parameters) {
            +	public ImmutableMap matchTarget(Object target, ImmutableMap parameters) {
             		if (target instanceof Map.Entry) {
             			Map.Entry targetEntry = (Map.Entry) target;
             			parameters = getMatchedParameters(getKey().matchAllWith(TobeMatched.create(parameters, ContainerKind.SINGLE, targetEntry.getKey())));
            @@ -134,7 +134,7 @@ public Quantifier getMatchingStrategy() {
             	}
             
             	@Override
            -	public boolean isTryNextMatch(ParameterValueProvider parameters) {
            +	public boolean isTryNextMatch(ImmutableMap parameters) {
             		if (key instanceof ParameterNode) {
             			return ((ParameterNode) key).isTryNextMatch(parameters);
             		}
            diff --git a/src/main/java/spoon/pattern/internal/node/ParameterNode.java b/src/main/java/spoon/pattern/internal/node/ParameterNode.java
            index 00b22cd4770..45d6c6052c8 100644
            --- a/src/main/java/spoon/pattern/internal/node/ParameterNode.java
            +++ b/src/main/java/spoon/pattern/internal/node/ParameterNode.java
            @@ -23,7 +23,7 @@
             import spoon.pattern.internal.ResultHolder;
             import spoon.pattern.internal.parameter.ParameterInfo;
             import spoon.reflect.declaration.CtElement;
            -import spoon.support.util.ParameterValueProvider;
            +import spoon.support.util.ImmutableMap;
             
             /**
              * Represents pattern model variable
            @@ -44,12 +44,12 @@ public boolean replaceNode(RootNode oldNode, RootNode newNode) {
             	}
             
             	@Override
            -	public  void generateTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters) {
            +	public  void generateTargets(Generator generator, ResultHolder result, ImmutableMap parameters) {
             		generator.getValueAs(parameterInfo, result, parameters);
             	}
             
             	@Override
            -	public ParameterValueProvider matchTarget(Object target, ParameterValueProvider parameters) {
            +	public ImmutableMap matchTarget(Object target, ImmutableMap parameters) {
             		return parameterInfo.addValueAs(parameters, target);
             	}
             
            @@ -63,12 +63,12 @@ public boolean isRepeatable() {
             	}
             
             	@Override
            -	public boolean isMandatory(ParameterValueProvider parameters) {
            +	public boolean isMandatory(ImmutableMap parameters) {
             		return parameterInfo.isMandatory(parameters);
             	}
             
             	@Override
            -	public boolean isTryNextMatch(ParameterValueProvider parameters) {
            +	public boolean isTryNextMatch(ImmutableMap parameters) {
             		return parameterInfo.isTryNextMatch(parameters);
             	}
             
            diff --git a/src/main/java/spoon/pattern/internal/node/PrimitiveMatcher.java b/src/main/java/spoon/pattern/internal/node/PrimitiveMatcher.java
            index 96047db9c0a..3237c00288c 100644
            --- a/src/main/java/spoon/pattern/internal/node/PrimitiveMatcher.java
            +++ b/src/main/java/spoon/pattern/internal/node/PrimitiveMatcher.java
            @@ -16,7 +16,7 @@
              */
             package spoon.pattern.internal.node;
             
            -import spoon.support.util.ParameterValueProvider;
            +import spoon.support.util.ImmutableMap;
             
             /**
              * Defines API of a primitive matcher - matcher for single target object
            @@ -28,5 +28,5 @@ public interface PrimitiveMatcher extends RepeatableMatcher {
             	 * @param parameters will receive the matching parameter values
             	 * @return true if `element` matches with pattern of this matcher
             	 */
            -	ParameterValueProvider matchTarget(Object target, ParameterValueProvider parameters);
            +	ImmutableMap matchTarget(Object target, ImmutableMap parameters);
             }
            diff --git a/src/main/java/spoon/pattern/internal/node/RepeatableMatcher.java b/src/main/java/spoon/pattern/internal/node/RepeatableMatcher.java
            index 403289a9ad5..d36e242a4d9 100644
            --- a/src/main/java/spoon/pattern/internal/node/RepeatableMatcher.java
            +++ b/src/main/java/spoon/pattern/internal/node/RepeatableMatcher.java
            @@ -17,7 +17,7 @@
             package spoon.pattern.internal.node;
             
             import spoon.pattern.Quantifier;
            -import spoon.support.util.ParameterValueProvider;
            +import spoon.support.util.ImmutableMap;
             
             /**
              * Defines API of a repeatable matcher.
            @@ -43,12 +43,12 @@ default boolean isRepeatable() {
             	 * @return true if this ValueResolver MUST match with next target in the state defined by current `parameters`.
             	 * false if match is optional
             	 */
            -	default boolean isMandatory(ParameterValueProvider parameters) {
            +	default boolean isMandatory(ImmutableMap parameters) {
             		return true;
             	}
             	/**
             	 * @param parameters matching parameters
             	 * @return true if this ValueResolver should be processed again to match next target in the state defined by current `parameters`.
             	 */
            -	boolean isTryNextMatch(ParameterValueProvider parameters);
            +	boolean isTryNextMatch(ImmutableMap parameters);
             }
            diff --git a/src/main/java/spoon/pattern/internal/node/RootNode.java b/src/main/java/spoon/pattern/internal/node/RootNode.java
            index 803f0fd496c..e8bb5aed27b 100644
            --- a/src/main/java/spoon/pattern/internal/node/RootNode.java
            +++ b/src/main/java/spoon/pattern/internal/node/RootNode.java
            @@ -23,7 +23,7 @@
             import spoon.pattern.internal.matcher.Matchers;
             import spoon.pattern.internal.matcher.TobeMatched;
             import spoon.pattern.internal.parameter.ParameterInfo;
            -import spoon.support.util.ParameterValueProvider;
            +import spoon.support.util.ImmutableMap;
             
             /**
              * Represents a parameterized Pattern ValueResolver, which can be used
            @@ -43,9 +43,9 @@ public interface RootNode extends Matchers {
             	 * Generates zero, one or more target depending on kind of this {@link RootNode}, expected `result` and input `parameters`
             	 * @param generator {@link Generator} which drives generation process
             	 * @param result holder for the generated objects
            -	 * @param parameters a {@link ParameterValueProvider} holding parameters
            +	 * @param parameters a {@link ImmutableMap} holding parameters
             	 */
            -	 void generateTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters);
            +	 void generateTargets(Generator generator, ResultHolder result, ImmutableMap parameters);
             
             	/**
             	 * @return true if generated result has to be evaluated to apply simplifications.
            diff --git a/src/main/java/spoon/pattern/internal/node/StringNode.java b/src/main/java/spoon/pattern/internal/node/StringNode.java
            index 3ca34205e62..cc775ebb345 100644
            --- a/src/main/java/spoon/pattern/internal/node/StringNode.java
            +++ b/src/main/java/spoon/pattern/internal/node/StringNode.java
            @@ -30,9 +30,8 @@
             import spoon.pattern.Quantifier;
             import spoon.pattern.internal.Generator;
             import spoon.pattern.internal.ResultHolder;
            -import spoon.pattern.internal.ResultHolder.Single;
             import spoon.pattern.internal.parameter.ParameterInfo;
            -import spoon.support.util.ParameterValueProvider;
            +import spoon.support.util.ImmutableMap;
             
             /**
              * Delivers single String value, which is created by replacing string markers in constant String template
            @@ -57,7 +56,7 @@ private String getStringValueWithMarkers() {
             
             	@SuppressWarnings("unchecked")
             	@Override
            -	public  void generateTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters) {
            +	public  void generateTargets(Generator generator, ResultHolder result, ImmutableMap parameters) {
             		Class requiredClass = result.getRequiredClass();
             		if (requiredClass != null && requiredClass.isAssignableFrom(String.class) == false) {
             			throw new SpoonException("StringValueResolver provides only String values. It doesn't support: " + requiredClass);
            @@ -80,7 +79,7 @@ public  void generateTargets(Generator generator, ResultHolder result, Par
             	}
             
             	@Override
            -	public ParameterValueProvider matchTarget(Object target, ParameterValueProvider parameters) {
            +	public ImmutableMap matchTarget(Object target, ImmutableMap parameters) {
             		if ((target instanceof String) == false) {
             			return null;
             		}
            @@ -277,7 +276,7 @@ public Quantifier getMatchingStrategy() {
             	}
             
             	@Override
            -	public boolean isTryNextMatch(ParameterValueProvider parameters) {
            +	public boolean isTryNextMatch(ImmutableMap parameters) {
             		//it always matches only once
             		return false;
             	}
            diff --git a/src/main/java/spoon/pattern/internal/node/SwitchNode.java b/src/main/java/spoon/pattern/internal/node/SwitchNode.java
            index 1e8330993ec..f0a7674dfec 100644
            --- a/src/main/java/spoon/pattern/internal/node/SwitchNode.java
            +++ b/src/main/java/spoon/pattern/internal/node/SwitchNode.java
            @@ -32,7 +32,7 @@
             import spoon.reflect.code.CtStatement;
             import spoon.reflect.factory.CoreFactory;
             import spoon.reflect.factory.Factory;
            -import spoon.support.util.ParameterValueProvider;
            +import spoon.support.util.ImmutableMap;
             
             /**
              * List of conditional cases
            @@ -73,7 +73,7 @@ public void addCase(PrimitiveMatcher vrOfExpression, RootNode statement) {
             	}
             
             	@Override
            -	public  void generateTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters) {
            +	public  void generateTargets(Generator generator, ResultHolder result, ImmutableMap parameters) {
             		for (CaseNode case1 : cases) {
             			generator.generateTargets(case1, result, parameters);
             		}
            @@ -154,7 +154,7 @@ public boolean replaceNode(RootNode oldNode, RootNode newNode) {
             
             		@Override
             		public TobeMatched matchTargets(TobeMatched targets, Matchers nextMatchers) {
            -			ParameterValueProvider parameters = targets.getParameters();
            +			ImmutableMap parameters = targets.getParameters();
             			//set all switch parameter values following match case. Even no matching case is OK - everything is false then
             			for (CaseNode case1 : cases) {
             				if (case1.vrOfExpression != null) {
            @@ -177,14 +177,14 @@ public void forEachParameterInfo(BiConsumer consumer) {
             			SwitchNode.this.forEachParameterInfo(consumer);
             		}
             		@Override
            -		public  void generateTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters) {
            +		public  void generateTargets(Generator generator, ResultHolder result, ImmutableMap parameters) {
             			if (statement != null) {
             				if (isCaseSelected(generator, parameters)) {
             					generator.generateTargets(statement, result, parameters);
             				}
             			}
             		}
            -		private boolean isCaseSelected(Generator generator, ParameterValueProvider parameters) {
            +		private boolean isCaseSelected(Generator generator, ImmutableMap parameters) {
             			if (vrOfExpression == null) {
             				return true;
             			}
            @@ -193,7 +193,7 @@ private boolean isCaseSelected(Generator generator, ParameterValueProvider param
             		}
             
             		@Override
            -		public  void generateInlineTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters) {
            +		public  void generateInlineTargets(Generator generator, ResultHolder result, ImmutableMap parameters) {
             			Factory f = generator.getFactory();
             			CoreFactory cf = f.Core();
             			CtBlock block = cf.createBlock();
            @@ -214,7 +214,7 @@ public  void generateInlineTargets(Generator generator, ResultHolder resul
             	}
             
             	@Override
            -	public  void generateInlineTargets(Generator generator, ResultHolder result, ParameterValueProvider parameters) {
            +	public  void generateInlineTargets(Generator generator, ResultHolder result, ImmutableMap parameters) {
             		CtStatement resultStmt = null;
             		CtStatement lastElse = null;
             		CtIf lastIf = null;
            diff --git a/src/main/java/spoon/pattern/internal/parameter/AbstractParameterInfo.java b/src/main/java/spoon/pattern/internal/parameter/AbstractParameterInfo.java
            index 07fbaee58f4..c32fefe79d2 100644
            --- a/src/main/java/spoon/pattern/internal/parameter/AbstractParameterInfo.java
            +++ b/src/main/java/spoon/pattern/internal/parameter/AbstractParameterInfo.java
            @@ -34,7 +34,7 @@
             import spoon.reflect.factory.Factory;
             import spoon.reflect.meta.ContainerKind;
             import spoon.reflect.reference.CtTypeReference;
            -import spoon.support.util.ParameterValueProvider;
            +import spoon.support.util.ImmutableMap;
             
             /**
              */
            @@ -83,7 +83,7 @@ public final String getName() {
             	protected abstract String getWrappedName(String containerName);
             
             	@Override
            -	public ParameterValueProvider addValueAs(ParameterValueProvider parameters, Object value) {
            +	public ImmutableMap addValueAs(ImmutableMap parameters, Object value) {
             		Class requiredType = getParameterValueType();
             		if (requiredType != null && value != null && requiredType.isInstance(value) == false) {
             			return null;
            @@ -97,7 +97,7 @@ public ParameterValueProvider addValueAs(ParameterValueProvider parameters, Obje
             		if (newContainer == NO_MERGE) {
             			return null;
             		}
            -		return (ParameterValueProvider) newContainer;
            +		return (ImmutableMap) newContainer;
             	}
             
             	protected Object addValueToContainer(Object container, Function merger) {
            @@ -316,7 +316,7 @@ public boolean isRepeatable() {
             	 * @return true if the ValueResolver of this parameter MUST match with next target in the state defined by current `parameters`.
             	 * false if match is optional
             	 */
            -	public boolean isMandatory(ParameterValueProvider parameters) {
            +	public boolean isMandatory(ImmutableMap parameters) {
             		int nrOfValues = getNumberOfValues(parameters);
             		//current number of values is smaller then minimum number of values. Value is mandatory
             		return nrOfValues < getMinOccurences();
            @@ -326,7 +326,7 @@ public boolean isMandatory(ParameterValueProvider parameters) {
             	 * @param parameters matching parameters
             	 * @return true if the ValueResolver of this parameter should be processed again to match next target in the state defined by current `parameters`.
             	 */
            -	public boolean isTryNextMatch(ParameterValueProvider parameters) {
            +	public boolean isTryNextMatch(ImmutableMap parameters) {
             		int nrOfValues = getNumberOfValues(parameters);
             		if (getContainerKind(parameters) == ContainerKind.SINGLE) {
             			/*
            @@ -343,7 +343,7 @@ public boolean isTryNextMatch(ParameterValueProvider parameters) {
             	 * @param parameters
             	 * @return 0 if there is no value. 1 if there is single value or null. Number of values in collection if there is a collection
             	 */
            -	private int getNumberOfValues(ParameterValueProvider parameters) {
            +	private int getNumberOfValues(ImmutableMap parameters) {
             		if (parameters.hasValue(getName()) == false) {
             			return 0;
             		}
            @@ -361,7 +361,7 @@ public AbstractParameterInfo setContainerKind(ContainerKind containerKind) {
             		this.containerKind = containerKind;
             		return this;
             	}
            -	protected ContainerKind getContainerKind(ParameterValueProvider params) {
            +	protected ContainerKind getContainerKind(ImmutableMap params) {
             		return getContainerKind(params.getValue(getName()), null);
             	}
             	protected ContainerKind getContainerKind(Object existingValue, Object value) {
            @@ -377,7 +377,7 @@ protected ContainerKind getContainerKind(Object existingValue, Object value) {
             		if (existingValue instanceof Map) {
             			return ContainerKind.MAP;
             		}
            -		if (existingValue instanceof ParameterValueProvider) {
            +		if (existingValue instanceof ImmutableMap) {
             			return ContainerKind.MAP;
             		}
             		if (existingValue != null) {
            @@ -396,14 +396,14 @@ protected ContainerKind getContainerKind(Object existingValue, Object value) {
             		if (value instanceof Map) {
             			return ContainerKind.MAP;
             		}
            -		if (value instanceof ParameterValueProvider) {
            +		if (value instanceof ImmutableMap) {
             			return ContainerKind.MAP;
             		}
             		return ContainerKind.SINGLE;
             	}
             
             	@Override
            -	public  void getValueAs(Factory factory, ResultHolder result, ParameterValueProvider parameters) {
            +	public  void getValueAs(Factory factory, ResultHolder result, ImmutableMap parameters) {
             		//get raw parameter value
             		Object rawValue = getValue(parameters);
             		if (isMultiple() && rawValue instanceof CtBlock)  {
            @@ -416,7 +416,7 @@ public  void getValueAs(Factory factory, ResultHolder result, ParameterVal
             		convertValue(factory, result, rawValue);
             	}
             
            -	protected Object getValue(ParameterValueProvider parameters) {
            +	protected Object getValue(ImmutableMap parameters) {
             		if (containerItemAccessor != null) {
             			return containerItemAccessor.getValue(parameters);
             		}
            diff --git a/src/main/java/spoon/pattern/internal/parameter/ListParameterInfo.java b/src/main/java/spoon/pattern/internal/parameter/ListParameterInfo.java
            index f6c52e06059..6868a265ff5 100644
            --- a/src/main/java/spoon/pattern/internal/parameter/ListParameterInfo.java
            +++ b/src/main/java/spoon/pattern/internal/parameter/ListParameterInfo.java
            @@ -24,7 +24,7 @@
             import java.util.Set;
             import java.util.function.Function;
             
            -import spoon.support.util.ParameterValueProvider;
            +import spoon.support.util.ImmutableMap;
             
             /**
              */
            @@ -97,7 +97,7 @@ protected List getEmptyContainer() {
             		return Collections.emptyList();
             	}
             	@Override
            -	protected Object getValue(ParameterValueProvider parameters) {
            +	protected Object getValue(ImmutableMap parameters) {
             		List list = castTo(super.getValue(parameters), List.class);
             		if (idx < 0) {
             			return list;
            diff --git a/src/main/java/spoon/pattern/internal/parameter/MapParameterInfo.java b/src/main/java/spoon/pattern/internal/parameter/MapParameterInfo.java
            index 571c2c04c55..47014f9758d 100644
            --- a/src/main/java/spoon/pattern/internal/parameter/MapParameterInfo.java
            +++ b/src/main/java/spoon/pattern/internal/parameter/MapParameterInfo.java
            @@ -19,12 +19,12 @@
             import java.util.Map;
             import java.util.function.Function;
             
            -import spoon.support.util.ParameterValueProvider;
            -import spoon.support.util.UnmodifiableParameterValueProvider;
            +import spoon.support.util.ImmutableMap;
            +import spoon.support.util.ImmutableMapImpl;
             
             /**
              * A kind of {@link ParameterInfo} which returns value by the named parameter
            - * From a container of type {@link ParameterValueProvider} or {@link Map}
            + * From a container of type {@link ImmutableMap} or {@link Map}
              */
             public class MapParameterInfo extends AbstractParameterInfo {
             
            @@ -59,7 +59,7 @@ protected String getWrappedName(String containerName) {
             
             	@Override
             	protected Object addValueAs(Object container, Function merger) {
            -		ParameterValueProvider parameters = castTo(container, ParameterValueProvider.class);
            +		ImmutableMap parameters = castTo(container, ImmutableMap.class);
             		if (name == null) {
             			//This accessor matches any entry - has no predefined key
             			Object newValue = merger.apply(null);
            @@ -117,22 +117,22 @@ protected Object addValueAs(Object container, Function merger) {
             	}
             
             	@Override
            -	protected Object getValue(ParameterValueProvider parameters) {
            -		ParameterValueProvider map = castTo(super.getValue(parameters), ParameterValueProvider.class);
            +	protected Object getValue(ImmutableMap parameters) {
            +		ImmutableMap map = castTo(super.getValue(parameters), ImmutableMap.class);
             		return name == null ? map : map.getValue(name);
             	}
             
             	@Override
             	protected  T castTo(Object o, Class type) {
             		if (o instanceof Map) {
            -			o = new UnmodifiableParameterValueProvider((Map) o);
            +			o = new ImmutableMapImpl((Map) o);
             		}
             		return super.castTo(o, type);
             	}
             
            -	private static final ParameterValueProvider EMPTY = new UnmodifiableParameterValueProvider();
            +	private static final ImmutableMap EMPTY = new ImmutableMapImpl();
             	@Override
            -	protected ParameterValueProvider getEmptyContainer() {
            +	protected ImmutableMap getEmptyContainer() {
             		return EMPTY;
             	}
             }
            diff --git a/src/main/java/spoon/pattern/internal/parameter/ParameterInfo.java b/src/main/java/spoon/pattern/internal/parameter/ParameterInfo.java
            index d0d3caa083f..5201a54a224 100644
            --- a/src/main/java/spoon/pattern/internal/parameter/ParameterInfo.java
            +++ b/src/main/java/spoon/pattern/internal/parameter/ParameterInfo.java
            @@ -21,7 +21,7 @@
             import spoon.pattern.internal.ResultHolder;
             import spoon.pattern.internal.node.RootNode;
             import spoon.reflect.factory.Factory;
            -import spoon.support.util.ParameterValueProvider;
            +import spoon.support.util.ImmutableMap;
             
             /**
              * Represents the parameter of {@link Pattern}
            @@ -38,13 +38,13 @@ public interface ParameterInfo {
             	/**
             	 * Matches `value` into `parameters` under the name/structure defined by this ParameterInfo.
             	 * 1) checks that value matches with optional internal rules of this {@link ParameterInfo}
            -	 * 2) creates new copy of {@link ParameterValueProvider} which contains the new `value` and returns that copy
            +	 * 2) creates new copy of {@link ImmutableMap} which contains the new `value` and returns that copy
             	 *
             	 * @param parameters the existing parameters
             	 * @param value the new, to be stored value
             	 * @return copy of `parameters` with new value or existing `parameters` if value is already there or null if value doesn't fit into these parameters
             	 */
            -	ParameterValueProvider addValueAs(ParameterValueProvider parameters, Object value);
            +	ImmutableMap addValueAs(ImmutableMap parameters, Object value);
             
             	/**
             	 * Takes the value of parameter identified by this {@link ParameterInfo} from the `parameters`
            @@ -53,7 +53,7 @@ public interface ParameterInfo {
             	 * @param result the receiver of the result value. It defined required type of returned value and multiplicity of returned value
             	 * @param parameters here are stored all the parameter values
             	 */
            -	 void getValueAs(Factory factory, ResultHolder result, ParameterValueProvider parameters);
            +	 void getValueAs(Factory factory, ResultHolder result, ImmutableMap parameters);
             
             	/**
             	 * @return true if the value container has to be a List, otherwise the container will be a single value
            @@ -87,11 +87,11 @@ public interface ParameterInfo {
             	 * @return true if the ValueResolver of this parameter MUST match with next target in the state defined by current `parameters`.
             	 * false if match is optional
             	 */
            -	boolean isMandatory(ParameterValueProvider parameters);
            +	boolean isMandatory(ImmutableMap parameters);
             
             	/**
             	 * @param parameters matching parameters
             	 * @return true if the ValueResolver of this parameter should be processed again to match next target in the state defined by current `parameters`.
             	 */
            -	boolean isTryNextMatch(ParameterValueProvider parameters);
            +	boolean isTryNextMatch(ImmutableMap parameters);
             }
            diff --git a/src/main/java/spoon/pattern/internal/parameter/SetParameterInfo.java b/src/main/java/spoon/pattern/internal/parameter/SetParameterInfo.java
            index be68afc5c0e..589039c09ce 100644
            --- a/src/main/java/spoon/pattern/internal/parameter/SetParameterInfo.java
            +++ b/src/main/java/spoon/pattern/internal/parameter/SetParameterInfo.java
            @@ -24,7 +24,7 @@
             import java.util.Set;
             import java.util.function.Function;
             
            -import spoon.support.util.ParameterValueProvider;
            +import spoon.support.util.ImmutableMap;
             
             /**
              */
            @@ -77,7 +77,7 @@ protected Set getEmptyContainer() {
             		return Collections.emptySet();
             	}
             	@Override
            -	protected Object getValue(ParameterValueProvider parameters) {
            +	protected Object getValue(ImmutableMap parameters) {
             		return castTo(super.getValue(parameters), Set.class);
             	}
             
            diff --git a/src/main/java/spoon/support/Experimental.java b/src/main/java/spoon/support/Experimental.java
            new file mode 100644
            index 00000000000..0706ff796dc
            --- /dev/null
            +++ b/src/main/java/spoon/support/Experimental.java
            @@ -0,0 +1,31 @@
            +/**
            + * 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;
            +
            +import java.lang.annotation.ElementType;
            +import java.lang.annotation.Retention;
            +import java.lang.annotation.RetentionPolicy;
            +import java.lang.annotation.Target;
            +
            +/**
            + * Tells that a type has recently been introduced and may be subject to non-backward compatible changes without deprecation.
            + * The annotation is expected to be removed at the latest one year after its introduction (you can do a "git blame" to see when it appeared).
            + */
            +@Retention(RetentionPolicy.RUNTIME)
            +@Target({ ElementType.TYPE })
            +public @interface Experimental {
            +}
            diff --git a/src/main/java/spoon/support/Internal.java b/src/main/java/spoon/support/Internal.java
            new file mode 100644
            index 00000000000..e4aa58c3134
            --- /dev/null
            +++ b/src/main/java/spoon/support/Internal.java
            @@ -0,0 +1,31 @@
            +/**
            + * 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;
            +
            +import java.lang.annotation.ElementType;
            +import java.lang.annotation.Retention;
            +import java.lang.annotation.RetentionPolicy;
            +import java.lang.annotation.Target;
            +
            +/**
            + * Tells that a class or method is not in the public API (even if it has Java visibility "public")
            + * Required because package-visibility is too coarse-grained.
            + */
            +@Retention(RetentionPolicy.RUNTIME)
            +@Target({ ElementType.METHOD, ElementType.TYPE })
            +public @interface Internal {
            +}
            diff --git a/src/main/java/spoon/support/util/ParameterValueProvider.java b/src/main/java/spoon/support/util/ImmutableMap.java
            similarity index 74%
            rename from src/main/java/spoon/support/util/ParameterValueProvider.java
            rename to src/main/java/spoon/support/util/ImmutableMap.java
            index c8e6dad4a2a..5196ada9b5c 100644
            --- a/src/main/java/spoon/support/util/ParameterValueProvider.java
            +++ b/src/main/java/spoon/support/util/ImmutableMap.java
            @@ -16,6 +16,8 @@
              */
             package spoon.support.util;
             
            +import spoon.support.Internal;
            +
             import java.util.Map;
             
             /**
            @@ -24,8 +26,10 @@
              * (eg unmodifiable storage of parameter name-value pairs).
              * The values may be primitive values or List, Set, Map of values.
              * All internal containers are unmodifiable too.
            + *
            + * Internal class only, not in the public API.
              */
            -public interface ParameterValueProvider {
            +public interface ImmutableMap {
             
             	/**
             	 * @param parameterName to be checked parameter name
            @@ -42,9 +46,9 @@ public interface ParameterValueProvider {
             	/**
             	 * @param parameterName to be set parameter name
             	 * @param value the new value
            -	 * @return copies this {@link ParameterValueProvider}, sets the new value there and returns that copy
            +	 * @return copies this {@link ImmutableMap}, sets the new value there and returns that copy
             	 */
            -	ParameterValueProvider putValue(String parameterName, Object value);
            +	ImmutableMap putValue(String parameterName, Object value);
             
             	/**
             	 * @return underlying unmodifiable Map<String, Object>
            @@ -52,15 +56,15 @@ public interface ParameterValueProvider {
             	Map asMap();
             
             	/**
            -	 * @return a new instance of {@link ParameterValueProvider}, which inherits all values from this {@link ParameterValueProvider}
            +	 * @return a new instance of {@link ImmutableMap}, which inherits all values from this {@link ImmutableMap}
             	 * Any call of {@link #putValue(String, Object)} is remembered in local Map of parameters.
            -	 * At the end of process the {@link #getModifiedParameters()} can be used to return all the parameters which were changed
            -	 * after local {@link ParameterValueProvider} was created
            +	 * At the end of process the {@link #getModifiedValues()} can be used to return all the parameters which were changed
            +	 * after local {@link ImmutableMap} was created
             	 */
            -	ParameterValueProvider checkpoint();
            +	ImmutableMap checkpoint();
             
             	/**
             	 * @return the modified parameters since last call to {@link #checkpoint()}
             	 */
            -	Map getModifiedParameters();
            +	Map getModifiedValues();
             }
            diff --git a/src/main/java/spoon/support/util/UnmodifiableParameterValueProvider.java b/src/main/java/spoon/support/util/ImmutableMapImpl.java
            similarity index 71%
            rename from src/main/java/spoon/support/util/UnmodifiableParameterValueProvider.java
            rename to src/main/java/spoon/support/util/ImmutableMapImpl.java
            index e299206f64a..c33bd92580d 100644
            --- a/src/main/java/spoon/support/util/UnmodifiableParameterValueProvider.java
            +++ b/src/main/java/spoon/support/util/ImmutableMapImpl.java
            @@ -16,6 +16,8 @@
              */
             package spoon.support.util;
             
            +import spoon.support.Internal;
            +
             import java.util.ArrayList;
             import java.util.Collections;
             import java.util.HashMap;
            @@ -23,34 +25,38 @@
             import java.util.Map;
             
             /**
            - * Provides value of parameter
            + *
            + * Internal class only, not in the public API.
            + *
            + * Spoon implementation of {@link ImmutableMap}
              */
            -public class UnmodifiableParameterValueProvider implements ParameterValueProvider {
            +@Internal
            +public class ImmutableMapImpl implements ImmutableMap {
             
             	public static class Factory implements ParameterValueProviderFactory {
             		public static final Factory INSTANCE = new Factory();
             		@Override
            -		public ParameterValueProvider createParameterValueProvider() {
            -			return new UnmodifiableParameterValueProvider();
            +		public ImmutableMap createParameterValueProvider() {
            +			return new ImmutableMapImpl();
             		}
             	}
             
            -	protected final ParameterValueProvider parent;
            +	protected final ImmutableMap parent;
             	protected final Map map;
             
            -	public UnmodifiableParameterValueProvider(Map map) {
            +	public ImmutableMapImpl(Map map) {
             		this(null, map);
             	}
            -	private UnmodifiableParameterValueProvider(ParameterValueProvider parent, Map map) {
            +	private ImmutableMapImpl(ImmutableMap parent, Map map) {
             		this.parent = parent;
             		this.map = Collections.unmodifiableMap(map);
             	}
             
            -	public UnmodifiableParameterValueProvider(Map map, String parameterName, Object value) {
            +	public ImmutableMapImpl(Map map, String parameterName, Object value) {
             		this(null, map, parameterName, value);
             	}
             
            -	private UnmodifiableParameterValueProvider(ParameterValueProvider parent, Map map, String parameterName, Object value) {
            +	private ImmutableMapImpl(ImmutableMap parent, Map map, String parameterName, Object value) {
             		this.parent = null;
             		Map copy = new HashMap<>(map.size() + 1);
             		copy.putAll(map);
            @@ -58,14 +64,14 @@ private UnmodifiableParameterValueProvider(ParameterValueProvider parent, Map asMap() {
             	}
             
             	@Override
            -	public Map getModifiedParameters() {
            +	public Map getModifiedValues() {
             		return map;
             	}
             
             	@Override
             	public boolean equals(Object obj) {
            -		if (obj instanceof ParameterValueProvider) {
            -			obj = ((ParameterValueProvider) obj).asMap();
            +		if (obj instanceof ImmutableMap) {
            +			obj = ((ImmutableMap) obj).asMap();
             		}
             		if (obj instanceof Map) {
             			Map map = (Map) obj;
            diff --git a/src/main/java/spoon/support/util/ParameterValueProviderFactory.java b/src/main/java/spoon/support/util/ParameterValueProviderFactory.java
            index fd4b0046dab..11e76a07a49 100644
            --- a/src/main/java/spoon/support/util/ParameterValueProviderFactory.java
            +++ b/src/main/java/spoon/support/util/ParameterValueProviderFactory.java
            @@ -17,11 +17,11 @@
             package spoon.support.util;
             
             /**
            - * Creates instances of {@link ParameterValueProvider}
            + * Creates instances of {@link ImmutableMap}
              */
             public interface ParameterValueProviderFactory {
             	/**
            -	 * @return new instance of empty {@link ParameterValueProvider}
            +	 * @return new instance of empty {@link ImmutableMap}
             	 */
            -	ParameterValueProvider createParameterValueProvider();
            +	ImmutableMap createParameterValueProvider();
             }
            diff --git a/src/main/java/spoon/template/TemplateBuilder.java b/src/main/java/spoon/template/TemplateBuilder.java
            index 8cc4a872d06..3831289ab3e 100644
            --- a/src/main/java/spoon/template/TemplateBuilder.java
            +++ b/src/main/java/spoon/template/TemplateBuilder.java
            @@ -31,7 +31,7 @@
             import spoon.reflect.factory.Factory;
             import spoon.reflect.reference.CtTypeReference;
             import spoon.support.template.Parameters;
            -import spoon.support.util.UnmodifiableParameterValueProvider;
            +import spoon.support.util.ImmutableMapImpl;
             
             /**
              * Internal class used to provide pattern-based implementation of Template and TemplateMatcher
            @@ -88,8 +88,6 @@ public static TemplateBuilder createPattern(CtElement templateRoot, CtClass t
             					//all other type members have to be part of the pattern model
             					return true;
             				});
            -				//remove `... extends Template`, which doesn't have to be part of pattern model
            -				tv.removeSuperClass();
             			};
             			pb = PatternBuilder.create(tv.getPatternElements());
             		} else {
            @@ -158,6 +156,6 @@ public  T substituteSingle(CtType targetType, Class i
             	 * @return List of substituted elements
             	 */
             	public  List substituteList(Factory factory, CtType targetType, Class itemType) {
            -		return build().substitute(factory, itemType, new UnmodifiableParameterValueProvider(getTemplateParameters(targetType)));
            +		return build().substitute(factory, itemType, new ImmutableMapImpl(getTemplateParameters(targetType)));
             	}
             }
            diff --git a/src/main/java/spoon/template/TemplateMatcher.java b/src/main/java/spoon/template/TemplateMatcher.java
            index 4736a3f19ed..512d3ad27ca 100644
            --- a/src/main/java/spoon/template/TemplateMatcher.java
            +++ b/src/main/java/spoon/template/TemplateMatcher.java
            @@ -29,9 +29,9 @@
             import spoon.reflect.meta.ContainerKind;
             import spoon.reflect.visitor.Filter;
             import spoon.reflect.visitor.chain.CtConsumer;
            -import spoon.support.util.ParameterValueProvider;
            +import spoon.support.util.ImmutableMap;
             import spoon.support.util.ParameterValueProviderFactory;
            -import spoon.support.util.UnmodifiableParameterValueProvider;
            +import spoon.support.util.ImmutableMapImpl;
             
             /**
              * This class defines an engine for matching a template to pieces of code.
            @@ -50,8 +50,8 @@ public class TemplateMatcher implements Filter {
             	 * 
              any value of primitive attribute, like String, Enum value, number, ... * */ - private ParameterValueProvider matches; - private ParameterValueProviderFactory parameterValueProviderFactory = UnmodifiableParameterValueProvider.Factory.INSTANCE; + private ImmutableMap matches; + private ParameterValueProviderFactory parameterValueProviderFactory = ImmutableMapImpl.Factory.INSTANCE; /** * Constructs a matcher for a given template. @@ -96,7 +96,7 @@ public boolean matches(CtElement element) { * The {@link #matches(CtElement)} method must have been called before and must return true. * Otherwise it returns null. */ - public ParameterValueProvider getMatches() { + public ImmutableMap getMatches() { return matches; } diff --git a/src/test/java/spoon/MavenLauncherTest.java b/src/test/java/spoon/MavenLauncherTest.java index 4b8e4406f39..7cc12c170f2 100644 --- a/src/test/java/spoon/MavenLauncherTest.java +++ b/src/test/java/spoon/MavenLauncherTest.java @@ -18,7 +18,7 @@ public void spoonMavenLauncherTest() { assertEquals(7, launcher.getEnvironment().getSourceClasspath().length); // 56 because of the sub folders of src/main/java - assertEquals(56, launcher.getModelBuilder().getInputSources().size()); + assertEquals(57, launcher.getModelBuilder().getInputSources().size()); // with the tests launcher = new MavenLauncher("./", MavenLauncher.SOURCE_TYPE.ALL_SOURCE); diff --git a/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java b/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java index 9015901d08b..9c763fe1bd0 100644 --- a/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java +++ b/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java @@ -336,9 +336,10 @@ public void testSpecPackage() throws Exception { officialPackages.add("spoon.experimental"); officialPackages.add("spoon.legacy"); officialPackages.add("spoon.pattern"); - officialPackages.add("spoon.pattern.matcher"); - officialPackages.add("spoon.pattern.node"); - officialPackages.add("spoon.pattern.parameter"); + officialPackages.add("spoon.pattern.internal."); + officialPackages.add("spoon.pattern.internal.matcher"); + officialPackages.add("spoon.pattern.internal.node"); + officialPackages.add("spoon.pattern.internal.parameter"); officialPackages.add("spoon.processing"); officialPackages.add("spoon.refactoring"); officialPackages.add("spoon.reflect.annotations"); diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index 726c1a5f41b..f6db48f639a 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -4,7 +4,7 @@ import spoon.Launcher; import spoon.pattern.ConflictResolutionMode; import spoon.pattern.Match; -import spoon.pattern.ParametersBuilder; +import spoon.pattern.PatternParameterConfigurator; import spoon.pattern.Pattern; import spoon.pattern.PatternBuilder; import spoon.pattern.PatternBuilderHelper; @@ -30,8 +30,8 @@ import spoon.reflect.reference.CtTypeReference; import spoon.reflect.visitor.filter.TypeFilter; import spoon.support.compiler.FileSystemFile; -import spoon.support.util.ParameterValueProvider; -import spoon.support.util.UnmodifiableParameterValueProvider; +import spoon.support.util.ImmutableMap; +import spoon.support.util.ImmutableMapImpl; import spoon.test.template.testclasses.LoggerModel; import spoon.test.template.testclasses.ToBeMatched; import spoon.test.template.testclasses.logger.Logger; @@ -91,7 +91,7 @@ public void testMatchForeach() throws Exception { // } // } Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("matcher1").getPatternElements()) - .configureParameters(pb -> { + .configurePatternParameters(pb -> { pb.parameter("values").byVariable("values").setContainerKind(ContainerKind.LIST).matchInlinedStatements(); }) .build(); @@ -128,7 +128,7 @@ public void testMatchForeachWithOuterSubstitution() throws Exception { CtType type = ctClass.getFactory().Type().get(MatchForEach2.class); Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("matcher1").getPatternElements()) - .configureParameters(pb -> { + .configurePatternParameters(pb -> { pb.parameter("values").byVariable("values").setContainerKind(ContainerKind.LIST).matchInlinedStatements(); // the variable "var" of the template is a parameter pb.parameter("varName").byString("var"); @@ -179,13 +179,13 @@ public void testMatchIfElse() throws Exception { CtType type = ctClass.getFactory().Type().get(MatchIfElse.class); Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("matcher1").getPatternElements()) - .configureParameters(pb -> { + .configurePatternParameters(pb -> { pb.parameter("option").byVariable("option"); pb.parameter("value").byFilter(new TypeFilter(CtLiteral.class)); }) //we have to configure inline statements after all expressions //of combined if statement are marked as pattern parameters - .configureInlineStatements(lsb -> lsb.byVariableName("option")) + .configureInlineStatements(lsb -> lsb.inlineIfOrForeachReferringTo("option")) .build(); List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0)); @@ -232,7 +232,7 @@ public void testGenerateMultiValues() throws Exception { Factory factory = ctClass.getFactory(); Pattern pattern = MatchMultiple.createPattern(null, null, null); - ParameterValueProvider params = new UnmodifiableParameterValueProvider(); + ImmutableMap params = new ImmutableMapImpl(); // created in "MatchMultiple.createPattern",matching a literal "something" // so "something" si replaced by "does it work?" @@ -533,7 +533,7 @@ public void testMatchPossesiveMultiValueMinCount() throws Exception { CtType type = ctClass.getFactory().Type().get(MatchMultiple3.class); Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("matcher1").getPatternElements()) .configurePatternParameters() - .configureParameters(pb -> { + .configurePatternParameters(pb -> { pb.parameter("statements1").setContainerKind(ContainerKind.LIST).setMatchingStrategy(Quantifier.GREEDY); pb.parameter("statements2").setContainerKind(ContainerKind.LIST).setMatchingStrategy(Quantifier.POSSESSIVE).setMinOccurence(countFinal).setMaxOccurence(countFinal); pb.parameter("printedValue").byFilter((CtLiteral literal) -> "something".equals(literal.getValue())); @@ -571,7 +571,8 @@ public void testMatchPossesiveMultiValueMinCount2() throws Exception { final int countFinal = count; Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(ctClass).setBodyOfMethod("matcher1").getPatternElements()) .configurePatternParameters() -.configureParameters(pb -> { +.configurePatternParameters(pb -> { + pb.setConflictResolutionMode(ConflictResolutionMode.USE_NEW_NODE); pb.parameter("statements1").setContainerKind(ContainerKind.LIST).setMatchingStrategy(Quantifier.GREEDY); pb.parameter("statements2").setContainerKind(ContainerKind.LIST).setMatchingStrategy(Quantifier.POSSESSIVE).setMinOccurence(countFinal).setMaxOccurence(countFinal); pb.parameter("inlinedSysOut").byVariable("something").setMatchingStrategy(Quantifier.POSSESSIVE).setContainerKind(ContainerKind.LIST).setMinOccurence(2).matchInlinedStatements(); @@ -612,7 +613,8 @@ public void testMatchGreedyMultiValueMinCount2() throws Exception { CtType type = ctClass.getFactory().Type().get(MatchMultiple2.class); Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("matcher1").getPatternElements()) .configurePatternParameters() - .configureParameters(pb -> { + .configurePatternParameters(pb -> { + pb.setConflictResolutionMode(ConflictResolutionMode.USE_NEW_NODE); pb.parameter("statements1").setContainerKind(ContainerKind.LIST).setMatchingStrategy(Quantifier.RELUCTANT); pb.parameter("statements2").setContainerKind(ContainerKind.LIST).setMatchingStrategy(Quantifier.GREEDY).setMaxOccurence(count); pb.parameter("printedValue").byVariable("something").matchInlinedStatements(); @@ -655,7 +657,7 @@ public void testMatchParameterValue() throws Exception { // pattern: a call to System.out.println with anything as parameter Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(ctClass).setBodyOfMethod("matcher1").getPatternElements()) - .configureParameters(pb -> { + .configurePatternParameters(pb -> { // anything in place of the variable reference value can be matched pb.parameter("value").byVariable("value"); }) @@ -686,7 +688,7 @@ public void testMatchParameterValueType() throws Exception { { // now we match only the ones with a literal as parameter Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(ctClass).setBodyOfMethod("matcher1").getPatternElements()) - .configureParameters(pb -> { + .configurePatternParameters(pb -> { pb.parameter("value").byVariable("value"); pb.setValueType(CtLiteral.class); }) @@ -719,7 +721,7 @@ public void testMatchParameterValueType() throws Exception { { // now we match a System.out.println with an invocation as paramter Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(ctClass).setBodyOfMethod("matcher1").getPatternElements()) - .configureParameters(pb -> { + .configurePatternParameters(pb -> { pb.parameter("value").byVariable("value"); pb.setValueType(CtInvocation.class); }) @@ -746,9 +748,9 @@ public void testMatchParameterCondition() throws Exception { { // matching a System.out.println with a literal Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(ctClass).setBodyOfMethod("matcher1").getPatternElements()) - .configureParameters(pb -> { + .configurePatternParameters(pb -> { pb.parameter("value").byVariable("value"); - pb.matchCondition(null, (Object value) -> value instanceof CtLiteral); + pb.byCondition(null, (Object value) -> value instanceof CtLiteral); }) .build(); @@ -784,10 +786,10 @@ public void testMatchOfAttribute() throws Exception { { //match all methods with arbitrary name, modifiers, parameters, but with empty body and return type void Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(ctClass).setTypeMember("matcher1").getPatternElements()) - .configureParameters(pb -> { - pb.parameter("modifiers").byRole(new TypeFilter(CtMethod.class), CtRole.MODIFIER); + .configurePatternParameters(pb -> { + pb.parameter("modifiers").byRole(CtRole.MODIFIER, new TypeFilter(CtMethod.class)); pb.parameter("methodName").byString("matcher1"); - pb.parameter("parameters").byRole(new TypeFilter(CtMethod.class), CtRole.PARAMETER); + pb.parameter("parameters").byRole(CtRole.PARAMETER, new TypeFilter(CtMethod.class)); }) .build(); List matches = pattern.getMatches(ctClass); @@ -825,11 +827,11 @@ public void testMatchOfAttribute() throws Exception { { //match all methods with arbitrary name, modifiers, parameters and body, but with return type void Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(ctClass).setTypeMember("matcher1").getPatternElements()) - .configureParameters(pb -> { - pb.parameter("modifiers").byRole(new TypeFilter(CtMethod.class), CtRole.MODIFIER); + .configurePatternParameters(pb -> { + pb.parameter("modifiers").byRole(CtRole.MODIFIER, new TypeFilter(CtMethod.class)); pb.parameter("methodName").byString("matcher1"); - pb.parameter("parameters").byRole(new TypeFilter(CtMethod.class), CtRole.PARAMETER); - pb.parameter("statements").byRole(new TypeFilter(CtBlock.class), CtRole.STATEMENT); + pb.parameter("parameters").byRole(CtRole.PARAMETER, new TypeFilter(CtMethod.class)); + pb.parameter("statements").byRole(CtRole.STATEMENT, new TypeFilter(CtBlock.class)); }) .build(); List matches = pattern.getMatches(ctClass); @@ -863,9 +865,9 @@ public void testMatchOfMapAttribute() throws Exception { // void matcher1() { // } Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(type).setTypeMember("matcher1").getPatternElements()) - .configureParameters(pb -> { + .configurePatternParameters(pb -> { //match any value of @Check annotation to parameter `testAnnotations` - pb.parameter("__pattern_param_annot").byRole(new TypeFilter(CtAnnotation.class), CtRole.VALUE).setContainerKind(ContainerKind.MAP); + pb.parameter("__pattern_param_annot").byRole(CtRole.VALUE, new TypeFilter(CtAnnotation.class)).setContainerKind(ContainerKind.MAP); //match any method name pb.parameter("__pattern_param_method_name").byString("matcher1"); }) @@ -923,16 +925,16 @@ public void testMatchOfMapAttributeAndOtherAnnotations() throws Exception { // create a pattern from method matcher1 // match all methods with arbitrary name, with any annotation set, Test modifiers, parameters, but with empty body and return type void Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(type).setTypeMember("matcher1").getPatternElements()) - .configureParameters(pb -> { + .configurePatternParameters(pb -> { //match any value of @Check annotation to parameter `testAnnotations` //match any method name pb.parameter("methodName").byString("matcher1"); // match on any annotation pb.parameter("allAnnotations") .setConflictResolutionMode(ConflictResolutionMode.APPEND) - .byRole(new TypeFilter<>(CtMethod.class), CtRole.ANNOTATION) + .byRole(CtRole.ANNOTATION, new TypeFilter<>(CtMethod.class)) ; - pb.parameter("CheckAnnotationValues").byRole(new TypeFilter(CtAnnotation.class), CtRole.VALUE).setContainerKind(ContainerKind.MAP); + pb.parameter("CheckAnnotationValues").byRole(CtRole.VALUE, new TypeFilter(CtAnnotation.class)).setContainerKind(ContainerKind.MAP); }) .build(); List matches = pattern.getMatches(ctClass); @@ -961,7 +963,7 @@ public void testMatchOfMapKeySubstring() throws Exception { // match all methods with arbitrary name, and Annotation Test modifiers, parameters, but with empty body and return type void CtType type = ctClass.getFactory().Type().get(MatchMap.class); Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(type).setTypeMember("m1").getPatternElements()) - .configureParameters(pb -> { + .configurePatternParameters(pb -> { //match any value of @Check annotation to parameter `testAnnotations` pb.parameter("CheckKey").bySubstring("value"); pb.parameter("CheckValue").byFilter((CtLiteral lit) -> true); @@ -970,7 +972,7 @@ public void testMatchOfMapKeySubstring() throws Exception { //match on all annotations of method pb.parameter("allAnnotations") .setConflictResolutionMode(ConflictResolutionMode.APPEND) - .byRole(new TypeFilter<>(CtMethod.class), CtRole.ANNOTATION); + .byRole(CtRole.ANNOTATION, new TypeFilter<>(CtMethod.class)); }) .build(); List matches = pattern.getMatches(ctClass); @@ -1007,20 +1009,20 @@ public void testMatchInSet() throws Exception { // we match a method with any "throws" clause // and the match "throws" are captured in the parameter Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(ctClass).setTypeMember("matcher1").getPatternElements()) - .configureParameters(pb -> { + .configurePatternParameters(pb -> { pb.parameter("otherThrowables") //add matcher for other arbitrary throwables .setConflictResolutionMode(ConflictResolutionMode.APPEND) .setContainerKind(ContainerKind.SET) .setMinOccurence(0) - .byRole(new TypeFilter(CtMethod.class), CtRole.THROWN); + .byRole(CtRole.THROWN, new TypeFilter(CtMethod.class)); }) - .configureParameters(pb -> { + .configurePatternParameters(pb -> { //define other parameters too to match all kinds of methods - pb.parameter("modifiers").byRole(new TypeFilter(CtMethod.class), CtRole.MODIFIER); + pb.parameter("modifiers").byRole(CtRole.MODIFIER, new TypeFilter(CtMethod.class)); pb.parameter("methodName").byString("matcher1"); - pb.parameter("parameters").byRole(new TypeFilter(CtMethod.class), CtRole.PARAMETER); - pb.parameter("statements").byRole(new TypeFilter(CtBlock.class), CtRole.STATEMENT); + pb.parameter("parameters").byRole(CtRole.PARAMETER, new TypeFilter(CtMethod.class)); + pb.parameter("statements").byRole(CtRole.STATEMENT, new TypeFilter(CtBlock.class)); }) .build(); String str = pattern.toString(); @@ -1093,7 +1095,7 @@ public void testPatternParameters() { ); Pattern p = OldPattern.createPatternFromMethodPatternModel(f); Map parameterInfos = p.getParameterInfos(); - // the pattern has 15 pattern parameters (all usages of variable "params" and "item" + // the pattern has all usages of variable "params" and "item" assertEquals(15, parameterInfos.size()); // .. which are assertEquals(new HashSet<>(Arrays.asList("next","item","startPrefixSpace","printer","start", @@ -1204,13 +1206,13 @@ public void testMatchSample1() throws Exception { // Create a pattern from all statements of OldPattern#patternModel Pattern p = PatternBuilder .create(new PatternBuilderHelper(type).setBodyOfMethod("patternModel").getPatternElements()) - .configureParameters((ParametersBuilder pb) -> pb + .configurePatternParameters((PatternParameterConfigurator pb) -> pb // creating patterns parameters for all references to "params" and "items" - .createPatternParameterForVariable("params", "item") + .byVariable("params", "item") .parameter("statements").setContainerKind(ContainerKind.LIST) ) - .createPatternParameters() - .configureInlineStatements(ls -> ls.byVariableName("useStartKeyword")) + .configurePatternParameters() + .configureInlineStatements(ls -> ls.inlineIfOrForeachReferringTo("useStartKeyword")) .build(); // so let's try to match this complex pattern on DJPP @@ -1218,7 +1220,7 @@ public void testMatchSample1() throws Exception { // there are two results (the try-with-resource in each method) assertEquals(2, matches.size()); - ParameterValueProvider params = matches.get(0).getParameters(); + ImmutableMap params = matches.get(0).getParameters(); assertEquals("\"extends\"", params.getValue("startKeyword").toString()); assertEquals(Boolean.TRUE, params.getValue("useStartKeyword")); assertEquals("false", params.getValue("startPrefixSpace").toString()); @@ -1256,11 +1258,8 @@ public void testAddGeneratedBy() throws Exception { // creating a pattern from AClassWithMethodsAndRefs CtType templateModel = ModelUtils.buildClass(AClassWithMethodsAndRefs.class); Factory factory = templateModel.getFactory(); - Pattern pattern = PatternBuilder.create(templateModel).build(); + Pattern pattern = PatternBuilder.create(templateModel).setAddGeneratedBy(true).build(); - assertFalse(pattern.isAddGeneratedBy()); - - pattern.setAddGeneratedBy(true); assertTrue(pattern.isAddGeneratedBy()); } @@ -1274,9 +1273,7 @@ public void testGenerateClassWithSelfReferences() throws Exception { // creating a pattern from AClassWithMethodsAndRefs CtType templateModel = ModelUtils.buildClass(AClassWithMethodsAndRefs.class); Factory factory = templateModel.getFactory(); - Pattern pattern = PatternBuilder.create(templateModel).build(); - - pattern.setAddGeneratedBy(true); + Pattern pattern = PatternBuilder.create(templateModel).setAddGeneratedBy(true).build(); final String newQName = "spoon.test.generated.ACloneOfAClassWithMethodsAndRefs"; CtClass generatedType = pattern.createType(factory, newQName, Collections.emptyMap()); @@ -1499,7 +1496,7 @@ public void testExtensionDecoupledSubstitutionVisitor() throws Exception { spoon.pattern.Pattern pattern = PatternBuilder.create(type.getMethodsByName("block").get(0)) //all the variable references which are declared out of type member "block" are automatically considered //as pattern parameters - .createPatternParameters() + .configurePatternParameters() .build(); final List aMethods = pattern.applyToType(aTargetType, CtMethod.class, params); assertEquals(1, aMethods.size()); @@ -1517,7 +1514,7 @@ public void testExtensionDecoupledSubstitutionVisitor() throws Exception { private Map getMap(Match match, String name) { Object v = match.getParametersMap().get(name); assertNotNull(v); - return ((ParameterValueProvider) v).asMap(); + return ((ImmutableMap) v).asMap(); } diff --git a/src/test/java/spoon/test/template/core/ParameterInfoTest.java b/src/test/java/spoon/test/template/core/ParameterInfoTest.java index 23f14ae2aaf..2e6d42af86a 100644 --- a/src/test/java/spoon/test/template/core/ParameterInfoTest.java +++ b/src/test/java/spoon/test/template/core/ParameterInfoTest.java @@ -23,8 +23,8 @@ import spoon.pattern.internal.parameter.ParameterInfo; import spoon.pattern.internal.parameter.SetParameterInfo; import spoon.reflect.meta.ContainerKind; -import spoon.support.util.ParameterValueProvider; -import spoon.support.util.UnmodifiableParameterValueProvider; +import spoon.support.util.ImmutableMap; +import spoon.support.util.ImmutableMapImpl; public class ParameterInfoTest { @@ -54,7 +54,7 @@ public void testParameterNames() { public void testSingleValueParameterByNameIntoNullContainer() { ParameterInfo namedParam = new MapParameterInfo("year"); {//adding value into null container, creates a new container with that value - ParameterValueProvider val = namedParam.addValueAs(null, 2018); + ImmutableMap val = namedParam.addValueAs(null, 2018); assertNotNull(val); assertEquals(map().put("year", 2018), val.asMap()); } @@ -63,8 +63,8 @@ public void testSingleValueParameterByNameIntoNullContainer() { public void testSingleValueParameterByNameIntoEmptyContainer() { ParameterInfo namedParam = new MapParameterInfo("year"); {//adding value into empty container, creates a new container with that value - ParameterValueProvider empty = new UnmodifiableParameterValueProvider(); - ParameterValueProvider val = namedParam.addValueAs(empty, 2018); + ImmutableMap empty = new ImmutableMapImpl(); + ImmutableMap val = namedParam.addValueAs(empty, 2018); assertNotNull(val); assertEquals(map().put("year", 2018), val.asMap()); //empty is still empty @@ -75,7 +75,7 @@ public void testSingleValueParameterByNameIntoEmptyContainer() { public void testSingleValueParameterByNameWhenAlreadyExists() { ParameterInfo namedParam = new MapParameterInfo("year"); {//adding value into container, which already contains that value changes nothing and returns origin container - ParameterValueProvider oldContainer = new UnmodifiableParameterValueProvider().putValue("year", 2018); + ImmutableMap oldContainer = new ImmutableMapImpl().putValue("year", 2018); assertEquals(map().put("year", 2018), oldContainer.asMap()); //it returned the same container assertSame(oldContainer, namedParam.addValueAs(oldContainer, 2018)); @@ -86,7 +86,7 @@ public void testSingleValueParameterByNameWhenAlreadyExists() { public void testSingleValueParameterByNameWhenDifferentExists() { ParameterInfo namedParam = new MapParameterInfo("year"); {//adding a value into container, which already contains a different value returns null - no match - ParameterValueProvider oldContainer = new UnmodifiableParameterValueProvider().putValue("year", 2018); + ImmutableMap oldContainer = new ImmutableMapImpl().putValue("year", 2018); assertNull(namedParam.addValueAs(oldContainer, 2111)); assertNull(namedParam.addValueAs(oldContainer, 0)); assertNull(namedParam.addValueAs(oldContainer, null)); @@ -100,7 +100,7 @@ public void testOptionalSingleValueParameterByName() { .setMinOccurences(0); {//adding null value into an container with minCount == 0, returns unchanged container. //because minCount == 0 means that value is optional - ParameterValueProvider container = new UnmodifiableParameterValueProvider().putValue("a", "b"); + ImmutableMap container = new ImmutableMapImpl().putValue("a", "b"); assertSame(container, namedParam.addValueAs(container, null)); assertEquals(map().put("a", "b"), container.asMap()); } @@ -112,7 +112,7 @@ public void testMandatorySingleValueParameterByName() { ParameterInfo namedParam = new MapParameterInfo("year") .setMinOccurences(1); { - ParameterValueProvider container = new UnmodifiableParameterValueProvider().putValue("a", "b"); + ImmutableMap container = new ImmutableMapImpl().putValue("a", "b"); assertNull(namedParam.addValueAs(container, null)); assertEquals(map().put("a", "b"), container.asMap()); } @@ -122,21 +122,21 @@ public void testSingleValueParameterByNameConditionalMatcher() { ParameterInfo namedParam = new MapParameterInfo("year").setMatchCondition(Integer.class, i -> i > 2000); //matching value is accepted - ParameterValueProvider val = namedParam.addValueAs(null, 2018); + ImmutableMap val = namedParam.addValueAs(null, 2018); assertNotNull(val); assertEquals(map().put("year", 2018), val.asMap()); //not matching value is not accepted assertNull(namedParam.addValueAs(null, 1000)); assertNull(namedParam.addValueAs(null, "3000")); //even matching value is STILL not accepted when there is already a different value - assertNull(namedParam.addValueAs(new UnmodifiableParameterValueProvider().putValue("year", 3000), 2018)); + assertNull(namedParam.addValueAs(new ImmutableMapImpl().putValue("year", 3000), 2018)); } @Test public void testListParameterByNameIntoNull() { ParameterInfo namedParam = new MapParameterInfo("year").setContainerKind(ContainerKind.LIST); {//adding value into null container, creates a new container with List which contains that value - ParameterValueProvider val = namedParam.addValueAs(null, 2018); + ImmutableMap val = namedParam.addValueAs(null, 2018); assertNotNull(val); assertEquals(map().put("year", Arrays.asList(2018)), val.asMap()); } @@ -145,8 +145,8 @@ public void testListParameterByNameIntoNull() { public void testListParameterByNameIntoEmptyContainer() { ParameterInfo namedParam = new MapParameterInfo("year").setContainerKind(ContainerKind.LIST); {//adding value into empty container, creates a new container with List which contains that value - ParameterValueProvider empty = new UnmodifiableParameterValueProvider(); - ParameterValueProvider val = namedParam.addValueAs(empty, 2018); + ImmutableMap empty = new ImmutableMapImpl(); + ImmutableMap val = namedParam.addValueAs(empty, 2018); assertNotNull(val); assertEquals(map().put("year", Arrays.asList(2018)), val.asMap()); //empty is still empty @@ -157,11 +157,11 @@ public void testListParameterByNameIntoEmptyContainer() { public void testListParameterByNameIntoEmptyContainerWithEmptyList() { Consumer check = (namedParam) -> {//adding value into container, which already contains a empty list, creates a new container with List which contains that value - ParameterValueProvider empty = new UnmodifiableParameterValueProvider().putValue("year", Collections.emptyList()); + ImmutableMap empty = new ImmutableMapImpl().putValue("year", Collections.emptyList()); - ParameterValueProvider val = namedParam.addValueAs(empty, 2018); + ImmutableMap val = namedParam.addValueAs(empty, 2018); //adding same value - adds the second value again - ParameterValueProvider val2 = namedParam.addValueAs(val, 2018); + ImmutableMap val2 = namedParam.addValueAs(val, 2018); //adding null value - adds nothing. Same container is returned assertSame(val2, namedParam.addValueAs(val2, null)); @@ -184,7 +184,7 @@ public void testListParameterByNameIntoEmptyContainerWithEmptyList() { @Test public void testMergeOnDifferentValueTypeContainers() { - BiConsumer checker = (parameter, params) -> { + BiConsumer checker = (parameter, params) -> { //contract: the same value can be always applied when it already exists there independent on container type assertSame(params, parameter.addValueAs(params, "x")); //contract: the different value must be never applied independent on container type @@ -192,7 +192,7 @@ public void testMergeOnDifferentValueTypeContainers() { //contract: the different value must be never applied independent on container type assertNull(parameter.addValueAs(params, null)); }; - ParameterValueProvider empty = new UnmodifiableParameterValueProvider(); + ImmutableMap empty = new ImmutableMapImpl(); checker.accept(new MapParameterInfo("year"), empty.putValue("year", "x")); checker.accept(new ListParameterInfo(0, new MapParameterInfo("year")), empty.putValue("year", Collections.singletonList("x"))); checker.accept(new ListParameterInfo(1, new MapParameterInfo("year")), empty.putValue("year", Arrays.asList("zz","x"))); @@ -203,7 +203,7 @@ public void testMergeOnDifferentValueTypeContainers() { @Test public void testAppendIntoList() { ParameterInfo parameter = new MapParameterInfo("years").setContainerKind(ContainerKind.LIST); - ParameterValueProvider params = parameter.addValueAs(null, 1000); + ImmutableMap params = parameter.addValueAs(null, 1000); assertNotNull(params); assertEquals(map().put("years", Arrays.asList(1000)), params.asMap()); @@ -223,7 +223,7 @@ public void testAppendIntoList() { @Test public void testSetIntoList() { ParameterInfo named = new MapParameterInfo("years"); - ParameterValueProvider params = new ListParameterInfo(2, named).addValueAs(null, 1000); + ImmutableMap params = new ListParameterInfo(2, named).addValueAs(null, 1000); assertNotNull(params); assertEquals(map().put("years", Arrays.asList(null, null, 1000)), params.asMap()); @@ -239,7 +239,7 @@ public void testSetIntoList() { @Test public void testAppendIntoSet() { ParameterInfo parameter = new MapParameterInfo("years").setContainerKind(ContainerKind.SET); - ParameterValueProvider params = parameter.addValueAs(null, 1000); + ImmutableMap params = parameter.addValueAs(null, 1000); assertNotNull(params); assertEquals(map().put("years", asSet(1000)), params.asMap()); @@ -257,14 +257,14 @@ public void testAppendIntoSet() { } @Test public void testMapEntryInParameterByName() { - BiConsumer checker = (namedParam, empty) -> + BiConsumer checker = (namedParam, empty) -> {//the Map.Entry value is added into property of type Map //only Map.Entries can be added assertNull(namedParam.addValueAs(empty, "a value")); - final ParameterValueProvider val = namedParam.addValueAs(empty, entry("year", 2018)); + final ImmutableMap val = namedParam.addValueAs(empty, entry("year", 2018)); assertNotNull(val); - assertEquals(map().put("map", new UnmodifiableParameterValueProvider().putValue("year", 2018)), val.asMap()); + assertEquals(map().put("map", new ImmutableMapImpl().putValue("year", 2018)), val.asMap()); //adding null entry changes nothing assertSame(val, namedParam.addValueAs(val, null)); @@ -273,33 +273,33 @@ public void testMapEntryInParameterByName() { //adding entry value with same key, but different value - no match assertNull(namedParam.addValueAs(val, entry("year", 1111))); - ParameterValueProvider val2 = namedParam.addValueAs(val, entry("age", "best")); + ImmutableMap val2 = namedParam.addValueAs(val, entry("age", "best")); assertNotNull(val2); - assertEquals(map().put("map", new UnmodifiableParameterValueProvider() + assertEquals(map().put("map", new ImmutableMapImpl() .putValue("year", 2018) .putValue("age", "best")), val2.asMap()); //after all the once returned val is still the same - unmodified - assertEquals(map().put("map", new UnmodifiableParameterValueProvider().putValue("year", 2018)), val.asMap()); + assertEquals(map().put("map", new ImmutableMapImpl().putValue("year", 2018)), val.asMap()); }; - checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider()); - checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider().putValue("map", null)); - checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider().putValue("map", Collections.emptyMap())); + checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new ImmutableMapImpl()); + checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new ImmutableMapImpl().putValue("map", null)); + checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new ImmutableMapImpl().putValue("map", Collections.emptyMap())); //the map container is detected automatically from the type of value - checker.accept(new MapParameterInfo("map"), new UnmodifiableParameterValueProvider().putValue("map", Collections.emptyMap())); + checker.accept(new MapParameterInfo("map"), new ImmutableMapImpl().putValue("map", Collections.emptyMap())); //the map container is detected automatically from the type of value - checker.accept(new MapParameterInfo("map"), new UnmodifiableParameterValueProvider().putValue("map", new UnmodifiableParameterValueProvider())); + checker.accept(new MapParameterInfo("map"), new ImmutableMapImpl().putValue("map", new ImmutableMapImpl())); } @Test public void testAddMapIntoParameterByName() { - BiConsumer checker = (namedParam, empty) -> + BiConsumer checker = (namedParam, empty) -> {//the Map value is added into property of type Map - ParameterValueProvider val = namedParam.addValueAs(empty, Collections.emptyMap()); - assertEquals(map().put("map", new UnmodifiableParameterValueProvider()), val.asMap()); + ImmutableMap val = namedParam.addValueAs(empty, Collections.emptyMap()); + assertEquals(map().put("map", new ImmutableMapImpl()), val.asMap()); val = namedParam.addValueAs(empty, map().put("year", 2018)); - assertEquals(map().put("map", new UnmodifiableParameterValueProvider().putValue("year", 2018)), val.asMap()); + assertEquals(map().put("map", new ImmutableMapImpl().putValue("year", 2018)), val.asMap()); val = namedParam.addValueAs(empty, map().put("year", 2018).put("age", 1111)); - assertEquals(map().put("map", new UnmodifiableParameterValueProvider() + assertEquals(map().put("map", new ImmutableMapImpl() .putValue("year", 2018) .putValue("age", 1111)), val.asMap()); @@ -310,22 +310,22 @@ public void testAddMapIntoParameterByName() { //adding entry value with same key, but different value - no match assertNull(namedParam.addValueAs(val, entry("year", 1111))); }; - checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider()); - checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider().putValue("map", null)); - checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new UnmodifiableParameterValueProvider().putValue("map", Collections.emptyMap())); + checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new ImmutableMapImpl()); + checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new ImmutableMapImpl().putValue("map", null)); + checker.accept(new MapParameterInfo("map").setContainerKind(ContainerKind.MAP), new ImmutableMapImpl().putValue("map", Collections.emptyMap())); //the map container is detected automatically from the type of value - checker.accept(new MapParameterInfo("map"), new UnmodifiableParameterValueProvider().putValue("map", Collections.emptyMap())); + checker.accept(new MapParameterInfo("map"), new ImmutableMapImpl().putValue("map", Collections.emptyMap())); //the map container is detected automatically from the type of value - checker.accept(new MapParameterInfo("map"), new UnmodifiableParameterValueProvider().putValue("map", new UnmodifiableParameterValueProvider())); + checker.accept(new MapParameterInfo("map"), new ImmutableMapImpl().putValue("map", new ImmutableMapImpl())); //the map container is detected automatically from the type of new value checker.accept(new MapParameterInfo("map"), null); } @Test public void testAddListIntoParameterByName() { - BiConsumer checker = (namedParam, empty) -> + BiConsumer checker = (namedParam, empty) -> {//the List value is added into property of type List - ParameterValueProvider val = namedParam.addValueAs(empty, Collections.emptyList()); + ImmutableMap val = namedParam.addValueAs(empty, Collections.emptyList()); assertEquals(map().put("list", Collections.emptyList()), val.asMap()); val = namedParam.addValueAs(empty, Arrays.asList(2018)); assertEquals(map().put("list", Arrays.asList(2018)), val.asMap()); @@ -335,21 +335,21 @@ public void testAddListIntoParameterByName() { //adding null entry changes nothing assertSame(val, namedParam.addValueAs(val, null)); }; - checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.LIST), new UnmodifiableParameterValueProvider()); - checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.LIST), new UnmodifiableParameterValueProvider().putValue("list", null)); - checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.LIST), new UnmodifiableParameterValueProvider().putValue("list", Collections.emptyList())); + checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.LIST), new ImmutableMapImpl()); + checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.LIST), new ImmutableMapImpl().putValue("list", null)); + checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.LIST), new ImmutableMapImpl().putValue("list", Collections.emptyList())); //Set can be converted to List - checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.LIST), new UnmodifiableParameterValueProvider().putValue("list", Collections.emptySet())); + checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.LIST), new ImmutableMapImpl().putValue("list", Collections.emptySet())); //the list container is detected automatically from the type of value - checker.accept(new MapParameterInfo("list"), new UnmodifiableParameterValueProvider().putValue("list", Collections.emptyList())); + checker.accept(new MapParameterInfo("list"), new ImmutableMapImpl().putValue("list", Collections.emptyList())); //the list container is detected automatically from the type of new value checker.accept(new MapParameterInfo("list"), null); } @Test public void testAddSetIntoParameterByName() { - BiConsumer checker = (namedParam, empty) -> + BiConsumer checker = (namedParam, empty) -> {//the Set value is added into property of type Set - ParameterValueProvider val = namedParam.addValueAs(empty, Collections.emptySet()); + ImmutableMap val = namedParam.addValueAs(empty, Collections.emptySet()); assertEquals(map().put("list", Collections.emptySet()), val.asMap()); val = namedParam.addValueAs(empty, asSet(2018)); assertEquals(map().put("list", asSet(2018)), val.asMap()); @@ -365,13 +365,13 @@ public void testAddSetIntoParameterByName() { //adding Set with same entry changes nothing assertSame(val, namedParam.addValueAs(val, asSet(2018, 1111))); }; - checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.SET), new UnmodifiableParameterValueProvider()); - checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.SET), new UnmodifiableParameterValueProvider().putValue("list", null)); - checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.SET), new UnmodifiableParameterValueProvider().putValue("list", Collections.emptySet())); + checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.SET), new ImmutableMapImpl()); + checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.SET), new ImmutableMapImpl().putValue("list", null)); + checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.SET), new ImmutableMapImpl().putValue("list", Collections.emptySet())); //The container kind has higher priority, so List will be converted to Set - checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.SET), new UnmodifiableParameterValueProvider().putValue("list", Collections.emptyList())); + checker.accept(new MapParameterInfo("list").setContainerKind(ContainerKind.SET), new ImmutableMapImpl().putValue("list", Collections.emptyList())); //the list container is detected automatically from the type of value - checker.accept(new MapParameterInfo("list"), new UnmodifiableParameterValueProvider().putValue("list", Collections.emptySet())); + checker.accept(new MapParameterInfo("list"), new ImmutableMapImpl().putValue("list", Collections.emptySet())); //the list container is detected automatically from the type of new value checker.accept(new MapParameterInfo("list"), null); } @@ -379,7 +379,7 @@ public void testAddSetIntoParameterByName() { public void testFailOnUnpectedContainer() { ParameterInfo namedParam = new MapParameterInfo("year").setContainerKind(ContainerKind.LIST); try { - namedParam.addValueAs(new UnmodifiableParameterValueProvider().putValue("year", "unexpected"), 1); + namedParam.addValueAs(new ImmutableMapImpl().putValue("year", "unexpected"), 1); fail(); } catch (Exception e) { //OK @@ -390,9 +390,9 @@ public void testFailOnUnpectedContainer() { public void testSetEmptyMap() { ParameterInfo namedParam = new MapParameterInfo("year").setContainerKind(ContainerKind.MAP); {//adding empty Map works - ParameterValueProvider val = namedParam.addValueAs(null, null); + ImmutableMap val = namedParam.addValueAs(null, null); assertNotNull(val); - assertEquals(map().put("year", new UnmodifiableParameterValueProvider()), val.asMap()); + assertEquals(map().put("year", new ImmutableMapImpl()), val.asMap()); } } diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple.java b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple.java index fcaa96e3692..be703a6b098 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple.java @@ -17,7 +17,7 @@ public class MatchMultiple { public static Pattern createPattern(Quantifier matchingStrategy, Integer minCount, Integer maxCount) throws Exception { CtType type = ModelUtils.buildClass(MatchMultiple.class); return PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("matcher1").getPatternElements()) - .configureParameters(pb -> { + .configurePatternParameters(pb -> { // matching anything that is called "statements" (in this case call to method statement. // the setContainerKind(ContainerKind.LIST) means match zero, one or more then one arbitrary statement diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple2.java b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple2.java index 20f8e05f850..c77cd566a03 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple2.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple2.java @@ -1,18 +1,10 @@ package spoon.test.template.testclasses.match; -import spoon.pattern.ParametersBuilder; -import spoon.pattern.Pattern; -import spoon.pattern.PatternBuilder; -import spoon.pattern.PatternBuilderHelper; -import spoon.reflect.declaration.CtType; -import spoon.reflect.factory.Factory; -import spoon.reflect.meta.ContainerKind; import spoon.template.TemplateParameter; import static java.lang.System.out; import java.util.List; -import java.util.function.Consumer; public class MatchMultiple2 { diff --git a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple3.java b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple3.java index 494c2e69463..e830c8a05ca 100644 --- a/src/test/java/spoon/test/template/testclasses/match/MatchMultiple3.java +++ b/src/test/java/spoon/test/template/testclasses/match/MatchMultiple3.java @@ -1,19 +1,9 @@ package spoon.test.template.testclasses.match; -import spoon.pattern.ParametersBuilder; -import spoon.pattern.Pattern; -import spoon.pattern.PatternBuilder; -import spoon.pattern.PatternBuilderHelper; -import spoon.reflect.code.CtLiteral; -import spoon.reflect.declaration.CtType; -import spoon.reflect.factory.Factory; -import spoon.reflect.meta.ContainerKind; import spoon.template.TemplateParameter; import static java.lang.System.out; -import java.util.function.Consumer; - public class MatchMultiple3 { public void matcher1() { diff --git a/src/test/java/spoon/test/template/testclasses/replace/NewPattern.java b/src/test/java/spoon/test/template/testclasses/replace/NewPattern.java index c2560ad4c8c..eec9269ac40 100644 --- a/src/test/java/spoon/test/template/testclasses/replace/NewPattern.java +++ b/src/test/java/spoon/test/template/testclasses/replace/NewPattern.java @@ -35,8 +35,8 @@ private void patternModel(OldPattern.Parameters params) throws Exception { public static Pattern createPatternFromNewPattern(Factory factory) { CtType type = factory.Type().get(NewPattern.class); return PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("patternModel").getPatternElements()) - .createPatternParameters() - .configureParameters(pb -> { + .configurePatternParameters() + .configurePatternParameters(pb -> { pb.parameter("statements").setContainerKind(ContainerKind.LIST); }) .build(); diff --git a/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java b/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java index 6e14dbb1e54..b82aa443c2d 100644 --- a/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java +++ b/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java @@ -64,12 +64,12 @@ void patternModel(Parameters params) throws Exception { public static Pattern createPatternFromMethodPatternModel(Factory factory) { CtType type = factory.Type().get(OldPattern.class); return PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("patternModel").getPatternElements()) - .configureParameters(pb->pb - .createPatternParameterForVariable("params", "item") + .configurePatternParameters(pb->pb + .byVariable("params", "item") .parameter("statements").setContainerKind(ContainerKind.LIST) ) - .createPatternParameters() - .configureInlineStatements(ls -> ls.byVariableName("useStartKeyword")) + .configurePatternParameters() + .configureInlineStatements(ls -> ls.inlineIfOrForeachReferringTo("useStartKeyword")) .build(); } From c6dfc8d1f1cb928431008ba7a472e10d25f2792e Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Mon, 21 May 2018 22:33:23 +0200 Subject: [PATCH 092/131] up --- src/test/java/spoon/test/template/PatternTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index f6db48f639a..79f9d9e6ae7 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -694,7 +694,7 @@ public void testMatchParameterValueType() throws Exception { }) .build(); - List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1")); + List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0)); // there are 3 System.out.println with a literal as parameter assertEquals(3, matches.size()); From 898f8781ca956fe1efaeacb8b6905e77337be202 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Mon, 21 May 2018 22:44:05 +0200 Subject: [PATCH 093/131] up --- .../java/spoon/pattern/PatternBuilderHelper.java | 14 ++++---------- src/main/java/spoon/support/util/ImmutableMap.java | 2 -- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/main/java/spoon/pattern/PatternBuilderHelper.java b/src/main/java/spoon/pattern/PatternBuilderHelper.java index 6caf35f98a8..174bc6bf2e0 100644 --- a/src/main/java/spoon/pattern/PatternBuilderHelper.java +++ b/src/main/java/spoon/pattern/PatternBuilderHelper.java @@ -16,28 +16,22 @@ */ package spoon.pattern; -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; - import spoon.SpoonException; import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtReturn; import spoon.reflect.code.CtStatement; -import spoon.reflect.declaration.CtAnnotation; import spoon.reflect.declaration.CtElement; import spoon.reflect.declaration.CtExecutable; import spoon.reflect.declaration.CtMethod; import spoon.reflect.declaration.CtType; import spoon.reflect.declaration.CtTypeMember; -import spoon.reflect.reference.CtTypeReference; import spoon.reflect.visitor.Filter; import spoon.support.Experimental; +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; + /** * Utility class to select parts of AST to be used as a model of a {@link PatternBuilder}. * diff --git a/src/main/java/spoon/support/util/ImmutableMap.java b/src/main/java/spoon/support/util/ImmutableMap.java index 5196ada9b5c..15f1b37a786 100644 --- a/src/main/java/spoon/support/util/ImmutableMap.java +++ b/src/main/java/spoon/support/util/ImmutableMap.java @@ -16,8 +16,6 @@ */ package spoon.support.util; -import spoon.support.Internal; - import java.util.Map; /** From 15f2543ba838041aeddba7bd8612c2c617ba45d6 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Mon, 21 May 2018 22:50:11 +0200 Subject: [PATCH 094/131] up --- .../pattern/PatternParameterConfigurator.java | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/main/java/spoon/pattern/PatternParameterConfigurator.java b/src/main/java/spoon/pattern/PatternParameterConfigurator.java index ee52ab98f19..d1bea8f9c61 100644 --- a/src/main/java/spoon/pattern/PatternParameterConfigurator.java +++ b/src/main/java/spoon/pattern/PatternParameterConfigurator.java @@ -38,6 +38,7 @@ import spoon.reflect.code.CtArrayAccess; import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtExpression; +import spoon.reflect.code.CtFieldRead; import spoon.reflect.code.CtInvocation; import spoon.reflect.code.CtLiteral; import spoon.reflect.code.CtReturn; @@ -225,6 +226,36 @@ public PatternParameterConfigurator byLocalType(CtType searchScope, String lo return this; } + /** + * Add parameters for each variable reference of `variable` + * @param variable to be substituted variable + * @return this to support fluent API + */ + private void createPatternParameterForVariable(CtVariable variable) { + CtQueryable searchScope; + if (patternBuilder.isInModel(variable)) { + addSubstitutionRequest( + parameter(variable.getSimpleName()).getCurrentParameter(), + variable); + searchScope = variable; + } else { + searchScope = queryModel(); + } + searchScope.map(new VariableReferenceFunction(variable)) + .forEach((CtVariableReference varRef) -> { + CtFieldRead fieldRead = varRef.getParent(CtFieldRead.class); + if (fieldRead != null) { + addSubstitutionRequest( + parameter(fieldRead.getVariable().getSimpleName()).getCurrentParameter(), + fieldRead); + } else { + addSubstitutionRequest( + parameter(varRef.getSimpleName()).getCurrentParameter(), + varRef); + } + }); + } + /** * variable read/write of `variable` * @param variable a variable whose references will be substituted @@ -267,13 +298,13 @@ public PatternParameterConfigurator byVariable(String... variableName) { for (String varName : variableName) { CtVariable var = queryModel().map(new PotentialVariableDeclarationFunction(varName)).first(); if (var != null) { - byVariable(var); + createPatternParameterForVariable(var); } else { List> vars = queryModel().filterChildren(new NamedElementFilter(CtVariable.class, varName)).list(); if (vars.size() > 1) { throw new SpoonException("Ambiguous variable " + varName); } else if (vars.size() == 1) { - byVariable(vars.get(0)); + createPatternParameterForVariable(vars.get(0)); } //else may be we should fail when variable is not found? } } From 176468d4b483c30c722308d3838bed38ff871096 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Mon, 21 May 2018 22:51:37 +0200 Subject: [PATCH 095/131] up --- .../spoon/test/architecture/SpoonArchitectureEnforcerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java b/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java index 9c763fe1bd0..8cf1fc1b23b 100644 --- a/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java +++ b/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java @@ -336,7 +336,7 @@ public void testSpecPackage() throws Exception { officialPackages.add("spoon.experimental"); officialPackages.add("spoon.legacy"); officialPackages.add("spoon.pattern"); - officialPackages.add("spoon.pattern.internal."); + officialPackages.add("spoon.pattern.internal"); officialPackages.add("spoon.pattern.internal.matcher"); officialPackages.add("spoon.pattern.internal.node"); officialPackages.add("spoon.pattern.internal.parameter"); From 0a30676aa44ab7196825264e415bbb30dadea47f Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Tue, 22 May 2018 22:23:42 +0200 Subject: [PATCH 096/131] delete ParameterValueProviderFactory --- src/main/java/spoon/pattern/Pattern.java | 6 ++--- .../internal/matcher/MatchingScanner.java | 12 ++++----- .../spoon/support/util/ImmutableMapImpl.java | 8 ------ .../util/ParameterValueProviderFactory.java | 27 ------------------- .../java/spoon/template/TemplateMatcher.java | 4 +-- 5 files changed, 8 insertions(+), 49 deletions(-) delete mode 100644 src/main/java/spoon/support/util/ParameterValueProviderFactory.java diff --git a/src/main/java/spoon/pattern/Pattern.java b/src/main/java/spoon/pattern/Pattern.java index c95926b4a21..42c5b621d96 100644 --- a/src/main/java/spoon/pattern/Pattern.java +++ b/src/main/java/spoon/pattern/Pattern.java @@ -39,7 +39,6 @@ import spoon.support.Experimental; import spoon.support.util.ImmutableMap; import spoon.support.util.ImmutableMapImpl; -import spoon.support.util.ParameterValueProviderFactory; /** * Represents a pattern for matching code. A pattern is composed of a list of AST models, where a model is an AST with some nodes being "pattern parameters". @@ -58,7 +57,6 @@ */ @Experimental public class Pattern { - private ParameterValueProviderFactory parameterValueProviderFactory = ImmutableMapImpl.Factory.INSTANCE; private ModelNode modelValueResolver; private boolean addGeneratedBy = false; @@ -195,8 +193,8 @@ public void forEachMatch(Object input, CtConsumer consumer) { input = Arrays.asList((Object[]) input); } - MatchingScanner scanner = new MatchingScanner(modelValueResolver, parameterValueProviderFactory, consumer); - ImmutableMap parameters = parameterValueProviderFactory.createParameterValueProvider(); + MatchingScanner scanner = new MatchingScanner(modelValueResolver, consumer); + ImmutableMap parameters = new ImmutableMapImpl(); if (input instanceof Collection) { scanner.scan(null, (Collection) input); } else if (input instanceof Map) { diff --git a/src/main/java/spoon/pattern/internal/matcher/MatchingScanner.java b/src/main/java/spoon/pattern/internal/matcher/MatchingScanner.java index 4b370d32fce..b1de88d4655 100644 --- a/src/main/java/spoon/pattern/internal/matcher/MatchingScanner.java +++ b/src/main/java/spoon/pattern/internal/matcher/MatchingScanner.java @@ -30,19 +30,17 @@ import spoon.reflect.path.CtRole; import spoon.reflect.visitor.EarlyTerminatingScanner; import spoon.reflect.visitor.chain.CtConsumer; -import spoon.support.util.ParameterValueProviderFactory; +import spoon.support.util.ImmutableMapImpl; /** * Represents a Match of TemplateMatcher */ public class MatchingScanner extends EarlyTerminatingScanner { private final ModelNode pattern; - private ParameterValueProviderFactory parameterValueProviderFactory; private CtConsumer matchConsumer; - public MatchingScanner(ModelNode pattern, ParameterValueProviderFactory parameterValueProviderFactory, CtConsumer matchConsumer) { + public MatchingScanner(ModelNode pattern, CtConsumer matchConsumer) { this.pattern = pattern; - this.parameterValueProviderFactory = parameterValueProviderFactory; this.matchConsumer = matchConsumer; } @@ -72,7 +70,7 @@ private int searchMatchInList(CtRole role, List list, boole int matchCount = 0; if (list.size() > 0) { TobeMatched tobeMatched = TobeMatched.create( - parameterValueProviderFactory.createParameterValueProvider(), + new ImmutableMapImpl(), ContainerKind.LIST, list); while (tobeMatched.hasTargets()) { @@ -85,7 +83,7 @@ private int searchMatchInList(CtRole role, List list, boole matchConsumer.accept(new Match(matchedTargets, nextTobeMatched.getParameters())); //do not scan children of matched elements. They already matched, so we must not scan them again //use targets of last match together with new parameters for next match - tobeMatched = nextTobeMatched.copyAndSetParams(parameterValueProviderFactory.createParameterValueProvider()); + tobeMatched = nextTobeMatched.copyAndSetParams(new ImmutableMapImpl()); continue; } //else the template matches nothing. Understand it as no match in this context } @@ -105,7 +103,7 @@ private void searchMatchInSet(CtRole role, Set set) { //copy targets, because it might be modified by call of matchConsumer, when refactoring spoon model //use List, because Spoon uses Sets with predictable order - so keep the order TobeMatched tobeMatched = TobeMatched.create( - parameterValueProviderFactory.createParameterValueProvider(), + new ImmutableMapImpl(), ContainerKind.SET, set); while (tobeMatched.hasTargets()) { diff --git a/src/main/java/spoon/support/util/ImmutableMapImpl.java b/src/main/java/spoon/support/util/ImmutableMapImpl.java index c33bd92580d..f2477c65b4b 100644 --- a/src/main/java/spoon/support/util/ImmutableMapImpl.java +++ b/src/main/java/spoon/support/util/ImmutableMapImpl.java @@ -33,14 +33,6 @@ @Internal public class ImmutableMapImpl implements ImmutableMap { - public static class Factory implements ParameterValueProviderFactory { - public static final Factory INSTANCE = new Factory(); - @Override - public ImmutableMap createParameterValueProvider() { - return new ImmutableMapImpl(); - } - } - protected final ImmutableMap parent; protected final Map map; diff --git a/src/main/java/spoon/support/util/ParameterValueProviderFactory.java b/src/main/java/spoon/support/util/ParameterValueProviderFactory.java deleted file mode 100644 index 11e76a07a49..00000000000 --- a/src/main/java/spoon/support/util/ParameterValueProviderFactory.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * 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.util; - -/** - * Creates instances of {@link ImmutableMap} - */ -public interface ParameterValueProviderFactory { - /** - * @return new instance of empty {@link ImmutableMap} - */ - ImmutableMap createParameterValueProvider(); -} diff --git a/src/main/java/spoon/template/TemplateMatcher.java b/src/main/java/spoon/template/TemplateMatcher.java index 512d3ad27ca..e980c3e5b2e 100644 --- a/src/main/java/spoon/template/TemplateMatcher.java +++ b/src/main/java/spoon/template/TemplateMatcher.java @@ -30,7 +30,6 @@ import spoon.reflect.visitor.Filter; import spoon.reflect.visitor.chain.CtConsumer; import spoon.support.util.ImmutableMap; -import spoon.support.util.ParameterValueProviderFactory; import spoon.support.util.ImmutableMapImpl; /** @@ -51,7 +50,6 @@ public class TemplateMatcher implements Filter { * */ private ImmutableMap matches; - private ParameterValueProviderFactory parameterValueProviderFactory = ImmutableMapImpl.Factory.INSTANCE; /** * Constructs a matcher for a given template. @@ -84,7 +82,7 @@ public boolean matches(CtElement element) { return false; } matches = getMatchedParameters(patternModel.matchAllWith(TobeMatched.create( - parameterValueProviderFactory.createParameterValueProvider(), + new ImmutableMapImpl(), ContainerKind.SINGLE, element))); return matches != null; From edd4b211b69bbbbab42b5bfd64f7f4d3e22a2338 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Tue, 22 May 2018 22:24:06 +0200 Subject: [PATCH 097/131] fix problems caused by refactoring --- .../pattern/PatternParameterConfigurator.java | 77 ++++++++++++------- .../java/spoon/test/template/PatternTest.java | 8 +- .../testclasses/replace/OldPattern.java | 2 +- 3 files changed, 54 insertions(+), 33 deletions(-) diff --git a/src/main/java/spoon/pattern/PatternParameterConfigurator.java b/src/main/java/spoon/pattern/PatternParameterConfigurator.java index d1bea8f9c61..62c08f3089f 100644 --- a/src/main/java/spoon/pattern/PatternParameterConfigurator.java +++ b/src/main/java/spoon/pattern/PatternParameterConfigurator.java @@ -227,33 +227,16 @@ public PatternParameterConfigurator byLocalType(CtType searchScope, String lo } /** - * Add parameters for each variable reference of `variable` - * @param variable to be substituted variable - * @return this to support fluent API + * variable read/write of `variable` + * @param variableName a variable whose references will be substituted + * @return {@link ParametersBuilder} to support fluent API */ - private void createPatternParameterForVariable(CtVariable variable) { - CtQueryable searchScope; - if (patternBuilder.isInModel(variable)) { - addSubstitutionRequest( - parameter(variable.getSimpleName()).getCurrentParameter(), - variable); - searchScope = variable; - } else { - searchScope = queryModel(); - } - searchScope.map(new VariableReferenceFunction(variable)) - .forEach((CtVariableReference varRef) -> { - CtFieldRead fieldRead = varRef.getParent(CtFieldRead.class); - if (fieldRead != null) { - addSubstitutionRequest( - parameter(fieldRead.getVariable().getSimpleName()).getCurrentParameter(), - fieldRead); - } else { - addSubstitutionRequest( - parameter(varRef.getSimpleName()).getCurrentParameter(), - varRef); - } - }); + public PatternParameterConfigurator byVariable(String variableName) { + CtVariable var = queryModel().map(new PotentialVariableDeclarationFunction(variableName)).first(); + if (var != null) { + byVariable(var); + } //else may be we should fail? + return this; } /** @@ -291,10 +274,21 @@ public PatternParameterConfigurator byInvocation(CtMethod method) { /** * Add parameters for each field reference to variable named `variableName` + * For example this pattern model + * class Params { + * int paramA; + * int paramB; + * } + * void matcher(Params p) { + * return p.paramA + p.paramB; + * } + * + * called with `byFieldRefOfVariable("p")` will create pattern parameters: `paramA` and `paramB` + * * @param variableName the name of the variable reference * @return {@link PatternParameterConfigurator} to support fluent API */ - public PatternParameterConfigurator byVariable(String... variableName) { + public PatternParameterConfigurator byFieldRefOfVariable(String... variableName) { for (String varName : variableName) { CtVariable var = queryModel().map(new PotentialVariableDeclarationFunction(varName)).first(); if (var != null) { @@ -310,6 +304,35 @@ public PatternParameterConfigurator byVariable(String... variableName) { } return this; } + /** + * Add parameters for each variable reference of `variable` + * @param variable to be substituted variable + * @return this to support fluent API + */ + private void createPatternParameterForVariable(CtVariable variable) { + CtQueryable searchScope; + if (patternBuilder.isInModel(variable)) { + addSubstitutionRequest( + parameter(variable.getSimpleName()).getCurrentParameter(), + variable); + searchScope = variable; + } else { + searchScope = queryModel(); + } + searchScope.map(new VariableReferenceFunction(variable)) + .forEach((CtVariableReference varRef) -> { + CtFieldRead fieldRead = varRef.getParent(CtFieldRead.class); + if (fieldRead != null) { + addSubstitutionRequest( + parameter(fieldRead.getVariable().getSimpleName()).getCurrentParameter(), + fieldRead); + } else { + addSubstitutionRequest( + parameter(varRef.getSimpleName()).getCurrentParameter(), + varRef); + } + }); + } /** * variable read/write of `variable` of type {@link TemplateParameter} diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index 79f9d9e6ae7..923aae211e3 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -570,9 +570,8 @@ public void testMatchPossesiveMultiValueMinCount2() throws Exception { for (int count = 0; count < 5; count++) { final int countFinal = count; Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(ctClass).setBodyOfMethod("matcher1").getPatternElements()) -.configurePatternParameters() +.configurePatternParameters((Map) null) .configurePatternParameters(pb -> { - pb.setConflictResolutionMode(ConflictResolutionMode.USE_NEW_NODE); pb.parameter("statements1").setContainerKind(ContainerKind.LIST).setMatchingStrategy(Quantifier.GREEDY); pb.parameter("statements2").setContainerKind(ContainerKind.LIST).setMatchingStrategy(Quantifier.POSSESSIVE).setMinOccurence(countFinal).setMaxOccurence(countFinal); pb.parameter("inlinedSysOut").byVariable("something").setMatchingStrategy(Quantifier.POSSESSIVE).setContainerKind(ContainerKind.LIST).setMinOccurence(2).matchInlinedStatements(); @@ -612,9 +611,8 @@ public void testMatchGreedyMultiValueMinCount2() throws Exception { final int count = i; CtType type = ctClass.getFactory().Type().get(MatchMultiple2.class); Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("matcher1").getPatternElements()) - .configurePatternParameters() + .configurePatternParameters((Map) null) .configurePatternParameters(pb -> { - pb.setConflictResolutionMode(ConflictResolutionMode.USE_NEW_NODE); pb.parameter("statements1").setContainerKind(ContainerKind.LIST).setMatchingStrategy(Quantifier.RELUCTANT); pb.parameter("statements2").setContainerKind(ContainerKind.LIST).setMatchingStrategy(Quantifier.GREEDY).setMaxOccurence(count); pb.parameter("printedValue").byVariable("something").matchInlinedStatements(); @@ -1208,7 +1206,7 @@ public void testMatchSample1() throws Exception { .create(new PatternBuilderHelper(type).setBodyOfMethod("patternModel").getPatternElements()) .configurePatternParameters((PatternParameterConfigurator pb) -> pb // creating patterns parameters for all references to "params" and "items" - .byVariable("params", "item") + .byFieldRefOfVariable("params", "item") .parameter("statements").setContainerKind(ContainerKind.LIST) ) .configurePatternParameters() diff --git a/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java b/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java index b82aa443c2d..da73a01d256 100644 --- a/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java +++ b/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java @@ -65,7 +65,7 @@ public static Pattern createPatternFromMethodPatternModel(Factory factory) { CtType type = factory.Type().get(OldPattern.class); return PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("patternModel").getPatternElements()) .configurePatternParameters(pb->pb - .byVariable("params", "item") + .byFieldRefOfVariable("params", "item") .parameter("statements").setContainerKind(ContainerKind.LIST) ) .configurePatternParameters() From ba1b554b885f9a587b741e082b69da0d20206ec7 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Tue, 22 May 2018 22:38:51 +0200 Subject: [PATCH 098/131] re-add removeSuperClass which is needed to create pattern from legacy template correctly --- src/main/java/spoon/pattern/PatternBuilderHelper.java | 8 ++++++++ src/main/java/spoon/template/TemplateBuilder.java | 2 ++ 2 files changed, 10 insertions(+) diff --git a/src/main/java/spoon/pattern/PatternBuilderHelper.java b/src/main/java/spoon/pattern/PatternBuilderHelper.java index 174bc6bf2e0..18c8c75a824 100644 --- a/src/main/java/spoon/pattern/PatternBuilderHelper.java +++ b/src/main/java/spoon/pattern/PatternBuilderHelper.java @@ -171,6 +171,14 @@ public PatternBuilderHelper keepTypeMembers(Filter filter) { return this; } + /** + * removes super class from the template + */ + public PatternBuilderHelper removeSuperClass() { + getClonedPatternType().setSuperclass(null); + return this; + } + /** * @return a List of {@link CtElement}s, which has to be used as pattern model */ diff --git a/src/main/java/spoon/template/TemplateBuilder.java b/src/main/java/spoon/template/TemplateBuilder.java index 3831289ab3e..e89483af768 100644 --- a/src/main/java/spoon/template/TemplateBuilder.java +++ b/src/main/java/spoon/template/TemplateBuilder.java @@ -88,6 +88,8 @@ public static TemplateBuilder createPattern(CtElement templateRoot, CtClass t //all other type members have to be part of the pattern model return true; }); + //remove `... extends Template`, which doesn't have to be part of pattern model + tv.removeSuperClass(); }; pb = PatternBuilder.create(tv.getPatternElements()); } else { From 15c22f1d400dc87473cbd4550a1b2f1650240d42 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Tue, 22 May 2018 23:04:52 +0200 Subject: [PATCH 099/131] add test for Match#toString --- src/test/java/spoon/test/template/PatternTest.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index 923aae211e3..4672b5ee52e 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -620,6 +620,8 @@ public void testMatchGreedyMultiValueMinCount2() throws Exception { }) .build(); List matches = pattern.getMatches(ctClass.getMethodsByName("testMatch1").get(0).getBody()); + + if (count < 7) { //the last template has nothing to match -> no match assertEquals("count=" + count, 1, matches.size()); @@ -663,6 +665,13 @@ public void testMatchParameterValue() throws Exception { List matches = pattern.getMatches(ctClass); + // specifying Match#toString + assertEquals("{\n" + + "value=value\n" + + "}\n" + + "----------\n" + + "1) java.lang.System.out.println(value)", matches.get(0).toString()); + // we match in the whole class, which means the original matcher statements and the ones from testMatcher1 assertEquals(5, matches.size()); From e8d934dd59c1825d50aa4c3568fb96fa1bb607ce Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Tue, 22 May 2018 23:10:17 +0200 Subject: [PATCH 100/131] add test for ImmutableMapImpl#asMap --- src/test/java/spoon/test/template/PatternTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index 4672b5ee52e..3aaf98aa6a2 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -1255,6 +1255,9 @@ public void testMatchSample1() throws Exception { assertEquals("\";\"", params.getValue("end").toString()); assertEquals("ctEnum.getEnumValues()", params.getValue("getIterable").toString()); assertEquals("[scan(enumValue)]", params.getValue("statements").toString()); + + // additional test for ImmutableMap + assertEquals(params.asMap(), params.checkpoint().asMap()); } @Test From c729798f9c385023bd30ae4bab100ef8f43eabbe Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Tue, 22 May 2018 23:16:38 +0200 Subject: [PATCH 101/131] remove untested useless code in StatementTemplate --- .../java/spoon/template/StatementTemplate.java | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/main/java/spoon/template/StatementTemplate.java b/src/main/java/spoon/template/StatementTemplate.java index 17ba7d9a781..f39e40f531a 100644 --- a/src/main/java/spoon/template/StatementTemplate.java +++ b/src/main/java/spoon/template/StatementTemplate.java @@ -48,18 +48,10 @@ public CtStatement apply(CtType targetType) { List statements = TemplateBuilder.createPattern(patternModel, this) .setAddGeneratedBy(isAddGeneratedBy()) .substituteList(c.getFactory(), targetType, CtStatement.class); - if (statements.isEmpty()) { - return null; + if (statements.size() != 1) { + throw new IllegalStateException(); } - if (statements.size() == 1) { - return statements.get(0); - } - CtBlock block = patternModel.getFactory().createBlock(); - block.setImplicit(true); - for (CtStatement stmt : statements) { - block.addStatement(stmt); - } - return block; + return statements.get(0); } public Void S() { From b47696395a8a74f7837d93012f10f7ee3a7db833 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Tue, 22 May 2018 23:18:50 +0200 Subject: [PATCH 102/131] fix javadoc bug --- src/main/java/spoon/pattern/PatternParameterConfigurator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/spoon/pattern/PatternParameterConfigurator.java b/src/main/java/spoon/pattern/PatternParameterConfigurator.java index 62c08f3089f..ab583bf44c3 100644 --- a/src/main/java/spoon/pattern/PatternParameterConfigurator.java +++ b/src/main/java/spoon/pattern/PatternParameterConfigurator.java @@ -229,7 +229,7 @@ public PatternParameterConfigurator byLocalType(CtType searchScope, String lo /** * variable read/write of `variable` * @param variableName a variable whose references will be substituted - * @return {@link ParametersBuilder} to support fluent API + * @return this to support fluent API */ public PatternParameterConfigurator byVariable(String variableName) { CtVariable var = queryModel().map(new PotentialVariableDeclarationFunction(variableName)).first(); From d132fc5e5a020e7ac402a4a0cd16830c4262593e Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Tue, 22 May 2018 23:38:56 +0200 Subject: [PATCH 103/131] fix checkstyle --- src/main/java/spoon/template/StatementTemplate.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/spoon/template/StatementTemplate.java b/src/main/java/spoon/template/StatementTemplate.java index f39e40f531a..7b5a80742f4 100644 --- a/src/main/java/spoon/template/StatementTemplate.java +++ b/src/main/java/spoon/template/StatementTemplate.java @@ -16,13 +16,12 @@ */ package spoon.template; -import java.util.List; - -import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtStatement; import spoon.reflect.declaration.CtClass; import spoon.reflect.declaration.CtType; +import java.util.List; + /** * This class represents a template parameter that defines a statement list * directly expressed in Java (no returns). From b363333858cf787c28e470fb9e9ea3281dca0bf0 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Wed, 23 May 2018 18:34:15 +0200 Subject: [PATCH 104/131] up --- .../pattern/PatternParameterConfigurator.java | 24 +++++++++---------- .../java/spoon/test/template/PatternTest.java | 2 +- .../testclasses/replace/OldPattern.java | 2 +- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/main/java/spoon/pattern/PatternParameterConfigurator.java b/src/main/java/spoon/pattern/PatternParameterConfigurator.java index ab583bf44c3..5c0fef29f66 100644 --- a/src/main/java/spoon/pattern/PatternParameterConfigurator.java +++ b/src/main/java/spoon/pattern/PatternParameterConfigurator.java @@ -288,19 +288,17 @@ public PatternParameterConfigurator byInvocation(CtMethod method) { * @param variableName the name of the variable reference * @return {@link PatternParameterConfigurator} to support fluent API */ - public PatternParameterConfigurator byFieldRefOfVariable(String... variableName) { - for (String varName : variableName) { - CtVariable var = queryModel().map(new PotentialVariableDeclarationFunction(varName)).first(); - if (var != null) { - createPatternParameterForVariable(var); - } else { - List> vars = queryModel().filterChildren(new NamedElementFilter(CtVariable.class, varName)).list(); - if (vars.size() > 1) { - throw new SpoonException("Ambiguous variable " + varName); - } else if (vars.size() == 1) { - createPatternParameterForVariable(vars.get(0)); - } //else may be we should fail when variable is not found? - } + public PatternParameterConfigurator byFieldAccessOnVariable(String varName) { + CtVariable var = queryModel().map(new PotentialVariableDeclarationFunction(varName)).first(); + if (var != null) { + createPatternParameterForVariable(var); + } else { + List> vars = queryModel().filterChildren(new NamedElementFilter(CtVariable.class, varName)).list(); + if (vars.size() > 1) { + throw new SpoonException("Ambiguous variable " + varName); + } else if (vars.size() == 1) { + createPatternParameterForVariable(vars.get(0)); + } //else may be we should fail when variable is not found? } return this; } diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index 3aaf98aa6a2..bac4b75b350 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -1215,7 +1215,7 @@ public void testMatchSample1() throws Exception { .create(new PatternBuilderHelper(type).setBodyOfMethod("patternModel").getPatternElements()) .configurePatternParameters((PatternParameterConfigurator pb) -> pb // creating patterns parameters for all references to "params" and "items" - .byFieldRefOfVariable("params", "item") + .byFieldAccessOnVariable("params").byFieldAccessOnVariable("item") .parameter("statements").setContainerKind(ContainerKind.LIST) ) .configurePatternParameters() diff --git a/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java b/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java index da73a01d256..018a506a489 100644 --- a/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java +++ b/src/test/java/spoon/test/template/testclasses/replace/OldPattern.java @@ -65,7 +65,7 @@ public static Pattern createPatternFromMethodPatternModel(Factory factory) { CtType type = factory.Type().get(OldPattern.class); return PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("patternModel").getPatternElements()) .configurePatternParameters(pb->pb - .byFieldRefOfVariable("params", "item") + .byFieldAccessOnVariable("params").byFieldAccessOnVariable("item") .parameter("statements").setContainerKind(ContainerKind.LIST) ) .configurePatternParameters() From 1719d49149b205a167e191676c0dbff0842d72f7 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Wed, 23 May 2018 20:21:29 +0200 Subject: [PATCH 105/131] fix javadoc error --- src/main/java/spoon/pattern/PatternParameterConfigurator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/spoon/pattern/PatternParameterConfigurator.java b/src/main/java/spoon/pattern/PatternParameterConfigurator.java index 5c0fef29f66..959c65afeeb 100644 --- a/src/main/java/spoon/pattern/PatternParameterConfigurator.java +++ b/src/main/java/spoon/pattern/PatternParameterConfigurator.java @@ -285,7 +285,7 @@ public PatternParameterConfigurator byInvocation(CtMethod method) { * * called with `byFieldRefOfVariable("p")` will create pattern parameters: `paramA` and `paramB` * - * @param variableName the name of the variable reference + * @param varName the name of the variable reference * @return {@link PatternParameterConfigurator} to support fluent API */ public PatternParameterConfigurator byFieldAccessOnVariable(String varName) { From 71a5ac316327015ef9b9bdd24488a57ecae6daad Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Wed, 23 May 2018 21:45:16 +0200 Subject: [PATCH 106/131] move feature configurePatternParameters(Map) to byTemplateParameter and byParameterValues --- .../java/spoon/pattern/PatternBuilder.java | 180 +---------------- .../pattern/PatternParameterConfigurator.java | 187 ++++++++++++++++++ .../java/spoon/template/Substitution.java | 5 +- .../java/spoon/template/TemplateBuilder.java | 6 +- .../java/spoon/test/template/PatternTest.java | 6 +- 5 files changed, 203 insertions(+), 181 deletions(-) diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index 4f6fb055775..fbd489658c3 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -38,19 +38,13 @@ import spoon.pattern.internal.parameter.AbstractParameterInfo; import spoon.pattern.internal.parameter.ParameterInfo; import spoon.reflect.code.CtBlock; -import spoon.reflect.code.CtInvocation; -import spoon.reflect.code.CtLiteral; import spoon.reflect.code.CtStatement; import spoon.reflect.declaration.CtElement; -import spoon.reflect.declaration.CtField; import spoon.reflect.declaration.CtType; -import spoon.reflect.declaration.CtTypeMember; import spoon.reflect.declaration.CtVariable; import spoon.reflect.factory.Factory; import spoon.reflect.factory.QueryFactory; -import spoon.reflect.meta.ContainerKind; import spoon.reflect.path.CtRole; -import spoon.reflect.reference.CtArrayTypeReference; import spoon.reflect.reference.CtTypeReference; import spoon.reflect.reference.CtVariableReference; import spoon.reflect.visitor.Filter; @@ -58,11 +52,8 @@ import spoon.reflect.visitor.chain.CtFunction; import spoon.reflect.visitor.chain.CtQuery; import spoon.reflect.visitor.chain.CtQueryable; -import spoon.reflect.visitor.filter.AllTypeMembersFunction; import spoon.reflect.visitor.filter.TypeFilter; import spoon.support.Experimental; -import spoon.template.Parameter; -import spoon.template.TemplateParameter; /** * The master class to create a {@link Pattern} instance. @@ -400,173 +391,6 @@ PatternBuilder configureLocalParameters(Consumer p return this; } - /** - * method to support legacy {@link TemplateParameter} and {@link Parameter} annotation - * @param templateParameters parameters, which will be used in substitution. It is needed here, - * when parameter value types influences which AST nodes will be the target of substitution in legacy template patterns - * @return this to support fluent API - */ - public PatternBuilder configurePatternParameters(Map templateParameters) { - return configurePatternParameters(templateTypeRef.getTypeDeclaration(), templateParameters); - } - - /** - * 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 - */ - private PatternBuilder configurePatternParameters(CtType templateType, Map templateParameters) { - configurePatternParameters(pb -> { - templateType.map(new AllTypeMembersFunction()).forEach((CtTypeMember typeMember) -> { - configurePatternParameter(templateType, templateParameters, pb, typeMember); - }); - if (templateParameters != null) { - //configure template parameters based on parameter values only - these without any declaration in Template - templateParameters.forEach((paramName, paramValue) -> { - if (pb.isSubstituted(paramName) == false) { - //and only these parameters whose name isn't already handled by explicit template parameters - if (paramValue instanceof CtTypeReference) { - pb.parameter(paramName) - .setConflictResolutionMode(ConflictResolutionMode.KEEP_OLD_NODE) - .byLocalType(templateType, paramName); - } - pb.parameter(paramName) - .setConflictResolutionMode(ConflictResolutionMode.KEEP_OLD_NODE) - .bySubstring(paramName); - } - }); - } - }); - return this; - } - - private void configurePatternParameter(CtType templateType, Map templateParameters, PatternParameterConfigurator pb, CtTypeMember typeMember) { - Factory f = typeMember.getFactory(); - CtTypeReference templateParamRef = f.Type().createReference(TemplateParameter.class); - CtTypeReference typeReferenceRef = f.Type().createReference(CtTypeReference.class); - CtTypeReference ctStatementRef = f.Type().createReference(CtStatement.class); - Parameter param = typeMember.getAnnotation(Parameter.class); - if (param != null) { - if (typeMember instanceof CtField) { - CtField paramField = (CtField) typeMember; - /* - * We have found a CtField annotated by @Parameter. - * Use it as Pattern parameter - */ - String fieldName = typeMember.getSimpleName(); - String stringMarker = (param.value() != null && param.value().length() > 0) ? param.value() : fieldName; - //for the compatibility reasons with Parameters.getNamesToValues(), use the proxy name as parameter name - String parameterName = stringMarker; - - CtTypeReference paramType = paramField.getType(); - - if (paramType.isSubtypeOf(f.Type().ITERABLE) || paramType instanceof CtArrayTypeReference) { - //parameter is a multivalue - // here we need to replace all named element and all references whose simpleName == stringMarker - pb.parameter(parameterName).setContainerKind(ContainerKind.LIST).byNamedElement(stringMarker).byReferenceName(stringMarker); - } else if (paramType.isSubtypeOf(typeReferenceRef) || paramType.getQualifiedName().equals(Class.class.getName())) { - /* - * parameter with value type TypeReference or Class, identifies replacement of local type whose name is equal to parameter name - */ - CtTypeReference nestedType = getLocalTypeRefBySimpleName(templateType, stringMarker); - if (nestedType != null) { - //all references to nestedType has to be replaced - pb.parameter(parameterName).byType(nestedType); - } - //and replace the variable references by class access - pb.parameter(parameterName).byVariable(paramField); - } else if (paramType.getQualifiedName().equals(String.class.getName())) { - CtTypeReference nestedType = getLocalTypeRefBySimpleName(templateType, stringMarker); - if (nestedType != null) { - //There is a local type with such name. Replace it - pb.parameter(parameterName).byType(nestedType); - } - } else if (paramType.isSubtypeOf(templateParamRef)) { - pb.parameter(parameterName) - .byTemplateParameterReference(paramField); - //if there is any invocation of method with name matching to stringMarker, then substitute their invocations too. - templateType.getMethodsByName(stringMarker).forEach(m -> { - pb.parameter(parameterName).byInvocation(m); - }); - } else if (paramType.isSubtypeOf(ctStatementRef)) { - //if there is any invocation of method with name matching to stringMarker, then substitute their invocations too. - templateType.getMethodsByName(stringMarker).forEach(m -> { - pb.parameter(parameterName).setContainerKind(ContainerKind.LIST).byInvocation(m); - }); - } else { - //it is not a String. It is used to substitute CtLiteral of parameter value - pb.parameter(parameterName) - //all occurrences of parameter name in pattern model are subject of substitution - .byVariable(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 = pb.parameter(parameterName).getCurrentParameter(); - pb.queryModel().filterChildren((CtInvocation inv) -> { - return inv.getExecutable().getSimpleName().equals(stringMarker); - }).forEach((CtInvocation inv) -> { - pb.addSubstitutionRequest(pi, inv); - }); - } - } - - //any value can be converted to String. Substitute content of all string attributes - pb.parameter(parameterName).setConflictResolutionMode(ConflictResolutionMode.KEEP_OLD_NODE) - .bySubstring(stringMarker); - - if (templateParameters != null) { - //handle automatic inline statements - addInlineStatements(fieldName, templateParameters.get(parameterName)); - } - } else { - //TODO CtMethod was may be supported in old Template engine!!! - throw new SpoonException("Template Parameter annotation on " + typeMember.getClass().getName() + " is not supported"); - } - } else if (typeMember instanceof CtField && ((CtField) typeMember).getType().isSubtypeOf(templateParamRef)) { - CtField field = (CtField) typeMember; - String parameterName = typeMember.getSimpleName(); - Object value = templateParameters == null ? null : templateParameters.get(parameterName); - Class valueType = null; - boolean multiple = false; - if (value != null) { - valueType = value.getClass(); - if (value instanceof CtBlock) { - //the CtBlock in this situation is expected as container of Statements in legacy templates. - multiple = true; - } - } - pb.parameter(parameterName).setValueType(valueType).setContainerKind(multiple ? ContainerKind.LIST : ContainerKind.SINGLE) - .byTemplateParameterReference(field); - - if (templateParameters != null) { - //handle automatic inline statements - addInlineStatements(parameterName, templateParameters.get(parameterName)); - } - } - - } - - private void addInlineStatements(String variableName, Object paramValue) { - if (paramValue != null && paramValue.getClass().isArray()) { - //the parameters with Array value are meta parameters in legacy templates - configureInlineStatements(sb -> { - //we are adding inline statements automatically from legacy templates, - //so do not fail if it is sometime not possible - it means that it is not a inline statement then - sb.setFailOnMissingParameter(false); - sb.inlineIfOrForeachReferringTo(variableName); - }); - } - } - /** * Configures inlined statements * @@ -688,4 +512,8 @@ public PatternBuilder setAutoSimplifySubstitutions(boolean autoSimplifySubstitut this.autoSimplifySubstitutions = autoSimplifySubstitutions; return this; } + + CtTypeReference getTemplateTypeRef() { + return templateTypeRef; + } } diff --git a/src/main/java/spoon/pattern/PatternParameterConfigurator.java b/src/main/java/spoon/pattern/PatternParameterConfigurator.java index 959c65afeeb..1904c82a64d 100644 --- a/src/main/java/spoon/pattern/PatternParameterConfigurator.java +++ b/src/main/java/spoon/pattern/PatternParameterConfigurator.java @@ -42,16 +42,21 @@ 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; import spoon.reflect.declaration.CtElement; +import spoon.reflect.declaration.CtField; import spoon.reflect.declaration.CtMethod; import spoon.reflect.declaration.CtNamedElement; import spoon.reflect.declaration.CtType; +import spoon.reflect.declaration.CtTypeMember; import spoon.reflect.declaration.CtVariable; +import spoon.reflect.factory.Factory; import spoon.reflect.meta.ContainerKind; import spoon.reflect.meta.RoleHandler; import spoon.reflect.meta.impl.RoleHandlerHelper; import spoon.reflect.path.CtRole; +import spoon.reflect.reference.CtArrayTypeReference; import spoon.reflect.reference.CtExecutableReference; import spoon.reflect.reference.CtReference; import spoon.reflect.reference.CtTypeReference; @@ -59,11 +64,13 @@ import spoon.reflect.visitor.CtScanner; import spoon.reflect.visitor.Filter; import spoon.reflect.visitor.chain.CtQueryable; +import spoon.reflect.visitor.filter.AllTypeMembersFunction; import spoon.reflect.visitor.filter.InvocationFilter; import spoon.reflect.visitor.filter.NamedElementFilter; import spoon.reflect.visitor.filter.PotentialVariableDeclarationFunction; import spoon.reflect.visitor.filter.VariableReferenceFunction; import spoon.support.Experimental; +import spoon.template.Parameter; import spoon.template.TemplateParameter; /** @@ -332,6 +339,186 @@ private void createPatternParameterForVariable(CtVariable variable) { }); } + /** + * Creates pattern parameter for each field of type {@link TemplateParameter} + * @return this to support fluent API + */ + public PatternParameterConfigurator byTemplateParameter() { + return byTemplateParameter(null); + } + /** + * Creates pattern parameter for each field of type {@link TemplateParameter} + * @param parameterValues pattern parameter values. + * Note these values may influence the way how pattern parameters are created. + * This unclear and ambiguous technique was used in legacy templates + * @return this to support fluent API + */ + @Deprecated + public PatternParameterConfigurator byTemplateParameter(Map parameterValues) { + CtType templateType = patternBuilder.getTemplateTypeRef().getTypeDeclaration(); + templateType.map(new AllTypeMembersFunction()).forEach((CtTypeMember typeMember) -> { + configureByTemplateParameter(templateType, parameterValues, typeMember); + }); + return this; + } + + private void configureByTemplateParameter(CtType templateType, Map parameterValues, CtTypeMember typeMember) { + Factory f = typeMember.getFactory(); + CtTypeReference typeReferenceRef = f.Type().createReference(CtTypeReference.class); + CtTypeReference ctStatementRef = f.Type().createReference(CtStatement.class); + CtTypeReference templateParamRef = f.Type().createReference(TemplateParameter.class); + Parameter param = typeMember.getAnnotation(Parameter.class); + if (param != null) { + if (typeMember instanceof CtField) { + CtField paramField = (CtField) typeMember; + /* + * We have found a CtField annotated by @Parameter. + * Use it as Pattern parameter + */ + String fieldName = typeMember.getSimpleName(); + String stringMarker = (param.value() != null && param.value().length() > 0) ? param.value() : fieldName; + //for the compatibility reasons with Parameters.getNamesToValues(), use the proxy name as parameter name + String parameterName = stringMarker; + + CtTypeReference paramType = paramField.getType(); + + if (paramType.isSubtypeOf(f.Type().ITERABLE) || paramType instanceof CtArrayTypeReference) { + //parameter is a multivalue + // here we need to replace all named element and all references whose simpleName == stringMarker + parameter(parameterName).setContainerKind(ContainerKind.LIST).byNamedElement(stringMarker).byReferenceName(stringMarker); + } else if (paramType.isSubtypeOf(typeReferenceRef) || paramType.getQualifiedName().equals(Class.class.getName())) { + /* + * parameter with value type TypeReference or Class, identifies replacement of local type whose name is equal to parameter name + */ + CtTypeReference nestedType = getLocalTypeRefBySimpleName(templateType, stringMarker); + if (nestedType != null) { + //all references to nestedType has to be replaced + parameter(parameterName).byType(nestedType); + } + //and replace the variable references by class access + parameter(parameterName).byVariable(paramField); + } else if (paramType.getQualifiedName().equals(String.class.getName())) { + CtTypeReference nestedType = getLocalTypeRefBySimpleName(templateType, stringMarker); + if (nestedType != null) { + //There is a local type with such name. Replace it + parameter(parameterName).byType(nestedType); + } + } else if (paramType.isSubtypeOf(templateParamRef)) { + parameter(parameterName) + .byTemplateParameterReference(paramField); + //if there is any invocation of method with name matching to stringMarker, then substitute their invocations too. + templateType.getMethodsByName(stringMarker).forEach(m -> { + parameter(parameterName).byInvocation(m); + }); + } else if (paramType.isSubtypeOf(ctStatementRef)) { + //if there is any invocation of method with name matching to stringMarker, then substitute their invocations too. + templateType.getMethodsByName(stringMarker).forEach(m -> { + parameter(parameterName).setContainerKind(ContainerKind.LIST).byInvocation(m); + }); + } else { + //it is not a String. It is used to substitute CtLiteral of parameter value + parameter(parameterName) + //all occurrences of parameter name in pattern model are subject of substitution + .byVariable(paramField); + } + if (paramType.getQualifiedName().equals(Object.class.getName()) && parameterValues != null) { + //if the parameter type is Object, then detect the real parameter type from the parameter value + Object value = parameterValues.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 = parameter(parameterName).getCurrentParameter(); + queryModel().filterChildren((CtInvocation inv) -> { + return inv.getExecutable().getSimpleName().equals(stringMarker); + }).forEach((CtInvocation inv) -> { + addSubstitutionRequest(pi, inv); + }); + } + } + + //any value can be converted to String. Substitute content of all string attributes + parameter(parameterName).setConflictResolutionMode(ConflictResolutionMode.KEEP_OLD_NODE) + .bySubstring(stringMarker); + + if (parameterValues != null) { + //handle automatic inline statements + addInlineStatements(fieldName, parameterValues.get(parameterName)); + } + } else { + //TODO CtMethod was may be supported in old Template engine!!! + throw new SpoonException("Template Parameter annotation on " + typeMember.getClass().getName() + " is not supported"); + } + } else if (typeMember instanceof CtField && ((CtField) typeMember).getType().isSubtypeOf(templateParamRef)) { + CtField field = (CtField) typeMember; + String parameterName = typeMember.getSimpleName(); + Object value = parameterValues == null ? null : parameterValues.get(parameterName); + Class valueType = null; + boolean multiple = false; + if (value != null) { + valueType = value.getClass(); + if (value instanceof CtBlock) { + //the CtBlock in this situation is expected as container of Statements in legacy templates. + multiple = true; + } + } + parameter(parameterName).setValueType(valueType).setContainerKind(multiple ? ContainerKind.LIST : ContainerKind.SINGLE) + .byTemplateParameterReference(field); + + if (parameterValues != null) { + //handle automatic inline statements + addInlineStatements(parameterName, parameterValues.get(parameterName)); + } + } + } + + private void addInlineStatements(String variableName, Object paramValue) { + if (paramValue != null && paramValue.getClass().isArray()) { + //the parameters with Array value are meta parameters in legacy templates + patternBuilder.configureInlineStatements(sb -> { + //we are adding inline statements automatically from legacy templates, + //so do not fail if it is sometime not possible - it means that it is not a inline statement then + sb.setFailOnMissingParameter(false); + sb.inlineIfOrForeachReferringTo(variableName); + }); + } + } + + /** + * Creates pattern parameter for each key of parameterValues {@link Map}. + * The parameter is created only if doesn't exist yet. + * If the parameter value is a CtTypeReference, then all local types whose simple name equals to parameter name are substituted + * Then any name in source code which contains a parameter name will be converted to parameter + * + * Note: This unclear and ambiguous technique was used in legacy templates + * + * @param parameterValues pattern parameter values or null if not known + * @return this to support fluent API + */ + @Deprecated + public PatternParameterConfigurator byParameterValues(Map parameterValues) { + if (parameterValues != null) { + CtType templateType = patternBuilder.getTemplateTypeRef().getTypeDeclaration(); + //configure template parameters based on parameter values only - these without any declaration in Template + parameterValues.forEach((paramName, paramValue) -> { + if (isSubstituted(paramName) == false) { + //and only these parameters whose name isn't already handled by explicit template parameters + if (paramValue instanceof CtTypeReference) { + parameter(paramName) + .setConflictResolutionMode(ConflictResolutionMode.KEEP_OLD_NODE) + .byLocalType(templateType, paramName); + } + parameter(paramName) + .setConflictResolutionMode(ConflictResolutionMode.KEEP_OLD_NODE) + .bySubstring(paramName); + } + }); + } + return this; + } + /** * variable read/write of `variable` of type {@link TemplateParameter} * @param variable a variable whose references will be substituted diff --git a/src/main/java/spoon/template/Substitution.java b/src/main/java/spoon/template/Substitution.java index 30625b7ce75..7e5fb6f9dac 100644 --- a/src/main/java/spoon/template/Substitution.java +++ b/src/main/java/spoon/template/Substitution.java @@ -107,7 +107,10 @@ public static > void insertAll(CtType targetType, T tem public static > T createTypeFromTemplate(String qualifiedTypeName, CtType templateOfType, Map templateParameters) { return PatternBuilder .create(templateOfType) - .configurePatternParameters(templateParameters) + .configurePatternParameters(pc -> { + pc.byTemplateParameter(templateParameters); + pc.byParameterValues(templateParameters); + }) .build() .createType(templateOfType.getFactory(), qualifiedTypeName, templateParameters); } diff --git a/src/main/java/spoon/template/TemplateBuilder.java b/src/main/java/spoon/template/TemplateBuilder.java index e89483af768..84832225827 100644 --- a/src/main/java/spoon/template/TemplateBuilder.java +++ b/src/main/java/spoon/template/TemplateBuilder.java @@ -98,7 +98,11 @@ public static TemplateBuilder createPattern(CtElement templateRoot, CtClass t Map templateParameters = template == null ? null : Parameters.getTemplateParametersAsMap(f, null, template); //legacy templates always automatically simplifies generated code pb.setAutoSimplifySubstitutions(true); - pb.configurePatternParameters(templateParameters); + pb.configurePatternParameters(pc -> { + pc.byTemplateParameter(templateParameters); + pc.byParameterValues(templateParameters); + }); + return new TemplateBuilder(templateType, pb, template); } diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index bac4b75b350..0a95199534e 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -570,8 +570,8 @@ public void testMatchPossesiveMultiValueMinCount2() throws Exception { for (int count = 0; count < 5; count++) { final int countFinal = count; Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(ctClass).setBodyOfMethod("matcher1").getPatternElements()) -.configurePatternParameters((Map) null) -.configurePatternParameters(pb -> { + .configurePatternParameters(pb -> { + pb.byTemplateParameter(); pb.parameter("statements1").setContainerKind(ContainerKind.LIST).setMatchingStrategy(Quantifier.GREEDY); pb.parameter("statements2").setContainerKind(ContainerKind.LIST).setMatchingStrategy(Quantifier.POSSESSIVE).setMinOccurence(countFinal).setMaxOccurence(countFinal); pb.parameter("inlinedSysOut").byVariable("something").setMatchingStrategy(Quantifier.POSSESSIVE).setContainerKind(ContainerKind.LIST).setMinOccurence(2).matchInlinedStatements(); @@ -611,8 +611,8 @@ public void testMatchGreedyMultiValueMinCount2() throws Exception { final int count = i; CtType type = ctClass.getFactory().Type().get(MatchMultiple2.class); Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("matcher1").getPatternElements()) - .configurePatternParameters((Map) null) .configurePatternParameters(pb -> { + pb.byTemplateParameter(); pb.parameter("statements1").setContainerKind(ContainerKind.LIST).setMatchingStrategy(Quantifier.RELUCTANT); pb.parameter("statements2").setContainerKind(ContainerKind.LIST).setMatchingStrategy(Quantifier.GREEDY).setMaxOccurence(count); pb.parameter("printedValue").byVariable("something").matchInlinedStatements(); From e445995fe490241634e41a2e3bd2df31d538908c Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Wed, 23 May 2018 22:00:57 +0200 Subject: [PATCH 107/131] Pattern generator methods moved to class Generator --- src/main/java/spoon/pattern/Generator.java | 147 ++++++++++++++++++ src/main/java/spoon/pattern/Pattern.java | 98 +----------- .../java/spoon/template/Substitution.java | 1 + .../java/spoon/template/TemplateBuilder.java | 4 +- .../java/spoon/test/template/PatternTest.java | 10 +- 5 files changed, 159 insertions(+), 101 deletions(-) create mode 100644 src/main/java/spoon/pattern/Generator.java diff --git a/src/main/java/spoon/pattern/Generator.java b/src/main/java/spoon/pattern/Generator.java new file mode 100644 index 00000000000..cc777bcc90a --- /dev/null +++ b/src/main/java/spoon/pattern/Generator.java @@ -0,0 +1,147 @@ +/** + * 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.pattern; + +import java.util.List; +import java.util.Map; + +import spoon.pattern.internal.DefaultGenerator; +import spoon.reflect.declaration.CtElement; +import spoon.reflect.declaration.CtPackage; +import spoon.reflect.declaration.CtType; +import spoon.reflect.declaration.CtTypeMember; +import spoon.reflect.factory.Factory; +import spoon.reflect.reference.CtTypeReference; +import spoon.support.Experimental; +import spoon.support.util.ImmutableMap; +import spoon.support.util.ImmutableMapImpl; + +/** + * Represents a generator of new code where + * (Pattern) + (pattern parameters) => (copy of pattern where parameters are replaced by parameter values) + * This is done with {@link #substitute(Factory, Class, ImmutableMap)} + * + * Main documentation at http://spoon.gforge.inria.fr/pattern.html. + * + * Instances of {@link Generator} can created with {@link PatternBuilder} and then call {@link Pattern#generator()}. + * + */ +@Experimental +public class Generator { + private Pattern pattern; + + /** package-protected, must use {@link Pattern#generator()} */ + Generator(Pattern pattern) { + this.pattern = pattern; + } + + /** + * Main method to generate a new AST made from substituting of parameters by values in `params` + * @param factory TODO + * @param valueType - the expected type of returned items + * @param params - the substitution parameters + * @return List of generated elements + */ + public List substitute(Factory factory, Class valueType, ImmutableMap params) { + return new DefaultGenerator(factory).setAddGeneratedBy(isAddGeneratedBy()).generateTargets(pattern.getModelValueResolver(), params, valueType); + } + + /** Utility method that provides the same feature as {@link #substitute(Factory, Class, ImmutableMap)}, but with a Map as third parameter */ + public List substituteList(Factory factory, Class valueType, Map params) { + return substitute(factory, valueType, new ImmutableMapImpl(params)); + } + + /** Utility method that provides the same feature as {@link #substitute(Factory, Class, ImmutableMap)}, but returns a single element, and uses a map as parameter */ + public T substituteSingle(Factory factory, Class valueType, Map params) { + return substituteSingle(factory, valueType, new ImmutableMapImpl(params)); + } + + /** Utility method that provides the same feature as {@link #substitute(Factory, Class, ImmutableMap)}, but returns a single element */ + public T substituteSingle(Factory factory, Class valueType, ImmutableMap params) { + return new DefaultGenerator(factory).setAddGeneratedBy(isAddGeneratedBy()).generateSingleTarget(pattern.getModelValueResolver(), params, valueType); + } + + /** + * Generates type with qualified name `typeQualifiedName` using this {@link Generator} and provided `params`. + * + * Note: the root of pattern element must be one or more types. + * + * @param typeQualifiedName the qualified name of to be generated type + * @param params the pattern parameters + * @return the generated type + */ + public > T createType(Factory factory, String typeQualifiedName, Map params) { + CtTypeReference newTypeRef = factory.Type().createReference(typeQualifiedName); + CtPackage ownerPackage = newTypeRef.getFactory().Package().getOrCreate(newTypeRef.getPackage().getQualifiedName()); + return createType(ownerPackage, newTypeRef.getSimpleName(), params); + } + + /** + * Generates type in the package `ownerPackage` with simple name `typeSimpleName` using this {@link Generator} and provided `params` + * + * Note: the root of pattern element must be one or more types. + * + * @param ownerPackage the target package + * @param typeSimpleName the simple name of future generated type + * @param params the pattern parameters + * @return the generated type + */ + @SuppressWarnings("unchecked") + private > T createType(CtPackage ownerPackage, String typeSimpleName, Map params) { + @SuppressWarnings({ "rawtypes" }) + List types = substitute(ownerPackage.getFactory(), CtType.class, new ImmutableMapImpl(params, + PatternBuilder.TARGET_TYPE, ownerPackage.getFactory().Type().createReference(getQualifiedName(ownerPackage, typeSimpleName)))); + T result = null; + for (CtType type : types) { + ownerPackage.addType(type); + if (type.getSimpleName().equals(typeSimpleName)) { + result = (T) type; + } + } + return result; + } + + /** + * generates elements following this template with expected target scope `targetType` + * If they are {@link CtTypeMember} then adds them into `targetType`. + * + * @param targetType the existing type, which will contain newly generates {@link CtElement}s + * @param valueType the type of generated elements + * @param params the pattern parameters + * @return List of generated elements + */ + public List applyToType(CtType targetType, Class valueType, Map params) { + List results = substitute(targetType.getFactory(), valueType, new ImmutableMapImpl(params, PatternBuilder.TARGET_TYPE, targetType.getReference())); + for (T result : results) { + if (result instanceof CtTypeMember) { + targetType.addTypeMember((CtTypeMember) result); + } + } + return results; + } + + private static String getQualifiedName(CtPackage pckg, String simpleName) { + if (pckg.isUnnamedPackage()) { + return simpleName; + } + return pckg.getQualifiedName() + CtPackage.PACKAGE_SEPARATOR + simpleName; + } + + public boolean isAddGeneratedBy() { + return pattern.isAddGeneratedBy(); + } +} diff --git a/src/main/java/spoon/pattern/Pattern.java b/src/main/java/spoon/pattern/Pattern.java index 42c5b621d96..dedabd4d0c3 100644 --- a/src/main/java/spoon/pattern/Pattern.java +++ b/src/main/java/spoon/pattern/Pattern.java @@ -25,16 +25,11 @@ import java.util.Map; import spoon.SpoonException; -import spoon.pattern.internal.DefaultGenerator; import spoon.pattern.internal.matcher.MatchingScanner; import spoon.pattern.internal.node.ModelNode; import spoon.pattern.internal.parameter.ParameterInfo; import spoon.reflect.declaration.CtElement; -import spoon.reflect.declaration.CtPackage; -import spoon.reflect.declaration.CtType; -import spoon.reflect.declaration.CtTypeMember; import spoon.reflect.factory.Factory; -import spoon.reflect.reference.CtTypeReference; import spoon.reflect.visitor.chain.CtConsumer; import spoon.support.Experimental; import spoon.support.util.ImmutableMap; @@ -95,88 +90,10 @@ public Map getParameterInfos() { } /** - * Main method to generate a new AST made from substituting of parameters by values in `params` - * @param factory TODO - * @param valueType - the expected type of returned items - * @param params - the substitution parameters - * @return List of generated elements + * @return a {@link Generator}, which can be used to generate a code based on this {@link Pattern} */ - public List substitute(Factory factory, Class valueType, ImmutableMap params) { - return new DefaultGenerator(factory).setAddGeneratedBy(isAddGeneratedBy()).generateTargets(modelValueResolver, params, valueType); - } - - /** Utility method that provides the same feature as {@link #substitute(Factory, Class, ImmutableMap)}, but with a Map as third parameter */ - public List substituteList(Factory factory, Class valueType, Map params) { - return substitute(factory, valueType, new ImmutableMapImpl(params)); - } - - /** Utility method that provides the same feature as {@link #substitute(Factory, Class, ImmutableMap)}, but returns a single element, and uses a map as parameter */ - public T substituteSingle(Factory factory, Class valueType, Map params) { - return substituteSingle(factory, valueType, new ImmutableMapImpl(params)); - } - - /** Utility method that provides the same feature as {@link #substitute(Factory, Class, ImmutableMap)}, but returns a single element */ - public T substituteSingle(Factory factory, Class valueType, ImmutableMap params) { - return new DefaultGenerator(factory).setAddGeneratedBy(isAddGeneratedBy()).generateSingleTarget(modelValueResolver, params, valueType); - } - - /** - * Generates type with qualified name `typeQualifiedName` using this {@link Pattern} and provided `params`. - * - * Note: the root of pattern element must be one or more types. - * - * @param typeQualifiedName the qualified name of to be generated type - * @param params the pattern parameters - * @return the generated type - */ - public > T createType(Factory factory, String typeQualifiedName, Map params) { - CtTypeReference newTypeRef = factory.Type().createReference(typeQualifiedName); - CtPackage ownerPackage = newTypeRef.getFactory().Package().getOrCreate(newTypeRef.getPackage().getQualifiedName()); - return createType(ownerPackage, newTypeRef.getSimpleName(), params); - } - - /** - * Generates type in the package `ownerPackage` with simple name `typeSimpleName` using this {@link Pattern} and provided `params` - * - * Note: the root of pattern element must be one or more types. - * - * @param ownerPackage the target package - * @param typeSimpleName the simple name of future generated type - * @param params the pattern parameters - * @return the generated type - */ - @SuppressWarnings("unchecked") - private > T createType(CtPackage ownerPackage, String typeSimpleName, Map params) { - @SuppressWarnings({ "rawtypes" }) - List types = substitute(ownerPackage.getFactory(), CtType.class, new ImmutableMapImpl(params, - PatternBuilder.TARGET_TYPE, ownerPackage.getFactory().Type().createReference(getQualifiedName(ownerPackage, typeSimpleName)))); - T result = null; - for (CtType type : types) { - ownerPackage.addType(type); - if (type.getSimpleName().equals(typeSimpleName)) { - result = (T) type; - } - } - return result; - } - - /** - * generates elements following this template with expected target scope `targetType` - * If they are {@link CtTypeMember} then adds them into `targetType`. - * - * @param targetType the existing type, which will contain newly generates {@link CtElement}s - * @param valueType the type of generated elements - * @param params the pattern parameters - * @return List of generated elements - */ - public List applyToType(CtType targetType, Class valueType, Map params) { - List results = substitute(targetType.getFactory(), valueType, new ImmutableMapImpl(params, PatternBuilder.TARGET_TYPE, targetType.getReference())); - for (T result : results) { - if (result instanceof CtTypeMember) { - targetType.addTypeMember((CtTypeMember) result); - } - } - return results; + public Generator generator() { + return new Generator(this); } /** @@ -223,14 +140,7 @@ public String toString() { return modelValueResolver.toString(); } - private static String getQualifiedName(CtPackage pckg, String simpleName) { - if (pckg.isUnnamedPackage()) { - return simpleName; - } - return pckg.getQualifiedName() + CtPackage.PACKAGE_SEPARATOR + simpleName; - } - - public boolean isAddGeneratedBy() { + boolean isAddGeneratedBy() { return addGeneratedBy; } diff --git a/src/main/java/spoon/template/Substitution.java b/src/main/java/spoon/template/Substitution.java index 7e5fb6f9dac..ebbf64730a5 100644 --- a/src/main/java/spoon/template/Substitution.java +++ b/src/main/java/spoon/template/Substitution.java @@ -112,6 +112,7 @@ public static > T createTypeFromTemplate(String qualifiedTyp pc.byParameterValues(templateParameters); }) .build() + .generator() .createType(templateOfType.getFactory(), qualifiedTypeName, templateParameters); } diff --git a/src/main/java/spoon/template/TemplateBuilder.java b/src/main/java/spoon/template/TemplateBuilder.java index 84832225827..7c4ab552577 100644 --- a/src/main/java/spoon/template/TemplateBuilder.java +++ b/src/main/java/spoon/template/TemplateBuilder.java @@ -153,7 +153,7 @@ public Map getTemplateParameters(CtType targetType) { * @return a substituted element */ public T substituteSingle(CtType targetType, Class itemType) { - return build().substituteSingle(targetType.getFactory(), itemType, getTemplateParameters(targetType)); + return build().generator().substituteSingle(targetType.getFactory(), itemType, getTemplateParameters(targetType)); } /** * generates a new AST nodes made by cloning of `patternModel` and by substitution of parameters by their values @@ -162,6 +162,6 @@ public T substituteSingle(CtType targetType, Class i * @return List of substituted elements */ public List substituteList(Factory factory, CtType targetType, Class itemType) { - return build().substitute(factory, itemType, new ImmutableMapImpl(getTemplateParameters(targetType))); + return build().generator().substitute(factory, itemType, new ImmutableMapImpl(getTemplateParameters(targetType))); } } diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index 0a95199534e..cf52ce9c730 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -245,7 +245,7 @@ public void testGenerateMultiValues() throws Exception { // created in "MatchMultiple.createPattern",matching a method "statements" params = params.putValue("statements", statementsToBeAdded); - List generated = pattern.substitute(factory, CtStatement.class, params); + List generated = pattern.generator().substitute(factory, CtStatement.class, params); assertEquals(Arrays.asList( //these statements comes from `statements` parameter value "int foo = 0", @@ -1270,7 +1270,7 @@ public void testAddGeneratedBy() throws Exception { Factory factory = templateModel.getFactory(); Pattern pattern = PatternBuilder.create(templateModel).setAddGeneratedBy(true).build(); - assertTrue(pattern.isAddGeneratedBy()); + assertTrue(pattern.generator().isAddGeneratedBy()); } @@ -1286,7 +1286,7 @@ public void testGenerateClassWithSelfReferences() throws Exception { Pattern pattern = PatternBuilder.create(templateModel).setAddGeneratedBy(true).build(); final String newQName = "spoon.test.generated.ACloneOfAClassWithMethodsAndRefs"; - CtClass generatedType = pattern.createType(factory, newQName, Collections.emptyMap()); + CtClass generatedType = pattern.generator().createType(factory, newQName, Collections.emptyMap()); assertNotNull(generatedType); // sanity check that the new type contains all the expected methods @@ -1336,7 +1336,7 @@ public void testGenerateMethodWithSelfReferences() throws Exception { CtClass generatedType = factory.createClass("spoon.test.generated.ACloneOfAClassWithMethodsAndRefs"); - pattern.applyToType(generatedType, CtMethod.class, Collections.emptyMap()); + pattern.generator().applyToType(generatedType, CtMethod.class, Collections.emptyMap()); //contract: the foo method has been added assertEquals(Arrays.asList("foo"), @@ -1508,7 +1508,7 @@ public void testExtensionDecoupledSubstitutionVisitor() throws Exception { //as pattern parameters .configurePatternParameters() .build(); - final List aMethods = pattern.applyToType(aTargetType, CtMethod.class, params); + final List aMethods = pattern.generator().applyToType(aTargetType, CtMethod.class, params); assertEquals(1, aMethods.size()); final CtMethod aMethod = aMethods.get(0); assertTrue(aMethod.getBody().getStatement(0) instanceof CtTry); From 8ee350729a9f937191af9d739033942051361144 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Wed, 23 May 2018 22:35:38 +0200 Subject: [PATCH 108/131] fix javadoc error --- src/main/java/spoon/pattern/Pattern.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/spoon/pattern/Pattern.java b/src/main/java/spoon/pattern/Pattern.java index dedabd4d0c3..1520adca05b 100644 --- a/src/main/java/spoon/pattern/Pattern.java +++ b/src/main/java/spoon/pattern/Pattern.java @@ -29,7 +29,6 @@ import spoon.pattern.internal.node.ModelNode; import spoon.pattern.internal.parameter.ParameterInfo; import spoon.reflect.declaration.CtElement; -import spoon.reflect.factory.Factory; import spoon.reflect.visitor.chain.CtConsumer; import spoon.support.Experimental; import spoon.support.util.ImmutableMap; @@ -44,7 +43,7 @@ * * The {@link Pattern} can also be used to generate new code where * (Pattern) + (pattern parameters) => (copy of pattern where parameters are replaced by parameter values) - * This is done with {@link #substitute(Factory, Class, ImmutableMap)} + * This is done with {@link #generator()} and it's methods * * Differences with {@link spoon.template.TemplateMatcher}: * - it can match sequences of elements From 5f4001787cdab383de85e5d3193392ec309e70f8 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Thu, 24 May 2018 22:50:31 +0200 Subject: [PATCH 109/131] up --- src/main/java/spoon/pattern/Generator.java | 147 --------------------- 1 file changed, 147 deletions(-) delete mode 100644 src/main/java/spoon/pattern/Generator.java diff --git a/src/main/java/spoon/pattern/Generator.java b/src/main/java/spoon/pattern/Generator.java deleted file mode 100644 index cc777bcc90a..00000000000 --- a/src/main/java/spoon/pattern/Generator.java +++ /dev/null @@ -1,147 +0,0 @@ -/** - * 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.pattern; - -import java.util.List; -import java.util.Map; - -import spoon.pattern.internal.DefaultGenerator; -import spoon.reflect.declaration.CtElement; -import spoon.reflect.declaration.CtPackage; -import spoon.reflect.declaration.CtType; -import spoon.reflect.declaration.CtTypeMember; -import spoon.reflect.factory.Factory; -import spoon.reflect.reference.CtTypeReference; -import spoon.support.Experimental; -import spoon.support.util.ImmutableMap; -import spoon.support.util.ImmutableMapImpl; - -/** - * Represents a generator of new code where - * (Pattern) + (pattern parameters) => (copy of pattern where parameters are replaced by parameter values) - * This is done with {@link #substitute(Factory, Class, ImmutableMap)} - * - * Main documentation at http://spoon.gforge.inria.fr/pattern.html. - * - * Instances of {@link Generator} can created with {@link PatternBuilder} and then call {@link Pattern#generator()}. - * - */ -@Experimental -public class Generator { - private Pattern pattern; - - /** package-protected, must use {@link Pattern#generator()} */ - Generator(Pattern pattern) { - this.pattern = pattern; - } - - /** - * Main method to generate a new AST made from substituting of parameters by values in `params` - * @param factory TODO - * @param valueType - the expected type of returned items - * @param params - the substitution parameters - * @return List of generated elements - */ - public List substitute(Factory factory, Class valueType, ImmutableMap params) { - return new DefaultGenerator(factory).setAddGeneratedBy(isAddGeneratedBy()).generateTargets(pattern.getModelValueResolver(), params, valueType); - } - - /** Utility method that provides the same feature as {@link #substitute(Factory, Class, ImmutableMap)}, but with a Map as third parameter */ - public List substituteList(Factory factory, Class valueType, Map params) { - return substitute(factory, valueType, new ImmutableMapImpl(params)); - } - - /** Utility method that provides the same feature as {@link #substitute(Factory, Class, ImmutableMap)}, but returns a single element, and uses a map as parameter */ - public T substituteSingle(Factory factory, Class valueType, Map params) { - return substituteSingle(factory, valueType, new ImmutableMapImpl(params)); - } - - /** Utility method that provides the same feature as {@link #substitute(Factory, Class, ImmutableMap)}, but returns a single element */ - public T substituteSingle(Factory factory, Class valueType, ImmutableMap params) { - return new DefaultGenerator(factory).setAddGeneratedBy(isAddGeneratedBy()).generateSingleTarget(pattern.getModelValueResolver(), params, valueType); - } - - /** - * Generates type with qualified name `typeQualifiedName` using this {@link Generator} and provided `params`. - * - * Note: the root of pattern element must be one or more types. - * - * @param typeQualifiedName the qualified name of to be generated type - * @param params the pattern parameters - * @return the generated type - */ - public > T createType(Factory factory, String typeQualifiedName, Map params) { - CtTypeReference newTypeRef = factory.Type().createReference(typeQualifiedName); - CtPackage ownerPackage = newTypeRef.getFactory().Package().getOrCreate(newTypeRef.getPackage().getQualifiedName()); - return createType(ownerPackage, newTypeRef.getSimpleName(), params); - } - - /** - * Generates type in the package `ownerPackage` with simple name `typeSimpleName` using this {@link Generator} and provided `params` - * - * Note: the root of pattern element must be one or more types. - * - * @param ownerPackage the target package - * @param typeSimpleName the simple name of future generated type - * @param params the pattern parameters - * @return the generated type - */ - @SuppressWarnings("unchecked") - private > T createType(CtPackage ownerPackage, String typeSimpleName, Map params) { - @SuppressWarnings({ "rawtypes" }) - List types = substitute(ownerPackage.getFactory(), CtType.class, new ImmutableMapImpl(params, - PatternBuilder.TARGET_TYPE, ownerPackage.getFactory().Type().createReference(getQualifiedName(ownerPackage, typeSimpleName)))); - T result = null; - for (CtType type : types) { - ownerPackage.addType(type); - if (type.getSimpleName().equals(typeSimpleName)) { - result = (T) type; - } - } - return result; - } - - /** - * generates elements following this template with expected target scope `targetType` - * If they are {@link CtTypeMember} then adds them into `targetType`. - * - * @param targetType the existing type, which will contain newly generates {@link CtElement}s - * @param valueType the type of generated elements - * @param params the pattern parameters - * @return List of generated elements - */ - public List applyToType(CtType targetType, Class valueType, Map params) { - List results = substitute(targetType.getFactory(), valueType, new ImmutableMapImpl(params, PatternBuilder.TARGET_TYPE, targetType.getReference())); - for (T result : results) { - if (result instanceof CtTypeMember) { - targetType.addTypeMember((CtTypeMember) result); - } - } - return results; - } - - private static String getQualifiedName(CtPackage pckg, String simpleName) { - if (pckg.isUnnamedPackage()) { - return simpleName; - } - return pckg.getQualifiedName() + CtPackage.PACKAGE_SEPARATOR + simpleName; - } - - public boolean isAddGeneratedBy() { - return pattern.isAddGeneratedBy(); - } -} From 80919541bffd8d5f103a00e1b9865770e1872e12 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Thu, 24 May 2018 22:50:34 +0200 Subject: [PATCH 110/131] up --- src/main/java/spoon/pattern/Pattern.java | 11 +- .../java/spoon/pattern/PatternBuilder.java | 2 +- .../pattern/internal/DefaultGenerator.java | 131 ++++++++++++++++-- .../spoon/pattern/internal/Generator.java | 86 ------------ .../pattern/internal/PatternPrinter.java | 2 +- .../pattern/internal/node/ConstantNode.java | 5 +- .../pattern/internal/node/ElementNode.java | 9 +- .../pattern/internal/node/ForEachNode.java | 7 +- .../pattern/internal/node/InlineNode.java | 5 +- .../pattern/internal/node/ListOfNodes.java | 5 +- .../pattern/internal/node/MapEntryNode.java | 5 +- .../pattern/internal/node/ParameterNode.java | 5 +- .../spoon/pattern/internal/node/RootNode.java | 5 +- .../pattern/internal/node/StringNode.java | 5 +- .../pattern/internal/node/SwitchNode.java | 13 +- .../java/spoon/template/Substitution.java | 2 +- .../java/spoon/template/TemplateBuilder.java | 4 +- .../java/spoon/test/template/PatternTest.java | 9 +- 18 files changed, 173 insertions(+), 138 deletions(-) delete mode 100644 src/main/java/spoon/pattern/internal/Generator.java diff --git a/src/main/java/spoon/pattern/Pattern.java b/src/main/java/spoon/pattern/Pattern.java index 1520adca05b..46e52f438f7 100644 --- a/src/main/java/spoon/pattern/Pattern.java +++ b/src/main/java/spoon/pattern/Pattern.java @@ -25,10 +25,12 @@ import java.util.Map; import spoon.SpoonException; +import spoon.pattern.internal.DefaultGenerator; import spoon.pattern.internal.matcher.MatchingScanner; import spoon.pattern.internal.node.ModelNode; import spoon.pattern.internal.parameter.ParameterInfo; import spoon.reflect.declaration.CtElement; +import spoon.reflect.factory.Factory; import spoon.reflect.visitor.chain.CtConsumer; import spoon.support.Experimental; import spoon.support.util.ImmutableMap; @@ -53,10 +55,11 @@ public class Pattern { private ModelNode modelValueResolver; private boolean addGeneratedBy = false; - + private final Factory factory; /** package-protected, must use {@link PatternBuilder} */ - Pattern(ModelNode modelValueResolver) { + Pattern(Factory factory, ModelNode modelValueResolver) { this.modelValueResolver = modelValueResolver; + this.factory = factory; } /** @@ -89,10 +92,10 @@ public Map getParameterInfos() { } /** - * @return a {@link Generator}, which can be used to generate a code based on this {@link Pattern} + * @return a {@link GeneratorImpl}, which can be used to generate a code based on this {@link Pattern} */ public Generator generator() { - return new Generator(this); + return new DefaultGenerator(factory, this).setAddGeneratedBy(addGeneratedBy); } /** diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index fbd489658c3..974650e2836 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -320,7 +320,7 @@ public Pattern build() { built = true; //clean the mapping so it is not possible to further modify built pattern using this builder patternElementToSubstRequests.clear(); - return new Pattern(new ModelNode(patternNodes.getNodes())).setAddGeneratedBy(isAddGeneratedBy()); + return new Pattern(getFactory(), new ModelNode(patternNodes.getNodes())).setAddGeneratedBy(isAddGeneratedBy()); } static List bodyToStatements(CtStatement statementOrBlock) { diff --git a/src/main/java/spoon/pattern/internal/DefaultGenerator.java b/src/main/java/spoon/pattern/internal/DefaultGenerator.java index a7521ed3f32..0a5c4e878a1 100644 --- a/src/main/java/spoon/pattern/internal/DefaultGenerator.java +++ b/src/main/java/spoon/pattern/internal/DefaultGenerator.java @@ -16,6 +16,9 @@ */ package spoon.pattern.internal; +import spoon.pattern.Generator; +import spoon.pattern.Pattern; +import spoon.pattern.PatternBuilder; import spoon.pattern.internal.node.RootNode; import spoon.pattern.internal.parameter.ParameterInfo; import spoon.reflect.code.CtCodeElement; @@ -23,11 +26,17 @@ import spoon.reflect.cu.CompilationUnit; import spoon.reflect.cu.SourcePosition; import spoon.reflect.declaration.CtElement; +import spoon.reflect.declaration.CtPackage; import spoon.reflect.declaration.CtType; import spoon.reflect.declaration.CtTypeMember; import spoon.reflect.factory.Factory; +import spoon.reflect.reference.CtTypeReference; import spoon.support.SpoonClassNotFoundException; import spoon.support.util.ImmutableMap; +import spoon.support.util.ImmutableMapImpl; + +import java.util.List; +import java.util.Map; /** * Drives generation process @@ -35,13 +44,49 @@ public class DefaultGenerator implements Generator { protected final Factory factory; private boolean addGeneratedBy = false; + private Pattern pattern; - public DefaultGenerator(Factory factory) { + public DefaultGenerator(Factory factory, Pattern pattern) { super(); + this.pattern = pattern; this.factory = factory; } - @Override + /** + * Generates one target depending on kind of this {@link RootNode}, expected `expectedType` and input `parameters` + * @param node to be generated node + * @param parameters {@link ImmutableMap} + * @param expectedType defines {@link Class} of returned value + * + * @return a generate value or null + */ + public T generateSingleTarget(RootNode node, ImmutableMap parameters, Class expectedType) { + ResultHolder.Single result = new ResultHolder.Single<>(expectedType); + generateTargets(node, result, parameters); + return result.getResult(); + } + + /** + * Generates zero, one or more targets depending on kind of this {@link RootNode}, expected `expectedType` and input `parameters` + * @param node to be generated node + * @param parameters {@link ImmutableMap} + * @param expectedType defines {@link Class} of returned value + * + * @return a {@link List} of generated targets + */ + public List generateTargets(RootNode node, ImmutableMap parameters, Class expectedType) { + ResultHolder.Multiple result = new ResultHolder.Multiple<>(expectedType); + generateTargets(node, result, parameters); + return result.getResult(); + } + + + /** + * Generates zero, one or more target depending on kind of this {@link RootNode}, expected `result` and input `parameters` + * @param node to be generated node + * @param result the holder which receives the generated node + * @param parameters the input parameters + */ public void generateTargets(RootNode node, ResultHolder result, ImmutableMap parameters) { node.generateTargets(this, result, parameters); if (node.isSimplifyGenerated()) { @@ -70,7 +115,12 @@ public void generateTargets(RootNode node, ResultHolder result, Immutable } } - @Override + /** + * Returns zero, one or more values into `result`. The value comes from `parameters` from the location defined by `parameterInfo` + * @param parameterInfo the {@link ParameterInfo}, which describes exact parameter from `parameters` + * @param result the holder which receives the generated node + * @param parameters the input parameters + */ public void getValueAs(ParameterInfo parameterInfo, ResultHolder result, ImmutableMap parameters) { parameterInfo.getValueAs(factory, result, parameters); } @@ -80,27 +130,22 @@ public Factory getFactory() { return factory; } - public boolean isAddGeneratedBy() { - return addGeneratedBy; - } - public DefaultGenerator setAddGeneratedBy(boolean addGeneratedBy) { this.addGeneratedBy = addGeneratedBy; return this; } @Override - public void applyGeneratedBy(CtElement generatedElement, CtElement templateElement) { + public void applyGeneratedBy(CtElement generatedElement, String genBy) { if (isAddGeneratedBy() && generatedElement instanceof CtTypeMember) { - String genBy = getGeneratedByComment(templateElement); if (genBy != null) { addGeneratedByComment(generatedElement, genBy); } } } - private String getGeneratedByComment(CtElement ele) { + public String getGeneratedByComment(CtElement ele) { SourcePosition pos = ele.getPosition(); - if (pos != null) { + if (pos != null && pos.isValidPosition()) { CompilationUnit cu = pos.getCompilationUnit(); if (cu != null) { CtType mainType = cu.getMainType(); @@ -172,4 +217,68 @@ private CtComment getJavaDoc(CtElement ele) { return c; } + @Override + public List substitute(Class valueType, ImmutableMap params) { + return setAddGeneratedBy(isAddGeneratedBy()).generateTargets(pattern.getModelValueResolver(), params, valueType); + } + + @Override + public List substitute(Class valueType, Map params) { + return substitute(valueType, new ImmutableMapImpl(params)); + } + + @Override + public > T createType(String typeQualifiedName, Map params) { + CtTypeReference newTypeRef = factory.Type().createReference(typeQualifiedName); + CtPackage ownerPackage = newTypeRef.getFactory().Package().getOrCreate(newTypeRef.getPackage().getQualifiedName()); + return createType(ownerPackage, newTypeRef.getSimpleName(), params); + } + + /** + * Generates type in the package `ownerPackage` with simple name `typeSimpleName` using this {@link GeneratorImpl} and provided `params` + * + * Note: the root of pattern element must be one or more types. + * + * @param ownerPackage the target package + * @param typeSimpleName the simple name of future generated type + * @param params the pattern parameters + * @return the generated type + */ + @SuppressWarnings("unchecked") + private > T createType(CtPackage ownerPackage, String typeSimpleName, Map params) { + @SuppressWarnings({ "rawtypes" }) + List types = substitute(CtType.class, new ImmutableMapImpl(params, + PatternBuilder.TARGET_TYPE, ownerPackage.getFactory().Type().createReference(getQualifiedName(ownerPackage, typeSimpleName)))); + T result = null; + for (CtType type : types) { + ownerPackage.addType(type); + if (type.getSimpleName().equals(typeSimpleName)) { + result = (T) type; + } + } + return result; + } + + @Override + public List addToType(Class valueType, Map params, CtType targetType) { + List results = substitute(valueType, new ImmutableMapImpl(params, PatternBuilder.TARGET_TYPE, targetType.getReference())); + for (T result : results) { + if (result instanceof CtTypeMember) { + targetType.addTypeMember((CtTypeMember) result); + } + } + return results; + } + + private static String getQualifiedName(CtPackage pckg, String simpleName) { + if (pckg.isUnnamedPackage()) { + return simpleName; + } + return pckg.getQualifiedName() + CtPackage.PACKAGE_SEPARATOR + simpleName; + } + + public boolean isAddGeneratedBy() { + return addGeneratedBy; + } + } diff --git a/src/main/java/spoon/pattern/internal/Generator.java b/src/main/java/spoon/pattern/internal/Generator.java deleted file mode 100644 index 0dd4432db5a..00000000000 --- a/src/main/java/spoon/pattern/internal/Generator.java +++ /dev/null @@ -1,86 +0,0 @@ -/** - * 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.pattern.internal; - -import java.util.List; - -import spoon.pattern.internal.node.RootNode; -import spoon.pattern.internal.parameter.ParameterInfo; -import spoon.reflect.declaration.CtElement; -import spoon.reflect.factory.Factory; -import spoon.support.util.ImmutableMap; - -/** - * Drives generation process - */ -public interface Generator { - /** - * @return a {@link Factory}, which has to be used to generate instances - */ - Factory getFactory(); - - /** - * Adds a Generated by comment to the javadoc of generatedElement - * @param generatedElement a newly generated element - * @param templateElement a source template element, which was used to generate `generatedElement` - */ - void applyGeneratedBy(CtElement generatedElement, CtElement templateElement); - - /** - * Generates zero, one or more target depending on kind of this {@link RootNode}, expected `result` and input `parameters` - * @param node to be generated node - * @param result the holder which receives the generated node - * @param parameters the input parameters - */ - void generateTargets(RootNode node, ResultHolder result, ImmutableMap parameters); - - /** - * Returns zero, one or more values into `result`. The value comes from `parameters` from the location defined by `parameterInfo` - * @param parameterInfo the {@link ParameterInfo}, which describes exact parameter from `parameters` - * @param result the holder which receives the generated node - * @param parameters the input parameters - */ - void getValueAs(ParameterInfo parameterInfo, ResultHolder result, ImmutableMap parameters); - - /** - * Generates one target depending on kind of this {@link RootNode}, expected `expectedType` and input `parameters` - * @param node to be generated node - * @param parameters {@link ImmutableMap} - * @param expectedType defines {@link Class} of returned value - * - * @return a generate value or null - */ - default T generateSingleTarget(RootNode node, ImmutableMap parameters, Class expectedType) { - ResultHolder.Single result = new ResultHolder.Single<>(expectedType); - generateTargets(node, result, parameters); - return result.getResult(); - } - - /** - * Generates zero, one or more targets depending on kind of this {@link RootNode}, expected `expectedType` and input `parameters` - * @param node to be generated node - * @param parameters {@link ImmutableMap} - * @param expectedType defines {@link Class} of returned value - * - * @return a {@link List} of generated targets - */ - default List generateTargets(RootNode node, ImmutableMap parameters, Class expectedType) { - ResultHolder.Multiple result = new ResultHolder.Multiple<>(expectedType); - generateTargets(node, result, parameters); - return result.getResult(); - } -} diff --git a/src/main/java/spoon/pattern/internal/PatternPrinter.java b/src/main/java/spoon/pattern/internal/PatternPrinter.java index 53b2b51070e..2a007eb59ab 100644 --- a/src/main/java/spoon/pattern/internal/PatternPrinter.java +++ b/src/main/java/spoon/pattern/internal/PatternPrinter.java @@ -54,7 +54,7 @@ public class PatternPrinter extends DefaultGenerator { private List params = new ArrayList<>(); public PatternPrinter() { - super(DEFAULT_FACTORY); + super(DEFAULT_FACTORY, null); } public String printNode(RootNode node) { diff --git a/src/main/java/spoon/pattern/internal/node/ConstantNode.java b/src/main/java/spoon/pattern/internal/node/ConstantNode.java index 78c68b35a12..40696ff73ee 100644 --- a/src/main/java/spoon/pattern/internal/node/ConstantNode.java +++ b/src/main/java/spoon/pattern/internal/node/ConstantNode.java @@ -19,7 +19,8 @@ import java.util.function.BiConsumer; import spoon.pattern.Quantifier; -import spoon.pattern.internal.Generator; +import spoon.pattern.Generator; +import spoon.pattern.internal.DefaultGenerator; import spoon.pattern.internal.ResultHolder; import spoon.pattern.internal.parameter.ParameterInfo; import spoon.support.util.ImmutableMap; @@ -50,7 +51,7 @@ public void forEachParameterInfo(BiConsumer consumer) { } @Override - public void generateTargets(Generator generator, ResultHolder result, ImmutableMap parameters) { + public void generateTargets(DefaultGenerator generator, ResultHolder result, ImmutableMap parameters) { result.addResult((U) template); } diff --git a/src/main/java/spoon/pattern/internal/node/ElementNode.java b/src/main/java/spoon/pattern/internal/node/ElementNode.java index 95ed02b2f39..e7e619d450b 100644 --- a/src/main/java/spoon/pattern/internal/node/ElementNode.java +++ b/src/main/java/spoon/pattern/internal/node/ElementNode.java @@ -32,7 +32,8 @@ import spoon.Metamodel; import spoon.SpoonException; import spoon.pattern.Quantifier; -import spoon.pattern.internal.Generator; +import spoon.pattern.Generator; +import spoon.pattern.internal.DefaultGenerator; import spoon.pattern.internal.ResultHolder; import spoon.pattern.internal.matcher.Matchers; import spoon.pattern.internal.matcher.TobeMatched; @@ -273,15 +274,15 @@ public void forEachParameterInfo(BiConsumer consumer) { @SuppressWarnings("unchecked") @Override - public void generateTargets(Generator generator, ResultHolder result, ImmutableMap parameters) { + public void generateTargets(DefaultGenerator generator, ResultHolder result, ImmutableMap parameters) { //TODO implement create on Metamodel.Type CtElement clone = generator.getFactory().Core().create(elementType.getModelInterface()); generateSingleNodeAttributes(generator, clone, parameters); - generator.applyGeneratedBy(clone, templateElement); + generator.applyGeneratedBy(clone, generator.getGeneratedByComment(templateElement)); result.addResult((U) clone); } - protected void generateSingleNodeAttributes(Generator generator, CtElement clone, ImmutableMap parameters) { + protected void generateSingleNodeAttributes(DefaultGenerator generator, CtElement clone, ImmutableMap parameters) { for (Map.Entry e : getRoleToNode().entrySet()) { Metamodel.Field mmField = e.getKey(); switch (mmField.getContainerKind()) { diff --git a/src/main/java/spoon/pattern/internal/node/ForEachNode.java b/src/main/java/spoon/pattern/internal/node/ForEachNode.java index fd086b8ffbf..43188f52912 100644 --- a/src/main/java/spoon/pattern/internal/node/ForEachNode.java +++ b/src/main/java/spoon/pattern/internal/node/ForEachNode.java @@ -20,7 +20,8 @@ import java.util.function.BiConsumer; import spoon.pattern.Quantifier; -import spoon.pattern.internal.Generator; +import spoon.pattern.Generator; +import spoon.pattern.internal.DefaultGenerator; import spoon.pattern.internal.ResultHolder; import spoon.pattern.internal.matcher.TobeMatched; import spoon.pattern.internal.parameter.ParameterInfo; @@ -71,7 +72,7 @@ public boolean replaceNode(RootNode oldNode, RootNode newNode) { } @Override - public void generateTargets(Generator generator, ResultHolder result, ImmutableMap parameters) { + public void generateTargets(DefaultGenerator generator, ResultHolder result, ImmutableMap parameters) { for (Object parameterValue : generator.generateTargets(iterableParameter, parameters, Object.class)) { generator.generateTargets(nestedModel, result, parameters.putValue(localParameter.getName(), parameterValue)); } @@ -146,7 +147,7 @@ public boolean isTryNextMatch(ImmutableMap parameters) { } @Override - public void generateInlineTargets(Generator generator, ResultHolder result, ImmutableMap parameters) { + public void generateInlineTargets(DefaultGenerator generator, ResultHolder result, ImmutableMap parameters) { Factory f = generator.getFactory(); CtForEach forEach = f.Core().createForEach(); forEach.setVariable(f.Code().createLocalVariable(f.Type().objectType(), localParameter.getName(), null)); diff --git a/src/main/java/spoon/pattern/internal/node/InlineNode.java b/src/main/java/spoon/pattern/internal/node/InlineNode.java index 88b3556278c..bcbd506eabc 100644 --- a/src/main/java/spoon/pattern/internal/node/InlineNode.java +++ b/src/main/java/spoon/pattern/internal/node/InlineNode.java @@ -16,7 +16,8 @@ */ package spoon.pattern.internal.node; -import spoon.pattern.internal.Generator; +import spoon.pattern.Generator; +import spoon.pattern.internal.DefaultGenerator; import spoon.pattern.internal.ResultHolder; import spoon.support.util.ImmutableMap; @@ -34,5 +35,5 @@ public interface InlineNode extends RootNode { * @param result holder of the result * @param parameters a {@link ImmutableMap} with current parameters */ - void generateInlineTargets(Generator generator, ResultHolder result, ImmutableMap parameters); + void generateInlineTargets(DefaultGenerator generator, ResultHolder result, ImmutableMap parameters); } diff --git a/src/main/java/spoon/pattern/internal/node/ListOfNodes.java b/src/main/java/spoon/pattern/internal/node/ListOfNodes.java index 88a6109eda8..6a2685654ff 100644 --- a/src/main/java/spoon/pattern/internal/node/ListOfNodes.java +++ b/src/main/java/spoon/pattern/internal/node/ListOfNodes.java @@ -19,7 +19,8 @@ import java.util.List; import java.util.function.BiConsumer; -import spoon.pattern.internal.Generator; +import spoon.pattern.Generator; +import spoon.pattern.internal.DefaultGenerator; import spoon.pattern.internal.ResultHolder; import spoon.pattern.internal.matcher.ChainOfMatchersImpl; import spoon.pattern.internal.matcher.Matchers; @@ -46,7 +47,7 @@ public void forEachParameterInfo(BiConsumer consumer) { } @Override - public void generateTargets(Generator generator, ResultHolder result, ImmutableMap parameters) { + public void generateTargets(DefaultGenerator generator, ResultHolder result, ImmutableMap parameters) { for (RootNode node : nodes) { generator.generateTargets(node, result, parameters); } diff --git a/src/main/java/spoon/pattern/internal/node/MapEntryNode.java b/src/main/java/spoon/pattern/internal/node/MapEntryNode.java index 0ef0c3f59b3..130148a5ee9 100644 --- a/src/main/java/spoon/pattern/internal/node/MapEntryNode.java +++ b/src/main/java/spoon/pattern/internal/node/MapEntryNode.java @@ -23,7 +23,8 @@ import spoon.SpoonException; import spoon.pattern.Quantifier; -import spoon.pattern.internal.Generator; +import spoon.pattern.Generator; +import spoon.pattern.internal.DefaultGenerator; import spoon.pattern.internal.ResultHolder; import spoon.pattern.internal.matcher.TobeMatched; import spoon.pattern.internal.parameter.ParameterInfo; @@ -105,7 +106,7 @@ public CtElement setValue(CtElement value) { } @Override - public void generateTargets(Generator generator, ResultHolder result, ImmutableMap parameters) { + public void generateTargets(DefaultGenerator generator, ResultHolder result, ImmutableMap parameters) { String entryKey = generator.generateSingleTarget(key, parameters, String.class); CtElement entryValue = generator.generateSingleTarget(value, parameters, CtElement.class); if (entryKey != null && entryValue != null) { diff --git a/src/main/java/spoon/pattern/internal/node/ParameterNode.java b/src/main/java/spoon/pattern/internal/node/ParameterNode.java index 45d6c6052c8..9c0f3d8de3e 100644 --- a/src/main/java/spoon/pattern/internal/node/ParameterNode.java +++ b/src/main/java/spoon/pattern/internal/node/ParameterNode.java @@ -19,7 +19,8 @@ import java.util.function.BiConsumer; import spoon.pattern.Quantifier; -import spoon.pattern.internal.Generator; +import spoon.pattern.Generator; +import spoon.pattern.internal.DefaultGenerator; import spoon.pattern.internal.ResultHolder; import spoon.pattern.internal.parameter.ParameterInfo; import spoon.reflect.declaration.CtElement; @@ -44,7 +45,7 @@ public boolean replaceNode(RootNode oldNode, RootNode newNode) { } @Override - public void generateTargets(Generator generator, ResultHolder result, ImmutableMap parameters) { + public void generateTargets(DefaultGenerator generator, ResultHolder result, ImmutableMap parameters) { generator.getValueAs(parameterInfo, result, parameters); } diff --git a/src/main/java/spoon/pattern/internal/node/RootNode.java b/src/main/java/spoon/pattern/internal/node/RootNode.java index e8bb5aed27b..8b2948ded32 100644 --- a/src/main/java/spoon/pattern/internal/node/RootNode.java +++ b/src/main/java/spoon/pattern/internal/node/RootNode.java @@ -18,7 +18,8 @@ import java.util.function.BiConsumer; -import spoon.pattern.internal.Generator; +import spoon.pattern.Generator; +import spoon.pattern.internal.DefaultGenerator; import spoon.pattern.internal.ResultHolder; import spoon.pattern.internal.matcher.Matchers; import spoon.pattern.internal.matcher.TobeMatched; @@ -45,7 +46,7 @@ public interface RootNode extends Matchers { * @param result holder for the generated objects * @param parameters a {@link ImmutableMap} holding parameters */ - void generateTargets(Generator generator, ResultHolder result, ImmutableMap parameters); + void generateTargets(DefaultGenerator generator, ResultHolder result, ImmutableMap parameters); /** * @return true if generated result has to be evaluated to apply simplifications. diff --git a/src/main/java/spoon/pattern/internal/node/StringNode.java b/src/main/java/spoon/pattern/internal/node/StringNode.java index cc775ebb345..f56cafc92be 100644 --- a/src/main/java/spoon/pattern/internal/node/StringNode.java +++ b/src/main/java/spoon/pattern/internal/node/StringNode.java @@ -28,7 +28,8 @@ import spoon.SpoonException; import spoon.pattern.Quantifier; -import spoon.pattern.internal.Generator; +import spoon.pattern.Generator; +import spoon.pattern.internal.DefaultGenerator; import spoon.pattern.internal.ResultHolder; import spoon.pattern.internal.parameter.ParameterInfo; import spoon.support.util.ImmutableMap; @@ -56,7 +57,7 @@ private String getStringValueWithMarkers() { @SuppressWarnings("unchecked") @Override - public void generateTargets(Generator generator, ResultHolder result, ImmutableMap parameters) { + public void generateTargets(DefaultGenerator generator, ResultHolder result, ImmutableMap parameters) { Class requiredClass = result.getRequiredClass(); if (requiredClass != null && requiredClass.isAssignableFrom(String.class) == false) { throw new SpoonException("StringValueResolver provides only String values. It doesn't support: " + requiredClass); diff --git a/src/main/java/spoon/pattern/internal/node/SwitchNode.java b/src/main/java/spoon/pattern/internal/node/SwitchNode.java index f0a7674dfec..498dbea5bda 100644 --- a/src/main/java/spoon/pattern/internal/node/SwitchNode.java +++ b/src/main/java/spoon/pattern/internal/node/SwitchNode.java @@ -21,7 +21,8 @@ import java.util.function.BiConsumer; import spoon.SpoonException; -import spoon.pattern.internal.Generator; +import spoon.pattern.Generator; +import spoon.pattern.internal.DefaultGenerator; import spoon.pattern.internal.ResultHolder; import spoon.pattern.internal.matcher.Matchers; import spoon.pattern.internal.matcher.TobeMatched; @@ -73,7 +74,7 @@ public void addCase(PrimitiveMatcher vrOfExpression, RootNode statement) { } @Override - public void generateTargets(Generator generator, ResultHolder result, ImmutableMap parameters) { + public void generateTargets(DefaultGenerator generator, ResultHolder result, ImmutableMap parameters) { for (CaseNode case1 : cases) { generator.generateTargets(case1, result, parameters); } @@ -177,14 +178,14 @@ public void forEachParameterInfo(BiConsumer consumer) { SwitchNode.this.forEachParameterInfo(consumer); } @Override - public void generateTargets(Generator generator, ResultHolder result, ImmutableMap parameters) { + public void generateTargets(DefaultGenerator generator, ResultHolder result, ImmutableMap parameters) { if (statement != null) { if (isCaseSelected(generator, parameters)) { generator.generateTargets(statement, result, parameters); } } } - private boolean isCaseSelected(Generator generator, ImmutableMap parameters) { + private boolean isCaseSelected(DefaultGenerator generator, ImmutableMap parameters) { if (vrOfExpression == null) { return true; } @@ -193,7 +194,7 @@ private boolean isCaseSelected(Generator generator, ImmutableMap parameters) { } @Override - public void generateInlineTargets(Generator generator, ResultHolder result, ImmutableMap parameters) { + public void generateInlineTargets(DefaultGenerator generator, ResultHolder result, ImmutableMap parameters) { Factory f = generator.getFactory(); CoreFactory cf = f.Core(); CtBlock block = cf.createBlock(); @@ -214,7 +215,7 @@ public void generateInlineTargets(Generator generator, ResultHolder resul } @Override - public void generateInlineTargets(Generator generator, ResultHolder result, ImmutableMap parameters) { + public void generateInlineTargets(DefaultGenerator generator, ResultHolder result, ImmutableMap parameters) { CtStatement resultStmt = null; CtStatement lastElse = null; CtIf lastIf = null; diff --git a/src/main/java/spoon/template/Substitution.java b/src/main/java/spoon/template/Substitution.java index ebbf64730a5..9e2607eaef3 100644 --- a/src/main/java/spoon/template/Substitution.java +++ b/src/main/java/spoon/template/Substitution.java @@ -113,7 +113,7 @@ public static > T createTypeFromTemplate(String qualifiedTyp }) .build() .generator() - .createType(templateOfType.getFactory(), qualifiedTypeName, templateParameters); + .createType(qualifiedTypeName, templateParameters); } /** diff --git a/src/main/java/spoon/template/TemplateBuilder.java b/src/main/java/spoon/template/TemplateBuilder.java index 7c4ab552577..fb272b1e3bd 100644 --- a/src/main/java/spoon/template/TemplateBuilder.java +++ b/src/main/java/spoon/template/TemplateBuilder.java @@ -153,7 +153,7 @@ public Map getTemplateParameters(CtType targetType) { * @return a substituted element */ public T substituteSingle(CtType targetType, Class itemType) { - return build().generator().substituteSingle(targetType.getFactory(), itemType, getTemplateParameters(targetType)); + return build().generator().substitute(itemType, new ImmutableMapImpl(getTemplateParameters(targetType))).get(0); } /** * generates a new AST nodes made by cloning of `patternModel` and by substitution of parameters by their values @@ -162,6 +162,6 @@ public T substituteSingle(CtType targetType, Class i * @return List of substituted elements */ public List substituteList(Factory factory, CtType targetType, Class itemType) { - return build().generator().substitute(factory, itemType, new ImmutableMapImpl(getTemplateParameters(targetType))); + return build().generator().substitute(itemType, getTemplateParameters(targetType)); } } diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index cf52ce9c730..683bf00fb9c 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -245,7 +245,7 @@ public void testGenerateMultiValues() throws Exception { // created in "MatchMultiple.createPattern",matching a method "statements" params = params.putValue("statements", statementsToBeAdded); - List generated = pattern.generator().substitute(factory, CtStatement.class, params); + List generated = pattern.generator().substitute(CtStatement.class, params); assertEquals(Arrays.asList( //these statements comes from `statements` parameter value "int foo = 0", @@ -1270,7 +1270,6 @@ public void testAddGeneratedBy() throws Exception { Factory factory = templateModel.getFactory(); Pattern pattern = PatternBuilder.create(templateModel).setAddGeneratedBy(true).build(); - assertTrue(pattern.generator().isAddGeneratedBy()); } @@ -1286,7 +1285,7 @@ public void testGenerateClassWithSelfReferences() throws Exception { Pattern pattern = PatternBuilder.create(templateModel).setAddGeneratedBy(true).build(); final String newQName = "spoon.test.generated.ACloneOfAClassWithMethodsAndRefs"; - CtClass generatedType = pattern.generator().createType(factory, newQName, Collections.emptyMap()); + CtClass generatedType = pattern.generator().createType(newQName, Collections.emptyMap()); assertNotNull(generatedType); // sanity check that the new type contains all the expected methods @@ -1336,7 +1335,7 @@ public void testGenerateMethodWithSelfReferences() throws Exception { CtClass generatedType = factory.createClass("spoon.test.generated.ACloneOfAClassWithMethodsAndRefs"); - pattern.generator().applyToType(generatedType, CtMethod.class, Collections.emptyMap()); + pattern.generator().addToType(CtMethod.class, Collections.emptyMap(), generatedType); //contract: the foo method has been added assertEquals(Arrays.asList("foo"), @@ -1508,7 +1507,7 @@ public void testExtensionDecoupledSubstitutionVisitor() throws Exception { //as pattern parameters .configurePatternParameters() .build(); - final List aMethods = pattern.generator().applyToType(aTargetType, CtMethod.class, params); + final List aMethods = pattern.generator().addToType(CtMethod.class, params, aTargetType); assertEquals(1, aMethods.size()); final CtMethod aMethod = aMethods.get(0); assertTrue(aMethod.getBody().getStatement(0) instanceof CtTry); From 9ce97bdfd3191b1511da307806613297db0971f7 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Thu, 24 May 2018 23:13:13 +0200 Subject: [PATCH 111/131] up --- doc/_data/sidebar_doc.yml | 26 +++++++++++++++++-- doc/pattern.md | 5 ++++ .../pattern/internal/DefaultGenerator.java | 18 ++++++++----- .../java/spoon/template/Substitution.java | 2 +- .../java/spoon/template/TemplateBuilder.java | 4 +-- .../java/spoon/test/template/PatternTest.java | 12 ++++----- 6 files changed, 49 insertions(+), 18 deletions(-) diff --git a/doc/_data/sidebar_doc.yml b/doc/_data/sidebar_doc.yml index 5f8c2b717c5..d772723d7c7 100755 --- a/doc/_data/sidebar_doc.yml +++ b/doc/_data/sidebar_doc.yml @@ -159,13 +159,20 @@ entries: product: all version: all - - title: Matchers + - title: Matcher url: /matcher.html audience: writers, designers platform: all product: all version: all + - title: Pattern + url: /pattern.html + audience: writers, designers + platform: all + product: all + version: all + - title: Transforming source code audience: writers, designers platform: all @@ -187,13 +194,28 @@ entries: product: all version: all - - title: Templates + - title: Generating source code + audience: writers, designers + platform: all + product: all + version: all + + items: + - title: Template url: /template_definition.html audience: writers, designers platform: all product: all version: all + items: + - title: Generator + url: '/pattern.html#generator' + audience: writers, designers + platform: all + product: all + version: all + - title: Testing audience: writers, designers platform: all diff --git a/doc/pattern.md b/doc/pattern.md index 89d13d66a89..b547701a559 100644 --- a/doc/pattern.md +++ b/doc/pattern.md @@ -181,3 +181,8 @@ inline statements * `markAsInlined(CtForEach|CtIf)` - provided CtForEach or CtIf statement is understood as inline statement +## Generator + +All patterns can be used for code generation. The idea is that one calls `#generator()` on a pattern object to get a `Generator`. This class contains methods that takes as input a map of string,objects where each string key points to a pattern parameter name and each map value contains the element to be put in place of the pattern parameter. + + diff --git a/src/main/java/spoon/pattern/internal/DefaultGenerator.java b/src/main/java/spoon/pattern/internal/DefaultGenerator.java index 0a5c4e878a1..a83879d2d35 100644 --- a/src/main/java/spoon/pattern/internal/DefaultGenerator.java +++ b/src/main/java/spoon/pattern/internal/DefaultGenerator.java @@ -135,7 +135,11 @@ public DefaultGenerator setAddGeneratedBy(boolean addGeneratedBy) { return this; } - @Override + /** + * Adds a Generated by comment to the javadoc of generatedElement + * @param generatedElement a newly generated element + * @param templateElement a source template element, which was used to generate `generatedElement` + */ public void applyGeneratedBy(CtElement generatedElement, String genBy) { if (isAddGeneratedBy() && generatedElement instanceof CtTypeMember) { if (genBy != null) { @@ -218,17 +222,17 @@ private CtComment getJavaDoc(CtElement ele) { } @Override - public List substitute(Class valueType, ImmutableMap params) { + public List generate(Class valueType, ImmutableMap params) { return setAddGeneratedBy(isAddGeneratedBy()).generateTargets(pattern.getModelValueResolver(), params, valueType); } @Override - public List substitute(Class valueType, Map params) { - return substitute(valueType, new ImmutableMapImpl(params)); + public List generate(Class valueType, Map params) { + return generate(valueType, new ImmutableMapImpl(params)); } @Override - public > T createType(String typeQualifiedName, Map params) { + public > T generateType(String typeQualifiedName, Map params) { CtTypeReference newTypeRef = factory.Type().createReference(typeQualifiedName); CtPackage ownerPackage = newTypeRef.getFactory().Package().getOrCreate(newTypeRef.getPackage().getQualifiedName()); return createType(ownerPackage, newTypeRef.getSimpleName(), params); @@ -247,7 +251,7 @@ public > T createType(String typeQualifiedName, Map> T createType(CtPackage ownerPackage, String typeSimpleName, Map params) { @SuppressWarnings({ "rawtypes" }) - List types = substitute(CtType.class, new ImmutableMapImpl(params, + List types = generate(CtType.class, new ImmutableMapImpl(params, PatternBuilder.TARGET_TYPE, ownerPackage.getFactory().Type().createReference(getQualifiedName(ownerPackage, typeSimpleName)))); T result = null; for (CtType type : types) { @@ -261,7 +265,7 @@ private > T createType(CtPackage ownerPackage, String typeSi @Override public List addToType(Class valueType, Map params, CtType targetType) { - List results = substitute(valueType, new ImmutableMapImpl(params, PatternBuilder.TARGET_TYPE, targetType.getReference())); + List results = generate(valueType, new ImmutableMapImpl(params, PatternBuilder.TARGET_TYPE, targetType.getReference())); for (T result : results) { if (result instanceof CtTypeMember) { targetType.addTypeMember((CtTypeMember) result); diff --git a/src/main/java/spoon/template/Substitution.java b/src/main/java/spoon/template/Substitution.java index 9e2607eaef3..cc0a69021f7 100644 --- a/src/main/java/spoon/template/Substitution.java +++ b/src/main/java/spoon/template/Substitution.java @@ -113,7 +113,7 @@ public static > T createTypeFromTemplate(String qualifiedTyp }) .build() .generator() - .createType(qualifiedTypeName, templateParameters); + .generateType(qualifiedTypeName, templateParameters); } /** diff --git a/src/main/java/spoon/template/TemplateBuilder.java b/src/main/java/spoon/template/TemplateBuilder.java index fb272b1e3bd..5b9739a6139 100644 --- a/src/main/java/spoon/template/TemplateBuilder.java +++ b/src/main/java/spoon/template/TemplateBuilder.java @@ -153,7 +153,7 @@ public Map getTemplateParameters(CtType targetType) { * @return a substituted element */ public T substituteSingle(CtType targetType, Class itemType) { - return build().generator().substitute(itemType, new ImmutableMapImpl(getTemplateParameters(targetType))).get(0); + return build().generator().generate(itemType, new ImmutableMapImpl(getTemplateParameters(targetType))).get(0); } /** * generates a new AST nodes made by cloning of `patternModel` and by substitution of parameters by their values @@ -162,6 +162,6 @@ public T substituteSingle(CtType targetType, Class i * @return List of substituted elements */ public List substituteList(Factory factory, CtType targetType, Class itemType) { - return build().generator().substitute(itemType, getTemplateParameters(targetType)); + return build().generator().generate(itemType, getTemplateParameters(targetType)); } } diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index 683bf00fb9c..9b6fa7e428e 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -221,10 +221,10 @@ public void testMatchIfElse() throws Exception { @Test public void testGenerateMultiValues() throws Exception { // contract: the pattern parameter (in this case 'statements') - //can have type List and can be replaced by list of elements + // can have type List and can be replaced by list of elements //(in this case by list of statements) - // here, in particular, we test method "substituteList" + // here, in particular, we test method "substitute" // setup of the test @@ -245,7 +245,7 @@ public void testGenerateMultiValues() throws Exception { // created in "MatchMultiple.createPattern",matching a method "statements" params = params.putValue("statements", statementsToBeAdded); - List generated = pattern.generator().substitute(CtStatement.class, params); + List generated = pattern.generator().generate(CtStatement.class, params); assertEquals(Arrays.asList( //these statements comes from `statements` parameter value "int foo = 0", @@ -1276,7 +1276,7 @@ public void testAddGeneratedBy() throws Exception { @Test public void testGenerateClassWithSelfReferences() throws Exception { - // main contract: a class with methods and fields can be used as pattern using method #createType + // main contract: a class with methods and fields can be generated with method #createType // in particular, all the references to the origin class are replace by reference to the new class cloned class // creating a pattern from AClassWithMethodsAndRefs @@ -1285,7 +1285,7 @@ public void testGenerateClassWithSelfReferences() throws Exception { Pattern pattern = PatternBuilder.create(templateModel).setAddGeneratedBy(true).build(); final String newQName = "spoon.test.generated.ACloneOfAClassWithMethodsAndRefs"; - CtClass generatedType = pattern.generator().createType(newQName, Collections.emptyMap()); + CtClass generatedType = pattern.generator().generateType(newQName, Collections.emptyMap()); assertNotNull(generatedType); // sanity check that the new type contains all the expected methods @@ -1480,7 +1480,7 @@ private int indexOf(List list, Object o) { @Test public void testExtensionDecoupledSubstitutionVisitor() throws Exception { - //contract: all the variable references which are declared out of the pattern model are automatically considered as pattern parameters + //contract: one can add type members with Generator final Launcher launcher = new Launcher(); launcher.setArgs(new String[] {"--output-type", "nooutput" }); launcher.addInputResource("./src/test/java/spoon/test/template/testclasses/logger/Logger.java"); From 5632894febec62b9eb5b62aa9ec391ecee9611c9 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Thu, 24 May 2018 23:18:07 +0200 Subject: [PATCH 112/131] up --- src/main/java/spoon/pattern/Generator.java | 74 ++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 src/main/java/spoon/pattern/Generator.java diff --git a/src/main/java/spoon/pattern/Generator.java b/src/main/java/spoon/pattern/Generator.java new file mode 100644 index 00000000000..ac7f1b0b471 --- /dev/null +++ b/src/main/java/spoon/pattern/Generator.java @@ -0,0 +1,74 @@ +/** + * 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.pattern; + +import java.util.List; +import java.util.Map; + +import spoon.reflect.declaration.CtElement; +import spoon.reflect.declaration.CtType; +import spoon.reflect.declaration.CtTypeMember; +import spoon.reflect.factory.Factory; +import spoon.support.Experimental; +import spoon.support.util.ImmutableMap; + +/** + * Generates code from patterns. The core idea is to replace pattern parameters by objects. + */ +@Experimental +public interface Generator { + /** + * @return a {@link Factory}, which has to be used to generate instances + */ + Factory getFactory(); + + + /** + * Main method to generate a new AST made from substituting of parameters by values in `params` + * @param valueType - the expected type of returned items + * @param params - the substitution parameters + * @return List of generated elements + */ + List generate(Class valueType, Map params); + + /** Utility method that provides the same feature as {@link #substitute(Factory, Class, Map)}, but with a {@link ImmutableMap} as parameter (a Spoon elegant utility type) */ + List generate(Class valueType, ImmutableMap params); + + /** + * Adds type members (fields and methods) to `targetType`. + * + * The root elements of the pattern must be type members. + * + * @param valueType the type of generated elements + * @param params the pattern parameters + * @param targetType the existing type, which will contain the added generated {@link CtElement}s + * @return List of generated elements + */ + List addToType(Class valueType, Map params, CtType targetType); + + /** + * Generates type with qualified name `typeQualifiedName` the provided `params`. + * + * Note: the root element of pattern must be one type. + * + * @param typeQualifiedName the qualified name of to be generated type + * @param params the pattern parameters + * @return the generated type + */ + > T generateType(String typeQualifiedName, Map params); + +} From 6ed144c94ca492c626a0c3b2544517768b19aeb5 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Thu, 24 May 2018 23:22:11 +0200 Subject: [PATCH 113/131] up --- src/main/java/spoon/pattern/ConflictResolutionMode.java | 2 +- src/main/java/spoon/pattern/Quantifier.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/spoon/pattern/ConflictResolutionMode.java b/src/main/java/spoon/pattern/ConflictResolutionMode.java index 00050f81ca9..d244ec175f5 100644 --- a/src/main/java/spoon/pattern/ConflictResolutionMode.java +++ b/src/main/java/spoon/pattern/ConflictResolutionMode.java @@ -20,7 +20,7 @@ import spoon.pattern.internal.node.RootNode; /** - * Defines what happens when before explicitly added {@link RootNode} has to be replaced by another {@link RootNode} + * Defines what happens when a {@link RootNode} has to be replaced by another {@link RootNode} */ public enum ConflictResolutionMode { /** diff --git a/src/main/java/spoon/pattern/Quantifier.java b/src/main/java/spoon/pattern/Quantifier.java index 93498c89394..854bf696044 100644 --- a/src/main/java/spoon/pattern/Quantifier.java +++ b/src/main/java/spoon/pattern/Quantifier.java @@ -19,7 +19,7 @@ import spoon.pattern.internal.node.RootNode; /** - * Defines a strategy used to resolve conflict between two {@link RootNode}s + * Defines a matching strategy for pattern parameters. */ public enum Quantifier { /** @@ -31,7 +31,7 @@ public enum Quantifier { GREEDY, /** * The reluctant quantifier takes the opposite approach: It start at the beginning of the input, - * then reluctantly eat one character at a time looking for a match. + * then reluctantly eats one character at a time looking for a match. * The last thing it tries is the entire input. */ RELUCTANT, From eecce4c93f9beb95fdf6ba74005c3ddf556a5bc3 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Thu, 24 May 2018 23:46:41 +0200 Subject: [PATCH 114/131] up --- src/main/java/spoon/pattern/Quantifier.java | 2 -- .../pattern/internal/node/ConstantNode.java | 5 ++-- .../pattern/internal/node/ElementNode.java | 27 +++++++++---------- .../pattern/internal/node/ForEachNode.java | 7 +++-- .../pattern/internal/node/ListOfNodes.java | 7 +++-- .../pattern/internal/node/MapEntryNode.java | 11 ++++---- .../pattern/internal/node/ParameterNode.java | 5 ++-- .../pattern/internal/node/StringNode.java | 15 +++++------ .../pattern/internal/node/SwitchNode.java | 9 +++---- 9 files changed, 39 insertions(+), 49 deletions(-) diff --git a/src/main/java/spoon/pattern/Quantifier.java b/src/main/java/spoon/pattern/Quantifier.java index 854bf696044..9406739de54 100644 --- a/src/main/java/spoon/pattern/Quantifier.java +++ b/src/main/java/spoon/pattern/Quantifier.java @@ -16,8 +16,6 @@ */ package spoon.pattern; -import spoon.pattern.internal.node.RootNode; - /** * Defines a matching strategy for pattern parameters. */ diff --git a/src/main/java/spoon/pattern/internal/node/ConstantNode.java b/src/main/java/spoon/pattern/internal/node/ConstantNode.java index 40696ff73ee..6b832198d3e 100644 --- a/src/main/java/spoon/pattern/internal/node/ConstantNode.java +++ b/src/main/java/spoon/pattern/internal/node/ConstantNode.java @@ -16,15 +16,14 @@ */ package spoon.pattern.internal.node; -import java.util.function.BiConsumer; - import spoon.pattern.Quantifier; -import spoon.pattern.Generator; import spoon.pattern.internal.DefaultGenerator; import spoon.pattern.internal.ResultHolder; import spoon.pattern.internal.parameter.ParameterInfo; import spoon.support.util.ImmutableMap; +import java.util.function.BiConsumer; + /** * Generates/Matches a copy of single template object */ diff --git a/src/main/java/spoon/pattern/internal/node/ElementNode.java b/src/main/java/spoon/pattern/internal/node/ElementNode.java index e7e619d450b..8fafc9d4235 100644 --- a/src/main/java/spoon/pattern/internal/node/ElementNode.java +++ b/src/main/java/spoon/pattern/internal/node/ElementNode.java @@ -16,23 +16,9 @@ */ package spoon.pattern.internal.node; -import static spoon.pattern.internal.matcher.TobeMatched.getMatchedParameters; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.BiConsumer; -import java.util.stream.Collectors; - import spoon.Metamodel; import spoon.SpoonException; import spoon.pattern.Quantifier; -import spoon.pattern.Generator; import spoon.pattern.internal.DefaultGenerator; import spoon.pattern.internal.ResultHolder; import spoon.pattern.internal.matcher.Matchers; @@ -44,6 +30,19 @@ import spoon.reflect.reference.CtExecutableReference; import spoon.support.util.ImmutableMap; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; + +import static spoon.pattern.internal.matcher.TobeMatched.getMatchedParameters; + /** * Generates/Matches a copy of a single CtElement AST node with all it's children (whole AST tree of the root CtElement) */ diff --git a/src/main/java/spoon/pattern/internal/node/ForEachNode.java b/src/main/java/spoon/pattern/internal/node/ForEachNode.java index 43188f52912..7ea3a332348 100644 --- a/src/main/java/spoon/pattern/internal/node/ForEachNode.java +++ b/src/main/java/spoon/pattern/internal/node/ForEachNode.java @@ -16,11 +16,7 @@ */ package spoon.pattern.internal.node; -import java.util.Map; -import java.util.function.BiConsumer; - import spoon.pattern.Quantifier; -import spoon.pattern.Generator; import spoon.pattern.internal.DefaultGenerator; import spoon.pattern.internal.ResultHolder; import spoon.pattern.internal.matcher.TobeMatched; @@ -32,6 +28,9 @@ import spoon.reflect.factory.Factory; import spoon.support.util.ImmutableMap; +import java.util.Map; +import java.util.function.BiConsumer; + /** * Pattern node of multiple occurrences of the same model, just with different parameters. * Example with three occurrences of model `System.out.println(_x_)`, with parameter `_x_` diff --git a/src/main/java/spoon/pattern/internal/node/ListOfNodes.java b/src/main/java/spoon/pattern/internal/node/ListOfNodes.java index 6a2685654ff..71276f02439 100644 --- a/src/main/java/spoon/pattern/internal/node/ListOfNodes.java +++ b/src/main/java/spoon/pattern/internal/node/ListOfNodes.java @@ -16,10 +16,6 @@ */ package spoon.pattern.internal.node; -import java.util.List; -import java.util.function.BiConsumer; - -import spoon.pattern.Generator; import spoon.pattern.internal.DefaultGenerator; import spoon.pattern.internal.ResultHolder; import spoon.pattern.internal.matcher.ChainOfMatchersImpl; @@ -28,6 +24,9 @@ import spoon.pattern.internal.parameter.ParameterInfo; import spoon.support.util.ImmutableMap; +import java.util.List; +import java.util.function.BiConsumer; + /** * List of {@link RootNode}s. The {@link RootNode}s are processed in same order like they were inserted in the list */ diff --git a/src/main/java/spoon/pattern/internal/node/MapEntryNode.java b/src/main/java/spoon/pattern/internal/node/MapEntryNode.java index 130148a5ee9..dbdeb8298d5 100644 --- a/src/main/java/spoon/pattern/internal/node/MapEntryNode.java +++ b/src/main/java/spoon/pattern/internal/node/MapEntryNode.java @@ -16,14 +16,8 @@ */ package spoon.pattern.internal.node; -import static spoon.pattern.internal.matcher.TobeMatched.getMatchedParameters; - -import java.util.Map; -import java.util.function.BiConsumer; - import spoon.SpoonException; import spoon.pattern.Quantifier; -import spoon.pattern.Generator; import spoon.pattern.internal.DefaultGenerator; import spoon.pattern.internal.ResultHolder; import spoon.pattern.internal.matcher.TobeMatched; @@ -32,6 +26,11 @@ import spoon.reflect.meta.ContainerKind; import spoon.support.util.ImmutableMap; +import java.util.Map; +import java.util.function.BiConsumer; + +import static spoon.pattern.internal.matcher.TobeMatched.getMatchedParameters; + /** * Represents a ValueResolver of one Map.Entry */ diff --git a/src/main/java/spoon/pattern/internal/node/ParameterNode.java b/src/main/java/spoon/pattern/internal/node/ParameterNode.java index 9c0f3d8de3e..6a70e89595e 100644 --- a/src/main/java/spoon/pattern/internal/node/ParameterNode.java +++ b/src/main/java/spoon/pattern/internal/node/ParameterNode.java @@ -16,16 +16,15 @@ */ package spoon.pattern.internal.node; -import java.util.function.BiConsumer; - import spoon.pattern.Quantifier; -import spoon.pattern.Generator; import spoon.pattern.internal.DefaultGenerator; import spoon.pattern.internal.ResultHolder; import spoon.pattern.internal.parameter.ParameterInfo; import spoon.reflect.declaration.CtElement; import spoon.support.util.ImmutableMap; +import java.util.function.BiConsumer; + /** * Represents pattern model variable * Delivers/Matches 0, 1 or more values of defined parameter. diff --git a/src/main/java/spoon/pattern/internal/node/StringNode.java b/src/main/java/spoon/pattern/internal/node/StringNode.java index f56cafc92be..34541558867 100644 --- a/src/main/java/spoon/pattern/internal/node/StringNode.java +++ b/src/main/java/spoon/pattern/internal/node/StringNode.java @@ -16,6 +16,13 @@ */ package spoon.pattern.internal.node; +import spoon.SpoonException; +import spoon.pattern.Quantifier; +import spoon.pattern.internal.DefaultGenerator; +import spoon.pattern.internal.ResultHolder; +import spoon.pattern.internal.parameter.ParameterInfo; +import spoon.support.util.ImmutableMap; + import java.util.ArrayList; import java.util.Collections; import java.util.IdentityHashMap; @@ -26,14 +33,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import spoon.SpoonException; -import spoon.pattern.Quantifier; -import spoon.pattern.Generator; -import spoon.pattern.internal.DefaultGenerator; -import spoon.pattern.internal.ResultHolder; -import spoon.pattern.internal.parameter.ParameterInfo; -import spoon.support.util.ImmutableMap; - /** * Delivers single String value, which is created by replacing string markers in constant String template * by String value of appropriate parameter. diff --git a/src/main/java/spoon/pattern/internal/node/SwitchNode.java b/src/main/java/spoon/pattern/internal/node/SwitchNode.java index 498dbea5bda..8650bec6a8a 100644 --- a/src/main/java/spoon/pattern/internal/node/SwitchNode.java +++ b/src/main/java/spoon/pattern/internal/node/SwitchNode.java @@ -16,12 +16,7 @@ */ package spoon.pattern.internal.node; -import java.util.ArrayList; -import java.util.List; -import java.util.function.BiConsumer; - import spoon.SpoonException; -import spoon.pattern.Generator; import spoon.pattern.internal.DefaultGenerator; import spoon.pattern.internal.ResultHolder; import spoon.pattern.internal.matcher.Matchers; @@ -35,6 +30,10 @@ import spoon.reflect.factory.Factory; import spoon.support.util.ImmutableMap; +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiConsumer; + /** * List of conditional cases * {code} From 585a4df63553a7c9c963e40386b23e08bdc7d5e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Fri, 25 May 2018 20:42:45 +0200 Subject: [PATCH 115/131] remove ModelNode, and Pattern#getModelValueResolver --- src/main/java/spoon/pattern/Pattern.java | 18 +-- .../java/spoon/pattern/PatternBuilder.java | 9 +- .../pattern/PatternParameterConfigurator.java | 6 +- .../pattern/internal/DefaultGenerator.java | 10 +- .../internal/matcher/MatchingScanner.java | 6 +- .../pattern/internal/node/ModelNode.java | 117 ------------------ .../java/spoon/template/TemplateBuilder.java | 25 +++- .../java/spoon/template/TemplateMatcher.java | 6 +- 8 files changed, 44 insertions(+), 153 deletions(-) delete mode 100644 src/main/java/spoon/pattern/internal/node/ModelNode.java diff --git a/src/main/java/spoon/pattern/Pattern.java b/src/main/java/spoon/pattern/Pattern.java index 46e52f438f7..14978e98157 100644 --- a/src/main/java/spoon/pattern/Pattern.java +++ b/src/main/java/spoon/pattern/Pattern.java @@ -27,7 +27,7 @@ import spoon.SpoonException; import spoon.pattern.internal.DefaultGenerator; import spoon.pattern.internal.matcher.MatchingScanner; -import spoon.pattern.internal.node.ModelNode; +import spoon.pattern.internal.node.ListOfNodes; import spoon.pattern.internal.parameter.ParameterInfo; import spoon.reflect.declaration.CtElement; import spoon.reflect.factory.Factory; @@ -53,25 +53,15 @@ */ @Experimental public class Pattern { - private ModelNode modelValueResolver; + private ListOfNodes modelValueResolver; private boolean addGeneratedBy = false; private final Factory factory; /** package-protected, must use {@link PatternBuilder} */ - Pattern(Factory factory, ModelNode modelValueResolver) { + Pattern(Factory factory, ListOfNodes modelValueResolver) { this.modelValueResolver = modelValueResolver; this.factory = factory; } - /** - * - * Not in the public API - * - * @return a {@link ModelNode} of this pattern - */ - public ModelNode getModelValueResolver() { - return modelValueResolver; - } - /** * @return Map of parameter names to {@link ParameterInfo} for each parameter of this {@link Pattern} */ @@ -95,7 +85,7 @@ public Map getParameterInfos() { * @return a {@link GeneratorImpl}, which can be used to generate a code based on this {@link Pattern} */ public Generator generator() { - return new DefaultGenerator(factory, this).setAddGeneratedBy(addGeneratedBy); + return new DefaultGenerator(factory, modelValueResolver).setAddGeneratedBy(addGeneratedBy); } /** diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index 974650e2836..67434af1f44 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -33,7 +33,6 @@ import spoon.pattern.internal.ValueConvertorImpl; import spoon.pattern.internal.node.ElementNode; import spoon.pattern.internal.node.ListOfNodes; -import spoon.pattern.internal.node.ModelNode; import spoon.pattern.internal.node.RootNode; import spoon.pattern.internal.parameter.AbstractParameterInfo; import spoon.pattern.internal.parameter.ParameterInfo; @@ -79,13 +78,13 @@ public static PatternBuilder create(CtElement... elems) { } private final List patternModel; - private final ListOfNodes patternNodes; + protected final ListOfNodes patternNodes; private final Map patternElementToSubstRequests = new IdentityHashMap<>(); private final Set explicitNodes = Collections.newSetFromMap(new IdentityHashMap<>()); private CtTypeReference templateTypeRef; private final Map parameterInfos = new HashMap<>(); -// ModelNode pattern; +// ListOfNodes pattern; CtQueryable patternQuery; private ValueConvertor valueConvertor; private boolean addGeneratedBy = false; @@ -113,7 +112,7 @@ public CtQuery map(CtConsumableFunction queryStep) { } } - private PatternBuilder(List template) { + protected PatternBuilder(List template) { this.templateTypeRef = getDeclaringTypeRef(template); this.patternModel = Collections.unmodifiableList(new ArrayList<>(template)); if (template == null) { @@ -320,7 +319,7 @@ public Pattern build() { built = true; //clean the mapping so it is not possible to further modify built pattern using this builder patternElementToSubstRequests.clear(); - return new Pattern(getFactory(), new ModelNode(patternNodes.getNodes())).setAddGeneratedBy(isAddGeneratedBy()); + return new Pattern(getFactory(), new ListOfNodes(patternNodes.getNodes())).setAddGeneratedBy(isAddGeneratedBy()); } static List bodyToStatements(CtStatement statementOrBlock) { diff --git a/src/main/java/spoon/pattern/PatternParameterConfigurator.java b/src/main/java/spoon/pattern/PatternParameterConfigurator.java index 1904c82a64d..32a0e6af4a7 100644 --- a/src/main/java/spoon/pattern/PatternParameterConfigurator.java +++ b/src/main/java/spoon/pattern/PatternParameterConfigurator.java @@ -27,7 +27,7 @@ import spoon.pattern.internal.ValueConvertor; import spoon.pattern.internal.node.ListOfNodes; import spoon.pattern.internal.node.MapEntryNode; -import spoon.pattern.internal.node.ModelNode; +import spoon.pattern.internal.node.ListOfNodes; import spoon.pattern.internal.node.ParameterNode; import spoon.pattern.internal.node.RootNode; import spoon.pattern.internal.node.StringNode; @@ -798,14 +798,14 @@ void addSubstitutionRequest(ParameterInfo parameter, CtElement element) { } /** - * Adds request to substitute value of `attributeRole` of `element`, by the value of this {@link ModelNode} parameter {@link ParameterInfo} value + * Adds request to substitute value of `attributeRole` of `element`, by the value of this {@link ListOfNodes} parameter {@link ParameterInfo} value * @param element whose attribute of {@link CtRole} `attributeRole` have to be replaced */ void addSubstitutionRequest(ParameterInfo parameter, CtElement element, CtRole attributeRole) { patternBuilder.setNodeOfAttributeOfElement(element, attributeRole, new ParameterNode(parameter), conflictResolutionMode); } /** - * Adds request to substitute substring of {@link String} value of `attributeRole` of `element`, by the value of this {@link ModelNode} parameter {@link ParameterInfo} value + * Adds request to substitute substring of {@link String} value of `attributeRole` of `element`, by the value of this {@link ListOfNodes} parameter {@link ParameterInfo} value * @param element whose part of String attribute of {@link CtRole} `attributeRole` have to be replaced */ void addSubstitutionRequest(ParameterInfo parameter, CtElement element, CtRole attributeRole, String subStringMarker) { diff --git a/src/main/java/spoon/pattern/internal/DefaultGenerator.java b/src/main/java/spoon/pattern/internal/DefaultGenerator.java index a83879d2d35..66658974dfd 100644 --- a/src/main/java/spoon/pattern/internal/DefaultGenerator.java +++ b/src/main/java/spoon/pattern/internal/DefaultGenerator.java @@ -17,8 +17,8 @@ package spoon.pattern.internal; import spoon.pattern.Generator; -import spoon.pattern.Pattern; import spoon.pattern.PatternBuilder; +import spoon.pattern.internal.node.ListOfNodes; import spoon.pattern.internal.node.RootNode; import spoon.pattern.internal.parameter.ParameterInfo; import spoon.reflect.code.CtCodeElement; @@ -44,11 +44,11 @@ public class DefaultGenerator implements Generator { protected final Factory factory; private boolean addGeneratedBy = false; - private Pattern pattern; + private ListOfNodes nodes; - public DefaultGenerator(Factory factory, Pattern pattern) { + public DefaultGenerator(Factory factory, ListOfNodes nodes) { super(); - this.pattern = pattern; + this.nodes = nodes; this.factory = factory; } @@ -223,7 +223,7 @@ private CtComment getJavaDoc(CtElement ele) { @Override public List generate(Class valueType, ImmutableMap params) { - return setAddGeneratedBy(isAddGeneratedBy()).generateTargets(pattern.getModelValueResolver(), params, valueType); + return setAddGeneratedBy(isAddGeneratedBy()).generateTargets(nodes, params, valueType); } @Override diff --git a/src/main/java/spoon/pattern/internal/matcher/MatchingScanner.java b/src/main/java/spoon/pattern/internal/matcher/MatchingScanner.java index b1de88d4655..6f5a6f9ab1a 100644 --- a/src/main/java/spoon/pattern/internal/matcher/MatchingScanner.java +++ b/src/main/java/spoon/pattern/internal/matcher/MatchingScanner.java @@ -24,7 +24,7 @@ import spoon.SpoonException; import spoon.pattern.Match; -import spoon.pattern.internal.node.ModelNode; +import spoon.pattern.internal.node.ListOfNodes; import spoon.reflect.declaration.CtElement; import spoon.reflect.meta.ContainerKind; import spoon.reflect.path.CtRole; @@ -36,10 +36,10 @@ * Represents a Match of TemplateMatcher */ public class MatchingScanner extends EarlyTerminatingScanner { - private final ModelNode pattern; + private final ListOfNodes pattern; private CtConsumer matchConsumer; - public MatchingScanner(ModelNode pattern, CtConsumer matchConsumer) { + public MatchingScanner(ListOfNodes pattern, CtConsumer matchConsumer) { this.pattern = pattern; this.matchConsumer = matchConsumer; } diff --git a/src/main/java/spoon/pattern/internal/node/ModelNode.java b/src/main/java/spoon/pattern/internal/node/ModelNode.java deleted file mode 100644 index 95382ef2745..00000000000 --- a/src/main/java/spoon/pattern/internal/node/ModelNode.java +++ /dev/null @@ -1,117 +0,0 @@ -/** - * 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.pattern.internal.node; - -import java.util.List; - -import spoon.pattern.PatternBuilder; - -/** - * The AST model based parameterized model, which can generate or match other AST models. - * - * The instance is created by {@link PatternBuilder} - */ -public class ModelNode extends ListOfNodes { - - public ModelNode(List nodes) { - super(nodes); - } - -/* - private static class SubstReqOnPosition { - final int sourceStart; - final CtElement sourceElement; - final Node valueResolver; - SubstReqOnPosition(CtElement sourceElement, Node substReq) { - this.sourceElement = sourceElement; - this.sourceStart = getSourceStart(sourceElement); - this.valueResolver = substReq; - } - - @Override - public String toString() { - return String.valueOf(sourceStart) + ":" + valueResolver; - } - } - - - @Override - public String toString() { - Factory f = getFactory(); - Environment env = f.getEnvironment(); - final List allRequestWithSourcePos = new ArrayList<>(); - for (Map.Entry e : patternElementToSubstRequests.entrySet()) { - allRequestWithSourcePos.add(new SubstReqOnPosition(e.getKey(), e.getValue())); - } - allRequestWithSourcePos.sort((a, b) -> a.sourceStart - b.sourceStart); - class Iter { - int off = 0; - List getAndRemoveRequestUntil(int sourcePos) { - List res = new ArrayList<>(); - while (off < allRequestWithSourcePos.size() && allRequestWithSourcePos.get(off).sourceStart <= sourcePos) { - res.add(allRequestWithSourcePos.get(off)); - off++; - } - return res; - } - } - Iter iter = new Iter(); -// PrinterHelper printerHelper = new PrinterHelper(env); -// DefaultTokenWriter tokenWriter = new DefaultTokenWriter(printerHelper); - DefaultJavaPrettyPrinter printer = new DefaultJavaPrettyPrinter(env) { - protected void enter(CtElement e) { - int sourceStart = getSourceStart(e); - List requestOnPos = iter.getAndRemoveRequestUntil(sourceStart); - if (requestOnPos.size() > 0) { - getPrinterTokenWriter() - .writeComment(f.createComment(getSubstitutionRequestsDescription(e, sourceStart, requestOnPos), CommentType.BLOCK)) - .writeln(); - } - } - - }; - try { - for (CtElement ele : patternModel) { - printer.computeImports(ele); - } - for (CtElement ele : patternModel) { - printer.scan(ele); - } - } catch (ParentNotInitializedException ignore) { - return "Failed with: " + ignore.toString(); - } - // in line-preservation mode, newlines are added at the beginning to matches the lines - // removing them from the toString() representation - return printer.toString().replaceFirst("^\\s+", ""); - } - - private static int getSourceStart(CtElement ele) { - while (true) { - SourcePosition sp = ele.getPosition(); - if (sp != null && sp.getSourceStart() >= 0) { - //we have found a element with source position - return sp.getSourceStart(); - } - if (ele.isParentInitialized() == false) { - return -1; - } - ele = ele.getParent(); - } - } - -*/ -} diff --git a/src/main/java/spoon/template/TemplateBuilder.java b/src/main/java/spoon/template/TemplateBuilder.java index 5b9739a6139..575565c3700 100644 --- a/src/main/java/spoon/template/TemplateBuilder.java +++ b/src/main/java/spoon/template/TemplateBuilder.java @@ -17,13 +17,15 @@ package spoon.template; +import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.function.Consumer; import spoon.SpoonException; import spoon.pattern.Pattern; -import spoon.pattern.PatternBuilder; import spoon.pattern.PatternBuilderHelper; +import spoon.pattern.internal.node.ListOfNodes; import spoon.reflect.declaration.CtClass; import spoon.reflect.declaration.CtElement; import spoon.reflect.declaration.CtField; @@ -49,6 +51,18 @@ public static TemplateBuilder createPattern(CtElement templateRoot, Template CtClass> templateType = Substitution.getTemplateCtClass(templateRoot.getFactory(), template); return createPattern(templateRoot, templateType, template); } + //needed to provide access to protected members + private static class PatternBuilder extends spoon.pattern.PatternBuilder { + + PatternBuilder(List template) { + super(template); + } + + ListOfNodes getListOfNodes() { + return new ListOfNodes(patternNodes.getNodes()); + } + } + /** * Creates a {@link TemplateBuilder}, which builds {@link Pattern} from {@link Template} * @param templateRoot the root element of {@link Template} model @@ -91,9 +105,9 @@ public static TemplateBuilder createPattern(CtElement templateRoot, CtClass t //remove `... extends Template`, which doesn't have to be part of pattern model tv.removeSuperClass(); }; - pb = PatternBuilder.create(tv.getPatternElements()); + pb = new PatternBuilder(tv.getPatternElements()); } else { - pb = PatternBuilder.create(templateRoot); + pb = new PatternBuilder(Collections.singletonList(templateRoot)); } Map templateParameters = template == null ? null : Parameters.getTemplateParametersAsMap(f, null, template); //legacy templates always automatically simplifies generated code @@ -123,6 +137,11 @@ public Pattern build() { return patternBuilder.build(); } + Pattern build(Consumer nodes) { + nodes.accept(patternBuilder.getListOfNodes()); + return build(); + } + /** * @param addGeneratedBy true if "generated by" comments has to be added into code generated by {@link Pattern} made by this {@link TemplateBuilder} * @return this to support fluent API diff --git a/src/main/java/spoon/template/TemplateMatcher.java b/src/main/java/spoon/template/TemplateMatcher.java index e980c3e5b2e..de0bd673ef9 100644 --- a/src/main/java/spoon/template/TemplateMatcher.java +++ b/src/main/java/spoon/template/TemplateMatcher.java @@ -23,7 +23,7 @@ import spoon.pattern.Match; import spoon.pattern.Pattern; import spoon.pattern.internal.matcher.TobeMatched; -import spoon.pattern.internal.node.ModelNode; +import spoon.pattern.internal.node.ListOfNodes; import spoon.reflect.declaration.CtClass; import spoon.reflect.declaration.CtElement; import spoon.reflect.meta.ContainerKind; @@ -38,6 +38,7 @@ public class TemplateMatcher implements Filter { private final Pattern pattern; + private ListOfNodes patternModel; private final CtElement templateRoot; /** @@ -68,14 +69,13 @@ public TemplateMatcher(CtElement templateRoot) { * @param templateType the class of the template, which contains all the template parameters */ public TemplateMatcher(CtElement templateRoot, CtClass templateType) { - this.pattern = TemplateBuilder.createPattern(templateRoot, templateType, null).build(); + this.pattern = TemplateBuilder.createPattern(templateRoot, templateType, null).build(nodes -> this.patternModel = nodes); this.templateRoot = templateRoot; } @Override public boolean matches(CtElement element) { //clear all matches from previous run before we start matching with `element` - ModelNode patternModel = pattern.getModelValueResolver(); if (element == templateRoot) { // This case can occur when we are scanning the entire package for example see TemplateTest#testTemplateMatcherWithWholePackage // Correct template matches itself of course, but client does not want that From b30534d110a6d9c2895a3364fe5b114abe5da5a8 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Fri, 25 May 2018 21:14:30 +0200 Subject: [PATCH 116/131] up --- src/main/java/spoon/pattern/Generator.java | 2 +- src/main/java/spoon/pattern/Pattern.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/spoon/pattern/Generator.java b/src/main/java/spoon/pattern/Generator.java index ac7f1b0b471..71a8975cbc5 100644 --- a/src/main/java/spoon/pattern/Generator.java +++ b/src/main/java/spoon/pattern/Generator.java @@ -45,7 +45,7 @@ public interface Generator { */ List generate(Class valueType, Map params); - /** Utility method that provides the same feature as {@link #substitute(Factory, Class, Map)}, but with a {@link ImmutableMap} as parameter (a Spoon elegant utility type) */ + /** Utility method that provides the same feature as {@link #generate(Class, Map)}, but with a {@link ImmutableMap} as parameter (a Spoon elegant utility type) */ List generate(Class valueType, ImmutableMap params); /** diff --git a/src/main/java/spoon/pattern/Pattern.java b/src/main/java/spoon/pattern/Pattern.java index 14978e98157..a26ef227cdf 100644 --- a/src/main/java/spoon/pattern/Pattern.java +++ b/src/main/java/spoon/pattern/Pattern.java @@ -82,7 +82,7 @@ public Map getParameterInfos() { } /** - * @return a {@link GeneratorImpl}, which can be used to generate a code based on this {@link Pattern} + * @return a {@link Generator}, which can be used to generate a code based on this {@link Pattern} */ public Generator generator() { return new DefaultGenerator(factory, modelValueResolver).setAddGeneratedBy(addGeneratedBy); From 67090bb68df457821fee1d4db96042eefc98424f Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Fri, 25 May 2018 21:29:46 +0200 Subject: [PATCH 117/131] up --- .../pattern/PatternParameterConfigurator.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/main/java/spoon/pattern/PatternParameterConfigurator.java b/src/main/java/spoon/pattern/PatternParameterConfigurator.java index 32a0e6af4a7..d6d3deafd39 100644 --- a/src/main/java/spoon/pattern/PatternParameterConfigurator.java +++ b/src/main/java/spoon/pattern/PatternParameterConfigurator.java @@ -16,18 +16,10 @@ */ package spoon.pattern; -import static spoon.pattern.PatternBuilder.getLocalTypeRefBySimpleName; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.function.Predicate; - import spoon.SpoonException; import spoon.pattern.internal.ValueConvertor; import spoon.pattern.internal.node.ListOfNodes; import spoon.pattern.internal.node.MapEntryNode; -import spoon.pattern.internal.node.ListOfNodes; import spoon.pattern.internal.node.ParameterNode; import spoon.pattern.internal.node.RootNode; import spoon.pattern.internal.node.StringNode; @@ -73,6 +65,13 @@ import spoon.template.Parameter; import spoon.template.TemplateParameter; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; + +import static spoon.pattern.PatternBuilder.getLocalTypeRefBySimpleName; + /** * Used to define pattern parameters. * From 5e547d72c96505d6970a904d5ed6018071eae215 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Fri, 25 May 2018 21:41:50 +0200 Subject: [PATCH 118/131] up --- .../java/spoon/pattern/ConflictResolutionMode.java | 10 +++++----- src/main/java/spoon/pattern/Quantifier.java | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/spoon/pattern/ConflictResolutionMode.java b/src/main/java/spoon/pattern/ConflictResolutionMode.java index d244ec175f5..2ca9174d84f 100644 --- a/src/main/java/spoon/pattern/ConflictResolutionMode.java +++ b/src/main/java/spoon/pattern/ConflictResolutionMode.java @@ -20,23 +20,23 @@ import spoon.pattern.internal.node.RootNode; /** - * Defines what happens when a {@link RootNode} has to be replaced by another {@link RootNode} + * Defines what happens when a {@link RootNode} has to be replaced by another {@link RootNode}, default in {@link #FAIL}. */ public enum ConflictResolutionMode { /** - * throw {@link SpoonException} + * Throw {@link SpoonException} if a conflict happens, it is the default in most cases. But there are some standard Pattern builder algorithms (mainly these which deals with legacy Templates), which are using the other modes. */ FAIL, /** - * get rid of old {@link RootNode} and use new {@link RootNode} instead + * Get rid of old {@link RootNode} and use new {@link RootNode} instead */ USE_NEW_NODE, /** - * keep old {@link RootNode} and ignore requests to add new {@link RootNode} + * Keep old {@link RootNode} and ignore requests to add new {@link RootNode} */ KEEP_OLD_NODE, /** - * add new {@link RootNode} after existing nodes + * Add new {@link RootNode} after existing nodes */ APPEND } diff --git a/src/main/java/spoon/pattern/Quantifier.java b/src/main/java/spoon/pattern/Quantifier.java index 9406739de54..9a8bb0e0564 100644 --- a/src/main/java/spoon/pattern/Quantifier.java +++ b/src/main/java/spoon/pattern/Quantifier.java @@ -17,12 +17,12 @@ package spoon.pattern; /** - * Defines a matching strategy for pattern parameters. + * Defines a matching strategy for pattern parameters, default is {@link #GREEDY}. */ public enum Quantifier { /** - * Greedy quantifiers are considered "greedy" because they force the matcher to read in, or eat, - * the entire input prior to attempting the next match. + * Force the matcher to read in, or eat, + * the entire input prior to attempting the next match (default). * If the next match attempt (the entire input) fails, the matcher backs off the input by one and tries again, * repeating the process until a match is found or there are no more elements left to back off from. */ From 41a7559b3277db690d1884cf3776d151411251b2 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Fri, 25 May 2018 21:59:55 +0200 Subject: [PATCH 119/131] up --- src/main/java/spoon/pattern/Generator.java | 2 +- src/main/java/spoon/pattern/internal/DefaultGenerator.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/spoon/pattern/Generator.java b/src/main/java/spoon/pattern/Generator.java index 71a8975cbc5..f4c8ba9d158 100644 --- a/src/main/java/spoon/pattern/Generator.java +++ b/src/main/java/spoon/pattern/Generator.java @@ -40,7 +40,7 @@ public interface Generator { /** * Main method to generate a new AST made from substituting of parameters by values in `params` * @param valueType - the expected type of returned items - * @param params - the substitution parameters + * @param params - the substitution parameters, it can be CtElement, primitive literals like String, Integer, ... and or List or Set of them. * @return List of generated elements */ List generate(Class valueType, Map params); diff --git a/src/main/java/spoon/pattern/internal/DefaultGenerator.java b/src/main/java/spoon/pattern/internal/DefaultGenerator.java index 66658974dfd..73f28c15960 100644 --- a/src/main/java/spoon/pattern/internal/DefaultGenerator.java +++ b/src/main/java/spoon/pattern/internal/DefaultGenerator.java @@ -138,7 +138,7 @@ public DefaultGenerator setAddGeneratedBy(boolean addGeneratedBy) { /** * Adds a Generated by comment to the javadoc of generatedElement * @param generatedElement a newly generated element - * @param templateElement a source template element, which was used to generate `generatedElement` + * @param genBy the documentation to be added */ public void applyGeneratedBy(CtElement generatedElement, String genBy) { if (isAddGeneratedBy() && generatedElement instanceof CtTypeMember) { From f0fc0293735e9b9a4cd7691a14b0395d9d139de7 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Sun, 27 May 2018 20:40:32 +0200 Subject: [PATCH 120/131] cleaning and buig fixing --- src/main/java/spoon/pattern/PatternBuilder.java | 8 -------- .../java/spoon/pattern/PatternBuilderHelper.java | 16 ++-------------- 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/src/main/java/spoon/pattern/PatternBuilder.java b/src/main/java/spoon/pattern/PatternBuilder.java index 67434af1f44..4c980be3778 100644 --- a/src/main/java/spoon/pattern/PatternBuilder.java +++ b/src/main/java/spoon/pattern/PatternBuilder.java @@ -455,14 +455,6 @@ protected Factory getFactory() { throw new SpoonException("PatternBuilder has no CtElement to provide a Factory"); } - private static void checkTemplateType(CtType type) { - if (type == null) { - throw new SpoonException("Cannot create Pattern from null Template type."); - } - if (type.isShadow()) { - throw new SpoonException("Cannot create Pattern from shadow Template type. Add sources of Template type into spoon model."); - } - } /** * @return a {@link CtElement}s which are the template model of this Pattern */ diff --git a/src/main/java/spoon/pattern/PatternBuilderHelper.java b/src/main/java/spoon/pattern/PatternBuilderHelper.java index 18c8c75a824..0547761529b 100644 --- a/src/main/java/spoon/pattern/PatternBuilderHelper.java +++ b/src/main/java/spoon/pattern/PatternBuilderHelper.java @@ -29,8 +29,8 @@ import spoon.support.Experimental; import java.util.ArrayList; +import java.util.Collections; import java.util.List; -import java.util.ListIterator; /** * Utility class to select parts of AST to be used as a model of a {@link PatternBuilder}. @@ -69,6 +69,7 @@ private CtType getClonedPatternType() { //set parent package, to keep origin qualified name of the Template. It is needed for correct substitution of Template name by target type reference clonedPatternType.setParent(patternType.getParent()); } + setElements(Collections.singletonList(clonedPatternType)); } return clonedPatternType; } @@ -90,19 +91,6 @@ private PatternBuilderHelper setTypeMember(Filter filter) { return this; } - private List getClonedElements() { - if (elements == null) { - throw new SpoonException("Template model is not defined yet"); - } - for (ListIterator iter = elements.listIterator(); iter.hasNext();) { - CtElement ele = iter.next(); - if (ele.getRoleInParent() != null) { - iter.set(ele.clone()); - } - } - return elements; - } - /** * Sets a template model from body of the method of template type * @param methodName the name of {@link CtMethod} From a15e5f807f3e366d54ae15b3702b714b2f763e51 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Sun, 27 May 2018 20:40:41 +0200 Subject: [PATCH 121/131] ExtensionTemplate uses new generator - more tests covered --- .../pattern/internal/DefaultGenerator.java | 10 +++++++++- .../java/spoon/template/ExtensionTemplate.java | 18 +++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/main/java/spoon/pattern/internal/DefaultGenerator.java b/src/main/java/spoon/pattern/internal/DefaultGenerator.java index 73f28c15960..39dc031644b 100644 --- a/src/main/java/spoon/pattern/internal/DefaultGenerator.java +++ b/src/main/java/spoon/pattern/internal/DefaultGenerator.java @@ -171,7 +171,7 @@ public String getGeneratedByComment(CtElement ele) { } private void appendInnerTypedElements(StringBuilder result, CtType mainType, CtElement ele) { CtTypeMember typeMember = getFirst(ele, CtTypeMember.class); - if (typeMember != null && typeMember != mainType) { + if (typeMember != null && isMainType(typeMember, mainType) == false) { if (typeMember.isParentInitialized()) { appendInnerTypedElements(result, mainType, typeMember.getParent()); } @@ -183,6 +183,14 @@ private void appendInnerTypedElements(StringBuilder result, CtType mainType, result.append(typeMember.getSimpleName()); } } + + private boolean isMainType(CtTypeMember tm, CtType mainType) { + if (tm instanceof CtType) { + return mainType.getQualifiedName().equals(((CtType) tm).getQualifiedName()); + } + return false; + } + @SuppressWarnings("unchecked") private T getFirst(CtElement ele, Class clazz) { if (ele != null) { diff --git a/src/main/java/spoon/template/ExtensionTemplate.java b/src/main/java/spoon/template/ExtensionTemplate.java index ec56291fe9b..972e9c9106b 100644 --- a/src/main/java/spoon/template/ExtensionTemplate.java +++ b/src/main/java/spoon/template/ExtensionTemplate.java @@ -16,7 +16,12 @@ */ package spoon.template; +import java.util.ArrayList; + +import spoon.reflect.declaration.CtClass; import spoon.reflect.declaration.CtType; +import spoon.reflect.declaration.CtTypeMember; +import spoon.reflect.reference.CtTypeReference; /** * Inserts all the methods, fields, constructors, initialization blocks (if @@ -28,7 +33,18 @@ public class ExtensionTemplate extends AbstractTemplate> { @Override public CtType apply(CtType target) { - Substitution.insertAll(target, this); + CtClass> templateType = Substitution.getTemplateCtClass(target.getFactory(), this); + CtType generated = TemplateBuilder.createPattern(templateType, templateType, this) + .setAddGeneratedBy(isAddGeneratedBy()) + .substituteSingle(target, CtType.class); + for (CtTypeReference iface : new ArrayList<>(generated.getSuperInterfaces())) { + iface.delete(); + target.addSuperInterface(iface); + } + for (CtTypeMember tm : new ArrayList(generated.getTypeMembers())) { + tm.delete(); + target.addTypeMember(tm); + } return target; } } From 60d038160fe056a6469ed34a8cb8b686d4779a01 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Tue, 29 May 2018 21:32:59 +0200 Subject: [PATCH 122/131] remove SubstitutionVisitor --- .../support/template/SubstitutionVisitor.java | 818 ------------------ 1 file changed, 818 deletions(-) delete mode 100644 src/main/java/spoon/support/template/SubstitutionVisitor.java diff --git a/src/main/java/spoon/support/template/SubstitutionVisitor.java b/src/main/java/spoon/support/template/SubstitutionVisitor.java deleted file mode 100644 index 5e2b832d818..00000000000 --- a/src/main/java/spoon/support/template/SubstitutionVisitor.java +++ /dev/null @@ -1,818 +0,0 @@ -/** - * 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.Collections; -import java.util.IdentityHashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import spoon.SpoonException; -import spoon.reflect.code.CtArrayAccess; -import spoon.reflect.code.CtBlock; -import spoon.reflect.code.CtCodeElement; -import spoon.reflect.code.CtComment; -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.CtLiteral; -import spoon.reflect.code.CtReturn; -import spoon.reflect.code.CtStatement; -import spoon.reflect.code.CtThisAccess; -import spoon.reflect.cu.CompilationUnit; -import spoon.reflect.cu.SourcePosition; -import spoon.reflect.declaration.CtElement; -import spoon.reflect.declaration.CtExecutable; -import spoon.reflect.declaration.CtNamedElement; -import spoon.reflect.declaration.CtType; -import spoon.reflect.declaration.CtTypeMember; -import spoon.reflect.factory.Factory; -import spoon.reflect.reference.CtExecutableReference; -import spoon.reflect.reference.CtFieldReference; -import spoon.reflect.reference.CtReference; -import spoon.reflect.reference.CtTypeReference; -import spoon.reflect.visitor.CtInheritanceScanner; -import spoon.reflect.visitor.CtScanner; -import spoon.support.visitor.SignaturePrinter; -import spoon.template.AbstractTemplate; -import spoon.template.Parameter; -import spoon.template.Template; -import spoon.template.TemplateParameter; - -class DoNotFurtherTemplateThisElement extends SpoonException { - private static final long serialVersionUID = 1L; - - Object skipped; - - DoNotFurtherTemplateThisElement(CtElement e) { - //Do not use e.toString(), which computes expensive String representation of whole element, - //which is sometime impossible to compute correctly in the middle of the substitution process - super("Skipping " + e.getClass().getName()); - skipped = e; - } - -} - -/** - * This visitor implements the substitution engine of Spoon templates. - */ -@Deprecated -public class SubstitutionVisitor extends CtScanner { - - private static final Object NULL_VALUE = new Object(); - private Context context; - - private class InheritanceSustitutionScanner extends CtInheritanceScanner { - - InheritanceSustitutionScanner() { - } - - @Override - public void visitCtComment(CtComment e) { - e.setContent(context.substituteName(e.getContent())); - super.visitCtComment(e); - } - - @SuppressWarnings("unchecked") - @Override - public void visitCtLiteral(CtLiteral e) { - Object value = e.getValue(); - if (value instanceof String) { - e.setValue((T) context.substituteName((String) value)); - } - super.visitCtLiteral(e); - } - - /** - * Replaces parameters in element names (even if detected as a - * substring). - */ - @Override - public void scanCtNamedElement(CtNamedElement element) { - Object value = context.getParameterValue(element.getSimpleName()); - if (value != null) { - if (value instanceof String) { - //the parameter value is a String. It is the case of substitution of the name only - //replace parameter (sub)strings in simplename - element.setSimpleName(context.substituteName(element.getSimpleName())); - } else if (value instanceof CtTypeReference && element instanceof CtType) { - //the parameter value is a type reference and the element is a type. Replace name of the type - element.setSimpleName(((CtTypeReference) value).getSimpleName()); - } else { - //this named element has to be replaced by zero one or more other elements - List values = getParameterValueAsListOfClones(element.getClass(), value); - throw context.replace(element, values); - } - } else { - //try to substitute substring of the name - element.setSimpleName(context.substituteName(element.getSimpleName())); - } - super.scanCtNamedElement(element); - } - - @Override - public void scanCtReference(CtReference reference) { - Object value = context.getParameterValue(reference.getSimpleName()); - if (value != null) { - if (reference instanceof CtTypeReference) { - /** - * Replaces type parameters and references to the template type with - * references to the target type. - */ - // replace type parameters - CtTypeReference typeReference = (CtTypeReference) reference; - boolean paramHasActualTypeArguments = value instanceof CtTypeReference; - CtTypeReference tr = getParameterValueAsTypeReference(factory, value); - if (paramHasActualTypeArguments) { - //the origin parameter has actual type arguments, apply them - typeReference.setActualTypeArguments(tr.getActualTypeArguments()); - } - typeReference.setPackage(tr.getPackage()); - typeReference.setSimpleName(tr.getSimpleName()); - typeReference.setDeclaringType(tr.getDeclaringType()); - } else { - if (value instanceof String) { - //the parameter value is a String. It is the case of substitution of the name only - //replace parameter (sub)strings in simplename - reference.setSimpleName(context.substituteName(reference.getSimpleName())); - } else { - //we have to replace the expression by another expression or statement - CtExpression expr = reference.getParent(CtExpression.class); - List values = getParameterValueAsListOfClones(CtCodeElement.class, value); - //TODO we might check consistency here, but we need to know context of the expr. Is it Statement or Expression? - //replace expression with statements or expressions - throw context.replace(expr, values); - } - } - - } else { - //try to substitute substring of the name - reference.setSimpleName(context.substituteName(reference.getSimpleName())); - } - super.scanCtReference(reference); - } - - /** statically inline foreach */ - @SuppressWarnings({ "rawtypes", "unchecked" }) - @Override - public void visitCtForEach(CtForEach foreach) { - if (foreach.getExpression() instanceof CtFieldAccess) { - CtFieldAccess fa = (CtFieldAccess) foreach.getExpression(); - Object value = context.getParameterValue(fa.getVariable().getSimpleName()); - if (value != null) { - //create local context which holds local substitution parameter - Context localContext = createContext(); - List list = getParameterValueAsListOfClones(CtExpression.class, value); - //ForEach always contains CtBlock. In some cases it is implicit. - CtBlock foreachBlock = (CtBlock) foreach.getBody(); - String newParamName = foreach.getVariable().getSimpleName(); - List newStatements = new ArrayList<>(); - for (CtExpression element : list) { - //for each item of foreach expression copy foreach body and substitute it is using local context containing new parameter - localContext.putParameter(newParamName, element); - for (CtStatement st : foreachBlock.getStatements()) { - newStatements.addAll(localContext.substitute(st.clone())); - } - } - throw context.replace(foreach, newStatements); - } - } - super.visitCtForEach(foreach); - } - - @Override - public void visitCtFieldRead(CtFieldRead fieldRead) { - visitFieldAccess(fieldRead); - } - - @Override - public void visitCtFieldWrite(CtFieldWrite fieldWrite) { - visitFieldAccess(fieldWrite); - } - - @SuppressWarnings({ "rawtypes" }) - private void visitFieldAccess(final CtFieldAccess fieldAccess) { - CtFieldReference ref = fieldAccess.getVariable(); - if ("length".equals(ref.getSimpleName())) { - if (fieldAccess.getTarget() instanceof CtFieldAccess) { - ref = ((CtFieldAccess) fieldAccess.getTarget()).getVariable(); - Object value = context.getParameterValue(ref.getSimpleName()); - if (value != null) { - //the items of this list are not cloned - List list = getParameterValueAsNewList(value); - throw context.replace(fieldAccess, (CtExpression) fieldAccess.getFactory().Code().createLiteral(list.size())); - } - } - } -// Object v = context.getParameterValue(Parameters.getParameterName(ref)); - Object v = context.getParameterValue(ref.getSimpleName()); - if (v != null) { - // replace direct field parameter accesses - Object value = getParameterValueAtIndex(Object.class, v, Parameters.getIndex(fieldAccess)); - CtExpression toReplace = fieldAccess; - if (fieldAccess.getParent() instanceof CtArrayAccess) { - toReplace = (CtExpression) fieldAccess.getParent(); - } - if (!(value instanceof TemplateParameter)) { - if (value instanceof Class) { - throw context.replace(toReplace, factory.Code() - .createClassAccess(factory.Type().createReference(((Class) value).getName()))); - } else if (value instanceof Enum) { - CtTypeReference enumType = factory.Type().createReference(value.getClass()); - CtFieldRead enumValueAccess = (CtFieldRead) factory.Code().createVariableRead( - factory.Field().createReference(enumType, enumType, ((Enum) value).name()), true); - enumValueAccess.setTarget(factory.Code().createTypeAccess(enumType)); - throw context.replace(toReplace, enumValueAccess); - } else if ((value != null) && value.getClass().isArray()) { - throw context.replace(toReplace, factory.Code().createLiteralArray((Object[]) value)); - } else if (fieldAccess == toReplace && value instanceof String) { - /* - * If the value is type String, then it is ambiguous request, because: - * A) sometime client wants to replace parameter field access by String literal - * - * @Parameter - * String field = "x" - * - * System.printLn(field) //is substitutes as: System.printLn("x") - * - * but in the case of local variables it already behaves like this - * { - * int field; - * System.printLn(field) //is substitutes as: System.printLn(x) - * } - * - * B) sometime client wants to keep field access and just substitute field name - * - * @Parameter("field") - * String fieldName = "x" - * - * System.printLn(field) //is substitutes as: System.printLn(x) - * - * ---------------------- - * - * The case B is more clear and is compatible with substitution of name of local variable, method name, etc. - * And case A can be easily modeled using this clear code - * - * @Parameter - * String field = "x" - * System.printLn("field") //is substitutes as: System.printLn("x") - * System.printLn(field) //is substitutes as: System.printLn("x") because the parameter `field` is constructed with literal value - */ - //New implementation always replaces the name of the accessed field - //so do nothing here. The string substitution is handled by #scanCtReference - } else { - throw context.replace(toReplace, factory.Code().createLiteral(value)); - } - } else { - throw context.replace(toReplace, (CtElement) value); - } - } - } - - /** - * Replaces _xx_.S(). - */ - @SuppressWarnings("unchecked") - @Override - public void visitCtInvocation(CtInvocation invocation) { - if (invocation.getExecutable().isOverriding(S)) { - CtFieldAccess fa = null; - if ((invocation.getTarget() instanceof CtFieldAccess)) { - fa = (CtFieldAccess) invocation.getTarget(); - } - if (((invocation.getTarget() instanceof CtArrayAccess) - && (((CtArrayAccess>) invocation.getTarget()) - .getTarget() instanceof CtFieldAccess))) { - fa = (CtFieldAccess) ((CtArrayAccess>) invocation.getTarget()).getTarget(); - } - if ((fa != null) && (fa.getTarget() == null || fa.getTarget() instanceof CtThisAccess)) { - CtCodeElement r = getParameterValueAtIndex(CtCodeElement.class, - context.getParameterValue(fa.getVariable().getSimpleName()), Parameters.getIndex(fa)); - List subst = null; - if (r != null) { - subst = createContext().substitute(r); - } else { - subst = Collections.emptyList(); - } - throw context.replace(invocation, subst); - } - } - super.visitCtInvocation(invocation); - } - } - - private Factory factory; - - private InheritanceSustitutionScanner inheritanceScanner; - - private CtExecutableReference S; - - private boolean addGeneratedBy = false; - - /** - * Creates new substitution visitor based on instance of Template, - * which defines template model and template parameters - * - * @param f - * the factory - * @param targetType - * the target type of the substitution (can be null) - * @param template - * the template that holds the parameter values - */ - @SuppressWarnings({ "unchecked", "rawtypes" }) - public SubstitutionVisitor(Factory f, CtType targetType, Template template) { - this(f, Parameters.getTemplateParametersAsMap(f, targetType, template)); - if (template instanceof AbstractTemplate) { - addGeneratedBy(((AbstractTemplate) template).isAddGeneratedBy()); - } - } - - /** - * Creates new substitution visitor - * with substitution model (doesn't have to implement {@link Template}) type - * and the substitution parameters (doesn't have to be bound to {@link TemplateParameter} or {@link Parameter}). - * - * @param f - * the factory - * @param templateParameters - * the parameter names and values which will be used during substitution - */ - public SubstitutionVisitor(Factory f, Map templateParameters) { - this.inheritanceScanner = new InheritanceSustitutionScanner(); - this.factory = f; - S = factory.Executable().createReference(factory.Type().createReference(TemplateParameter.class), - factory.Type().createTypeParameterReference("T"), "S"); - this.context = new Context(null).putParameters(templateParameters); - } - - /** - * @return true if the template engine ({@link SubstitutionVisitor}) adds Generated by ... comments into generated code - */ - public boolean isAddGeneratedBy() { - return addGeneratedBy; - } - - /** - * @param addGeneratedBy if true the template engine ({@link SubstitutionVisitor}) will add Generated by ... comments into generated code - */ - public SubstitutionVisitor addGeneratedBy(boolean addGeneratedBy) { - this.addGeneratedBy = addGeneratedBy; - return this; - } - - - @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) { - if (element != ignore.skipped) { - //we have to skip more - throw ignore; - } - } - } - - /** - * Substitutes all template parameters of element and returns substituted element. - * - * @param element to be substituted model - * @return substituted model - */ - public List substitute(E element) { - final Map elementToGeneratedByComment = addGeneratedBy ? new IdentityHashMap() : null; - if (addGeneratedBy) { - /* - * collect 'generated by' comments for each type member of the substituted element, before the substitution is done, - * so we know the origin names of the members. - */ - final CtInheritanceScanner internalScanner = new CtInheritanceScanner() { - public void scanCtTypeMember(CtTypeMember typeMeber) { - elementToGeneratedByComment.put(typeMeber, getGeneratedByComment(typeMeber)); - } - }; - new CtScanner() { - @Override - public void scan(CtElement p_element) { - internalScanner.scan(p_element); - super.scan(p_element); - } - }.scan(element); - } - List result = createContext().substitute(element); - if (addGeneratedBy) { - //add generated by comments after substitution, otherwise they would be substituted in comments too. - applyGeneratedByComments(elementToGeneratedByComment); - } - return result; - } - - private static String getGeneratedByComment(CtElement ele) { - SourcePosition pos = ele.getPosition(); - if (pos != null) { - CompilationUnit cu = pos.getCompilationUnit(); - if (cu != null) { - CtType mainType = cu.getMainType(); - if (mainType != null) { - StringBuilder result = new StringBuilder(); - result.append("Generated by "); - result.append(mainType.getQualifiedName()); - appendInnerTypedElements(result, mainType, ele); - result.append('('); - result.append(mainType.getSimpleName()); - result.append(".java:"); - result.append(pos.getLine()); - result.append(')'); - return result.toString(); - } - } - } - return null; - } - - private static void appendInnerTypedElements(StringBuilder result, CtType mainType, CtElement ele) { - CtTypeMember typeMember = getFirst(ele, CtTypeMember.class); - if (typeMember != null && typeMember != mainType) { - if (typeMember.isParentInitialized()) { - appendInnerTypedElements(result, mainType, typeMember.getParent()); - } - if (typeMember instanceof CtType) { - result.append('$'); - } else { - result.append('#'); - } - result.append(typeMember.getSimpleName()); - } - } - - private static void applyGeneratedByComments(Map elementToGeneratedByComment) { - for (Map.Entry e : elementToGeneratedByComment.entrySet()) { - addGeneratedByComment(e.getKey(), e.getValue()); - } - } - - private static void addGeneratedByComment(CtElement ele, String generatedBy) { - if (generatedBy == null) { - return; - } - String EOL = System.getProperty("line.separator"); - CtComment comment = getJavaDoc(ele); - String content = comment.getContent(); - if (content.trim().length() > 0) { - content += EOL + EOL; - } - content += generatedBy; - comment.setContent(content); - } - private static CtComment getJavaDoc(CtElement ele) { - for (CtComment comment : ele.getComments()) { - if (comment.getCommentType() == CtComment.CommentType.JAVADOC) { - return comment; - } - } - CtComment c = ele.getFactory().Code().createComment("", CtComment.CommentType.JAVADOC); - ele.addComment(c); - return c; - } - - @SuppressWarnings("unchecked") - private static T getFirst(CtElement ele, Class clazz) { - if (ele != null) { - if (clazz.isAssignableFrom(ele.getClass())) { - return (T) ele; - } - if (ele.isParentInitialized()) { - return getFirst(ele.getParent(), clazz); - } - } - return null; - } - - /** - * 1) Converts `parameterValue` to List using these rules - *
                - *
              • Array is converted to List . - *
              • {@link Iterable} is converted to List . - *
              • Single item is add to list. - *
              - * 2) assures that each list item has expected type `itemClass` - * 3) if itemClass is sub type of CtElement then clones it - * - * @param itemClass the type of the items of resulting list. - * If some item cannot be converted to the itemClass then {@link SpoonException} is thrown - * @param parameterValue a value of an template parameter - * @return list where each item is assured to be of type itemClass - */ - @SuppressWarnings("unchecked") - private List getParameterValueAsListOfClones(Class itemClass, Object parameterValue) { - List list = getParameterValueAsNewList(parameterValue); - for (int i = 0; i < list.size(); i++) { - list.set(i, getParameterValueAsClass(itemClass, list.get(i))); - } - return (List) list; - } - private List getParameterValueAsNewList(Object parameterValue) { - List list = new ArrayList<>(); - if (parameterValue != null) { - if (parameterValue instanceof Object[]) { - for (Object item : (Object[]) parameterValue) { - list.add(item); - } - } else if (parameterValue instanceof Iterable) { - for (Object item : (Iterable) parameterValue) { - list.add(item); - } - } else { - if (parameterValue != null && parameterValue != NULL_VALUE) { - list.add(parameterValue); - } - } - } - return list; - } - /** - * 1) Assures that parameterValue has expected type `itemClass` - * 2) if itemClass is sub type of CtElement then clones parameterValue - * - * @param itemClass required return class - * @param parameterValue a value of an template parameter - * @return parameterValue cast (in future potentially converted) to itemClass - */ - @SuppressWarnings("unchecked") - private T getParameterValueAsClass(Class itemClass, Object parameterValue) { - if (parameterValue == null || parameterValue == NULL_VALUE) { - return null; - } - - if (itemClass.isInstance(parameterValue)) { - if (CtElement.class.isAssignableFrom(itemClass)) { - /* - * the cloning is defined by itemClass and not by parameterValue, - * because there are cases when we do not want to clone parameterValue. - * In this case itemClass == Object.class - */ - parameterValue = ((CtElement) parameterValue).clone(); - } - return (T) parameterValue; - } - if (itemClass.isAssignableFrom(CtCodeElement.class)) { - if (parameterValue instanceof CtTypeReference) { - //convert type reference into code element as class access - CtTypeReference tr = (CtTypeReference) parameterValue; - return (T) factory.Code().createClassAccess(tr); - } - if (parameterValue instanceof String) { - //convert String to code element as Literal - return (T) factory.Code().createLiteral((String) parameterValue); - } - } - throw new SpoonException("Parameter value has unexpected class: " + parameterValue.getClass().getName() + ". Expected class is: " + itemClass.getName()); - } - /** - * @param parameterValue a value of an template parameter - * @return parameter value converted to String - */ - private String getParameterValueAsString(Object parameterValue) { - if (parameterValue == null) { - return null; - } - if (parameterValue instanceof String) { - return (String) parameterValue; - } else if (parameterValue instanceof CtNamedElement) { - return ((CtNamedElement) parameterValue).getSimpleName(); - } else if (parameterValue instanceof CtReference) { - return ((CtReference) parameterValue).getSimpleName(); - } else if (parameterValue instanceof Class) { - return ((Class) parameterValue).getSimpleName(); - } else if (parameterValue instanceof CtInvocation) { - return getShortSignatureForJavadoc(((CtInvocation) parameterValue).getExecutable()); - } else if (parameterValue instanceof CtExecutableReference) { - return getShortSignatureForJavadoc((CtExecutableReference) parameterValue); - } else if (parameterValue instanceof CtExecutable) { - return getShortSignatureForJavadoc(((CtExecutable) parameterValue).getReference()); - } else if (parameterValue instanceof CtLiteral) { - Object val = ((CtLiteral) parameterValue).getValue(); - return val == null ? null : val.toString(); - } - throw new SpoonException("Parameter value has unexpected class: " + parameterValue.getClass().getName() + ", whose conversion to String is not supported"); - } - - /* - * return the typical Javadoc style link Foo#method(). The class name is not fully qualified. - */ - private static String getShortSignatureForJavadoc(CtExecutableReference ref) { - SignaturePrinter sp = new SignaturePrinter(); - sp.writeNameAndParameters(ref); - return ref.getDeclaringType().getSimpleName() + CtExecutable.EXECUTABLE_SEPARATOR + sp.getSignature(); - } - - /** - * Converts `parameterValue` to {@link CtTypeReference}. - * It assures that new reference is returned. - * If parameterValue is already a {@link CtTypeReference}, then it is cloned. - * - * @param factory a Spoon factory used to create CtTypeReference instance - if needed - * @param parameterValue a value of an template parameter - * @return parameter value converted to {@link CtTypeReference} - */ - @SuppressWarnings("unchecked") - private static CtTypeReference getParameterValueAsTypeReference(Factory factory, Object parameterValue) { - if (parameterValue == null || parameterValue == NULL_VALUE) { - throw new SpoonException("The null value is not valid substitution for CtTypeReference"); - } - if (parameterValue instanceof Class) { - return factory.Type().createReference((Class) parameterValue); - } else if (parameterValue instanceof CtTypeReference) { - return ((CtTypeReference) parameterValue).clone(); - } else if (parameterValue instanceof CtType) { - return ((CtType) parameterValue).getReference(); - } else if (parameterValue instanceof String) { - return factory.Type().createReference((String) parameterValue); - } else { - throw new RuntimeException("unsupported reference substitution"); - } - } - - /** - * 1a) If index is null, then parameterValue must be a single item, which will be converted to itemClass - * 1b) If index is a number, then parameterValue is converted to List, the index-th item is converted to itemClass - * 2) if itemClass is sub type of CtElement then returned element is a clone - * - * @param itemClass required return class - * @param parameterValue a value of an template parameter - * @param index index of item from the list, or null if item is not expected to be a list - * @return parameterValue (optionally item from the list) cast (in future potentially converted) to itemClass - */ - private T getParameterValueAtIndex(Class itemClass, Object parameterValue, Integer index) { - if (index != null) { - //convert to list, but do not clone - List list = getParameterValueAsNewList(parameterValue); - if (list.size() > index) { - //convert and clone the returned item - return getParameterValueAsClass(itemClass, list.get(index)); - } - return null; - } - //convert and clone the returned item - return getParameterValueAsClass(itemClass, parameterValue); - } - - private Context createContext() { - //by default each new context has same input like parent and modifies same collection like parent context - return new Context(this.context); - } - - private class Context { - private final Context parentContext; - /** - * represents root element, which is target of the substitution. - * It can be substituted too. - */ - private CtElement input; - /** - * represents replacement of the `input`. - * it is null if input was not replaced - */ - private List result; - private Map parameterNameToValue; - - private Context(Context parent) { - this.parentContext = parent; - } - - private Context putParameter(String name, Object value) { - if (parameterNameToValue == null) { - parameterNameToValue = new LinkedHashMap<>(); - } - if (value == null) { - value = NULL_VALUE; - } - parameterNameToValue.put(name, value); - return this; - } - - private Context putParameters(Map parameters) { - if (parameters != null && parameters.isEmpty() == false) { - for (Map.Entry e : parameters.entrySet()) { - putParameter(e.getKey(), e.getValue()); - } - } - return this; - } - - private Object getParameterValue(String parameterName) { - if (parameterNameToValue != null) { - Object value = parameterNameToValue.get(parameterName); - if (value != null) { - return value; - } - } - if (parentContext != null) { - return parentContext.getParameterValue(parameterName); - } - return null; - } - - private DoNotFurtherTemplateThisElement replace(CtElement toBeReplaced, E replacement) { - return replace(toBeReplaced, replacement == null ? Collections.emptyList() : Collections.singletonList(replacement)); - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - private DoNotFurtherTemplateThisElement replace(CtElement toBeReplaced, List replacements) { - CtElement parentOfReplacement = toBeReplaced.isParentInitialized() ? toBeReplaced.getParent() : null; - if (parentOfReplacement instanceof CtReturn) { - if (replacements.size() == 1 && replacements.get(0) instanceof CtBlock) { - replacements = (List) ((CtBlock) replacements.get(0)).getStatements(); - } - if (replacements.size() > 1) { - //replace return too, because return expression cannot contain more statements - return context._replace(parentOfReplacement, replacements); - } else if (replacements.size() == 1 && (replacements.get(0) instanceof CtExpression) == false) { - //replace return too, because return expression cannot contain CtBlock - return context._replace(parentOfReplacement, replacements); - } - } - return context._replace(toBeReplaced, replacements); - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - private DoNotFurtherTemplateThisElement _replace(CtElement toBeReplaced, List replacements) { - if (input == toBeReplaced) { - if (result != null) { - throw new SpoonException("Illegal state. SubstitutionVisitor.Context#result was already replaced!"); - } - result = (List) replacements; - } else { - toBeReplaced.replace(replacements); - } - return new DoNotFurtherTemplateThisElement(toBeReplaced); - } - - @SuppressWarnings("unchecked") - private List substitute(E element) { - if (input != null) { - throw new SpoonException("Illegal state. SubstitutionVisitor.Context#input is already set."); - } - input = element; - result = null; - if (context != parentContext) { - throw new SpoonException("Illegal state. Context != parentContext"); - } - try { - context = this; - scan(element); - if (result != null) { - return (List) result; - } - return Collections.singletonList((E) input); - } finally { - context = this.parentContext; - input = null; - } - } - - private String substituteName(String name) { - if (name == null) { - return null; - } - if (parameterNameToValue != null) { - for (Map.Entry e : parameterNameToValue.entrySet()) { - String pname = e.getKey(); - if (name.contains(pname)) { - String value = getParameterValueAsString(e.getValue()); - name = name.replace(pname, value); - } - } - } - if (parentContext != null) { - name = parentContext.substituteName(name); - } - return name; - } - - } -} From dd30d1e3434c4a1304373e0a6af16b8c04acac75 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Tue, 29 May 2018 21:50:40 +0200 Subject: [PATCH 123/131] use/test PatternBuilderHelper#setReturnExpressionOfMethod --- src/main/java/spoon/pattern/PatternBuilderHelper.java | 5 +++-- src/main/java/spoon/template/ExpressionTemplate.java | 9 ++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/spoon/pattern/PatternBuilderHelper.java b/src/main/java/spoon/pattern/PatternBuilderHelper.java index 0547761529b..3689e8a5596 100644 --- a/src/main/java/spoon/pattern/PatternBuilderHelper.java +++ b/src/main/java/spoon/pattern/PatternBuilderHelper.java @@ -112,8 +112,9 @@ private void setBodyOfMethod(Filter> filter) { * Sets a template model from return expression of the method of template type selected by filter * @param methodName the name of {@link CtMethod} */ - public void setReturnExpressionOfMethod(String methodName) { + public PatternBuilderHelper setReturnExpressionOfMethod(String methodName) { setReturnExpressionOfMethod(tm -> methodName.equals(tm.getSimpleName())); + return this; } /** * Sets a template model from return expression of the method of template type selected by filter @@ -129,7 +130,7 @@ private void setReturnExpressionOfMethod(Filter> filter) { if (firstStatement instanceof CtReturn == false) { throw new SpoonException("The body of " + method.getSignature() + " must contain return statement. But there is:\n" + body.toString()); } - elements.add(((CtReturn) firstStatement).getReturnedExpression()); + setElements(Collections.singletonList(((CtReturn) firstStatement).getReturnedExpression())); } private List getByFilter(Filter filter) { diff --git a/src/main/java/spoon/template/ExpressionTemplate.java b/src/main/java/spoon/template/ExpressionTemplate.java index 2094a1956cd..3e40ce1c3b4 100644 --- a/src/main/java/spoon/template/ExpressionTemplate.java +++ b/src/main/java/spoon/template/ExpressionTemplate.java @@ -16,6 +16,7 @@ */ package spoon.template; +import spoon.pattern.PatternBuilderHelper; import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtExpression; import spoon.reflect.code.CtReturn; @@ -64,11 +65,9 @@ public ExpressionTemplate() { @SuppressWarnings("unchecked") public CtExpression apply(CtType targetType) { CtClass> c = Substitution.getTemplateCtClass(targetType, this); - CtBlock block = TemplateBuilder.createPattern(getExpressionBlock(c), this).setAddGeneratedBy(isAddGeneratedBy()).substituteSingle(targetType, CtBlock.class); - if (block == null || block.getStatements().isEmpty()) { - return null; - } - return ((CtReturn) block.getStatements().get(0)).getReturnedExpression(); + return TemplateBuilder.createPattern( + new PatternBuilderHelper(c).setReturnExpressionOfMethod("expression").getPatternElements().get(0), this) + .setAddGeneratedBy(isAddGeneratedBy()).substituteSingle(targetType, CtExpression.class); } public T S() { From c9eee720e7a18467b085ab51fb18e170a3650652 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Wed, 30 May 2018 22:23:39 +0200 Subject: [PATCH 124/131] up --- .../template/DefaultParameterMatcher.java | 40 --------------- .../support/template/ParameterMatcher.java | 49 ------------------- src/main/java/spoon/template/Parameter.java | 11 ----- 3 files changed, 100 deletions(-) delete mode 100644 src/main/java/spoon/support/template/DefaultParameterMatcher.java delete mode 100644 src/main/java/spoon/support/template/ParameterMatcher.java diff --git a/src/main/java/spoon/support/template/DefaultParameterMatcher.java b/src/main/java/spoon/support/template/DefaultParameterMatcher.java deleted file mode 100644 index 15e60f1a3c0..00000000000 --- a/src/main/java/spoon/support/template/DefaultParameterMatcher.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * 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 spoon.reflect.declaration.CtElement; -import spoon.template.TemplateMatcher; - -/** - * Default implementation for the {@link spoon.template.Parameter#match()} - * - * @author noguera - * - */ -public class DefaultParameterMatcher implements ParameterMatcher { - - /** - * Default implementation. - * - * @return always true. - */ - public boolean match(TemplateMatcher templateMatcher, CtElement template, - CtElement toMatch) { - return true; - } - -} diff --git a/src/main/java/spoon/support/template/ParameterMatcher.java b/src/main/java/spoon/support/template/ParameterMatcher.java deleted file mode 100644 index 3f4b774cfca..00000000000 --- a/src/main/java/spoon/support/template/ParameterMatcher.java +++ /dev/null @@ -1,49 +0,0 @@ -/** - * 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 spoon.reflect.declaration.CtElement; -import spoon.template.TemplateMatcher; - -/** - * Allows the definition of a specific matching policy for a given template - * parameter. When using {@link spoon.template.TemplateMatcher}, parameters are - * by default matched to anything. Defining a new type and precising it in the - * {@link spoon.template.Parameter} annotation allows to precise the form that - * the parameter will match. - * - *

              - * Note that this feature is not yet fully supported but will be in a close - * future. - */ -public interface ParameterMatcher { - - /** - * To be defined to implement a matching strategy for template parameter(s). - * - * @param templateMatcher - * the instance of the matcher that is currently performing the - * matching (up-caller) - * @param template - * the template element to match against - * @param toMatch - * the element to be tested for a match - * @return true if matching - */ - boolean match(TemplateMatcher templateMatcher, CtElement template, CtElement toMatch); - -} diff --git a/src/main/java/spoon/template/Parameter.java b/src/main/java/spoon/template/Parameter.java index f7b12c96821..aece312bc08 100644 --- a/src/main/java/spoon/template/Parameter.java +++ b/src/main/java/spoon/template/Parameter.java @@ -21,9 +21,6 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import spoon.support.template.DefaultParameterMatcher; -import spoon.support.template.ParameterMatcher; - /** * This annotation should be placed on templates' fields or methods to indicate * that they represent template parameters. It is only mandatory for names, @@ -68,12 +65,4 @@ */ String value() default ""; - /** - * Precises the type of the parameter matcher for this particular parameter - * when using the {@link spoon.template.TemplateMatcher} engine (optional). - * By default, the parameter will match under any form. Specifying an - * implementation of {@link ParameterMatcher} here allows the matching of - * more specific forms. - */ - Class match() default DefaultParameterMatcher.class; } From 6ffea86a6a09872a3c310c81eb8b2cd3ab317ee5 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Thu, 31 May 2018 22:14:51 +0200 Subject: [PATCH 125/131] test optional generator and fix SwitchNode --- .../pattern/internal/node/SwitchNode.java | 10 +++--- .../java/spoon/test/template/PatternTest.java | 33 +++++++++++++++++++ .../testclasses/match/GenerateIfElse.java | 15 +++++++++ 3 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 src/test/java/spoon/test/template/testclasses/match/GenerateIfElse.java diff --git a/src/main/java/spoon/pattern/internal/node/SwitchNode.java b/src/main/java/spoon/pattern/internal/node/SwitchNode.java index 8650bec6a8a..3545f6b564a 100644 --- a/src/main/java/spoon/pattern/internal/node/SwitchNode.java +++ b/src/main/java/spoon/pattern/internal/node/SwitchNode.java @@ -75,7 +75,11 @@ public void addCase(PrimitiveMatcher vrOfExpression, RootNode statement) { @Override public void generateTargets(DefaultGenerator generator, ResultHolder result, ImmutableMap parameters) { for (CaseNode case1 : cases) { - generator.generateTargets(case1, result, parameters); + if (case1.isCaseSelected(generator, parameters)) { + //generate result using first matching if branch + generator.generateTargets(case1, result, parameters); + return; + } } } @@ -179,9 +183,7 @@ public void forEachParameterInfo(BiConsumer consumer) { @Override public void generateTargets(DefaultGenerator generator, ResultHolder result, ImmutableMap parameters) { if (statement != null) { - if (isCaseSelected(generator, parameters)) { - generator.generateTargets(statement, result, parameters); - } + generator.generateTargets(statement, result, parameters); } } private boolean isCaseSelected(DefaultGenerator generator, ImmutableMap parameters) { diff --git a/src/test/java/spoon/test/template/PatternTest.java b/src/test/java/spoon/test/template/PatternTest.java index 9b6fa7e428e..07e7dbc0563 100644 --- a/src/test/java/spoon/test/template/PatternTest.java +++ b/src/test/java/spoon/test/template/PatternTest.java @@ -35,6 +35,7 @@ import spoon.test.template.testclasses.LoggerModel; import spoon.test.template.testclasses.ToBeMatched; import spoon.test.template.testclasses.logger.Logger; +import spoon.test.template.testclasses.match.GenerateIfElse; import spoon.test.template.testclasses.match.MatchForEach; import spoon.test.template.testclasses.match.MatchForEach2; import spoon.test.template.testclasses.match.MatchIfElse; @@ -218,6 +219,38 @@ public void testMatchIfElse() throws Exception { } } + + @Test + public void testGenerateIfElse() throws Exception { + //contract: it is possible to generate code using optional targets + CtType type = ModelUtils.buildClass(GenerateIfElse.class); + Pattern pattern = PatternBuilder.create(new PatternBuilderHelper(type).setBodyOfMethod("generator").getPatternElements()) + .configurePatternParameters(pb -> { + pb.parameter("option").byVariable("option"); + }) + //we have to configure inline statements after all expressions + //of combined if statement are marked as pattern parameters + .configureInlineStatements(lsb -> lsb.inlineIfOrForeachReferringTo("option")) + //contract: it is possible to configure pattern parameters after their parent is inlined + .configurePatternParameters(pb -> { + pb.parameter("value").byFilter(new TypeFilter(CtLiteral.class)); + }) + .build(); + + { + List statements = pattern.generator().generate(CtStatement.class, + new ImmutableMapImpl().putValue("option", true).putValue("value", "spoon")); + assertEquals(1, statements.size()); + assertEquals("java.lang.System.out.print(\"spoon\")", statements.get(0).toString()); + } + { + List statements = pattern.generator().generate(CtStatement.class, + new ImmutableMapImpl().putValue("option", false).putValue("value", 2.1)); + assertEquals(1, statements.size()); + assertEquals("java.lang.System.out.println(2.1)", statements.get(0).toString()); + } + } + @Test public void testGenerateMultiValues() throws Exception { // contract: the pattern parameter (in this case 'statements') diff --git a/src/test/java/spoon/test/template/testclasses/match/GenerateIfElse.java b/src/test/java/spoon/test/template/testclasses/match/GenerateIfElse.java new file mode 100644 index 00000000000..c0e7f8e57de --- /dev/null +++ b/src/test/java/spoon/test/template/testclasses/match/GenerateIfElse.java @@ -0,0 +1,15 @@ +package spoon.test.template.testclasses.match; + +public class GenerateIfElse { + + public void generator(boolean option) { + if (option) { + //generates `print` + System.out.print("string"); + } else { + //generates `println` + System.out.println("string"); + } + } + +} From 1451e5b657e9aa69fbec97ad3c0d5fbdd71597e2 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Sat, 9 Jun 2018 18:31:50 +0200 Subject: [PATCH 126/131] improve metamodel test --- .../java/spoon/test/api/MetamodelTest.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/test/java/spoon/test/api/MetamodelTest.java b/src/test/java/spoon/test/api/MetamodelTest.java index 03753d25bf8..8aaf4b8bee9 100644 --- a/src/test/java/spoon/test/api/MetamodelTest.java +++ b/src/test/java/spoon/test/api/MetamodelTest.java @@ -90,6 +90,7 @@ public void testRuntimeMetamodel() { expectedTypesByName.put(t.getName(), t); } }); + List problems = new ArrayList<>(); for (spoon.Metamodel.Type type : spoon.Metamodel.getAllMetamodelTypes()) { MetamodelConcept expectedType = expectedTypesByName.remove(type.getName()); assertSame(expectedType.getImplementationClass().getActualClass(), type.getModelClass()); @@ -97,12 +98,21 @@ public void testRuntimeMetamodel() { Map expectedRoleToField = new HashMap<>(expectedType.getRoleToProperty()); for (spoon.Metamodel.Field field : type.getFields()) { MetamodelProperty expectedField = expectedRoleToField.remove(field.getRole()); - assertSame("Field " + expectedField + ".derived", expectedField.isDerived(), field.isDerived()); - assertSame("Field " + expectedField + ".unsettable", expectedField.isUnsettable(), field.isUnsettable()); + if (expectedField.isDerived() != field.isDerived()) { + problems.add("Field " + expectedField + ".derived hardcoded value = " + field.isDerived() + " but computed value is " + expectedField.isDerived()); + } + if (expectedField.isUnsettable() != field.isUnsettable()) { + problems.add("Field " + expectedField + ".unsettable hardcoded value = " + field.isUnsettable() + " but computed value is " + expectedField.isUnsettable()); + } } - assertTrue("These Metamodel.Field instances are missing on Type " + type.getName() +": " + expectedRoleToField.keySet(), expectedRoleToField.isEmpty()); + if (expectedRoleToField.isEmpty() == false) { + problems.add("These Metamodel.Field instances are missing on Type " + type.getName() +": " + expectedRoleToField.keySet()); + } + } + if (expectedTypesByName.isEmpty() == false) { + problems.add("These Metamodel.Type instances are missing:" + expectedTypesByName.keySet()); } - assertTrue("These Metamodel.Type instances are missing: " + expectedTypesByName.keySet(), expectedTypesByName.isEmpty()); + assertTrue(String.join("\n", problems), problems.isEmpty()); } From 1483c51ecb9efd42f257fa4d8084036fea520627 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Sat, 9 Jun 2018 13:35:45 +0200 Subject: [PATCH 127/131] unsettableProperty implies derived on leaf concept --- src/main/java/spoon/metamodel/MetamodelProperty.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/spoon/metamodel/MetamodelProperty.java b/src/main/java/spoon/metamodel/MetamodelProperty.java index 6a9ba8b6098..cc446e49d8a 100644 --- a/src/main/java/spoon/metamodel/MetamodelProperty.java +++ b/src/main/java/spoon/metamodel/MetamodelProperty.java @@ -453,6 +453,10 @@ private CtTypeReference getMapValueType(CtTypeReference valueType) { */ public boolean isDerived() { if (derived == null) { + if (getOwner().getKind() == ConceptKind.LEAF && isUnsettable()) { + derived = Boolean.TRUE; + return derived; + } // by default it's derived derived = Boolean.FALSE; From 89123b2e78506985c00ddfa20696c2d8f340ae04 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Sun, 10 Jun 2018 11:12:40 +0200 Subject: [PATCH 128/131] Pattern uses new metamodel --- src/main/java/spoon/metamodel/Metamodel.java | 32 ++++++++++-- .../spoon/metamodel/MetamodelConcept.java | 18 +++++++ .../spoon/metamodel/MetamodelProperty.java | 27 ++++++++++ .../pattern/internal/PatternPrinter.java | 12 +++-- .../pattern/internal/node/ElementNode.java | 51 ++++++++++--------- 5 files changed, 106 insertions(+), 34 deletions(-) diff --git a/src/main/java/spoon/metamodel/Metamodel.java b/src/main/java/spoon/metamodel/Metamodel.java index e77b744bf98..5ba72cc1382 100644 --- a/src/main/java/spoon/metamodel/Metamodel.java +++ b/src/main/java/spoon/metamodel/Metamodel.java @@ -263,6 +263,18 @@ private Metamodel() { } } + /** + * @param clazz a {@link Class} of Spoon model + * @return {@link MetamodelConcept} which describes the `clazz` + */ + public MetamodelConcept getConcept(Class clazz) { + MetamodelConcept mc = nameToConcept.get(getConceptName(clazz)); + if (mc == null) { + throw new SpoonException("There is no Spoon metamodel concept for class " + clazz.getName()); + } + return mc; + } + /** * @return all {@link MetamodelConcept}s of spoon meta model */ @@ -289,13 +301,23 @@ public List> getAllInstantiableMetamodelInterfaces() * @return name of {@link MetamodelConcept}, which represents Spoon model {@link CtType} */ public static String getConceptName(CtType type) { - String name = type.getSimpleName(); - if (name.endsWith(CLASS_SUFFIX)) { - name = name.substring(0, name.length() - CLASS_SUFFIX.length()); - } - return name; + return getConceptName(type.getSimpleName()); } + public static String getConceptName(Class conceptClass) { + return getConceptName(conceptClass.getSimpleName()); + } + + /** + * @param simpleName a spoon model class or interface, whose concept name has to be returned + * @return name of {@link MetamodelConcept}, which represents Spoon model {@link CtType} + */ + private static String getConceptName(String simpleName) { + if (simpleName.endsWith(CLASS_SUFFIX)) { + simpleName = simpleName.substring(0, simpleName.length() - CLASS_SUFFIX.length()); + } + return simpleName; + } /** * @param iface the interface of spoon model element diff --git a/src/main/java/spoon/metamodel/MetamodelConcept.java b/src/main/java/spoon/metamodel/MetamodelConcept.java index 1a33c2b6f88..fab614dead2 100644 --- a/src/main/java/spoon/metamodel/MetamodelConcept.java +++ b/src/main/java/spoon/metamodel/MetamodelConcept.java @@ -19,6 +19,7 @@ import static spoon.metamodel.Metamodel.addUniqueObject; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; @@ -125,6 +126,23 @@ public Map getRoleToProperty() { return Collections.unmodifiableMap(role2Property); } + /** + * @return Collection of all {@link MetamodelProperty} of current {@link MetamodelConcept} + * Note: actually is the order undefined + * TODO: return List in the same order like it is scanned by CtScanner + */ + public Collection getProperties() { + return Collections.unmodifiableCollection(role2Property.values()); + } + + /** + * @param role a {@link CtRole} + * @return {@link MetamodelProperty} for `role` of this concept + */ + public MetamodelProperty getProperty(CtRole role) { + return role2Property.get(role); + } + /** * @return super types */ diff --git a/src/main/java/spoon/metamodel/MetamodelProperty.java b/src/main/java/spoon/metamodel/MetamodelProperty.java index cc446e49d8a..00b84b94fcc 100644 --- a/src/main/java/spoon/metamodel/MetamodelProperty.java +++ b/src/main/java/spoon/metamodel/MetamodelProperty.java @@ -32,6 +32,8 @@ import spoon.reflect.declaration.CtNamedElement; import spoon.reflect.factory.Factory; import spoon.reflect.meta.ContainerKind; +import spoon.reflect.meta.RoleHandler; +import spoon.reflect.meta.impl.RoleHandlerHelper; import spoon.reflect.path.CtRole; import spoon.reflect.reference.CtTypeParameterReference; import spoon.reflect.reference.CtTypeReference; @@ -72,6 +74,8 @@ public class MetamodelProperty { */ private CtTypeReference itemValueType; + private final RoleHandler roleHandler; + private Boolean derived; private Boolean unsettable; @@ -100,6 +104,7 @@ public class MetamodelProperty { this.name = name; this.role = role; this.ownerConcept = ownerConcept; + roleHandler = RoleHandlerHelper.getRoleHandler((Class) ownerConcept.getMetamodelInterface().getActualClass(), role); } void addMethod(CtMethod method) { @@ -571,4 +576,26 @@ private static ContainerKind containerKindOf(Class valueClass) { return ContainerKind.SINGLE; } + /** + * @return {@link RoleHandler} which can access runtime data of this Property + */ + public RoleHandler getRoleHandler() { + return this.roleHandler; + } + + /** + * @param element an instance whose attribute value is read + * @return a value of attribute defined by this {@link MetamodelProperty} from the provided `element` + */ + public U getValue(T element) { + return roleHandler.getValue(element); + } + + /** + * @param element an instance whose attribute value is set + * @param value to be set value of attribute defined by this {@link Field} on the provided `element` + */ + public void setValue(T element, U value) { + roleHandler.setValue(element, value); + } } diff --git a/src/main/java/spoon/pattern/internal/PatternPrinter.java b/src/main/java/spoon/pattern/internal/PatternPrinter.java index 2a007eb59ab..c282165425a 100644 --- a/src/main/java/spoon/pattern/internal/PatternPrinter.java +++ b/src/main/java/spoon/pattern/internal/PatternPrinter.java @@ -22,7 +22,9 @@ import java.util.TreeMap; import java.util.function.Consumer; -import spoon.Metamodel; +import spoon.metamodel.Metamodel; +import spoon.metamodel.MetamodelConcept; +import spoon.metamodel.MetamodelProperty; import spoon.pattern.internal.node.ConstantNode; import spoon.pattern.internal.node.ElementNode; import spoon.pattern.internal.node.InlineNode; @@ -80,8 +82,8 @@ public void generateTargets(RootNode node, ResultHolder result, Immutable if (node instanceof ElementNode) { ElementNode elementNode = (ElementNode) node; List paramsOnElement = new ArrayList<>(); - for (Map.Entry e : elementNode.getRoleToNode().entrySet()) { - Metamodel.Field mmField = e.getKey(); + for (Map.Entry e : elementNode.getRoleToNode().entrySet()) { + MetamodelProperty mmField = e.getKey(); foreachNode(e.getValue(), attrNode -> { if (attrNode instanceof ConstantNode || attrNode instanceof ElementNode) { return; @@ -111,8 +113,8 @@ private void foreachNode(RootNode rootNode, Consumer consumer) { private boolean isCommentVisible(Object obj) { if (obj instanceof CtElement) { - Metamodel.Type mmType = Metamodel.getMetamodelTypeByClass((Class) obj.getClass()); - Metamodel.Field mmCommentField = mmType.getField(CtRole.COMMENT); + MetamodelConcept mmType = Metamodel.getInstance().getConcept((Class) obj.getClass()); + MetamodelProperty mmCommentField = mmType.getProperty(CtRole.COMMENT); if (mmCommentField != null && mmCommentField.isDerived() == false) { return true; } diff --git a/src/main/java/spoon/pattern/internal/node/ElementNode.java b/src/main/java/spoon/pattern/internal/node/ElementNode.java index 8fafc9d4235..83725c38b66 100644 --- a/src/main/java/spoon/pattern/internal/node/ElementNode.java +++ b/src/main/java/spoon/pattern/internal/node/ElementNode.java @@ -16,8 +16,10 @@ */ package spoon.pattern.internal.node; -import spoon.Metamodel; import spoon.SpoonException; +import spoon.metamodel.Metamodel; +import spoon.metamodel.MetamodelConcept; +import spoon.metamodel.MetamodelProperty; import spoon.pattern.Quantifier; import spoon.pattern.internal.DefaultGenerator; import spoon.pattern.internal.ResultHolder; @@ -56,13 +58,13 @@ public class ElementNode extends AbstractPrimitiveMatcher { * @return a tree of {@link ElementNode}s, which reflects tree of `element` */ public static ElementNode create(CtElement element, Map patternElementToSubstRequests) { - Metamodel.Type mmConcept = Metamodel.getMetamodelTypeByClass(element.getClass()); + MetamodelConcept mmConcept = Metamodel.getInstance().getConcept(element.getClass()); ElementNode elementNode = new ElementNode(mmConcept, element); if (patternElementToSubstRequests.put(element, elementNode) != null) { throw new SpoonException("Each pattern element can have only one implicit Node."); } //iterate over all attributes of that element - for (Metamodel.Field mmField : mmConcept.getFields()) { + for (MetamodelProperty mmField : mmConcept.getProperties()) { if (mmField.isDerived()) { //skip derived fields, they are not relevant for matching or generating continue; @@ -176,15 +178,15 @@ private static ListOfNodes listOfNodesToNode(List nodes) { } private CtElement templateElement; - private Metamodel.Type elementType; - private Map roleToNode = new HashMap<>(); + private MetamodelConcept elementType; + private Map roleToNode = new HashMap<>(); /** * @param elementType The type of Spoon node which has to be generated/matched by this {@link ElementNode} * @param templateElement - optional ref to template element which was used to created this {@link ElementNode}. * It is used e.g. to generate generatedBy comment */ - public ElementNode(Metamodel.Type elementType, CtElement templateElement) { + public ElementNode(MetamodelConcept elementType, CtElement templateElement) { super(); this.elementType = elementType; this.templateElement = templateElement; @@ -192,7 +194,7 @@ public ElementNode(Metamodel.Type elementType, CtElement templateElement) { @Override public boolean replaceNode(RootNode oldNode, RootNode newNode) { - for (Map.Entry e : roleToNode.entrySet()) { + for (Map.Entry e : roleToNode.entrySet()) { RootNode node = e.getValue(); if (node == oldNode) { e.setValue(newNode); @@ -205,7 +207,7 @@ public boolean replaceNode(RootNode oldNode, RootNode newNode) { return false; } - public Map getRoleToNode() { + public Map getRoleToNode() { return roleToNode == null ? Collections.emptyMap() : Collections.unmodifiableMap(roleToNode); } @@ -224,7 +226,7 @@ public RootNode setNodeOfRole(CtRole role, RootNode newAttrNode) { public RootNode getOrCreateNodeOfRole(CtRole role, Map patternElementToSubstRequests) { RootNode node = getNodeOfRole(role); if (node == null) { - Metamodel.Field mmField = elementType.getField(role); + MetamodelProperty mmField = elementType.getProperty(role); if (mmField == null || mmField.isDerived()) { throw new SpoonException("The role " + role + " doesn't exist or is derived for " + elementType); } @@ -251,8 +253,8 @@ public T getValueOfRole(CtRole role, Class type) { return null; } - private Metamodel.Field getFieldOfRole(CtRole role) { - Metamodel.Field mmField = elementType.getField(role); + private MetamodelProperty getFieldOfRole(CtRole role) { + MetamodelProperty mmField = elementType.getProperty(role); if (mmField == null) { throw new SpoonException("CtRole." + role.name() + " isn't available for " + elementType); } @@ -274,25 +276,26 @@ public void forEachParameterInfo(BiConsumer consumer) { @SuppressWarnings("unchecked") @Override public void generateTargets(DefaultGenerator generator, ResultHolder result, ImmutableMap parameters) { - //TODO implement create on Metamodel.Type - CtElement clone = generator.getFactory().Core().create(elementType.getModelInterface()); + //TODO implement create on MetamodelConcept + @SuppressWarnings("rawtypes") + CtElement clone = generator.getFactory().Core().create((Class) elementType.getMetamodelInterface().getActualClass()); generateSingleNodeAttributes(generator, clone, parameters); generator.applyGeneratedBy(clone, generator.getGeneratedByComment(templateElement)); result.addResult((U) clone); } protected void generateSingleNodeAttributes(DefaultGenerator generator, CtElement clone, ImmutableMap parameters) { - for (Map.Entry e : getRoleToNode().entrySet()) { - Metamodel.Field mmField = e.getKey(); + for (Map.Entry e : getRoleToNode().entrySet()) { + MetamodelProperty mmField = e.getKey(); switch (mmField.getContainerKind()) { case SINGLE: - mmField.setValue(clone, generator.generateSingleTarget(e.getValue(), parameters, mmField.getValueClass())); + mmField.setValue(clone, generator.generateSingleTarget(e.getValue(), parameters, mmField.getTypeofItems().getActualClass())); break; case LIST: - mmField.setValue(clone, generator.generateTargets(e.getValue(), parameters, mmField.getValueClass())); + mmField.setValue(clone, generator.generateTargets(e.getValue(), parameters, mmField.getTypeofItems().getActualClass())); break; case SET: - mmField.setValue(clone, new LinkedHashSet<>(generator.generateTargets(e.getValue(), parameters, mmField.getValueClass()))); + mmField.setValue(clone, new LinkedHashSet<>(generator.generateTargets(e.getValue(), parameters, mmField.getTypeofItems().getActualClass()))); break; case MAP: mmField.setValue(clone, entriesToMap(generator.generateTargets(e.getValue(), parameters, Map.Entry.class))); @@ -314,14 +317,14 @@ public ImmutableMap matchTarget(Object target, ImmutableMap parameters) { if (target == null) { return null; } - if (target.getClass() != elementType.getModelClass()) { + if (target.getClass() != elementType.getImplementationClass().getActualClass()) { return null; } //it is spoon element, it matches if to be matched attributes matches //to be matched attributes must be same or substituted //iterate over all attributes of to be matched class - for (Map.Entry e : roleToNode.entrySet()) { + for (Map.Entry e : roleToNode.entrySet()) { parameters = matchesRole(parameters, (CtElement) target, e.getKey(), e.getValue()); if (parameters == null) { return null; @@ -330,8 +333,8 @@ public ImmutableMap matchTarget(Object target, ImmutableMap parameters) { return parameters; } - protected ImmutableMap matchesRole(ImmutableMap parameters, CtElement target, Metamodel.Field mmField, RootNode attrNode) { - if (isMatchingRole(mmField.getRole(), elementType.getModelInterface()) == false) { + protected ImmutableMap matchesRole(ImmutableMap parameters, CtElement target, MetamodelProperty mmField, RootNode attrNode) { + if (isMatchingRole(mmField.getRole(), elementType.getMetamodelInterface().getActualClass()) == false) { return parameters; } TobeMatched tobeMatched; @@ -403,11 +406,11 @@ public String toString() { // } // } - public Metamodel.Type getElementType() { + public MetamodelConcept getElementType() { return elementType; } - public void setElementType(Metamodel.Type elementType) { + public void setElementType(MetamodelConcept elementType) { this.elementType = elementType; } From 7e7f89f0524a40969b75dfe91b6eac6c79909599 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Sun, 10 Jun 2018 11:17:55 +0200 Subject: [PATCH 129/131] move old metamodel to test to be able to check computed metamode --- .../java/spoon/test/api}/Metamodel.java | 138 +----------------- .../java/spoon/test/api/MetamodelTest.java | 4 +- 2 files changed, 3 insertions(+), 139 deletions(-) rename src/{main/java/spoon => test/java/spoon/test/api}/Metamodel.java (83%) diff --git a/src/main/java/spoon/Metamodel.java b/src/test/java/spoon/test/api/Metamodel.java similarity index 83% rename from src/main/java/spoon/Metamodel.java rename to src/test/java/spoon/test/api/Metamodel.java index 330d2389a86..8574c652b2d 100644 --- a/src/main/java/spoon/Metamodel.java +++ b/src/test/java/spoon/test/api/Metamodel.java @@ -14,36 +14,26 @@ * 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; +package spoon.test.api; import spoon.reflect.code.CtForEach; import spoon.reflect.declaration.CtClass; import spoon.reflect.declaration.CtElement; import spoon.reflect.declaration.CtInterface; -import spoon.reflect.declaration.CtModuleDirective; -import spoon.reflect.declaration.CtPackageExport; -import spoon.reflect.declaration.CtProvidedService; -import spoon.reflect.declaration.CtType; -import spoon.reflect.factory.Factory; -import spoon.reflect.factory.FactoryImpl; import spoon.reflect.meta.ContainerKind; import spoon.reflect.meta.RoleHandler; import spoon.reflect.meta.impl.RoleHandlerHelper; import spoon.reflect.path.CtRole; import spoon.reflect.visitor.CtScanner; -import spoon.support.DefaultCoreFactory; -import spoon.support.StandardEnvironment; import spoon.support.reflect.code.CtForEachImpl; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.function.Consumer; /** @@ -52,132 +42,6 @@ public class Metamodel { private Metamodel() { } - /** - * Returns all interfaces of the Spoon metamodel. - * This method is stateless for sake of maintenance. - * If you need to call it several times, you should store the result. - */ - public static Set> getAllMetamodelInterfaces() { - Set> result = new HashSet<>(); - Factory factory = new FactoryImpl(new DefaultCoreFactory(), new StandardEnvironment()); - result.add(factory.Type().get(spoon.reflect.code.BinaryOperatorKind.class)); - result.add(factory.Type().get(spoon.reflect.code.CtAbstractInvocation.class)); - result.add(factory.Type().get(spoon.reflect.code.CtAnnotationFieldAccess.class)); - result.add(factory.Type().get(spoon.reflect.code.CtArrayAccess.class)); - result.add(factory.Type().get(spoon.reflect.code.CtArrayRead.class)); - result.add(factory.Type().get(spoon.reflect.code.CtArrayWrite.class)); - result.add(factory.Type().get(spoon.reflect.code.CtAssert.class)); - result.add(factory.Type().get(spoon.reflect.code.CtAssignment.class)); - result.add(factory.Type().get(spoon.reflect.code.CtBinaryOperator.class)); - result.add(factory.Type().get(spoon.reflect.code.CtBlock.class)); - result.add(factory.Type().get(spoon.reflect.code.CtBodyHolder.class)); - result.add(factory.Type().get(spoon.reflect.code.CtBreak.class)); - result.add(factory.Type().get(spoon.reflect.code.CtCFlowBreak.class)); - result.add(factory.Type().get(spoon.reflect.code.CtCase.class)); - result.add(factory.Type().get(spoon.reflect.code.CtCatch.class)); - result.add(factory.Type().get(spoon.reflect.code.CtCatchVariable.class)); - result.add(factory.Type().get(spoon.reflect.code.CtCodeElement.class)); - result.add(factory.Type().get(spoon.reflect.code.CtCodeSnippetExpression.class)); - result.add(factory.Type().get(spoon.reflect.code.CtCodeSnippetStatement.class)); - result.add(factory.Type().get(spoon.reflect.code.CtComment.class)); - result.add(factory.Type().get(spoon.reflect.code.CtConditional.class)); - result.add(factory.Type().get(spoon.reflect.code.CtConstructorCall.class)); - result.add(factory.Type().get(spoon.reflect.code.CtContinue.class)); - result.add(factory.Type().get(spoon.reflect.code.CtDo.class)); - result.add(factory.Type().get(spoon.reflect.code.CtExecutableReferenceExpression.class)); - result.add(factory.Type().get(spoon.reflect.code.CtExpression.class)); - result.add(factory.Type().get(spoon.reflect.code.CtFieldAccess.class)); - result.add(factory.Type().get(spoon.reflect.code.CtFieldRead.class)); - result.add(factory.Type().get(spoon.reflect.code.CtFieldWrite.class)); - result.add(factory.Type().get(spoon.reflect.code.CtFor.class)); - result.add(factory.Type().get(spoon.reflect.code.CtForEach.class)); - result.add(factory.Type().get(spoon.reflect.code.CtIf.class)); - result.add(factory.Type().get(spoon.reflect.code.CtInvocation.class)); - result.add(factory.Type().get(spoon.reflect.code.CtJavaDoc.class)); - result.add(factory.Type().get(spoon.reflect.code.CtJavaDocTag.class)); - result.add(factory.Type().get(spoon.reflect.code.CtLabelledFlowBreak.class)); - result.add(factory.Type().get(spoon.reflect.code.CtLambda.class)); - result.add(factory.Type().get(spoon.reflect.code.CtLiteral.class)); - result.add(factory.Type().get(spoon.reflect.code.CtLocalVariable.class)); - result.add(factory.Type().get(spoon.reflect.code.CtLoop.class)); - result.add(factory.Type().get(spoon.reflect.code.CtNewArray.class)); - result.add(factory.Type().get(spoon.reflect.code.CtNewClass.class)); - result.add(factory.Type().get(spoon.reflect.code.CtOperatorAssignment.class)); - result.add(factory.Type().get(spoon.reflect.code.CtRHSReceiver.class)); - result.add(factory.Type().get(spoon.reflect.code.CtReturn.class)); - result.add(factory.Type().get(spoon.reflect.code.CtStatement.class)); - result.add(factory.Type().get(spoon.reflect.code.CtStatementList.class)); - result.add(factory.Type().get(spoon.reflect.code.CtSuperAccess.class)); - result.add(factory.Type().get(spoon.reflect.code.CtSwitch.class)); - result.add(factory.Type().get(spoon.reflect.code.CtSynchronized.class)); - result.add(factory.Type().get(spoon.reflect.code.CtTargetedExpression.class)); - result.add(factory.Type().get(spoon.reflect.code.CtThisAccess.class)); - result.add(factory.Type().get(spoon.reflect.code.CtThrow.class)); - result.add(factory.Type().get(spoon.reflect.code.CtTry.class)); - result.add(factory.Type().get(spoon.reflect.code.CtTryWithResource.class)); - result.add(factory.Type().get(spoon.reflect.code.CtTypeAccess.class)); - result.add(factory.Type().get(spoon.reflect.code.CtUnaryOperator.class)); - result.add(factory.Type().get(spoon.reflect.code.CtVariableAccess.class)); - result.add(factory.Type().get(spoon.reflect.code.CtVariableRead.class)); - result.add(factory.Type().get(spoon.reflect.code.CtVariableWrite.class)); - result.add(factory.Type().get(spoon.reflect.code.CtWhile.class)); - result.add(factory.Type().get(spoon.reflect.code.UnaryOperatorKind.class)); - result.add(factory.Type().get(spoon.reflect.declaration.CtAnnotatedElementType.class)); - result.add(factory.Type().get(spoon.reflect.declaration.CtAnnotation.class)); - result.add(factory.Type().get(spoon.reflect.declaration.CtAnnotationMethod.class)); - result.add(factory.Type().get(spoon.reflect.declaration.CtAnnotationType.class)); - result.add(factory.Type().get(spoon.reflect.declaration.CtAnonymousExecutable.class)); - result.add(factory.Type().get(spoon.reflect.declaration.CtClass.class)); - result.add(factory.Type().get(spoon.reflect.declaration.CtCodeSnippet.class)); - result.add(factory.Type().get(spoon.reflect.declaration.CtConstructor.class)); - result.add(factory.Type().get(spoon.reflect.declaration.CtElement.class)); - result.add(factory.Type().get(spoon.reflect.declaration.CtEnum.class)); - result.add(factory.Type().get(spoon.reflect.declaration.CtEnumValue.class)); - result.add(factory.Type().get(spoon.reflect.declaration.CtExecutable.class)); - result.add(factory.Type().get(spoon.reflect.declaration.CtField.class)); - result.add(factory.Type().get(spoon.reflect.declaration.CtFormalTypeDeclarer.class)); - result.add(factory.Type().get(spoon.reflect.declaration.CtInterface.class)); - result.add(factory.Type().get(spoon.reflect.declaration.CtMethod.class)); - result.add(factory.Type().get(spoon.reflect.declaration.CtModifiable.class)); - result.add(factory.Type().get(spoon.reflect.declaration.CtMultiTypedElement.class)); - result.add(factory.Type().get(spoon.reflect.declaration.CtNamedElement.class)); - result.add(factory.Type().get(spoon.reflect.declaration.CtPackage.class)); - result.add(factory.Type().get(spoon.reflect.declaration.CtParameter.class)); - result.add(factory.Type().get(spoon.reflect.declaration.CtShadowable.class)); - result.add(factory.Type().get(spoon.reflect.declaration.CtType.class)); - result.add(factory.Type().get(spoon.reflect.declaration.CtTypeInformation.class)); - result.add(factory.Type().get(spoon.reflect.declaration.CtTypeMember.class)); - result.add(factory.Type().get(spoon.reflect.declaration.CtTypeParameter.class)); - result.add(factory.Type().get(spoon.reflect.declaration.CtTypedElement.class)); - result.add(factory.Type().get(spoon.reflect.declaration.CtVariable.class)); - result.add(factory.Type().get(spoon.reflect.declaration.ModifierKind.class)); - result.add(factory.Type().get(spoon.reflect.declaration.ParentNotInitializedException.class)); - result.add(factory.Type().get(spoon.reflect.reference.CtActualTypeContainer.class)); - result.add(factory.Type().get(spoon.reflect.reference.CtArrayTypeReference.class)); - result.add(factory.Type().get(spoon.reflect.reference.CtCatchVariableReference.class)); - result.add(factory.Type().get(spoon.reflect.reference.CtExecutableReference.class)); - result.add(factory.Type().get(spoon.reflect.reference.CtFieldReference.class)); - result.add(factory.Type().get(spoon.reflect.reference.CtIntersectionTypeReference.class)); - result.add(factory.Type().get(spoon.reflect.reference.CtLocalVariableReference.class)); - result.add(factory.Type().get(spoon.reflect.reference.CtPackageReference.class)); - result.add(factory.Type().get(spoon.reflect.reference.CtParameterReference.class)); - result.add(factory.Type().get(spoon.reflect.reference.CtReference.class)); - result.add(factory.Type().get(spoon.reflect.reference.CtTypeParameterReference.class)); - result.add(factory.Type().get(spoon.reflect.reference.CtTypeReference.class)); - result.add(factory.Type().get(spoon.reflect.reference.CtUnboundVariableReference.class)); - result.add(factory.Type().get(spoon.reflect.reference.CtVariableReference.class)); - result.add(factory.Type().get(spoon.reflect.reference.CtWildcardReference.class)); - result.add(factory.Type().get(spoon.reflect.declaration.CtImport.class)); - result.add(factory.Type().get(spoon.reflect.declaration.CtImportKind.class)); - result.add(factory.Type().get(spoon.reflect.declaration.CtModule.class)); - result.add(factory.Type().get(spoon.reflect.declaration.CtModuleRequirement.class)); - result.add(factory.Type().get(CtPackageExport.class)); - result.add(factory.Type().get(CtProvidedService.class)); - result.add(factory.Type().get(spoon.reflect.reference.CtModuleReference.class)); - result.add(factory.Type().get(spoon.reflect.declaration.CtUsedService.class)); - result.add(factory.Type().get(CtModuleDirective.class)); - return result; - } public static Collection getAllMetamodelTypes() { return typesByName.values(); diff --git a/src/test/java/spoon/test/api/MetamodelTest.java b/src/test/java/spoon/test/api/MetamodelTest.java index 8aaf4b8bee9..03dceb16083 100644 --- a/src/test/java/spoon/test/api/MetamodelTest.java +++ b/src/test/java/spoon/test/api/MetamodelTest.java @@ -91,12 +91,12 @@ public void testRuntimeMetamodel() { } }); List problems = new ArrayList<>(); - for (spoon.Metamodel.Type type : spoon.Metamodel.getAllMetamodelTypes()) { + for (spoon.test.api.Metamodel.Type type : spoon.test.api.Metamodel.getAllMetamodelTypes()) { MetamodelConcept expectedType = expectedTypesByName.remove(type.getName()); assertSame(expectedType.getImplementationClass().getActualClass(), type.getModelClass()); assertSame(expectedType.getMetamodelInterface().getActualClass(), type.getModelInterface()); Map expectedRoleToField = new HashMap<>(expectedType.getRoleToProperty()); - for (spoon.Metamodel.Field field : type.getFields()) { + for (spoon.test.api.Metamodel.Field field : type.getFields()) { MetamodelProperty expectedField = expectedRoleToField.remove(field.getRole()); if (expectedField.isDerived() != field.isDerived()) { problems.add("Field " + expectedField + ".derived hardcoded value = " + field.isDerived() + " but computed value is " + expectedField.isDerived()); From d7947c2863c39c3aa6546176d36b89be503487df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Sun, 10 Jun 2018 13:12:59 +0200 Subject: [PATCH 130/131] fix javadoc --- src/main/java/spoon/metamodel/MetamodelProperty.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/spoon/metamodel/MetamodelProperty.java b/src/main/java/spoon/metamodel/MetamodelProperty.java index 00b84b94fcc..8b96e9957fe 100644 --- a/src/main/java/spoon/metamodel/MetamodelProperty.java +++ b/src/main/java/spoon/metamodel/MetamodelProperty.java @@ -593,7 +593,7 @@ public U getValue(T element) { /** * @param element an instance whose attribute value is set - * @param value to be set value of attribute defined by this {@link Field} on the provided `element` + * @param value to be set value of attribute defined by this {@link MetamodelProperty} on the provided `element` */ public void setValue(T element, U value) { roleHandler.setValue(element, value); From bb782e92b4245e5dfb0e8854ad8f310295b0c744 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vojt=C4=9Bchovsk=C3=BD?= Date: Sun, 10 Jun 2018 15:26:27 +0200 Subject: [PATCH 131/131] remove metamodel test and MetamodelGenerator from this PR --- .../spoon/generating/MetamodelGenerator.java | 112 -- src/test/java/spoon/test/api/Metamodel.java | 1276 ----------------- .../java/spoon/test/api/MetamodelTest.java | 37 - 3 files changed, 1425 deletions(-) delete mode 100644 src/test/java/spoon/generating/MetamodelGenerator.java delete mode 100644 src/test/java/spoon/test/api/Metamodel.java diff --git a/src/test/java/spoon/generating/MetamodelGenerator.java b/src/test/java/spoon/generating/MetamodelGenerator.java deleted file mode 100644 index 30f2a432b65..00000000000 --- a/src/test/java/spoon/generating/MetamodelGenerator.java +++ /dev/null @@ -1,112 +0,0 @@ -package spoon.generating; - -import java.io.File; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import org.apache.commons.lang3.text.StrSubstitutor; - -import spoon.SpoonException; -import spoon.metamodel.ConceptKind; -import spoon.metamodel.Metamodel; -import spoon.reflect.declaration.CtElement; -import spoon.reflect.factory.Factory; -import spoon.reflect.path.CtRole; -import spoon.reflect.visitor.CtScanner; - -public class MetamodelGenerator { - - public static void main(String[] args) { - Metamodel mm = Metamodel.getInstance(); - Factory factory = mm.getConcepts().iterator().next().getMetamodelInterface().getFactory(); - factory.getEnvironment().useTabulations(true); - StringBuilder sb = new StringBuilder(); - for (spoon.metamodel.MetamodelConcept type : mm.getConcepts()) { - if (type.getKind()==ConceptKind.LEAF) { - sb.append(printType(factory, type)); - } - } - System.out.println(sb.toString()); - } - - - private static String printType(Factory factory, spoon.metamodel.MetamodelConcept type) { - Map valuesMap = new HashMap<>(); - valuesMap.put("typeName", type.getName()); - valuesMap.put("ifaceName", type.getMetamodelInterface().getQualifiedName()); - valuesMap.put("implName", type.getImplementationClass().getQualifiedName()); - valuesMap.put("fields", printFields(factory, type)); - - StrSubstitutor strSubst = new StrSubstitutor(valuesMap); - return strSubst.replace( - "types.add(new Type(\"${typeName}\", ${ifaceName}.class, ${implName}.class, fm -> fm\n" + - "${fields}\n" + - "));\n\n"); - - } - - private static String printFields(Factory factory, spoon.metamodel.MetamodelConcept type) { - Map allFields = new LinkedHashMap<>(type.getRoleToProperty()); - List rolesByScanner = getRoleScanningOrderOfType(factory, (Class) type.getMetamodelInterface().getActualClass()); - List elementFields = new ArrayList<>(); - for (CtRole ctRole : rolesByScanner) { - spoon.metamodel.MetamodelProperty field = allFields.remove(ctRole); - elementFields.add(printField(field)); - } - //generate remaining primitive fields, sorted by Enum#ordinal of CtRole - just to have a stable order - List primitiveFields = new ArrayList<>(); - new ArrayList(allFields.keySet()).stream().sorted().forEach(role -> { - spoon.metamodel.MetamodelProperty field = allFields.remove(role); - primitiveFields.add(printField(field)); - }); - if (allFields.isEmpty() == false) { - throw new SpoonException("There remained some fields?"); - } - StringBuilder sb = new StringBuilder(); - primitiveFields.addAll(elementFields); - primitiveFields.forEach(s->sb.append(s).append('\n')); - return sb.toString(); - } - - private static String printField(spoon.metamodel.MetamodelProperty field) { - Map valuesMap = new HashMap<>(); - valuesMap.put("role", field.getRole().name()); - valuesMap.put("derived", String.valueOf(field.isDerived())); - valuesMap.put("unsetable", String.valueOf(field.isUnsettable())); - - StrSubstitutor strSubst = new StrSubstitutor(valuesMap); - return strSubst.replace("\t.field(CtRole.${role}, ${derived}, ${unsetable})"); - } - - - private static List getRoleScanningOrderOfType(Factory factory, Class iface) { - List roles = new ArrayList<>(); - //generate fields in the same order like they are visited in CtScanner - CtElement ele = factory.Core().create(iface); - ele.accept(new CtScanner() { - @Override - public void scan(CtRole role, CtElement element) { - roles.add(role); - } - @Override - public void scan(CtRole role, Collection elements) { - roles.add(role); - } - @Override - public void scan(CtRole role, Map elements) { - roles.add(role); - } - }); - return roles; - } - -/* - types.add(new Type("CtClass", CtClassImpl.class, CtClass.class, fm -> - fm.field(CtRole.NAME, false, false) - )); -*/ -} diff --git a/src/test/java/spoon/test/api/Metamodel.java b/src/test/java/spoon/test/api/Metamodel.java deleted file mode 100644 index 8574c652b2d..00000000000 --- a/src/test/java/spoon/test/api/Metamodel.java +++ /dev/null @@ -1,1276 +0,0 @@ -/** - * 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.test.api; - -import spoon.reflect.code.CtForEach; -import spoon.reflect.declaration.CtClass; -import spoon.reflect.declaration.CtElement; -import spoon.reflect.declaration.CtInterface; -import spoon.reflect.meta.ContainerKind; -import spoon.reflect.meta.RoleHandler; -import spoon.reflect.meta.impl.RoleHandlerHelper; -import spoon.reflect.path.CtRole; -import spoon.reflect.visitor.CtScanner; -import spoon.support.reflect.code.CtForEachImpl; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; - -/** - * This class enables to reason on the Spoon metamodel directly - */ -public class Metamodel { - private Metamodel() { } - - - public static Collection getAllMetamodelTypes() { - return typesByName.values(); - } - - public static Type getMetamodelTypeByClass(Class clazz) { - return typesByClass.get(clazz); - } - - /** - * Describes a Spoon metamodel type - */ - public static class Type { - /** - * Name of the type - */ - private final String name; - - /** - * The {@link CtClass} linked to this {@link MetamodelConcept}. Is null in case of class without interface - */ - private final Class modelClass; - /** - * The {@link CtInterface} linked to this {@link MetamodelConcept}. Is null in case of interface without class - */ - private final Class modelInterface; - - private final List fields; - private final Map fieldsByRole; - - private Type(String name, Class modelInterface, Class modelClass, Consumer fieldsCreator) { - super(); - this.name = name; - this.modelClass = modelClass; - this.modelInterface = modelInterface; - List fields = new ArrayList<>(); - this.fields = Collections.unmodifiableList(fields); - fieldsCreator.accept(new FieldMaker() { - @Override - public FieldMaker field(CtRole role, boolean derived, boolean unsettable) { - fields.add(new Field(Type.this, role, derived, unsettable)); - return this; - } - }); - Map fieldsByRole = new LinkedHashMap<>(fields.size()); - fields.forEach(f -> fieldsByRole.put(f.getRole(), f)); - this.fieldsByRole = Collections.unmodifiableMap(fieldsByRole); - } - - /** - * @return interface name of Spoon model type. For example CtClass, CtForEach, ... - * It is never followed by xxxImpl - */ - public String getName() { - return name; - } - - /** - * @return {@link Class} which implements this type. For example {@link CtForEachImpl} - */ - public Class getModelClass() { - return modelClass; - } - /** - * @return {@link Class} which defines interface of this type. For example {@link CtForEach} - */ - public Class getModelInterface() { - return modelInterface; - } - - @Override - public String toString() { - return getName(); - } - - /** - * @return {@link List} of {@link Field}s of this spoon model {@link Type} in the same order, like they are processed by {@link CtScanner} - */ - public List getFields() { - return fields; - } - - /** - * @param role the {@link CtRole} of to be returned {@link Field} - * @return {@link Field} of this {@link Type} by {@link CtRole} or null if this {@link CtRole} doesn't exist on this {@link Type} - */ - public Field getField(CtRole role) { - return fieldsByRole.get(role); - } - } - /** - * Describes a Spoon metamodel Field - */ - public static class Field { - private final Type owner; - private final CtRole role; - private final RoleHandler roleHandler; - private final boolean derived; - private final boolean unsettable; - - private Field(Type owner, CtRole role, boolean derived, boolean unsettable) { - super(); - this.owner = owner; - this.role = role; - this.derived = derived; - this.unsettable = unsettable; - this.roleHandler = RoleHandlerHelper.getRoleHandler(owner.modelClass, role); - } - - /** - * @return {@link Type}, which contains this {@link Field} - */ - public Type getOwner() { - return owner; - } - - /** - * @return {@link CtRole} of this {@link Field} - */ - public CtRole getRole() { - return role; - } - - /** - * @return {@link RoleHandler} providing generic access to the value of this Field - */ - public RoleHandler getRoleHandler() { - return roleHandler; - } - - /** - * @return true if this field is derived (value is somehow computed) - */ - public boolean isDerived() { - return derived; - } - - /** - * @return true if it makes no sense to set this field on this type - */ - public boolean isUnsettable() { - return unsettable; - } - - /** - * @param element an instance whose attribute value is read - * @return a value of attribute defined by this {@link Field} from the provided `element` - */ - public U getValue(T element) { - return roleHandler.getValue(element); - } - - /** - * @param element an instance whose attribute value is set - * @param value to be set value of attribute defined by this {@link Field} on the provided `element` - */ - public void setValue(T element, U value) { - roleHandler.setValue(element, value); - } - - /** - * @return {@link Class} of {@link Field}'s value. - */ - public Class getValueClass() { - return roleHandler.getValueClass(); - } - - /** - * @return the container kind, to know whether an element, a list, a map, etc is returned. - */ - public ContainerKind getContainerKind() { - return roleHandler.getContainerKind(); - } - - @Override - public String toString() { - return getOwner().toString() + "#" + getRole().getCamelCaseName(); - } - } - - private interface FieldMaker { - /** - * Creates a instance of Field in Type - * @param role a role of the {@link Field} - * @param derived marker if field is derived - * @param unsettable marker if field is unsettable - * @return this to support fluent API - */ - FieldMaker field(CtRole role, boolean derived, boolean unsettable); - } - - private static final Map typesByName = new HashMap<>(); - private static final Map, Type> typesByClass = new HashMap<>(); - - static { - List types = new ArrayList<>(); - initTypes(types); - types.forEach(type -> { - typesByName.put(type.getName(), type); - typesByClass.put(type.getModelClass(), type); - typesByClass.put(type.getModelInterface(), type); - }); - } - private static void initTypes(List types) { - /** - * body of this method was generated by /spoon-core/src/test/java/spoon/generating/MetamodelGenerator.java - * Run the method main and copy the System output here - */ - types.add(new Type("CtConditional", spoon.reflect.code.CtConditional.class, spoon.support.reflect.code.CtConditionalImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.TYPE, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.CONDITION, false, false) - .field(CtRole.THEN, false, false) - .field(CtRole.ELSE, false, false) - .field(CtRole.COMMENT, false, false) - .field(CtRole.CAST, false, false) - - )); - - types.add(new Type("CtProvidedService", spoon.reflect.declaration.CtProvidedService.class, spoon.support.reflect.declaration.CtProvidedServiceImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.COMMENT, false, false) - .field(CtRole.SERVICE_TYPE, false, false) - .field(CtRole.IMPLEMENTATION_TYPE, false, false) - .field(CtRole.ANNOTATION, false, false) - - )); - - types.add(new Type("CtParameter", spoon.reflect.declaration.CtParameter.class, spoon.support.reflect.declaration.CtParameterImpl.class, fm -> fm - .field(CtRole.NAME, false, false) - .field(CtRole.IS_SHADOW, false, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.IS_VARARGS, false, false) - .field(CtRole.DEFAULT_EXPRESSION, true, true) - .field(CtRole.MODIFIER, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.TYPE, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtWhile", spoon.reflect.code.CtWhile.class, spoon.support.reflect.code.CtWhileImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.LABEL, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.EXPRESSION, false, false) - .field(CtRole.BODY, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtTypeReference", spoon.reflect.reference.CtTypeReference.class, spoon.support.reflect.reference.CtTypeReferenceImpl.class, fm -> fm - .field(CtRole.NAME, false, false) - .field(CtRole.IS_SHADOW, false, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.MODIFIER, true, true) - .field(CtRole.INTERFACE, true, true) - .field(CtRole.SUPER_TYPE, true, true) - .field(CtRole.POSITION, false, false) - .field(CtRole.PACKAGE_REF, false, false) - .field(CtRole.DECLARING_TYPE, false, false) - .field(CtRole.TYPE_ARGUMENT, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.COMMENT, true, true) - - )); - - types.add(new Type("CtCatchVariableReference", spoon.reflect.reference.CtCatchVariableReference.class, spoon.support.reflect.reference.CtCatchVariableReferenceImpl.class, fm -> fm - .field(CtRole.NAME, false, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.COMMENT, true, true) - .field(CtRole.TYPE, false, false) - .field(CtRole.ANNOTATION, false, false) - - )); - - types.add(new Type("CtContinue", spoon.reflect.code.CtContinue.class, spoon.support.reflect.code.CtContinueImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.LABEL, false, false) - .field(CtRole.TARGET_LABEL, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtInterface", spoon.reflect.declaration.CtInterface.class, spoon.support.reflect.declaration.CtInterfaceImpl.class, fm -> fm - .field(CtRole.NAME, false, false) - .field(CtRole.IS_SHADOW, false, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.MODIFIER, false, false) - .field(CtRole.SUPER_TYPE, true, true) - .field(CtRole.NESTED_TYPE, true, false) - .field(CtRole.METHOD, true, false) - .field(CtRole.FIELD, true, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.INTERFACE, false, false) - .field(CtRole.TYPE_PARAMETER, false, false) - .field(CtRole.TYPE_MEMBER, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtAssignment", spoon.reflect.code.CtAssignment.class, spoon.support.reflect.code.CtAssignmentImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.LABEL, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.TYPE, false, false) - .field(CtRole.CAST, false, false) - .field(CtRole.ASSIGNED, false, false) - .field(CtRole.ASSIGNMENT, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtBinaryOperator", spoon.reflect.code.CtBinaryOperator.class, spoon.support.reflect.code.CtBinaryOperatorImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.OPERATOR_KIND, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.TYPE, false, false) - .field(CtRole.CAST, false, false) - .field(CtRole.LEFT_OPERAND, false, false) - .field(CtRole.RIGHT_OPERAND, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtEnumValue", spoon.reflect.declaration.CtEnumValue.class, spoon.support.reflect.declaration.CtEnumValueImpl.class, fm -> fm - .field(CtRole.NAME, false, false) - .field(CtRole.IS_SHADOW, false, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.ASSIGNMENT, true, true) - .field(CtRole.MODIFIER, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.TYPE, false, false) - .field(CtRole.DEFAULT_EXPRESSION, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtModuleRequirement", spoon.reflect.declaration.CtModuleRequirement.class, spoon.support.reflect.declaration.CtModuleRequirementImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.MODIFIER, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.COMMENT, false, false) - .field(CtRole.MODULE_REF, false, false) - .field(CtRole.ANNOTATION, false, false) - - )); - - types.add(new Type("CtForEach", spoon.reflect.code.CtForEach.class, spoon.support.reflect.code.CtForEachImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.LABEL, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.FOREACH_VARIABLE, false, false) - .field(CtRole.EXPRESSION, false, false) - .field(CtRole.BODY, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtConstructor", spoon.reflect.declaration.CtConstructor.class, spoon.support.reflect.declaration.CtConstructorImpl.class, fm -> fm - .field(CtRole.NAME, true, true) - .field(CtRole.TYPE, true, true) - .field(CtRole.IS_SHADOW, false, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.MODIFIER, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.PARAMETER, false, false) - .field(CtRole.THROWN, false, false) - .field(CtRole.TYPE_PARAMETER, false, false) - .field(CtRole.BODY, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtSuperAccess", spoon.reflect.code.CtSuperAccess.class, spoon.support.reflect.code.CtSuperAccessImpl.class, fm -> fm - .field(CtRole.TYPE, true, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.COMMENT, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.CAST, false, false) - .field(CtRole.TARGET, false, false) - .field(CtRole.VARIABLE, false, false) - - )); - - types.add(new Type("CtAnonymousExecutable", spoon.reflect.declaration.CtAnonymousExecutable.class, spoon.support.reflect.declaration.CtAnonymousExecutableImpl.class, fm -> fm - .field(CtRole.NAME, true, true) - .field(CtRole.TYPE, true, true) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.PARAMETER, true, true) - .field(CtRole.THROWN, true, true) - .field(CtRole.MODIFIER, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.BODY, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtComment", spoon.reflect.code.CtComment.class, spoon.support.reflect.code.CtCommentImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.LABEL, false, false) - .field(CtRole.COMMENT_CONTENT, false, false) - .field(CtRole.COMMENT_TYPE, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.COMMENT, false, false) - .field(CtRole.ANNOTATION, false, false) - - )); - - types.add(new Type("CtWildcardReference", spoon.reflect.reference.CtWildcardReference.class, spoon.support.reflect.reference.CtWildcardReferenceImpl.class, fm -> fm - .field(CtRole.NAME, true, true) - .field(CtRole.IS_SHADOW, false, false) - .field(CtRole.IS_UPPER, false, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.MODIFIER, true, true) - .field(CtRole.COMMENT, true, true) - .field(CtRole.INTERFACE, true, true) - .field(CtRole.SUPER_TYPE, true, true) - .field(CtRole.TYPE_ARGUMENT, true, true) - .field(CtRole.POSITION, false, false) - .field(CtRole.PACKAGE_REF, false, false) - .field(CtRole.DECLARING_TYPE, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.BOUNDING_TYPE, false, false) - - )); - - types.add(new Type("CtThisAccess", spoon.reflect.code.CtThisAccess.class, spoon.support.reflect.code.CtThisAccessImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.COMMENT, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.TYPE, false, false) - .field(CtRole.CAST, false, false) - .field(CtRole.TARGET, false, false) - - )); - - types.add(new Type("CtArrayWrite", spoon.reflect.code.CtArrayWrite.class, spoon.support.reflect.code.CtArrayWriteImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.TYPE, false, false) - .field(CtRole.CAST, false, false) - .field(CtRole.TARGET, false, false) - .field(CtRole.EXPRESSION, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtPackageReference", spoon.reflect.reference.CtPackageReference.class, spoon.support.reflect.reference.CtPackageReferenceImpl.class, fm -> fm - .field(CtRole.NAME, false, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.COMMENT, true, true) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - - )); - - types.add(new Type("CtJavaDoc", spoon.reflect.code.CtJavaDoc.class, spoon.support.reflect.code.CtJavaDocImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.LABEL, false, false) - .field(CtRole.COMMENT_CONTENT, false, false) - .field(CtRole.COMMENT_TYPE, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.COMMENT, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.COMMENT_TAG, false, false) - - )); - - types.add(new Type("CtArrayRead", spoon.reflect.code.CtArrayRead.class, spoon.support.reflect.code.CtArrayReadImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.TYPE, false, false) - .field(CtRole.CAST, false, false) - .field(CtRole.TARGET, false, false) - .field(CtRole.EXPRESSION, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtStatementList", spoon.reflect.code.CtStatementList.class, spoon.support.reflect.code.CtStatementListImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.STATEMENT, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtVariableWrite", spoon.reflect.code.CtVariableWrite.class, spoon.support.reflect.code.CtVariableWriteImpl.class, fm -> fm - .field(CtRole.TYPE, true, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.CAST, false, false) - .field(CtRole.VARIABLE, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtParameterReference", spoon.reflect.reference.CtParameterReference.class, spoon.support.reflect.reference.CtParameterReferenceImpl.class, fm -> fm - .field(CtRole.NAME, false, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.COMMENT, true, true) - .field(CtRole.POSITION, false, false) - .field(CtRole.TYPE, false, false) - .field(CtRole.ANNOTATION, false, false) - - )); - - types.add(new Type("CtOperatorAssignment", spoon.reflect.code.CtOperatorAssignment.class, spoon.support.reflect.code.CtOperatorAssignmentImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.LABEL, false, false) - .field(CtRole.OPERATOR_KIND, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.TYPE, false, false) - .field(CtRole.CAST, false, false) - .field(CtRole.ASSIGNED, false, false) - .field(CtRole.ASSIGNMENT, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtAnnotationFieldAccess", spoon.reflect.code.CtAnnotationFieldAccess.class, spoon.support.reflect.code.CtAnnotationFieldAccessImpl.class, fm -> fm - .field(CtRole.TYPE, true, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.COMMENT, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.CAST, false, false) - .field(CtRole.TARGET, false, false) - .field(CtRole.VARIABLE, false, false) - - )); - - types.add(new Type("CtUnboundVariableReference", spoon.reflect.reference.CtUnboundVariableReference.class, spoon.support.reflect.reference.CtUnboundVariableReferenceImpl.class, fm -> fm - .field(CtRole.NAME, false, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.COMMENT, true, true) - .field(CtRole.ANNOTATION, true, true) - .field(CtRole.POSITION, false, false) - .field(CtRole.TYPE, false, false) - - )); - - types.add(new Type("CtAnnotationMethod", spoon.reflect.declaration.CtAnnotationMethod.class, spoon.support.reflect.declaration.CtAnnotationMethodImpl.class, fm -> fm - .field(CtRole.NAME, false, false) - .field(CtRole.BODY, true, true) - .field(CtRole.IS_SHADOW, false, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.IS_DEFAULT, false, false) - .field(CtRole.PARAMETER, true, true) - .field(CtRole.THROWN, true, true) - .field(CtRole.MODIFIER, false, false) - .field(CtRole.TYPE_PARAMETER, true, true) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.TYPE, false, false) - .field(CtRole.DEFAULT_EXPRESSION, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtClass", spoon.reflect.declaration.CtClass.class, spoon.support.reflect.declaration.CtClassImpl.class, fm -> fm - .field(CtRole.NAME, false, false) - .field(CtRole.IS_SHADOW, false, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.LABEL, true, true) - .field(CtRole.MODIFIER, false, false) - .field(CtRole.NESTED_TYPE, true, false) - .field(CtRole.CONSTRUCTOR, true, false) - .field(CtRole.METHOD, true, false) - .field(CtRole.ANNONYMOUS_EXECUTABLE, true, false) - .field(CtRole.FIELD, true, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.SUPER_TYPE, false, false) - .field(CtRole.INTERFACE, false, false) - .field(CtRole.TYPE_PARAMETER, false, false) - .field(CtRole.TYPE_MEMBER, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtBlock", spoon.reflect.code.CtBlock.class, spoon.support.reflect.code.CtBlockImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.LABEL, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.STATEMENT, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtPackage", spoon.reflect.declaration.CtPackage.class, spoon.support.reflect.declaration.CtPackageImpl.class, fm -> fm - .field(CtRole.NAME, false, false) - .field(CtRole.IS_SHADOW, false, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.SUB_PACKAGE, false, false) - .field(CtRole.CONTAINED_TYPE, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtTryWithResource", spoon.reflect.code.CtTryWithResource.class, spoon.support.reflect.code.CtTryWithResourceImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.LABEL, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.TRY_RESOURCE, false, false) - .field(CtRole.BODY, false, false) - .field(CtRole.CATCH, false, false) - .field(CtRole.FINALIZER, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtAssert", spoon.reflect.code.CtAssert.class, spoon.support.reflect.code.CtAssertImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.LABEL, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.CONDITION, false, false) - .field(CtRole.EXPRESSION, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtSwitch", spoon.reflect.code.CtSwitch.class, spoon.support.reflect.code.CtSwitchImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.LABEL, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.EXPRESSION, false, false) - .field(CtRole.CASE, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtTry", spoon.reflect.code.CtTry.class, spoon.support.reflect.code.CtTryImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.LABEL, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.BODY, false, false) - .field(CtRole.CATCH, false, false) - .field(CtRole.FINALIZER, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtSynchronized", spoon.reflect.code.CtSynchronized.class, spoon.support.reflect.code.CtSynchronizedImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.LABEL, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.EXPRESSION, false, false) - .field(CtRole.BODY, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtImport", spoon.reflect.declaration.CtImport.class, spoon.support.reflect.declaration.CtImportImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.IMPORT_REFERENCE, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtTypeParameterReference", spoon.reflect.reference.CtTypeParameterReference.class, spoon.support.reflect.reference.CtTypeParameterReferenceImpl.class, fm -> fm - .field(CtRole.NAME, false, false) - .field(CtRole.IS_SHADOW, false, false) - .field(CtRole.IS_UPPER, false, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.MODIFIER, true, true) - .field(CtRole.COMMENT, true, true) - .field(CtRole.INTERFACE, true, true) - .field(CtRole.SUPER_TYPE, true, true) - .field(CtRole.TYPE_ARGUMENT, true, true) - .field(CtRole.POSITION, false, false) - .field(CtRole.PACKAGE_REF, false, false) - .field(CtRole.DECLARING_TYPE, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.BOUNDING_TYPE, false, false) - - )); - - types.add(new Type("CtInvocation", spoon.reflect.code.CtInvocation.class, spoon.support.reflect.code.CtInvocationImpl.class, fm -> fm - .field(CtRole.TYPE, true, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.LABEL, false, false) - .field(CtRole.TYPE_ARGUMENT, true, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.CAST, false, false) - .field(CtRole.TARGET, false, false) - .field(CtRole.EXECUTABLE_REF, false, false) - .field(CtRole.ARGUMENT, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtCodeSnippetExpression", spoon.reflect.code.CtCodeSnippetExpression.class, spoon.support.reflect.code.CtCodeSnippetExpressionImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.SNIPPET, false, false) - .field(CtRole.TYPE, false, false) - .field(CtRole.COMMENT, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.CAST, false, false) - - )); - - types.add(new Type("CtFieldWrite", spoon.reflect.code.CtFieldWrite.class, spoon.support.reflect.code.CtFieldWriteImpl.class, fm -> fm - .field(CtRole.TYPE, true, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.CAST, false, false) - .field(CtRole.TARGET, false, false) - .field(CtRole.VARIABLE, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtUnaryOperator", spoon.reflect.code.CtUnaryOperator.class, spoon.support.reflect.code.CtUnaryOperatorImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.LABEL, false, false) - .field(CtRole.OPERATOR_KIND, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.TYPE, false, false) - .field(CtRole.CAST, false, false) - .field(CtRole.EXPRESSION, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtExecutableReference", spoon.reflect.reference.CtExecutableReference.class, spoon.support.reflect.reference.CtExecutableReferenceImpl.class, fm -> fm - .field(CtRole.NAME, false, false) - .field(CtRole.IS_STATIC, false, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.DECLARING_TYPE, false, false) - .field(CtRole.TYPE, false, false) - .field(CtRole.ARGUMENT_TYPE, false, false) - .field(CtRole.TYPE_ARGUMENT, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.COMMENT, true, true) - - )); - - types.add(new Type("CtFor", spoon.reflect.code.CtFor.class, spoon.support.reflect.code.CtForImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.LABEL, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.FOR_INIT, false, false) - .field(CtRole.EXPRESSION, false, false) - .field(CtRole.FOR_UPDATE, false, false) - .field(CtRole.BODY, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtVariableRead", spoon.reflect.code.CtVariableRead.class, spoon.support.reflect.code.CtVariableReadImpl.class, fm -> fm - .field(CtRole.TYPE, true, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.CAST, false, false) - .field(CtRole.VARIABLE, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtTypeParameter", spoon.reflect.declaration.CtTypeParameter.class, spoon.support.reflect.declaration.CtTypeParameterImpl.class, fm -> fm - .field(CtRole.NAME, false, false) - .field(CtRole.IS_SHADOW, false, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.MODIFIER, true, true) - .field(CtRole.INTERFACE, true, true) - .field(CtRole.TYPE_MEMBER, true, true) - .field(CtRole.NESTED_TYPE, true, true) - .field(CtRole.METHOD, true, true) - .field(CtRole.FIELD, true, true) - .field(CtRole.TYPE_PARAMETER, true, true) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.SUPER_TYPE, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtLocalVariable", spoon.reflect.code.CtLocalVariable.class, spoon.support.reflect.code.CtLocalVariableImpl.class, fm -> fm - .field(CtRole.NAME, false, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.LABEL, false, false) - .field(CtRole.ASSIGNMENT, true, true) - .field(CtRole.MODIFIER, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.TYPE, false, false) - .field(CtRole.DEFAULT_EXPRESSION, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtIf", spoon.reflect.code.CtIf.class, spoon.support.reflect.code.CtIfImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.LABEL, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.CONDITION, false, false) - .field(CtRole.THEN, false, false) - .field(CtRole.ELSE, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtModule", spoon.reflect.declaration.CtModule.class, spoon.support.reflect.declaration.CtModuleImpl.class, fm -> fm - .field(CtRole.NAME, false, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.MODIFIER, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.REQUIRED_MODULE, true, false) - .field(CtRole.EXPORTED_PACKAGE, true, false) - .field(CtRole.OPENED_PACKAGE, true, false) - .field(CtRole.SERVICE_TYPE, true, false) - .field(CtRole.PROVIDED_SERVICE, true, false) - .field(CtRole.COMMENT, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.MODULE_DIRECTIVE, false, false) - .field(CtRole.SUB_PACKAGE, false, false) - - )); - - types.add(new Type("CtPackageExport", spoon.reflect.declaration.CtPackageExport.class, spoon.support.reflect.declaration.CtPackageExportImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.OPENED_PACKAGE, false, false) - .field(CtRole.COMMENT, false, false) - .field(CtRole.PACKAGE_REF, false, false) - .field(CtRole.MODULE_REF, false, false) - .field(CtRole.ANNOTATION, false, false) - - )); - - types.add(new Type("CtConstructorCall", spoon.reflect.code.CtConstructorCall.class, spoon.support.reflect.code.CtConstructorCallImpl.class, fm -> fm - .field(CtRole.TYPE, true, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.LABEL, false, false) - .field(CtRole.TYPE_ARGUMENT, true, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.CAST, false, false) - .field(CtRole.EXECUTABLE_REF, false, false) - .field(CtRole.TARGET, false, false) - .field(CtRole.ARGUMENT, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtCase", spoon.reflect.code.CtCase.class, spoon.support.reflect.code.CtCaseImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.LABEL, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.EXPRESSION, false, false) - .field(CtRole.STATEMENT, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtModuleReference", spoon.reflect.reference.CtModuleReference.class, spoon.support.reflect.reference.CtModuleReferenceImpl.class, fm -> fm - .field(CtRole.NAME, false, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.COMMENT, true, true) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - - )); - - types.add(new Type("CtCatch", spoon.reflect.code.CtCatch.class, spoon.support.reflect.code.CtCatchImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.PARAMETER, false, false) - .field(CtRole.BODY, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtArrayTypeReference", spoon.reflect.reference.CtArrayTypeReference.class, spoon.support.reflect.reference.CtArrayTypeReferenceImpl.class, fm -> fm - .field(CtRole.NAME, false, false) - .field(CtRole.IS_SHADOW, false, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.MODIFIER, true, true) - .field(CtRole.INTERFACE, true, true) - .field(CtRole.SUPER_TYPE, true, true) - .field(CtRole.POSITION, false, false) - .field(CtRole.COMMENT, true, true) - .field(CtRole.PACKAGE_REF, false, false) - .field(CtRole.DECLARING_TYPE, false, false) - .field(CtRole.TYPE, false, false) - .field(CtRole.TYPE_ARGUMENT, false, false) - .field(CtRole.ANNOTATION, false, false) - - )); - - types.add(new Type("CtMethod", spoon.reflect.declaration.CtMethod.class, spoon.support.reflect.declaration.CtMethodImpl.class, fm -> fm - .field(CtRole.NAME, false, false) - .field(CtRole.IS_SHADOW, false, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.IS_DEFAULT, false, false) - .field(CtRole.MODIFIER, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.TYPE_PARAMETER, false, false) - .field(CtRole.TYPE, false, false) - .field(CtRole.PARAMETER, false, false) - .field(CtRole.THROWN, false, false) - .field(CtRole.BODY, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtLambda", spoon.reflect.code.CtLambda.class, spoon.support.reflect.code.CtLambdaImpl.class, fm -> fm - .field(CtRole.NAME, false, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.TYPE, false, false) - .field(CtRole.CAST, false, false) - .field(CtRole.PARAMETER, false, false) - .field(CtRole.THROWN, true, true) - .field(CtRole.BODY, false, false) - .field(CtRole.EXPRESSION, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtNewArray", spoon.reflect.code.CtNewArray.class, spoon.support.reflect.code.CtNewArrayImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.TYPE, false, false) - .field(CtRole.CAST, false, false) - .field(CtRole.EXPRESSION, false, false) - .field(CtRole.DIMENSION, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtUsedService", spoon.reflect.declaration.CtUsedService.class, spoon.support.reflect.declaration.CtUsedServiceImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.COMMENT, false, false) - .field(CtRole.SERVICE_TYPE, false, false) - .field(CtRole.ANNOTATION, false, false) - - )); - - types.add(new Type("CtIntersectionTypeReference", spoon.reflect.reference.CtIntersectionTypeReference.class, spoon.support.reflect.reference.CtIntersectionTypeReferenceImpl.class, fm -> fm - .field(CtRole.NAME, false, false) - .field(CtRole.IS_SHADOW, false, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.MODIFIER, true, true) - .field(CtRole.COMMENT, true, true) - .field(CtRole.INTERFACE, true, true) - .field(CtRole.SUPER_TYPE, true, true) - .field(CtRole.POSITION, false, false) - .field(CtRole.PACKAGE_REF, false, false) - .field(CtRole.DECLARING_TYPE, false, false) - .field(CtRole.TYPE_ARGUMENT, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.BOUND, false, false) - - )); - - types.add(new Type("CtThrow", spoon.reflect.code.CtThrow.class, spoon.support.reflect.code.CtThrowImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.LABEL, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.EXPRESSION, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtLiteral", spoon.reflect.code.CtLiteral.class, spoon.support.reflect.code.CtLiteralImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.VALUE, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.TYPE, false, false) - .field(CtRole.CAST, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtReturn", spoon.reflect.code.CtReturn.class, spoon.support.reflect.code.CtReturnImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.LABEL, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.EXPRESSION, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtJavaDocTag", spoon.reflect.code.CtJavaDocTag.class, spoon.support.reflect.code.CtJavaDocTagImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.COMMENT_CONTENT, false, false) - .field(CtRole.DOCUMENTATION_TYPE, false, false) - .field(CtRole.JAVADOC_TAG_VALUE, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.COMMENT, false, false) - .field(CtRole.ANNOTATION, false, false) - - )); - - types.add(new Type("CtField", spoon.reflect.declaration.CtField.class, spoon.support.reflect.declaration.CtFieldImpl.class, fm -> fm - .field(CtRole.NAME, false, false) - .field(CtRole.IS_SHADOW, false, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.ASSIGNMENT, true, true) - .field(CtRole.MODIFIER, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.TYPE, false, false) - .field(CtRole.DEFAULT_EXPRESSION, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtTypeAccess", spoon.reflect.code.CtTypeAccess.class, spoon.support.reflect.code.CtTypeAccessImpl.class, fm -> fm - .field(CtRole.TYPE, true, true) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.CAST, false, false) - .field(CtRole.ACCESSED_TYPE, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtCodeSnippetStatement", spoon.reflect.code.CtCodeSnippetStatement.class, spoon.support.reflect.code.CtCodeSnippetStatementImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.LABEL, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.SNIPPET, false, false) - .field(CtRole.COMMENT, false, false) - .field(CtRole.ANNOTATION, false, false) - - )); - - types.add(new Type("CtDo", spoon.reflect.code.CtDo.class, spoon.support.reflect.code.CtDoImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.LABEL, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.EXPRESSION, false, false) - .field(CtRole.BODY, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtAnnotation", spoon.reflect.declaration.CtAnnotation.class, spoon.support.reflect.declaration.CtAnnotationImpl.class, fm -> fm - .field(CtRole.IS_SHADOW, false, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.CAST, true, true) - .field(CtRole.POSITION, false, false) - .field(CtRole.TYPE, false, false) - .field(CtRole.COMMENT, false, false) - .field(CtRole.ANNOTATION_TYPE, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.VALUE, false, false) - - )); - - types.add(new Type("CtFieldRead", spoon.reflect.code.CtFieldRead.class, spoon.support.reflect.code.CtFieldReadImpl.class, fm -> fm - .field(CtRole.TYPE, true, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.CAST, false, false) - .field(CtRole.TARGET, false, false) - .field(CtRole.VARIABLE, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtBreak", spoon.reflect.code.CtBreak.class, spoon.support.reflect.code.CtBreakImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.LABEL, false, false) - .field(CtRole.TARGET_LABEL, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtFieldReference", spoon.reflect.reference.CtFieldReference.class, spoon.support.reflect.reference.CtFieldReferenceImpl.class, fm -> fm - .field(CtRole.NAME, false, false) - .field(CtRole.IS_FINAL, false, false) - .field(CtRole.IS_STATIC, false, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.COMMENT, true, true) - .field(CtRole.POSITION, false, false) - .field(CtRole.DECLARING_TYPE, false, false) - .field(CtRole.TYPE, false, false) - .field(CtRole.ANNOTATION, false, false) - - )); - - types.add(new Type("CtEnum", spoon.reflect.declaration.CtEnum.class, spoon.support.reflect.declaration.CtEnumImpl.class, fm -> fm - .field(CtRole.NAME, false, false) - .field(CtRole.IS_SHADOW, false, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.LABEL, true, true) - .field(CtRole.MODIFIER, false, false) - .field(CtRole.SUPER_TYPE, true, true) - .field(CtRole.NESTED_TYPE, true, false) - .field(CtRole.CONSTRUCTOR, true, false) - .field(CtRole.METHOD, true, false) - .field(CtRole.ANNONYMOUS_EXECUTABLE, true, false) - .field(CtRole.FIELD, true, false) - .field(CtRole.TYPE_PARAMETER, true, true) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.INTERFACE, false, false) - .field(CtRole.TYPE_MEMBER, false, false) - .field(CtRole.VALUE, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtNewClass", spoon.reflect.code.CtNewClass.class, spoon.support.reflect.code.CtNewClassImpl.class, fm -> fm - .field(CtRole.TYPE, true, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.LABEL, false, false) - .field(CtRole.TYPE_ARGUMENT, true, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.CAST, false, false) - .field(CtRole.EXECUTABLE_REF, false, false) - .field(CtRole.TARGET, false, false) - .field(CtRole.ARGUMENT, false, false) - .field(CtRole.NESTED_TYPE, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtLocalVariableReference", spoon.reflect.reference.CtLocalVariableReference.class, spoon.support.reflect.reference.CtLocalVariableReferenceImpl.class, fm -> fm - .field(CtRole.NAME, false, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.COMMENT, true, true) - .field(CtRole.POSITION, false, false) - .field(CtRole.TYPE, false, false) - .field(CtRole.ANNOTATION, false, false) - - )); - - types.add(new Type("CtAnnotationType", spoon.reflect.declaration.CtAnnotationType.class, spoon.support.reflect.declaration.CtAnnotationTypeImpl.class, fm -> fm - .field(CtRole.NAME, false, false) - .field(CtRole.IS_SHADOW, false, false) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.MODIFIER, false, false) - .field(CtRole.INTERFACE, true, true) - .field(CtRole.SUPER_TYPE, true, true) - .field(CtRole.NESTED_TYPE, true, false) - .field(CtRole.METHOD, true, false) - .field(CtRole.FIELD, true, false) - .field(CtRole.TYPE_PARAMETER, true, true) - .field(CtRole.POSITION, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.TYPE_MEMBER, false, false) - .field(CtRole.COMMENT, false, false) - - )); - - types.add(new Type("CtCatchVariable", spoon.reflect.code.CtCatchVariable.class, spoon.support.reflect.code.CtCatchVariableImpl.class, fm -> fm - .field(CtRole.NAME, false, false) - .field(CtRole.TYPE, true, true) - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.DEFAULT_EXPRESSION, true, true) - .field(CtRole.MODIFIER, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.COMMENT, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.MULTI_TYPE, false, false) - - )); - - types.add(new Type("CtExecutableReferenceExpression", spoon.reflect.code.CtExecutableReferenceExpression.class, spoon.support.reflect.code.CtExecutableReferenceExpressionImpl.class, fm -> fm - .field(CtRole.IS_IMPLICIT, false, false) - .field(CtRole.POSITION, false, false) - .field(CtRole.COMMENT, false, false) - .field(CtRole.ANNOTATION, false, false) - .field(CtRole.TYPE, false, false) - .field(CtRole.CAST, false, false) - .field(CtRole.EXECUTABLE_REF, false, false) - .field(CtRole.TARGET, false, false) - - )); - } -} diff --git a/src/test/java/spoon/test/api/MetamodelTest.java b/src/test/java/spoon/test/api/MetamodelTest.java index 03dceb16083..3719f348942 100644 --- a/src/test/java/spoon/test/api/MetamodelTest.java +++ b/src/test/java/spoon/test/api/MetamodelTest.java @@ -5,7 +5,6 @@ import spoon.Launcher; import spoon.SpoonAPI; import spoon.SpoonException; -import spoon.metamodel.ConceptKind; import spoon.metamodel.MMMethodKind; import spoon.metamodel.Metamodel; import spoon.metamodel.MetamodelConcept; @@ -80,42 +79,6 @@ public void testGetAllMetamodelInterfacess() { assertThat(Metamodel.getAllMetamodelInterfaces().stream().map(x->x.getQualifiedName()).collect(Collectors.toSet()), equalTo(interfaces.getModel().getAllTypes().stream().map(x->x.getQualifiedName()).collect(Collectors.toSet()))); } - @Test - public void testRuntimeMetamodel() { - // contract: Spoon supports runtime introspection on the metamodel - all (non abstract) Spoon classes and their fields are accessible by Metamodel - Metamodel testMetaModel = Metamodel.getInstance(); - Map expectedTypesByName = new HashMap<>(); - testMetaModel.getConcepts().forEach(t -> { - if (t.getKind() == ConceptKind.LEAF) { - expectedTypesByName.put(t.getName(), t); - } - }); - List problems = new ArrayList<>(); - for (spoon.test.api.Metamodel.Type type : spoon.test.api.Metamodel.getAllMetamodelTypes()) { - MetamodelConcept expectedType = expectedTypesByName.remove(type.getName()); - assertSame(expectedType.getImplementationClass().getActualClass(), type.getModelClass()); - assertSame(expectedType.getMetamodelInterface().getActualClass(), type.getModelInterface()); - Map expectedRoleToField = new HashMap<>(expectedType.getRoleToProperty()); - for (spoon.test.api.Metamodel.Field field : type.getFields()) { - MetamodelProperty expectedField = expectedRoleToField.remove(field.getRole()); - if (expectedField.isDerived() != field.isDerived()) { - problems.add("Field " + expectedField + ".derived hardcoded value = " + field.isDerived() + " but computed value is " + expectedField.isDerived()); - } - if (expectedField.isUnsettable() != field.isUnsettable()) { - problems.add("Field " + expectedField + ".unsettable hardcoded value = " + field.isUnsettable() + " but computed value is " + expectedField.isUnsettable()); - } - } - if (expectedRoleToField.isEmpty() == false) { - problems.add("These Metamodel.Field instances are missing on Type " + type.getName() +": " + expectedRoleToField.keySet()); - } - } - if (expectedTypesByName.isEmpty() == false) { - problems.add("These Metamodel.Type instances are missing:" + expectedTypesByName.keySet()); - } - assertTrue(String.join("\n", problems), problems.isEmpty()); - } - - @Test public void testGetterSetterFroRole() { // contract: all roles in spoon metamodel must at least have a setter and a getter