/******************************************************************************* * Copyright (c) 2010 SAP AG 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 * * Contributors: * SAP AG - initial API and implementation ******************************************************************************/ package com.sap.furcas.runtime.common.util; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.emf.common.util.BasicDiagnostic; import org.eclipse.emf.common.util.Diagnostic; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EClassifier; import org.eclipse.emf.ecore.EObject; 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.opposites.OppositeEndFinder; import com.sap.emf.ocl.prepared.PreparedOCLExpression; import com.sap.emf.ocl.prepared.PreparedOCLExpression.ParameterValue; import com.sap.furcas.metamodel.FURCAS.TCS.Template; import com.sap.furcas.runtime.common.exceptions.ModelAdapterException; import com.sap.ocl.oppositefinder.query2.Query2OppositeEndFinder; import de.hpi.sam.bp2009.solution.queryContextScopeProvider.impl.ProjectDependencyQueryContextProvider; /** * Helper class allowing to evaluate OCL expressions as used within TCS. * * OCL expressions may contain #context or #foreach references. The queries may furthermore contain "?" placeholders. * Those will be replaced by the provided keyValue when the query is executed. * * @author Stephan Erb (d049157) * */ public class TCSSpecificOCLEvaluator { private static class CacheKey { private final String queryToExecute; private final EClass context; @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + context.hashCode(); result = prime * result + queryToExecute.hashCode(); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof CacheKey)) { return false; } CacheKey other = (CacheKey) obj; return other.context.equals(this.context) && other.queryToExecute.equals(this.queryToExecute); } public CacheKey(String queryToExecute, EClass context) { this.queryToExecute = queryToExecute; this.context = context; } } private static final String LEGACY_QUERY_PARAMETER = "?"; private final OCL ocl; private final Helper oclHelper; private final OppositeEndFinder oppositeEndFinder; private final Map<CacheKey, PreparedOCLExpression> queryCache = new HashMap<CacheKey, PreparedOCLExpression>(); public TCSSpecificOCLEvaluator() { this(new Query2OppositeEndFinder(new ProjectDependencyQueryContextProvider())); } public TCSSpecificOCLEvaluator(OppositeEndFinder oppositeEndFinder) { this.oppositeEndFinder = oppositeEndFinder; this.ocl = org.eclipse.ocl.examples.impactanalyzer.util.OCL.newInstance(oppositeEndFinder); this.oclHelper = ocl.createOCLHelper(); this.oclHelper.setValidating(true); } /** * Returns all model elements found by the given OCL query. */ public Collection<?> findElementsWithOCLQuery(EObject sourceModelElement, Object keyValue, String oclQuery) throws ModelAdapterException { return findElementsWithOCLQuery(sourceModelElement, keyValue, oclQuery, /*contextObject*/ null, /*foreachObject*/ null); } /** * Returns all model elements found by the given OCL query. */ public Collection<?> findElementsWithOCLQuery(EObject sourceModelElement, Object keyValue, String oclQuery, EObject contextObject) throws ModelAdapterException { return findElementsWithOCLQuery(sourceModelElement, keyValue, oclQuery, contextObject, /*foreachObject*/ null); } /** * Returns all model elements found by the given OCL query. */ public Collection<?> findElementsWithOCLQuery(EObject sourceModelElement, Object keyValue, String queryToExecute, EObject contextObject, EObject foreachObject) throws ModelAdapterException { EObject objectForSelf = determineObjectForSelf(sourceModelElement, queryToExecute, foreachObject, contextObject); try { PreparedOCLExpression preparedExp = getOCLExpressionToExecute(objectForSelf, keyValue, queryToExecute); Object result = evaluate(objectForSelf, keyValue, preparedExp); if (ocl.isInvalid(result)) { throw new ModelAdapterException("Cannot evaluate OCLExpression:" + queryToExecute + " Reason: Result is invalid " + (ocl.getEvaluationProblems() == null ? "" : ocl.getEvaluationProblems().getMessage())); } if (result instanceof Collection<?>) { return (Collection<?>) result; } else { return Collections.singleton(result); } } catch (ParserException e) { throw new ModelAdapterException("Cannot evaluate) OCLExpression:" + queryToExecute + " Reason: " + e.getDiagnostic().getMessage(), e); } } private Object evaluate( EObject objectForSelf, Object keyValue, PreparedOCLExpression exp) { if (keyValue != null) { ParameterValue<Object> parameter = exp.createParameterValue(LEGACY_QUERY_PARAMETER, keyValue); return exp.evaluate(objectForSelf, parameter); } else { return exp.evaluate(objectForSelf); } } private PreparedOCLExpression getOCLExpressionToExecute(EObject objectForSelf, Object keyValue, String queryToExecute) throws ParserException { CacheKey key = new CacheKey(queryToExecute, objectForSelf.eClass()); if (queryCache.containsKey(key)) { return queryCache.get(key); } // FIXME: Workaround because prepared statements require '?' but our generator // only produces queries with a plain ?. // Once the parser generator is migrated this additional replacing can go away. queryToExecute = ContextAndForeachHelper.prepareOclQuery(queryToExecute, LEGACY_QUERY_PARAMETER); oclHelper.setContext(objectForSelf.eClass()); OCLExpression exp = oclHelper.createQuery(queryToExecute); PreparedOCLExpression preparedExp = null; if (keyValue != null) { // FIXME: ? is the legacy parameter. We have to replace it with something less problematic. preparedExp = new PreparedOCLExpression(oppositeEndFinder, exp, LEGACY_QUERY_PARAMETER); } else { preparedExp = new PreparedOCLExpression(oppositeEndFinder, exp); } queryCache.put(key, preparedExp); return preparedExp; } private EObject determineObjectForSelf(EObject sourceModelElement, String oclQuery, EObject foreachObject, EObject contextRefObject) throws ModelAdapterException { EObject objectForSelf; if (ContextAndForeachHelper.usesContext(oclQuery)) { objectForSelf = contextRefObject; } else if (ContextAndForeachHelper.usesForeach(oclQuery)) { objectForSelf = foreachObject; } else { objectForSelf = sourceModelElement; } if (objectForSelf == null) { throw new ModelAdapterException("Cannot find suitable self object for OCL expression " + oclQuery); } return objectForSelf; } public List<Diagnostic> validateOclQuery(Template template, String queryToValidate) { try { EClassifier parsingContext = ContextAndForeachHelper.getParsingContext(queryToValidate, template, oppositeEndFinder); return validateOclQuery(parsingContext, queryToValidate); } catch (ParserException e) { return Collections.singletonList((Diagnostic) new BasicDiagnostic(getClass().getName(), Diagnostic.ERROR, e.getMessage(), null)); } } public List<Diagnostic> validateOclQuery(EClassifier parsingContext, String queryToValidate) { queryToValidate = ContextAndForeachHelper.prepareOclQuery(queryToValidate, "__DUMMY__"); try { oclHelper.setContext(parsingContext); oclHelper.createQuery(queryToValidate); //TODO distinguish between errors and warnings Diagnostic diagnostic = oclHelper.getProblems(); if (diagnostic == null) { return Collections.emptyList(); } else { return Collections.singletonList(diagnostic); } } catch (ParserException e) { return Collections.singletonList(e.getDiagnostic()); } catch (RuntimeException e) { return Collections.singletonList((Diagnostic) new BasicDiagnostic(getClass().getName(), Diagnostic.ERROR, e.getMessage(), null)); } } public EObject getOclReturnType(EClassifier parsingContext, String oclQuery) throws ParserException { oclQuery = ContextAndForeachHelper.prepareOclQuery(oclQuery, "__DUMMY__"); oclHelper.setContext(parsingContext); return oclHelper.createQuery(oclQuery).getType(); } }