JDK-4176095 : Clarification that no 2 Tabs in JTabbedPane can have same component
  • Type: Bug
  • Component: client-libs
  • Sub-Component: javax.swing
  • Affected Version: 1.1.7,6
  • Priority: P3
  • Status: Resolved
  • Resolution: Won't Fix
  • OS: generic,windows_nt
  • CPU: generic,x86
  • Submitted: 1998-09-24
  • Updated: 2014-02-12
  • Resolved: 2014-02-12
Related Reports
Relates :  
Description
Name: mf23781			Date: 09/24/98


Clarify in documentation that no 2 tabs in a JTabbedPane can have
the same component assigned to them.. causes 
java.lang.ArrayIndexOutOfBoundsException: 1 > 0
	at java.util.Vector.insertElementAt(Vector.java:434)
	at com.sun.java.swing.JTabbedPane.insertTab(JTabbedPane.java:425)
	at com.sun.java.swing.JTabbedPane.addTab(JTabbedPane.java:473)

This restriction seems part of design since there is a method
remove(Component) which implies the strict one to one association.

Here is code demonstrating exception and comment line to correct.
import java.awt.*;
import java.awt.event.*;
import com.sun.java.swing.*;
import com.sun.java.swing.event.*;


public class TabTest extends JPanel  implements ActionListener {

   public static int INITIAL_WIDTH = 400;
   public static int INITIAL_HEIGHT = 200;
   BorderLayout bdl = new BorderLayout();
    JFrame jf = new JFrame("Tab Pane Test");
    JButton jb = new JButton("Button");

    JTabbedPane jtab = new JTabbedPane();

    // Create the GUI
    //
    public TabTest() {

       jb.addActionListener(this);

       System.out.println("Starting...");

       JCheckBox jch1 = new JCheckBox("check 1!");
       JCheckBox jch2 = new JCheckBox("check 2!");
       jtab.addTab("first", null,jch1);
       //jtab.addTab("second", null,jch2);              // this works
       jtab.addTab("second", null,jch1);            // same component causes exception

       setLayout(bdl);


        WindowListener l = new WindowAdapter() {
            public void windowClosing(WindowEvent e) {System.exit(0);}
            public void windowIconified(WindowEvent e) {System.exit(0);}
        };
        jf.addWindowListener(l);

        add("North", jb);
        add("Center",jtab);
        jf.getContentPane().add(this);
        jf.setSize(INITIAL_WIDTH, INITIAL_HEIGHT);
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        jf.setLocation(screenSize.width/2 - INITIAL_WIDTH/2,
                screenSize.height/2 - INITIAL_HEIGHT/2);
        jf.show();


    }


    static public void main(String args[]) {

        new TabTest();

    }


    public void actionPerformed(ActionEvent e) {
    }
}


======================================================================
Suggested fix by java.net member leouser

A DESCRIPTION OF THE FIX :
BUGID 4176095 Clarification that no 2 Tabs in JTabbedPane can have the same component
FILES AFFECTED: javax.swing.JTabbedPane
JDK VERSION
jdk-6-rc-bin-b64-linux-i586-15_dec_2005.bin

Discusion(embeded in test case as well):
/**
 * BUGID 4176095 Clarification that no 2 Tabs in JTabbedPane can have the same component
 * This interesting rfe after being reviewed by the engineer appears to have
 * turned into an investigation as to whether or not the JTabbedPane can handle
 * multiple instances of the same component into the JTabbedPane.  It appears
 * the research never reached fruition.  The enhancement I have put together
 * explores the issue and it appears successfully to have done so.
 * I have added 3 new methods to JTabbedPane:
 * setAllowsMultiTabComponent
 * getAllowsMultiTabComponent
 * indexesOfComponent
 * I have also enhanced a couple methods, remove(Component),insertTabAt,
 * setSelectedComponent and setComponentAt.
 * so they are adjusted to this new reality. remove removes all instances
 * of the component. setSelectedComponent will cycle to the next index of
 * the same component if the Component being selected is the current selection.
 *
 * These methods are what I see as the 'issues', they need to be sophisticated
 * enought to work with a component being added multiple times, if configured
 * to allow it.
 *
 * ANTI-RATIONALE:
 * 1. JTabbedPane is not final, uncertain if new methods will collide with
 * a subclass.
 * 2. Questionable use case.  Do people really want this?  I can't come up
 * with one at the top of my head, but it appears someone wanted it back in 98.
 * Anyway, even if I can't come up with a use case the new capability is somewhat
 * exciting to witness.
 *
 * TESTING STRATEGY:
 * 1. Exercise all methods that have been changed to see if they work in the
 * mult-component reality.
 * 2. Exercise the new methods... they get exercised in the altered ones so
 * this is already happening.
 * 3. Look at the visuals, see how one of the JTabbedPane's bring up the
 * same component many times... success? :)
 *
 * FILES AFFECTED: javax.swing.JTabbedPane
 *
 * JDK VERSION
 * jdk-6-rc-bin-b64-linux-i586-15_dec_2005.bin
 *
 * test ran succesfully on a SUSE 7.3 Linux distribution
 *
 * Brian Harry
 * ###@###.###
 * Jan 25, 2006
 */

