Search code examples
javajavafxjavafx-8concurrentmodification

ConcurrentException when trying clear JavaFX ObservableMap


I'm adding some accelerators to my JavaFX 8 applications and I'm fighting with a ConcurrentModificationException when I try clear the accelerators of the scene. Let me explain, instead use one scene for each dialog, I've created a pseudo dialog, it is simply a pane with overlay. When the pane is added the accelerators of the old pane are removed and the accelerators of the new one are added. When the dialog is closed the old accelerators are restored:

public abstract class DialogController{

    private Map<KeyCombination, Runnable> accelerators;
    .
    .
    .

    protected final void loadMainPane() throws IOException {
        .
        .
        .
        pane.sceneProperty().addListener(new ChangeListener<Scene>() {
            @Override
            public void changed(ObservableValue<? extends Scene> observableValue, Scene oldScene, Scene newScene) {
                if (newScene != null)
                    enableAccelerators();
                else
                    disableAccelerators(oldScene);
            }
        });
    }

    protected void enableAccelerators() {
        accelerators = new HashMap<KeyCombination, Runnable>(pane.getScene().getAccelerators());
        pane.getScene().getAccelerators().clear();
        pane.getScene().getAccelerators().put(new KeyCodeCombination(KeyCode.ENTER, KeyCombination.ALT_DOWN), new Runnable() {
            @Override
            public void run() {
                submit(null);
            }
        });
    }

    protected void disableAccelerators(Scene scene) {
        scene.getAccelerators().putAll(accelerators);
    }
}

When a subclass need add new accelerators they only need override enableAccelerators() method, call super and add the new accelerators desired. However, sometimes, when the enableAccelerators try clear accelerators of the scene, this exceptions is thrown:

java.util.ConcurrentModificationException: null
at java.util.HashMap$HashIterator.nextNode(HashMap.java:1429)
at java.util.HashMap$EntryIterator.next(HashMap.java:1463)
at java.util.HashMap$EntryIterator.next(HashMap.java:1461)
at com.sun.javafx.collections.ObservableMapWrapper$ObservableEntrySet$1.next(ObservableMapWrapper.java:588)
at com.sun.javafx.collections.ObservableMapWrapper$ObservableEntrySet$1.next(ObservableMapWrapper.java:576)
at com.sun.javafx.scene.KeyboardShortcutsHandler.processAccelerators(KeyboardShortcutsHandler.java:340)
at com.sun.javafx.scene.KeyboardShortcutsHandler.dispatchBubblingEvent(KeyboardShortcutsHandler.java:168)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
at javafx.event.Event.fireEvent(Event.java:204)
at javafx.scene.Scene$KeyHandler.process(Scene.java:3949)
at javafx.scene.Scene$KeyHandler.access$2100(Scene.java:3896)
at javafx.scene.Scene.impl_processKeyEvent(Scene.java:2036)
at javafx.scene.Scene$ScenePeerListener.keyEvent(Scene.java:2493)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:170)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:123)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleKeyEvent(GlassViewEventHandler.java:197)
at com.sun.glass.ui.View.handleKeyEvent(View.java:517)
at com.sun.glass.ui.View.notifyKey(View.java:927)
at com.sun.glass.ui.gtk.GtkApplication._runLoop(Native Method)
at com.sun.glass.ui.gtk.GtkApplication.access$200(GtkApplication.java:48)
at com.sun.glass.ui.gtk.GtkApplication$6$1.run(GtkApplication.java:149)
at java.lang.Thread.run(Thread.java:745)

I suspect my code is clearing the accelerators while JavaFX is iterating it. I tryied put the clearing part inside a synchronized block, but it didn't work:

protected void enableAccelerators() {
    accelerators = new HashMap<KeyCombination, Runnable>(pane.getScene().getAccelerators());
    synchronized (pane.getScene().getAccelerators()){
        pane.getScene().getAccelerators().clear();
    }
    pane.getScene().getAccelerators().put(new KeyCodeCombination(KeyCode.ENTER, KeyCombination.ALT_DOWN), new Runnable() {
        @Override
        public void run() {
            submit(null);
        }
    });
}

How can I avoid this problem?


Solution

  • I've solved the problem avoiding the removal of an element. Instead of remove an element of the map, now I'm making the value equals null:

    protected void enableAccelerators() {
        accelerators = new HashMap<KeyCombination, Runnable>(pane.getScene().getAccelerators());
        for (KeyCombination combination : accelerators.keySet())
            accelerators.put(combination, null);
        pane.getScene().getAccelerators().put(new KeyCodeCombination(KeyCode.ENTER, KeyCombination.ALT_DOWN), new Runnable() {
            @Override
            public void run() {
                submit(null);
            }
        });
    }