Search code examples
javaindexoutofboundsexceptionrepaintjiderepaintmanager

java.lang.IndexOutOfBoundsException: Index: 1, Size: 1 in Jidesoft DocumentPane


Edit3: This one does not duplicate general "I have indexoutofboundsexception in my code" questions because, as clearly stated in the answer, it was a bug in a widely used third-party library jide-common, confirmed by developers, there's a link below. This bug was recently fixed (after publication of this question) and many older versions of the library are still affected. So this information might be useful for other developers who stumble upon the same problem with jide-common.

I'm facing a problem with GUI programming in Java. I'm not sure about the specific source of a problem (still can't figure it out) - it might be either my error somewhere, some faulty code in jide components or even something wrong with awt/swing focus or event handling.

The following code depends on

  • com.jidesoft:jide-grids:3.5.1
  • com.jidesoft:jide-components:3.5.1
  • com.jidesoft:jide-common:3.5.1

I've found this hint on Stackoverflow, but after some debugging it seems for me that every modification to CellEditor and other components is done in EDT.

To reproduce the bug you should run this example and open tab containing the CellEditor (it must be the last in line, otherwise no index overflow would happen), then input some "incorrect" value in there and without removing focus from editor, click close "x" button on one of previous tabs. After that the following chain of events happens: 1) tab gets closed/removed, 2) CellEditor verifier shows the modal dialog which in turn triggers repaint of tab-pane 3) due to missing (closed) tab, an ArrayIndexOutOfBounds exception gets thrown.

package com.example;

import com.jidesoft.document.DocumentComponent;
import com.jidesoft.document.DocumentPane;
import com.jidesoft.grid.JideTable;
import com.jidesoft.grid.TableModelWrapperUtils;
import com.jidesoft.grid.TextFieldCellEditor;
import com.jidesoft.swing.JideTabbedPane;

import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.math.BigDecimal;
import java.util.Random;

/**
 * Dependends on:
 * com.jidesoft:jide-grids:3.5.1
 * com.jidesoft:jide-components:3.5.1
 * com.jidesoft:jide-common:3.5.1
 */
public class ModalPopupFailure {

    public static void main(String[] args) {
        JFrame frame = new JFrame();
        frame.setTitle("test");
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        JComponent panel = new ModalPopupFailure().buildPanel();
        frame.setContentPane(panel);
        frame.setBounds(450, 300, 700, 500);
        frame.setVisible(true);
    }

    /**
     * @return table editor panel
     */
    private JPanel tableTab() {
        JPanel result = new JPanel(new BorderLayout());

        DefaultTableModel model = new DefaultTableModel(1, 1);
        model.setValueAt(new BigDecimal(0L), 0, 0);
        JideTable table = new JideTable(model);
        table.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);

        final int actualCol0 = TableModelWrapperUtils.getActualColumnAt(table.getModel(), 0);
        CustomCellEditor bdce = new CustomCellEditor(Integer.class);
        table.getColumnModel().getColumn(actualCol0).setCellEditor(bdce);

