Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8159802

NPE can be thrown after undoing a text change from a specially configured Spinner

XMLWordPrintable

    • x86
    • windows_7, windows_8

      FULL PRODUCT VERSION :
      java version "1.8.0_92"
      Java(TM) SE Runtime Environment (build 1.8.0_92-b14)
      Java HotSpot(TM) 64-Bit Server VM (build 25.92-b14, mixed mode)

      ADDITIONAL OS VERSION INFORMATION :
      Windows 8, WIndows 7 (64 bit)

      A DESCRIPTION OF THE PROBLEM :
      I configured a Spinner<Integer> control such that text changes are automatically committed to adjust Spinner's value (see code below). Everything works as expected except for the undo behavior.
      If I attempt to undo a text change while Spinner's editor is in focus, the application throws NPE. See stack trace below. The text change must be done manually, via the editor, not by incrementing/decrementing the spinner.
      Analysis of stack trace drew my attention to TextInputControl class, method updateUndoRedoState(), line 1154. Specifically, there is a missing nullity check for "undoChange" value. I believe it is a bug.
      In line 1154 it would be logical to set "undoable" to false if undoChange is null as there is nothing to undo to. In my scenario the method is called while undoChange == null so NPE is thrown in line 1155 while attempting to access undoChange.next.
      Suggested change to updateUndoRedoState() method:
      private void updateUndoRedoState() {
              undoable.set(undoChange != undoChangeHead && undoChange != null);
              redoable.set(undoChange.next != null);
          }

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      1 - Run my demo application (code provided)
      2 - Click on the spinner's text field, select the number and change it to some other number (e.g. 3)
      3 - Press Ctrl + Z to undo the change
      4 - The change is performed but NPE is thrown

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      The undo should be either successful or not. NPE should not be thrown in either case.
      ACTUAL -
      NPE is thrown

      ERROR MESSAGES/STACK TRACES THAT OCCUR :
      Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
      at javafx.scene.control.TextInputControl.updateUndoRedoState(TextInputControl.java:1155)
      at javafx.scene.control.TextInputControl.undo(TextInputControl.java:1112)
      at com.sun.javafx.scene.control.behavior.TextInputControlBehavior.callAction(TextInputControlBehavior.java:148)
      at com.sun.javafx.scene.control.behavior.BehaviorBase.callActionForEvent(BehaviorBase.java:218)
      at com.sun.javafx.scene.control.behavior.TextInputControlBehavior.callActionForEvent(TextInputControlBehavior.java:127)
      at com.sun.javafx.scene.control.behavior.BehaviorBase.lambda$new$74(BehaviorBase.java:135)
      at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:218)
      at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
      at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
      at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
      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.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
      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:49)
      at javafx.event.Event.fireEvent(Event.java:198)
      at javafx.scene.Node.fireEvent(Node.java:8411)
      at com.sun.javafx.scene.control.skin.SpinnerSkin.lambda$new$473(SpinnerSkin.java:151)
      at com.sun.javafx.event.CompositeEventHandler$NormalEventFilterRecord.handleCapturingEvent(CompositeEventHandler.java:282)
      at com.sun.javafx.event.CompositeEventHandler.dispatchCapturingEvent(CompositeEventHandler.java:98)
      at com.sun.javafx.event.EventHandlerManager.dispatchCapturingEvent(EventHandlerManager.java:223)
      at com.sun.javafx.event.EventHandlerManager.dispatchCapturingEvent(EventHandlerManager.java:180)
      at com.sun.javafx.event.CompositeEventDispatcher.dispatchCapturingEvent(CompositeEventDispatcher.java:43)
      at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:52)
      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.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:198)
      at javafx.scene.Scene$KeyHandler.process(Scene.java:3964)
      at javafx.scene.Scene$KeyHandler.access$1800(Scene.java:3910)
      at javafx.scene.Scene.impl_processKeyEvent(Scene.java:2040)
      at javafx.scene.Scene$ScenePeerListener.keyEvent(Scene.java:2501)
      at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:216)
      at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:148)
      at java.security.AccessController.doPrivileged(Native Method)
      at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleKeyEvent$353(GlassViewEventHandler.java:247)
      at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
      at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleKeyEvent(GlassViewEventHandler.java:246)
      at com.sun.glass.ui.View.handleKeyEvent(View.java:546)
      at com.sun.glass.ui.View.notifyKey(View.java:966)
      at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
      at com.sun.glass.ui.win.WinApplication.lambda$null$148(WinApplication.java:191)
      at java.lang.Thread.run(Thread.java:745)

      REPRODUCIBILITY :
      This bug can be reproduced always.

      ---------- BEGIN SOURCE ----------
      package spinnertest;

      import javafx.application.Application;
      import javafx.scene.Scene;
      import javafx.scene.control.Spinner;
      import javafx.scene.control.SpinnerValueFactory;
      import javafx.scene.control.TextFormatter;
      import javafx.scene.layout.StackPane;
      import javafx.stage.Stage;

      public class SpinnerTest extends Application {
          
          @Override
          public void start(Stage primaryStage) {
              Spinner<Integer> mySpinner = new Spinner<>();
              mySpinner.setEditable(true);
              mySpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, 10, 5, 1));
              TextFormatter<Integer> formatter = new TextFormatter<>(
                      mySpinner.getValueFactory().getConverter(),
                      5);
              mySpinner.getEditor().setTextFormatter(formatter);
              
              // bind value properties of spinner and its editor
              // binding is necessary to reproduce NPE but does not have to be bidirectional
              mySpinner.getValueFactory()
                      .valueProperty()
                      .bindBidirectional(formatter.valueProperty());
              
              // this triggers value commit after each text edit
              // this is also necessary to reproduce NPE
              mySpinner.getEditor()
                      .textProperty()
                      .addListener((s, ov, nv) -> {
                          mySpinner.getEditor().commitValue();
                      });
              
              StackPane root = new StackPane();
              root.getChildren().add(mySpinner);
              Scene scene = new Scene(root, 300, 250);
              primaryStage.setScene(scene);
              primaryStage.show();
          }
      }
      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      I add an event filter to spinner's editor that consumes "Ctrl + Z" key combination events

            Unassigned Unassigned
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated: