JDK-6397556 : REGRESSION: StackOverflowError when 2 JToggleButtons within ButtonGroup bound to same Action
  • Type: Bug
  • Component: client-libs
  • Sub-Component: javax.swing
  • Affected Version: 6
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: linux
  • CPU: x86
  • Submitted: 2006-03-13
  • Updated: 2017-05-16
  • Resolved: 2006-04-12
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 6
6 b80Fixed
Related Reports
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.6.0-beta2"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.6.0-beta2-b74)
Java HotSpot(TM) Client VM (build 1.6.0-beta2-b74, mixed mode, sharing)


ADDITIONAL OS VERSION INFORMATION :
Linux vino.select-tech.si 2.6.15-1.1831_FC4 #1 Tue Feb 7 13:37:42 EST 2006 i686 i686 i386 GNU/Linux


EXTRA RELEVANT SYSTEM CONFIGURATION :
not relevant

A DESCRIPTION OF THE PROBLEM :
When two JToggleButtons within the same ButtonGroup are bound to the same Action (a sublclass of AbstractAction) then an infinite recursion is triggered by pressing one of them and consequently StackOverflowError is thrown.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Compile the source code attached and run it on the latest Mustang. Press one of the buttons. See StackOverflowError.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
When the same program is compiled and run on Tiger, it works correctly.
ACTUAL -
The actual result is StackOverflowError being thrown.

ERROR MESSAGES/STACK TRACES THAT OCCUR :
Exception in thread "AWT-EventQueue-0" java.lang.StackOverflowError
	at javax.swing.AbstractButton.access$100(AbstractButton.java:56)
	at javax.swing.AbstractButton$Handler.stateChanged(AbstractButton.java:2293)
	at javax.swing.DefaultButtonModel.fireStateChanged(DefaultButtonModel.java:323)
	at javax.swing.JToggleButton$ToggleButtonModel.setSelected(JToggleButton.java:247)
	at javax.swing.ButtonGroup.setSelected(ButtonGroup.java:145)
	at javax.swing.JToggleButton$ToggleButtonModel.setSelected(JToggleButton.java:232)
	at javax.swing.AbstractButton.setSelected(AbstractButton.java:329)
	at javax.swing.AbstractButton.setSelectedFromAction(AbstractButton.java:1281)
	at javax.swing.AbstractButton.actionPropertyChanged(AbstractButton.java:1195)
	at javax.swing.AbstractButton$ButtonActionPropertyChangeListener.actionPropertyChanged(AbstractButton.java:1330)
	at javax.swing.AbstractButton$ButtonActionPropertyChangeListener.actionPropertyChanged(AbstractButton.java:1319)
	at javax.swing.ActionPropertyChangeListener.propertyChange(ActionPropertyChangeListener.java:71)
	at java.beans.PropertyChangeSupport.firePropertyChange(PropertyChangeSupport.java:338)
	at javax.swing.event.SwingPropertyChangeSupport.firePropertyChange(SwingPropertyChangeSupport.java:75)
	at java.beans.PropertyChangeSupport.firePropertyChange(PropertyChangeSupport.java:275)
	at javax.swing.AbstractAction.firePropertyChange(AbstractAction.java:230)
	at javax.swing.AbstractAction.putValue(AbstractAction.java:165)
	at javax.swing.AbstractButton$Handler.itemStateChanged(AbstractButton.java:2320)
	at javax.swing.DefaultButtonModel.fireItemStateChanged(DefaultButtonModel.java:430)
	at javax.swing.JToggleButton$ToggleButtonModel.setSelected(JToggleButton.java:250)
	at javax.swing.ButtonGroup.setSelected(ButtonGroup.java:147)
	at javax.swing.JToggleButton$ToggleButtonModel.setSelected(JToggleButton.java:232)
	at javax.swing.AbstractButton.setSelected(AbstractButton.java:329)
	at javax.swing.AbstractButton.setSelectedFromAction(AbstractButton.java:1281)
	at javax.swing.AbstractButton.actionPropertyChanged(AbstractButton.java:1195)
	at javax.swing.AbstractButton$ButtonActionPropertyChangeListener.actionPropertyChanged(AbstractButton.java:1330)
	at javax.swing.AbstractButton$ButtonActionPropertyChangeListener.actionPropertyChanged(AbstractButton.java:1319)
	at javax.swing.ActionPropertyChangeListener.propertyChange(ActionPropertyChangeListener.java:71)
	at java.beans.PropertyChangeSupport.firePropertyChange(PropertyChangeSupport.java:338)
	at javax.swing.event.SwingPropertyChangeSupport.firePropertyChange(SwingPropertyChangeSupport.java:75)
	at java.beans.PropertyChangeSupport.firePropertyChange(PropertyChangeSupport.java:275)
