package com.sap.furcas.runtime.common.util;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.ocl.Environment;
import org.eclipse.ocl.ParserException;
import org.eclipse.ocl.ecore.OCL;
import org.eclipse.ocl.ecore.OCL.Helper;
import org.eclipse.ocl.ecore.OCLExpression;
import org.eclipse.ocl.ecore.TypeExp;
import org.eclipse.ocl.ecore.TypeType;
import org.eclipse.ocl.ecore.opposites.DefaultOppositeEndFinder;
import org.eclipse.ocl.ecore.opposites.OppositeEndFinder;
import com.sap.emf.ocl.prepared.PreparedOCLExpression;
import com.sap.furcas.metamodel.FURCAS.TCS.ConcreteSyntax;
import com.sap.furcas.metamodel.FURCAS.TCS.ContextTemplate;
import com.sap.furcas.metamodel.FURCAS.TCS.Template;
/**
* Knows the patterns for <code>#context</code> and <code>#foreach</code>. Furthermore, can create a {@link PreparedOCLExpression
* parameterized query} from those that contain literals matching specific patterns (see {@link #stringLiteralQueryArgPattern} and
* {@link #numberLiteralQueryArgPattern}).
*
* @author Axel Uhl (D043530)
*
*/
public class ContextAndForeachHelper {
public static final String contextPatternAsString = "#context(\\((\\w*)\\))?";
public static final String foreachPatternAsString = "#foreach\\((\\w+(::\\w+)*)\\)";
private static final String stringLiteralQueryArgPattern = "???";
private static final int numberLiteralQueryArgPattern = 999;
/**
* The pattern to match #context(...) or #context
*/
public static final Pattern contextPattern = Pattern.compile(contextPatternAsString);
/**
* The pattern to match #foreach
*/
public static final Pattern foreachPattern = Pattern.compile(foreachPatternAsString);
private static final String oclAsTypePatternSuffix = "\\b*\\.\\b*oclAsType\\(\\b*([^\\(]*)\\b*\\)";
private static final String QUERY_PARAM_NAME = "\\?";
/**
* The {@link ContextManager#contextPatternAsString} pattern contains two groups where the second group is for the name of the
* context tag. This group therefore contains three groups, the second being the context tag, the third being the name of the
* type to which the context is cast.
*/
private static final Pattern oclAsTypePattern = Pattern.compile(ContextAndForeachHelper.contextPatternAsString
+ oclAsTypePatternSuffix);
/**
* For a given context tag and a concrete syntax determines the meta-classes for which templates exist that use this
* tag for declaring a context. The common generalization of all such classes will be returned. This may be
* <tt>EObject</tt> if no other common generalization exists.
* <p>
*
* This implementation currently uses EMF query2 MQL to find the classes for which templates (class or operator)
* exist that declare a context with the <tt>contextTag</tt> among the tags.
*
* @param cs
* the concrete syntax in which to search for the use of the <tt>contextTag</tt>
* @param contextTag
* may be <tt>null</tt> or an empty string which means the untagged context
* @return the common generalization of all classes for which a template is defined as context using
* <tt>contextTag</tt> as the tag for the context declaration; <tt>null</tt> if no such template exists;
* <tt>Reflect::Element</tt> if no other common generalization exists
*/
public static EClass getCommonBaseClassForContextTag(ConcreteSyntax cs, String contextTag) {
Set<EClass> metaReferences = new HashSet<EClass>();
for (Template t : cs.getTemplates()) {
if (t instanceof ContextTemplate) {
ContextTemplate ct = (ContextTemplate) t;
if (ct.isIsContext() && matchesContextTag(contextTag, ct)) {
metaReferences.add((EClass) ct.getMetaReference());
}
}
}
boolean needReflectElement = false;
EClass commonGeneralization = null;
while (!needReflectElement && metaReferences.size() > 0) {
EClass candidate = metaReferences.iterator().next();
if (commonGeneralization == null) {
commonGeneralization = candidate;
metaReferences.remove(candidate); // first candidate
} else if (candidate.getEAllSuperTypes().contains(commonGeneralization)) {
metaReferences.remove(candidate); // commonGeneralization already covers candidate
} else {
List<EClass> allCommonGeneralizationSupertypes = commonGeneralization.getEAllSuperTypes();
boolean foundCommonSupertype = false;
for (EClass candidateSuperclass : candidate.getEAllSuperTypes()) {
if (allCommonGeneralizationSupertypes.contains(candidateSuperclass)) {
commonGeneralization = candidateSuperclass;
metaReferences.remove(candidate); // now commongeneralization also covers candidate
foundCommonSupertype = true;
break;
}
}
if (!foundCommonSupertype) {
needReflectElement = true;
break;
}
}
}
if (needReflectElement) {
commonGeneralization = org.eclipse.ocl.ecore.EcorePackage.eINSTANCE.getAnyType();
}
return commonGeneralization;
}
private static boolean matchesContextTag(String contextTag, ContextTemplate ct) {
boolean emptyContextTag = contextTag == null || contextTag.length() == 0;
return (emptyContextTag && (ct.getContextTags() == null || ct.getContextTags().getTags().isEmpty())) ||
(!emptyContextTag && ct.getContextTags() != null && ct.getContextTags().getTags().contains(contextTag));
}
/**
* <p>Based on the use of <code>self</code>, <code>#context</code> and <code>#foreach</code> and the template in whose context the
* expression occurs, determines the context type to be used for parsing the expression.
* <p/>
* Utilizes the {@link DefaultOppositeEndFinder}
*/
public static EClassifier getParsingContext(String oclExpression, Template template) throws ParserException {
return getParsingContext(oclExpression, template, DefaultOppositeEndFinder.getInstance());
}
/**
* Based on the use of <code>self</code>, <code>#context</code> and <code>#foreach</code> and the template in whose context the
* expression occurs, determines the context type to be used for parsing the expression.
*/
public static EClassifier getParsingContext(String oclExpression, Template template, OppositeEndFinder oppositeEndFinder) throws ParserException {
EClassifier parsingContext = null;
if (!usesContext(oclExpression)) {
if (usesForeach(oclExpression)) {
parsingContext = getForeachMetaObject(oclExpression, oppositeEndFinder);
if (parsingContext == null) {
throw new RuntimeException(
"Expected to find type of #foreach variable after #foreach in parentheses but didn't: "+
oclExpression);
}
} else {
parsingContext = template.getMetaReference();
}
} else if (usesContextWithSubsequentCast(oclExpression)) {
parsingContext = getContextMetaObject(oclExpression, oppositeEndFinder);
if (parsingContext == null) {
throw new RuntimeException("Didn't find type used in context casts in expression " + oclExpression);
}
} else {
parsingContext = getCommonBaseClassForContextTag(template.getConcreteSyntax(),
getContextTag(oclExpression));
if (parsingContext == null) {
throw new RuntimeException("Expected to find use of context " + getContextTag(oclExpression) + " but didn't");
}
}
return parsingContext;
}
public static String getContextTag(String oclExpression) {
Matcher matcher = contextPattern.matcher(oclExpression);
String result = null;
if (matcher.find()) {
if (matcher.groupCount() >= 2) {
result = matcher.group(2);
}
}
return result;
}
/**
* Checks if the <tt>oclExpression</tt> contains a combination of the sort <tt>#context(...).oclAsType(...)</tt>. This can be
* used to determine the type of the "self" variable which will then be the evaluation context for the expression
*/
public static boolean usesContextWithSubsequentCast(String oclExpression) {
Matcher matcher = oclAsTypePattern.matcher(oclExpression);
return matcher.find();
}
/**
* If the {@link #oclAsTypePattern} matches, this method determines the type to which the context is being cast. If the
* pattern does not match or the type is not found, <tt>null</tt> is returned.
*
* @throws ParserException
* in case the type name can't be parsed by the OCL evaluator
*/
public static EClassifier getContextMetaObject(String oclExpression, OppositeEndFinder oppositeEndFinder) throws ParserException {
EClassifier result = null;
Matcher matcher = oclAsTypePattern.matcher(oclExpression);
if (matcher.find()) {
if (matcher.groupCount() >= 3) {
String oclTypeName = matcher.group(3);
result = getTypeByNameUsingOCLTypeExp(oclTypeName, oppositeEndFinder);
}
}
return result;
}
/**
* If the {@link #foreachWithOclAsTypePattern} matches, this method determines the type to which the context is being cast. If
* the pattern does not match or the type is not found, <tt>null</tt> is returned.
*
* @throws ParserException
* in case the type name can't be parsed by the OCL evaluator
*/
private static EClassifier getForeachMetaObject(String oclExpression, OppositeEndFinder oppositeEndFinder) throws ParserException {
EClassifier result = null;
Matcher matcher = foreachPattern.matcher(oclExpression);
if (matcher.find()) {
if (matcher.groupCount() >= 1) {
String oclTypeName = matcher.group(1);
result = getTypeByNameUsingOCLTypeExp(oclTypeName, oppositeEndFinder);
}
}
return result;
}
protected static EClassifier getTypeByNameUsingOCLTypeExp(String oclTypeName, OppositeEndFinder oppositeEndFinder) throws ParserException {
EClassifier result;
OCL ocl = org.eclipse.ocl.examples.impactanalyzer.util.OCL.newInstance(oppositeEndFinder);
Helper helper = ocl.createOCLHelper();
helper.setContext(EcorePackage.eINSTANCE.getEClassifier()); // EClassifier is a classifier that's always in scope
TypeExp typeQuery = (TypeExp) helper.createQuery(oclTypeName);
result = ((TypeType) typeQuery.getType()).getReferredType();
return result;
}
public static boolean usesContext(String queryToExecute) {
Matcher matcher = contextPattern.matcher(queryToExecute);
return matcher.find();
}
public static boolean usesForeach(String queryToExecute) {
Matcher matcher = foreachPattern.matcher(queryToExecute);
return matcher.find();
}
/**
* Removes an optional leading "OCL:" prefix. Then performs all replacements of {@link #prepareOclQuery(String)} and
* replaces a "?" (see {@link #QUERY_PARAM_NAME}) by the {@link Object#toString()} representation of
* <code>keyValue</code>. The OCL expression string treated this way is returned.
*
* @deprecated Use only {@link #prepareOclQuery(String)} because ? replacement is obsolete
*/
@Deprecated
public static String prepareOclQuery(String queryToExecute, Object keyValue) {
String result = queryToExecute;
if (queryToExecute != null) {
result = result.replaceAll("\\\\\"","\"");
if (result.startsWith("OCL:")) {
result = result.replaceFirst("OCL:", "");
}
result = ContextAndForeachHelper.prepareOclQuery(result);
if (keyValue != null) {
result = result.replaceAll(QUERY_PARAM_NAME, "'" + keyValue.toString() + "'");
}
}
return result;
}
private static final String OCL_PREFIX = "OCL:";
/**
* Replaces occurrences of <code>#context</code> (see {@link #contextPattern}) and <code>#foreach</code>
* (see {@link #foreachPattern}) by <code>self</code> (see {@link Environment#SELF_VARIABLE_NAME}).
*/
public static String prepareOclQuery(String queryToExecute) {
String result = queryToExecute;
if (queryToExecute != null) {
if (queryToExecute.startsWith(OCL_PREFIX)) {
result = result.substring(OCL_PREFIX.length());
}
if (usesContext(result)) {
// TODO this would accidentally catch "self" occurring in a string literal or within another identifier
if (result.indexOf(Environment.SELF_VARIABLE_NAME) > -1) {
throw new RuntimeException("OCL Query cannot contain #context and self at the same time.");
} else if (ContextAndForeachHelper.usesForeach(result)) {
throw new RuntimeException("OCL query cannot contain #foreach and #context at the same time");
} else {
result = result.replaceAll(contextPattern.pattern(), Environment.SELF_VARIABLE_NAME);
}
} else if (ContextAndForeachHelper.usesForeach(result)) {
if (result.indexOf(Environment.SELF_VARIABLE_NAME) > -1) {
throw new RuntimeException("OCL Query cannot contain #foreach and self at the same time.");
} else if (ContextAndForeachHelper.usesContext(result)) {
throw new RuntimeException("OCL query cannot contain #foreach and #context at the same time");
} else {
result = result.replaceAll(ContextAndForeachHelper.foreachPattern.pattern(), Environment.SELF_VARIABLE_NAME);
}
}
}
return result;
}
public static PreparedOCLExpression prepareWithStringParameter(OCLExpression e) {
return new PreparedOCLExpression(e, stringLiteralQueryArgPattern);
}
public static PreparedOCLExpression prepareWithNumericParameter(OCLExpression e) {
return new PreparedOCLExpression(e, numberLiteralQueryArgPattern);
}
}