blob: bb6ae4423c7a8790a7efab76e59eff27ff62b4e6 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 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.pivot.schedule.utilities;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.ocl.pivot.utilities.ClassUtil;
import org.eclipse.qvtd.pivot.schedule.AbstractAction;
import org.eclipse.qvtd.pivot.schedule.AbstractDatum;
import org.eclipse.qvtd.pivot.schedule.Schedule;
public class DependencyUtil
{
public static class NaturalOrderer
{
private final @NonNull Schedule schedule;
/**
* Set of AbstractDatums that have been fully-produced (all producing actions have been ordered).
*/
private final @NonNull Set<@NonNull AbstractDatum> fullyProducedDatums = new HashSet<@NonNull AbstractDatum>();
/**
* Set of AbstractDatums that have been partially-produced (at least one producing actions has been ordered).
*/
private final @NonNull Set<@NonNull AbstractDatum> partiallyProducedDatums = new HashSet<@NonNull AbstractDatum>();
/**
* Queue of AbstractDatums that have been fully produced but whose requirers have yet to be ordered.
*/
private final @NonNull Deque<@NonNull AbstractDatum> fullyReadyDatums = new LinkedList<@NonNull AbstractDatum>();
/**
* Set of MappingActions whose order has been determined.
*/
private final @NonNull Set<@NonNull AbstractAction> orderedActions = new HashSet<@NonNull AbstractAction>();
private final @NonNull List<@NonNull AbstractAction> orderedActionList = new ArrayList<@NonNull AbstractAction>();
public NaturalOrderer(@NonNull Schedule schedule) {
this.schedule = schedule;
}
/**
* Assign the next ordered index to abstractAction and update the produced datums accordingly.
*/
private void addOrderedAction(@NonNull AbstractAction abstractAction) {
// System.out.println("Add ordered " + abstractAction);
assert !orderedActions.contains(abstractAction) : abstractAction.getClass().getSimpleName() + " " + abstractAction + " has already been ordered";
orderedActions.add(abstractAction);
orderedActionList.add(abstractAction);
for (@NonNull AbstractDatum abstractDatum : ClassUtil.nullFree(abstractAction.getProductions())) {
partiallyProducedDatums.add(abstractDatum);
// System.out.println("Partially produced " + abstractDatum);
boolean fullyProduced = true;
for (@NonNull AbstractAction producingAction : ClassUtil.nullFree(abstractDatum.getProducedBy())) {
if (!orderedActions.contains(producingAction)) {
fullyProduced = false;
break;
}
}
if (fullyProduced) {
boolean isNew = fullyProducedDatums.add(abstractDatum);
// System.out.println("Fully produced " + abstractDatum);
assert isNew : abstractDatum.getClass().getSimpleName() + " " + abstractDatum + " has already been fully produced";
fullyReadyDatums.add(abstractDatum);
// System.out.println("Fully ready " + abstractDatum);
}
}
}
/**
* Assign an ordering to all the actions so that in so far as possible no action is ordered after an
* action that produces one of the requisite inputs. Violations occur for loops.
*
* @return null if ok, or a diagnostic message describing the failure to assign an ordering.
*/
public @Nullable String assignNaturalOrdering() {
//
// Check that ordering has not yet been attempted
//
for (AbstractAction abstractAction : schedule.getActions()) {
assert abstractAction.getOrder() == 0 : abstractAction.getClass().getSimpleName() + " " + abstractAction + " has been pre-ordered";
}
//
computeOrdering();
for (int i = 0; i < orderedActionList.size(); i++) {
orderedActionList.get(i).setOrder(i+1);
}
//
// Check that ordering happened.
//
return diagnoseOrderingFailure();
}
/**
* Return the actions as an ordered list, or null if an ordering could not be established.
*/
public @Nullable List<@NonNull AbstractAction> computeOrdering() {
/**
* Queue of AbstractDatums that have been partially produced but whose requirers have yet to be ordered.
*/
Deque<@NonNull AbstractAction> partiallyReadyActions = new LinkedList<@NonNull AbstractAction>();
/**
* Queue of AbstractDatums that have been partially produced but whose requirers have yet to be ordered.
*/
Deque<@NonNull AbstractAction> notReadyActions = new LinkedList<@NonNull AbstractAction>();
//
// Assign all un-produced AbstractDatum (inputs) to the readyDatums Set.
//
// FIXME eAllContents copes with arbitrary depth. Is it more than CallDatum contains PropertyDatum?
for (TreeIterator<EObject> tit = schedule.eAllContents(); tit.hasNext(); ) {
EObject eObject = tit.next();
if (eObject instanceof AbstractDatum) {
// System.out.println("For " + eObject);
AbstractDatum abstractDatum = (AbstractDatum) eObject;
if (abstractDatum.getProducedBy().size() == 0) {
fullyProducedDatums.add(abstractDatum);
// System.out.println("Fully produced " + eObject);
partiallyProducedDatums.add(abstractDatum);
// System.out.println("Partially produced " + eObject);
if (!fullyReadyDatums.contains(abstractDatum)) {
fullyReadyDatums.add(abstractDatum);
// System.out.println("Fully ready " + eObject);
}
}
}
else {
tit.prune();
}
}
//
// Service the fully ready datum queue when not empty falling back to partially ready action queue.
// Each requiring action of a full ready datum is ordered if all its requisites have already been fully produced,
// otherwise the requiring action is placed in the partially ready action queue.
// Partially ready actions are also ordered, but may require run-time support to resolve their partially ready requisites.
//
for (boolean hasFullyReadyDatum; (hasFullyReadyDatum = !fullyReadyDatums.isEmpty()) || !partiallyReadyActions.isEmpty() || !notReadyActions.isEmpty(); ) {
if (hasFullyReadyDatum) {
AbstractDatum readyDatum = fullyReadyDatums.pop();
for (@NonNull AbstractAction candidateAction : ClassUtil.nullFree(readyDatum.getRequiredBy())) {
// System.out.println("candidateAction " + candidateAction);
if (!orderedActions.contains(candidateAction)) {
boolean allRequisitesFullyProduced = true;
boolean allRequisitesPartiallyProduced = true;
for (@NonNull AbstractDatum abstractDatum : ClassUtil.nullFree(candidateAction.getRequisites())) {
if (!partiallyProducedDatums.contains(abstractDatum)) {
allRequisitesFullyProduced = false;
allRequisitesPartiallyProduced = false;
// System.out.println(" needs1 " + abstractDatum);
break;
}
else if (!fullyProducedDatums.contains(abstractDatum)) {
allRequisitesFullyProduced = false;
// System.out.println(" needs2 " + abstractDatum);
}
}
if (allRequisitesFullyProduced) {
addOrderedAction(candidateAction);
}
else if (allRequisitesPartiallyProduced) {
if (!partiallyReadyActions.contains(candidateAction)) {
partiallyReadyActions.add(candidateAction);
// System.out.println("Partially ready push " + candidateAction);
}
}
else { // Some requisite hasn't been produced at all.
if (!notReadyActions.contains(candidateAction)) {
notReadyActions.add(candidateAction);
// System.out.println("Not-ready push " + candidateAction);
}
}
}
}
}
else if (!partiallyReadyActions.isEmpty()) {
AbstractAction partiallyReadyAction = partiallyReadyActions.pop();
// System.out.println("Partially ready pop " + partiallyReadyAction);
if (!orderedActions.contains(partiallyReadyAction)) { // Might have been fully ordered while queued.
addOrderedAction(partiallyReadyAction);
}
}
else {
AbstractAction notReadyAction = notReadyActions.pop();
// System.out.println("Not-ready pop " + notReadyAction);
if (!orderedActions.contains(notReadyAction)) { // Might have been fully ordered while queued.
addOrderedAction(notReadyAction);
}
}
}
//
if (schedule.getActions().size() == orderedActionList.size()) {
return orderedActionList;
}
else {
return null;
}
}
/**
* Return an explanation of the failure to find an order, or null if there was no failure.
* @return
*/
public @Nullable String diagnoseOrderingFailure() {
if (schedule.getActions().size() == orderedActionList.size()) {
return null;
}
StringBuilder s = new StringBuilder("Failed to assign an ordering:");
for (AbstractAction abstractAction : schedule.getActions()) {
int order = abstractAction.getOrder();
if (order == 0) {
s.append("\n\t" + abstractAction.getClass().getSimpleName() + " " + abstractAction + " has not been ordered");
}
}
return s.toString();
}
}
/**
* Assign the natural ordering to the dependency graph. Loops in the dependencies
* are broken by keeping track of previously ordered mappings.
* Although the nested schedule is not affected by this order, it is needed if we want a flat
* schedule, or if the nested scheduling fails and we need to fall back to the flat one.
*
* @return null if ok, or a diagnostic message describing the failure to assign an ordering.
*/
public static @Nullable String assignNaturalOrdering(@NonNull Schedule schedule) {
NaturalOrderer naturalOrderer = new NaturalOrderer(schedule);
return naturalOrderer.assignNaturalOrdering();
}
}