/*******************************************************************************
* Copyright (c) 2013 Rene Schneider, GEBIT Solutions GmbH and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
package de.gebit.integrity.parameter.resolving;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import de.gebit.integrity.dsl.ArbitraryParameterOrResultName;
import de.gebit.integrity.dsl.Call;
import de.gebit.integrity.dsl.ConstantDefinition;
import de.gebit.integrity.dsl.CustomOperation;
import de.gebit.integrity.dsl.EnumValue;
import de.gebit.integrity.dsl.KeyValuePair;
import de.gebit.integrity.dsl.NamedResult;
import de.gebit.integrity.dsl.NestedObject;
import de.gebit.integrity.dsl.Parameter;
import de.gebit.integrity.dsl.ParameterName;
import de.gebit.integrity.dsl.ParameterTableHeader;
import de.gebit.integrity.dsl.StandardOperation;
import de.gebit.integrity.dsl.StaticValue;
import de.gebit.integrity.dsl.TableTest;
import de.gebit.integrity.dsl.TableTestRow;
import de.gebit.integrity.dsl.Test;
import de.gebit.integrity.dsl.TypedNestedObject;
import de.gebit.integrity.dsl.ValueOrEnumValueOrOperation;
import de.gebit.integrity.dsl.ValueOrEnumValueOrOperationCollection;
import de.gebit.integrity.dsl.Variable;
import de.gebit.integrity.dsl.VariableDefinition;
import de.gebit.integrity.dsl.VariableOrConstantEntity;
import de.gebit.integrity.dsl.VariantDefinition;
import de.gebit.integrity.exceptions.ThisShouldNeverHappenException;
import de.gebit.integrity.operations.UnexecutableException;
import de.gebit.integrity.operations.custom.CustomOperationWrapper;
import de.gebit.integrity.operations.standard.StandardOperationProcessor;
import de.gebit.integrity.parameter.conversion.UnresolvableVariable;
import de.gebit.integrity.parameter.conversion.UnresolvableVariableHandling;
import de.gebit.integrity.parameter.variables.VariableManager;
import de.gebit.integrity.utils.IntegrityDSLUtil;
import de.gebit.integrity.utils.ParameterUtil.UnresolvableVariableException;
import de.gebit.integrity.wrapper.WrapperFactory;
/**
* The default implementation of a parameter resolver.
*
* @author Rene Schneider - initial API and implementation
*
*/
@Singleton
public class DefaultParameterResolver implements ParameterResolver {
/**
* The wrapper factory to use.
*/
@Inject(optional = true)
protected WrapperFactory wrapperFactory;
/**
* The variable manager to use.
*/
@Inject(optional = true)
protected VariableManager variableManager;
/**
* The processor for standard operations.
*/
@Inject
protected StandardOperationProcessor standardOperationProcessor;
@Override
public Map<String, Object> createParameterMap(Test aTest, boolean anIncludeArbitraryParametersFlag,
UnresolvableVariableHandling anUnresolvableVariableHandlingPolicy)
throws ClassNotFoundException, UnexecutableException, InstantiationException {
return createParameterMap(aTest.getParameters(), anIncludeArbitraryParametersFlag,
anUnresolvableVariableHandlingPolicy);
}
@Override
public Map<String, Object> createParameterMap(Call aCall, boolean anIncludeArbitraryParametersFlag,
UnresolvableVariableHandling anUnresolvableVariableHandlingPolicy)
throws ClassNotFoundException, UnexecutableException, InstantiationException {
return createParameterMap(aCall.getParameters(), anIncludeArbitraryParametersFlag,
anUnresolvableVariableHandlingPolicy);
}
@Override
public Map<String, Object> createParameterMap(TableTest aTableTest, TableTestRow aTableTestRow,
TableTestParameterResolveMethod aResolveMethod, boolean anIncludeArbitraryParametersFlag,
UnresolvableVariableHandling anUnresolvableVariableHandlingPolicy)
throws ClassNotFoundException, UnexecutableException, InstantiationException {
LinkedHashMap<ParameterName, ValueOrEnumValueOrOperationCollection> tempParameterMap = new LinkedHashMap<ParameterName, ValueOrEnumValueOrOperationCollection>();
if (aResolveMethod == null || aResolveMethod == TableTestParameterResolveMethod.COMBINED
|| aResolveMethod == TableTestParameterResolveMethod.ONLY_COMMON) {
for (Parameter tempParameter : aTableTest.getParameters()) {
tempParameterMap.put(tempParameter.getName(), tempParameter.getValue());
}
}
if (aResolveMethod == null || aResolveMethod == TableTestParameterResolveMethod.COMBINED
|| aResolveMethod == TableTestParameterResolveMethod.ONLY_INDIVIDUAL) {
int tempCount = 0;
for (ParameterTableHeader tempParameterHeader : aTableTest.getParameterHeaders()) {
tempParameterMap.put(tempParameterHeader.getName(),
(aTableTestRow == null || tempCount >= aTableTestRow.getValues().size()) ? null
: aTableTestRow.getValues().get(tempCount).getValue());
tempCount++;
}
}
return createParameterMap(tempParameterMap, anIncludeArbitraryParametersFlag,
anUnresolvableVariableHandlingPolicy);
}
@Override
public Map<String, Object> createParameterMap(List<Parameter> someParameters,
boolean anIncludeArbitraryParametersFlag, UnresolvableVariableHandling anUnresolvableVariableHandlingPolicy)
throws ClassNotFoundException, UnexecutableException, InstantiationException {
Map<ParameterName, ValueOrEnumValueOrOperationCollection> tempParameters = new LinkedHashMap<ParameterName, ValueOrEnumValueOrOperationCollection>();
for (Parameter tempParameter : someParameters) {
tempParameters.put(tempParameter.getName(), tempParameter.getValue());
}
return createParameterMap(tempParameters, anIncludeArbitraryParametersFlag,
anUnresolvableVariableHandlingPolicy);
}
private Map<String, Object> createParameterMap(
Map<ParameterName, ValueOrEnumValueOrOperationCollection> someParameters,
boolean anIncludeArbitraryParametersFlag, UnresolvableVariableHandling anUnresolvableVariableHandlingPolicy)
throws ClassNotFoundException, UnexecutableException, InstantiationException {
Map<String, Object> tempResult = new LinkedHashMap<String, Object>();
for (Entry<ParameterName, ValueOrEnumValueOrOperationCollection> tempEntry : someParameters.entrySet()) {
if (tempEntry.getKey() != null && tempEntry.getValue() != null) {
Object tempValue = resolveParameterValue((ValueOrEnumValueOrOperationCollection) tempEntry.getValue(),
anUnresolvableVariableHandlingPolicy);
if (anIncludeArbitraryParametersFlag
|| !(tempEntry.getKey() instanceof ArbitraryParameterOrResultName)) {
String tempKey = IntegrityDSLUtil.getParamNameStringFromParameterName(tempEntry.getKey());
if (tempKey != null) {
tempResult.put(tempKey, tempValue);
}
}
}
}
return tempResult;
}
@Override
public Object resolveParameterValue(ValueOrEnumValueOrOperationCollection aValueCollection,
UnresolvableVariableHandling anUnresolvableVariableHandlingPolicy)
throws UnexecutableException, InstantiationException, ClassNotFoundException {
if (aValueCollection.getMoreValues().size() > 0) {
// if multiple values have been provided
Object[] tempValueArray = new Object[aValueCollection.getMoreValues().size() + 1];
tempValueArray[0] = aValueCollection.getValue();
for (int i = 0; i <= aValueCollection.getMoreValues().size(); i++) {
ValueOrEnumValueOrOperation tempSingleValue = (i == 0 ? aValueCollection.getValue()
: aValueCollection.getMoreValues().get(i - 1));
tempValueArray[i] = resolveSingleParameterValue(tempSingleValue, anUnresolvableVariableHandlingPolicy);
}
return tempValueArray;
} else {
// if only one value has been provided
return resolveSingleParameterValue(aValueCollection.getValue(), anUnresolvableVariableHandlingPolicy);
}
}
@Override
public Object resolveSingleParameterValue(ValueOrEnumValueOrOperation aValue,
UnresolvableVariableHandling anUnresolvableVariableHandlingPolicy)
throws UnexecutableException, InstantiationException, ClassNotFoundException {
if (aValue instanceof Variable) {
Variable tempVariable = (Variable) aValue;
Object tempResolvedValue = (variableManager != null ? variableManager.get(tempVariable) : null);
if (tempResolvedValue != null) {
// We may need to recurse here, as it is possible to have containers being returned by the variable
// manager
if (tempResolvedValue instanceof ValueOrEnumValueOrOperationCollection) {
return resolveParameterValue((ValueOrEnumValueOrOperationCollection) tempResolvedValue,
anUnresolvableVariableHandlingPolicy);
} else if (tempResolvedValue instanceof ValueOrEnumValueOrOperation) {
return resolveSingleParameterValue((ValueOrEnumValueOrOperation) tempResolvedValue,
anUnresolvableVariableHandlingPolicy);
} else {
return tempResolvedValue;
}
} else {
switch (anUnresolvableVariableHandlingPolicy) {
case KEEP_UNRESOLVED:
return tempVariable;
case RESOLVE_TO_NULL_VALUE:
return null;
case RESOLVE_TO_NAME_STRING:
return tempVariable.getName().getName();
case RESOLVE_TO_UNRESOLVABLE_OBJECT:
return UnresolvableVariable.getInstance();
case EXCEPTION:
default:
throw new UnresolvableVariableException(
"Unresolvable variable " + tempVariable.getName().getName() + " encountered!");
}
}
} else if (aValue instanceof StandardOperation) {
try {
return standardOperationProcessor.executeOperation((StandardOperation) aValue);
} catch (UnexecutableException exc) {
// this is expected to happen in some cases during dry run - but not a problem
return null;
}
} else if (aValue instanceof CustomOperation) {
if (wrapperFactory != null) {
CustomOperationWrapper tempWrapper = wrapperFactory.newCustomOperationWrapper((CustomOperation) aValue);
return tempWrapper.executeOperation();
} else {
return null;
}
} else {
// TODO what about nested objects with inner operations or variables?
}
return aValue;
}
@Override
public Object resolveStatically(Variable aVariable, VariantDefinition aVariant) {
VariableOrConstantEntity tempEntity = aVariable.getName();
return IntegrityDSLUtil.getInitialValueForVariableOrConstantEntity(tempEntity, aVariant);
}
@Override
public Object resolveStatically(ValueOrEnumValueOrOperationCollection aValue, VariantDefinition aVariant)
throws UnexecutableException, ClassNotFoundException, InstantiationException {
if (aValue instanceof Variable) {
VariableOrConstantEntity tempEntity = ((Variable) aValue).getName();
return resolveStatically(IntegrityDSLUtil.getInitialValueForVariableOrConstantEntity(tempEntity, aVariant),
aVariant);
} else if (aValue instanceof StandardOperation) {
return standardOperationProcessor.executeOperation((StandardOperation) aValue);
} else if (aValue instanceof CustomOperation) {
if (wrapperFactory != null) {
CustomOperationWrapper tempWrapper = wrapperFactory.newCustomOperationWrapper((CustomOperation) aValue);
return tempWrapper.executeOperation();
} else {
return null;
}
}
return aValue;
}
@Override
public boolean isSafelyStaticallyResolvable(ValueOrEnumValueOrOperationCollection aValue,
VariantDefinition aVariant) {
if (aValue == null) {
return true;
}
if (!isSafelyStaticallyResolvable(aValue.getValue(), aVariant)) {
return false;
}
for (ValueOrEnumValueOrOperation tempSubValue : aValue.getMoreValues()) {
if (!isSafelyStaticallyResolvable(tempSubValue, aVariant)) {
return false;
}
}
return true;
}
@Override
public boolean isSafelyStaticallyResolvable(ValueOrEnumValueOrOperation aValue, VariantDefinition aVariant) {
if (aValue == null) {
return true;
}
if (aValue instanceof StaticValue) {
return true;
} else if (aValue instanceof Variable) {
VariableOrConstantEntity tempEntity = ((Variable) aValue).getName();
if (tempEntity instanceof VariableDefinition) {
// Variables can be altered during runtime -> definitely not statically resolvable to a constant value
return false;
} else if (tempEntity instanceof ConstantDefinition) {
// trace the initial value and check whether that can be resolved
return isSafelyStaticallyResolvable(
IntegrityDSLUtil.getInitialValueForVariableOrConstantEntity(tempEntity, aVariant), aVariant);
}
} else if (aValue instanceof NestedObject) {
// Explore the nested object and check all attributes. Might of course create recursion, but hey, recursion
// is cool :-)
for (KeyValuePair tempAttribute : ((NestedObject) aValue).getAttributes()) {
if (!isSafelyStaticallyResolvable(tempAttribute.getValue(), aVariant)) {
return false;
}
}
} else if (aValue instanceof TypedNestedObject) {
// The same as above, but for typed nested objects (we simply recurse into the inner nested object)
return isSafelyStaticallyResolvable(((TypedNestedObject) aValue).getNestedObject(), aVariant);
} else if (aValue instanceof EnumValue) {
// Enum values only make sense in the context of a fixture call. This method does not require such a
// context, thus it considers enum values to be nonresolvable.
return false;
} else if (aValue instanceof StandardOperation) {
// All parameters of the operation must be statically resolvable, just like with nested objects.
if (!isSafelyStaticallyResolvable(((StandardOperation) aValue).getFirstOperand(), aVariant)) {
return false;
}
for (ValueOrEnumValueOrOperation tempSubValue : ((StandardOperation) aValue).getMoreOperands()) {
if (!isSafelyStaticallyResolvable(tempSubValue, aVariant)) {
return false;
}
}
return true;
} else if (aValue instanceof CustomOperation) {
// Custom operations are considered to return the same values if the input parameters are kept constant,
// thus we check those input parameters. Note however that we cannot make sure that the code of a given
// custom operation actually obeys the rule of providing a stable mapping between input parameters and
// output, thus this assumption is unfortunately not 100% safe.
return isSafelyStaticallyResolvable(((CustomOperation) aValue).getPrefixOperand(), aVariant)
&& isSafelyStaticallyResolvable(((CustomOperation) aValue).getPostfixOperand(), aVariant);
}
throw new ThisShouldNeverHappenException();
}
@Override
public Object resolveStatically(ConstantDefinition aConstant, VariantDefinition aVariant)
throws UnexecutableException, ClassNotFoundException, InstantiationException {
ValueOrEnumValueOrOperationCollection tempValue = IntegrityDSLUtil.getInitialValueForConstant(aConstant,
aVariant);
return resolveStatically(tempValue, aVariant);
}
@Override
public Map<String, Object> createExpectedResultMap(Test aTest, boolean anIncludeArbitraryResultFlag) {
return createExpectedResultMap(aTest.getResults(), anIncludeArbitraryResultFlag);
}
private Map<String, Object> createExpectedResultMap(List<NamedResult> aTestResultList,
boolean anIncludeArbitraryResultFlag) {
Map<String, Object> tempResultMap = new LinkedHashMap<String, Object>();
for (NamedResult tempEntry : aTestResultList) {
if (tempEntry.getName() != null && tempEntry.getValue() != null) {
Object tempValue = tempEntry.getValue();
if (tempValue instanceof Variable) {
// Variable resolving is not supported here since this method is currently only used in
// circumstances where this is impossible anyway (autocompletion in the IDE)
tempValue = null;
}
if (anIncludeArbitraryResultFlag || !(tempEntry.getName() instanceof ArbitraryParameterOrResultName)) {
tempResultMap.put(
IntegrityDSLUtil.getExpectedResultNameStringFromTestResultName(tempEntry.getName()),
tempEntry.getValue());
}
}
}
return tempResultMap;
}
}