| /******************************************************************************* |
| * 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: |
| * Adolfo Sanchez-Barbudo Herrera - initial API and implementation |
| * E.D.Willink - use Complete model |
| *******************************************************************************/ |
| package org.eclipse.qvtd.compiler.internal.qvtm2qvts; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.stream.Stream; |
| |
| import org.eclipse.jdt.annotation.NonNull; |
| import org.eclipse.jdt.annotation.Nullable; |
| import org.eclipse.ocl.pivot.CompleteClass; |
| import org.eclipse.ocl.pivot.Property; |
| import org.eclipse.ocl.pivot.utilities.ClassUtil; |
| import org.eclipse.qvtd.compiler.internal.qvts2qvts.ClassDatumAnalysis; |
| import org.eclipse.qvtd.pivot.qvtschedule.ClassDatum; |
| import org.eclipse.qvtd.pivot.qvtschedule.Edge; |
| import org.eclipse.qvtd.pivot.qvtschedule.NavigableEdge; |
| import org.eclipse.qvtd.pivot.qvtschedule.Node; |
| import org.eclipse.qvtd.pivot.qvtschedule.PropertyDatum; |
| import org.eclipse.qvtd.pivot.qvtschedule.Region; |
| import org.eclipse.qvtd.pivot.qvtschedule.utilities.QVTscheduleUtil; |
| |
| import com.google.common.collect.Sets; |
| |
| /** |
| * A ContentsAnalysis provides an analysis of many (all) regions to facilitate lookup of all producers consumers of particular types and properties. |
| */ |
| public class ContentsAnalysis |
| { |
| protected final @NonNull ScheduleManager scheduleManager; |
| |
| /** |
| * The Speculation or Realized Nodes that produce each ClassDatum. |
| */ |
| private final @NonNull Map<@NonNull ClassDatumAnalysis, @NonNull List<@NonNull Node>> classDatumAnalysis2newNodes = new HashMap<>(); |
| |
| /** |
| * The input model classes that may be used as independent inputs by mappings and the nodes at which they are consumed. |
| * In the worst case a flat schedule just permutes allInstances() to provide all mapping inputs. |
| */ |
| private final @NonNull Map<@NonNull ClassDatumAnalysis, @NonNull List<@NonNull Node>> classDatumAnalysis2oldNodes = new HashMap<>(); |
| |
| /** |
| * The Realized Edges that produce each PropertyDatum (or its opposite). |
| */ |
| private final @NonNull Map<@NonNull PropertyDatum, @NonNull List<@NonNull NavigableEdge>> propertyDatum2newEdges = new HashMap<>(); |
| |
| public ContentsAnalysis(@NonNull ScheduleManager scheduleManager) { |
| this.scheduleManager = scheduleManager; |
| } |
| |
| private void addNewEdge(@NonNull NavigableEdge newEdge) { |
| PropertyDatum propertyDatum = basicGetPropertyDatum(newEdge); |
| if (propertyDatum == null) { |
| propertyDatum = basicGetPropertyDatum(newEdge); // FIXME debugging |
| } |
| assert propertyDatum != null; |
| addNewEdge(newEdge, propertyDatum); |
| } |
| private void addNewEdge(@NonNull NavigableEdge newEdge, @NonNull PropertyDatum propertyDatum) { |
| List<@NonNull NavigableEdge> edges = propertyDatum2newEdges.get(propertyDatum); |
| if (edges == null) { |
| edges = new ArrayList<>(); |
| propertyDatum2newEdges.put(propertyDatum, edges); |
| } |
| if (!edges.contains(newEdge)) { |
| edges.add(newEdge); |
| for (@NonNull PropertyDatum superAbstractDatum : ClassUtil.nullFree(propertyDatum.getSuperPropertyDatums())) { |
| addNewEdge(newEdge, superAbstractDatum); |
| } |
| } |
| } |
| |
| private void addNewNode(@NonNull Node newNode) { |
| ClassDatumAnalysis classDatumAnalysis = scheduleManager.getElementalClassDatumAnalysis(newNode); |
| for (@NonNull ClassDatumAnalysis superClassDatumAnalysis : classDatumAnalysis.getSuperClassDatumAnalyses()) { |
| List<@NonNull Node> nodes = classDatumAnalysis2newNodes.get(superClassDatumAnalysis); |
| if (nodes == null) { |
| nodes = new ArrayList<>(); |
| classDatumAnalysis2newNodes.put(superClassDatumAnalysis, nodes); |
| } |
| nodes.add(newNode); |
| } |
| } |
| |
| private void addOldNode(@NonNull Node oldNode) { |
| // assert !"EObject".equals(headNode.getCompleteClass().getName()); |
| // Region region = oldNode.getRegion(); |
| // Region invokingRegion = region.getInvokingRegion(); |
| // assert (invokingRegion == this) || (invokingRegion == null); |
| ClassDatumAnalysis classDatumAnalysis = RegionUtil.getClassDatumAnalysis(oldNode); |
| List<@NonNull Node> nodes = classDatumAnalysis2oldNodes.get(classDatumAnalysis); |
| if (nodes == null) { |
| nodes = new ArrayList<>(); |
| classDatumAnalysis2oldNodes.put(classDatumAnalysis, nodes); |
| } |
| if (!nodes.contains(oldNode)) { |
| nodes.add(oldNode); |
| } |
| } |
| |
| public void addRegion(@NonNull Region region) { |
| for (@NonNull Node oldNode : region.getOldNodes()) { |
| if (!oldNode.isDependency() && !oldNode.isConstant()) { |
| if (oldNode.isHead()) { |
| // if (oldNode.isLoaded()) { |
| addOldNode(oldNode); |
| // } |
| } |
| else { |
| // if (!oldNode.isLoaded()) { |
| if (!isOnlyCastOrRecursed(oldNode)) { // FIXME Eliminate cast nodes |
| addOldNode(oldNode); |
| } |
| // } |
| } |
| } |
| } |
| for (@NonNull Node newNode : region.getNewNodes()) { |
| if (newNode.isClass()) { |
| addNewNode(newNode); |
| } |
| } |
| for (@NonNull NavigableEdge newEdge : region.getRealizedNavigationEdges()) { |
| addNewEdge(newEdge); |
| } |
| } |
| |
| private @Nullable PropertyDatum basicGetPropertyDatum(@NonNull NavigableEdge producedEdge) { |
| assert !producedEdge.isCast(); // Handled by caller |
| Property forwardProperty = producedEdge.getProperty(); |
| ClassDatumAnalysis classDatumAnalysis = RegionUtil.getClassDatumAnalysis(producedEdge.getEdgeSource()); |
| ClassDatum forwardClassDatum = RegionUtil.getElementalClassDatum(classDatumAnalysis); |
| // PropertyDatum forwardPropertyDatum = getScheduleModel().getPropertyDatum(forwardClassDatum, property); |
| // if (forwardPropertyDatum.getClassDatum() == forwardClassDatum) { |
| // return forwardPropertyDatum; |
| // } |
| Iterable<@NonNull PropertyDatum> forwardPropertyDatums = scheduleManager.getAllPropertyDatums(forwardClassDatum); |
| for (PropertyDatum propertyDatum : forwardPropertyDatums) { |
| if ((propertyDatum.getReferredProperty() == forwardProperty) && (propertyDatum.getOwningClassDatum() == forwardClassDatum)) { |
| return propertyDatum; |
| } |
| } |
| PropertyDatum bestPropertyDatum = null; |
| for (PropertyDatum propertyDatum : forwardPropertyDatums) { |
| if (propertyDatum.getReferredProperty() == forwardProperty) { |
| if (bestPropertyDatum == null) { |
| bestPropertyDatum = propertyDatum; |
| } |
| else { |
| CompleteClass completeClass = propertyDatum.getOwningClassDatum().getCompleteClass(); |
| assert completeClass != null; |
| Set<@NonNull CompleteClass> allSuperCompleteClasses = Sets.newHashSet(completeClass.getProperSuperCompleteClasses()); |
| if (allSuperCompleteClasses.contains(bestPropertyDatum.getOwningClassDatum().getCompleteClass())) { |
| bestPropertyDatum = propertyDatum; |
| } |
| } |
| } |
| } |
| if (bestPropertyDatum != null) { |
| return bestPropertyDatum; |
| } |
| Property reverseProperty = forwardProperty.getOpposite(); |
| classDatumAnalysis = RegionUtil.getClassDatumAnalysis(producedEdge.getEdgeTarget()); |
| ClassDatum reverseClassDatum = RegionUtil.getElementalClassDatum(classDatumAnalysis); |
| Iterable<@NonNull PropertyDatum> reversePropertyDatums = scheduleManager.getAllPropertyDatums(reverseClassDatum); |
| for (PropertyDatum propertyDatum : reversePropertyDatums) { |
| if ((propertyDatum.getReferredProperty() == reverseProperty) && (propertyDatum.getOwningClassDatum() == reverseClassDatum)) { |
| return propertyDatum; |
| } |
| } |
| for (PropertyDatum propertyDatum : reversePropertyDatums) { |
| if (propertyDatum.getReferredProperty() == reverseProperty) { |
| if (bestPropertyDatum == null) { |
| bestPropertyDatum = propertyDatum; |
| } |
| else { |
| CompleteClass completeClass = propertyDatum.getOwningClassDatum().getCompleteClass(); |
| assert completeClass != null; |
| Set<@NonNull CompleteClass> allSuperCompleteClasses = Sets.newHashSet(completeClass.getProperSuperCompleteClasses()); |
| if (allSuperCompleteClasses.contains(bestPropertyDatum.getOwningClassDatum().getCompleteClass())) { |
| bestPropertyDatum = propertyDatum; |
| } |
| } |
| } |
| } |
| if (bestPropertyDatum != null) { |
| return bestPropertyDatum; |
| } |
| return null; |
| } |
| |
| public Stream<String> dumpClass2oldNode() { |
| Stream<String> entries = classDatumAnalysis2oldNodes.keySet().stream().map( |
| k -> { |
| List<Node> list = classDatumAnalysis2oldNodes.get(k); |
| assert list != null; |
| return String.valueOf(k) + " : " + list.stream().map( |
| p -> p.getDisplayName() |
| ).sorted().reduce("", QVTscheduleUtil.stringJoin("\n\t\t")); |
| } |
| ); |
| return entries.sorted(); |
| } |
| |
| public Stream<String> dumpClass2newNode() { |
| Stream<String> entries = classDatumAnalysis2newNodes.keySet().stream().map( |
| k -> { |
| List<Node> list = classDatumAnalysis2newNodes.get(k); |
| assert list != null; |
| return k.getDomainUsage() + " " + String.valueOf(k) + " : " + |
| list.stream().map( |
| p -> p.getDisplayName() |
| ).sorted().reduce("", QVTscheduleUtil.stringJoin("\n\t\t") |
| ); |
| } |
| ); |
| return entries.sorted(); |
| } |
| |
| /** |
| * Return all Realized NavigationEdges corresponding to predicatedEdge that navigate an isComposite property in either direction. |
| * Returns null in the very unusual event that there are none. |
| * |
| * It is assumed that edge is an predicated oclContainer edge to which almost all containment edges are compatible for a pathological |
| * input model whose metamodel extends the transformed metamodel with derived classes that merge the containment relationship |
| * of predicated/realized candidates. |
| * |
| * FIXME In the event that the ends of the realized edges are realized variables, we do know the precise |
| * type and could filter accordingly; a not-yet-exploited optimisation. |
| */ |
| private @Nullable Iterable<@NonNull NavigableEdge> getCompositeNewEdges(@NonNull NavigableEdge predicatedEdge) { |
| Set<@NonNull NavigableEdge> realizedEdges = null; |
| for (Map.Entry<@NonNull PropertyDatum, @NonNull List<@NonNull NavigableEdge>> entry : propertyDatum2newEdges.entrySet()) { |
| Property property = entry.getKey().getReferredProperty(); |
| if (property != null) { |
| @Nullable Property compositeProperty = null; |
| if (property.isIsComposite()) { |
| compositeProperty = property; |
| } |
| else { |
| Property oppositeProperty = property.getOpposite(); |
| if ((oppositeProperty != null) && oppositeProperty.isIsComposite()) { |
| compositeProperty = oppositeProperty; |
| } |
| } |
| if (compositeProperty != null) { |
| if (realizedEdges == null) { |
| realizedEdges = new HashSet<>(); |
| } |
| realizedEdges.addAll(entry.getValue()); |
| } |
| } |
| } |
| return realizedEdges; |
| } |
| |
| public @Nullable Iterable<@NonNull NavigableEdge> getNewEdges(@NonNull NavigableEdge edge, @NonNull ClassDatumAnalysis requiredClassDatumAnalysis) { |
| Property property = edge.getProperty(); |
| if (property.eContainer() == null) { // Ignore pseudo-properties such as «iterate» |
| return null; |
| } |
| PropertyDatum propertyDatum = basicGetPropertyDatum(edge); |
| if (propertyDatum == null) { |
| if (property == scheduleManager.getStandardLibraryHelper().getOclContainerProperty()) { |
| return getCompositeNewEdges(edge); |
| } |
| propertyDatum = basicGetPropertyDatum(edge); // FIXME debugging |
| } |
| if (propertyDatum == null) { // May be null for edges only used by operation dependencies |
| return null; |
| } |
| Iterable<@NonNull NavigableEdge> realizedEdges = propertyDatum2newEdges.get(propertyDatum); |
| if (realizedEdges == null) { |
| return null; |
| } |
| CompleteClass requiredClass = RegionUtil.getCompleteClass(requiredClassDatumAnalysis); |
| List<@NonNull NavigableEdge> conformantRealizedEdges = null; |
| for (@NonNull NavigableEdge realizedEdge : realizedEdges) { |
| Node targetNode = realizedEdge.getEdgeTarget(); |
| CompleteClass realizedClass = targetNode.getCompleteClass(); |
| if (RegionUtil.conformsToClassOrBehavioralClass(realizedClass, requiredClass)) { |
| if (conformantRealizedEdges == null) { |
| conformantRealizedEdges = new ArrayList<>(); |
| } |
| conformantRealizedEdges.add(realizedEdge); |
| } |
| } |
| return conformantRealizedEdges; |
| } |
| |
| public @Nullable Iterable<@NonNull Node> getNewNodes(@NonNull ClassDatumAnalysis classDatumAnalysis) { |
| return classDatumAnalysis2newNodes.get(classDatumAnalysis); |
| } |
| |
| public @Nullable Iterable<@NonNull Node> getOldNodes(@NonNull ClassDatumAnalysis classDatumAnalysis) { |
| return classDatumAnalysis2oldNodes.get(classDatumAnalysis); |
| } |
| |
| /** |
| * Return true if this node is consumed solely by casts (or recursions) and so need not be considered as a true consumer. |
| * The downstream usages will consume more accurately. |
| */ |
| private boolean isOnlyCastOrRecursed(@NonNull Node predicatedNode) { |
| boolean isCast = false; |
| for (Edge outgoingEdge : RegionUtil.getOutgoingEdges(predicatedNode)) { |
| if (!outgoingEdge.isCast() && !outgoingEdge.isRecursion()) { |
| return false; |
| } |
| isCast = true; |
| } |
| return isCast; |
| } |
| |
| private void removeNewEdge(@NonNull NavigableEdge newEdge) { |
| PropertyDatum propertyDatum = basicGetPropertyDatum(newEdge); |
| if (propertyDatum == null) { |
| propertyDatum = basicGetPropertyDatum(newEdge); // FIXME debugging |
| } |
| assert propertyDatum != null; |
| removeNewEdge(newEdge, propertyDatum); |
| } |
| private void removeNewEdge(@NonNull NavigableEdge newEdge, @NonNull PropertyDatum propertyDatum) { |
| List<@NonNull NavigableEdge> edges = propertyDatum2newEdges.get(propertyDatum); |
| if (edges != null) { |
| if (edges.remove(newEdge)) { |
| for (@NonNull PropertyDatum superAbstractDatum : ClassUtil.nullFree(propertyDatum.getSuperPropertyDatums())) { |
| removeNewEdge(newEdge, superAbstractDatum); |
| } |
| } |
| } |
| } |
| |
| private void removeNewNode(@NonNull Node newNode) { |
| ClassDatumAnalysis classDatumAnalysis = RegionUtil.getClassDatumAnalysis(newNode); |
| List<@NonNull Node> nodes = classDatumAnalysis2newNodes.get(classDatumAnalysis); |
| if (nodes != null) { |
| nodes.remove(newNode); |
| } |
| } |
| |
| private void removeOldNode(@NonNull Node oldNode) { |
| ClassDatumAnalysis classDatumAnalysis = RegionUtil.getClassDatumAnalysis(oldNode); |
| List<@NonNull Node> nodes = classDatumAnalysis2oldNodes.get(classDatumAnalysis); |
| if (nodes != null) { |
| nodes.remove(oldNode); |
| } |
| } |
| |
| public void removeRegion(@NonNull Region region) { |
| for (@NonNull Node oldNode : region.getOldNodes()) { |
| removeOldNode(oldNode); |
| } |
| for (@NonNull Node newNode : region.getNewNodes()) { |
| removeNewNode(newNode); |
| } |
| for (@NonNull NavigableEdge newEdge : region.getRealizedNavigationEdges()) { |
| removeNewEdge(newEdge); |
| } |
| } |
| } |