Skip to content

Commit

Permalink
Make DAG implementation a base class for easier FP handling; account …
Browse files Browse the repository at this point in the history
…for possibility of a stored redo also having edges
  • Loading branch information
JordanMartinez committed Jun 3, 2016
1 parent e49ae99 commit 6c58441
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 217 deletions.
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
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<C> {

public SuspendableNo performingActionProperty();

public boolean isPerformingAction();

public BiFunction<C, C, C> getUndoUpdater();
public C updateUndo(C pushedChange, C undoableChange);

public Predicate<C> getIsValidUndo();
public boolean isUndoValid(C undo);

public TriFunction<C, C, BubbledResult<C>, C> getUndoUpdaterPostBubble();
public BubbledResult<C> bubbleRedo(C change);

public C updateUndoPostBubble(C outdatedUndo, C bubblyUndo, BubbledResult<C> bubbledResult);

public BiFunction<C, C, C> getRedoUpdater();
public C updateRedo(C pushedChange, C redoableChange);

public Predicate<C> getIsValidRedo();
public boolean isRedoValid(C redo);

public BubbledResult<C> bubbleUndo(C change);

public TriFunction<C ,C, BubbledResult<C>, C> getRedoUpdaterPostBubble();
public C updateRedoPostBubble(C outdatedRedo, C bubblyRedo, BubbledResult<C> bubbledResult);

public boolean close();

Expand All @@ -35,10 +36,6 @@ public interface DirectedAcyclicGraph<C> {

public NonLinearChangeQueue<C> getLatestChangeSource();

public BubbledResult<C> bubbleRedo(C change);

public BubbledResult<C> bubbleUndo(C change);

public void updateChangesPostUndoBubble(C original, BubbledResult<C> bubbledResult);

public void updateRedosPostRedoBubble(C original, BubbledResult<C> bubbledResult);
Expand All @@ -49,7 +46,7 @@ public interface DirectedAcyclicGraph<C> {

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<C> changes);

Expand Down
Original file line number Diff line number Diff line change
@@ -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<C> implements DirectedAcyclicGraph<C> {
private final SuspendableNo performingAction = new SuspendableNo();
public final SuspendableNo performingActionProperty() { return performingAction; }
public final boolean isPerformingAction() { return performingAction.get(); }

private final List<NonLinearChangeQueue<C>> queues = new ArrayList<>(1);
private final HashMap<C, List<C>> 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<C> bubbleUndo(C change) {
List<C> dependencies = toFromEdges.get(change);
return bubbleUndo0(change, dependencies);
}

/**
* bubbles a "bubbly" undo. See also {@link BubbledResult}.
*/
protected abstract BubbledResult<C> bubbleUndo0(C change, List<C> 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<C> 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<C> 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<C> bubbledRedo0(C change, List<C> dependencies);

/**
* updates all redos after a "bubbly" undo/redo has been bubbled
*/
public abstract C updateRedoPostBubble(C outdatedRedo, C bubblyRedo, BubbledResult<C> bubbledResult);

public boolean close() {
if (queues.size() == 0) {
subscription.unsubscribe();
return true;
} else {
return false;
}
}

public final void registerQueue(NonLinearChangeQueue<C> 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<C> queue) {
forget(queue.getChanges());
queues.remove(queue);
}

private NonLinearChangeQueue<C> latestChangeSource;
public final void setLatestChangeSource(NonLinearChangeQueue<C> source) {
latestChangeSource = source;
}
public final NonLinearChangeQueue<C> getLatestChangeSource() {
return latestChangeSource;
}

public final void updateChangesPostUndoBubble(C original, BubbledResult<C> bubbledResult) {
queues.forEach(q -> q.updateChangesPostBubble(original, bubbledResult));
}

public final void updateRedosPostRedoBubble(C original, BubbledResult<C> 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<C> list = new ArrayList<>(1);
list.add(from);
toFromEdges.put(to, list);
}
}

public final void forget(List<C> 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();
}

}
Loading

0 comments on commit 6c58441

Please sign in to comment.