blob: 67486f568d2d38879740882d3d370591c5b1b4df [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016 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.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
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.CollectionType;
import org.eclipse.ocl.pivot.CompleteClass;
import org.eclipse.ocl.pivot.Property;
import org.eclipse.ocl.pivot.Type;
import org.eclipse.ocl.pivot.TypedElement;
import org.eclipse.ocl.pivot.utilities.ClassUtil;
import org.eclipse.ocl.pivot.utilities.NameUtil;
import org.eclipse.qvtd.compiler.internal.qvtp2qvts.Edge;
import org.eclipse.qvtd.compiler.internal.qvtp2qvts.MappingRegion;
import org.eclipse.qvtd.compiler.internal.qvtp2qvts.NavigableEdge;
import org.eclipse.qvtd.compiler.internal.qvtp2qvts.Node;
import org.eclipse.qvtd.compiler.internal.qvtp2qvts.QVTp2QVTs;
import org.eclipse.qvtd.compiler.internal.qvtp2qvts.Region;
import org.eclipse.qvtd.compiler.internal.qvtp2qvts.RegionUtil;
import org.eclipse.qvtd.compiler.internal.qvtp2qvts.analysis.ClassDatumAnalysis;
import org.eclipse.qvtd.compiler.internal.qvts2qvts.Region2Depth;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
/**
* EarlyMerger replaces one list of MappingRegions by another in which each set of regions that can be merged
* without knowledge of the schedule is replaced by an equivalent merged region.
*/
public class EarlyMerger
{
/**
* Replace those inputRegions that may be merged by merged regions.
*
* inputRegions should be naturally ordered to ensure that non-recursive dependencies are inherently satisfied.
*
* Returns the inputRegions after replacement of merges.
*/
public static @NonNull List<@NonNull MappingRegion> merge(@NonNull Iterable<@NonNull MappingRegion> inputRegions) {
EarlyMerger earlyMerger = new EarlyMerger(inputRegions);
return earlyMerger.merge();
}
protected final @NonNull LinkedHashSet<@NonNull MappingRegion> residualInputRegions;
protected final @NonNull List<@NonNull MappingRegion> outputRegions = new ArrayList<>();
protected final @NonNull Region2Depth region2depths = new Region2Depth();
protected EarlyMerger(@NonNull Iterable<@NonNull MappingRegion> inputRegions) {
this.residualInputRegions = Sets.newLinkedHashSet(inputRegions);
}
protected @Nullable Map<@NonNull Node, @NonNull Node> canMerge(@NonNull Region primaryRegion, @NonNull Region secondaryRegion, @NonNull Region2Depth region2depths, boolean isLateMerge) {
Map<@NonNull Node, @NonNull Node> secondaryNode2primaryNode = null;
Map<@NonNull CompleteClass, @NonNull List<@NonNull Node>> completeClass2nodes = RegionUtil.getCompleteClass2Nodes(primaryRegion);
//
// Find the unambiguous head-node matches
//
List<@NonNull Node> secondaryHeadNodes = secondaryRegion.getHeadNodes();
if (secondaryHeadNodes.size() != 1) {
return null;
}
Node secondaryHeadNode = secondaryHeadNodes.get(0);
CompleteClass completeClass = secondaryHeadNode.getCompleteClass();
List<@NonNull Node> primaryNodes = completeClass2nodes.get(completeClass);
if (primaryNodes != null) {
Node primaryHeadNode = selectMergedHeadNode(secondaryHeadNode, primaryNodes);
if (primaryHeadNode == null) {
return null;
}
secondaryNode2primaryNode = new HashMap<>();
secondaryNode2primaryNode.put(secondaryHeadNode, primaryHeadNode);
}
if (secondaryNode2primaryNode == null) {
return null;
}
//
// Validate the transitive navigation from the head nodes. All common navigation edges must have compatible node types.
//
if (!canMergeInternal(secondaryRegion, secondaryNode2primaryNode, region2depths, isLateMerge)) {
return null;
}
//FIXME Must be symmetrically mergeable; do tests before creating MergedRegion
//
// Validate the true node predicate compatibility
//
Iterable<@NonNull Node> primaryTrueNodes = primaryRegion.getTrueNodes();
Iterable<@NonNull Node> secondaryTrueNodes = secondaryRegion.getTrueNodes();
if (Iterables.size(primaryTrueNodes) != Iterables.size(secondaryTrueNodes)) {
return null;
}
for (@NonNull Node primaryTrueNode : primaryTrueNodes) {
boolean gotIt = false;
for (@NonNull Node secondaryTrueNode : secondaryTrueNodes) {
assert secondaryTrueNode != null;
Map<@NonNull Node, @NonNull Node> primary2secondary = new HashMap<>();
if (isEquivalent(primaryTrueNode, secondaryTrueNode, primary2secondary)) { // FIXME use hashes
gotIt = true;
for (@NonNull Node primaryNode : primary2secondary.keySet()) {
Node equivalentNode = primary2secondary.get(primaryNode);
assert equivalentNode != null;
secondaryNode2primaryNode.put(equivalentNode, primaryNode);
}
break;
}
}
if (!gotIt) {
return null;
}
}
return secondaryNode2primaryNode;
}
protected boolean canMergeInternal(@NonNull Region secondaryRegion, @NonNull Map<@NonNull Node, @NonNull Node> secondaryNode2primaryNode, @NonNull Region2Depth region2depths, boolean isLateMerge) {
Set<@NonNull Node> secondaryNodes = new HashSet<>(secondaryNode2primaryNode.keySet());
List<@NonNull Node> secondaryNodesList = new ArrayList<>(secondaryNodes);
for (int i = 0; i < secondaryNodesList.size(); i++) {
@NonNull Node secondarySourceNode = secondaryNodesList.get(i);
Node primarySourceNode = secondaryNode2primaryNode.get(secondarySourceNode);
if (primarySourceNode == null) {
if (secondarySourceNode.isPredicated()) {
return false;
}
}
for (@NonNull NavigableEdge secondaryEdge : secondarySourceNode.getNavigationEdges()) {
Node secondaryTargetNode = RegionUtil.getCastTarget(secondaryEdge.getTarget());
Node primaryTargetNode = null;
if (primarySourceNode != null) {
Edge primaryEdge = primarySourceNode.getNavigationEdge(secondaryEdge.getProperty());
if (primaryEdge != null) {
primaryTargetNode = RegionUtil.getCastTarget(primaryEdge.getTarget());
// primaryTargetNode = primaryTargetNode.getCastEquivalentNode();
// secondaryTargetNode = secondaryTargetNode.getCastEquivalentNode();
if (primaryTargetNode.getCompleteClass() != secondaryTargetNode.getCompleteClass()) { // FIXME conforms
return false;
}
if (primaryTargetNode.isExplicitNull() != secondaryTargetNode.isExplicitNull()) { // FIXME conforms
return false;
}
}
else {
if (secondaryEdge.isPredicated()) {
if (!isLateMerge) { // FIXME must locate in ancestry; if present can merge, if not cannot
return false;
}
for (@SuppressWarnings("unused") Node secondaryOriginNode : secondaryTargetNode.getUsedBindingSources()) {
return false;
}
}
}
}
if (primaryTargetNode != null) {
Node primaryTargetNode2 = secondaryNode2primaryNode.get(secondaryTargetNode);
if (primaryTargetNode2 == null) {
secondaryNode2primaryNode.put(secondaryTargetNode, primaryTargetNode);
}
}
assert secondaryTargetNode != null;
if (secondaryNodes.add(secondaryTargetNode)) {
// if (mergedTargetNode != null) {
// if (!secondaryTargetNode.isAttributeNode()) {
secondaryNodesList.add(secondaryTargetNode);
// }
}
}
if (!isLateMerge && (primarySourceNode != null)) {
for (@NonNull Edge secondaryEdge : secondarySourceNode.getComputationEdges()) {
Node secondaryTargetNode = secondaryEdge.getTarget();
Node primaryTargetNode = null;
for (@NonNull Edge primaryEdge : primarySourceNode.getComputationEdges()) {
if (ClassUtil.safeEquals(primaryEdge.getName(), secondaryEdge.getName())) {
primaryTargetNode = primaryEdge.getTarget();
if (isEquivalent(secondaryTargetNode, primaryTargetNode, secondaryNode2primaryNode)) {
secondaryNodesList.add(secondaryTargetNode);
}
}
}
}
}
/*
}
if (secondaryEdge instanceof NavigationEdge) {
Edge primaryEdge = primarySourceNode.getNavigationEdge(((NavigationEdge)secondaryEdge).getProperty());
if (primaryEdge != null) {
primaryTargetNode = primaryEdge.getTarget();
// primaryTargetNode = primaryTargetNode.getCastEquivalentNode();
// secondaryTargetNode = secondaryTargetNode.getCastEquivalentNode();
if (primaryTargetNode.getCompleteClass() != secondaryTargetNode.getCompleteClass()) { // FIXME conforms
return false;
}
}
else {
if (secondaryEdge.isPredicated()) {
if (!isLateMerge) { // FIXME must locate in ancestry; if present can merge, if not cannot
return false;
}
for (@SuppressWarnings("null")@NonNull Node secondaryOriginNode : secondaryTargetNode.getUsedBindingSources()) {
if (!region2depths.isAfter(secondaryOriginNode, this)) {
return false;
}
}
}
}
}
else {} // FIXME???
}
if (primaryTargetNode != null) {
Node primaryTargetNode2 = secondaryNode2primaryNode.get(secondaryTargetNode);
if (primaryTargetNode2 == null) {
secondaryNode2primaryNode.put(secondaryTargetNode, primaryTargetNode);
}
if (secondaryNodes.add(secondaryTargetNode)) {
// if (mergedTargetNode != null) {
if (!secondaryTargetNode.isAttributeNode()) {
secondaryNodesList.add(secondaryTargetNode);
}
}
}
} */
}
return true;
}
/**
* Return the nodes within region at which a suitably matching head of another region might be merged.
* The nodes must be bi-directionally one to one to respect 1:N trace node relationships.
*/
protected @NonNull Iterable<@NonNull Node> getHostNodes(@NonNull MappingRegion region) {
Set<@NonNull Node> hostNodes = new HashSet<>();
for (@NonNull Node node : region.getHeadNodes()) {
getHostNodesAccumulator(hostNodes, node);
}
return hostNodes;
}
protected void getHostNodesAccumulator(@NonNull Set<@NonNull Node> hostNodes, @NonNull Node node) {
// if (node.isNew()) {
// return;
// }
if (!node.isClass()) { // Simplify - this obviates many of the below
return;
}
if (node.isExplicitNull()) {
return;
}
if (node.isOperation()) {
return;
}
if (!node.isRequired()) {
return;
}
if (node.isTrue()) {
return;
}
if (!node.isPattern()) {
return;
}
if (!hostNodes.add(node)) {
return;
}
for (@NonNull NavigableEdge edge : node.getNavigationEdges()) {
if (/*!edge.isSecondary() &&*/ edge.isUnconditional()) { // ?? why isSecondary ?? why not isLoaded ??
Property property = edge.getProperty();
if (edge.isNew()) {
if (isToZeroOrOne(property) && isToZeroOrOne(property.getOpposite())) {
getHostNodesAccumulator(hostNodes, edge.getTarget());
}
}
else {
if (isToOne(property) && isToOne(property.getOpposite())) {
getHostNodesAccumulator(hostNodes, edge.getTarget());
}
}
}
}
}
/**
* Return true if the operation nodes for primaryNode and secondaryNode are equivalent
* assigning equivalences to equivalentNodes.
*/
protected boolean isEquivalent(@NonNull Node primaryNode, @NonNull Node secondaryNode, @NonNull Map<@NonNull Node, @NonNull Node> primary2secondary) {
Node node = primary2secondary.get(primaryNode);
if (node != null) {
return node == secondaryNode;
}
if (primaryNode.getNodeRole() != secondaryNode.getNodeRole()) {
return false;
}
if (!ClassUtil.safeEquals(primaryNode.getName(), secondaryNode.getName())) { // FIXME stronger e.g. referredOperation
return false;
}
HashMap<@NonNull Node, @NonNull Node> nestedPrimary2secondary = new HashMap<>(primary2secondary);
nestedPrimary2secondary.put(primaryNode, secondaryNode);
for (@NonNull Edge primaryEdge : primaryNode.getArgumentEdges()) {
boolean gotIt = false;
for (@NonNull Edge secondaryEdge : secondaryNode.getArgumentEdges()) {
if (ClassUtil.safeEquals(primaryEdge.getName(), secondaryEdge.getName())) {
if (!isEquivalent(primaryEdge.getSource(), secondaryEdge.getSource(), nestedPrimary2secondary)) {
return false;
}
gotIt = true;
break;
}
}
if (!gotIt) {
return false;
}
}
primary2secondary.putAll(nestedPrimary2secondary);
return true;
}
/**
* The primary region of a merge must be single-headed. It may be multiply-produced, e.g. recursed.
*
* FIXME Is there any need for this restriction.
*/
protected boolean isPrimaryCandidate(@NonNull Region mappingRegion) {
List<@NonNull Node> headNodes = mappingRegion.getHeadNodes();
return headNodes.size() == 1;
}
/**
* The secondary region of a merge must be single-headed and its head node must correspond to
* a non-null unconditional class within the primary region. It may be multiply-produced, e.g. recursed.
*/
protected boolean isSecondaryCandidate(@NonNull Region primaryRegion,
@NonNull Region secondaryRegion, @NonNull Set<@NonNull ClassDatumAnalysis> toOneReachableClasses) {
List<@NonNull Node> secondaryHeadNodes = secondaryRegion.getHeadNodes();
if (secondaryHeadNodes.size() != 1) {
return false;
}
Node classNode = secondaryHeadNodes.get(0);
ClassDatumAnalysis classDatumAnalysis = classNode.getClassDatumAnalysis();
return toOneReachableClasses.contains(classDatumAnalysis);
}
/**
* Return true if any primaryRegion head coincides with a secondaryRegion head.
*/
protected boolean isSharedHead(@NonNull Region primaryRegion, @NonNull Region secondaryRegion) {
for (@NonNull Node primaryHead : primaryRegion.getHeadNodes()) {
ClassDatumAnalysis primaryClassDatumAnalysis = primaryHead.getClassDatumAnalysis();
for (@NonNull Node secondaryHead : secondaryRegion.getHeadNodes()) {
if (primaryClassDatumAnalysis == secondaryHead.getClassDatumAnalysis()) {
return true;
}
}
}
return false;
}
private boolean isToOne(@Nullable TypedElement typedElement) {
if (typedElement == null) {
return false;
}
if (!typedElement.isIsRequired()) {
return false;
}
Type type = typedElement.getType();
if (type instanceof CollectionType) {
return false;
}
else {
return true;
}
}
private boolean isToZeroOrOne(@Nullable TypedElement typedElement) {
if (typedElement == null) {
return false;
}
Type type = typedElement.getType();
if (type instanceof CollectionType) {
return false;
}
else {
return true;
}
}
/**
* Replace those orderedRegions that may be aggregated as part of a GuardedRegion decision tree by GuardedRegions.
* orderedRegions should be naturally ordered to ensure that non-recursive dependencies are inherently satisfied.
*
* Returns the orderedRegions plus the new aggregates less those aggregated.
*/
protected @NonNull List<@NonNull MappingRegion> merge() {
while (!residualInputRegions.isEmpty()) { // Subject to nested removal
@NonNull MappingRegion candidateRegion = residualInputRegions.iterator().next();
MappingRegion mergedRegion = null;
if (isPrimaryCandidate(candidateRegion)) {
Iterable<@NonNull MappingRegion> secondaryRegions = selectSecondaryRegions(candidateRegion);
if (secondaryRegions != null) {
mergedRegion = merge(candidateRegion, secondaryRegions);
}
}
if (mergedRegion == null) {
outputRegions.add(candidateRegion);
}
else {
outputRegions.add(mergedRegion);
if (QVTp2QVTs.DEBUG_GRAPHS.isActive()) {
mergedRegion.writeDebugGraphs("2-merged");
}
}
residualInputRegions.remove(candidateRegion);
}
return outputRegions;
}
protected @Nullable MappingRegion merge(@NonNull MappingRegion candidateRegion, @NonNull Iterable<@NonNull MappingRegion> secondaryRegions) {
MappingRegion primaryRegion = candidateRegion;
MappingRegion mergedRegion = null;
for (@NonNull MappingRegion secondaryRegion : secondaryRegions) {
if (residualInputRegions.contains(secondaryRegion)) {
Map<@NonNull Node, @NonNull Node> secondaryNode2primaryNode = canMerge(primaryRegion, secondaryRegion, region2depths, false);
if (secondaryNode2primaryNode != null) {
boolean isSharedHead = isSharedHead(primaryRegion, secondaryRegion);
if (!isSharedHead || (canMerge(secondaryRegion, primaryRegion, region2depths, false) != null)) {
residualInputRegions.remove(mergedRegion);
residualInputRegions.remove(secondaryRegion);
RegionMerger regionMerger = new RegionMerger(primaryRegion);
regionMerger.addSecondaryRegion(secondaryRegion, secondaryNode2primaryNode);
regionMerger.prune();
mergedRegion = regionMerger.create();
regionMerger.check(mergedRegion);
region2depths.addRegion(mergedRegion);
primaryRegion = mergedRegion;
}
}
}
}
return mergedRegion;
}
/**
* 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 = selectBestHeadNode(mergedNodes);
if (mergedNode.isIterator()) {
return null;
}
return mergedNode;
}
if (mergedNodes.size() == 0) {
return null;
}
Iterable<NavigableEdge> predicateEdges = headNode.getPredicateEdges();
// if (predicateEdges == null) {
// return null;
// }
for (@NonNull Node mergedNode : mergedNodes) {
boolean ok = !mergedNode.isIterator();
if (ok) {
for (@NonNull NavigableEdge predicateEdge : predicateEdges) {
Property property = predicateEdge.getProperty();
Node navigation = mergedNode.getNavigationTarget(property);
if (navigation == null) {
ok = false;
break;
}
}
}
if (ok) { // FIXME stronger checking
return mergedNode;
}
}
return null;
}
/**
* Return a list of single-headed to-one navigable regions whose head is transitively to-one reachable from the primaryRegion's head.
*/
protected @Nullable Iterable<@NonNull MappingRegion> selectSecondaryRegions(@NonNull MappingRegion primaryRegion) {
//
// Find the classes that could be consumed by a secondary region head, and the number
// of possible consuming contexts.
//
Map<@NonNull ClassDatumAnalysis, @NonNull Integer> hostClass2count = new HashMap<>();
for (@NonNull Node hostNode : getHostNodes(primaryRegion)) {
ClassDatumAnalysis hostClassDatumAnalysis = hostNode.getClassDatumAnalysis();
Integer count = hostClass2count.get(hostClassDatumAnalysis);
hostClass2count.put(hostClassDatumAnalysis, count != null ? count+1 : 1);
}
//
// Find the secondary regions for single possibility host classes.
//
Set<@NonNull MappingRegion> secondaryRegions = new HashSet<>();
for (Map.Entry<@NonNull ClassDatumAnalysis, @NonNull Integer> entry : hostClass2count.entrySet()) {
if (entry.getValue() == 1) {
ClassDatumAnalysis primaryClassDatumAnalysis = entry.getKey();
for (@NonNull MappingRegion secondaryRegion : primaryClassDatumAnalysis.getConsumingRegions()) {
if (secondaryRegion != primaryRegion) {
for (@NonNull Node secondaryHeadNode : secondaryRegion.getHeadNodes()) {
if (secondaryHeadNode.getClassDatumAnalysis() == primaryClassDatumAnalysis) {
secondaryRegions.add(secondaryRegion);
break;
}
}
}
}
}
}
List<@NonNull MappingRegion> sortedSecondaryRegions = new ArrayList<>(secondaryRegions);
Collections.sort(sortedSecondaryRegions,NameUtil.NAMEABLE_COMPARATOR);
return sortedSecondaryRegions;
}
}