        result.add(table, BorderLayout.CENTER);
        return result;
    }

    /**
     * @return Main panel with tabs
     */
    private JComponent buildPanel() {
        JPanel mainPanel = new JPanel(new BorderLayout());

        final DocumentPane documentPane = new DocumentPane();

        DocumentComponent dc1 = new DocumentComponent(new JLabel("tab1 label"), "aaa");
        documentPane.openDocument(dc1);

        DocumentComponent dc2 = new DocumentComponent(tableTab(), "TABLETAB");
        documentPane.openDocument(dc2);
        documentPane.setActiveDocument("TABLETAB");

        documentPane.setTabbedPaneCustomizer(new DocumentPane.TabbedPaneCustomizer() {
            @Override
            public void customize(final JideTabbedPane tabbedPane) {
                tabbedPane.setShowCloseButton(true);
                tabbedPane.setUseDefaultShowCloseButtonOnTab(false);
                tabbedPane.setShowCloseButtonOnTab(true);
            }
        });

        documentPane.setTabPlacement(SwingConstants.TOP);

        mainPanel.add(documentPane, BorderLayout.CENTER);
        mainPanel.add(new JButton(new AbstractAction("New tab") {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        Integer randInt = new Random().nextInt();
                        DocumentComponent newdc = new DocumentComponent(
                                new JLabel("Generated tab label " + randInt),
                                Integer.toString(randInt));

                        documentPane.openDocument(newdc);
                    }
                }),
                BorderLayout.NORTH);
        JLabel decription = new JLabel("<html>Steps to reproduce:<br />" +
                "TABLETAB should be the last tab<br />" +
                "The \"correct\" value for cell editor is 777.<br />" +
                "Enter any \"incorrect\" number, then close any tab standing before TABLETAB.<br />" +
                "An exception caused by window repaint should be raised by now.<br />" +
                "If dialog would not be modal, there would be no window repaint triggered.</html>");
        mainPanel.add(decription, BorderLayout.SOUTH);

        return mainPanel;
    }

    private class CustomCellEditor extends TextFieldCellEditor {

        boolean firstTime = true;

        public CustomCellEditor(Class<?> aClass) {
            super(aClass);
            _textField.setInputVerifier(new InputVerifier() {
                @Override
                public boolean verify(JComponent input) {
                    boolean valid = "777".equals(_textField.getText());
                    if (valid) return true;

                    final JDialog dialog = new JDialog();
                    dialog.setTitle("Exception expected");
                    dialog.setContentPane(new JLabel("<html>If one of previous tabs was closed, an exception should be raised by now.</html>"));
                    dialog.setLocationRelativeTo(null);

                    dialog.setModal(true); // Switching to false seems to fix the problem

                    dialog.setSize(new Dimension(300, 100));
                    dialog.setVisible(true);

                    return true;
                }
            });
        }

        @Override
        public boolean stopCellEditing() {
            System.out.println("Stopping cell edit " + new Random().nextInt() + "in edt: " + SwingUtilities.isEventDispatchThread());
            if (firstTime) {
                firstTime = false;
                return false;
            }

            return _textField.getInputVerifier().shouldYieldFocus(_textField) && super.stopCellEditing();
        }
    }
}

Exception:

Exception in thread "AWT-EventQueue-0" java.lang.IndexOutOfBoundsException: Index: 1, Size: 1
    at java.util.ArrayList.rangeCheck(ArrayList.java:635)
    at java.util.ArrayList.get(ArrayList.java:411)
    at javax.swing.JTabbedPane.getComponentAt(JTabbedPane.java:1224)
    at com.jidesoft.plaf.vsnet.VsnetJideTabbedPaneUI.paintContentBorder(Unknown Source)
    at com.jidesoft.plaf.basic.BasicJideTabbedPaneUI.paintContentBorder(Unknown Source)
    at com.jidesoft.plaf.basic.BasicJideTabbedPaneUI.paint(Unknown Source)
    at javax.swing.plaf.ComponentUI.update(ComponentUI.java:161)
    at javax.swing.JComponent.paintComponent(JComponent.java:779)
    at javax.swing.JComponent.paint(JComponent.java:1055)
    at javax.swing.JComponent.paintChildren(JComponent.java:888)
    at javax.swing.JComponent.paint(JComponent.java:1064)
    at javax.swing.JComponent.paintChildren(JComponent.java:888)
    at javax.swing.JComponent.paint(JComponent.java:1064)
    at javax.swing.JComponent.paintToOffscreen(JComponent.java:5232)
    at javax.swing.BufferStrategyPaintManager.paint(BufferStrategyPaintManager.java:295)
    at javax.swing.RepaintManager.paint(RepaintManager.java:1249)
    at javax.swing.JComponent._paintImmediately(JComponent.java:5180)
    at javax.swing.JComponent.paintImmediately(JComponent.java:4991)
    at javax.swing.RepaintManager$3.run(RepaintManager.java:808)
    at javax.swing.RepaintManager$3.run(RepaintManager.java:796)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:76)
    at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:796)
    at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:769)
    at javax.swing.RepaintManager.prePaintDirtyRegions(RepaintManager.java:718)
    at javax.swing.RepaintManager.access$1100(RepaintManager.java:62)
    at javax.swing.RepaintManager$ProcessingRunnable.run(RepaintManager.java:1677)
    at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:312)
    at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:745)
    at java.awt.EventQueue.access$300(EventQueue.java:103)
    at java.awt.EventQueue$3.run(EventQueue.java:706)
    at java.awt.EventQueue$3.run(EventQueue.java:704)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:76)
    at java.awt.EventQueue.dispatchEvent(EventQueue.java:715)
    at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:242)
    at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:161)
    at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:154)
    at java.awt.WaitDispatchSupport$2.run(WaitDispatchSupport.java:182)
    at java.awt.WaitDispatchSupport$4.run(WaitDispatchSupport.java:221)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.awt.WaitDispatchSupport.enter(WaitDispatchSupport.java:219)
    at java.awt.Dialog.show(Dialog.java:1082)
    at java.awt.Component.show(Component.java:1655)
    at java.awt.Component.setVisible(Component.java:1607)
    at java.awt.Window.setVisible(Window.java:1014)
    at java.awt.Dialog.setVisible(Dialog.java:1005)
    at com.example.ModalPopupFailure$CustomCellEditor$1.verify(ModalPopupFailure.java:124)
    at javax.swing.InputVerifier.shouldYieldFocus(InputVerifier.java:132)
    at javax.swing.JComponent$1.acceptRequestFocus(JComponent.java:3589)
    at java.awt.Component.isRequestFocusAccepted(Component.java:7739)
    at java.awt.Component.requestFocusHelper(Component.java:7621)
    at java.awt.Component.requestFocusInWindow(Component.java:7544)
    at java.awt.Component.transferFocus(Component.java:7842)
    at java.awt.Component.hide(Component.java:1688)
    at javax.swing.JComponent.hide(JComponent.java:5585)
    at java.awt.Component.show(Component.java:1657)
    at java.awt.Component.setVisible(Component.java:1607)
    at javax.swing.JComponent.setVisible(JComponent.java:2641)
    at com.jidesoft.plaf.basic.BasicJideTabbedPaneUI.setVisibleComponent(Unknown Source)
    at com.jidesoft.plaf.basic.BasicJideTabbedPaneUI$PropertyChangeHandler.propertyChange(Unknown Source)
    at java.beans.PropertyChangeSupport.fire(PropertyChangeSupport.java:335)
    at java.beans.PropertyChangeSupport.firePropertyChange(PropertyChangeSupport.java:327)
    at java.beans.PropertyChangeSupport.firePropertyChange(PropertyChangeSupport.java:263)
    at java.awt.Component.firePropertyChange(Component.java:8393)
    at javax.swing.JComponent.putClientProperty(JComponent.java:4103)
    at javax.swing.JTabbedPane.removeTabAt(JTabbedPane.java:971)
    at com.jidesoft.swing.JideTabbedPane.removeTabAt(Unknown Source)
    at com.jidesoft.document.TdiGroup.removeDocument(Unknown Source)
    at com.jidesoft.document.DocumentPane.a(Unknown Source)
    at com.jidesoft.document.DocumentPane.a(Unknown Source)
    at com.jidesoft.document.DocumentPane.a(Unknown Source)
    at com.jidesoft.document.DocumentPane.a(Unknown Source)
    at com.jidesoft.document.DocumentPane.closeDocument(Unknown Source)
    at com.jidesoft.document.DocumentPane.closeSingleDocument(Unknown Source)
    at com.jidesoft.document.DocumentPane$34.actionPerformed(Unknown Source)
    at com.jidesoft.plaf.basic.BasicJideTabbedPaneUI$CloseTabAction.actionPerformed(Unknown Source)
    at javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:2018)
    at javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2341)
    at javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:402)
    at javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:259)
    at javax.swing.plaf.basic.BasicButtonListener.mouseReleased(BasicButtonListener.java:252)
    at java.awt.AWTEventMulticaster.mouseReleased(AWTEventMulticaster.java:289)
    at java.awt.AWTEventMulticaster.mouseReleased(AWTEventMulticaster.java:289)
    at java.awt.Component.processMouseEvent(Component.java:6516)
    at javax.swing.JComponent.processMouseEvent(JComponent.java:3321)
    at java.awt.Component.processEvent(Component.java:6281)
    at java.awt.Container.processEvent(Container.java:2229)
    at java.awt.Component.dispatchEventImpl(Component.java:4872)
    at java.awt.Container.dispatchEventImpl(Container.java:2287)
    at java.awt.Component.dispatchEvent(Component.java:4698)
    at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4832)
    at java.awt.LightweightDispatcher.processMouseEvent(Container.java:4492)
    at java.awt.LightweightDispatcher.dispatchEvent(Container.java:4422)
    at java.awt.Container.dispatchEventImpl(Container.java:2273)
    at java.awt.Window.dispatchEventImpl(Window.java:2719)
    at java.awt.Component.dispatchEvent(Component.java:4698)
    at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:747)
    at java.awt.EventQueue.access$300(EventQueue.java:103)
    at java.awt.EventQueue$3.run(EventQueue.java:706)
    at java.awt.EventQueue$3.run(EventQueue.java:704)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:76)
    at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:87)
    at java.awt.EventQueue$4.run(EventQueue.java:720)
    at java.awt.EventQueue$4.run(EventQueue.java:718)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:76)
    at java.awt.EventQueue.dispatchEvent(EventQueue.java:717)
    at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:242)
    at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:161)
    at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:150)
    at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:146)
    at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:138)
    at java.awt.EventDispatchThread.run(EventDispatchThread.java:91)

