package com.sap.emf.ocl.prepared;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.ocl.ecore.EcoreEnvironmentFactory;
import org.eclipse.ocl.ecore.LiteralExp;
import org.eclipse.ocl.ecore.OCL;
import org.eclipse.ocl.ecore.OCLExpression;
import org.eclipse.ocl.ecore.OppositePropertyCallExp;
import org.eclipse.ocl.ecore.opposites.OppositeEndFinder;
import org.eclipse.ocl.examples.impactanalyzer.util.OclHelper;
import com.sap.emf.ocl.prepared.parameters.DuplicateParameterValueException;
import com.sap.emf.ocl.prepared.parameters.Parameter;
import com.sap.emf.ocl.prepared.parameters.ParameterFactory;
import com.sap.emf.ocl.prepared.parameters.ParameterFinder;
import com.sap.emf.ocl.prepared.parameters.ParameterNotFoundException;
/**
* Similar to a JDBC prepared statement, where parameters can be set to specific values before executing it,
* a prepared OCL expression can have a number of parameters which are substituted by values before
* evaluating the expression. Only {@link LiteralExp} expressions can be configured using parameters. The
* {@link LiteralExp} expressions to be made parameterizable can either be specified explicitly (see
* {@link #PreparedOCLExpression(OCLExpression, LiteralExp...)}) or indirectly by means of specifying the
* literal's value (see {@link #PreparedOCLExpression(OCLExpression, Object...)}). In the latter case, a
* visitor traverses the expression tree and looks for {@link LiteralExp} expressions such as string
* constants or enumeration literals, and compares them to the objects passed to the constructor. If a match
* is found, that literal expression is made parameterizable.
* <p>
*
* Once you hold a {@link PreparedOCLExpression} in hand, you may evaluate it, providing specific values for the
* various parameters. You use the {@link #evaluate(Object, ParameterValue...)} method for this purpose.
*
* The expression that is parameterized will be modified upon evaluation. Therefore, evaluation is
* synchronized on the original {@link OCLExpression} object so that no race conditions may occur when multiple
* threads evaluate the same parameterized expression for different parameter values or different parameterized
* expressions based on the same original OCL expression.<p>
*
* For evaluation, an {@link OCLWithHiddenOpposites} environment is used which adds support for
* {@link OppositePropertyCallExp} expressions.
*
* @author Axel Uhl (D043530)
*
*/
public class PreparedOCLExpression {
private final OCLExpression expression;
private final List<Parameter<?>> params;
private final Map<Object, Parameter<?>> paramsByIdentifyingSymbols;
private OppositeEndFinder oppositeEndFinder;
private EcoreEnvironmentFactory environmentFactory;
public class ParameterValue<T> {
private final Parameter<T> param;
private final T value;
public ParameterValue(Parameter<T> param, T value) {
this.param = param;
this.value = value;
}
public Parameter<T> getParameter() {
return param;
}
T getValue() {
return value;
}
/**
* Replaces the parameterized literal's symbol by the value stored in this object
*/
void set() {
getParameter().set(getValue());
}
ParameterValue<T> getUndo() {
return new ParameterValue<T>(getParameter(), getParameter().get());
}
}
/**
* The <code>params</code> subexpressions which are expected to be contained in <code>expression</code>'s
* expression tree are marked as the parameters of the resulting {@link PreparedOCLExpression}. When
* {@link PreparedOCLExpression#evaluate(Object, ParameterValue...)} is called, the parameter values
* will be substituted in order for the <code>params</code>' literal objects.
*/
public PreparedOCLExpression(OCLExpression expression, LiteralExp... params) {
for (LiteralExp param : params) {
if (OclHelper.getRootExpression(param) != expression) {
throw new RuntimeException("Parameter expression from wrong root expression: "+
"Should be "+expression+" but was "+OclHelper.getRootExpression(param));
}
}
this.expression = expression;
this.params = new ArrayList<Parameter<?>>(params.length);
this.paramsByIdentifyingSymbols = new HashMap<Object, Parameter<?>>();
ParameterFactory factory = ParameterFactory.INSTANCE;
for (LiteralExp param : params) {
Parameter<?> p = factory.getParameterFor(param);
this.params.add(p);
paramsByIdentifyingSymbols.put(p.get(), p);
}
}
/**
* Finds {@link LiteralExp} expressions contained in <code>expression</code> that have any of the <code>paramValues</code> as
* their literal symbol and initializes the parameters accordingly, such that they refer to the respective literal
* expressions.
*
* @param paramValues
* a sequence of unique values to be found in {@link LiteralExp} expressions' symbols occurring inside
* <code>expression</code>
*
* @throws DuplicateParameterValueException
* if any of <code>paramValues</code> occurs in more than one literal expression inside <code>expression</code>
* @throws ParameterNotFoundException
* if one or more of the <code>paramValues</code> are not found in any literal expression inside
* <code>expression</code>
* @throws IllegalArgumentException
* in case <code>paramValues</code> contains duplicates considering the definition of <code>equals</code> on these
* objects
*/
public PreparedOCLExpression(OCLExpression expression, Object... paramValues) throws ParameterNotFoundException,
DuplicateParameterValueException, IllegalArgumentException {
ParameterFinder finder = new ParameterFinder(paramValues);
paramsByIdentifyingSymbols = finder.visit(expression);
Parameter<?>[] paramsArray = new Parameter<?>[paramValues.length];
List<Object> paramValuesAsList = Arrays.asList(paramValues);
for (Parameter<?> p : paramsByIdentifyingSymbols.values()) {
paramsArray[paramValuesAsList.indexOf(p.get())] = p;
}
this.params = Arrays.asList(paramsArray);
this.expression = expression;
}
/**
* Like {@link #PreparedOCLExpression(OCLExpression, LiteralExp...)}, but allows clients to specify a specific OCL environment
* factory to be used for expression evaluation.
*/
public PreparedOCLExpression(EcoreEnvironmentFactory environmentFactory,
OCLExpression expression, LiteralExp... params) {
this(expression, params);
this.environmentFactory = environmentFactory;
}
/**
* Like {@link #PreparedOCLExpression(OCLExpression, LiteralExp...)}, but allows clients to provide
* a specific {@link OppositeEndFinder} to be used during expression evaluation.
*/
public PreparedOCLExpression(OppositeEndFinder oppositeEndFinder,
OCLExpression expression, LiteralExp... params) {
this(expression, params);
this.oppositeEndFinder = oppositeEndFinder;
}
/**
* Like {@link #PreparedOCLExpression(OCLExpression, Object...)}, but allows clients to provide
* a specific {@link OppositeEndFinder} to be used during expression evaluation.
*/
public PreparedOCLExpression(OppositeEndFinder oppositeEndFinder,
OCLExpression expression, Object... paramValues) {
this(expression, paramValues);
this.oppositeEndFinder = oppositeEndFinder;
}
/**
* Produces a parameter setting for the parameter identified by its original value <code>originalValue</code>.
* This could, e.g., be one of the values passed to the {@link #PreparedOCLExpression(OCLExpression, Object...)}.
* The <code>newValue</code> is the actual value to which to set the parameter during evaluation when the resulting
* {@link ParameterValue} object is passed to {@link #evaluate(Object, ParameterValue...)}.
*/
@SuppressWarnings("unchecked")
public <T> ParameterValue<T> createParameterValue(T originalValue, T newValue) {
return new ParameterValue<T>((Parameter<T>) paramsByIdentifyingSymbols.get(originalValue), newValue);
}
/**
* Produces a {@link ParameterValue} which can be used to pass to {@link #evaluate(Object, ParameterValue...)}. The parameter
* is identified by its position. Positions are assigned according to the order of values or literal expressions passed to the
* respective constructor.
*/
@SuppressWarnings("unchecked")
public <T> ParameterValue<T> createPositionalParameterValue(int i, T newValue) {
return new ParameterValue<T>((Parameter<T>) params.get(i), newValue);
}
/**
* Sets the parameter values according to <code>parameterValues</code>, evaluates the parameterized expression and then resets
* the parameterized literals to their original symbols.
*
* @param parameterValues
* a collection of arbitrary size; positions in this collection don't matter. Parameter assignment doesn't happen
* positionally, but the parameter to be set is identified by the {@link ParameterValue#getParameter()} result. The
* set of parameter values provided here may be a (even an empty) subset of the parameters of this expression.
* Those parameters for which no value is provided here remains at its original value as it is provided by the
* original {@link OCLExpression} passed to the constructor.
* @param context
* evaluation context; see also {@link OCL#evaluate(Object, org.eclipse.ocl.expressions.OCLExpression)}.
*/
public Object evaluate(Object context, ParameterValue<?>... parameterValues) {
Set<ParameterValue<?>> originalValues = new HashSet<ParameterValue<?>>();
try {
synchronized (expression) {
for (ParameterValue<?> pv : parameterValues) {
originalValues.add(pv.getUndo());
pv.set();
}
return getOCL().evaluate(context, expression);
}
} finally {
for (ParameterValue<?> originalValue : originalValues) {
originalValue.set();
}
}
}
private OCL getOCL() {
if (oppositeEndFinder != null) {
return org.eclipse.ocl.examples.impactanalyzer.util.OCL.newInstance(oppositeEndFinder);
} else {
if (environmentFactory != null) {
return OCL.newInstance(environmentFactory);
} else {
return org.eclipse.ocl.examples.impactanalyzer.util.OCL.newInstance();
}
}
}
}