Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-4176095

Clarification that no 2 Tabs in JTabbedPane can have same component

XMLWordPrintable

    • generic, x86
    • generic, windows_nt

      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

            Unassigned Unassigned
            miflemi Mick Fleming
            Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

              Created:
              Updated:
              Resolved:
              Imported:
              Indexed: