blob: 498a9b970c489b3a8b348142cb93cfb4de54cfa7 [file] [log] [blame]
package org.eclipse.qvtd.pivot.dependencies.utilities;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.qvtd.pivot.dependencies.AbstractAction;
import org.eclipse.qvtd.pivot.dependencies.AbstractDatum;
import org.eclipse.qvtd.pivot.dependencies.ClassDatum;
import org.eclipse.qvtd.pivot.dependencies.DependencyGraph;
import org.eclipse.qvtd.pivot.dependencies.PropertyDatum;
public class DependencyUtil
{
private enum Readyness {
NONE,
FULLY,
PARTIALlY,
}
public static class OrderingResult {
public static class LoopStart {
private AbstractAction loopEntryAction;
private List<AbstractDatum> partiallyReadyDatums;
public LoopStart(AbstractAction loopActionEntry, List<AbstractDatum> partiallyReadyDatums) {
this.loopEntryAction = loopActionEntry;
this.partiallyReadyDatums = partiallyReadyDatums;
}
public AbstractAction getLoopActionEntrance() {
return loopEntryAction;
}
public List<AbstractDatum> getPartiallyReadyDatums() {
return partiallyReadyDatums;
}
}
@NonNull
private List<LoopStart> loopStarts = new ArrayList<>();
private List<AbstractAction> unresolvedActions = new ArrayList<>();
@SuppressWarnings("null")
@NonNull
public List<LoopStart> getLoopStarts() {
return Collections.unmodifiableList(loopStarts);
}
protected void addLoopStart(LoopStart loopStart) {
loopStarts.add(loopStart);
}
@SuppressWarnings("null")
@NonNull
public List<AbstractAction> getUnresolvedActions() {
return Collections.unmodifiableList(unresolvedActions);
}
protected void addUnresolvedAction(AbstractAction action) {
unresolvedActions.add(action);
}
}
private static class NaturalOrderer
{
private final @NonNull DependencyGraph dependencyGraph;
/**
* Set of AbstractDatums that have been fully-produced (all producing actions have been ordered).
*/
private final Set<AbstractDatum> fullyProducedDatums = new HashSet<AbstractDatum>();
/**
* Set of AbstractDatums that have been partially-produced (at least one producing actions has been ordered).
*/
private final Set<AbstractDatum> partiallyProducedDatums = new HashSet<AbstractDatum>();
/**
* Queue of AbstractDatums that have been fully produced but whose consumer have yet to be ordered.
*/
private final Deque<AbstractDatum> fullyReadyDatums = new LinkedList<AbstractDatum>();
/**
* Set of MappingActions whose order has been determined.
*/
private final Set<AbstractAction> orderedActions = new HashSet<AbstractAction>();
private final List<AbstractAction> debugOrderedActionList = new ArrayList<AbstractAction>();
private final DependencyCaches depCaches;
private OrderingResult result;
public NaturalOrderer(@NonNull DependencyGraph dependencyGraph) {
this.dependencyGraph = dependencyGraph;
this.depCaches = new DependencyCaches(dependencyGraph);
}
/**
* Assign the next ordered index to abstractAction and update the produced datums accordingly.
*/
private void addOrderedAction(@NonNull AbstractAction abstractAction) {
assert !orderedActions.contains(abstractAction) : abstractAction.getClass().getSimpleName() + " " + abstractAction + " has already been ordered";
orderedActions.add(abstractAction);
debugOrderedActionList.add(abstractAction);
abstractAction.setOrder(orderedActions.size());
for (AbstractDatum abstractDatum : depCaches.getProductions(abstractAction)) {
partiallyProducedDatums.add(abstractDatum);
boolean fullyProduced = true;
for (AbstractAction producingAction : depCaches.getProducers(abstractDatum)) {
if (!hasBeenOrdered(producingAction)) {
fullyProduced = false;
break;
}
}
if (fullyProduced) {
boolean isNew = fullyProducedDatums.add(abstractDatum);
assert isNew : abstractDatum.getClass().getSimpleName() + " " + abstractDatum + " has already been fully produced";
fullyReadyDatums.add(abstractDatum);
}
}
}
private void initialDatumProcessing (AbstractDatum abstractDatum) {
// Assign all un-produced AbstractDatum (inputs) to the readyDatums Set.
if (depCaches.getProducers(abstractDatum).size() == 0) {
fullyProducedDatums.add(abstractDatum);
partiallyProducedDatums.add(abstractDatum);
fullyReadyDatums.add(abstractDatum);
}
}
private boolean hasBeenOrdered(AbstractAction action) {
return orderedActions.contains(action);
}
/**
* 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 an OrderingResult instance with the carried result
*/
public @NonNull OrderingResult assignNaturalOrdering() {
//
// Check that ordering has not yet been attempted
//
assert assertGraphIsNotOrdered(dependencyGraph);
/**
* Queue of AbstractDatums that have been partially produced but whose consumers have yet to be ordered.
*/
Deque<AbstractAction> partiallyReadyActions = new LinkedList<AbstractAction>();
result = new OrderingResult();
// DependencyGraph -> contains ClassDatums -> contain PropertyDatums
for (AbstractDatum abstractDatum : dependencyGraph.getDatums()) {
initialDatumProcessing(abstractDatum);
if (abstractDatum instanceof ClassDatum) {
for (PropertyDatum pDatum : ((ClassDatum) abstractDatum).getPropertyDatums()) {
initialDatumProcessing(pDatum);
}
}
}
int totalNumberOfActions = dependencyGraph.getActions().size();
//
// 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()
|| (orderedActions.size() < totalNumberOfActions) );) {
if (hasFullyReadyDatum) {
AbstractDatum readyDatum = fullyReadyDatums.pop();
for (AbstractAction candidateAction : depCaches.getConsumers(readyDatum)) {
if (!hasBeenOrdered(candidateAction)) {
Readyness actionIsReady = getReadyness(candidateAction);
switch (actionIsReady) {
case FULLY:
addOrderedAction(candidateAction);
break;
case PARTIALlY:
if (!partiallyReadyActions.contains(candidateAction)) {
partiallyReadyActions.add(candidateAction);
}
break;
case NONE:
// Some requisite hasn't been produced at all. When it is, it will be reanalyzed.
break;
}
}
}
}
else if (!partiallyReadyActions.isEmpty()) {
AbstractAction partiallyReadyAction = partiallyReadyActions.pop();
if (!hasBeenOrdered(partiallyReadyAction)) { // Might have been fully ordered while queued.
// Partially ready actions, processed at this point, are the start of the loop :
// They don't have fullyReady requirement, because the execution of the action
// will produce them
addOrderedAction(partiallyReadyAction);
processLoopStart(partiallyReadyAction);
}
} else {
// Actions not reached from initially ready input datums
// These action can:
// - either require an output never produced
// - or belong to a loop start whose inputs are never partially produced (e.g. they produce the input they consume)
//
// For the time being we will order them and add them to the result list of non reachable mappings
// Note that Action-Datum dependencies don't distinguish those dependencies belonging to branched code (eg. ifExps). Those dependencies
// are conditional and shouldn't prevent a mapping to be executed, even though, they are not partially produced. This
// situation prevents some special mappings which partially provide property datums which are further consumed.
for (AbstractAction action : dependencyGraph.getActions()) {
if (!hasBeenOrdered(action)) {
addOrderedAction(action);
processNonInvokedAction(action);
break; // Give another chance to pending actions to be ordered in the normal algorithm
}
}
}
}
//
// Check that ordering happened.
//
assert assertGraphIsOrdered(dependencyGraph);
return result;
}
private Readyness getReadyness(AbstractDatum datum) {
// note that the algorithm ensure that a fullyProduced datum is in the both partially and fully produced queues
if (!partiallyProducedDatums.contains(datum)) {
return Readyness.NONE;
}
else if (!fullyProducedDatums.contains(datum)) {
return Readyness.PARTIALlY;
} else {
return Readyness.FULLY;
}
}
private Readyness getReadyness(AbstractAction action) {
boolean allRequisitesFullyProduced = true;
boolean allRequisitesPartiallyProduced = true;
for (AbstractDatum abstractDatum : action.getRequisites()) {
Readyness datumReady = getReadyness(abstractDatum);
if (datumReady == Readyness.NONE) {
allRequisitesFullyProduced = false;
allRequisitesPartiallyProduced = false;
break;
}
else if (datumReady == Readyness.PARTIALlY) {
allRequisitesFullyProduced = false;
}
}
if (allRequisitesFullyProduced) {
return Readyness.FULLY;
}
else if (allRequisitesPartiallyProduced) {
return Readyness.PARTIALlY;
}
else {
return Readyness.NONE;
}
}
private void processLoopStart(AbstractAction partiallyReadyAction) {
List<AbstractDatum> pReadyDatums = new ArrayList<AbstractDatum>();
for (AbstractDatum pDatum : partiallyReadyAction.getRequisites()) {
if (getReadyness(pDatum) == Readyness.PARTIALlY) {
pReadyDatums.add(pDatum);
}
}
result.addLoopStart(new OrderingResult.LoopStart(partiallyReadyAction, pReadyDatums));
}
private void processNonInvokedAction(AbstractAction nonInvokedAction) {
result.addUnresolvedAction(nonInvokedAction);
}
}
/**
* 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.
*
*/
@NonNull
public static OrderingResult assignNaturalOrdering(@NonNull DependencyGraph dependencyGraph) {
NaturalOrderer naturalOrderer = new NaturalOrderer(dependencyGraph);
return naturalOrderer.assignNaturalOrdering();
}
private static boolean assertGraphIsNotOrdered(DependencyGraph dependencyGraph) {
for (AbstractAction abstractAction : dependencyGraph.getActions()) {
assert abstractAction.getOrder() == 0 : abstractAction.getClass().getSimpleName() + " " + abstractAction + " has been pre-ordered";
}
return true;
}
public static boolean assertGraphIsOrdered(DependencyGraph dependencyGraph) {
AbstractAction[] orderedChecks = new AbstractAction[dependencyGraph.getActions().size()];
for (AbstractAction abstractAction : dependencyGraph.getActions()) {
int order = abstractAction.getOrder();
assert order != 0 : abstractAction.getClass().getSimpleName() + " " + abstractAction + " has not been ordered";
assert orderedChecks[order-1] == null : "conflicting AbstractAction order";
orderedChecks[order-1] = abstractAction;
}
return true;
}
}