UNIFIED DIFF:
--- /home/nstuff/java6/jdk1.6.0/javax/swing/JTabbedPane.java	Thu Dec 15 02:17:37 2005
+++ /home/javarefs/javax/swing/JTabbedPane.java	Wed Jan 25 13:58:21 2006
@@ -452,6 +452,27 @@
     }
 
     /**
+     * Sets whether a {@code Component} instance can be added
+     * to the {@code JTabbedPane} multiple times.  When allowed
+     * each addition will create a new tab.
+     *
+     * @param allows whether or not to allow a {@code Component}
+     *               multiple times
+     */
+    public void setAllowsMultiTabComponent(boolean allows){
+        putClientProperty("allowsMultiTabComponents", allows);
+    }
+
+    /**
+     * Returns if a {@code Component} can be added multiple times.
+     *
+     * @return if a {@code Component} can be added multiple times
+     */
+    public boolean getAllowsMultiTabComponent(){
+	return Boolean.TRUE.equals(getClientProperty("allowsMultiTabComponents"));
+    }
+
+    /**
      * Returns the currently selected index for this tabbedpane.
      * Returns -1 if there is no currently selected tab.
      *
@@ -561,10 +582,23 @@
      * description: The tabbedpane's selected component.
      */
     public void setSelectedComponent(Component c) {
-        int index = indexOfComponent(c);
-        if (index != -1) {
-            setSelectedIndex(index);
-        } else {
+        int[] indexes = indexesOfComponent(c);
+        int sindex = getSelectedIndex();
+        int ind = -1;
+        for(int i = 0; i < indexes.length; i++){
+	    if(indexes[i] == sindex){
+		ind = i;
+                break;
+            }
+        }
+        if(indexes.length != 0){
+	    if(ind == -1 || (ind + 1) == indexes.length)
+		setSelectedIndex(indexes[0]);
+            else{
+		setSelectedIndex(indexes[ind + 1]);
+            }
+        }
+        else {
             throw new IllegalArgumentException("component not found in tabbed pane");
         }
     }
@@ -594,12 +628,14 @@
         // but we really should throw an exception because much of the
         // rest of the JTabbedPane implementation isn't designed to deal
         // with null components for tabs.
-        int removeIndex = indexOfComponent(component);
-        if (component != null && removeIndex != -1) {
-            removeTabAt(removeIndex);
-            if (newIndex > removeIndex) {
-                newIndex--;
-            }
+        if(!getAllowsMultiTabComponent()){
+            int removeIndex = indexOfComponent(component);
+            if (component != null && removeIndex != -1) {
+                removeTabAt(removeIndex);
+                if (newIndex > removeIndex) {
+                    newIndex--;
+                }
+	    }
         }
 
         int selectedIndex = getSelectedIndex();
@@ -609,8 +645,10 @@
 
 
         if (component != null) {
-            addImpl(component, null, -1);
-            component.setVisible(false);
+            if(!(component.getParent() == this) || !getAllowsMultiTabComponent()){
+                addImpl(component, null, -1);
+                component.setVisible(false);
+            }
         }
 
         if (pages.size() == 1) {
@@ -875,7 +913,10 @@
     public void remove(Component component) {
         int index = indexOfComponent(component);
         if (index != -1) {
-            removeTabAt(index);
+            while(index != -1){
+		removeTabAt(index);
+                index = indexOfComponent(component);
+	    }
         } else {
             // Container#remove(comp) invokes Container#remove(int)
             // so make sure JTabbedPane#remove(int) isn't called here
@@ -1387,13 +1428,10 @@
                 // REMIND(aim): this is really silly;
                 // why not if (page.component.getParent() == this) remove(component)
                 synchronized(getTreeLock()) {
-                    int count = getComponentCount();
-                    Component children[] = getComponents();
-                    for (int i = 0; i < count; i++) {
-                        if (children[i] == page.component) {
-                            super.remove(i);
-                        }
-                    }
+                    Component c = getComponentAt(index);
+                    if(c == page.component
+                          && indexesOfComponent(c).length == 1)
+                              super.remove(index);
                 }
                 component.setVisible(page.component.isVisible());
             } else {
@@ -1402,7 +1440,8 @@
                 component.setVisible(false);
             }
             page.component = component;
-            addImpl(component, null, -1);
+            if(!getAllowsMultiTabComponent() || component.getParent() != this)
+                addImpl(component, null, -1);
             
             revalidate();
         }
@@ -1548,6 +1587,29 @@
             }
         }
         return -1;
+    }
+
+    /**
+     * Returns the indexes of the tabs for the specified component.
+     * Returns an empty array if there are no tabs for this component.
+     *
+     * @param component the component for the tabs
+     * @return the tabs which match the component, or an
+     *         empty array if none are found.
+     */
+    public int[] indexesOfComponent(Component component){
+	java.util.List<Integer> indexes = new ArrayList<Integer>();
+        for(int i = 0; i < getTabCount(); i++) {
+            Component c = getComponentAt(i);
+            if ((c != null && c.equals(component)) ||
+                (c == null && c == component)) {
+                indexes.add(i);
+            }
+        }
+	int[] rv = new int[indexes.size()];
+	for(int i = 0; i < rv.length; i++)
+	    rv[i] = indexes.get(i);
+	return rv;
     }
 
     /**


JUnit TESTCASE :
import javax.swing.*;
import junit.framework.TestCase;
import junit.textui.TestRunner;
import static java.lang.System.out;


/**
 * BUGID 4176095 Clarification that no 2 Tabs in JTabbedPane can have the same component
 * This interesting rfe after being reviewed by the engineer appears to have
 * turned into an investigation as to whether or not the JTabbedPane can handle
 * multiple instances of the same component into the JTabbedPane.  It appears
 * the research never reached fruition.  The enhancement I have put together
 * explores the issue and it appears successfully to have done so.
 * I have added 3 new methods to JTabbedPane:
 * setAllowsMultiTabComponent
 * getAllowsMultiTabComponent
 * indexesOfComponent
 * I have also enhanced a couple methods, remove(Component),insertTabAt,
 * setSelectedComponent and setComponentAt.
 * so they are adjusted to this new reality. remove removes all instances
 * of the component. setSelectedComponent will cycle to the next index of
 * the same component if the Component being selected is the current selection.
 *
 * These methods are what I see as the 'issues', they need to be sophisticated
 * enought to work with a component being added multiple times, if configured
 * to allow it.
 *
 * ANTI-RATIONALE:
 * 1. JTabbedPane is not final, uncertain if new methods will collide with
 * a subclass.
 * 2. Questionable use case.  Do people really want this?  I can't come up
 * with one at the top of my head, but it appears someone wanted it back in 98.
 * Anyway, even if I can't come up with a use case the new capability is somewhat
 * exciting to witness.
 *
 * TESTING STRATEGY:
 * 1. Exercise all methods that have been changed to see if they work in the
 * mult-component reality.
 * 2. Exercise the new methods... they get exercised in the altered ones so
 * this is already happening.
 * 3. Look at the visuals, see how one of the JTabbedPane's bring up the
 * same component many times... success? :)
 *
 * FILES AFFECTED: javax.swing.JTabbedPane
 *
 * JDK VERSION
 * jdk-6-rc-bin-b64-linux-i586-15_dec_2005.bin
 *
 * test ran succesfully on a SUSE 7.3 Linux distribution
 *
 * Brian Harry
 * ###@###.###
 * Jan 25, 2006
 */
