Recording and undoing live on the same object on purpose: a command already holds the journal
in order to record, so a separate recording object would mean threading a second handle
everywhere the first one goes. A separate interface costs nothing, and UndoManager is
that — the recording half, which is all the ~120 classes holding a handle need. This type is
for the few that also drive the stacks.
Plain Java on purpose. Nothing here hops to the JavaFX thread, so recording works in a plain
unit test and the journal could in time be used outside the GUI. Menu enablement subscribes
through org., which owns that hop.
Commands push from background tasks, so each operation holds this object's monitor for exactly as long as it touches the stacks:
Applying is inside the lock, in applyEdit, undo and redo alike, or two threads
could undo the same change, or an undo could land between a change being applied and
being recorded. The price is a constraint on BibChange#apply: it must write to the
model and return, never wait for another thread — one that waited for the JavaFX thread
would deadlock against a menu refresh already inside canUndo.
Everything else runs outside the lock: addEdit(String, Consumer) never holds it while
calling mutations, and notifyListeners runs after it is released, because a listener
may wait for the JavaFX thread.
Listeners are told that the stacks changed, never what changed, so they read the state they need when they run. Two threads recording concurrently may therefore notify in the opposite order to the one they pushed in, which does not matter: the worst case is being told twice about the same state.
| Modifier and Type | Class and Description |
|---|---|
| private class | JabRefUndoManager.
One command's claim on this library, ended by |
| private static record | JabRefUndoManager.
A change together with the identity of the position it occupies in the history. |
| Modifier and Type | Field and Description |
|---|---|
| private final ThreadLocal | |
| private final ThreadLocal | |
| private final CoalescingPolicy | coalescingPolicy
The rule for when a change continues the step on top of the stack. |
| private long | emptyStackId
The identity of the empty undo stack: the position the library stands at once everything still on the stack has been undone. |
| private static final int | LIMIT
Same depth javax.swing.undo.UndoManager defaulted to. |
| private final List | listeners
Notified after every change to either stack. |
| private static final Logger | |
| private static final long | NEVER_SAVED
A saved position no position will ever match, so the library counts as changed until it is saved again. |
| private long | nextId
Source of position ids. |
| private static final long | ORIGIN
The id of the empty stack before anything was ever pushed. |
| private final Deque | |
| private long | savedId
The position the library was last saved at, as an id rather than a count. |
| private boolean | stepFinished
Whether the step on top of the stack is finished, so that the next change starts a new one rather than continuing it. |
| private final Set | suspensions
The commands currently applying changes they have not yet handed over, in the order they started. |
| private final Deque |
| Access | Constructor and Description |
|---|---|
| public | |
| public | JabRefUndoManager(CoalescingPolicy coalescingPolicy)
For tests that want every change to be its own step, or a rule of their own. |
| Modifier and Type | Method and Description |
|---|---|
| public void | addEdit(BibChange change)
Implements org. Records a single change as its own undo step, or as part of the enclosing step when
called inside |
| public boolean | Returns: whether anything was recorded, for callers that report the outcome to the userImplements org. Runs |
| public void | addListener(Runnable listener)
Registers a listener, from any thread and at any time — including from inside another
listener, since |
| public ApplyResult | Returns: what was applied, and what was not — seeBibChange#applyImplements org. Performs |
| private ApplyResult | |
| public synchronized boolean | |
| public synchronized boolean | canUndo()
Whether there is a step to take back — the stack only, not whether a command is holding the library. |
| public void | |
| private synchronized long | currentPosition()
The identity of the position the history is at: the change on top of the undo stack, or
|
| public void | endStep()
Implements org. Ends the step being collected, so that the next change starts one of its own. |
| public synchronized boolean | |
| public boolean | |
| private static boolean | |
| private static boolean | |
| public void | markChanged()
Implements org. Marks the library as changed by something this journal cannot take back — a migration on load, a shared-database update, a setting written without being recorded. |
| public void | |
| private void | |
| private void | |
| private void | record(BibChange change, EditSource source)
Records |
| public Optional | |
| public synchronized Optional | suspendedBy()
The command currently holding this library, if one is: of those still writing, the one that started first. |
| public UndoSuspension | suspendUndo(String name)
Implements org. Suspends undo and redo for this library until the returned suspension is closed. |
| public Optional | Returns: what was undone — its name for the user, and what of it could not be applied — or empty if there was nothing to undo, or a command is holding the library (seesuspendUndo; suspendedBy tells the two apart). The name is taken inside the
monitor, and it is all that leaves the journal, so nothing outside reads the
stacks.Applies the inverse before moving the change across, so a change that throws stays undoable instead of vanishing from both stacks. |
| active | back to summary |
|---|---|
| private final ThreadLocal<@Nullable CompoundEdit> active Set exactly while a | |
| applying | back to summary |
|---|---|
| private final ThreadLocal<Boolean> applying Set while this journal is applying a change on this thread, in | |
| coalescingPolicy | back to summary |
|---|---|
| private final CoalescingPolicy coalescingPolicy The rule for when a change continues the step on top of the stack. | |
| emptyStackId | back to summary |
|---|---|
| private long emptyStackId The identity of the empty undo stack: the position the library stands at once everything still on the stack has been undone.
| |
| LIMIT | back to summary |
|---|---|
| private static final int LIMIT Same depth javax.swing.undo.UndoManager defaulted to. The stack retains the BibEntry objects of removed entries, so an unbounded one keeps every deletion of a session alive. | |
| listeners | back to summary |
|---|---|
| private final List<Runnable> listeners Notified after every change to either stack. Commands push from background tasks, so a listener that touches the UI is responsible for getting itself onto the right thread. Copy-on-write rather than a plain list under the lock: registration happens a handful of times while the window is built, notification happens on every edit. Paying for a copy on the rare operation keeps the frequent one lock-free, and publishing through this list is what makes a listener registered on one thread visible to a push on another. | |
| LOGGER | back to summary |
|---|---|
| private static final Logger LOGGER | |
| NEVER_SAVED | back to summary |
|---|---|
| private static final long NEVER_SAVED A saved position no position will ever match, so the library counts as changed until it is
saved again. | |
| nextId | back to summary |
|---|---|
| private long nextId Source of position ids. Only ever increments, so an id identifies one position for the lifetime of this manager: a position that history has discarded can never be matched by a later one that happens to sit at the same depth. | |
| ORIGIN | back to summary |
|---|---|
| private static final long ORIGIN The id of the empty stack before anything was ever pushed. Distinct from every id
| |
| redoStack | back to summary |
|---|---|
| private final Deque<JabRefUndoManager. | |
| savedId | back to summary |
|---|---|
| private long savedId The position the library was last saved at, as an id rather than a count. Compared by
| |
| stepFinished | back to summary |
|---|---|
| private boolean stepFinished Whether the step on top of the stack is finished, so that the next change starts a new one rather than continuing it. Guarded by this object's monitor. Set by anything that ends a user action — a recorded block, an undo, a redo, a caller
saying so through | |
| suspensions | back to summary |
|---|---|
| private final Set<JabRefUndoManager. The commands currently applying changes they have not yet handed over, in the order they started. Guarded by this object's monitor. A collection rather than a flag: two background commands can write to one library at the same time, and undo may only return once both have handed over. Ordered, so the message names the command the user has been waiting on longest — and one that has finished is no longer in it to be named. | |
| undoStack | back to summary |
|---|---|
| private final Deque<JabRefUndoManager. | |
| JabRefUndoManager | back to summary |
|---|---|
| public JabRefUndoManager() | |
| JabRefUndoManager | back to summary |
|---|---|
| public JabRefUndoManager(CoalescingPolicy coalescingPolicy) For tests that want every change to be its own step, or a rule of their own. | |
| addEdit | back to summary |
|---|---|
| public void addEdit(BibChange change) Implements org. Records a single change as its own undo step, or as part of the enclosing step when
called inside A lone change needs no group and therefore no name: a
| |
| addEdit | back to summary |
|---|---|
| public boolean addEdit(String name, Consumer<CompoundEdit> mutations) Implements org. Runs Nothing is pushed if no change was recorded, so callers need not check first — which is the point: a command cannot collect changes and then forget to hand them over. A nested call becomes a nested A block that fails part-way through still hands over what it managed to change. The
library is modified either way at that point, and the difference between the two paths
is only whether the user can take it back. The failure itself is not swallowed: it
propagates once the step is on the stack. This holds for anything thrown, an
| |
| addListener | back to summary |
|---|---|
| public void addListener(Runnable listener) Registers a listener, from any thread and at any time — including from inside another
listener, since | |
| applyEdit | back to summary |
|---|---|
| public ApplyResult applyEdit(BibChange change, EditSource source) Implements org. Performs The counterpart to The single acquisition is the point. Applying and then recording as two operations leaves
a window in which the library holds the change and the journal does not: an Inside an
| |
| applying | back to summary |
|---|---|
| private ApplyResult applying(Supplier<ApplyResult> apply) | |
| canRedo | back to summary |
|---|---|
| public synchronized boolean canRedo() | |
| canUndo | back to summary |
|---|---|
| public synchronized boolean canUndo() Whether there is a step to take back — the stack only, not whether a command is holding the library. Menu enablement binds to this, and a disabled menu item swallows its accelerator, so
answering false during a suspension would leave Ctrl+Z doing nothing at all. | |
| clear | back to summary |
|---|---|
| public void clear() | |
| currentPosition | back to summary |
|---|---|
| private synchronized long currentPosition() The identity of the position the history is at: the change on top of the undo stack, or
| |
| endStep | back to summary |
|---|---|
| public void endStep() Implements org. Ends the step being collected, so that the next change starts one of its own. The journal cannot see where one user action ends and the next begins — a field editor records one change per keystroke either way — so the caller that knows says so. Ending a step nobody is continuing does nothing.
| |
| hasChanged | back to summary |
|---|---|
| public synchronized boolean hasChanged() Whether the library differs from the last saved position. Compares which position the history is at, not how far it has travelled. A count cannot
answer this: pushing a change clears the redo stack, so two different histories reach the
same count — save, undo, then make a different edit, and an edit balance is back where it
started although the library now holds something that was never saved. That reported a
modified library as saved, and Ids are never reused, so a saved position discarded by a redo-stack clear can never be
matched again — correct, because the library can no longer be brought back to it. A
position dropped by the | |
| isApplying | back to summary |
|---|---|
| public boolean isApplying() Whether this journal is applying a change on this thread right now. For listeners that a model write reaches synchronously, on the applying thread, while
| |
| isEmptyStep | back to summary |
|---|---|
| private static boolean isEmptyStep(BibChange change) Whether A set that collected nothing is not an undo step: pushing one would enable Undo and let the next Ctrl+Z consume a no-op instead of the user's previous edit. Applying one changes nothing either, so both entry points skip it for the same reason. | |
| isNoOp | back to summary |
|---|---|
| private static boolean isNoOp(BibChange change) Whether a merged run ended where it started. | |
| markChanged | back to summary |
|---|---|
| public void markChanged() Implements org. Marks the library as changed by something this journal cannot take back — a migration on load, a shared-database update, a setting written without being recorded. The stack is left alone; what moves is the saved position, to one no position can equal. Undoing every step therefore no longer means "back to what was saved", which is the truth: the change is still there. Saving is what clears it. | |
| markUnchanged | back to summary |
|---|---|
| public void markUnchanged() Marks the current position as saved. Notifies, like every other move of the saved position: a listener deriving the modified
marker from | |
| notifyListeners | back to summary |
|---|---|
| private void notifyListeners() Runs every listener even if one of them throws. By the time this is called the change is on the stack and applied to the library, so letting a listener's failure out would report a completed operation as failed — and would silently deprive the remaining listeners of a notification they have no other way to get. | |
| push | back to summary |
|---|---|
| private void push(BibChange change) Puts Callers hold this object's monitor: the stack transition is what the monitor protects,
and | |
| record | back to summary |
|---|---|
| private void record(BibChange change, EditSource source) Records A merged step takes a new position id, because the library is somewhere it has not been before: keeping the old id would let a save taken mid-run report the library as unchanged after the rest of the run was typed. For the same reason a step is never merged into while it is the saved position: the user would lose the saved state from the stack, and undoing once would take back more than they typed since saving. A run that ends where it started leaves nothing to undo — typing a word and deleting it again — so the step goes rather than sitting on the stack doing nothing. Callers hold this object's monitor. | |
| redo | back to summary |
|---|---|
| public Optional | |
| suspendedBy | back to summary |
|---|---|
| public synchronized Optional The command currently holding this library, if one is: of those still writing, the one that started first. For the Undo and Redo actions, which have to tell "nothing to undo" from "not while this is
running" — both of which make | |
| suspendUndo | back to summary |
|---|---|
| public UndoSuspension suspendUndo(String name) Implements org. Suspends undo and redo for this library until the returned suspension is closed. Opening and closing are both stack changes as far as observers are concerned — enablement has to fall while a command holds the library and rise again afterwards — so both notify, and both do so after the monitor is released.
| |
| undo | back to summary |
|---|---|
| public Optional Applies the inverse before moving the change across, so a change that throws stays undoable instead of vanishing from both stacks. A change that refuses — because the library no longer holds what it recorded — does move across: the step was consumed, retrying it would refuse again, and the caller is told through the result that not all of it applied.
| |
close.
| Access | Constructor and Description |
|---|---|
| private |
| Modifier and Type | Method and Description |
|---|---|
| public void | |
| private String |
| closed | back to summary |
|---|---|
| private boolean closed | |
| name | back to summary |
|---|---|
| private final String name | |
| Suspension | back to summary |
|---|---|
| private Suspension(String name) | |
| close | back to summary |
|---|---|
| public void close() Implements org. Doc from org. Ends the suspension. Never throws, so it is safe in a
| |
| name | back to summary |
|---|---|
| private String name() | |
The id lives here rather than on the change because a BibChange is a value describing a
modification, and a position in this journal is bookkeeping that only this class needs.
Putting it on the change would also cost inverted() its involution: it would have to
either copy the id, giving two distinct positions the same identity, or drop it.
| Modifier and Type | Field and Description |
|---|---|
| private final BibChange | |
| private final long |
| Access | Constructor and Description |
|---|---|
| private |
| Modifier and Type | Method and Description |
|---|---|
| public BibChange | |
| public final boolean | equals(Object o)
Implements abstract java. Indicates whether some other object is "equal to" this one. |
| public final int | |
| public long | |
| public final String | toString()
Implements abstract java. Returns a string representation of the record. |
| change | back to summary |
|---|---|
| private final BibChange change Record Component accessed by change(). | |
| id | back to summary |
|---|---|
| private final long id Record Component accessed by id(). | |
| UndoJournalEntry | back to summary |
|---|---|
| private UndoJournalEntry(long id, BibChange change) | |
| change | back to summary |
|---|---|
| public BibChange change() Record Component getter of change. | |
| equals | back to summary |
|---|---|
| public final boolean equals(Object o) Implements abstract java. Doc from java. Indicates whether some other object is "equal to" this one. In addition
to the general contract of
R copy = new R(r.c1(), r.c2(), ..., r.cn());
then it must be the case that r.equals(copy).
| |
| hashCode | back to summary |
|---|---|
| public final int hashCode() Implements abstract java. Doc from java. Returns a hash code value for the record.
Obeys the general contract of | |
| id | back to summary |
|---|---|
| public long id() Record Component getter of id. | |
| toString | back to summary |
|---|---|
| public final String toString() Implements abstract java. Doc from java. Returns a string representation of the record.
In accordance with the general contract of In addition to this general contract, record classes must further participate in the invariant that any two records which are equal must produce equal strings. This invariant is necessarily relaxed in the rare case where corresponding equal component values might fail to produce equal strings for themselves. | |