/******************************************************************************* * Copyright (c) 2009, 2012 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 org.eclipse.ocl.examples.impactanalyzer.instanceScope.traceback; import java.util.HashSet; import java.util.Set; import java.util.Stack; import org.eclipse.emf.common.notify.Notification; import org.eclipse.emf.common.notify.Notifier; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.ocl.ecore.OCLExpression; import org.eclipse.ocl.ecore.OperationCallExp; import org.eclipse.ocl.ecore.TypeExp; import org.eclipse.ocl.ecore.opposites.OppositeEndFinder; import org.eclipse.ocl.examples.impactanalyzer.impl.OperationBodyToCallMapper; import org.eclipse.ocl.examples.impactanalyzer.instanceScope.InstanceScopeAnalysis; import org.eclipse.ocl.examples.impactanalyzer.instanceScope.unusedEvaluation.UnusedEvaluationRequestFactory; import org.eclipse.ocl.examples.impactanalyzer.instanceScope.unusedEvaluation.UnusedEvaluationRequestSet; import org.eclipse.ocl.examples.impactanalyzer.util.AnnotatedEObject; import org.eclipse.ocl.examples.impactanalyzer.util.FlatSet; import org.eclipse.ocl.examples.impactanalyzer.util.IterableAsOperationCallExpKeyedSet; import org.eclipse.ocl.examples.impactanalyzer.util.OCLFactory; import org.eclipse.ocl.examples.impactanalyzer.util.OperationCallExpKeyedSet; import org.eclipse.ocl.utilities.PredefinedType; public class OperationCallTracebackStep extends BranchingTracebackStep<OperationCallExp> { private static final Set<String> sourcePassThroughStdLibOpNames; private static final Set<String> argumentPassThroughStdLibOpNames; static { sourcePassThroughStdLibOpNames = new HashSet<String>(); sourcePassThroughStdLibOpNames.add(PredefinedType.ANY_NAME); sourcePassThroughStdLibOpNames.add(PredefinedType.AS_BAG_NAME); sourcePassThroughStdLibOpNames.add(PredefinedType.AS_SET_NAME); sourcePassThroughStdLibOpNames.add(PredefinedType.AS_ORDERED_SET_NAME); sourcePassThroughStdLibOpNames.add(PredefinedType.AS_SEQUENCE_NAME); sourcePassThroughStdLibOpNames.add(PredefinedType.AT_NAME); // sourcePassThroughStdLibOpNames.add(PredefinedType.ATRPRE_NAME); sourcePassThroughStdLibOpNames.add(PredefinedType.EXCLUDING_NAME); sourcePassThroughStdLibOpNames.add(PredefinedType.FIRST_NAME); sourcePassThroughStdLibOpNames.add(PredefinedType.FLATTEN_NAME); sourcePassThroughStdLibOpNames.add(PredefinedType.INCLUDING_NAME); sourcePassThroughStdLibOpNames.add(PredefinedType.INSERT_AT_NAME); sourcePassThroughStdLibOpNames.add(PredefinedType.APPEND_NAME); sourcePassThroughStdLibOpNames.add(PredefinedType.INTERSECTION_NAME); sourcePassThroughStdLibOpNames.add(PredefinedType.OCL_AS_TYPE_NAME); sourcePassThroughStdLibOpNames.add(PredefinedType.UNION_NAME); sourcePassThroughStdLibOpNames.add(PredefinedType.SELECT_BY_KIND_NAME); argumentPassThroughStdLibOpNames = new HashSet<String>(); argumentPassThroughStdLibOpNames.add(PredefinedType.INCLUDING_NAME); argumentPassThroughStdLibOpNames.add(PredefinedType.INSERT_AT_NAME); argumentPassThroughStdLibOpNames.add(PredefinedType.APPEND_NAME); argumentPassThroughStdLibOpNames.add(PredefinedType.UNION_NAME); // TODO what about "product"? } /** * If set to a non-<code>null</code> value, the step executes an all-instances query using the * {@link #oppositeEndFinder}. Otherwise, the steps are performed and their results returned. */ private final EClass allInstancesClass; private final OppositeEndFinder oppositeEndFinder; /** * In case of a <code>selectByType</code> operation on a collection, the * type in {@link AbstractTracebackStep#requiredType} must be matched * exactly. In this case, this attribute is set to <code>true</code> by the * constructor, and an exact type check is performed by * {@link #performSubsequentTraceback(AnnotatedEObject, UnusedEvaluationRequestSet, TracebackCache, Notification)} * . */ private final boolean requireTypeExactly; /** * Set to <code>true</code> for operations whose body is specified again in OCL. For such operations, this step will trace * back using the body expression. Except for <code>allInstances()</code> cases this will lead back to <code>self</code> or * operation parameters for which then all calls to the operation will be investigated. * <p> * * While it would be nice to restrict this traceback to the single call currently under investigation, we so far haven't found * a way to combine this with the caching technique used. We would have to record the complete call stack as part of the cache * key, leading to a combinatorial explosion of cache keys and therefore to almost zero cache hits.<p> * * Instead, we compute the results for all calls but key them by the {@link OperationCallExp} through which the traceback * of <code>self</code> or any parameter variables went so that later we can filter for those whose call we're actually * interested in.<p> * * However, this applies only for operations whose body is specified in OCL. Therefore this flag. */ private final boolean filterResultsByCall; public OperationCallTracebackStep(OperationCallExp sourceExpression, EClass context, OperationBodyToCallMapper operationBodyToCallMapper, Stack<String> tupleLiteralNamesToLookFor, TracebackStepCache tracebackStepCache, UnusedEvaluationRequestFactory unusedEvaluationRequestFactory, OCLFactory oclFactory) { super(sourceExpression, tupleLiteralNamesToLookFor, tracebackStepCache.getOppositeEndFinder(), operationBodyToCallMapper, unusedEvaluationRequestFactory, oclFactory); oppositeEndFinder = tracebackStepCache.getOppositeEndFinder(); // important to enter this step before recursive lookups may occur: tracebackStepCache.put(sourceExpression, tupleLiteralNamesToLookFor, this); OCLExpression body = operationBodyToCallMapper.getOperationBody(sourceExpression.getReferredOperation()); if (body != null) { allInstancesClass = null; filterResultsByCall = true; requireTypeExactly = false; // an OCL-specified operation; trace back using the body expression getSteps().add( createTracebackStepAndScopeChange(sourceExpression, body, context, operationBodyToCallMapper, tupleLiteralNamesToLookFor, tracebackStepCache)); } else { filterResultsByCall = false; String opName = sourceExpression.getReferredOperation().getName(); if (opName.equals(PredefinedType.OCL_AS_TYPE_NAME)) { allInstancesClass = null; requireTypeExactly = false; handleOclAsType(sourceExpression, context, operationBodyToCallMapper, tupleLiteralNamesToLookFor, tracebackStepCache); } else if (opName.equals(PredefinedType.SELECT_BY_TYPE_NAME)) { allInstancesClass = null; requireTypeExactly = true; handleSourcePassThroughOperation(sourceExpression, context, operationBodyToCallMapper, tupleLiteralNamesToLookFor, tracebackStepCache, opName); } else if (sourcePassThroughStdLibOpNames.contains(opName)) { allInstancesClass = null; requireTypeExactly = false; handleSourcePassThroughOperation(sourceExpression, context, operationBodyToCallMapper, tupleLiteralNamesToLookFor, tracebackStepCache, opName); } else if (opName.equals(PredefinedType.ALL_INSTANCES_NAME)) { // the object from where to trace back later in the navigate method may not // conform to the type on which allInstances() is invoked here; for example, the // expression may navigate from the result of allInstances() across an association // defined on a superclass of the one on which allInstances() was invoked. Therefore, // ensure that the typing of the AllInstancesNavigationStep is correct. allInstancesClass = context; requireTypeExactly = false; } else { allInstancesClass = null; requireTypeExactly = false; } // hope, we didn't forget stdlib operations that pass on // source or argument values into their result } } private void handleSourcePassThroughOperation(OperationCallExp sourceExpression, EClass context, OperationBodyToCallMapper operationBodyToCallMapper, Stack<String> tupleLiteralNamesToLookFor, TracebackStepCache tracebackStepCache, String opName) { // FIXME handle product getSteps().add(createTracebackStepAndScopeChange(sourceExpression, (OCLExpression) sourceExpression.getSource(), context, operationBodyToCallMapper, tupleLiteralNamesToLookFor, tracebackStepCache)); if (argumentPassThroughStdLibOpNames.contains(opName)) { int paramPos = 0; if (opName.equals(PredefinedType.INSERT_AT_NAME)) { // "insertAt" takes two arguments, the index and the object to add. // The OCL spec says the index comes first, so getting the first argument makes no sense in this case. paramPos = 1; } OCLExpression argument = (OCLExpression) (sourceExpression.getArgument()).get(paramPos); getSteps().add(createTracebackStepAndScopeChange(sourceExpression, argument, context, operationBodyToCallMapper, tupleLiteralNamesToLookFor, tracebackStepCache)); } } private void handleOclAsType(OperationCallExp sourceExpression, EClass context, OperationBodyToCallMapper operationBodyToCallMapper, Stack<String> tupleLiteralNamesToLookFor, TracebackStepCache tracebackStepCache) { OCLExpression argument = (OCLExpression) (sourceExpression.getArgument()).get(0); if (argument instanceof TypeExp) { // trace the source expression of the cast getSteps().add(createTracebackStepAndScopeChange(sourceExpression, (OCLExpression) sourceExpression.getSource(), context, operationBodyToCallMapper, tupleLiteralNamesToLookFor, tracebackStepCache)); } else { throw new RuntimeException("What else could be the argument of oclAsType if not a TypeExp? " + (argument.eClass()).getName()); } } /** * @param changeEvent * if not <code>null</code>, and the event has a non- * <code>null</code> {@link Notification#getNotifier() notifier}, * this notifier will be used as the lookup context in case an * <code>allInstances</code> operation call needs to be traced * back by computing all context instances of the overall * expression; otherwise, the <code>source</code> object will be * used as the lookup context for <code>allInstances</code> * lookup */ @Override protected OperationCallExpKeyedSet performSubsequentTraceback(AnnotatedEObject source, UnusedEvaluationRequestSet pendingUnusedEvalRequests, org.eclipse.ocl.examples.impactanalyzer.instanceScope.traceback.TracebackCache tracebackCache, Notification changeEvent) { OperationCallExpKeyedSet result; if (allInstancesClass != null) { FlatSet preResult = new FlatSet(); Notifier allInstancesLookupContext; if (changeEvent != null && changeEvent.getNotifier() != null) { allInstancesLookupContext = (Notifier) changeEvent.getNotifier(); } else { allInstancesLookupContext = source.getAnnotatedObject(); } for (EObject roi : InstanceScopeAnalysis.getAllPossibleContextInstances( allInstancesLookupContext, allInstancesClass, oppositeEndFinder)) { preResult.add(annotateEObject(source, roi)); } result = preResult; } else { OperationCallExpKeyedSet preResult; if (requireTypeExactly && source.eClass() != requiredType) { preResult = FlatSet.emptySet(); } else { preResult = (OperationCallExpKeyedSet) super .performSubsequentTraceback(source, pendingUnusedEvalRequests, tracebackCache, changeEvent); } if (filterResultsByCall && tracebackCache.getConfiguration().isOperationCallSelectionActive()) { result = new IterableAsOperationCallExpKeyedSet(preResult.getCombinedResultsFor(getExpression())); } else { result = preResult; } } return result; } }