public class JTPane4176095 extends TestCase{


    public void createTab(JPanel parent, boolean multi){
        JTabbedPane jtp = new JTabbedPane();
        jtp.setAllowsMultiTabComponent(multi);
	parent.add(jtp);
        JButton zero = new JButton( "I wont show up in multi!");
        JButton jb1 = new JButton("Hi1!");
        JButton jb2 = new JButton("Hi2!");
	jtp.addTab( "zero", zero);
	jtp.addTab("first", jb1);
	jtp.addTab("second", jb2);
        jtp.addTab("third", jb1);
        for(int i : jtp.indexesOfComponent(jb1))
            out.println(i);
	JTextPane jte = new JTextPane();
	jte.setText( "Java is Coolll....");
	for(int i = 0; i < 5; i++)
	    jtp.addTab(String.valueOf(i), jte);
        if(multi)
           jtp.setComponentAt(0, jte);
    }

    public void testGUI(){

	JFrame jf = new JFrame();
	JPanel jp = new JPanel();
	JPanel multi = new JPanel();
	multi.setBorder(BorderFactory.createTitledBorder(multi.getBorder(), "Multi"));
	createTab(multi, true);
        jp.add(multi);
	JPanel single = new JPanel();
	single.setBorder(BorderFactory.createTitledBorder(single.getBorder(), "Single"));
	createTab(single, false);
	jp.add(single);
	jf.add(jp);
	jf.pack();
	jf.setVisible(true);
    }

