blob: e19cb7b8b5e26e8e87cf055a1838eedd5aca6f69 [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.Collections;
import java.util.Comparator;
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.qvtd.pivot.qvtschedule.Edge;
import org.eclipse.qvtd.pivot.qvtschedule.MappingRegion;
import org.eclipse.qvtd.pivot.qvtschedule.NavigationEdge;
import org.eclipse.qvtd.pivot.qvtschedule.Node;
import org.eclipse.qvtd.pivot.qvtschedule.utilities.QVTscheduleUtil;
import com.google.common.collect.Iterables;
/**
* HeadAnalysis is a helper class to compute the head nodes of a region.
*/
public abstract class HeadAnalysis
{
/**
* HeadComparator supports sorting a list of Head candidates into a most suitable first order.
*/
protected static class HeadComparator implements Comparator<@NonNull Node>
{
private final @NonNull Map<@NonNull Node, @NonNull Set<@NonNull Node>> targetFromSourceClosure;
private final @Nullable List<@NonNull Node> preferredHeadNodes;
private @Nullable Map<@NonNull Node, @NonNull Integer> node2implicity = null;
public HeadComparator(@NonNull Map<@NonNull Node, @NonNull Set<@NonNull Node>> targetFromSourceClosure, @Nullable List<@NonNull Node> preferredHeadNodes) {
this.targetFromSourceClosure = targetFromSourceClosure;
this.preferredHeadNodes = preferredHeadNodes;
}
@Override
public int compare(@NonNull Node o1, @NonNull Node o2) {
//
// DataTypes last
//
boolean d1 = o1.isDataType();
boolean d2 = o2.isDataType();
if (d1 != d2) {
return d1 ? 1 : -1;
}
//
// Preferred heads in preference order.
//
List<@NonNull Node> preferredHeadNodes2 = preferredHeadNodes;
if (preferredHeadNodes2 != null) {
int i1 = preferredHeadNodes2.indexOf(o1);
int i2 = preferredHeadNodes2.indexOf(o2);
if (i1 != i2) {
if (i1 < 0) {
return 1;
}
else if (i2 < 0) {
return -1;
}
else {
return i1 - i2;
}
}
}
//
// Explicit head first
//
// if (o1.isHead() != o2.isHead()) {
// return o1.isHead() ? -1 : 1;
// }
//
// Speculated first
//
if (o1.isSpeculated() != o2.isSpeculated()) {
return o1.isSpeculated() ? -1 : 1;
}
//
// Constant next - never happens
//
if (o1.isConstant() != o2.isConstant()) {
return o1.isConstant() ? -1 : 1;
}
//
// Least reachable next
//
Set<@NonNull Node> set1 = targetFromSourceClosure.get(o1);
Set<@NonNull Node> set2 = targetFromSourceClosure.get(o2);
assert (set1 != null) && (set2 != null);
int l1 = set1.size();
int l2 = set2.size();
int diff = l1 - l2;
if (diff != 0) {
return diff;
}
//
// Loaded heads seem more attractive than predicated, since we can just dispatch on an input object collection. If the scheduler ensures
// that the predicated input is actually ready then we just incur a probably inefficuent implicit opposite navigation.
//
// But conversely, predicated heads ensure that we use what we are waiting for anyway and then navigate more efficiently.
//
// The clinching argument demonstrated by testQVTcCompiler_Forward2Reverse is that the predicated candidate might
// be part of a nasty cyclic dependency and so need speculation. Heads cannot be speculated. So we must prefer a loaded head
// at least until we can anticipate speculation more accurately.
//
// Loaded next
//
if (o1.isLoaded() != o2.isLoaded()) {
return o1.isLoaded() ? -1 : 1;
}
//
// Predicated next
//
if (o1.isPredicated() != o2.isPredicated()) {
return o1.isPredicated() ? -1 : 1;
}
//
// Least implicit next (prefers middle to output)
//
int i1 = getImplicity(o1);
int i2 = getImplicity(o2);
diff = i1 - i2;
if (diff != 0) {
return diff;
}
//
// Alphabetical last
//
String n1 = o1.getName();
String n2 = o2.getName();
return n1.compareTo(n2);
}
/**
* Return the number of outgoing implicit connection from node. A middle model node
* has no implicit connections and so is a better candodate for a head than an output
* model node which has implicit connections to the trace.
*/
private int getImplicity(@NonNull Node node) {
Map<@NonNull Node, @NonNull Integer> node2implicity2 = node2implicity;
if (node2implicity2 == null) {
node2implicity = node2implicity2 = new HashMap<>();
}
Integer implicity = node2implicity2.get(node);
if (implicity == null) {
implicity = 0;
for (@NonNull Edge e : QVTscheduleUtil.getOutgoingEdges(node)) {
if (e.isNavigation() && QVTscheduleUtil.getReferredProperty((NavigationEdge)e).isIsImplicit()) {
implicity++;
}
}
node2implicity2.put(node, implicity);
}
return implicity;
}
}
// protected final @NonNull Partition partition;
protected final @NonNull MappingRegion mappingRegion;
protected HeadAnalysis(@NonNull MappingRegion mappingRegion) {
this.mappingRegion = mappingRegion;;
}
protected @NonNull List<@NonNull HeadNodeGroup> computeHeadNodeGroups(@NonNull Map<@NonNull Node, @NonNull Set<@NonNull Node>> targetFromSourcesClosure,
@NonNull Map<@NonNull Node, @NonNull Set<@NonNull Node>> sourceToTargetsClosure,
@Nullable List<@NonNull Node> preferredHeadNodes) {
//
// Sort the guardNodes into least reachable first order enabling the heads to be identified
// by successive removal from the start of the list.
//
List<@NonNull Node> headLessNodes = new ArrayList<>();
Iterables.addAll(headLessNodes, targetFromSourcesClosure.keySet());
Collections.sort(headLessNodes, new HeadComparator(targetFromSourcesClosure, preferredHeadNodes));
//
// Loop to identify the least reachable residual node and then remove all nodes reachable from this new head.
// Removed nodes from which the new head is reachable are accumulated as a mutually reachable head group.
//
List<@NonNull HeadNodeGroup> headNodeGroups = new ArrayList<>();
Set<@NonNull Node> reachableNodes = new HashSet<>();
while (!headLessNodes.isEmpty()) {
Node headNode = headLessNodes.remove(0);
assert headNode != null;
List<@NonNull Node> headNodeGroup = new ArrayList<>();
headNodeGroup.add(headNode);
@SuppressWarnings("unused") Set<@NonNull Node> debugSourceClosure = targetFromSourcesClosure.get(headNode);
Set<@NonNull Node> targetsClosure = sourceToTargetsClosure.get(headNode);
assert targetsClosure != null;
for (@NonNull Node targetNode : targetsClosure) {
if (reachableNodes.add(targetNode)) {
headLessNodes.remove(targetNode);
if (targetNode != headNode) {
Set<@NonNull Node> otherTargetsClosure = sourceToTargetsClosure.get(targetNode);
assert otherTargetsClosure != null;
if (otherTargetsClosure.contains(headNode)) {
headNodeGroup.add(targetNode);
}
}
}
}
headNodeGroups.add(createHeadNodeGroup(headNodeGroup));
}
//
// Prune any computationally derived heads
//
if (headNodeGroups.size() > 1) { // FIXME do we really need to do this for unidirectional transformations??
for (int iGroup = headNodeGroups.size()-1; iGroup >= 0; --iGroup) { // Reverse index to allow removal
HeadNodeGroup headNodeGroup = headNodeGroups.get(iGroup);
for (@NonNull HeadNodeGroup otherHeadNodeGroup : headNodeGroups) {
if (otherHeadNodeGroup != headNodeGroup) {
if (headNodeGroup.isDeriveableFrom(otherHeadNodeGroup)) {
headNodeGroups.remove(iGroup);
break;
}
}
}
}
}
return headNodeGroups;
}
protected abstract @NonNull HeadNodeGroup createHeadNodeGroup(@NonNull List<@NonNull Node> headNodeGroup);
/*
* Pick the first element of each headNodeGroup as a headNode.
*/
protected @NonNull List<@NonNull Node> selectHeadNodes(@NonNull List<@NonNull HeadNodeGroup> headNodeGroups,
@Nullable Iterable<@NonNull Node> preferredHeadNodes) {
List<@NonNull Node> headNodes = new ArrayList<>();
for (@NonNull HeadNodeGroup headNodeGroup : headNodeGroups) {
Node headNode = headNodeGroup.getPreferredHeadNode(preferredHeadNodes);
assert !headNodes.contains(headNode);
headNodes.add(headNode);
}
return headNodes;
}
@Override
public String toString() {
return mappingRegion.toString();
}
}