Getting 'Attempt to mutate notification' exception
Asked Answered
S

2

18

My goal is to implement blue coloring of keywords written by user into JTextPane. This is how my code look like:

private class DocumentHandler implements DocumentListener {

        @Override
        public void changedUpdate(DocumentEvent ev) {
        }

        @Override
        public void insertUpdate(DocumentEvent ev) {
            highlight();
        }

        @Override
        public void removeUpdate(DocumentEvent ev) {
            highlight();
        }

        private void highlight() {
            String code = codePane.getText();
            SimpleAttributeSet defSet = new SimpleAttributeSet();
            StyleConstants.setForeground(defSet, Color.BLACK);
            doc.setCharacterAttributes(0, code.length(), defSet, true);
            SimpleAttributeSet set = new SimpleAttributeSet();
            StyleConstants.setForeground(set, Color.BLUE);
            for (String keyword : keywords) {
                Pattern pattern = Pattern.compile(keyword + "(\\[\\])*");
                Matcher matcher = pattern.matcher(code);
                while (matcher.find()) {

                    //Just for test
                    System.out.print("Start index: " + matcher.start());
                    System.out.print(" End index: " + matcher.end());
                    System.out.println(" Found: " + matcher.group());

                    doc.setCharacterAttributes(matcher.start(), keyword.length(), set, true);
                }
            }
        }
    }

After typing anything into pane I get:

Exception in thread "AWT-EventQueue-0" java.lang.IllegalStateException: Attempt to mutate in notification
    at javax.swing.text.AbstractDocument.writeLock(AbstractDocument.java:1338)
    at javax.swing.text.DefaultStyledDocument.setCharacterAttributes(DefaultStyledDocument.java:500)
    at jnotepad.MainPanel$DocumentHandler.highlight(MainPanel.java:121)
    at jnotepad.MainPanel$DocumentHandler.insertUpdate(MainPanel.java:108)
    at javax.swing.text.AbstractDocument.fireInsertUpdate(AbstractDocument.java:202)
    at javax.swing.text.AbstractDocument.handleInsertString(AbstractDocument.java:749)

How to solve my problem? Maybe I should use something other than DocumentListener?

Scrutinize answered 4/3, 2013 at 16:45 Comment(0)
P
31

You need to invoke changes to the document from the Event Dispatcher Thread.

Try this:

private void highlight() {

    Runnable doHighlight = new Runnable() {
        @Override
        public void run() {
            // your highlight code
        }
    };       
    SwingUtilities.invokeLater(doHighlight);
}
Policewoman answered 4/3, 2013 at 17:2 Comment(5)
Issue is not that highlight() is executing from wrong thread. Rather, invokeLater(Runnable) fixes problem because it postpones execution until Document lock is released.Veer
@HollisWaite thats what I thought. If unlucky scheduling occurs, this will fail I guess?Delores
@MoritzSchmidt, unlucky scheduling should never occur. Queued EDT tasks run to completion before successors are processed.Veer
@HollisWaite Thats nice to know. Thank you. But the solution still looks like a hack to me.. Or is this really the cleanest solution?Delores
@MoritzSchmidt, I'm no expert on JTextPane internals. I think that's the most elegant solution but I could be wrong. Regardless, if you're building a moderately complex Swing application, you'll inevitably find yourself leveraging the 'invokeLater' idiom. In this particular case, the cleanest option is to use some third-party library (e.g. JSyntaxPane).Veer
C
4

I had the same problem, I solved it by using this:

expiration_timeTF.getDocument().addDocumentListener(
            new DocumentListener() {
                @Override
                public void removeUpdate(DocumentEvent e) {
                    System.out.println("remove");
                }

                private void assistDateText() {
                    Runnable doAssist = new Runnable() {
                        @Override
                        public void run() {
                            // when input "2013",will add to "2013-";when
                            // input "2013-10",will add to "2013-10-"
                            String input = expiration_timeTF.getText();
                            if (input.matches("^[0-9]{4}")) {
                                expiration_timeTF.setText(input + "-");
                            } else if (input.matches("^[0-9]{4}-[0-9]{2}")) {
                                expiration_timeTF.setText(input + "-");
                            }
                        }
                    };
                    SwingUtilities.invokeLater(doAssist);
                }

                @Override
                public void insertUpdate(DocumentEvent e) {
                    // System.out.println("insert");
                    assistDateText();
                }

                @Override
                public void changedUpdate(DocumentEvent e) {
                    // System.out.println("change");
                }
            });
Coo answered 22/10, 2013 at 1:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.