    public void testRemove(){
	JTabbedPane jtp = new JTabbedPane();
	jtp.setAllowsMultiTabComponent(true);
	JButton jb1 = new JButton("Hi!");
	for(int i = 0; i < 5; i++)
	    jtp.addTab(String.valueOf(i), jb1);
        assertEquals(jtp.indexesOfComponent(jb1).length, 5);
        jtp.removeTabAt(0);
        assertEquals(jtp.indexesOfComponent(jb1).length, 4);
	jtp.remove(jb1);
	assertEquals(jtp.indexesOfComponent(jb1).length,0);

    }

    public void testRemove2(){
	JTabbedPane jtp = new JTabbedPane();
	jtp.setAllowsMultiTabComponent(false);
	JButton jb1 = new JButton("Hi!");
	jtp.addTab( "MOO", jb1);
        assertEquals(jtp.indexesOfComponent(jb1).length, 1);
        jtp.removeTabAt(0);
	assertEquals(jtp.indexesOfComponent(jb1).length,0);

    }

    public void testSelect(){
	JTabbedPane jtp = new JTabbedPane();
	jtp.setAllowsMultiTabComponent(true);
	JButton jb1 = new JButton("Hi!");
	for(int i = 0; i < 5; i++)
	    jtp.addTab(String.valueOf(i), jb1);
	// jtp.setSelectedIndex(0);
	for(int i = 0; i < 5; i++){
            out.println( "i is " + i + " should be " + jtp.getSelectedIndex());
            assertEquals(i, jtp.getSelectedIndex());
	    jtp.setSelectedComponent(jb1);
        }
	int[] indexes = new int[]{ 1,2,3,4,0};
	jtp.setSelectedIndex(1);
	for(int i: indexes){
            out.println( "i is " + i + " should be " + jtp.getSelectedIndex());
            assertEquals(i, jtp.getSelectedIndex());
	    jtp.setSelectedComponent(jb1);
        }

    }

    public void testSelect2(){
	JTabbedPane jtp = new JTabbedPane();
	jtp.setAllowsMultiTabComponent(false);
	JButton jb1 = new JButton("Hi!");
        jtp.addTab("yeah", jb1);
	for(int i = 0; i < 5; i++){
	    jtp.setSelectedComponent(jb1);
	    assertEquals(jb1, jtp.getSelectedComponent());
        }
    }

    public static void main(String ... args){

	Runnable run = new Runnable(){
		public void run(){
		    JTPane4176095 jtp = new JTPane4176095();
		    jtp.testGUI();
                    jtp.testRemove();
                    jtp.testRemove2();
		    jtp.testSelect();
                    jtp.testSelect2();
                }
	    };
	SwingUtilities.invokeLater(run);

    }

}


FIX FOR BUG NUMBER:
4176095

Comments
EVALUATION Contribution-forum:https://jdk-collaboration.dev.java.net/servlets/ProjectForumMessageView?forumID=1463&messageID=11033
25-01-2006

WORK AROUND Name: mf23781 Date: 09/24/98 Build a 'wrapper component" such as JPanel if you want same component to appear in different tab panes. ======================================================================
25-09-2004

EVALUATION Ideally I'd like to remove this restriction because it would be very useful to share components across multiple tabs. I'll need to investigate what the compatibility (if any) issues there may be with relaxing this restriction. amy.fowler@Eng 2000-03-15
15-03-2000