Note: the same problem has already been reported in the mailing list. In this report I included an explanation of the problem and a better fix. See the mailing list post here: http://sourceforge.net/mailarchive/forum.php?thread_name=49671DA1.1030902%40infonode.net&forum_name=infonode-users
The problem: I systematically get the following exception when I run the application with only minimum amount of memory so that GC kicks in often:
Exception in thread "AWT-EventQueue-0" java.lang.IndexOutOfBoundsException: Index: 4, Tab count: 4
at javax.swing.JTabbedPane.checkIndex(Unknown Source)
at javax.swing.JTabbedPane.setSelectedIndex(Unknown Source)
at net.infonode.tabbedpanel.theme.internal.laftheme.PaneUI.initTabLocations(PaneUI.java:1115)
at net.infonode.tabbedpanel.theme.internal.laftheme.PaneUI.paintTabArea(PaneUI.java:696)
at net.infonode.tabbedpanel.theme.LookAndFeelTheme$3$2.paint(LookAndFeelTheme.java:208)
at net.infonode.gui.shaped.panel.ShapedPanel.paintComponent(ShapedPanel.java:205)
at javax.swing.JComponent.paint(Unknown Source)
at javax.swing.JComponent.paintChildren(Unknown Source)
at net.infonode.gui.shaped.panel.ShapedPanel.paintChildren(ShapedPanel.java:185)
at javax.swing.JComponent.paint(Unknown Source)
at net.infonode.tabbedpanel.TabbedPanel$ShadowPanel.paint(TabbedPanel.java:1604)
at javax.swing.JComponent.paintChildren(Unknown Source)
Explanation: PaneUI uses ComponentCache to generate empty components, these components are then added via JTabbedPane.addTab(). If the same component is added twice, JTabbedPane removes a tab with the component and only then adds a new one, resulting in the same number of tabs, that is, not every call to addTab() is guaranteed to increase the number of tabs, but PaneUI expects that.
Turns out there's a problem in ComponentCache and it can return the same component twice without being reset.
This is the original code for
ComponentCache.getComponent():
Component getComponent() {
JComponent c = null;
if (index == cache.size()) {
c = createComponent();
cache.add(new SoftReference(c));
}
else {
c = (JComponent) ((SoftReference) cache.get(index)).get();
if (c == null) {
cache.remove(index);
c = createComponent();
cache.add(new SoftReference(c));
}
}
index++;
c.setOpaque(false);
return c;
}
The problem is that when the SoftReference is GCed, the element is removed from the cache list, a new component is created and _added at the end_ of the list. In this case, the current invocation of getComponent() returns the newly created component but also _some future invocation_ will return the same component since it was added at the end of the list.
The fix is, when the SoftReference is null, we should _replace_ the invalid SoftReference with the new one in the list:
if (c == null) {
c = createComponent();
cache.set(index, new SoftReference(c));
}
I opened a maintenance branch on Github and applied the suggested change:
https://github.com/ebourg/infonode/commit/e14d433c1c2d467913bf5f069904fbb292be0e04