-
Bug
-
Resolution: Fixed
-
P3
-
5.0
-
b48
-
x86
-
linux
Name: gm110360 Date: 09/28/2004
FULL PRODUCT VERSION :
java version "1.5.0-rc"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0-rc-b63)
Java HotSpot(TM) Client VM (build 1.5.0-rc-b63, mixed mode, sharing)
ADDITIONAL OS VERSION INFORMATION :
Linux ims-360-9-lnx 2.4.21-4.0.1.ELsmp #1 SMP Thu Oct 23 01:27:36 EDT 2003 i686 i686 i386 GNU/Linux
A DESCRIPTION OF THE PROBLEM :
In my application, my users are experiencing exceptions or unexpected results after the JTable method selectAll() is used. It turns out that when data changes in my tables and the data set becomes smaller, rows that were previous selected beyond the end of the data set become selected.
This is due to the lead and anchor selection index not being reset in the DefaultListSelectionModel after the table data changes.
I believe the bug is in the JTable.tableChanged() method. It should check for an event of type, TableModelEvent.UPDATE; and update the selectionModel to have the lead and anchor selection index to the value, 0.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Compile the program below and run it.
1. Select the last row in the table, "Megan".
2. Press the button, "Switch Data". This will load the table with a new data set.
3. Press the button, "Get Selection List", this call JTable.getSelectedRows(). Notice in the terminal window that you will find a list of rows selected. There should be none.
4. Press the button, "Select All", which calls JTable.selectAll().
5. Press the button, "Get Selection List", again. Notice that the list includes all the rows, plus row 7 which doesn't exist.
Now,
6. Press the button, "Clear Data", which calls JTable.clearSelection().
7. Press the button, "Get Selection List. The list of selected rows is cleared.
8. Press the button, "Select All".
9. Press the button, "Get Selection List. Notice that the list again includes the row 7.
I've got debug coming out to the terminal to show what the selection and anchor indexes are when we enter the selectAll() method. You can see these values are not consistent with the TableModel.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
When I press select all, I only expect the rows in the data set to be selected.
ACTUAL -
I'm seeing rows beyond the end of the data set selected.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.AbstractTableModel;
import java.awt.*;
import java.awt.event.*;
public class TableSelectBug extends JFrame implements ActionListener{
MyTableModel myModel = new MyTableModel();
JTable table;
JButton switchDataButton;
JButton selectAllButton;
JButton clearAllButton;
JButton getSelectionListButton;
public TableSelectBug() {
super("TableSelectBug");
JPanel buttonPanel = new JPanel();
switchDataButton = new JButton("Switch Data");
switchDataButton.addActionListener(this);
selectAllButton = new JButton("Select All");
selectAllButton.addActionListener(this);
clearAllButton = new JButton("Clear All");
clearAllButton.addActionListener(this);
getSelectionListButton = new JButton("Get Selection List");
getSelectionListButton.addActionListener(this);
buttonPanel.add(switchDataButton, BorderLayout.NORTH);
buttonPanel.add(selectAllButton, BorderLayout.CENTER);
buttonPanel.add(clearAllButton, BorderLayout.CENTER);
buttonPanel.add(getSelectionListButton, BorderLayout.SOUTH);
table = new JTable(myModel) {
public void selectAll() {
ListSelectionModel selModel = getSelectionModel();
System.out.println("selModel.getLeadSelectionIndex() = " +
selModel.getLeadSelectionIndex() +
", selModel.getAnchorSelectionIndex() = " +
selModel.getAnchorSelectionIndex() +
", rowCount = " + getRowCount());
super.selectAll();
}
public void tableChanged(TableModelEvent e) {
System.out.println("tableChanged()");
if (e.getFirstRow() == TableModelEvent.HEADER_ROW) {
System.out.println("HEADER ROW");
}
if (e.getType() == TableModelEvent.INSERT) {
System.out.println("INSERT");
}
if (e.getType() == TableModelEvent.DELETE) {
System.out.println("DELETE");
}
if (e.getType() == TableModelEvent.UPDATE) {
System.out.println("UPDATE");
}
super.tableChanged(e);
}
};
table.setPreferredScrollableViewportSize(new Dimension(500, 200));
JScrollPane scrollPane = new JScrollPane(table);
JPanel panel = new JPanel();
panel.add(scrollPane, BorderLayout.CENTER);
add(buttonPanel, BorderLayout.NORTH);
add(panel, BorderLayout.CENTER);
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
public void actionPerformed(ActionEvent e) {
Object src = e.getSource();
if (src == switchDataButton) {
myModel.switchData();
myModel.fireTableDataChanged();
System.out.println("Switched Data Set");
} else if (src == selectAllButton) {
System.out.println("Select All");
table.selectAll();
} else if (src == clearAllButton) {
System.out.println("Clear All");
table.clearSelection();
} else if (src == getSelectionListButton) {
int[] list = table.getSelectedRows();
System.out.print("List of selected rows: ");
for (int i = 0; i < list.length; i++) {
System.out.print(list[i] + " ");
}
System.out.println("");
}
}
class MyTableModel extends AbstractTableModel {
final String[] columnNames = {"First Name"};
final Object[][] data1 = {
{"Mary"}, {"Alison"}, {"Kathy"}, {"Mark"}, {"Angela"},
{"Bob"}, {"Jack"}, {"Megan"}
};
final Object[][] data2 = {
{"Kay"}, {"Al"}, {"Jill"}, {"Dave"},
};
Object[][] data = data1;
Object[][] oldData = data2;
public void switchData() {
Object[][] tmp = data;
data = oldData;
oldData = tmp;
}
public int getColumnCount() {
return columnNames.length;
}
public int getRowCount() {
return data.length;
}
public String getColumnName(int col) {
return columnNames[col];
}
public Object getValueAt(int row, int col) {
return data[row][col];
}
}
public static void main(String[] args) {
TableSelectBug frame = new TableSelectBug();
frame.pack();
frame.setVisible(true);
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Subclass JTable and override the method, tableChanged() to look something like this:
public void tableChanged(TableModelEvent e) {
if (e.getType() == TableModelEvent.UPDATE) {
getSelectionModel().setAnchorSelectionIndex(0);
getSelectionModel().setLeadSelectionIndex(0);
}
super.tableChanged(e);
}
Release Regression From : 1.4.2
The above release value was the last known release where this
bug was known to work. Since then there has been a regression.
(Incident Review ID: 315719)
======================================================================
FULL PRODUCT VERSION :
java version "1.5.0-rc"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0-rc-b63)
Java HotSpot(TM) Client VM (build 1.5.0-rc-b63, mixed mode, sharing)
ADDITIONAL OS VERSION INFORMATION :
Linux ims-360-9-lnx 2.4.21-4.0.1.ELsmp #1 SMP Thu Oct 23 01:27:36 EDT 2003 i686 i686 i386 GNU/Linux
A DESCRIPTION OF THE PROBLEM :
In my application, my users are experiencing exceptions or unexpected results after the JTable method selectAll() is used. It turns out that when data changes in my tables and the data set becomes smaller, rows that were previous selected beyond the end of the data set become selected.
This is due to the lead and anchor selection index not being reset in the DefaultListSelectionModel after the table data changes.
I believe the bug is in the JTable.tableChanged() method. It should check for an event of type, TableModelEvent.UPDATE; and update the selectionModel to have the lead and anchor selection index to the value, 0.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Compile the program below and run it.
1. Select the last row in the table, "Megan".
2. Press the button, "Switch Data". This will load the table with a new data set.
3. Press the button, "Get Selection List", this call JTable.getSelectedRows(). Notice in the terminal window that you will find a list of rows selected. There should be none.
4. Press the button, "Select All", which calls JTable.selectAll().
5. Press the button, "Get Selection List", again. Notice that the list includes all the rows, plus row 7 which doesn't exist.
Now,
6. Press the button, "Clear Data", which calls JTable.clearSelection().
7. Press the button, "Get Selection List. The list of selected rows is cleared.
8. Press the button, "Select All".
9. Press the button, "Get Selection List. Notice that the list again includes the row 7.
I've got debug coming out to the terminal to show what the selection and anchor indexes are when we enter the selectAll() method. You can see these values are not consistent with the TableModel.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
When I press select all, I only expect the rows in the data set to be selected.
ACTUAL -
I'm seeing rows beyond the end of the data set selected.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.AbstractTableModel;
import java.awt.*;
import java.awt.event.*;
public class TableSelectBug extends JFrame implements ActionListener{
MyTableModel myModel = new MyTableModel();
JTable table;
JButton switchDataButton;
JButton selectAllButton;
JButton clearAllButton;
JButton getSelectionListButton;
public TableSelectBug() {
super("TableSelectBug");
JPanel buttonPanel = new JPanel();
switchDataButton = new JButton("Switch Data");
switchDataButton.addActionListener(this);
selectAllButton = new JButton("Select All");
selectAllButton.addActionListener(this);
clearAllButton = new JButton("Clear All");
clearAllButton.addActionListener(this);
getSelectionListButton = new JButton("Get Selection List");
getSelectionListButton.addActionListener(this);
buttonPanel.add(switchDataButton, BorderLayout.NORTH);
buttonPanel.add(selectAllButton, BorderLayout.CENTER);
buttonPanel.add(clearAllButton, BorderLayout.CENTER);
buttonPanel.add(getSelectionListButton, BorderLayout.SOUTH);
table = new JTable(myModel) {
public void selectAll() {
ListSelectionModel selModel = getSelectionModel();
System.out.println("selModel.getLeadSelectionIndex() = " +
selModel.getLeadSelectionIndex() +
", selModel.getAnchorSelectionIndex() = " +
selModel.getAnchorSelectionIndex() +
", rowCount = " + getRowCount());
super.selectAll();
}
public void tableChanged(TableModelEvent e) {
System.out.println("tableChanged()");
if (e.getFirstRow() == TableModelEvent.HEADER_ROW) {
System.out.println("HEADER ROW");
}
if (e.getType() == TableModelEvent.INSERT) {
System.out.println("INSERT");
}
if (e.getType() == TableModelEvent.DELETE) {
System.out.println("DELETE");
}
if (e.getType() == TableModelEvent.UPDATE) {
System.out.println("UPDATE");
}
super.tableChanged(e);
}
};
table.setPreferredScrollableViewportSize(new Dimension(500, 200));
JScrollPane scrollPane = new JScrollPane(table);
JPanel panel = new JPanel();
panel.add(scrollPane, BorderLayout.CENTER);
add(buttonPanel, BorderLayout.NORTH);
add(panel, BorderLayout.CENTER);
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
public void actionPerformed(ActionEvent e) {
Object src = e.getSource();
if (src == switchDataButton) {
myModel.switchData();
myModel.fireTableDataChanged();
System.out.println("Switched Data Set");
} else if (src == selectAllButton) {
System.out.println("Select All");
table.selectAll();
} else if (src == clearAllButton) {
System.out.println("Clear All");
table.clearSelection();
} else if (src == getSelectionListButton) {
int[] list = table.getSelectedRows();
System.out.print("List of selected rows: ");
for (int i = 0; i < list.length; i++) {
System.out.print(list[i] + " ");
}
System.out.println("");
}
}
class MyTableModel extends AbstractTableModel {
final String[] columnNames = {"First Name"};
final Object[][] data1 = {
{"Mary"}, {"Alison"}, {"Kathy"}, {"Mark"}, {"Angela"},
{"Bob"}, {"Jack"}, {"Megan"}
};
final Object[][] data2 = {
{"Kay"}, {"Al"}, {"Jill"}, {"Dave"},
};
Object[][] data = data1;
Object[][] oldData = data2;
public void switchData() {
Object[][] tmp = data;
data = oldData;
oldData = tmp;
}
public int getColumnCount() {
return columnNames.length;
}
public int getRowCount() {
return data.length;
}
public String getColumnName(int col) {
return columnNames[col];
}
public Object getValueAt(int row, int col) {
return data[row][col];
}
}
public static void main(String[] args) {
TableSelectBug frame = new TableSelectBug();
frame.pack();
frame.setVisible(true);
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Subclass JTable and override the method, tableChanged() to look something like this:
public void tableChanged(TableModelEvent e) {
if (e.getType() == TableModelEvent.UPDATE) {
getSelectionModel().setAnchorSelectionIndex(0);
getSelectionModel().setLeadSelectionIndex(0);
}
super.tableChanged(e);
}
Release Regression From : 1.4.2
The above release value was the last known release where this
bug was known to work. Since then there has been a regression.
(Incident Review ID: 315719)
======================================================================