blob: 93ef87f7085ad3e8739e43749c0e561ad8052c8a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2015, 2018 Willink Transformations 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:
* E.D.Willink - Initial API and implementation
*******************************************************************************/
package org.eclipse.qvtd.compiler.internal.qvtb2qvts;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.ocl.pivot.CallExp;
import org.eclipse.ocl.pivot.CollectionItem;
import org.eclipse.ocl.pivot.CollectionLiteralExp;
import org.eclipse.ocl.pivot.CollectionLiteralPart;
import org.eclipse.ocl.pivot.CollectionRange;
import org.eclipse.ocl.pivot.CollectionType;
import org.eclipse.ocl.pivot.CompleteClass;
import org.eclipse.ocl.pivot.CompleteModel;
import org.eclipse.ocl.pivot.Element;
import org.eclipse.ocl.pivot.ExpressionInOCL;
import org.eclipse.ocl.pivot.IfExp;
import org.eclipse.ocl.pivot.LanguageExpression;
import org.eclipse.ocl.pivot.LetExp;
import org.eclipse.ocl.pivot.LiteralExp;
import org.eclipse.ocl.pivot.LoopExp;
import org.eclipse.ocl.pivot.MapLiteralExp;
import org.eclipse.ocl.pivot.MapLiteralPart;
import org.eclipse.ocl.pivot.NavigationCallExp;
import org.eclipse.ocl.pivot.NullLiteralExp;
import org.eclipse.ocl.pivot.OCLExpression;
import org.eclipse.ocl.pivot.Operation;
import org.eclipse.ocl.pivot.OperationCallExp;
import org.eclipse.ocl.pivot.Property;
import org.eclipse.ocl.pivot.ShadowExp;
import org.eclipse.ocl.pivot.ShadowPart;
import org.eclipse.ocl.pivot.StandardLibrary;
import org.eclipse.ocl.pivot.TupleLiteralExp;
import org.eclipse.ocl.pivot.TupleLiteralPart;
import org.eclipse.ocl.pivot.Type;
import org.eclipse.ocl.pivot.TypeExp;
import org.eclipse.ocl.pivot.TypedElement;
import org.eclipse.ocl.pivot.Variable;
import org.eclipse.ocl.pivot.VariableDeclaration;
import org.eclipse.ocl.pivot.VariableExp;
import org.eclipse.ocl.pivot.VoidType;
import org.eclipse.ocl.pivot.ids.OperationId;
import org.eclipse.ocl.pivot.internal.manager.FinalAnalysis;
import org.eclipse.ocl.pivot.internal.manager.PivotMetamodelManager;
import org.eclipse.ocl.pivot.internal.utilities.EnvironmentFactoryInternal.EnvironmentFactoryInternalExtension;
import org.eclipse.ocl.pivot.util.Visitable;
import org.eclipse.ocl.pivot.utilities.ClassUtil;
import org.eclipse.ocl.pivot.utilities.MetamodelManager;
import org.eclipse.ocl.pivot.utilities.NameUtil;
import org.eclipse.ocl.pivot.utilities.NameUtil.ToStringComparator;
import org.eclipse.ocl.pivot.utilities.ParserException;
import org.eclipse.ocl.pivot.utilities.PivotUtil;
import org.eclipse.ocl.pivot.utilities.TracingOption;
import org.eclipse.qvtd.compiler.CompilerConstants;
import org.eclipse.qvtd.pivot.qvtbase.Function;
import org.eclipse.qvtd.pivot.qvtbase.Transformation;
import org.eclipse.qvtd.pivot.qvtbase.utilities.QVTbaseUtil;
import org.eclipse.qvtd.pivot.qvtbase.utilities.StandardLibraryHelper;
import org.eclipse.qvtd.pivot.qvtcore.CorePattern;
import org.eclipse.qvtd.pivot.qvtcore.Mapping;
import org.eclipse.qvtd.pivot.qvtcore.OppositePropertyAssignment;
import org.eclipse.qvtd.pivot.qvtcore.PropertyAssignment;
import org.eclipse.qvtd.pivot.qvtcore.analysis.RootDomainUsageAnalysis;
import org.eclipse.qvtd.pivot.qvtcore.util.AbstractExtendingQVTcoreVisitor;
import org.eclipse.qvtd.pivot.qvtcore.utilities.QVTcoreUtil;
import org.eclipse.qvtd.pivot.qvtschedule.utilities.DomainUsage;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
/**
* The DependencyAnalyzer performs an simulated execution of an expression tree propagating dependencies to called operations
* and returning the referenced types and the hidden type/property-path accesses within the execution.
* Cached Operation analyses are specialized wrt their invocation arguments so that the analysis of each OperationCallExp
* is sensitive to actual usage.
*/
public class OperationDependencyAnalysis
{
public static final @NonNull TracingOption ATTEMPT = new TracingOption(CompilerConstants.PLUGIN_ID, "dependency/attempt");
public static final @NonNull TracingOption CALL = new TracingOption(CompilerConstants.PLUGIN_ID, "dependency/call");
public static final @NonNull TracingOption CREATE = new TracingOption(CompilerConstants.PLUGIN_ID, "dependency/create");
public static final @NonNull TracingOption HYPOTHECATING = new TracingOption(CompilerConstants.PLUGIN_ID, "dependency/hypothecating");
public static final @NonNull TracingOption PENDING = new TracingOption(CompilerConstants.PLUGIN_ID, "dependency/pending");
public static final @NonNull TracingOption REFINING = new TracingOption(CompilerConstants.PLUGIN_ID, "dependency/refining");
public static final @NonNull TracingOption RESULT = new TracingOption(CompilerConstants.PLUGIN_ID, "dependency/result");
public static final @NonNull TracingOption START = new TracingOption(CompilerConstants.PLUGIN_ID, "dependency/start");
/**
* The DependencyStepFactory ensures that all DependencyStep instances are singletons. This in turn ensures that List<DependencyStep>
* and Set<List<DependencyStep>> are compatibly hashed for use as keys ensuring singleton DependencyPaths.
*/
protected static class DependencyStepFactory
{
protected final @NonNull DomainUsage usage;
private final @NonNull Map<org.eclipse.ocl.pivot.@NonNull Class, @NonNull ClassDependencyStep> class2step = new HashMap<>();
private final @NonNull Map<@NonNull VariableDeclaration, @NonNull ParameterDependencyStep> parameter2step = new HashMap<>();
private final @NonNull Map<@NonNull Property, @NonNull NavigationDependencyStep> property2step = new HashMap<>();
protected DependencyStepFactory(@NonNull DomainUsage usage) {
this.usage = ClassUtil.nonNullState(usage);
}
public @NonNull ClassDependencyStep createClassDependencyStep(org.eclipse.ocl.pivot.@NonNull Class type, @NonNull Element element) {
ClassDependencyStep dependencyStep = class2step.get(type);
if (dependencyStep == null) {
dependencyStep = new ClassDependencyStep(usage, type, element);
class2step.put(type, dependencyStep);
}
return dependencyStep;
}
public @NonNull ParameterDependencyStep createParameterDependencyStep(org.eclipse.ocl.pivot.@NonNull Class type, @NonNull VariableDeclaration parameter) {
ParameterDependencyStep dependencyStep = parameter2step.get(parameter);
if (dependencyStep == null) {
dependencyStep = new ParameterDependencyStep(usage, type, parameter);
parameter2step.put(parameter, dependencyStep);
}
return dependencyStep;
}
public @NonNull NavigationDependencyStep createPropertyDependencyStep(@NonNull NavigationCallExp navigationCallExp) {
Property property = PivotUtil.getReferredProperty(navigationCallExp);
assert property != null;
NavigationDependencyStep dependencyStep = property2step.get(property);
if (dependencyStep == null) {
dependencyStep = new NavigationDependencyStep(usage, property, navigationCallExp);
property2step.put(property, dependencyStep);
}
return dependencyStep;
}
public @NonNull NavigationDependencyStep createPropertyDependencyStep(@NonNull Property containerProperty, @NonNull OperationCallExp oclContainerCallExp) {
NavigationDependencyStep dependencyStep = property2step.get(containerProperty);
if (dependencyStep == null) {
dependencyStep = new NavigationDependencyStep(usage, containerProperty, oclContainerCallExp);
property2step.put(containerProperty, dependencyStep);
}
return dependencyStep;
}
public @NonNull DomainUsage getUsage() {
return usage;
}
}
protected abstract static class BasicDependencyStep implements OperationDependencyStep
{
protected final @NonNull DomainUsage usage;
protected final @NonNull Element element;
protected BasicDependencyStep(@NonNull DomainUsage usage, @NonNull Element element) {
this.usage = usage;
this.element = element;
}
@Override
public @NonNull Element getElement() {
return element;
}
public @Nullable CallExp getCallExp() {
return null;
}
public @Nullable Property getProperty() {
return null;
}
@Override
public @NonNull DomainUsage getUsage() {
return usage;
}
public boolean isParameter() {
return false;
}
}
protected static class ClassDependencyStep extends BasicDependencyStep implements OperationDependencyStep.ClassStep
{
private final org.eclipse.ocl.pivot.@NonNull Class type;
public ClassDependencyStep(@NonNull DomainUsage usage, org.eclipse.ocl.pivot.@NonNull Class type, @NonNull Element element) {
super(usage, element);
this.type = type;
assert !(type instanceof CollectionType);
assert !(type instanceof VoidType);
}
@Override
public org.eclipse.ocl.pivot.@NonNull Class getElementalType() {
return type;
}
@Override
public String getName() {
return type.getName();
}
@Override
public @NonNull Element getPathElement() {
return type;
}
@Override
public String toString() {
return usage + " «" + type.eClass().getName() + "»" + type.toString();
}
}
protected static class NavigationDependencyStep extends BasicDependencyStep implements OperationDependencyStep.PropertyStep
{
protected final @NonNull Property property;
public NavigationDependencyStep(@NonNull DomainUsage usage, @NonNull Property property, @NonNull CallExp element) {
super(usage, element);
this.property = property;
}
@Override
public @NonNull CallExp getCallExp() {
return (CallExp) element;
}
@Override
public org.eclipse.ocl.pivot.@NonNull Class getElementalType() {
Type type = property.getType();
assert type != null;
while (type instanceof CollectionType) {
type = ((CollectionType)type).getElementType();
assert type != null;
}
return (org.eclipse.ocl.pivot.Class) type;
}
@Override
public String getName() {
return property.getName();
}
@Override
public @NonNull Element getPathElement() {
return property;
}
@Override
public @NonNull Property getProperty() {
return property;
}
@Override
public String toString() {
return usage + " «" + property.eClass().getName() + "»" + property.toString();
}
}
protected static class ParameterDependencyStep extends BasicDependencyStep
{
private final org.eclipse.ocl.pivot.@NonNull Class type;
public ParameterDependencyStep(@NonNull DomainUsage usage, org.eclipse.ocl.pivot.@NonNull Class type, @NonNull VariableDeclaration parameter) {
super(usage, parameter);
this.type = type;
assert !(type instanceof CollectionType);
}
@Override
public org.eclipse.ocl.pivot.@NonNull Class getElementalType() {
return type;
}
@Override
public String getName() {
return ((VariableDeclaration)element).getName();
}
@Override
public boolean isParameter() {
return true;
}
@Override
public @NonNull Element getPathElement() {
return element;
}
@Override
public String toString() {
return usage + " «" + type.eClass().getName() + "»" + ((VariableDeclaration)element).getName() + ":" + type.toString();
}
}
/**
* DependencyPaths describes the set of alternative usages of an analyzed operation input/output value.
*
* Each usage is a sequence of dependency steps, rooted by a ClassDependencyStp and optionally suffixed by
* NavigationDependencySteps.
*
* Each usage comprises overt usages that contribute to the analyze value and further hidden usages
* that are used behind the scenes. e.g. a.b=c, has Boolean as an explicit return and A::b, C as hidden usages.
*/
protected static class BasicDependencyPaths implements OperationDependencyPaths
{
protected final @NonNull OperationDependencyAnalysis operationDependencyAnalysis;
private final @NonNull Set<@NonNull List<@NonNull OperationDependencyStep>> returnPaths;
private final @NonNull Set<@NonNull List<@NonNull OperationDependencyStep>> hiddenPaths;
protected BasicDependencyPaths(@NonNull OperationDependencyAnalysis operationDependencyAnalysis,
@Nullable Set<@NonNull List<@NonNull OperationDependencyStep>> returnPaths,
@Nullable Set<@NonNull List<@NonNull OperationDependencyStep>> hiddenPaths) {
this.operationDependencyAnalysis = operationDependencyAnalysis;
Set<@NonNull List<@NonNull OperationDependencyStep>> immutableReturnPaths;
if (returnPaths != null) {
immutableReturnPaths = new HashSet<>(returnPaths.size());
for (@NonNull List<@NonNull OperationDependencyStep> returnPath : returnPaths) {
immutableReturnPaths.add(Collections.unmodifiableList(returnPath));
}
}
else {
immutableReturnPaths = Collections.emptySet();
}
this.returnPaths = Collections.unmodifiableSet(immutableReturnPaths);
Set<@NonNull List<@NonNull OperationDependencyStep>> immutableHiddenPaths;
if (hiddenPaths != null) {
immutableHiddenPaths = new HashSet<>(hiddenPaths.size());
for (@NonNull List<@NonNull OperationDependencyStep> hiddenPath : hiddenPaths) {
immutableHiddenPaths.add(Collections.unmodifiableList(hiddenPath));
}
}
else {
immutableHiddenPaths = Collections.emptySet();
}
this.hiddenPaths = Collections.unmodifiableSet(immutableHiddenPaths);
}
/**
* Return a new DependencyPaths formed by the addition of both the returnPaths and the hiddenPaths of secondPath to these hiddenPaths
*/
public @NonNull BasicDependencyPaths addHidden(@NonNull BasicDependencyPaths secondPath) {
Set<@NonNull List<@NonNull OperationDependencyStep>> newHiddenPaths = hiddenPaths;
Set<@NonNull List<@NonNull OperationDependencyStep>> secondPathHiddenPaths = secondPath.basicGetHiddenPaths();
newHiddenPaths = new HashSet<>(newHiddenPaths);
newHiddenPaths.addAll(secondPathHiddenPaths);
Set<@NonNull List<@NonNull OperationDependencyStep>> secondPathReturnPaths = secondPath.basicGetReturnPaths();
newHiddenPaths = new HashSet<>(newHiddenPaths);
newHiddenPaths.addAll(secondPathReturnPaths);
return operationDependencyAnalysis.createDependencyPaths(returnPaths, newHiddenPaths);
}
/**
* Return a new DependencyPaths formed by the addition of the returnPaths of secondPath to these returnPaths
* and the addition of the hiddenPaths of secondPath to these hiddenPaths
*/
public @NonNull BasicDependencyPaths addReturn(@NonNull BasicDependencyPaths secondPath) {
Set<@NonNull List<@NonNull OperationDependencyStep>> newReturnPaths;
Set<@NonNull List<@NonNull OperationDependencyStep>> secondPathReturnPaths = secondPath.basicGetReturnPaths();
newReturnPaths = new HashSet<>(returnPaths);
newReturnPaths.addAll(secondPathReturnPaths);
Set<@NonNull List<@NonNull OperationDependencyStep>> newHiddenPaths;
Set<@NonNull List<@NonNull OperationDependencyStep>> secondPathHiddenPaths = secondPath.basicGetHiddenPaths();
newHiddenPaths = new HashSet<>(hiddenPaths);
newHiddenPaths.addAll(secondPathHiddenPaths);
return operationDependencyAnalysis.createDependencyPaths(newReturnPaths, newHiddenPaths);
}
public @NonNull BasicDependencyPaths append(@NonNull NavigationDependencyStep propertyDependencyStep) {
Set<@NonNull List<@NonNull OperationDependencyStep>> oldReturnPaths = returnPaths;
if (isRedundant(oldReturnPaths, propertyDependencyStep.getProperty())) {
return this;
}
StandardLibrary standardLibrary = operationDependencyAnalysis.getStandardLibrary();
Set<@NonNull List<@NonNull OperationDependencyStep>> newReturnPaths = new HashSet<>();
for (@NonNull List<@NonNull OperationDependencyStep> oldReturnPath : oldReturnPaths) {
List<@NonNull OperationDependencyStep> newReturnPath = null;
int size = oldReturnPath.size();
assert size > 0;
if ((size > 0) && (oldReturnPath.get(size-1) == propertyDependencyStep)) { // ?? conformance
newReturnPath = oldReturnPath;
}
else {
org.eclipse.ocl.pivot.Class endClass = oldReturnPath.get(size-1).getElementalType();
org.eclipse.ocl.pivot.Class sourceClass = propertyDependencyStep.getProperty().getOwningClass();
assert sourceClass != null;
if (endClass.conformsTo(standardLibrary, sourceClass)) {
newReturnPath = new ArrayList<>(oldReturnPath);
newReturnPath.add(propertyDependencyStep);
}
else if (sourceClass.conformsTo(standardLibrary, endClass)) {
if (oldReturnPath.size() == 1) {
ClassDependencyStep newClassDependencyStep = operationDependencyAnalysis.createClassDependencyStep(sourceClass, oldReturnPath.get(0).getElement());
newReturnPath = new ArrayList<>();
newReturnPath.add(newClassDependencyStep);
}
else {
newReturnPath = new ArrayList<>(oldReturnPath); // Consumer is responsible for observing the retyping from endClass to sourceClass
}
newReturnPath.add(propertyDependencyStep);
}
else {
newReturnPath = oldReturnPath;
}
}
newReturnPaths.add(newReturnPath);
}
return operationDependencyAnalysis.createDependencyPaths(newReturnPaths, hiddenPaths);
}
// @Override
public @NonNull Set<@NonNull List<@NonNull OperationDependencyStep>> basicGetHiddenPaths() {
return hiddenPaths;
}
// @Override
public @NonNull Set<@NonNull List<@NonNull OperationDependencyStep>> basicGetReturnPaths() {
return returnPaths;
}
/**
* Return the hidden paths as an ordered iterable.
*/
@Override
public @NonNull Iterable<@NonNull List<@NonNull OperationDependencyStep>> getHiddenPaths() {
Map<@NonNull String, @NonNull List<@NonNull OperationDependencyStep>> map = new HashMap<>();
for (@NonNull List<@NonNull OperationDependencyStep> list : hiddenPaths) {
map.put(String.valueOf(list), list);
}
List<@NonNull String> sortedKeys = new ArrayList<>(map.keySet());
Collections.sort(sortedKeys);
List<@NonNull List<@NonNull OperationDependencyStep>> sortedList = new ArrayList<>();
for (@NonNull String key : sortedKeys) {
List<@NonNull OperationDependencyStep> steps = map.get(key);
assert steps != null;
sortedList.add(steps);
}
return sortedList;
}
/**
* Return the return paths as an ordered iterable.
*/
@Override
public @NonNull Iterable<@NonNull List<@NonNull OperationDependencyStep>> getReturnPaths() {
Map<@NonNull String, @NonNull List<@NonNull OperationDependencyStep>> map = new HashMap<>();
for (@NonNull List<@NonNull OperationDependencyStep> list : returnPaths) {
map.put(String.valueOf(list), list);
}
List<@NonNull String> sortedKeys = new ArrayList<@NonNull String>(map.keySet());
Collections.sort(sortedKeys);
List<@NonNull List<@NonNull OperationDependencyStep>> sortedList = new ArrayList<>();
for (@NonNull String key : sortedKeys) {
List<@NonNull OperationDependencyStep> steps = map.get(key);
assert steps != null;
sortedList.add(steps);
}
return sortedList;
}
/**
* A property isRedundant if all paths have property as their final navigation.
*/
protected static boolean isRedundant(@NonNull Set<@NonNull List<@NonNull OperationDependencyStep>> paths, @NonNull Property property) {
for (@NonNull List<@NonNull OperationDependencyStep> path : paths) {
int size = path.size();
if (size <= 0) {
return false;
}
OperationDependencyStep dependencyStep = path.get(size-1);
if (!(dependencyStep instanceof NavigationDependencyStep)) {
return false;
}
if (((NavigationDependencyStep)dependencyStep).getProperty() != property) {
return false;
}
}
return true;
}
@Override
public @NonNull String toString() {
StringBuilder s = new StringBuilder();
s.append("{");
Iterable<@NonNull List<@NonNull OperationDependencyStep>> returnPaths2 = getReturnPaths();
boolean iFirst = true;
for (@NonNull List<@NonNull OperationDependencyStep> path : returnPaths2) {
if (!iFirst) {
s.append("|");
}
boolean jFirst = true;
for (@NonNull OperationDependencyStep step : path) {
if (!jFirst) {
s.append("::");
}
s.append(step.getName());
jFirst = false;
}
iFirst = false;
}
s.append("}++{");
Iterable<@NonNull List<@NonNull OperationDependencyStep>> hiddenPaths2 = getHiddenPaths();
iFirst = true;
for (@NonNull List<@NonNull OperationDependencyStep> path : hiddenPaths2) {
if (!iFirst) {
s.append("|");
}
boolean jFirst = true;
for (@NonNull OperationDependencyStep step : path) {
if (!jFirst) {
s.append("::");
}
s.append(step.getName());
jFirst = false;
}
iFirst = false;
}
s.append("}");
return s.toString();
}
}
/**
* A DependencyAnalyzerVisitor performs an analyzed execution of a QVTi expression propagating the set of returned and used paths as
* the analysis result.
*/
protected class DependencyAnalyzerVisitor extends AbstractExtendingQVTcoreVisitor<@Nullable BasicDependencyPaths, @Nullable Object>
{
private final @NonNull AbstractOperationAnalysis analysis;
/**
* True if an exact result (or null) is required. False if a partial result is permitted in order to unlock an analysis loop.
*/
private final boolean exactResult;
private final @Nullable DependencyAnalyzerVisitor parent;
private final @NonNull Map<@NonNull VariableDeclaration, @NonNull BasicDependencyPaths> variable2dependencies = new HashMap<>();
protected DependencyAnalyzerVisitor(@NonNull AbstractOperationAnalysis analysis, boolean exactResult) {
super(null);
this.analysis = analysis;
this.exactResult = exactResult;
this.parent = null;
}
protected DependencyAnalyzerVisitor(@NonNull DependencyAnalyzerVisitor parent) {
super(null);
this.analysis = parent.analysis;
this.exactResult = parent.exactResult;
this.parent = parent;
}
private @Nullable BasicDependencyPaths addHidden(@Nullable BasicDependencyPaths accumulatingResult, @Nullable BasicDependencyPaths additionalResult) {
if (exactResultRequired()) {
if ((accumulatingResult == null) || (additionalResult == null)) {
return null;
}
else {
return accumulatingResult.addHidden(additionalResult);
}
}
else {
assert accumulatingResult != null;
if (additionalResult == null) {
return accumulatingResult;
}
else {
return accumulatingResult.addHidden(additionalResult);
}
}
}
private @Nullable BasicDependencyPaths addReturn(@Nullable BasicDependencyPaths accumulatingResult, @Nullable BasicDependencyPaths additionalResult) {
if (exactResultRequired()) {
if ((accumulatingResult == null) || (additionalResult == null)) {
return null;
}
else {
return accumulatingResult.addReturn(additionalResult);
}
}
else {
assert accumulatingResult != null;
if (additionalResult == null) {
return accumulatingResult;
}
else {
return accumulatingResult.addReturn(additionalResult);
}
}
}
private void addVariable(@NonNull VariableDeclaration variable, @NonNull BasicDependencyPaths value) {
variable2dependencies.put(variable, value);
}
public @Nullable BasicDependencyPaths analyze(/*@NonNull*/ Visitable element) {
return element.accept(this);
}
public @NonNull OperationDependencyPaths analyzeOperation(@NonNull OperationCallExp operationCallExp) {
Operation referredOperation = operationCallExp.getReferredOperation();
assert referredOperation != null;
//
// Form the resultPaths by retaining all hidden dependencies; all dependencies are passed out.
//
List<@NonNull BasicDependencyPaths> resultPaths = getResultPaths(operationCallExp);
assert resultPaths != null;
List<@NonNull BasicDependencyPaths> allSourceAndArgumentPaths = getAllSourceAndArgumentPaths(resultPaths);
BasicDependencyPaths result = emptyDependencyPaths;
for (@NonNull List<@NonNull BasicDependencyPaths> aSourceAndArgumentPaths : getEachSourceAndArgumentPaths(allSourceAndArgumentPaths)) {
OperationDependencyPaths sourcePaths = aSourceAndArgumentPaths.get(0);
for (@NonNull List<@NonNull OperationDependencyStep> steps : sourcePaths.getReturnPaths()) {
for (@NonNull OperationDependencyStep step : steps) {
org.eclipse.ocl.pivot.Class sourceClass = step.getElementalType();
CompleteClass selfClass = completeModel.getCompleteClass(sourceClass);
Iterable<@NonNull Operation> overrides = getOverrides(selfClass, referredOperation);
for (@NonNull Operation operation : overrides) {
OperationId operationId = operation.getOperationId();
Map<@NonNull List<@NonNull BasicDependencyPaths>, @NonNull OperationAnalysis> args2result = operation2paths2analysis.get(operationId);
if (args2result != null) {
OperationAnalysis operationAnalysis = args2result.get(aSourceAndArgumentPaths);
if (operationAnalysis != null) {
BasicDependencyPaths operationResult = operationAnalysis.basicGetResult();
if (operationResult != null) {
result = result.addReturn(operationResult);
}
}
}
}
}
}
}
return result;
}
private @NonNull BasicDependencyPaths analyzeOperationCallExp_oclContainer(@NonNull OperationCallExp operationCallExp, @NonNull List<@NonNull BasicDependencyPaths> sourceAndArgumentPaths) {
assert sourceAndArgumentPaths.size() == 1;
BasicDependencyPaths sourcePath = sourceAndArgumentPaths.get(0);
Iterable<@NonNull List<@NonNull OperationDependencyStep>> oldReturnPaths = sourcePath.getReturnPaths();
BasicDependencyPaths result = null;
//
// The simple case of a single property can be appended to the existing path.
//
if (Iterables.size(oldReturnPaths) == 1) {
List<@NonNull OperationDependencyStep> steps = oldReturnPaths.iterator().next();
int size = steps.size();
assert size > 0;
OperationDependencyStep lastStep = steps.get(size-1);
CompleteClass containedClass = completeModel.getCompleteClass(lastStep.getElementalType());
Iterable<@NonNull Property> containmentProperties = containmentAnalysis.getContainmentProperties(containedClass); // FIXME exploit an oclAsType
if (Iterables.size(containmentProperties) == 1) {
Property containmentProperty = containmentProperties.iterator().next();
Property containerProperty = containmentProperty.getOpposite();
if (containerProperty != null) {
result = sourcePath.append(createPropertyDependencyStep(containerProperty, operationCallExp));
}
}
}
//
// The more complex case risks a combinatorial explosion of possible paths. Therefore the new container types become the return types,
// and each possible containment is a hidden path in addition to all the source paths.
//
if (result == null) {
Set<@NonNull List<@NonNull OperationDependencyStep>> newReturnPaths = new HashSet<>();
Set<@NonNull List<@NonNull OperationDependencyStep>> newHiddenPaths = new HashSet<>();
Iterables.addAll(newHiddenPaths, oldReturnPaths);
Iterables.addAll(newHiddenPaths, sourcePath.getHiddenPaths());
for (@NonNull List<@NonNull OperationDependencyStep> steps : oldReturnPaths) {
int size = steps.size();
assert size > 0;
OperationDependencyStep lastStep = steps.get(size-1);
CompleteClass containedClass = completeModel.getCompleteClass(lastStep.getElementalType());
Iterable<@NonNull Property> containmentProperties = containmentAnalysis.getContainmentProperties(containedClass); // FIXME exploit an oclAsType
for (@NonNull Property containmentProperty : containmentProperties) {
org.eclipse.ocl.pivot.Class containerClass = PivotUtil.getOwningClass(containmentProperty);
ClassDependencyStep classDependencyStep = createClassDependencyStep(containerClass, operationCallExp);
newReturnPaths.add(Lists.newArrayList(classDependencyStep));
newHiddenPaths.add(Lists.newArrayList(classDependencyStep, createPropertyDependencyStep(containmentProperty, operationCallExp)));
}
}
result = createDependencyPaths(newReturnPaths, newHiddenPaths);
}
if (RESULT.isActive()) {
StringBuilder s = new StringBuilder();
for (@NonNull OperationDependencyPaths paths : sourceAndArgumentPaths) {
s.append("\n\t=> " + paths.toString());
}
s.append("\n\t= " + result.toString());
RESULT.println(operationCallExp.getReferredOperation() + s.toString());
}
return result;
}
private boolean exactResultRequired() {
return exactResult || (pending.size() > 0);
}
/* private @NonNull DependencyPaths analyzeOperationCallExp_collectionSelectByKind(@NonNull OperationCallExp operationCallExp,
@NonNull List<@NonNull DependencyPaths> argumentPaths) {
OCLExpression oclExpression = operationCallExp.getOwnedArguments().get(0);
Type typeValue = oclExpression.getTypeValue();
DependencyPaths result = emptyDependencyPaths;
for (DependencyPaths argumentPath : argumentPaths) {
if (argumentPath.conformsTo(typeValue)) {
result.addReturn(argumentPath);
}
}
return result;
} */
protected @NonNull List<@NonNull BasicDependencyPaths> getAllSourceAndArgumentPaths(@NonNull List<@NonNull BasicDependencyPaths> resultPaths) {
List<@NonNull BasicDependencyPaths> sourceAndArgumentPaths = new ArrayList<>(resultPaths.size());
for (@NonNull BasicDependencyPaths resultPath : resultPaths) {
Set<@NonNull List<@NonNull OperationDependencyStep>> typePaths = new HashSet<>();
for (@NonNull List<@NonNull OperationDependencyStep> path : resultPath.basicGetReturnPaths()) {
OperationDependencyStep lastStep = path.get(path.size()-1);
TypedElement typedElement = (TypedElement)lastStep.getElement();
assert typedElement != null;
Type type = typedElement.getType();
assert type != null;
ClassDependencyStep classStep = createClassDependencyStep(type, typedElement);
typePaths.add(Collections.singletonList(classStep));
}
sourceAndArgumentPaths.add(createDependencyPaths(typePaths, null));
}
return sourceAndArgumentPaths;
}
protected @NonNull Iterable<@NonNull List<@NonNull BasicDependencyPaths>> getEachSourceAndArgumentPaths(@NonNull List<@NonNull BasicDependencyPaths> allSourceAndArgumentPaths) {
List<@NonNull List<@NonNull BasicDependencyPaths>> eachSourceAndArgumentPaths = new ArrayList<>();
for (@NonNull List<@NonNull OperationDependencyStep> aSourceStep : allSourceAndArgumentPaths.get(0).getReturnPaths()) {
List<@NonNull BasicDependencyPaths> aSourceAndArgumentPaths = new ArrayList<>();
aSourceAndArgumentPaths.add(createDependencyPaths(Collections.singleton(aSourceStep), null));
for (int i = 1; i < allSourceAndArgumentPaths.size(); i++) {
aSourceAndArgumentPaths.add(allSourceAndArgumentPaths.get(i));
}
eachSourceAndArgumentPaths.add(aSourceAndArgumentPaths);
}
return eachSourceAndArgumentPaths;
}
protected @NonNull Iterable<@NonNull Operation> getOverrides(@NonNull CompleteClass selfClass, @NonNull Operation referredOperation) {
if ((selfClass == oclVoidCompleteClass) || (selfClass == oclInvalidCompleteClass)) {
return Collections.emptyList();
}
return finalAnalysis.getOverrides(referredOperation, selfClass);
}
protected @Nullable List<@NonNull BasicDependencyPaths> getResultPaths(@NonNull OperationCallExp operationCallExp) {
List<@NonNull BasicDependencyPaths> resultPaths = new ArrayList<>();
BasicDependencyPaths sourcePaths = analyze(operationCallExp.getOwnedSource());
if (sourcePaths == null) {
return null;
}
resultPaths.add(createDependencyPaths(sourcePaths.basicGetReturnPaths(), sourcePaths.basicGetHiddenPaths()));
List<@NonNull OCLExpression> arguments = ClassUtil.nullFree(operationCallExp.getOwnedArguments());
for (@NonNull OCLExpression argument : arguments) {
BasicDependencyPaths argumentPaths = analyze(argument);
if (argumentPaths == null) {
return null;
}
resultPaths.add(createDependencyPaths(argumentPaths.basicGetReturnPaths(), argumentPaths.basicGetHiddenPaths()));
}
return resultPaths;
}
protected @NonNull BasicDependencyPaths getVariable(@NonNull VariableDeclaration variable) {
BasicDependencyPaths result = variable2dependencies.get(variable);
if (result != null) {
return result;
}
else if (parent != null) {
return parent.getVariable(variable);
}
else {
EObject eContainer = variable.eContainer();
if (eContainer instanceof CorePattern) {
return createParameterDependencyPaths(variable);
}
else {
return createDependencyPaths(variable);
}
}
}
@Override
public @Nullable BasicDependencyPaths visiting(@NonNull Visitable visitable) {
throw new UnsupportedOperationException(getClass().getSimpleName() + ": " + visitable.getClass().getSimpleName());
}
@Override
public @Nullable BasicDependencyPaths visitCollectionLiteralExp(@NonNull CollectionLiteralExp collectionLiteralExp) {
BasicDependencyPaths result = emptyDependencyPaths;
for (@NonNull CollectionLiteralPart ownedPart : ClassUtil.nullFree(collectionLiteralExp.getOwnedParts())) {
result = addReturn(result, analyze(ownedPart));
}
return result;
}
@Override
public @Nullable BasicDependencyPaths visitCollectionItem(@NonNull CollectionItem collectionItem) {
return analyze(collectionItem.getOwnedItem());
}
@Override
public @Nullable BasicDependencyPaths visitCollectionRange(@NonNull CollectionRange collectionRange) {
return null;
}
@Override
public @Nullable BasicDependencyPaths visitIfExp(@NonNull IfExp ifExp) {
if (ifExp.toString().contains("lookupPackage")) {
ifExp.toString();
}
BasicDependencyPaths result = analyze(ifExp.getOwnedThen());
result = addReturn(result, analyze(ifExp.getOwnedElse()));
result = addHidden(result, analyze(ifExp.getOwnedCondition()));
return result;
}
@Override
public @Nullable BasicDependencyPaths visitLetExp(@NonNull LetExp letExp) {
Variable ownedVariable = letExp.getOwnedVariable();
BasicDependencyPaths initResult = analyze(ownedVariable.getOwnedInit());
if (initResult == null) {
return null;
}
DependencyAnalyzerVisitor nestedAnalyzer = new DependencyAnalyzerVisitor(this);
nestedAnalyzer.addVariable(ownedVariable, initResult);
BasicDependencyPaths inResult = nestedAnalyzer.analyze(letExp.getOwnedIn());
if (inResult == null) {
return null;
}
BasicDependencyPaths result = addHidden(inResult, initResult);
return result;
}
@Override
public @NonNull BasicDependencyPaths visitLiteralExp(@NonNull LiteralExp literalExp) {
return createDependencyPaths(literalExp);
}
@Override
public @Nullable BasicDependencyPaths visitLoopExp(@NonNull LoopExp loopExp) {
BasicDependencyPaths sourcePaths = analyze(loopExp.getOwnedSource());
if (sourcePaths == null) {
return null;
}
DependencyAnalyzerVisitor nestedAnalyzer = new DependencyAnalyzerVisitor(this);
for (@SuppressWarnings("null")@NonNull Variable iterator : loopExp.getOwnedIterators()) {
nestedAnalyzer.addVariable(iterator, sourcePaths);
}
return addHidden(nestedAnalyzer.analyze(loopExp.getOwnedBody()), sourcePaths);
}
@Override
public @Nullable BasicDependencyPaths visitMapLiteralExp(@NonNull MapLiteralExp mapLiteralExp) {
BasicDependencyPaths result = emptyDependencyPaths;
for (MapLiteralPart ownedPart : mapLiteralExp.getOwnedParts()) {
result = addReturn(result, analyze(ownedPart));
}
return result;
}
@Override
public @Nullable BasicDependencyPaths visitMapLiteralPart(@NonNull MapLiteralPart mapLiteralPart) {
return analyze(mapLiteralPart.getOwnedValue());
}
@Override
public @Nullable BasicDependencyPaths visitNavigationCallExp(@NonNull NavigationCallExp navigationCallExp) {
BasicDependencyPaths sourcePaths = analyze(navigationCallExp.getOwnedSource());
if (sourcePaths == null) {
return null;
}
NavigationDependencyStep dependencyStep = createPropertyDependencyStep(navigationCallExp);
BasicDependencyPaths result = sourcePaths.append(dependencyStep);
return result.addHidden(sourcePaths);
}
@Override
public @Nullable BasicDependencyPaths visitNullLiteralExp(@NonNull NullLiteralExp object) {
return emptyDependencyPaths; // We do not need OclVoid dependencies
}
@Override
public @Nullable BasicDependencyPaths visitOperationCallExp(@NonNull OperationCallExp operationCallExp) {
Operation referredOperation = operationCallExp.getReferredOperation();
assert referredOperation != null;
//
// Form the resultPaths by retaining all hidden dependencies; the result of an operationCallExp
// includes all dependencies from the source and argument expressions.
//
List<@NonNull BasicDependencyPaths> resultPaths = getResultPaths(operationCallExp);
if (resultPaths == null) {
return null;
}
//
// Form the sourceAndArgumentPaths by stripping all hidden dependencies; only source and argument type dependencies are
// passed into the operation.
//
List<@NonNull BasicDependencyPaths> allSourceAndArgumentPaths = getAllSourceAndArgumentPaths(resultPaths);
if (CALL.isActive()) {
StringBuilder s = new StringBuilder();
for (@NonNull OperationDependencyPaths paths : allSourceAndArgumentPaths) {
s.append("\n\t=> " + paths.toString());
}
CALL.println(referredOperation + s.toString());
}
//
// Analyze each possible source type
//
BasicDependencyPaths result = emptyDependencyPaths;
StringBuilder s = ATTEMPT.isActive() || RESULT.isActive() ? new StringBuilder() : null;
if (s != null) {
Operation containingOperation = PivotUtil.getContainingOperation(operationCallExp);
if (containingOperation != null) {
s.append("\n\tin: " + containingOperation);
}
else {
Mapping containingMapping = QVTcoreUtil.getContainingMapping(operationCallExp);
if (containingMapping != null) {
s.append("\n\tin: " + containingMapping);
}
}
String indent = "\n\t";
for (DependencyAnalyzerVisitor visitor = this; visitor != null; visitor = visitor.parent) {
indent = indent + " ";
List<@NonNull VariableDeclaration> variables = new ArrayList<>(visitor.variable2dependencies.keySet());
Collections.sort(variables, NameUtil.NAMEABLE_COMPARATOR);
for (@NonNull VariableDeclaration variable : variables) {
s.append(indent);
s.append(variable.getName());
s.append(": ");
s.append(visitor.variable2dependencies.get(variable));
}
}
}
for (@NonNull List<@NonNull BasicDependencyPaths> aSourceAndArgumentPaths : getEachSourceAndArgumentPaths(allSourceAndArgumentPaths)) {
if (s != null) {
int i = 0;
for (@NonNull BasicDependencyPaths callPath : aSourceAndArgumentPaths) {
s.append("\n\t=> ");
s.append(i == 0 ?"self" : referredOperation.getOwnedParameters().get(i-1).getName());
s.append(": ");
s.append(callPath.toString());
i++;
}
}
//
// Analyze each possible override
//
BasicDependencyPaths aSourcePath = aSourceAndArgumentPaths.get(0);
Iterable<@NonNull List<@NonNull OperationDependencyStep>> aSourceReturnPaths = aSourcePath.getReturnPaths();
assert Iterables.size(aSourceReturnPaths) == 1;
List<@NonNull OperationDependencyStep> steps2 = aSourceReturnPaths.iterator().next();
int size = steps2.size();
assert size > 0;
OperationDependencyStep lastStep = steps2.get(size-1);
org.eclipse.ocl.pivot.Class sourceClass = lastStep.getElementalType();
CompleteClass selfClass = completeModel.getCompleteClass(sourceClass);
List<@NonNull Operation> sortedOverrides = Lists.newArrayList(getOverrides(selfClass, referredOperation));
Collections.sort(sortedOverrides, ToStringComparator.INSTANCE);
for (@NonNull Operation operation : sortedOverrides) {
OperationId operationId = operation.getOperationId();
Map<@NonNull List<@NonNull BasicDependencyPaths>, @NonNull OperationAnalysis> args2result = operation2paths2analysis.get(operationId);
if (args2result == null) {
args2result = new HashMap<>();
operation2paths2analysis.put(operationId, args2result);
}
OperationAnalysis operationAnalysis = args2result.get(aSourceAndArgumentPaths);
if (operationAnalysis == null) {
if (referredOperation.getBodyExpression() != null) {
operationAnalysis = new OperationAnalysis(OperationDependencyAnalysis.this, operation, aSourceAndArgumentPaths, null);
addPendingAnalysis(operationAnalysis); // Queue for evaluation later
result = null;
}
else {
BasicDependencyPaths localResult;
if (PivotUtil.isSameOperation(operationId, standardLibraryHelper.getOclElementOclContainerId())) {
localResult = analyzeOperationCallExp_oclContainer(operationCallExp, aSourceAndArgumentPaths);
}
// if (PivotUtil.isSameOperation(operationId, scheduler.getCollectionSelectByKindId())) {
// result = analyzeOperationCallExp_collectionSelectByKind(operationCallExp, argumentPaths);
// }
// else if (PivotUtil.isSameOperation(operationId, scheduler.getOclAnyOclIsKindOfId())) {
// result = analyzeOperationCallExp_oclIsKindOf(sourceNode, operationCallExp);
// }
else {
localResult = createDependencyPaths(operationCallExp);
}
operationAnalysis = new OperationAnalysis(OperationDependencyAnalysis.this, operation, aSourceAndArgumentPaths, localResult);
result = addReturn(result, localResult);
}
for (@NonNull BasicDependencyPaths callPath : aSourceAndArgumentPaths) {
assert Iterables.isEmpty(callPath.getHiddenPaths());
}
args2result.put(aSourceAndArgumentPaths, operationAnalysis);
}
BasicDependencyPaths operationResult = operationAnalysis.getResult(analysis);
if (s != null) {
s.append("\n\tto: " + operation);
s.append("\n\t\t<= " + operationResult);
}
if (operationResult != null) {
result = addReturn(result, operationResult);
}
else if (exactResultRequired()) {
result = null;
}
else {
Type type = operation.getType();
assert type != null;
ClassDependencyStep classDependencyStep = createClassDependencyStep(type, operationCallExp);
BasicDependencyPaths dependencyPaths = createDependencyPaths(classDependencyStep);
result = addReturn(result, dependencyPaths);
}
}
}
if (result != null) {
for (@NonNull BasicDependencyPaths argumentPath : resultPaths) {
result = addHidden(result, argumentPath);
}
}
if (s != null) {
s.append("\n\t" + (exactResultRequired() ? "«exact» " : "«partial» ") + result);
(result != null ? RESULT : ATTEMPT).println(operationCallExp + s.toString());
}
return result;
}
@Override
public @Nullable BasicDependencyPaths visitOppositePropertyAssignment(@NonNull OppositePropertyAssignment propertyAssignment) {
return visiting(propertyAssignment); // No OppositePropertyAssignment in Operation
}
@Override
public @Nullable BasicDependencyPaths visitPropertyAssignment(@NonNull PropertyAssignment propertyAssignment) {
return visiting(propertyAssignment); // No PropertyAssignment in Operation
}
@Override
public @Nullable BasicDependencyPaths visitShadowExp(@NonNull ShadowExp shadowExp) {
BasicDependencyPaths result = createDependencyPaths(shadowExp);
for (ShadowPart ownedPart : shadowExp.getOwnedParts()) {
result = addReturn(result, analyze(ownedPart));
}
return result;
}
@Override
public @Nullable BasicDependencyPaths visitShadowPart(@NonNull ShadowPart shadowPart) {
return analyze(shadowPart.getOwnedInit());
}
@Override
public @Nullable BasicDependencyPaths visitTupleLiteralExp(@NonNull TupleLiteralExp tupleLiteralExp) {
BasicDependencyPaths result = emptyDependencyPaths;
for (TupleLiteralPart ownedPart : tupleLiteralExp.getOwnedParts()) {
result = addReturn(result, analyze(ownedPart));
}
return result;
}
@Override
public @Nullable BasicDependencyPaths visitTupleLiteralPart(@NonNull TupleLiteralPart tupleLiteralPart) {
return analyze(tupleLiteralPart.getOwnedInit());
}
@Override
public @Nullable BasicDependencyPaths visitTypeExp(@NonNull TypeExp typeExp) {
return emptyDependencyPaths;
}
@Override
public @Nullable BasicDependencyPaths visitVariableExp(@NonNull VariableExp variableExp) {
VariableDeclaration referredVariable = variableExp.getReferredVariable();
assert referredVariable != null;
return getVariable(referredVariable);
}
}
public static abstract class AbstractOperationAnalysis
{
protected final @NonNull OperationDependencyAnalysis operationDependencyAnalysis;
/**
* Nested operation invocations whose analysis returned a future result.
*/
protected @Nullable Collection<@NonNull OperationAnalysis> invokedFutureAnalyses = null;
/**
* Invoking analyses that use the future result of this analysis.
*/
protected @Nullable Set<@NonNull AbstractOperationAnalysis> invokingFutureAnalyses = null;
/**
* The definitive analyzed execution result once all nested invocations have been fully analyzed.
*/
protected /*@LazyNonNull*/ BasicDependencyPaths result = null;
protected AbstractOperationAnalysis(@NonNull OperationDependencyAnalysis operationDependencyAnalysis) {
this.operationDependencyAnalysis = operationDependencyAnalysis;
}
protected void addInvokedFutureAnalysis(@NonNull OperationAnalysis invokedAnalysis) {
Collection<@NonNull OperationAnalysis> invokedFutureAnalyses2 = invokedFutureAnalyses;
if (invokedFutureAnalyses2 == null) {
invokedFutureAnalyses = invokedFutureAnalyses2 = new HashSet<>();
}
invokedFutureAnalyses2.add(invokedAnalysis);
}
protected void addInvokingFutureAnalysis(@NonNull AbstractOperationAnalysis invokingAnalysis) {
Set<@NonNull AbstractOperationAnalysis> invokingFutureAnalyses2 = invokingFutureAnalyses;
if (invokingFutureAnalyses2 == null) {
invokingFutureAnalyses = invokingFutureAnalyses2 = new HashSet<>();
}
invokingFutureAnalyses2.add(invokingAnalysis);
}
public abstract void analyze(boolean exactResult);
public @Nullable BasicDependencyPaths basicGetResult() {
return result;
}
public void check() {
Collection<@NonNull OperationAnalysis> invokedFutureAnalyses2 = invokedFutureAnalyses;
Collection<@NonNull AbstractOperationAnalysis> invokingFutureAnalyses2 = invokingFutureAnalyses;
if (result != null) { // KNOWN
assert (invokedFutureAnalyses2 == null) || invokedFutureAnalyses2.isEmpty() : "Analysis with result should not invoke futures:" + this;
assert (invokingFutureAnalyses2 == null) || invokingFutureAnalyses2.isEmpty() : "Analysis with result should not be invoked as a future: " + this;
assert !operationDependencyAnalysis.pending.contains(this) : "Analysis with result should not be pending: " + this;
assert !operationDependencyAnalysis.refining.contains(this) : "Analysis with result should not be refining: " + this;
}
else { // PENDING / REFINING
// assert (invokingFutureAnalyses2 != null) && !invokingFutureAnalyses2.isEmpty() : "Analysis with future result should be invoking a future: " + this;
if (operationDependencyAnalysis.refining.contains(this)) { // REFINING
assert !operationDependencyAnalysis.pending.contains(this) : "Analysis with refining result should not be pending: " + this;
// assert (invokingFutureAnalyses2 != null) && !invokingFutureAnalyses2.isEmpty() : "Analysis with future result should be invoking a future: " + this;
}
else {
assert operationDependencyAnalysis.pending.contains(this) : "Analysis with pending result should be pending: " + this;
assert !operationDependencyAnalysis.refining.contains(this) : "Analysis with pending result should not be refining: " + this;
}
}
}
protected void removeInvokedAnalysis(@NonNull OperationAnalysis operationAnalysis) {
assert invokedFutureAnalyses != null;
boolean wasRemoved = invokedFutureAnalyses.remove(operationAnalysis);
assert wasRemoved;
}
protected void removeInvokingAnalysis(@NonNull AbstractOperationAnalysis operationAnalysis) {
assert invokingFutureAnalyses != null;
boolean wasRemoved = invokingFutureAnalyses.remove(operationAnalysis);
assert wasRemoved;
}
protected void resetInvokedFutureAnalyses() {
Collection<@NonNull OperationAnalysis> invokedFutureAnalyses2 = invokedFutureAnalyses;
if (invokedFutureAnalyses2 != null) {
for (@NonNull OperationAnalysis invokedFutureAnalysis : invokedFutureAnalyses2) {
invokedFutureAnalysis.removeInvokingAnalysis(this);
}
invokedFutureAnalyses2.clear();
}
}
}
protected static class RootOperationAnalysis extends AbstractOperationAnalysis
{
protected final @NonNull OperationCallExp operationCallExp;
protected final @NonNull DependencyAnalyzerVisitor visitor;
public RootOperationAnalysis(@NonNull OperationDependencyAnalysis operationDependencyAnalysis, @NonNull OperationCallExp operationCallExp) {
super(operationDependencyAnalysis);
this.operationCallExp = operationCallExp;
this.visitor = operationDependencyAnalysis.createDependencyAnalyzerVisitor(this, true);
}
@Override
public void analyze(boolean exactResult) {
visitor.analyze(operationCallExp);
}
// @Override
// public @NonNull DependencyPaths getResult(@NonNull AbstractOperationAnalysis invokingAnalysis) {
// return ClassUtil.nonNullState(result);
// }
/* public void assignUnknownResult() {
DependencyPaths dependencyPaths = sourceAndArgumentPaths.get(0);
Iterable<@NonNull List<@NonNull DependencyStep>> returnPaths = dependencyPaths.getReturnPaths();
DependencyStep step = returnPaths.iterator().next().get(0);
result = createDependencyPaths("result", new UnknownDependencyStep(step.getUsage(), step.getElementalType(), step.getElement()));
if (invokingFutureAnalyses != null) {
for (OperationAnalysis invokingAnalysis : new ArrayList<OperationAnalysis>(invokingFutureAnalyses)) {
invokingFutureAnalyses.remove(invokingAnalysis);
invokingAnalysis.removeFailedAnalysis(this);
}
// invokingFutureAnalyses.clear();
}
if (invokedFutureAnalyses != null) {
for (@NonNull OperationAnalysis failedAnalysis : new ArrayList<@NonNull OperationAnalysis>(invokedFutureAnalyses)) {
invokedFutureAnalyses.remove(failedAnalysis);
failedAnalysis.removeInvokingAnalysis(this);
}
// invokedFutureAnalyses.clear();
}
unblock(this);
checkAll();
} */
}
/**
* An OperationAnalysis captures the dependencies of an operation invocation with a particular combination of source and argument dependencies.
*
* An Analysis has four states:
* - UNKNOWN - no OperationAnalysis has been created
* - PENDING - an OperationAnalysis has been created and is ready for (re-)analysis.
* - REFINING - an OperationAnalysis has been created and analyzed and awaits progress wrt an invoked analysis before re-analyzing
* - KNOWN - an OperationAnalysis has been created and successfully analyzed to give a definitive result.
*
* Transitions:
* - UNKNOWN->PENDING construction as a result of an OperationCallExp analysis
* - PENDING->KNOWN successful analysis
* - PENDING->REFINING unsuccessful analysis
* - REFINING->PENDING successful analysis of an invoked operation
*
* State identification
* - UNKNOWN - no object
* - KNOWN - result<>null
* - PENDING - result=null, analyzer.pending->includes(self)
* - REFINING - result=null, analyzer.refining->includes(self)
*
* State invariants
* - UNKNOWN - no object
* - KNOWN - invokedFutureAnalyses->isEmpty(), invokingFutureAnalyses->isEmpty(),
* not analyzer.pending->includes(self), not analyzer.refining->includes(self)
* - PENDING invokingFutureAnalyses->notEmpty(), not analyzer.refining->includes(self)
* - REFINING invokingFutureAnalyses->notEmpty(), not analyzer.pending->includes(self)
*/
protected static class OperationAnalysis extends AbstractOperationAnalysis
{
/**
* The analyzed operation.
*/
private final @NonNull Operation operation;
/**
* The source and arguments for th analyzed execution.
*/
private final @NonNull List<@NonNull BasicDependencyPaths> sourceAndArgumentPaths;
public OperationAnalysis(@NonNull OperationDependencyAnalysis operationDependencyAnalysis, @NonNull Operation operation, @NonNull List<@NonNull BasicDependencyPaths> sourceAndArgumentPaths, @Nullable BasicDependencyPaths result) {
super(operationDependencyAnalysis);
this.operation = operation;
this.sourceAndArgumentPaths = sourceAndArgumentPaths;
this.result = result;
CREATE.println(toString());
}
@Override
public void analyze(boolean exactResult) {
assert result == null;
if (START.isActive()) {
StringBuilder s = new StringBuilder();
toDebug(s);
START.println(s.toString());
}
resetInvokedFutureAnalyses();
DependencyAnalyzerVisitor visitor = operationDependencyAnalysis.createDependencyAnalyzerVisitor(this, exactResult);
LanguageExpression bodyExpression;
List<? extends VariableDeclaration> ownedParameters;
OCLExpression ownedBody;
if (operation instanceof Function) {
Function function = (Function)operation;
Transformation transformation = QVTbaseUtil.getContainingTransformation(function);
Variable thisVariable = QVTbaseUtil.getContextVariable(operationDependencyAnalysis.metamodelManager.getStandardLibrary(), transformation);
visitor.addVariable(thisVariable, ClassUtil.nonNullState(sourceAndArgumentPaths.get(0)));
ownedParameters = function.getOwnedParameters();
ownedBody = function.getQueryExpression();
}
else {
bodyExpression = ClassUtil.nonNullState(operation.getBodyExpression());
ExpressionInOCL specification;
try {
specification = operationDependencyAnalysis.environmentFactory.parseSpecification(bodyExpression);
} catch (ParserException e) {
throw new IllegalStateException(e);
}
visitor.addVariable(ClassUtil.nonNullState(specification.getOwnedContext()), ClassUtil.nonNullState(sourceAndArgumentPaths.get(0)));
ownedParameters = specification.getOwnedParameters();
ownedBody = specification.getOwnedBody();
}
int iMax = Math.min(ownedParameters.size(), sourceAndArgumentPaths.size()-1);
for (int i = 0; i < iMax; i++) {
visitor.addVariable(ClassUtil.nonNullState(ownedParameters.get(i)), ClassUtil.nonNullState(sourceAndArgumentPaths.get(i+1)));
}
BasicDependencyPaths analysisResult = visitor.analyze(ownedBody);
Collection<@NonNull OperationAnalysis> invokedFutureAnalyses2 = invokedFutureAnalyses;
if (analysisResult != null) {
if (exactResult) {
assert (invokedFutureAnalyses2 == null) || (invokedFutureAnalyses2.size() == 0);
}
else {
resetInvokedFutureAnalyses();
operationDependencyAnalysis.removeRefiningAnalysis(this);
}
//
// If this analysis has a result, everyone that invokes this analysis needs waking up.
//
Collection<@NonNull AbstractOperationAnalysis> invokingFutureAnalyses2 = invokingFutureAnalyses;
if (invokingFutureAnalyses2 != null) {
List<@NonNull AbstractOperationAnalysis> sortedInvokingFutureAnalyses = new ArrayList<>(invokingFutureAnalyses2);
Collections.sort(sortedInvokingFutureAnalyses, ToStringComparator.INSTANCE);
for (@NonNull AbstractOperationAnalysis invokingFutureAnalysis : sortedInvokingFutureAnalyses) {
operationDependencyAnalysis.addPendingAnalysis(invokingFutureAnalysis);
invokingFutureAnalysis.removeInvokedAnalysis(this);
}
invokingFutureAnalyses2.clear();
}
result = analysisResult;
}
else {
//
// If this analysis has no result, this analysis goes into the refining queue.
//
assert (invokedFutureAnalyses2 != null) && (invokedFutureAnalyses2.size() > 0);
if (!operationDependencyAnalysis.pending.contains(this)) {
operationDependencyAnalysis.addRefiningAnalysis(this);
}
}
// }
if ((result != null) && RESULT.isActive()) {
StringBuilder s = new StringBuilder();
toDebug(s);
RESULT.println(s.toString());
}
}
public @Nullable BasicDependencyPaths getResult(@NonNull AbstractOperationAnalysis invokingAnalysis) {
if (result != null) {
return result;
}
this.addInvokingFutureAnalysis(invokingAnalysis);
invokingAnalysis.addInvokedFutureAnalysis(this);
return null;
}
private void toDebug(@NonNull StringBuilder s) {
s.append(operation);
int i = 0;
for (@NonNull OperationDependencyPaths sourceAndArgumentPath : sourceAndArgumentPaths) {
s.append("\n\t=> ");
s.append((i == 0 ? "self" : operation.getOwnedParameters().get(i-1).getName()));
s.append(": " + sourceAndArgumentPath);
i++;
}
s.append("\n\t<= " + result);
}
@Override
public @NonNull String toString() {
return operation.toString() + "; " + result + " <= " + sourceAndArgumentPaths;
}
}
private final @NonNull EnvironmentFactoryInternalExtension environmentFactory;
private final @NonNull MetamodelManager metamodelManager;
private final @NonNull CompleteModel completeModel;
protected final @NonNull StandardLibraryHelper standardLibraryHelper;
protected final @NonNull RootDomainUsageAnalysis domainUsageAnalysis;
// protected final @NonNull ScheduleModel scheduler;
private final @NonNull Map<@NonNull List<@Nullable Object>, @NonNull BasicDependencyPaths> content2path = new HashMap<>();
private final @NonNull BasicDependencyPaths emptyDependencyPaths = createDependencyPaths(null, null);
private final @NonNull Map<@NonNull OperationId, @NonNull Map<@NonNull List<@NonNull BasicDependencyPaths>, @NonNull OperationAnalysis>> operation2paths2analysis = new HashMap<>();
private final @NonNull Map<org.eclipse.qvtd.pivot.qvtschedule.utilities.DomainUsage, @NonNull DependencyStepFactory> usage2factory = new HashMap<>();
private final @NonNull ContainmentAnalysis containmentAnalysis;
private final @NonNull FinalAnalysis finalAnalysis;
protected final @NonNull CompleteClass oclVoidCompleteClass;
protected final @NonNull CompleteClass oclInvalidCompleteClass;
/**
* OperationAnalysis instances for which a (re-)analysis is pending.
*/
private final @NonNull Deque<@NonNull AbstractOperationAnalysis> pending = new LinkedList<>();
/**
* OperationAnalysis instances for which a (re-)analysis awaits progress by one of its invoked analyses.
*/
private final @NonNull Set<@NonNull OperationAnalysis> refining = new HashSet<>();
public OperationDependencyAnalysis(@NonNull ContainmentAnalysis containmentAnalysis, @NonNull RootDomainUsageAnalysis domainUsageAnalysis) {
this.environmentFactory = (EnvironmentFactoryInternalExtension) containmentAnalysis.getEnvironmentFactory();
this.metamodelManager = environmentFactory.getMetamodelManager();
StandardLibrary standardLibrary = environmentFactory.getStandardLibrary();
this.standardLibraryHelper = new StandardLibraryHelper(standardLibrary);
this.domainUsageAnalysis = domainUsageAnalysis;
this.containmentAnalysis = containmentAnalysis;
// this.scheduler = scheduler;
this.finalAnalysis = ((PivotMetamodelManager)metamodelManager).getFinalAnalysis(); //new FinalAnalysis((CompleteModelInternal) environmentFactory.getCompleteModel());
this.completeModel = environmentFactory.getCompleteModel();
this.oclVoidCompleteClass = completeModel.getCompleteClass(standardLibrary.getOclVoidType());
this.oclInvalidCompleteClass = completeModel.getCompleteClass(standardLibrary.getOclInvalidType());
}
protected void addPendingAnalysis(@NonNull AbstractOperationAnalysis operationAnalysis) {
PENDING.println(operationAnalysis.toString()); // + "\n from " + failedAnalysis);
assert operationAnalysis.basicGetResult() == null;
refining.remove(operationAnalysis);
if (!pending.contains(operationAnalysis)) {
pending.add(operationAnalysis);
}
}
protected void addRefiningAnalysis(@NonNull OperationAnalysis operationAnalysis) {
REFINING.println(operationAnalysis.toString()); // + "\n from " + failedAnalysis);
assert !pending.contains(operationAnalysis);
assert operationAnalysis.basicGetResult() == null;
refining.add(operationAnalysis);
}
public @NonNull OperationDependencyPaths analyze(@NonNull OperationCallExp operationCallExp) {
RootOperationAnalysis rootAnalysis = new RootOperationAnalysis(this, operationCallExp);
return analyze(rootAnalysis.visitor, rootAnalysis.operationCallExp);
}
private @NonNull OperationDependencyPaths analyze(@NonNull DependencyAnalyzerVisitor visitor, @NonNull Element element) {
@SuppressWarnings("unused")
OperationDependencyPaths result = visitor.analyze(element);
while ((pending.size() > 0) || (refining.size() > 0)) {
while (pending.size() > 0) {
checkAll();
AbstractOperationAnalysis analysis = pending.remove();
analysis.analyze(true);
}
if (refining.size() > 0) {
checkAll();
OperationAnalysis analysis = removeRefining();
analysis.analyze(false);
}
}
OperationDependencyPaths result2 = visitor.analyze(element);
return ClassUtil.nonNullState(result2);
}
public @NonNull OperationDependencyPaths analyzeOperation(@NonNull OperationCallExp operationCallExp) {
RootOperationAnalysis rootAnalysis = new RootOperationAnalysis(this, operationCallExp);
analyze(rootAnalysis.visitor, operationCallExp);
return rootAnalysis.visitor.analyzeOperation(operationCallExp);
}
private void checkAll() {
for (@NonNull Map<@NonNull List<@NonNull BasicDependencyPaths>, @NonNull OperationAnalysis> paths2analysis : operation2paths2analysis.values()) {
for (@NonNull List<@NonNull BasicDependencyPaths> paths : paths2analysis.keySet()) {
for (@NonNull OperationDependencyPaths path : paths) {
assert Iterables.isEmpty(path.getHiddenPaths());
for (@NonNull List<@NonNull OperationDependencyStep> steps : path.getReturnPaths()) {
for (@NonNull OperationDependencyStep step : steps) {
assert step instanceof ClassDependencyStep;
}
}
}
}
for (@NonNull OperationAnalysis operationAnalysis : paths2analysis.values()) {
operationAnalysis.check();
}
}
}
protected @NonNull ClassDependencyStep createClassDependencyStep(@NonNull Type type, @NonNull Element element) {
while (type instanceof CollectionType) {
type = ClassUtil.nonNullState((org.eclipse.ocl.pivot.Class) ((CollectionType)type).getElementType());
}
DomainUsage usage1 = domainUsageAnalysis.basicGetUsage(type);
DomainUsage usage = usage1 != null ? usage1 : getUsage(element);
DependencyStepFactory factory = getDependencyStepFactory(usage);
return factory.createClassDependencyStep((org.eclipse.ocl.pivot.Class)type, element);
}
protected @NonNull DependencyAnalyzerVisitor createDependencyAnalyzerVisitor(@NonNull AbstractOperationAnalysis operationAnalysis, boolean exactResult) {
return new DependencyAnalyzerVisitor(operationAnalysis, exactResult);
}
public @NonNull BasicDependencyPaths createDependencyPaths(@NonNull TypedElement typedElement) {
org.eclipse.ocl.pivot.Class type = ClassUtil.nonNullState((org.eclipse.ocl.pivot.Class)typedElement.getType());
return createDependencyPaths(createClassDependencyStep(type, typedElement));
}
protected @NonNull BasicDependencyPaths createDependencyPaths(@NonNull OperationDependencyStep returnStep) {
Set<@NonNull List<@NonNull OperationDependencyStep>> returnPaths = new HashSet<>();
returnPaths.add(Collections.singletonList(returnStep));
return createDependencyPaths(returnPaths, null);
}
protected @NonNull BasicDependencyPaths createDependencyPaths(@Nullable Set<@NonNull List<@NonNull OperationDependencyStep>> returnPaths, @Nullable Set<@NonNull List<@NonNull OperationDependencyStep>> hiddenPaths) {
List<@Nullable Object> content = new ArrayList<>();
content.add(returnPaths);
content.add(hiddenPaths);
BasicDependencyPaths path = content2path.get(content);
if (path == null) {
path = new BasicDependencyPaths(this, returnPaths, hiddenPaths);
content2path.put(content, path);
}
return path;
}
public @NonNull BasicDependencyPaths createParameterDependencyPaths(@NonNull VariableDeclaration parameter) {
return createDependencyPaths(createParameterDependencyStep(parameter));
}
protected @NonNull ParameterDependencyStep createParameterDependencyStep(@NonNull VariableDeclaration parameter) {
Type type = parameter.getType();
while (type instanceof CollectionType) {
type = ClassUtil.nonNullState(((CollectionType)type).getElementType());
}
assert type != null;
DomainUsage usage1 = domainUsageAnalysis.basicGetUsage(type);
DomainUsage usage = usage1 != null ? usage1 : getUsage(parameter);
DependencyStepFactory factory = getDependencyStepFactory(usage);
return factory.createParameterDependencyStep((org.eclipse.ocl.pivot.Class)type, parameter);
}
protected @NonNull NavigationDependencyStep createPropertyDependencyStep(@NonNull NavigationCallExp navigationCallExp) {
DomainUsage usage = getUsage(navigationCallExp);
DependencyStepFactory factory = getDependencyStepFactory(usage);
return factory.createPropertyDependencyStep(navigationCallExp);
}
protected @NonNull NavigationDependencyStep createPropertyDependencyStep(@NonNull Property containmentProperty, @NonNull OperationCallExp oclContainerCallExp) {
DomainUsage usage = getUsage(containmentProperty);
DependencyStepFactory factory = getDependencyStepFactory(usage);
return factory.createPropertyDependencyStep(containmentProperty, oclContainerCallExp);
}
public void dump() {
List<@NonNull OperationId> operationIds = new ArrayList<>(operation2paths2analysis.keySet());
Collections.sort(operationIds, new Comparator<@NonNull OperationId>(){
@Override
public int compare(@NonNull OperationId o1, @NonNull OperationId o2) {
return o1.getName().compareTo(o2.getName());
}});
for (@NonNull OperationId operationId : operationIds) {
System.out.println(operationId);
Map<@NonNull List<@NonNull BasicDependencyPaths>, @NonNull OperationAnalysis> map = operation2paths2analysis.get(operationId);
assert map != null;
for (@NonNull List<@NonNull BasicDependencyPaths> paths : map.keySet()) {
for (@NonNull BasicDependencyPaths path : paths) {
System.out.println("\t" + path);
}
System.out.println("\t=>" + map.get(paths));
}
}
}
protected @NonNull DependencyStepFactory getDependencyStepFactory(@NonNull DomainUsage usage) {
DependencyStepFactory factory = usage2factory.get(usage);
if (factory == null) {
factory = new DependencyStepFactory(usage);
usage2factory.put(usage, factory);
}
return factory;
}
private @NonNull Set<@NonNull AbstractOperationAnalysis> getInvokers(@NonNull AbstractOperationAnalysis analysis, @NonNull Set<@NonNull AbstractOperationAnalysis> invokers) {
if (invokers.add(analysis) && (analysis.invokingFutureAnalyses != null)) {
for (@NonNull AbstractOperationAnalysis invokingAnalysis : analysis.invokingFutureAnalyses) {
getInvokers(invokingAnalysis, invokers);
}
}
return invokers;
}
public @NonNull StandardLibrary getStandardLibrary() {
return standardLibraryHelper.getStandardLibrary();
}
protected @NonNull DomainUsage getUsage(@NonNull Element element) {
DomainUsage usage = domainUsageAnalysis.getUsage(element);
assert usage != null;
return usage;
}
protected void removeRefiningAnalysis(@NonNull OperationAnalysis operationAnalysis) {
// REFINING.println(operationAnalysis.toString()); // + "\n from " + failedAnalysis);
// assert !pending.contains(operationAnalysis);
// assert operationAnalysis.basicGetResult() == null;
refining.remove(operationAnalysis);
}
private @NonNull OperationAnalysis removeRefining() {
final @NonNull Map<@NonNull OperationAnalysis, @NonNull Set<@NonNull AbstractOperationAnalysis>> analysis2invokers = new HashMap<>();
Map<@NonNull String, @NonNull OperationAnalysis> key2analysis = new HashMap<>();
for (@NonNull OperationAnalysis analysis : refining) {
key2analysis.put(analysis.toString(), analysis);
analysis2invokers.put(analysis, getInvokers(analysis, new HashSet<>()));
analysis.check();
}
List<@NonNull OperationAnalysis> sortedBlockedAnalyses = new ArrayList<>(refining);
Collections.sort(sortedBlockedAnalyses, new Comparator<@NonNull AbstractOperationAnalysis>()
{
@Override
public int compare(@NonNull AbstractOperationAnalysis o1, @NonNull AbstractOperationAnalysis o2) {
Set<@NonNull AbstractOperationAnalysis> s1 = analysis2invokers.get(o1);
Set<@NonNull AbstractOperationAnalysis> s2 = analysis2invokers.get(o2);
assert (s1 != null) && (s2 != null);
int i1 = s1.size();
int i2 = s2.size();
int diff = i1 - i2;
if (diff != 0) {
return diff;
}
return ClassUtil.safeCompareTo(o1.toString(), o2.toString());
}});
OperationAnalysis mostBlocked = null;
mostBlocked = sortedBlockedAnalyses.get(sortedBlockedAnalyses.size()-1);
// System.out.println("Most blocked " + mostBlockedCount + " : " + mostBlocked);
assert mostBlocked != null;
refining.remove(mostBlocked);
HYPOTHECATING.println(mostBlocked.toString()); // + "\n from " + failedAnalysis);
return mostBlocked;
}
}