blob: f9db05bed9326bc9a06e4e47a16700405e31ad24 [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.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.utilities.TracingOption;
import org.eclipse.qvtd.compiler.CompilerConstants;
import org.eclipse.qvtd.compiler.internal.utilities.CompilerUtil;
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.Sets;
/**
* RuleHeadAnalysis is a helper class to compute the head nodes of a region.
*/
public class RuleHeadAnalysis extends HeadAnalysis
{
public static final @NonNull TracingOption RULE_HEAD_NODE_GROUPS = new TracingOption(CompilerConstants.PLUGIN_ID, "qvts2qvts/partition/headNodeGroups");
/**
* Return the minimal head(s) of a partitioned MappingRegion from which all old nodes in the region may be reached by to-one navigation.
*
* The head/not-head nodes are installed by invoking Node.setHead(true/false) as appropriate.
*
* FIXME is this an unduely complicated way of computing the traceNode ??
*/
public static @NonNull Iterable<@NonNull Node> computeRealizedHeadNodes(@NonNull MappingRegion mappingRegion, @NonNull List<@NonNull Node> realizedMiddleNodes) {
RuleHeadAnalysis mappingRegionAnalysis = new RuleHeadAnalysis(mappingRegion);
Map<@NonNull Node, @NonNull Set<@NonNull Node>> targetFromSourceClosure = mappingRegionAnalysis.computeNewTargetFromSources(realizedMiddleNodes);
Iterable<@NonNull Node> headNodes = mappingRegionAnalysis.computeHeadNodes(targetFromSourceClosure, null);
return headNodes;
}
/**
* Return the minimal head(s) of a MappingRegion from which all old nodes in the region may be reached by to-one navigation.
*
* This uni-directional analysis is performed after the trace nodes are synthesized and so can exploit to-one traces to
* locate output nodes wrt input nodes.
*
* The head/not-head nodes are installed by invoking Node.setHead(true/false) as appropriate.
*/
public static @NonNull Iterable<@NonNull Node> computeRuleHeadNodes(@NonNull ScheduleManager scheduleManager, @NonNull MappingRegion mappingRegion, @Nullable List<@NonNull Node> preferredHeadNodes) {
RuleHeadAnalysis mappingRegionAnalysis = new RuleHeadAnalysis(mappingRegion);
Map<@NonNull Node, @NonNull Set<@NonNull Node>> targetFromSources = mappingRegionAnalysis.computeOldTargetFromSources();
Iterable<@NonNull Node> headNodes = mappingRegionAnalysis.computeHeadNodes(targetFromSources, preferredHeadNodes);
mappingRegionAnalysis.checkHeadNodeConsistency(headNodes);
return headNodes;
}
protected RuleHeadAnalysis(@NonNull MappingRegion mappingRegion) {
super(mappingRegion);
}
private void addExtraHeads(@NonNull List<@NonNull Node> headNodes) {
for (@NonNull Node node : QVTscheduleUtil.getOwnedNodes(mappingRegion)) {
// assert !node.isDependency();
if (node.isDependency()) {
node.setHead();
assert !headNodes.contains(node);
headNodes.add(node);
}
}
}
private void checkHeadNodeConsistency(@NonNull Iterable<@NonNull Node> headNodes) {
Set<@NonNull Node> debugHeadNodes = new HashSet<>();
for (@NonNull Node node : QVTscheduleUtil.getOwnedNodes(mappingRegion)) {
if (node.isDependency() || node.isHead()) {
debugHeadNodes.add(node);
}
}
assert debugHeadNodes.equals(Sets.newHashSet(headNodes));
}
private @NonNull Iterable<@NonNull Node> computeHeadNodes(@NonNull Map<@NonNull Node, @NonNull Set<@NonNull Node>> targetFromSources, @Nullable List<@NonNull Node> preferredHeadNodes) {
//
// Ripple the local target-from-sources to compute the overall target-from-sources closure.
//
Map<@NonNull Node, @NonNull Set<@NonNull Node>> targetFromSourcesClosure = CompilerUtil.computeClosure(targetFromSources);
//
// Compute the inverse Set of all source nodes from which a particular target node can be reached by transitive to-one navigation.
//
Map<@NonNull Node, @NonNull Set<@NonNull Node>> sourceToTargetsClosure = CompilerUtil.computeInverseClosure(targetFromSourcesClosure);
List<@NonNull HeadNodeGroup> headNodeGroups = computeHeadNodeGroups(targetFromSourcesClosure, sourceToTargetsClosure, preferredHeadNodes);
if (RULE_HEAD_NODE_GROUPS.isActive()) {
StringBuilder s = new StringBuilder();
s.append(mappingRegion.getName());
for (@NonNull HeadNodeGroup headNodeGroup : headNodeGroups) {
s.append("\n\t");
headNodeGroup.appendTo(s);
}
RULE_HEAD_NODE_GROUPS.println(s.toString());
}
List<@NonNull Node> headNodes = selectHeadNodes(headNodeGroups, preferredHeadNodes);
//
// Add the pseudo-heads for true and dependency nodes.
//
addExtraHeads(headNodes);
//
// Set/reset the head flag on each node.
//
setHeadNodes(targetFromSourcesClosure.keySet(), headNodes);
return headNodes;
}
//
// Compute the Set of all source nodes from which each target can be reached by transitive to-one new navigation.
//
private @NonNull Map<@NonNull Node, @NonNull Set<@NonNull Node>> computeNewTargetFromSources(@NonNull Iterable<@NonNull Node> realizedMiddleNodes) {
Map<@NonNull Node, @NonNull Set<@NonNull Node>> targetFromSources = new HashMap<>();
for (@NonNull Node targetNode : realizedMiddleNodes) {
Set<@NonNull Node> sources = Sets.newHashSet(targetNode);
targetFromSources.put(targetNode, sources);
for (@NonNull Edge edge : QVTscheduleUtil.getIncomingEdges(targetNode)) {
if (edge.isRealized() && edge.isNavigation()) {
NavigationEdge navigationEdge = (NavigationEdge)edge;
Node sourceNode = navigationEdge.getEdgeSource();
sources.add(sourceNode);
}
}
}
return targetFromSources;
}
//
// Compute the Set of all source nodes from which each target can be reached by a direct to-one old navigation.
//
private @NonNull Map<@NonNull Node, @NonNull Set<@NonNull Node>> computeOldTargetFromSources() {
Map<@NonNull Node, @NonNull Set<@NonNull Node>> targetFromSources = new HashMap<>();
for (@NonNull Node targetNode : QVTscheduleUtil.getOwnedNodes(mappingRegion)) {
if (targetNode.isMatched() && !targetNode.isConstant() && !targetNode.isOperation()) { // Operations are traversed by derived head group analysis
if (targetNode.isLoaded() || targetNode.isChecked()) {
Set<@NonNull Node> sources = Sets.newHashSet(targetNode);
targetFromSources.put(targetNode, sources);
for (@NonNull Edge edge : QVTscheduleUtil.getIncomingEdges(targetNode)) {
if (edge instanceof NavigationEdge) {
if (!edge.isRealized() && !((NavigationEdge)edge).isPartial()) {
Node sourceNode = edge.getEdgeSource();
if (sourceNode.isMatched() && !sourceNode.isConstant() && !sourceNode.isOperation()) {
if (sourceNode.isLoaded() || sourceNode.isChecked()) {
sources.add(sourceNode);
}
}
}
}
}
}
}
}
return targetFromSources;
}
@Override
protected @NonNull HeadNodeGroup createHeadNodeGroup(@NonNull List<@NonNull Node> headNodeGroup) {
return new HeadNodeGroup(headNodeGroup)
{
@Override
protected boolean canBeSameGroup(@NonNull Node sourceNode, @NonNull Edge source2targetEdge) {
boolean isOldSource = sourceNode.isOld();
return isOldSource ? source2targetEdge.isOld() : source2targetEdge.isNew();
}
@Override
public String getName() {
return headNodeGroup.get(0).getName();
}
};
}
private void setHeadNodes(@NonNull Set<@NonNull Node> reachableNodes, @NonNull List<@NonNull Node> headNodes) {
for (@NonNull Node node : reachableNodes) {
if (headNodes.contains(node)) {
node.setHead();
}
else {
node.resetHead();
}
}
}
}