blob: d36e93cc8c4d835969524c7b396b1d71c46dc678 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2018 SAP AG and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* SAP AG - initial API and implementation
******************************************************************************/
package org.eclipse.ocl.examples.impactanalyzer.instanceScope;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.ocl.ecore.OCLExpression;
import org.eclipse.ocl.ecore.PropertyCallExp;
import org.eclipse.ocl.ecore.TupleLiteralExp;
import org.eclipse.ocl.ecore.opposites.OppositeEndFinder;
import org.eclipse.ocl.examples.impactanalyzer.impl.OperationBodyToCallMapper;
import org.eclipse.ocl.examples.impactanalyzer.util.OCLFactory;
import org.eclipse.ocl.examples.impactanalyzer.util.Tuple.Pair;
/**
* The instance scope analysis's goal is to pre-compute steps for each {@link PropertyCallExp} subexpression in an OCL
* expression's expression tree. These steps can each be a graph, referring to other potentially composite steps. The graph can
* even be cyclic, as in the case for recursive operation calls.
* <p>
*
* During the analysis of the <em>traceback</em> paths, for each subexpression visited, the <code>StepType</code> objects for that
* node is stored in this cache.
* <p>
*
* Don't re-use an instance of this class for analyzing more than one expression when those expressions are dynamically parsed
* because in those cases, new operation calls are created dynamically which turn existing entries in the
* {@link AbstractPathCache} for <tt>self</tt> and parameter expressions of the operation called invalid. Additionally, all
* dependent paths would become invalid too. Identifying and removing those entries from a {@link AbstractPathCache} seems to
* cause more effort than using a new {@link AbstractPathCache} object for each expression analyzed, particularly given the fact
* that the step assembly only has to happen once per life-time of an {@link OCLExpression} during
* a session.
*
*/
public abstract class AbstractPathCache<StepType> {
/**
* Keys are OCL expressions for which a navigation path is cached, together with a list of tuple part names to be collected
* during construction of that step. Only if this list is equal during construction then the step can be re-used. Example:
* <p>
*
* <pre>
* let a:Tuple{x1:X=self.myX1, x2:X=self.myX2} in
* Set{a.x1, a.x2}-&gt;collect(x | x.name)
* </pre>
*
* If this expression is analyzed for an attribute change event of an <tt>X.name</tt>, the iterator variable <tt>x</tt> is
* traced back to the collect's source expression which is the collection literal. A branching step is created with one branch
* for each of its literal parts. Both parts access a tuple part, one the <tt>x1</tt> part, the other the <tt>x2</tt> part of
* the same tuple. For <tt>a.x1</tt> the tracer pushes "x1" onto the "stack" and constructs a tracer for the <tt>a</tt> source
* expression. The {@link VariableExpTracer} finds the let expression and constructs the navigation path for the variable's
* init expression. This, however, is the same {@link TupleLiteralExp} that will be found when tracing back the <tt>a</tt> in
* <tt>a.x2</tt>. However, the first time the {@link TupleLiteralExpTracer} is constructed, it needs to descend into the
* <tt>x1</tt> part's init expression whereas for the <tt>a.x2</tt> it needs to descend into <tt>x2</tt>'s init expression.
* <p>
*
* This shows that a navigation path can only be re-used if the request for its construction has an equal list of tuple
* literal part names on the "stack" as the one cached.
* <p>
*
* The <tt>List&lt;String&gt;</tt> element of the key pair may be <tt>null</tt>. It <em>must</em> be <tt>null</tt> instead of
* passing an empty list to avoid ambiguities.
*/
private final Map<Pair<OCLExpression, List<String>>, StepType> subexpressionToPath = new HashMap<Pair<OCLExpression, List<String>>, StepType>();
/**
* Can be used for certain metamodel queries such as finding all subclasses, but as well during an <code>allInstances</code>
* query.
*/
private final OppositeEndFinder oppositeEndFinder;
private InstanceScopeAnalysis instanceScopeAnalysis;
public AbstractPathCache(OppositeEndFinder oppositeEndFinder) {
this.oppositeEndFinder = oppositeEndFinder;
}
/**
* Call only immediately after the constructor was called and only in case <code>null</code> was passed for
* the constructor's <code>instanceScopeAnalysis</code> argument.
*/
public void initInstanceScopeAnalysis(InstanceScopeAnalysis instanceScopeAnalysis) {
if (this.instanceScopeAnalysis != null) {
throw new IllegalStateException("instanceScopeAnalysis field on "+this+" already initialized");
}
this.instanceScopeAnalysis = instanceScopeAnalysis;
}
public InstanceScopeAnalysis getInstanceScopeAnalysis() {
return instanceScopeAnalysis;
}
public OppositeEndFinder getOppositeEndFinder() {
return oppositeEndFinder;
}
public StepType getPathForNode(OCLExpression subexpression, Stack<String> tupleLiteralPartNamesToLookFor) {
return subexpressionToPath.get(new Pair<OCLExpression, List<String>>(subexpression,
getTupleLiteralPartNamesToLookForAsList(tupleLiteralPartNamesToLookFor)));
}
/**
* Also adds <code>path</code> to {@link #allSteps}. If the source type is <code>null</code> and the step is not
* absolute, this path cache registers as a listener on the step (see
* <code>StepType.addSourceTypeChangeListener(SourceTypeChangeListener)</code>. If the target type is <code>null</code>, this
* path cache registers as target type listener on the step (see
* <code>StepType.addTargetTypeChangeListener(TargetTypeChangeListener)</code>. If the step is not marked as always empty,
* this path cache registers as listener for a change in the step's always-empty setting. If any of these change events are
* received, the respective step is re-hashed into {@link #allSteps}.
*/
public void put(OCLExpression subexpression, Stack<String> tupleLiteralPartNamesToLookFor, StepType path) {
List<String> tupleLiteralPartNamesToLookForAsList = getTupleLiteralPartNamesToLookForAsList(tupleLiteralPartNamesToLookFor);
subexpressionToPath.put(new Pair<OCLExpression, List<String>>(subexpression, tupleLiteralPartNamesToLookForAsList), path);
}
private static List<String> getTupleLiteralPartNamesToLookForAsList(Stack<String> tupleLiteralPartNamesToLookFor) {
List<String> tupleLiteralPartNamesToLookForAsList;
if (tupleLiteralPartNamesToLookFor == null || tupleLiteralPartNamesToLookFor.size() == 0) {
tupleLiteralPartNamesToLookForAsList = null;
} else {
tupleLiteralPartNamesToLookForAsList = new ArrayList<String>();
Iterator<String> it = tupleLiteralPartNamesToLookFor.listIterator();
while(it.hasNext()){
tupleLiteralPartNamesToLookForAsList.add(it.next());
}
}
return tupleLiteralPartNamesToLookForAsList;
}
public StepType getOrCreateNavigationPath(OCLExpression sourceExpression, EClass context,
OperationBodyToCallMapper operationBodyToCallMapper, Stack<String> tupleLiteralNamesToLookFor, OCLFactory oclFactory) {
StepType result = getPathForNode(sourceExpression, tupleLiteralNamesToLookFor);
if (result == null) {
result = createStep(sourceExpression, context, operationBodyToCallMapper, tupleLiteralNamesToLookFor, oclFactory);
put(sourceExpression, tupleLiteralNamesToLookFor, result);
}
return result;
}
protected abstract StepType createStep(OCLExpression sourceExpression, EClass context,
OperationBodyToCallMapper operationBodyToCallMapper, Stack<String> tupleLiteralNamesToLookFor, OCLFactory oclFactory);
}