Fehler "java.lang.IllegalStateException: Attempt to mutate in notification"

Ich habe ein JTextField in einer Anwendung, dessen Textinhalt ich wie folgt prüfen möchte (siehe den Code weiter unten). Das bedeutet, wenn die Methode parseTerm false zurückliefert, dann möchte ich das letzte char des Textes im JTextFrame entfernen. Wenn ich dies aber mit isplay.setText(termWithoutLastChar) tun möchte, bekomme ich folgenden Fehler - „java.lang.IllegalStateException: Attempt to mutate in notification“

Gibt es da eine überhaupt eine Möglichkeit, aus dem Eventlistener heraus auf das Textfeld zuzugreifen?

JTextField display = new JTextField();  
        
display.getDocument().addDocumentListener(new DocumentListener() {
            
        	@Override
            public void insertUpdate(DocumentEvent e) {
                // Wird aufgerufen, wenn Text eingefügt wird
                handleTextChange();
            }

            @Override
            public void removeUpdate(DocumentEvent e) {
                // Wird aufgerufen, wenn Text gelöscht wird
                //handleTextChange();
            }

            @Override
            public void changedUpdate(DocumentEvent e) {
                // Wird aufgerufen, wenn sich Attribute des Dokuments ändern (nicht für PlainDocument)
            	//handleTextChange();
            }
           

            private void handleTextChange() {
            	String term = display.getText();
                boolean res = parseTerm(term);
                if(! res) {
				
				//An dieser Stelle will ich den String term prüfen.
            	    StringBuilder stringBuilder = new StringBuilder(term);
	            stringBuilder.deleteCharAt(term.length() - 1);
	            String termWithoutLastChar = stringBuilder.toString();
	            display.setText(termWithoutLastChar);
                
                }
            }

            //Dummy-Methode.
			private boolean parseTerm(String term) { 
		    	return false;
			}
        });
        
        

In der API Doc von AbstractDocument steht:
Diese Klasse implementiert einen Sperrmechanismus für das Dokument. Es ermöglicht mehrere Leser oder einen Autor, und Autoren müssen warten, bis alle Beobachter des Dokuments über Änderungen informiert wurden.

Es müssen also erst alle Beobachter bescheid wissen, erst dann darfst Du die nächste Änderung vornehmen.
So wie es aussieht, versuchst Du aber bereits die nächste Änderung am Dokument, während die Beobachter informiert werden bzw. die Methode dazu durchläuft.

Schau mal im Exception-Log deiner IDE, in welcher Zeile die Exception geworfen wird.
Ich vermute mal irgendwo hier: display.setText(termWithoutLastChar);

Man kann den relevanten Teil nach der Abarbeitung des Events ausführen lassen, mit SwingUtilities.invokeLater. Der Grund warum die Exception kommt ist… wenn das Dokument verändert wird, und man darauf reagiert, indem man das Dokument verändert, dann… reagiert man darauf ja wieder und wieder…

Man muss sich also trotzdem noch überlegen, wie das aufhören soll. Eine einfache (pragmatische) Möglichkeit ist, in einer boolean-Variable zu speichern, ob man an der Veränderung „selbst schuld“ ist, und dann ggf. den Event zu ignorieren…

package bytewelt;

import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

public class ModifyText
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(() -> createAndShowGui());
    }

    private static void createAndShowGui()
    {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JTextField display = new JTextField();

        display.getDocument().addDocumentListener(new DocumentListener()
        {
            private boolean currentlyModifying = false;

            @Override
            public void insertUpdate(DocumentEvent e)
            {
                // Wird aufgerufen, wenn Text eingefügt wird
                handleTextChange();
            }

            @Override
            public void removeUpdate(DocumentEvent e)
            {
                // Wird aufgerufen, wenn Text gelöscht wird
                // handleTextChange();
            }

            @Override
            public void changedUpdate(DocumentEvent e)
            {
                // Wird aufgerufen, wenn sich Attribute des Dokuments ändern
                // (nicht für PlainDocument)
                // handleTextChange();
            }

            private void handleTextChange()
            {
                if (currentlyModifying)
                {
                    return;
                }
                String term = display.getText();
                boolean res = parseTerm(term);
                if (!res)
                {
                    SwingUtilities.invokeLater(() -> modifyText(term));
                }
            }

            private void modifyText(String term)
            {
                currentlyModifying = true;

                // Irgendeine veränderung am text vornehmen:
                display.setText(term + "\nmodified");

                currentlyModifying = false;

            }

            // Dummy-Methode.
            private boolean parseTerm(String term)
            {
                return false;
            }
        });

        f.getContentPane().add(display);
        f.setSize(500, 500);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }
}
1 Like

Vielen Dank! Ich habs hinbekommen.