JDK-4765272 : REGRESSION: IAE: focusCycleRoot not focus cyle root of a Component
  • Type: Bug
  • Component: client-libs
  • Sub-Component: javax.swing
  • Affected Version: 1.4.0
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: windows_nt
  • CPU: x86
  • Submitted: 2002-10-18
  • Updated: 2002-11-16
  • Resolved: 2002-11-16
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.
Other
1.4.2 mantisFixed
Description

Name: sv35042			Date: 10/18/2002


FULL PRODUCT VERSION :
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.0_01-b03)
Java HotSpot(TM) Client VM (build 1.4.0_01-b03, mixed mode)

FULL OPERATING SYSTEM VERSION :
4NT  3.01A   Windows NT 4.00

A DESCRIPTION OF THE PROBLEM :
First of all, this problem does not appear in jdk 1.3.1.
In version 1.4 (and also in 1.4.0_01), when a component is
removed from a container, the container tries to set the
focus on to the next component, if the component being
removed had the focus. If the next component in the focus
traversal list is disabled, then the following exception is
thrown.

java.lang.IllegalArgumentException: focusCycleRoot is not a
focus cyle root of a Component
        at
javax.swing.SortingFocusTraversalPolicy.getComponentAfter
(SortingFocusTraversalPolicy.java:195)
        at
javax.swing.LayoutFocusTraversalPolicy.getComponentAfter
(LayoutFocusTraversalPolicy.java:85)
        at
javax.swing.LegacyGlueFocusTraversalPolicy.getComponentAfter
(LegacyGlueFocusTraversalPolicy.java:63)
        at java.awt.Component.nextFocusHelper
(Component.java:6156)
        at java.awt.Container.nextFocusHelper
(Container.java:2197)
        at java.awt.Component.removeNotify
(Component.java:5436)
        at java.awt.Container.removeNotify
(Container.java:1883)
        at javax.swing.JComponent.removeNotify
(JComponent.java:4286)
        at java.awt.Container.removeAll(Container.java:609)

.
.
.


REGRESSION.  Last worked in version 1.3.1

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1.Create a panel and add 3 JTextFields to it. Let's call
them text1, text2 and text3
2.text1.setNextFocusableComponent(text3);
3.text3.setEnabled(false); //so that control cannot really
go to text3
4.add the panel to a frame and show it
5.Now try to remove all the components from frame (say when
someone tries to close the frame. There is a reason why we
are having to do this instead of disposing the frame
directly). The following method would do this. (Pass
reference to the frame as a parameter to this method):
 
    public static void removeComponentsFromContainer
(Container c) {
         if (c == null) return;
         Component[] components = c.getComponents();
         for (int i = 0; i < components.length; i++) {
             if (components[i] instanceof Container) {
                 removeComponentsFromContainer((Container)
components[i]);
             }
         }
         c.removeAll();
     }

This throws the exception mentioned above. This piece of
code does not throw any exception under jdk 1.3.
(full source code included)

EXPECTED VERSUS ACTUAL BEHAVIOR :
Excepted result: The code should be able to remove all the
components
Actual result: Exception being thrown

ERROR MESSAGES/STACK TRACES THAT OCCUR :
java.lang.IllegalArgumentException: focusCycleRoot is not a focus cyle root of
a Component
        at javax.swing.SortingFocusTraversalPolicy.getComponentAfter
(SortingFocusTraversalPolicy.java:195)
        at javax.swing.LayoutFocusTraversalPolicy.getComponentAfter
(LayoutFocusTraversalPolicy.java:85)
        at javax.swing.LegacyGlueFocusTraversalPolicy.getComponentAfter
(LegacyGlueFocusTraversalPolicy.java:63)
        at java.awt.Component.nextFocusHelper(Component.java:6156)
        at java.awt.Container.nextFocusHelper(Container.java:2197)
        at java.awt.Component.removeNotify(Component.java:5436)
        at java.awt.Container.removeNotify(Container.java:1883)
        at javax.swing.JComponent.removeNotify(JComponent.java:4286)
        at java.awt.Container.removeAll(Container.java:609)


REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
public class CloseTest
{
    public static JPanel getPanel()
    {
        JPanel panel = new JPanel();
        panel.setLayout(new GridLayout(4,2));

        JLabel label1 = new JLabel("label 1");
        JLabel label2 = new JLabel("label 2");
        JLabel label3 = new JLabel("label 3");
        JTextField text1 = new JTextField("label 1 text");
        JTextField text2 = new JTextField("label 2 text");
        JTextField text3 = new JTextField("label 3 text");
        panel.add(label1);
        panel.add(text1);
        panel.add(label2);
        panel.add(text2);
        panel.add(label3);
        panel.add(text3);
        label2.requestFocus();
        panel.setBorder(BorderFactory.createEtchedBorder());
        text1.setNextFocusableComponent(text3);
        text3.setEnabled(false);
        return panel;
    }
    public static void removeComponentsFromContainer(Container c)
     {
         if (c == null) return;
         Component[] components = c.getComponents();
         for (int i = 0; i < components.length; i++)
         {
             if (components[i] instanceof Container)
             {
                 removeComponentsFromContainer((Container) components[i]);
             }
         }
         c.removeAll();
     }
    
