All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
spoon.pattern.internal.parameter.AbstractParameterInfo Maven / Gradle / Ivy
Go to download
Spoon is a tool for meta-programming, analysis and transformation of Java programs.
/*
* SPDX-License-Identifier: (MIT OR CECILL-C)
*
* Copyright (C) 2006-2023 INRIA and contributors
*
* Spoon is available either under the terms of the MIT License (see LICENSE-MIT.txt) or the Cecill-C License (see LICENSE-CECILL-C.txt). You as the user are entitled to choose the terms under which to adopt Spoon.
*/
package spoon.pattern.internal.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.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;
import spoon.reflect.meta.ContainerKind;
import spoon.reflect.reference.CtTypeReference;
import spoon.support.util.ImmutableMap;
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 AbstractParameterInfo containerItemAccessor;
private ContainerKind containerKind = null;
private Boolean repeatable = null;
private int minOccurrences = 0;
private int maxOccurrences = UNLIMITED_OCCURRENCES;
private Quantifier matchingStrategy = Quantifier.GREEDY;
private ValueConvertor valueConvertor;
private Predicate matchCondition;
private Class> parameterValueType;
protected AbstractParameterInfo(ParameterInfo containerItemAccessor) {
this.containerItemAccessor = (AbstractParameterInfo) containerItemAccessor;
}
protected String getContainerName() {
if (containerItemAccessor != null) {
return containerItemAccessor.getPlainName();
}
return "";
}
@Override
public final String getName() {
AbstractParameterInfo cca = getContainerKindAccessor(getContainerKind(null, null));
if (cca != null) {
return cca.getWrappedName(getPlainName());
}
// this string can be used later in setSimpleName, which does not allow square brackets, so we replace them
return getPlainName().replace("[", "_").replace("]", "_");
}
protected abstract String getPlainName();
protected abstract String getWrappedName(String containerName);
@Override
public ImmutableMap addValueAs(ImmutableMap parameters, Object value) {
Class> requiredType = getParameterValueType();
if (requiredType != null && value != null && !requiredType.isInstance(value)) {
return null;
}
if (!matches(value)) {
return null;
}
Object newContainer = addValueToContainer(parameters, existingValue -> {
return merge(existingValue, value);
});
if (newContainer == NO_MERGE) {
return null;
}
return (ImmutableMap) 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);
AbstractParameterInfo cca = getContainerKindAccessor(cc);
if (cca == null) {
return mergeSingle(existingValue, newValue);
}
return cca.addValueAs(existingValue,
existingListItemValue -> mergeSingle(existingListItemValue, newValue));
}
protected AbstractParameterInfo getContainerKindAccessor(ContainerKind containerKind) {
switch (containerKind) {
case SINGLE:
return null;
case LIST:
return new ListParameterInfo(this);
case SET:
return new SetParameterInfo(this);
case MAP:
return new MapParameterInfo(this);
}
throw new SpoonException("Unexpected ContainerKind " + containerKind);
}
protected Object mergeSingle(Object existingValue, Object newValue) {
if (newValue == null && getMinOccurrences() > 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();
/**
* @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;
}
/**
* 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()))) {
return false;
}
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
*/
@Override
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;
}
/**
* @return true if the value container has to be a List, otherwise the container will be a single value
*/
@Override
public boolean isMultiple() {
return getContainerKind(null, null) != ContainerKind.SINGLE;
}
/**
* @param repeatable if this matcher can be applied more than 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;
}
public int getMinOccurrences() {
return minOccurrences;
}
public AbstractParameterInfo setMinOccurrences(int minOccurrences) {
this.minOccurrences = minOccurrences;
return this;
}
/**
* @return maximum number of values in this parameter.
* Note: if {@link #isMultiple()}==false, then it never returns value > 1
*/
public int getMaxOccurrences() {
return isMultiple() ? maxOccurrences : Math.min(maxOccurrences, 1);
}
public void setMaxOccurrences(int maxOccurrences) {
this.maxOccurrences = maxOccurrences;
}
@Override
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 AbstractParameterInfo 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 than once in the same container of targets
* Note: even if false, it may be applied again to another container and to match EQUAL value
*/
@Override
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
*/
@Override
public boolean isMandatory(ImmutableMap parameters) {
int nrOfValues = getNumberOfValues(parameters);
//current number of values is smaller than minimum number of values. Value is mandatory
return nrOfValues < getMinOccurrences();
}
/**
* @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`.
*/
@Override
public boolean isTryNextMatch(ImmutableMap 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 than maximum number of values. Can try next match
return nrOfValues < getMaxOccurrences();
}
/**
* @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(ImmutableMap parameters) {
if (!parameters.hasValue(getName())) {
return 0;
}
Object value = parameters.getValue(getName());
if (value instanceof Collection) {
return ((Collection) value).size();
}
return 1;
}
public ContainerKind getContainerKind() {
return containerKind;
}
public AbstractParameterInfo setContainerKind(ContainerKind containerKind) {
this.containerKind = containerKind;
return this;
}
protected ContainerKind getContainerKind(ImmutableMap params) {
return getContainerKind(params.getValue(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 ImmutableMap) {
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 ImmutableMap) {
return ContainerKind.MAP;
}
return ContainerKind.SINGLE;
}
@Override
public void getValueAs(Factory factory, ResultHolder result, ImmutableMap 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(factory, result, rawValue);
}
protected Object getValue(ImmutableMap parameters) {
if (containerItemAccessor != null) {
return containerItemAccessor.getValue(parameters);
}
return parameters;
}
protected void convertValue(Factory factory, ResultHolder result, Object rawValue) {
//convert raw parameter value to expected type
if (result.isMultiple()) {
forEachItem(rawValue, singleValue -> {
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(convertSingleValue(factory, rawValue, result.getRequiredClass()));
}
}
protected T convertSingleValue(Factory factory, Object value, Class type) {
ValueConvertor valueConvertor = getValueConvertor();
return valueConvertor.getValueAs(factory, getName(), 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();
sb.append(getName());
if (getParameterValueType() != null) {
sb.append(" : ");
sb.append(getParameterValueType().getName());
}
return sb.toString();
}
}