JDK-8182577 : Exception when Tab key moves focus to a JCheckbox with a custom ButtonModel
  • Type: Bug
  • Component: client-libs
  • Sub-Component: javax.swing
  • Affected Version: 9
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: generic
  • CPU: x86
  • Submitted: 2017-06-19
  • Updated: 2024-08-02
  • Resolved: 2017-07-01
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 10 JDK 7 JDK 8 Other
10 b23Fixed 7u331Fixed 8u311Fixed openjdk8u432Unresolved
Related Reports
CSR :  
Duplicate :  
Relates :  
Relates :  
Relates :  
Description
FULL PRODUCT VERSION :
java version "9-ea"
Java(TM) SE Runtime Environment (build 9-ea+174)
Java HotSpot(TM) 64-Bit Server VM (build 9-ea+174, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [Version 10.0.10586]

A DESCRIPTION OF THE PROBLEM :
We use a customised "tri-state" checkbox. In JDK-9 a crash occurs (in LayoutFocusTraversalPolicy) when the keyboard focus moves to it by pressing the TAB key. The crash appears to have been introduced with additional logic added in JDK-9. I think the cast should be guarded with an instanceof check to match what is possible via the API of JToggleButton (and derivatives).

The relevant code in LayoutFocusTraversalPolicy.accept is:

    } else if (aComponent instanceof JComponent) {
        if (SunToolkit.isInstanceOf(aComponent,
"javax.swing.JToggleButton")) {
            JToggleButton.ToggleButtonModel model =
                    (JToggleButton.ToggleButtonModel) ((JToggleButton)
                            aComponent).getModel();   // <- Line 243
            if (model != null) {
                ButtonGroup group = model.getGroup(); // <- Only use of 'model'

The Tri-state check button code was taken from an old online article:

http://www.javaspecialists.eu/archive/Issue082.html

It's model derives directly from ButtonModel, (then delegates all method calls to a real ToggleButtonModel).

The solution I imagine is to perform an instanceof check for either DefaultButtonModel or ToggleButtonModel (depending on the desired behaviour). Note that in JToggeleButton.getGroupSelection we see similar code (also added in JDK-9?) using a check-and-cast to DefaultButtonModel.

Also note that while JToggleButton creates a ToggleButtonModel by default, it does not enforce it via setModel().



REGRESSION.  Last worked in version 8u131

ADDITIONAL REGRESSION INFORMATION: 
Seems related to focus logic changes with button-groups introduced in JDK-9. For example, see 8074883

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Execute the given code, press TAB twice and the crash should be seen


ERROR MESSAGES/STACK TRACES THAT OCCUR :
Exception in thread "AWT-EventQueue-0" java.lang.ClassCastException: java.desktop/javax.swing.DefaultButtonModel cannot be cast to java.desktop/javax.swing.JToggleButton$ToggleButtonModel
	at java.desktop/javax.swing.LayoutFocusTraversalPolicy.accept(LayoutFocusTraversalPolicy.java:243)
	at java.desktop/javax.swing.SortingFocusTraversalPolicy.getComponentAfter(SortingFocusTraversalPolicy.java:332)
	at java.desktop/javax.swing.LayoutFocusTraversalPolicy.getComponentAfter(LayoutFocusTraversalPolicy.java:107)
	at java.desktop/java.awt.Component.getNextFocusCandidate(Component.java:8155)
	at java.desktop/java.awt.Component.transferFocus(Component.java:8122)
	at java.desktop/java.awt.Component.nextFocus(Component.java:8115)
	at java.desktop/java.awt.Component.transferFocus(Component.java:8106)
	at java.desktop/java.awt.DefaultKeyboardFocusManager.focusNextComponent(DefaultKeyboardFocusManager.java:1403)
	at java.desktop/java.awt.DefaultKeyboardFocusManager.processKeyEvent(DefaultKeyboardFocusManager.java:1167)
	at java.desktop/java.awt.Component.dispatchEventImpl(Component.java:4877)
	at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2317)
	at java.desktop/java.awt.Component.dispatchEvent(Component.java:4793)
	at java.desktop/java.awt.KeyboardFocusManager.redispatchEvent(KeyboardFocusManager.java:1950)
	at java.desktop/java.awt.DefaultKeyboardFocusManager.dispatchKeyEvent(DefaultKeyboardFocusManager.java:827)
	at java.desktop/java.awt.DefaultKeyboardFocusManager.preDispatchKeyEvent(DefaultKeyboardFocusManager.java:1096)
	at java.desktop/java.awt.DefaultKeyboardFocusManager.typeAheadAssertions(DefaultKeyboardFocusManager.java:966)
	at java.desktop/java.awt.DefaultKeyboardFocusManager.dispatchEvent(DefaultKeyboardFocusManager.java:792)
	at java.desktop/java.awt.Component.dispatchEventImpl(Component.java:4842)
	at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2317)
	at java.desktop/java.awt.Window.dispatchEventImpl(Window.java:2758)
	at java.desktop/java.awt.Component.dispatchEvent(Component.java:4793)
	at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:761)
	at java.desktop/java.awt.EventQueue.access$500(EventQueue.java:97)
	at java.desktop/java.awt.EventQueue$3.run(EventQueue.java:712)
	at java.desktop/java.awt.EventQueue$3.run(EventQueue.java:706)
	at java.base/java.security.AccessController.doPrivileged(Native Method)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:89)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:99)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:734)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:732)
	at java.base/java.security.AccessController.doPrivileged(Native Method)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:89)
	at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:731)
	at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:199)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:117)
	at java.desktop/java.awt.WaitDispatchSupport$2.run(WaitDispatchSupport.java:190)
	at java.desktop/java.awt.WaitDispatchSupport$4.run(WaitDispatchSupport.java:235)
	at java.desktop/java.awt.WaitDispatchSupport$4.run(WaitDispatchSupport.java:233)
	at java.base/java.security.AccessController.doPrivileged(Native Method)
	at java.desktop/java.awt.WaitDispatchSupport.enter(WaitDispatchSupport.java:233)
	at java.desktop/java.awt.Dialog.show(Dialog.java:1070)
	at java.desktop/javax.swing.JOptionPane.showOptionDialog(JOptionPane.java:876)
	at java.desktop/javax.swing.JOptionPane.showMessageDialog(JOptionPane.java:672)
	at java.desktop/javax.swing.JOptionPane.showMessageDialog(JOptionPane.java:643)
	at java.desktop/javax.swing.JOptionPane.showMessageDialog(JOptionPane.java:614)
	at snippet.Snippet.go(Snippet.java:23)
	at snippet.Snippet.lambda$0(Snippet.java:8)
	at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:313)
	at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:759)
	at java.desktop/java.awt.EventQueue.access$500(EventQueue.java:97)
	at java.desktop/java.awt.EventQueue$3.run(EventQueue.java:712)
	at java.desktop/java.awt.EventQueue$3.run(EventQueue.java:706)
	at java.base/java.security.AccessController.doPrivileged(Native Method)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:89)
	at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:729)
	at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:199)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
	at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)


REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.awt.*;
import javax.swing.*;

public class Snippet {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> new Snippet().go());
    }

    private void go() {

        // Non-functional model for brevity. The original, implementing
        // interface ButtonModel directly, can be found at:
        // http://www.javaspecialists.eu/archive/Issue082.html
        ButtonModel model = new DefaultButtonModel();

        JCheckBox check = new JCheckBox("a bit broken");
        check.setModel(model);
        JPanel panel = new JPanel(new BorderLayout());
        panel.add(new JTextField("Press Tab (twice?)"), BorderLayout.NORTH);
        panel.add(check);
        JOptionPane.showMessageDialog(null, panel);
        System.exit(0);
    }
}


---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Users could click with the mouse instead of navigating via the keyboard.

Programmers can move to a different tri-state checkbox implementation (from Jide, for example).


Comments
Thanks for clarifying that. I've removed it.
02-08-2024

[~phh] I wonder why you added 8 to Affects Version. This issue does not affect 8. It is a regression from JDK-8154043 which isn't backported to 8.
02-08-2024

A pull request was submitted for review. Branch: master URL: https://git.openjdk.org/jdk8u-dev/pull/285 Date: 2023-03-15 05:47:54 +0000
09-07-2024

In 8u backport, the regression test, DefaultButtonModelCrashTest.java, includes the changes from JDK-8191678 which adds "@key headful" tag and null-check for frame.
19-11-2021

URL: http://hg.openjdk.java.net/jdk10/jdk10/jdk/rev/e1e784e7fe35 User: prr Date: 2017-09-07 18:20:03 +0000
07-09-2017

URL: http://hg.openjdk.java.net/jdk10/client/jdk/rev/e1e784e7fe35 User: psadhukhan Date: 2017-07-01 04:32:23 +0000
01-07-2017

http://mail.openjdk.java.net/pipermail/swing-dev/2017-June/007504.html
20-06-2017

This seems a regression in JDK 9. When run with JDK 8u131 and 9 ea b174 could confirm the issue as reported. To verify, run the attached test case. Result: =========== 8u131: OK 8u152 ea b04: OK 9 ea b174: FAIL Output with 9 ea b174: ================================================ >java Snippet Exception in thread "AWT-EventQueue-0" java.lang.ClassCastException: java.desktop/javax.swing.DefaultButtonModel cannot be cast to java.desktop/javax.swing.JToggleButton$ToggleButtonModel at java.desktop/javax.swing.LayoutFocusTraversalPolicy.accept(LayoutFocusTraversalPolicy.java:243) ================================================
20-06-2017