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 partiallyReadyDatums; public LoopStart(AbstractAction loopActionEntry, List partiallyReadyDatums) { this.loopEntryAction = loopActionEntry; this.partiallyReadyDatums = partiallyReadyDatums; } public AbstractAction getLoopActionEntrance() { return loopEntryAction; } public List getPartiallyReadyDatums() { return partiallyReadyDatums; } } @NonNull private List loopStarts = new ArrayList<>(); private List unresolvedActions = new ArrayList<>(); @SuppressWarnings("null") @NonNull public List getLoopStarts() { return Collections.unmodifiableList(loopStarts); } protected void addLoopStart(LoopStart loopStart) { loopStarts.add(loopStart); } @SuppressWarnings("null") @NonNull public List 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 fullyProducedDatums = new HashSet(); /** * Set of AbstractDatums that have been partially-produced (at least one producing actions has been ordered). */ private final Set partiallyProducedDatums = new HashSet(); /** * Queue of AbstractDatums that have been fully produced but whose consumer have yet to be ordered. */ private final Deque fullyReadyDatums = new LinkedList(); /** * Set of MappingActions whose order has been determined. */ private final Set orderedActions = new HashSet(); private final List debugOrderedActionList = new ArrayList(); 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 partiallyReadyActions = new LinkedList(); 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 pReadyDatums = new ArrayList(); 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; } }