blob: 0678cd6faab06be5c14ecdf41b05d5083dc207c4 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2015, 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.qvtp2qvts;
import java.io.IOException;
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.ExpressionInOCL;
import org.eclipse.ocl.pivot.LanguageExpression;
import org.eclipse.ocl.pivot.OCLExpression;
import org.eclipse.ocl.pivot.Operation;
import org.eclipse.ocl.pivot.OperationCallExp;
import org.eclipse.ocl.pivot.utilities.ClassUtil;
import org.eclipse.ocl.pivot.utilities.EnvironmentFactory;
import org.eclipse.ocl.pivot.utilities.NameUtil;
import org.eclipse.ocl.pivot.utilities.ParserException;
import org.eclipse.ocl.pivot.utilities.TracingOption;
import org.eclipse.qvtd.compiler.CompilerConstants;
import org.eclipse.qvtd.compiler.CompilerProblem;
import org.eclipse.qvtd.compiler.ProblemHandler;
import org.eclipse.qvtd.compiler.internal.qvtp2qvts.analysis.ClassDatumAnalysis;
import org.eclipse.qvtd.compiler.internal.qvts2qvts.Region2Depth;
import org.eclipse.qvtd.compiler.internal.qvts2qvts.merger.EarlyMerger;
import org.eclipse.qvtd.pivot.qvtbase.Transformation;
import org.eclipse.qvtd.pivot.qvtcore.Mapping;
import org.eclipse.qvtd.pivot.schedule.ClassDatum;
public class QVTp2QVTs extends SchedulerConstants
{
public static final @NonNull TracingOption CONNECTION_CREATION = new TracingOption(CompilerConstants.PLUGIN_ID, "qvtp2qvts/connectionCreation");
public static final @NonNull TracingOption CONNECTION_ROUTING = new TracingOption(CompilerConstants.PLUGIN_ID, "qvtp2qvts/connectionRouting");
public static final @NonNull TracingOption DEBUG_GRAPHS = new TracingOption(CompilerConstants.PLUGIN_ID, "qvtp2qvts/debugGraphs");
public static final @NonNull TracingOption DUMP_CLASS_TO_CONSUMING_NODES = new TracingOption(CompilerConstants.PLUGIN_ID, "qvtp2qvts/dump/class2consumingNodes");
public static final @NonNull TracingOption DUMP_CLASS_TO_CONTAINING_PROPERTIES = new TracingOption(CompilerConstants.PLUGIN_ID, "qvtp2qvts/dump/class2containingProperty");
public static final @NonNull TracingOption DUMP_CLASS_TO_REALIZED_NODES = new TracingOption(CompilerConstants.PLUGIN_ID, "qvtp2qvts/dump/class2realizedNodes");
public static final @NonNull TracingOption DUMP_INPUT_MODEL_TO_DOMAIN_USAGE = new TracingOption(CompilerConstants.PLUGIN_ID, "qvtp2qvts/dump/inputModel2domainUsage");
public static final @NonNull TracingOption DUMP_PROPERTY_TO_CONSUMING_CLASSES = new TracingOption(CompilerConstants.PLUGIN_ID, "qvtp2qvts/dump/property2consumingClass");
public static final @NonNull TracingOption EDGE_ORDER = new TracingOption(CompilerConstants.PLUGIN_ID, "qvtp2qvts/edgeOrder");
public static final @NonNull TracingOption REGION_CYCLES = new TracingOption(CompilerConstants.PLUGIN_ID, "qvtp2qvts/regionCycles");
public static final @NonNull TracingOption REGION_DEPTH = new TracingOption(CompilerConstants.PLUGIN_ID, "qvtp2qvts/regionDepth");
// public static final @NonNull TracingOption REGION_LOCALITY = new TracingOption(CompilerConstants.PLUGIN_ID, "qvtp2qvts/regionLocality");
public static final @NonNull TracingOption REGION_ORDER = new TracingOption(CompilerConstants.PLUGIN_ID, "qvtp2qvts/regionOrder");
public static final @NonNull TracingOption REGION_STACK = new TracingOption(CompilerConstants.PLUGIN_ID, "qvtp2qvts/regionStack");
public static final @NonNull TracingOption REGION_TRAVERSAL = new TracingOption(CompilerConstants.PLUGIN_ID, "qvtp2qvts/regionTraversal");
protected final @NonNull ProblemHandler problemHandler;
/**
* The Region to which each mapping is allocated.
*/
private final @NonNull Map<@NonNull Mapping, @NonNull BasicMappingRegion> mapping2mappingRegion = new HashMap<>();
public QVTp2QVTs(@NonNull ProblemHandler problemHandler, @NonNull EnvironmentFactory environmentFactory, @NonNull Transformation asTransformation) {
super(environmentFactory, asTransformation);
this.problemHandler = problemHandler;
}
private Map<@NonNull OperationDatum, @NonNull OperationRegion> map = new HashMap<>();
public void addProblem(@NonNull CompilerProblem problem) {
problemHandler.addProblem(problem);
}
public @NonNull OperationRegion analyzeOperation(@NonNull MultiRegion multiRegion, @NonNull OperationCallExp operationCallExp) {
Operation operation = operationCallExp.getReferredOperation();
LanguageExpression bodyExpression = operation.getBodyExpression();
assert bodyExpression != null;
ExpressionInOCL specification;
try {
specification = getEnvironmentFactory().getMetamodelManager().parseSpecification(bodyExpression);
OperationDatum operationDatum = createOperationDatum(operationCallExp);
OperationRegion operationRegion = map.get(operationDatum);
if (operationRegion == null) {
operationRegion = new OperationRegion(multiRegion, operationDatum, specification, operationCallExp);
map.put(operationDatum, operationRegion);
if (QVTp2QVTs.DEBUG_GRAPHS.isActive()) {
operationRegion.writeDebugGraphs(null);
}
}
return operationRegion;
} catch (ParserException e) {
// TODO Auto-generated catch block
e.printStackTrace();
throw new UnsupportedOperationException(e);
}
}
private @NonNull OperationDatum createOperationDatum(@NonNull OperationCallExp operationCallExp) {
List<@NonNull OCLExpression> ownedArguments = ClassUtil.nullFree(operationCallExp.getOwnedArguments());
@NonNull ClassDatum[] classDatums = new @NonNull ClassDatum[1 + ownedArguments.size()];
int i = 0;
@SuppressWarnings("null")@NonNull OCLExpression source = operationCallExp.getOwnedSource();
classDatums[i++] = getClassDatum(source);
for (@NonNull OCLExpression argument : ownedArguments) {
classDatums[i++] = getClassDatum(argument);
}
String operationName = operationCallExp.getReferredOperation().getName();
assert operationName != null;
return new OperationDatum(this, operationName, classDatums);
}
@Override
protected @NonNull ClassDatumAnalysis createClassDatumAnalysis(@NonNull ClassDatum classDatum) {
return new ClassDatumAnalysis(this, classDatum);
}
/**
* 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.
*/
public @NonNull List<@NonNull MappingRegion> earlyRegionMerge(@NonNull List<@NonNull BasicMappingRegion> orderedRegions) {
Region2Depth region2depths = new Region2Depth();
List<@NonNull MappingRegion> outputRegions = new ArrayList<>();
LinkedHashSet<@NonNull MappingRegion> residualInputRegions = new LinkedHashSet<>(orderedRegions); // order preserving fast random removal
while (!residualInputRegions.isEmpty()) {
@NonNull MappingRegion candidateRegion = residualInputRegions.iterator().next();
boolean isMerged = false;
if (isEarlyMergePrimaryCandidate(candidateRegion)) {
List<@NonNull MappingRegion> secondaryRegions = selectSecondaryRegions(candidateRegion);
if (secondaryRegions != null) {
MappingRegion mergedRegion = candidateRegion;
for (@NonNull MappingRegion secondaryRegion : secondaryRegions) {
assert secondaryRegion != null;
if (residualInputRegions.contains(secondaryRegion)) {
Map<@NonNull Node, @NonNull Node> secondaryNode2primaryNode = EarlyMerger.canMerge(mergedRegion, secondaryRegion, region2depths, false);
if (secondaryNode2primaryNode != null) {
boolean isSharedHead = isSharedHead(mergedRegion, secondaryRegion);
if (!isSharedHead || (EarlyMerger.canMerge(secondaryRegion, mergedRegion, region2depths, false) != null)) {
residualInputRegions.remove(mergedRegion);
residualInputRegions.remove(secondaryRegion);
mergedRegion = RegionMerger.createMergedRegion(mergedRegion, secondaryRegion, secondaryNode2primaryNode);
region2depths.addRegion(mergedRegion);
}
}
}
}
if (mergedRegion != candidateRegion) {
// mergedRegion.resolveRecursion();
if (QVTp2QVTs.DEBUG_GRAPHS.isActive()) {
mergedRegion.writeDebugGraphs("2-merged");
}
// GuardedRegion guardedRegion = createGuardedRegion(mergedRegion, mergeableRegions);
// outputRegions.add(guardedRegion);
outputRegions.add(mergedRegion);
isMerged = true;
}
}
}
if (!isMerged) {
outputRegions.add(candidateRegion);
}
residualInputRegions.remove(candidateRegion);
}
return outputRegions;
}
public @NonNull MappingRegion getMappingRegion(@NonNull Mapping mapping) {
MappingRegion mappingRegion = mapping2mappingRegion.get(mapping);
assert mappingRegion != null;
return mappingRegion;
}
/**
* Return the nodes with 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> getMergeableNodes(@NonNull MappingRegion region) {
Set<@NonNull Node> mergeableNodes = new HashSet<>();
for (@NonNull Node node : region.getHeadNodes()) {
getMergeableNodes(mergeableNodes, node);
}
return mergeableNodes;
}
private void getMergeableNodes(@NonNull Set<@NonNull Node> mergeableNodes, @NonNull Node node) {
if (isMergeable(node) && mergeableNodes.add(node)) {
for (@NonNull NavigableEdge edge : node.getNavigationEdges()) {
if (edge.getOppositeEdge() != null) {
getMergeableNodes(mergeableNodes, edge.getTarget());
}
}
}
}
private boolean isMergeable(@NonNull Node node) { // FIXME this is legacy creep
NodeRole nodeRole = node.getNodeRole();
if (nodeRole.isRealized() || nodeRole.isSpeculation()) {
return false;
}
if (!node.isClass()) {
return false;
}
if (node.isExplicitNull()) {
return true;
}
if (node.isOperation()) {
return true;
}
if (node.isTrue()) {
return true;
}
if (node.isPattern()) {
return true;
}
return false;
}
/**
* The primary region in a GuardedRegion must be single-headed. It may be multiply-produced, e.g. recursed.
*/
private boolean isEarlyMergePrimaryCandidate(@NonNull Region mappingRegion) {
List<@NonNull Node> headNodes = mappingRegion.getHeadNodes();
return headNodes.size() == 1;
}
/**
* The secondary region in a GuardedRegion must be single-headed and at least one its head nodes must be a class in use within
* the primary region. It may be multiply-produced, e.g. recursed.
*/
private boolean isEarlyMergeSecondaryCandidate(@NonNull Region primaryRegion,
@NonNull Region secondaryRegion, @NonNull Set<ClassDatumAnalysis> toOneReachableClasses) {
List<@NonNull Node> secondaryHeadNodes = secondaryRegion.getHeadNodes();
if (secondaryHeadNodes.size() == 1) {
Node classNode = secondaryHeadNodes.get(0);
ClassDatumAnalysis classDatumAnalysis = classNode.getClassDatumAnalysis();
if (toOneReachableClasses.contains(classDatumAnalysis)) {
return true;
}
}
return false;
}
/**
* Return true if any primaryRegion head coincides with a secondaryRegion head.
*/
private boolean isSharedHead(@NonNull Region primaryRegion, @NonNull Region secondaryRegion) {
for (Node primaryHead : primaryRegion.getHeadNodes()) {
ClassDatumAnalysis primaryClassDatumAnalysis = primaryHead.getClassDatumAnalysis();
for (Node secondaryHead : secondaryRegion.getHeadNodes()) {
if (primaryClassDatumAnalysis == secondaryHead.getClassDatumAnalysis()) {
return true;
}
}
}
return false;
}
/**
* Return a list of single-headed to-one navigable regions whose head is transitively to-one reachable from the primaryRegion's head.
*/
private @Nullable List<@NonNull MappingRegion> selectSecondaryRegions(@NonNull MappingRegion primaryRegion) {
//
// All regions that consume one of the primary nodes.
//
Set<@NonNull MappingRegion> allConsumingRegions = new HashSet<>();
allConsumingRegions.add(primaryRegion);
//
// All classes reachable from the primary head.
//
Set<org.eclipse.qvtd.compiler.internal.qvtp2qvts.analysis.ClassDatumAnalysis> toOneReachableClasses = new HashSet<>();
List<@NonNull MappingRegion> secondaryRegions = null;
List<@NonNull MappingRegion> allConsumingRegionsList = new ArrayList<>(allConsumingRegions); // CME-proof iterable List shadowing a mutating Set
for (int i = 0; i < allConsumingRegionsList.size(); i++) {
@NonNull MappingRegion secondaryRegion = allConsumingRegionsList.get(i);
if ((i == 0) || isEarlyMergeSecondaryCandidate(primaryRegion, secondaryRegion, toOneReachableClasses)) {
if (i > 0) {
if (secondaryRegions == null) {
secondaryRegions = new ArrayList<>();
}
secondaryRegions.add(secondaryRegion);
}
for (@NonNull Node predicatedNode : getMergeableNodes(secondaryRegion)) {
if (predicatedNode.isClass()) { // Ignore nulls, attributes
ClassDatumAnalysis predicatedClassDatumAnalysis = predicatedNode.getClassDatumAnalysis();
if (toOneReachableClasses.add(predicatedClassDatumAnalysis)) {
for (@NonNull MappingRegion consumingRegion : predicatedClassDatumAnalysis.getConsumingRegions()) {
if (allConsumingRegions.add(consumingRegion)) {
allConsumingRegionsList.add(consumingRegion);
}
}
}
}
}
for (@NonNull Node newNode : secondaryRegion.getNewNodes()) {
if (newNode.isClass()) { // Ignore nulls, attributes
ClassDatumAnalysis consumingClassDatumAnalysis = newNode.getClassDatumAnalysis();
if (toOneReachableClasses.add(consumingClassDatumAnalysis)) {
for (@NonNull MappingRegion consumingRegion : consumingClassDatumAnalysis.getConsumingRegions()) {
if (allConsumingRegions.add(consumingRegion)) {
allConsumingRegionsList.add(consumingRegion);
}
}
}
}
}
}
}
assert allConsumingRegionsList.size() == allConsumingRegions.size(); // Check same changes to CME-proof shadow
return secondaryRegions;
}
public @NonNull MultiRegion transform() throws IOException {
MultiRegion multiRegion = new MultiRegion(this);
Iterable<@NonNull Mapping> orderedMappings = getOrderedMappings();
//
// Extract salient characteristics from within each MappingAction.
//
for (@NonNull Mapping mapping : orderedMappings) {
BasicMappingRegion mappingRegion = BasicMappingRegion.createMappingRegion(multiRegion, mapping);
mapping2mappingRegion.put(mapping, mappingRegion);
}
List<@NonNull BasicMappingRegion> mappingRegions = new ArrayList<>(mapping2mappingRegion.values());
Collections.sort(mappingRegions, NameUtil.NAMEABLE_COMPARATOR); // Stabilize side effect of symbol name disambiguator suffixes
for (@NonNull BasicMappingRegion mappingRegion : mappingRegions) {
mappingRegion.registerConsumptionsAndProductions();
}
if (QVTp2QVTs.DEBUG_GRAPHS.isActive()) {
for (@NonNull MappingRegion mappingRegion : mappingRegions) {
mappingRegion.writeDebugGraphs(null);
}
}
List<@NonNull BasicMappingRegion> orderedRegions = new ArrayList<>();
for (@NonNull Mapping mapping : orderedMappings) {
BasicMappingRegion mappingRegion = mapping2mappingRegion.get(mapping);
assert mappingRegion != null;
orderedRegions.add(mappingRegion);
// mappingRegion.resolveRecursion();
}
List<@NonNull Region> activeRegions = new ArrayList<>(earlyRegionMerge(orderedRegions));
// for (@NonNull Region activeRegion : activeRegions) {
// ((AbstractRegion)activeRegion).resolveRecursion();
// }
for (@NonNull OperationRegion operationRegion : multiRegion.getOperationRegions()) {
activeRegions.add(operationRegion);
}
multiRegion.setActiveRegions(activeRegions);
return multiRegion;
}
}