blob: 7893eb8ad6ba28ab9c9741e8186cb63bd797ac65 [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.Collections;
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.ocl.pivot.utilities.ClassUtil;
import org.eclipse.qvtd.compiler.internal.qvtm2qvts.RegionHelper;
import org.eclipse.qvtd.compiler.internal.qvtm2qvts.RegionUtil;
import org.eclipse.qvtd.pivot.qvtschedule.Edge;
import org.eclipse.qvtd.pivot.qvtschedule.MappingRegion;
import org.eclipse.qvtd.pivot.qvtschedule.Node;
import org.eclipse.qvtd.pivot.qvtschedule.Region;
import com.google.common.collect.Iterables;
/**
* RegionMerger provides the ability to merge one or more secondary regions into a primary region to
* create a new merged region.
*
* RegionMerger is used by classes such as EarlyMerger and LateMerger that orchestrate what may be merged safely.
*/
abstract class RegionMerger // implements Region
{
/**
* The primary original region contributing to the merged region.
*/
protected final @NonNull MappingRegion primaryRegion;
/**
* Additional secondary region contributing to the merged region.
*/
protected final @NonNull List<@NonNull MappingRegion> secondaryRegions = new ArrayList<>();
/**
* Nodes to be merged from a secondary region mapped to a corresponding primary region node.
*/
protected final @NonNull Map<@NonNull Node, @NonNull Node> secondaryNode2primaryNode = new HashMap<>();
/**
* Mapping from each primary and secondary region node to the corresponding merged node.
*/
protected final @NonNull Map<@NonNull Node, @NonNull NodeMerger> oldNode2nodeMerger = new HashMap<>();
/**
* Mapping from each primary and secondary region edge to the corresponding merged edge.
*/
private final @NonNull Map<@NonNull Edge, @NonNull EdgeMerger> oldEdge2edgeMerger = new HashMap<>();
/**
* The original edges that are not represented in the result.
*/
private /*@LazyNonNull*/ Set<@NonNull Edge> debugPrunedEdges = null;
protected RegionMerger(@NonNull MappingRegion primaryRegion) {
this.primaryRegion = primaryRegion;
// assert !(primaryRegion instanceof MicroMappingRegion);
//
for (@NonNull Node primaryNode : RegionUtil.getOwnedNodes(primaryRegion)) {
new NodeMerger(this, primaryNode);
}
//
for (@NonNull Edge primaryEdge : RegionUtil.getOwnedEdges(primaryRegion)) {
if (!primaryEdge.isSecondary()) {
new EdgeMerger(this, primaryEdge);
}
}
}
private void addPrunedEdge(@NonNull Edge prunedEdge) {
Set<@NonNull Edge> debugPrunedEdges2 = debugPrunedEdges;
if (debugPrunedEdges2 == null) {
debugPrunedEdges2 = debugPrunedEdges = new HashSet<>();
}
boolean wasAdded = debugPrunedEdges2.add(prunedEdge);
assert wasAdded;
}
protected void addSecondaryEdge(@NonNull Edge secondaryEdge) {
NodeMerger sourceNodeMerger = getNodeMerger(secondaryEdge.getEdgeSource());
NodeMerger targetNodeMerger = getNodeMerger(secondaryEdge.getEdgeTarget());
if (sourceNodeMerger != targetNodeMerger) {
boolean isMerged = false;
for (@NonNull EdgeMerger edgeMerger : sourceNodeMerger.getOutgoingEdgeMergers(targetNodeMerger)) {
if (edgeMerger.sameEdge(secondaryEdge) != null) {
edgeMerger.addOldEdge(secondaryEdge);
isMerged = true;
break;
}
}
if (!isMerged) {
new EdgeMerger(this, secondaryEdge);
}
}
else {
addPrunedEdge(secondaryEdge);
}
}
public void addSecondaryRegion(@NonNull MappingRegion secondaryRegion, @NonNull Map<@NonNull Node, @NonNull Node> secondaryNode2primaryNode) {
// assert !(secondaryRegion instanceof MicroMappingRegion);
assert !secondaryRegions.contains(secondaryRegion);
secondaryRegions.add(secondaryRegion);
this.secondaryNode2primaryNode.putAll(secondaryNode2primaryNode);
//
for (@NonNull Node secondaryNode : RegionUtil.getOwnedNodes(secondaryRegion)) {
Node primaryNode = secondaryNode2primaryNode.get(secondaryNode);
if (primaryNode != null) {
NodeMerger nodeMerger = oldNode2nodeMerger.get(primaryNode);
assert nodeMerger != null;
nodeMerger.addOldNode(secondaryNode);
}
else {
new NodeMerger(this, secondaryNode);
}
}
//
for (@NonNull Edge secondaryEdge : RegionUtil.getOwnedEdges(secondaryRegion)) {
if (!secondaryEdge.isSecondary()) {
addSecondaryEdge(secondaryEdge);
}
}
}
public void check(@NonNull MappingRegion newRegion) {
checkNodes(newRegion, primaryRegion);
for (@NonNull Region secondaryRegion : secondaryRegions) {
checkNodes(newRegion, secondaryRegion);
}
checkEdges(newRegion, primaryRegion);
for (@NonNull Region secondaryRegion : secondaryRegions) {
checkEdges(newRegion, secondaryRegion);
}
}
protected void checkEdges(@NonNull MappingRegion newRegion, @NonNull Region oldRegion) {
for (@NonNull Edge oldEdge : RegionUtil.getOwnedEdges(oldRegion)) {
assert oldEdge.getOwningRegion() == oldRegion;
if (!oldEdge.isRecursion() && !oldEdge.isSecondary()) { // FIXME Remove this irregularity
EdgeMerger edgeMerger = oldEdge2edgeMerger.get(oldEdge);
if (edgeMerger != null) {
assert Iterables.contains(edgeMerger.getOldEdges(), oldEdge);
assert edgeMerger.getNewEdge().getOwningRegion() == newRegion;
}
else {
assert debugPrunedEdges.contains(oldEdge);
}
}
}
}
protected void checkNodes(@NonNull MappingRegion newRegion, @NonNull Region oldRegion) {
for (@NonNull Node oldNode : RegionUtil.getOwnedNodes(oldRegion)) {
assert oldNode.getOwningRegion() == oldRegion;
Node nodeMerger = getNodeMerger(oldNode).getNewNode();
assert nodeMerger.getOwningRegion() == newRegion;
}
}
public @NonNull MappingRegion create() {
@NonNull String newName = createNewName();
MappingRegion newRegion = createNewRegion(newName);
createNewNodes(newRegion);
createNewEdges();
RegionHelper.initHeadNodes(newRegion);
return newRegion;
}
protected @NonNull String createNewName() {
List<@NonNull String> names = new ArrayList<>();
names.add(primaryRegion.getName().replace("»\\n", "» "));
for (@NonNull MappingRegion secondaryRegion : secondaryRegions) {
names.add(secondaryRegion.getName().replace("»\\n", "» "));
}
Collections.sort(names);
StringBuilder s = new StringBuilder();
for (@NonNull String name : names) {
if (s.length() > 0) {
s.append("\\n");
}
s.append(name);
}
return s.toString();
}
protected void createNewEdges() {
for (@NonNull EdgeMerger edgeMerger : new HashSet<>(oldEdge2edgeMerger.values())) {
Node sourceNodeMerger = getNodeMerger(edgeMerger.getOldSource()).getNewNode();
Node targetNodeMerger = getNodeMerger(edgeMerger.getOldTarget()).getNewNode();
edgeMerger.createNewEdge(sourceNodeMerger, targetNodeMerger);
}
}
protected void createNewNodes(@NonNull MappingRegion newRegion) {
for (@NonNull NodeMerger nodeMerger : new HashSet<>(oldNode2nodeMerger.values())) {
nodeMerger.createNewNode(newRegion);
}
}
protected abstract @NonNull MappingRegion createNewRegion(@NonNull String newName);
protected @NonNull EdgeMerger getEdgeMerger(@NonNull Edge oldEdge) {
return ClassUtil.nonNullState(oldEdge2edgeMerger.get(oldEdge));
}
protected @NonNull NodeMerger getNodeMerger(@NonNull Node oldNode) {
return ClassUtil.nonNullState(oldNode2nodeMerger.get(oldNode));
}
protected @NonNull List<@NonNull MappingRegion> getSecondaryRegions() {
return secondaryRegions;
}
protected void mapOldEdge(@NonNull Edge oldEdge, @NonNull EdgeMerger edgeMerger) {
EdgeMerger oldMergedEdge = oldEdge2edgeMerger.put(oldEdge, edgeMerger);
assert oldMergedEdge == null;
}
protected void mapOldNode(@NonNull Node oldNode, @NonNull NodeMerger nodeMerger) {
NodeMerger oldMergedNode = oldNode2nodeMerger.put(oldNode, nodeMerger);
assert oldMergedNode == null;
}
/**
* Prune nodes (and edges) that are not needed.
*
* Nodes that can be folded into other nodes, e.g. the input of a cast can be folded into its output, are pruned.
*/
public void prune() {
List<@NonNull EdgeMerger> foldableEdgeMergers = new ArrayList<>();
for (@NonNull NodeMerger nodeMerger : new HashSet<>(oldNode2nodeMerger.values())) {
nodeMerger.gatherFoldableEdges(foldableEdgeMergers);
}
for (@NonNull EdgeMerger foldableEdgeMerger : foldableEdgeMergers) {
NodeMerger sourceNodeMerger = foldableEdgeMerger.getSource();
NodeMerger targetNodeMerger = foldableEdgeMerger.getTarget();
sourceNodeMerger.destroy();
for (@NonNull Node oldNode : sourceNodeMerger.getOldNodes()) {
targetNodeMerger.addOldNode(oldNode);
for (@NonNull Edge oldEdge : RegionUtil.getIncomingEdges(oldNode)) {
addSecondaryEdge(oldEdge);
}
for (@NonNull Edge oldEdge : RegionUtil.getOutgoingEdges(oldNode)) {
addSecondaryEdge(oldEdge);
}
}
}
}
@Override
public String toString() {
return createNewName();
}
protected void unmapOldEdge(@NonNull Edge oldEdge, @NonNull EdgeMerger edgeMerger) {
EdgeMerger oldEdgeMerger = oldEdge2edgeMerger.remove(oldEdge);
assert oldEdgeMerger == edgeMerger;
}
protected void unmapOldNode(@NonNull Node oldNode, @NonNull NodeMerger nodeMerger) {
NodeMerger oldNodeMerger = oldNode2nodeMerger.remove(oldNode);
assert oldNodeMerger == nodeMerger;
}
}