/*******************************************************************************
* Copyright (c) 2006-2012
* Software Technology Group, Dresden University of Technology
* DevBoost GmbH, Berlin, Amtsgericht Charlottenburg, HRB 140026
*
* 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
*
* Contributors:
* Software Technology Group - TU Dresden, Germany;
* DevBoost GmbH - Berlin, Germany
* - initial API and implementation
******************************************************************************/
package org.reuseware.coconut.reuseextension.evaluator.ocl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.ECollections;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EParameter;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.ocl.ParserException;
import org.eclipse.ocl.ecore.CollectionType;
import org.eclipse.ocl.ecore.EcoreEnvironmentFactory;
import org.eclipse.ocl.ecore.OCL;
import org.eclipse.ocl.ecore.OCL.Helper;
import org.eclipse.ocl.ecore.OCL.Query;
import org.eclipse.ocl.ecore.OCLExpression;
import org.eclipse.ocl.expressions.Variable;
import org.eclipse.ocl.helper.Choice;
import org.eclipse.ocl.helper.ConstraintKind;
import org.reuseware.coconut.id.ID;
import org.reuseware.coconut.id.IdFactory;
import org.reuseware.coconut.id.IdPackage;
import org.reuseware.coconut.reuseextension.evaluator.Evaluator;
/**
* A REX evaluator for OCL.
*/
public abstract class OCLEvaluator implements Evaluator {
/**
* String result indicating that an error occured during evaluation.
*/
public static final String ERROR_STRING = "<ERROR_NULL>";
protected final OCL env = OCL.newInstance(getEnvironmentFactory());
protected abstract EcoreEnvironmentFactory getEnvironmentFactory();
/**
* Evaluates the OCL expression towards a boolean value.
*
* @param iD id of the fragment on which the expression is to be evaluated
* @param context an element in the fragment on which the expression is to be evaluated
* @param expression the unparsed expression as string
* @param args additional arguments to be used in the evaluation
*
* @return the result of the evaluation
*/
public boolean eval(List<String> iD, EObject context, String expression, Map<String, String> args) {
if (expression == null || expression.equals("")) {
return true;
}
return Boolean.parseBoolean(executeQueryExpressionToString(iD, context, expression, args));
}
/**
* Evaluates the OCL expression towards a boolean value (with a list of elements as context).
*
* @param iD id of the fragment on which the expression is to be evaluated
* @param context an elements in the fragment on which the expression is to be evaluated
* @param expression the unparsed expression as string
* @param args additional arguments to be used in the evaluation
*
* @return the result of the evaluation
*/
public boolean eval(List<String> iD, List<EObject> context, String expression, Map<String, String> args) {
for (EObject singleElementContext : context) {
if (eval(iD, singleElementContext, expression, args)) {
return true;
}
}
return false;
}
/**
* Evaluates the OCL expression towards a String value.
*
* @param iD id of the fragment on which the expression is to be evaluated
* @param context an element in the fragment on which the expression is to be evaluated
* @param expression the unparsed expression as string
* @param args additional arguments to be used in the evaluation
*
* @return the result of the evaluation
*/
public String derive(List<String> iD, EObject context, String expression, Map<String, String> args) {
if (expression == null || expression.equals("")) {
return "";
}
return executeQueryExpressionToString(iD, context, expression, args);
}
/**
* Evaluates the OCL expression towards an integer value.
*
* @param iD id of the fragment on which the expression is to be evaluated
* @param context an element in the fragment on which the expression is to be evaluated
* @param expression the unparsed expression as string
* @param args additional arguments to be used in the evaluation
*
* @return the result of the evaluation
*/
public int deriveInt(List<String> iD, EObject context, String expression, Map<String, String> args) {
if (expression == null || expression.equals("")) {
return -1;
}
try {
return Integer.parseInt(executeQueryExpressionToString(iD, context, expression, args));
} catch (NumberFormatException e) {
return -1;
}
}
/**
* Evaluates the OCL expression towards an ID (a list of Strings).
*
* @param iD id of the fragment on which the expression is to be evaluated
* @param context an element in the fragment on which the expression is to be evaluated
* @param expression the unparsed expression as string
* @param args additional arguments to be used in the evaluation
*
* @return the result of the evaluation
*/
public List<String> deriveID(List<String> iD, EObject context, String expression, Map<String, String> args) {
String result = derive(iD, context, expression, args);
if (!ERROR_STRING.equals(result)) {
try {
return Arrays.asList(result.split("/"));
} catch (Exception e) {
e.printStackTrace();
}
}
return Collections.emptyList();
}
/**
* Evaluates the OCL expression towards a list of model elements.
*
* @param iD id of the fragment on which the expression is to be evaluated
* @param context an element in the fragment on which the expression is to be evaluated
* @param expression the unparsed expression as string
* @param args additional arguments to be used in the evaluation
*
* @return the result of the evaluation
*/
public List<EObject> deriveElementList(List<String> iD, EObject context, String expression, Map<String, String> args) {
if (expression == null || expression.equals("")) {
return Collections.singletonList(context);
}
return executeQueryExpressionToList(iD, context, expression, args);
}
private final Map<EObject, Map<String, Object>> cache =
new HashMap<EObject, Map<String, Object>>();
private ResourceSet currentResourceSet = null;
private String computeCacheID(String expression, Map<String, String> args) {
String id = expression + "?";
for (String property : args.keySet()) {
id = id + property + "=" + args.get(property) + ";";
}
return id;
}
private Object getFromCache(EObject context, String expression, Map<String, String> args) {
if (context.eResource() == null) {
currentResourceSet = null;
return null;
}
ResourceSet contextResourceSet = context.eResource().getResourceSet();
if (contextResourceSet == null) {
currentResourceSet = null;
return null;
}
if (contextResourceSet != currentResourceSet) {
currentResourceSet = contextResourceSet;
cache.clear();
}
String cacheID = computeCacheID(expression, args);
Map<String, Object> contextCache = cache.get(context);
if (contextCache != null) {
return contextCache.get(cacheID);
}
return null;
}
private void putIntoCach(EObject context, String expression, Map<String, String> args, Object result) {
if (currentResourceSet == null) {
return;
}
String cacheID = computeCacheID(expression, args);
Map<String, Object> contextCache = cache.get(context);
if (contextCache == null) {
contextCache = new HashMap<String, Object>();
cache.put(context, contextCache);
}
contextCache.put(cacheID, result);
}
protected Object executeQueryExpression(List<String> iD, EObject context, String expression, Map<String, String> args) {
if (args == null) {
return "";
}
if (args.equals("")) {
return "";
}
Object result = getFromCache(context, expression, args);
if (result != null) {
return result;
}
createContextVariables(args.keySet());
OCLExpression oclExpression = null;
try {
oclExpression = parse(context.eClass(), expression);
} catch (ParserException e) {
// Error in parsing OCL. If this happens, an error was
// already reported by validateExpression()
result = ERROR_STRING;
}
if (oclExpression != null) {
Query query = env.createQuery(oclExpression);
ID ufiIDElement = IdFactory.eINSTANCE.createID();
ufiIDElement.getSegments().addAll(iD);
query.getEvaluationEnvironment().add("ufi", ufiIDElement);
for (String property : args.keySet()) {
query.getEvaluationEnvironment().add(property, args.get(property));
}
try {
result = query.evaluate(context);
} catch (Exception e) {
result = null;
}
}
if (result == null) {
result = "";
}
if (result instanceof EObject && ((EObject) result).eClass().getName().equals("OclInvalid_Class")) {
//OCL "Null Pointer Exception"
result = ERROR_STRING;
}
putIntoCach(context, expression, args, result);
return result;
}
protected List<EObject> executeQueryExpressionToList(
List<String> ufi,
EObject context,
String expression,
Map<String, String> args) {
Object result = executeQueryExpression(ufi, context, expression, args);
if (result instanceof Collection<?>) {
@SuppressWarnings("unchecked")
ArrayList<EObject> resultAsList =
new ArrayList<EObject>((Collection<EObject>) result);
return resultAsList;
}
if (result instanceof EObject) {
return Collections.singletonList((EObject) result);
}
return ECollections.emptyEList();
}
protected String executeQueryExpressionToString(
List<String> iD,
EObject context,
String expression,
Map<String, String> args) {
Object result = executeQueryExpression(iD, context, expression, args);
if (result instanceof List<?>) {
@SuppressWarnings("unchecked")
List<String> resultAsList = (List<String>) result;
result = idToString(resultAsList);
}
if (result instanceof Collection<?>) {
EList<String> idList = new BasicEList<String>();
for (Object o : (Collection<?>) result) {
if (o instanceof String) {
idList.add((String) o);
}
}
result = idToString((EList<String>) idList);
}
return result.toString();
}
private void createContextVariables(Collection<String> parameters) {
Variable<EClassifier, EParameter> ufiContextVar =
env.getEnvironment().getOCLFactory().createVariable();
ufiContextVar.setName("ufi");
ufiContextVar.setType(IdPackage.Literals.ID);
env.getEnvironment().addElement(ufiContextVar.getName(),
ufiContextVar, true);
for (String property : parameters) {
Variable<EClassifier, EParameter> contextVar =
env.getEnvironment().getOCLFactory().createVariable();
contextVar.setName(property);
contextVar.setType(env.getEnvironment().getOCLStandardLibrary().getString());
env.getEnvironment().addElement(contextVar.getName(),
contextVar, true);
}
}
private OCLExpression parse(EClass context, String expression) throws ParserException {
if (expression == null) {
return null;
}
OCLExpression oclExpression = null;
Helper helper = env.createOCLHelper();
helper.setContext(context);
oclExpression = helper.createQuery(expression);
return oclExpression;
}
private String idToString(List<String> stringList) {
String result = "";
Iterator<String> stringIt = stringList.iterator();
while (stringIt.hasNext()) {
result += stringIt.next();
if (stringIt.hasNext()) {
result += "/";
}
}
return result;
}
/**
* Validates if the OCL expression is correct.
*
* @param context the context in which to evaluate the expression
* @param expression the unparsed expression as string
* @param parameters additional parameters to be used in the evaluation
* @return error message if errors were found
*/
public String validateExpression(EClass context, String expression, List<String> parameters) {
createContextVariables(parameters);
try {
parse(context, expression);
} catch (ParserException e) {
return e.getLocalizedMessage();
}
return null;
}
/**
* Determines the result type of the given expression.
*
* @param context the context in which to evaluate the expression
* @param expression the unparsed expression as string
* @param parameters additional parameters to be used in the evaluation
*
* @return the expression's result type
*/
public EClass getResultType(EClass context, String expression, List<String> parameters) {
createContextVariables(parameters);
try {
OCLExpression query = parse(context, expression);
EClassifier type = query.getType();
if (type instanceof CollectionType) {
type = ((CollectionType) type).getElementType();
}
return type instanceof EClass ? (EClass) type : null;
} catch (ParserException e) {
//
}
return null;
}
/**
* Computes completion proposals for the given incomplete OCL expressions.
* This method delegates to <code>getSyntaxHelp()</code> of the OCL helper.
*
* @param context the context in which to evaluate the expression
* @param expressionStart the unparsed beginning of the expression to be completed
* @param parameters additional parameters to be used in the evaluation
*
* @return a list of completion proposals (complete expressions as Strings)
*/
public List<String> getCompletionProposals(EClass context,
String expressionStart, List<String> parameters) {
List<String> result = new ArrayList<String>();
createContextVariables(parameters);
Helper helper = env.createOCLHelper();
helper.setContext(context);
List<Choice> choices = helper.getSyntaxHelp(ConstraintKind.DERIVATION, expressionStart);
String prefix = expressionStart;
while (prefix.matches(".*\\w")) {
prefix = prefix.substring(0, prefix.length() - 1);
}
for (Choice choice : choices) {
result.add(prefix + choice.getName());
}
return result;
}
}