There's an "extended" version of code with a bit of debugging info added. I still can't even figure out the source of a problem, not mentioning the fix. Thanks in advance.

Edit: OK one thing I figured out by now is that showing a dialog from within verify() method is wrong, according to docs:

The verify method exists only to determine whether the input is valid it should never bring up a dialog-box or cause any other side effects. The shouldYieldFocus method calls verify and, if a values is invalid, sets it to the minimum or maximum value. The shouldYieldFocus method is allowed to cause side effects...

So I moved all not-actually-verifying code to shouldYieldFocus(). That didn't solve the problem, but further delaying dialog.show() with SwingUtilities.invokeLater() seems to work fine, no pitfalls... yet.

public CustomCellEditor(Class<?> aClass) {
    super(aClass);
    _textField.setInputVerifier(new InputVerifier() {

        @Override
        public boolean shouldYieldFocus(JComponent input) {
            boolean inputOK = verify(input);
            if (!inputOK) {
                final ru.esc.erp.core.components.Dialog dialog = new ru.esc.erp.core.components.Dialog();
                dialog.setTitle("Exception may be raised");
                dialog.setContentPane(new JLabel("<html>Modal dialog opened with yet another delay.</html>"));
                dialog.setLocationRelativeTo(null);

                dialog.setModal(true); // false "fixes" the problem

                dialog.setSize(new Dimension(300, 100));
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        dialog.setVisible(true);
                    }
                });
            }
            return inputOK;
        }

        @Override
        public boolean verify(JComponent input) {
            return "777".equals(_textField.getText());
        }
    });
}

Edit2: gif with captured video


Solution

  • OK, it was a bug in JideTabbedPaneUI. I've tested the patched jide-oss with my example and there's no exception anymore (however, there are some non-critical UI glitches now while closing a tab). The source of the problem is found.

    EDIT: they fixed this bug later in 3.6.17:

    by JIDE Support » Wed Feb 01, 2017 12:10 am Just so you know, this bug was fixed in 3.6.17 which was just released.