| /******************************************************************************* |
| * 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; |
| } |
| } |