0

I'll apologize in advance for the lack of specificity on this, but I am unsure how this is being caused.

We have a XMLDocument that extends javax.swing.text.DefaultStyledDocument

If we make rapid undo/redo calls (say, holding ctrl+z or ctrl+shift+z), the AWT EventQueue thread throws an exception:

javax.swing.text.BadLocationException: Length must be positive
getText():810, AbstractDocument {javax.swing.text}
getText():135, GlyphView {javax.swing.text}
getSpan():60, GlyphPainter1 {javax.swing.text}
getPreferredSpan():592, GlyphView {javax.swing.text}
getPreferredSpan():732, FlowView$LogicalView {javax.swing.text}
calculateMinorAxisRequirements():233, FlowView {javax.swing.text}
calculateMinorAxisRequirements():724, ParagraphView {javax.swing.text}
checkRequests():935, BoxView {javax.swing.text}
getMinimumSpan():568, BoxView {javax.swing.text}
calculateMinorAxisRequirements():903, BoxView {javax.swing.text}
checkRequests():935, BoxView {javax.swing.text}
setSpanOnAxis():343, BoxView {javax.swing.text}
layout():708, BoxView {javax.swing.text}
setSize():397, BoxView {javax.swing.text}
setSize():1714, BasicTextUI$RootView {javax.swing.plaf.basic}
modelToView():1046, BasicTextUI {javax.swing.plaf.basic}
repaintNewCaret():1311, DefaultCaret {javax.swing.text}
run():1290, DefaultCaret$1 {javax.swing.text}
dispatch():312, InvocationEvent {java.awt.event}
dispatchEventImpl():733, EventQueue {java.awt}
access$200():103, EventQueue {java.awt}
run():694, EventQueue$3 {java.awt}
run():692, EventQueue$3 {java.awt}
doPrivileged():-1, AccessController {java.security}
doIntersectionPrivilege():76, ProtectionDomain$1 {java.security}
dispatchEvent():703, EventQueue {java.awt}
pumpOneEventForFilters():242, EventDispatchThread {java.awt}
pumpEventsForFilter():161, EventDispatchThread {java.awt}
pumpEventsForHierarchy():150, EventDispatchThread {java.awt}
pumpEvents():146, EventDispatchThread {java.awt}
pumpEvents():138, EventDispatchThread {java.awt}
run():91, EventDispatchThread {java.awt}

Now, obviously this is being called from the AWT Update thread.

The actual element it's throwing up on is:

[4] = {javax.swing.text.AbstractDocument$LeafElement@6621}"LeafElement(content) 223,217\n"
p0 = {javax.swing.text.GapContent$StickyPosition@6652}"223"
p1 = {javax.swing.text.GapContent$StickyPosition@6655}"217"
this$0 = {com.timetra.nms.client.gui.script.editor.document.XmlApiDocument@6211}
parent = {javax.swing.text.AbstractDocument$BranchElement@6585}"BranchElement(paragraph) 191,238\n"
attributes = {javax.swing.text.StyleContext$SmallAttributeSet@6656}"{family=Courier,size=12,foreground=java.awt.Color[r=0,g=0,b=0],}"
this$0 = {com.timetra.nms.client.gui.script.editor.document.XmlApiDocument@6211}

(Taken from intelliJ debugger).

as you can see p0 and p1 are incorrect. but also, this element shouldn't be there since the [3] and [5] elements in the array match the range:

[3] = {javax.swing.text.AbstractDocument$LeafElement@6620}"LeafElement(content) 215,223\n"
[4] = {javax.swing.text.AbstractDocument$LeafElement@6621}"LeafElement(content) 223,217\n"
[5] = {javax.swing.text.AbstractDocument$LeafElement@6622}"LeafElement(content) 223,237\n"

I assume that I'm missing a synchronization, or a SwingUtilities.runLater(...) invocation, but I have no idea where, I've put them everywhere I can think of in the Undo/Redo classes that we use.

Edit:

This is how edits are being added to the UndoManager:

public void undoableEditHappened(final UndoableEditEvent ev)
    {
        UndoableEdit edit = ev.getEdit();
        // Include this method to ignore syntax changes
        // To reduce memory consumption, do this check before adding runnable.
        if (edit instanceof AbstractDocument.DefaultDocumentEvent &&
            ((AbstractDocument.DefaultDocumentEvent) edit).getType() ==
              AbstractDocument.DefaultDocumentEvent.EventType.CHANGE)
        {
            return;
        }

        try {
            UndoableEdit temp = ev.getEdit();
            undoManager.addEdit(temp);
            undoAction.updateState();
            redoAction.updateState();
            setChanged(true);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

Edit 2: I found the majority of the issues I saw were caused by a function that is on the invokeLater() runnable which attempts to syntax highlight the text after an undo/redo event. I'm concerned that even though it's on the EventQueue it's not in sync. Is there a priority order or something for the order in which these events are fired?

Doug Edey
  • 224
  • 2
  • 10
  • Nobody can help you without seeing your code, and I suspect it's way too long to post here. You will have to set an exception breakpoint and try to catch it in the act. – Jim Garrison Sep 03 '14 at 21:41
  • I can only get the exception from the AWT EventQueue thread, and that's where I got the information I posted above, there's no other exception thrown. I know it's a long shot without a distinct testcase, I'm working to see if I can recreate it (I'm working on a massively old piece of code that no-one knows about). What I'm looking for is pointers. – Doug Edey Sep 03 '14 at 22:14
  • You can try to wrap all the undo/redo calls in the SwingUtilities.invokeLater(). One more point is to check how the UndoableEdits are stored. Do they have copy of DOM structure or references to the structure. Copy is safer – StanislavL Sep 04 '14 at 05:29
  • @StanislavL I'm not sure what you mean, I've added the code for the undoable edit listener. – Doug Edey Sep 04 '14 at 18:48
  • You must add the Change events to the UndoManager. In opposite case Document structure is changed but you try to undo with old structure. You can merge multiple UndoableEdits in one see http://java-sl.com/tip_merge_undo_edits.html – StanislavL Sep 05 '14 at 05:26
  • @StanislavL: Can you add this as answer? – trashgod Sep 05 '14 at 16:21

3 Answers3

2

You are correct to infer the possibility of incorrect synchronization. The exception may occur on the EDT due to an errant Document update on another thread. Some methods of certain text components are no longer marked as thread safe in recent Java versions. Look for EDT violations, as suggested in the articles cited here. For an initial check, CheckThreadViolationRepaintManager is the easier approach.

Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • I used the CheckThreadViolationRepaintManager method first, however this didn't show anything, so I went with the aspect route and found several places were our code wasn't threadsafe, but nothing showed up in the area with the document. – Doug Edey Sep 04 '14 at 18:33
  • Sorry it didn't pan out; I'll defer to @StanislavL about your `UndoableEditEvent` handler. – trashgod Sep 05 '14 at 16:23
0

You must add the Change events to the UndoManager. In opposite case Document structure is changed but you try to undo with old structure. You can merge multiple UndoableEdits in one see java-sl.com/tip_merge_undo_edits.html

StanislavL
  • 56,971
  • 9
  • 68
  • 98
0

I worked out a solution of adding a timer to the update of the syntax highlighting that was being applied to the text box after an edit. If the text box is updated within a 200ms timer, the update is queued and applied after the syntax highlighting occurs.

Doug Edey
  • 224
  • 2
  • 10