... and so on...


REPRODUCIBILITY :
This bug can be reproduced always.

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

//
// this program produces StackOverflowError when run on latest Mustang builds
// but works correctly on Tiger
public class ToggleButtonTest
{
   public static void doTest()
   {
      Action action1 = new AbstractAction("Action 1")
      {
         public void actionPerformed(ActionEvent e)
         {
            System.out.println(e.getActionCommand());
         }
      };

      Action action2 = new AbstractAction("Action 2")
      {
         public void actionPerformed(ActionEvent e)
         {
            System.out.println(e.getActionCommand());
         }
      };

      ButtonGroup group = new ButtonGroup();

      JToggleButton button1 = new JToggleButton(action1);
      button1.setActionCommand("Button 1 selected");
      group.add(button1);

      // if you put action2 instead of action1 in the following line, it works on Java SE 6 too...
      JToggleButton button2 = new JToggleButton(action1);
      button2.setActionCommand("Button 2 selected");
      group.add(button2);

      JFrame f = new JFrame("ToggleButtonTest");
      f.getContentPane().setLayout(new FlowLayout());
      f.getContentPane().add(button1);
      f.getContentPane().add(button2);
      f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      f.pack();
      f.setVisible(true);
   }

   public static void main(String[] args)
   {
      SwingUtilities.invokeLater(new Runnable()
      {
         public void run()
         {
            doTest();
         }
      });
   }
}

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

CUSTOMER SUBMITTED WORKAROUND :
There might be (wrapping action with another action, filtering events, etc) but not an easy or elegant one.

Release Regression From : 5.0
The above release value was the last known release where this 
bug was known to work. Since then there has been a regression.

Comments
EVALUATION This regression was introduced in adding support for the selected state to actions (4133141). After the change for 4133141 when ever a button changes its selected state it'll put that value to the Action (action.putValue(Action.SELECTED_KEY)). Unfortunately this has a number of undesirable side effects: 1. If you share an Action between buttons in the same buttongroup you can get a stack overflow error (this bug). 2. When you invoke setAction(action) the selected state is changed immediately. This is new behavior and there are scenarios where developers might not expect expect this. For example, any previous code that hadn't defined SELECTED_KEY and that did button.setSelected(true), button.setAction(action) the selected state would change to false (a value of null for SELECTED_KEY is treated as false). 3. Selected is the only property that is pushed back to the Action. Developers might not expect putValue to ever be called by Swing, in fact it's possible for them to write a putValue method like: void putValue(...) { throw new RuntimeException(); } And it would have worked because Swing never invoked putValue before. Now that we do, code such as this would throw an exception. For all these reasons we need to make using the new selected functionality optional. The best way to do that is to check if there is a value defined for SELECTED_KEY, if there is, then'll we do putValue and all that. If the value for the key SELECTED_KEY is null, we will not change the selected state of the button to mirror that of the Action, and we won't update the Action when the selected state of the button changes. In other words, the only way to get the new selected functionality is if you have a valid value for SELECTED_KEY, or opt in
16-03-2006