    public static void main(String[] args)
    {
        try
        {
        UIManager.setLookAndFeel(
	                "com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }

        final JFrame frame = new JFrame("Close Test");
        Container container = frame.getContentPane();
        frame.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                removeComponentsFromContainer(frame);
                System.exit(0);
            }
        });
        container.add(getPanel());
        frame.setLocation(400,300);
        frame.setSize(300,150);
        frame.show();
    }
}
---------- END SOURCE ----------

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

(Review ID: 153584) 
======================================================================

Comments
CONVERTED DATA BugTraq+ Release Management Values COMMIT TO FIX: mantis FIXED IN: mantis INTEGRATED IN: mantis mantis-b08
24-08-2004

EVALUATION This needs to be fixed for mantis. ###@###.### 2002-10-18 Name: apR10133 Date: 11/05/2002 The container's removeAll removes its children in the back order. When the panel removes first (focused) button LegacyGlueFocusTraversalPolicy attempts to transfer focus to the hardcoded forward component (i.e. to the third button). The third button is not displayable (as it is alredy removed from components hierarchy), hence calls getComponentAfter() for the third button. But frame is no more the FCR for third button, hence exception is thrown. when we set next focusable component the invoker component (current component)register this next focusable component and add it to the forwardMap of LegacyGlueFocusTraversalPolicy. Unregistration of next focusable component occur when the current component is removed from component hierarchy. So if we remove the "next focusable component" from component hierarchy there is no any unregistration happen and the forwardMap point to the invalid component. As the "next focusable component" could be a Component, but not JComponent, it would be resonable to check if the hardcoded component is valid before we call getComponentAfter(). ###@###.### ======================================================================
24-08-2004

WORK AROUND Two options: . Clear out focus before removing the container: KeyboardFocusManager.getCurrentKeyboardFocusManager().clearGlobalFocusOwner(); Because focus is removed asynchronously you'll have to remove your components using an invoke later, eg: public void windowClosing(WindowEvent e) { KeyboardFocusManager.getCurrentKeyboardFocusManager().clearGlobalFocusOwner(); SwingUtilities.invokeLater(new Runnable() { public void run() { removeComponentsFromContainer(frame); System.exit(0); } }); } . Option two is to use remove(int) instead of removeAll, eg: public static void removeComponentsFromContainer(Container c) { if (c == null) return; Component[] components = c.getComponents(); for (int i = 0; i < components.length; i++) { if (components[i] instanceof Container) { removeComponentsFromContainer((Container) components[i]); } c.remove(0); } }
24-08-2004

SUGGESTED FIX Name: apR10133 Date: 11/05/2002 ------- LegacyGlueFocusTraversalPolicy.java ------- *** /tmp/sccs.uRa4uu Mon Nov 4 21:59:19 2002 --- LegacyGlueFocusTraversalPolicy.java Mon Nov 4 21:56:27 2002 *************** *** 59,65 **** prevHardCoded = hardCoded; hardCoded = (Component)forwardMap.get(hardCoded); if (hardCoded == null) { ! if (delegatePolicy != null) { return delegatePolicy.getComponentAfter(focusCycleRoot, prevHardCoded); } else if (delegateManager != null) { --- 59,66 ---- prevHardCoded = hardCoded; hardCoded = (Component)forwardMap.get(hardCoded); if (hardCoded == null) { ! if (delegatePolicy != null && ! prevHardCoded.isFocusCycleRoot(focusCycleRoot)) { return delegatePolicy.getComponentAfter(focusCycleRoot, prevHardCoded); } else if (delegateManager != null) { *************** *** 87,93 **** prevHardCoded = hardCoded; hardCoded = (Component)backwardMap.get(hardCoded); if (hardCoded == null) { ! if (delegatePolicy != null) { return delegatePolicy.getComponentBefore(focusCycleRoot, prevHardCoded); } else if (delegateManager != null) { --- 88,95 ---- prevHardCoded = hardCoded; hardCoded = (Component)backwardMap.get(hardCoded); if (hardCoded == null) { ! if (delegatePolicy != null && ! prevHardCoded.isFocusCycleRoot(focusCycleRoot)) { return delegatePolicy.getComponentBefore(focusCycleRoot, prevHardCoded); } else if (delegateManager != null) { ###@###.### ======================================================================
24-08-2004