From 6c584418fcc99225a635058945209922cc37f541 Mon Sep 17 00:00:00 2001 From: Jordan Martinez Date: Thu, 2 Jun 2016 21:23:39 -0700 Subject: [PATCH] Make DAG implementation a base class for easier FP handling; account for possibility of a stored redo also having edges --- .../impl/nonlinear/DirectedAcyclicGraph.java | 25 +-- .../nonlinear/DirectedAcyclicGraphBase.java | 177 ++++++++++++++++ .../nonlinear/DirectedAcyclicGraphImpl.java | 196 ------------------ .../UnlimitedNonLinearChangeQueue.java | 15 +- 4 files changed, 196 insertions(+), 217 deletions(-) create mode 100644 undofx/src/main/java/org/fxmisc/undo/impl/nonlinear/DirectedAcyclicGraphBase.java delete mode 100644 undofx/src/main/java/org/fxmisc/undo/impl/nonlinear/DirectedAcyclicGraphImpl.java diff --git a/undofx/src/main/java/org/fxmisc/undo/impl/nonlinear/DirectedAcyclicGraph.java b/undofx/src/main/java/org/fxmisc/undo/impl/nonlinear/DirectedAcyclicGraph.java index 9711ba2..fbcf882 100644 --- a/undofx/src/main/java/org/fxmisc/undo/impl/nonlinear/DirectedAcyclicGraph.java +++ b/undofx/src/main/java/org/fxmisc/undo/impl/nonlinear/DirectedAcyclicGraph.java @@ -1,11 +1,8 @@ package org.fxmisc.undo.impl.nonlinear; import org.reactfx.SuspendableNo; -import org.reactfx.util.TriFunction; import java.util.List; -import java.util.function.BiFunction; -import java.util.function.Predicate; public interface DirectedAcyclicGraph { @@ -13,17 +10,21 @@ public interface DirectedAcyclicGraph { public boolean isPerformingAction(); - public BiFunction getUndoUpdater(); + public C updateUndo(C pushedChange, C undoableChange); - public Predicate getIsValidUndo(); + public boolean isUndoValid(C undo); - public TriFunction, C> getUndoUpdaterPostBubble(); + public BubbledResult bubbleRedo(C change); + + public C updateUndoPostBubble(C outdatedUndo, C bubblyUndo, BubbledResult bubbledResult); - public BiFunction getRedoUpdater(); + public C updateRedo(C pushedChange, C redoableChange); - public Predicate getIsValidRedo(); + public boolean isRedoValid(C redo); + + public BubbledResult bubbleUndo(C change); - public TriFunction, C> getRedoUpdaterPostBubble(); + public C updateRedoPostBubble(C outdatedRedo, C bubblyRedo, BubbledResult bubbledResult); public boolean close(); @@ -35,10 +36,6 @@ public interface DirectedAcyclicGraph { public NonLinearChangeQueue getLatestChangeSource(); - public BubbledResult bubbleRedo(C change); - - public BubbledResult bubbleUndo(C change); - public void updateChangesPostUndoBubble(C original, BubbledResult bubbledResult); public void updateRedosPostRedoBubble(C original, BubbledResult bubbledResult); @@ -49,7 +46,7 @@ public interface DirectedAcyclicGraph { public void remapEdges(C outdated, C updated); - public void testForDependencies(C from, C to); + public void testForDependency(C pushedChange, C storedChange); public void forget(List changes); diff --git a/undofx/src/main/java/org/fxmisc/undo/impl/nonlinear/DirectedAcyclicGraphBase.java b/undofx/src/main/java/org/fxmisc/undo/impl/nonlinear/DirectedAcyclicGraphBase.java new file mode 100644 index 0000000..e9fc598 --- /dev/null +++ b/undofx/src/main/java/org/fxmisc/undo/impl/nonlinear/DirectedAcyclicGraphBase.java @@ -0,0 +1,177 @@ +package org.fxmisc.undo.impl.nonlinear; + +import org.reactfx.Subscription; +import org.reactfx.SuspendableNo; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; + +public abstract class DirectedAcyclicGraphBase implements DirectedAcyclicGraph { + private final SuspendableNo performingAction = new SuspendableNo(); + public final SuspendableNo performingActionProperty() { return performingAction; } + public final boolean isPerformingAction() { return performingAction.get(); } + + private final List> queues = new ArrayList<>(1); + private final HashMap> toFromEdges = new HashMap<>(); + private final Subscription subscription; + + public DirectedAcyclicGraphBase() { + subscription = performingAction.values() + .filter(perfomingUpdate -> !perfomingUpdate) + .subscribe(ignore -> recalculateAllValidChanges()); + } + + /** + * Determines if the new pushed change alters or modifies the stored change. If so, an edge is added from + * the pushed change to the stored change + */ + protected abstract boolean firstDependsOnSecond(C pushedChange, C storedChange); + + /** + * Given a new change and an undoable change, either returns an updated version of the undoable change or + * the original undoable change if no update is needed + */ + public abstract C updateUndo(C pushedChange, C undoableChange); + + /** + * returns true if the given undo is mutually independent or could be bubbled + */ + public abstract boolean isUndoValid(C undo); + + public BubbledResult bubbleUndo(C change) { + List dependencies = toFromEdges.get(change); + return bubbleUndo0(change, dependencies); + } + + /** + * bubbles a "bubbly" undo. See also {@link BubbledResult}. + */ + protected abstract BubbledResult bubbleUndo0(C change, List dependencies); + + /** + * updates all changes after a "bubbly" undo has been bubbled to refactor the result's grounded change + * and to ignore the result's bubbled change + */ + public abstract C updateUndoPostBubble(C outdatedUndo, C bubblyUndo, BubbledResult bubbledResult); + + /** + * Given a new change and an redoable change, either returns an updated version of the redoable change or + * the original redoable change if no update is needed + */ + public abstract C updateRedo(C pushedChange, C redoableChange); + + /** + * returns true if the given redo is redoable in whole without bubbling it or in part after bubbling it + */ + public abstract boolean isRedoValid(C redo); + + public BubbledResult bubbleRedo(C change) { + return bubbledRedo0(change, toFromEdges.containsKey(change) ? toFromEdges.get(change) : Collections.emptyList()); + } + + /** + * bubbles a "bubbly" redo. See also {@link BubbledResult} + */ + protected abstract BubbledResult bubbledRedo0(C change, List dependencies); + + /** + * updates all redos after a "bubbly" undo/redo has been bubbled + */ + public abstract C updateRedoPostBubble(C outdatedRedo, C bubblyRedo, BubbledResult bubbledResult); + + public boolean close() { + if (queues.size() == 0) { + subscription.unsubscribe(); + return true; + } else { + return false; + } + } + + public final void registerQueue(NonLinearChangeQueue queue) { + if (!queue.getChanges().isEmpty()) { + throw new IllegalArgumentException("A ChangeQueue cannot be registered if it already has 1 or more changes."); + } + queues.add(queue); + } + + public final void unregisterQueue(NonLinearChangeQueue queue) { + forget(queue.getChanges()); + queues.remove(queue); + } + + private NonLinearChangeQueue latestChangeSource; + public final void setLatestChangeSource(NonLinearChangeQueue source) { + latestChangeSource = source; + } + public final NonLinearChangeQueue getLatestChangeSource() { + return latestChangeSource; + } + + public final void updateChangesPostUndoBubble(C original, BubbledResult bubbledResult) { + queues.forEach(q -> q.updateChangesPostBubble(original, bubbledResult)); + } + + public final void updateRedosPostRedoBubble(C original, BubbledResult bubbledResult) { + queues.forEach(q -> q.updateRedosPostChangeBubble(original, bubbledResult)); + } + + public final void updateQueueChanges(C pushedChange) { + queues.forEach(q -> q.updateChanges(pushedChange)); + } + + public final void recalculateAllValidChanges() { + queues.forEach(NonLinearChangeQueue::recalculateValidChanges); + } + + public final void remapEdges(C outdated, C updated) { + toFromEdges.forEach((key, list) -> { + if (key.equals(outdated)) { + toFromEdges.put(updated, toFromEdges.remove(outdated)); + } else { + int index = list.indexOf(outdated); + if (index != -1) { + list.set(index, updated); + } + } + }); + } + + public final void testForDependency(C pushedChange, C storedChange) { + if (firstDependsOnSecond(pushedChange, storedChange)) { + addEdgeFromTo(pushedChange, storedChange); + } + } + + private void addEdgeFromTo(C from, C to) { + if (toFromEdges.containsKey(to)) { + toFromEdges.get(to).add(from); + } else { + List list = new ArrayList<>(1); + list.add(from); + toFromEdges.put(to, list); + } + } + + public final void forget(List changes) { + changes.forEach(toFromEdges::remove); + toFromEdges.values().forEach(ls -> ls.removeAll(changes)); + } + + public final void removeRelatedEdgesOf(C change) { + toFromEdges.forEach((key, list) -> { + if (key.equals(change)) { + toFromEdges.remove(key); + } else { + list.remove(change); + } + }); + } + + public final boolean isMutuallyIndependent(C change) { + return toFromEdges.get(change).isEmpty(); + } + +} diff --git a/undofx/src/main/java/org/fxmisc/undo/impl/nonlinear/DirectedAcyclicGraphImpl.java b/undofx/src/main/java/org/fxmisc/undo/impl/nonlinear/DirectedAcyclicGraphImpl.java deleted file mode 100644 index 0d9e147..0000000 --- a/undofx/src/main/java/org/fxmisc/undo/impl/nonlinear/DirectedAcyclicGraphImpl.java +++ /dev/null @@ -1,196 +0,0 @@ -package org.fxmisc.undo.impl.nonlinear; - -import org.reactfx.Subscription; -import org.reactfx.SuspendableNo; -import org.reactfx.util.TriFunction; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.function.BiFunction; -import java.util.function.BiPredicate; -import java.util.function.Function; -import java.util.function.Predicate; - -public class DirectedAcyclicGraphImpl implements DirectedAcyclicGraph { - - private final SuspendableNo performingAction = new SuspendableNo(); - public final SuspendableNo performingActionProperty() { return performingAction; } - public final boolean isPerformingAction() { return performingAction.get(); } - - private final List> queues = new ArrayList<>(1); - private final HashMap> toFromEdges = new HashMap<>(); - private final Subscription subscription; - - private final BiPredicate firstDependsOnSecond; - - // Undos Functional Programming - private final BiFunction undoUpdater; - public final BiFunction getUndoUpdater() { return undoUpdater; } - - private final Predicate isValidUndo; - public final Predicate getIsValidUndo() { return isValidUndo; } - - private final BiFunction, BubbledResult> undoBubbler; - - private final TriFunction, C> undoUpdaterPostBubble; - public final TriFunction, C> getUndoUpdaterPostBubble() { return undoUpdaterPostBubble; } - - // Redos Functional Programming - private final BiFunction redoUpdater; - public final BiFunction getRedoUpdater() { return redoUpdater; } - - private final Predicate isValidRedo; - public final Predicate getIsValidRedo() { return isValidRedo; } - - private final Function> redoBubbler; - - private final TriFunction, C> redoUpdaterPostBubble; - public final TriFunction, C> getRedoUpdaterPostBubble() { return redoUpdaterPostBubble; } - - /** - * - * @param firstDependsOnSecond returns true if the first given change modifies/alters the second given change - * @param undoUpdater Given a new change and an undoable change, either returns an updated version of the undoable - * change or the original undoable change if no update is needed - * @param isValidUndo returns true if the given undo is mutually independent or could be bubbled - * @param undoBubbler bubbles a "bubbly" undo. See also {@link BubbledResult}. - * @param undoUpdaterPostBubble updates all changes after a "bubbly" undo has been bubbled to refactor the - * result's grounded change and to ignore the result's bubbled change - * @param redoUpdater Given a new change and an redoable change, either returns an updated version of the redoable - * change or the original redoable change if no update is needed - * @param isValidRedo returns true if the given redo is redoable in whole without bubbling it or in part after - * bubbling it - * @param redoBubbler bubbles a "bubbly" redo. See also {@link BubbledResult} - * @param redoUpdaterPostBubble updates all redos after a "bubbly" undo/redo has been bubbled - */ - public DirectedAcyclicGraphImpl( - BiPredicate firstDependsOnSecond, - BiFunction undoUpdater, - Predicate isValidUndo, - BiFunction, BubbledResult> undoBubbler, - TriFunction, C> undoUpdaterPostBubble, - BiFunction redoUpdater, - Predicate isValidRedo, - Function> redoBubbler, - TriFunction, C> redoUpdaterPostBubble) { - this.firstDependsOnSecond = firstDependsOnSecond; - - this.undoUpdater = undoUpdater; - this.isValidUndo = isValidUndo; - this.undoBubbler = undoBubbler; - this.undoUpdaterPostBubble = undoUpdaterPostBubble; - - this.redoUpdater = redoUpdater; - this.isValidRedo = isValidRedo; - this.redoBubbler = redoBubbler; - this.redoUpdaterPostBubble = redoUpdaterPostBubble; - - subscription = performingAction.values() - .filter(perfomingUpdate -> !perfomingUpdate) - .subscribe(ignore -> recalculateAllValidChanges()); - } - - public final boolean close() { - if (queues.size() == 0) { - subscription.unsubscribe(); - return true; - } else { - return false; - } - } - - public final void registerQueue(NonLinearChangeQueue queue) { - if (!queue.getChanges().isEmpty()) { - throw new IllegalArgumentException("A ChangeQueue cannot be registered if it already has 1 or more changes."); - } - queues.add(queue); - } - - public final void unregisterQueue(NonLinearChangeQueue queue) { - forget(queue.getChanges()); - queues.remove(queue); - } - - private NonLinearChangeQueue latestChangeSource; - public final void setLatestChangeSource(NonLinearChangeQueue source) { - latestChangeSource = source; - } - public final NonLinearChangeQueue getLatestChangeSource() { - return latestChangeSource; - } - - public final BubbledResult bubbleRedo(C change) { - return redoBubbler.apply(change); - } - - public final BubbledResult bubbleUndo(C change) { - List dependencies = toFromEdges.get(change); - return undoBubbler.apply(change, dependencies); - } - - public final void updateChangesPostUndoBubble(C original, BubbledResult bubbledResult) { - queues.forEach(q -> q.updateChangesPostBubble(original, bubbledResult)); - } - - public final void updateRedosPostRedoBubble(C original, BubbledResult bubbledResult) { - queues.forEach(q -> q.updateRedosPostChangeBubble(original, bubbledResult)); - } - - public final void updateQueueChanges(C pushedChange) { - queues.forEach(q -> q.updateChanges(pushedChange)); - } - - public final void recalculateAllValidChanges() { - queues.forEach(NonLinearChangeQueue::recalculateValidChanges); - } - - public final void remapEdges(C outdated, C updated) { - toFromEdges.forEach((key, list) -> { - if (key.equals(outdated)) { - toFromEdges.put(updated, toFromEdges.remove(outdated)); - } else { - int index = list.indexOf(outdated); - if (index != -1) { - list.set(index, updated); - } - } - }); - } - - public final void testForDependencies(C from, C to) { - if (firstDependsOnSecond.test(from, to)) { - addEdgeFromTo(from, to); - } - } - - private void addEdgeFromTo(C from, C to) { - if (toFromEdges.containsKey(to)) { - toFromEdges.get(to).add(from); - } else { - List list = new ArrayList<>(1); - list.add(from); - toFromEdges.put(to, list); - } - } - - public final void forget(List changes) { - changes.forEach(toFromEdges::remove); - toFromEdges.values().forEach(ls -> ls.removeAll(changes)); - } - - public final void removeRelatedEdgesOf(C change) { - toFromEdges.forEach((key, list) -> { - if (key.equals(change)) { - toFromEdges.remove(key); - } else { - list.remove(change); - } - }); - } - - public final boolean isMutuallyIndependent(C change) { - return toFromEdges.get(change).isEmpty(); - } - -} diff --git a/undofx/src/main/java/org/fxmisc/undo/impl/nonlinear/UnlimitedNonLinearChangeQueue.java b/undofx/src/main/java/org/fxmisc/undo/impl/nonlinear/UnlimitedNonLinearChangeQueue.java index be908c5..6aa84a0 100644 --- a/undofx/src/main/java/org/fxmisc/undo/impl/nonlinear/UnlimitedNonLinearChangeQueue.java +++ b/undofx/src/main/java/org/fxmisc/undo/impl/nonlinear/UnlimitedNonLinearChangeQueue.java @@ -61,7 +61,7 @@ public final void recalculateValidChanges() { List undos = getUndoChanges(); for (int i = undos.size() - 1; i >= 0; i--) { C possibleUndo = undos.get(i); - if (graph.getIsValidUndo().test(possibleUndo)) { + if (graph.isUndoValid(possibleUndo)) { undoNext = new IndexedChange<>(i, possibleUndo); break; } @@ -71,7 +71,7 @@ public final void recalculateValidChanges() { List redos = getRedoChanges(); for (int i = 0; i < redos.size(); i++) { C possibleRedo = redos.get(i); - if (graph.getIsValidRedo().test(possibleRedo)) { + if (graph.isRedoValid(possibleRedo)) { redoNext = new IndexedChange<>(i, possibleRedo); break; } @@ -111,6 +111,7 @@ public final C next() { } else { graph.updateRedosPostRedoBubble(redo, bubbledResult); changes.set(redoNext.getIndex(), bubbledResult.getGrounded()); + graph.remapEdges(redo, bubbledResult.getGrounded()); changes.add(currentPosition, bubbledChange); validRedo = bubbledChange; @@ -200,9 +201,9 @@ public final void push(C... changes) { public final void updateChanges(C pushedChange) { getUndoChanges().replaceAll(outdatedUndo -> { - C updatedUndo = graph.getUndoUpdater().apply(pushedChange, outdatedUndo); + C updatedUndo = graph.updateUndo(pushedChange, outdatedUndo); - graph.testForDependencies(pushedChange, updatedUndo); + graph.testForDependency(pushedChange, updatedUndo); if (outdatedUndo.equals(updatedUndo)) { return outdatedUndo; @@ -213,7 +214,7 @@ public final void updateChanges(C pushedChange) { }); getRedoChanges().replaceAll(outdatedRedo -> { - C updatedRedo = graph.getRedoUpdater().apply(pushedChange, outdatedRedo); + C updatedRedo = graph.updateRedo(pushedChange, outdatedRedo); return outdatedRedo.equals(updatedRedo) ? outdatedRedo @@ -227,7 +228,7 @@ public final void updateChangesPostBubble(C original, BubbledResult bubbledRe return outdatedChange; } - C updatedChange = graph.getUndoUpdaterPostBubble().apply(outdatedChange, original, bubbledResult); + C updatedChange = graph.updateUndoPostBubble(outdatedChange, original, bubbledResult); if (!outdatedChange.equals(updatedChange)) { graph.remapEdges(outdatedChange, updatedChange); @@ -245,7 +246,7 @@ public final void updateRedosPostChangeBubble(C original, BubbledResult bubbl return outdatedChange; } - C updatedChange = graph.getRedoUpdaterPostBubble().apply(outdatedChange, original, bubbledResult); + C updatedChange = graph.updateRedoPostBubble(outdatedChange, original, bubbledResult); return !outdatedChange.equals(updatedChange) ? updatedChange