blob: e89c7e4ec51797e981cc47f5d1bd1e8bd20f2b83 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016, 2017 Willink Transformations 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:
* E.D.Willink - Initial API and implementation
*******************************************************************************/
package org.eclipse.qvtd.compiler.internal.qvts2qvts.merger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.ocl.pivot.CompleteClass;
import org.eclipse.ocl.pivot.Property;
import org.eclipse.ocl.pivot.utilities.ClassUtil;
import org.eclipse.qvtd.compiler.internal.qvtm2qvts.RegionUtil;
import org.eclipse.qvtd.pivot.qvtschedule.BasicMappingRegion;
import org.eclipse.qvtd.pivot.qvtschedule.Edge;
import org.eclipse.qvtd.pivot.qvtschedule.MappingRegion;
import org.eclipse.qvtd.pivot.qvtschedule.NavigableEdge;
import org.eclipse.qvtd.pivot.qvtschedule.Node;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
class Correlator
{
public static @Nullable Correlator correlate(@NonNull MappingRegion secondaryRegion, @NonNull MappingRegion primaryRegion, @NonNull CorrelationStrategy strategy, @Nullable Map<@NonNull Node, @NonNull Node> primaryNode2secondaryNode) {
if (secondaryRegion instanceof BasicMappingRegion) {
if (((BasicMappingRegion)secondaryRegion).getReferredMapping().isIsAbstract()) {
return null;
}
}
if (primaryRegion instanceof BasicMappingRegion) {
if (((BasicMappingRegion)primaryRegion).getReferredMapping().isIsAbstract()) {
return null;
}
}
Correlator correlator = new Correlator(primaryRegion, secondaryRegion, strategy, primaryNode2secondaryNode);
return correlator.correlate() ? correlator : null;
}
private static interface CorrelationStrategy
{
boolean navigableEdgesMatch(@NonNull NavigableEdge secondaryEdge, @Nullable NavigableEdge primaryEdge);
// boolean navigableIntermediateNodesMatch(@NonNull Node secondaryNode, @NonNull Node primaryNode);
// boolean navigableNodeMaybeUnmatched(@NonNull Node secondaryNode);
boolean navigableNodesMatch(@NonNull Node secondaryNode, @Nullable Node primaryNode);
}
static abstract class AbstractCorrelationStrategy implements CorrelationStrategy
{
protected final boolean debugFailures = AbstractMerger.FAILURE.isActive();
@Override
public boolean navigableEdgesMatch(@NonNull NavigableEdge secondaryEdge, @Nullable NavigableEdge primaryEdge) {
return true;
}
@Override
public boolean navigableNodesMatch(@NonNull Node secondaryNode, @Nullable Node primaryNode) {
if (primaryNode == null) {
if (secondaryNode.isPredicated()) {
if (debugFailures) {
AbstractMerger.FAILURE.println("Missing predicated match for : " + secondaryNode);
}
return false;
}
return true;
}
else {
assert secondaryNode.isExplicitNull() == primaryNode.isExplicitNull();
return true;
}
}
// @Override
// public boolean navigableIntermediateNodesMatch(@NonNull Node secondaryNode, @NonNull Node primaryNode) {
// if (secondaryNode.isExplicitNull() != primaryNode.isExplicitNull()) {
// return false;
// }
// return true;
// }
}
protected final @NonNull MappingRegion primaryRegion;
protected final @NonNull MappingRegion secondaryRegion;
protected final @NonNull CorrelationStrategy strategy;
protected final @NonNull Map<@NonNull Node, @NonNull Node> secondaryNode2primaryNode = new HashMap<>();
protected final @NonNull Map<@NonNull CompleteClass, @NonNull List<@NonNull Node>> completeClass2primaryNodes;
protected final boolean debugFailures = AbstractMerger.FAILURE.isActive();
protected Correlator(@NonNull MappingRegion primaryRegion, @NonNull MappingRegion secondaryRegion, @NonNull CorrelationStrategy strategy, @Nullable Map<@NonNull Node, @NonNull Node> primaryNode2secondaryNode) {
this.primaryRegion = primaryRegion;
this.secondaryRegion = secondaryRegion;
this.strategy = strategy;
this.completeClass2primaryNodes = RegionUtil.getCompleteClass2Nodes(primaryRegion);
if (primaryNode2secondaryNode != null) {
for (Map.Entry<@NonNull Node, @NonNull Node> entry : primaryNode2secondaryNode.entrySet()) {
secondaryNode2primaryNode.put(entry.getValue(), entry.getKey());
}
}
}
protected boolean correlate() {
//
// Find the unambiguous head-node matches
//
if (!correlateHeadNodes()) {
return false;
}
//
// Accumulate the transitive navigation from the head nodes. All common navigation edges must have compatible node types.
//
if (!correlateNavigablePredicates()) { // FIXME this may be more efficient but it's a pig to debug, rewrite as intersection then no-extra-predicates
return false;
}
Set<@NonNull Node> navigableNodes = secondaryNode2primaryNode.keySet();
//
// Accumulate the transitive computation to the true nodes. All common computation edges must have compatible node types.
//
if (!correlateComputedPredicates()) {
return false;
}
//
// Accumulate the remaining node pairings from computations enriching the navigable nodes.
//
correlateResidualComputations(navigableNodes);
return true;
}
/**
* Return true if the tree of computation nodes for firstNode and secondNode are equivalent
* assigning equivalences to first2second.
*/
protected boolean correlateComputation(@NonNull Node firstNode, @NonNull Node secondNode, @NonNull Map<@NonNull Node, @NonNull Node> first2second) {
Node node = first2second.get(firstNode);
if (node != null) {
return node == secondNode;
}
if (firstNode.getNodeRole() != secondNode.getNodeRole()) {
return false;
}
if (!ClassUtil.safeEquals(firstNode.getName(), secondNode.getName())) { // FIXME stronger e.g. referredOperation
return false;
}
Map<@NonNull Node, @NonNull Node> nestedFirst2second = new HashMap<>(first2second);
nestedFirst2second.put(firstNode, secondNode);
List<@NonNull Edge> residualSecondArgumentEdges = Lists.newArrayList(secondNode.getArgumentEdges());
for (@NonNull Edge firstEdge : firstNode.getArgumentEdges()) {
boolean gotIt = false;
for (@NonNull Edge secondEdge : residualSecondArgumentEdges) {
if (ClassUtil.safeEquals(firstEdge.getName(), secondEdge.getName())) {
if (!correlateComputation(firstEdge.getEdgeSource(), secondEdge.getEdgeSource(), nestedFirst2second)) {
return false;
}
gotIt = true;
residualSecondArgumentEdges.remove(secondEdge);
break;
}
}
if (!gotIt) {
return false;
}
}
first2second.putAll(nestedFirst2second);
return true;
}
/**
* Return true if all the computed (TrueNode) predicates exactly match between primaryRegion and secondRegion,
* updating secondaryNode2primaryNode accordingly.
*/
protected boolean correlateComputedPredicates() {
Iterable<@NonNull Node> primaryTrueNodes = primaryRegion.getTrueNodes();
Iterable<@NonNull Node> secondaryTrueNodes = secondaryRegion.getTrueNodes();
int primaryTrueSize = Iterables.size(primaryTrueNodes);
if (primaryTrueSize != Iterables.size(secondaryTrueNodes)) {
return false;
}
if (primaryTrueSize == 0) {
return true;
}
Map<@NonNull Node, @NonNull Node> primary2secondary = new HashMap<>();
if (primaryTrueSize == 1) {
Node primaryTrueNode = primaryTrueNodes.iterator().next();
Node secondaryTrueNode = secondaryTrueNodes.iterator().next();
if (!correlateComputation(primaryTrueNode, secondaryTrueNode, primary2secondary)) {
return false;
}
}
else {
Set<@NonNull Node> residualSecondaryTrueNodes = Sets.newHashSet(secondaryTrueNodes);
for (@NonNull Node primaryTrueNode : primaryTrueNodes) {
boolean gotIt = false;
for (@NonNull Node secondaryTrueNode : residualSecondaryTrueNodes) {
Map<@NonNull Node, @NonNull Node> primary2secondary2 = new HashMap<>();
if (correlateComputation(primaryTrueNode, secondaryTrueNode, primary2secondary2)) { // FIXME use hashes
gotIt = true;
primary2secondary.putAll(primary2secondary2);
residualSecondaryTrueNodes.remove(secondaryTrueNode);
break;
}
}
if (!gotIt) {
return false;
}
}
}
for (@NonNull Node primaryNode : primary2secondary.keySet()) {
Node equivalentNode = primary2secondary.get(primaryNode);
assert equivalentNode != null;
secondaryNode2primaryNode.put(equivalentNode, primaryNode);
}
return true;
}
protected boolean correlateHeadNodes() {
List<Node> secondaryHeadNodes = secondaryRegion.getHeadNodes();
if (secondaryHeadNodes.size() != 1) { // FIXME Surely consistent multiple heads are ok?
if (debugFailures) {
AbstractMerger.FAILURE.println("More than 1 secondary head nodes: " + secondaryHeadNodes.size());
}
return false;
}
Node secondaryHeadNode = secondaryHeadNodes.get(0);
CompleteClass completeClass = secondaryHeadNode.getCompleteClass();
List<@NonNull Node> primaryNodes = completeClass2primaryNodes.get(completeClass);
if ((primaryNodes == null) || (primaryNodes.size() == 0)) {
if (debugFailures) {
AbstractMerger.FAILURE.println("No primary nodes of type: " + completeClass);
}
return false;
}
Node primaryHeadNode = secondaryNode2primaryNode.get(secondaryHeadNode);
if (primaryHeadNode == null) {
primaryHeadNode = selectMergedHeadNode(secondaryHeadNode, primaryNodes);
if (primaryHeadNode == null) {
primaryHeadNode = selectMergedHeadNode(secondaryHeadNode, primaryNodes); // FIXME debugging
if (debugFailures) {
AbstractMerger.FAILURE.println("No primary head node to match: " + secondaryHeadNode);
}
return false;
}
secondaryNode2primaryNode.put(secondaryHeadNode, primaryHeadNode);
}
if (primaryNodes.size() > 1) {
for (@NonNull Node primaryNode : primaryNodes) {
if ((primaryNode != primaryHeadNode) && !primaryNode.isLoaded()) {
if (debugFailures) { // FIXME multiple matching not-speculated might be ok
AbstractMerger.FAILURE.println("Multiple primary nodes of type: " + completeClass);
}
return false;
}
}
}
return true;
}
protected boolean correlateNavigablePredicates() {
//
// Loop over a growing worklist of secondary nodes.
//
Set<@NonNull Node> secondaryNodes = new HashSet<>(secondaryNode2primaryNode.keySet());
List<@NonNull Node> secondaryNodesWorkList = new ArrayList<>(secondaryNodes);
for (int i = 0; i < secondaryNodesWorkList.size(); i++) {
@NonNull Node secondarySourceNode = secondaryNodesWorkList.get(i);
@Nullable Node primarySourceNode = secondaryNode2primaryNode.get(secondarySourceNode);
if (!strategy.navigableNodesMatch(secondarySourceNode, primarySourceNode)) {
return false;
}
for (@NonNull NavigableEdge uncastSecondaryEdge : secondarySourceNode.getNavigationEdges()) {
Node uncastSecondaryTargetNode = uncastSecondaryEdge.getEdgeTarget();
Iterable<@NonNull Node> secondaryTargetNodes = RegionUtil.getCastTargets(uncastSecondaryTargetNode, true);
if (primarySourceNode != null) {
NavigableEdge uncastPrimaryEdge = primarySourceNode.getNavigationEdge(RegionUtil.getProperty(uncastSecondaryEdge)); // Skip isSecondary properties
if (!strategy.navigableEdgesMatch(uncastSecondaryEdge, uncastPrimaryEdge)) {
return false;
}
if (uncastPrimaryEdge != null) {
Node uncastPrimaryTargetNode = uncastPrimaryEdge.getEdgeTarget();
if (uncastSecondaryTargetNode.isExplicitNull() != uncastPrimaryTargetNode.isExplicitNull()) {
if (debugFailures) {
AbstractMerger.FAILURE.println("Inconsistent ExplicitNull: " + uncastSecondaryTargetNode);
}
return false;
}
Map<@NonNull CompleteClass, @NonNull Node> completeClass2primaryTargetNodes = new HashMap<>();
for (@NonNull Node primaryTargetNode : RegionUtil.getCastTargets(uncastPrimaryTargetNode, true)) {
CompleteClass targetCompleteClass = primaryTargetNode.getCompleteClass();
Node oldNode = completeClass2primaryTargetNodes.put(targetCompleteClass, primaryTargetNode);
if (oldNode != null) {
if (debugFailures) {
AbstractMerger.FAILURE.println("Inconsistent paths to: " + targetCompleteClass);
}
return false; // FIXME should have an earlier cast rationalizer
}
}
for (@NonNull Node secondaryTargetNode : secondaryTargetNodes) {
CompleteClass targetCompleteClass = secondaryTargetNode.getCompleteClass();
Node primaryTargetNode = completeClass2primaryTargetNodes.remove(targetCompleteClass);
if (primaryTargetNode == null) {
if (debugFailures) {
AbstractMerger.FAILURE.println("Inconsistent types at: " + primaryTargetNode + ", " + secondaryTargetNode);
}
return false; // FIXME Inconsistent navigation is too complex
}
else {
Node primaryTargetNode2 = secondaryNode2primaryNode.get(secondaryTargetNode);
if (primaryTargetNode2 == null) {
secondaryNode2primaryNode.put(secondaryTargetNode, primaryTargetNode);
}
else if (primaryTargetNode != primaryTargetNode2) {
if (debugFailures) {
AbstractMerger.FAILURE.println("Inconsistent paths to: " + primaryTargetNode + ", " + primaryTargetNode2);
}
return false; // FIXME Inconsistent navigation is too complex
}
}
}
}
}
for (@NonNull Node secondaryTargetNode : secondaryTargetNodes) {
if (secondaryNodes.add(secondaryTargetNode)) {
secondaryNodesWorkList.add(secondaryTargetNode);
}
}
}
}
return true;
}
protected void correlateResidualComputations(@NonNull Iterable<@NonNull Node> secondaryNodes) {
for (@NonNull Node secondaryNode : secondaryNodes) {
Node primaryNode = secondaryNode2primaryNode.get(secondaryNode);
assert primaryNode != null;
Map<@NonNull Node, @NonNull Node> secondary2primary2 = new HashMap<>();
if (correlateComputation(secondaryNode, primaryNode, secondary2primary2)) { // FIXME use hashes
secondaryNode2primaryNode.putAll(secondary2primary2);
}
}
}
public @NonNull Map<@NonNull Node, @NonNull Node> getNode2Node() {
return secondaryNode2primaryNode;
}
/**
* Chose the headNode from a group of peer nodes that has the most non-implicit properties targeting its peers.
*
protected @NonNull Node selectBestHeadNode(@NonNull List<@NonNull Node> headNodes) {
int size = headNodes.size();
assert size >= 1;
if (size == 1) {
return headNodes.get(0);
}
Node bestHeadNode = null;
int bestNonImplicits = -1;
List<@NonNull Node> sortedHeadNodes = new ArrayList<>(headNodes);
Collections.sort(sortedHeadNodes, NameUtil.NAMEABLE_COMPARATOR); // Stabilize order
for (@NonNull Node thisHeadNode : sortedHeadNodes) {
int nonImplicits = 0;
for (@NonNull Node thatHeadNode : sortedHeadNodes) {
for (@NonNull NavigableEdge edge : thisHeadNode.getNavigationEdges()) {
if (edge.getTarget() == thatHeadNode) {
Property property = edge.getProperty();
if (!property.isIsImplicit()) {
nonImplicits++;
break;
}
}
}
}
if (nonImplicits > bestNonImplicits) {
bestHeadNode = thisHeadNode;
bestNonImplicits = nonImplicits;
}
}
assert bestHeadNode != null;
return bestHeadNode;
} */
protected @Nullable Node selectMergedHeadNode(@NonNull Node headNode, @NonNull List<@NonNull Node> mergedNodes) {
if (mergedNodes.size() == 1) {
Node mergedNode = mergedNodes.get(0);//selectBestHeadNode(mergedNodes);
// if (mergedNode.isIterator()) {
// return null;
// }
return mergedNode;
}
if (mergedNodes.size() == 0) {
return null;
}
Iterable<NavigableEdge> predicateEdges = headNode.getPredicateEdges();
for (@NonNull Node mergedNode : mergedNodes) {
boolean ok = !mergedNode.isIterator();
if (ok) {
for (@NonNull NavigableEdge predicateEdge : predicateEdges) {
Property property = RegionUtil.getProperty(predicateEdge);
Node navigation = mergedNode.getNavigationTarget(property);
if (navigation == null) {
ok = false;
break;
}
}
}
if (ok) { // FIXME stronger checking
return mergedNode;
}
}
return null;
}
}