-
Bug
-
Resolution: Unresolved
-
P4
-
None
-
6u10
-
x86
-
windows_vista
FULL PRODUCT VERSION :
java version "1.6.0_10"
Java(TM) SE Runtime Environment (build 1.6.0_10-b33)
Java HotSpot(TM) Client VM (build 11.0-b15, mixed mode, sharing)
ADDITIONAL OS VERSION INFORMATION :
Windows Vista, however should be irrelevant
EXTRA RELEVANT SYSTEM CONFIGURATION :
irrelevant
A DESCRIPTION OF THE PROBLEM :
DefaultCellEditor in the JCheckBox configuration will callback to the JTable in the call to getTableCellEditorComponent in order to retrieve the TableCellRenderer for the specific cell being edited. However it will forward its own value parameter to the TableCellRenderer's getTableCellRendererComponent instead of querying the table for the value at the given location.
This can lead to all kind of severe problems and exceptions because the DefaultCellEditor instance will be given a Boolean value as the value even though the TableModel does not actually contain a plain Boolean at that cell:
Consider the following scenario:
The TableModel contains instances of a custom class which shall be visualized as a Boolean value but of course they are not of type Boolean.
Now the developer will register a custom CellRenderer implementation that downcasts the object to the custom class and return a JCheckBox to indicate the boolean state of the object.
In order to make the model editable, the developer chooses to implement a custom CellEditor implementation that also downcasts the object to the custom class, retrieves the boolean state and passes a new Boolean to a DefaultCellEditor instance to delegate the actual work to.
The problem now is that during the delegation call to DefaultCellEditor's getTableCellEditorComponent, DefaultCellEditor will obtain the renderer for that cell from the table and pass the Boolean to the custom renderer which would actually expect an instance of the custom class. This will lead to a ClassCastException and thus a crash in the table cell editing process.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Execute the below code using jdk6u10 and try clicking on the checkbox to edit the value in the JTable.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The checkbox should toggle its value.
ACTUAL -
Nothing happens in the GUI, instead an exception will be thrown.
ERROR MESSAGES/STACK TRACES THAT OCCUR :
Exception in thread "AWT-EventQueue-0" java.lang.ClassCastException: java.lang.Boolean cannot be cast to demo.option.DefaultCellEditorTest$MyBoolValueHolder
at demo.option.DefaultCellEditorTest$2.getTableCellRendererComponent(DefaultCellEditorTest.java:49)
at javax.swing.DefaultCellEditor.getTableCellEditorComponent(DefaultCellEditor.java:259)
at demo.option.DefaultCellEditorTest$3.getTableCellEditorComponent(DefaultCellEditorTest.java:57)
at javax.swing.JTable.prepareEditor(JTable.java:5784)
at javax.swing.JTable.editCellAt(JTable.java:3492)
at javax.swing.plaf.basic.BasicTableUI$Handler.adjustSelection(BasicTableUI.java:1084)
at javax.swing.plaf.basic.BasicTableUI$Handler.mousePressed(BasicTableUI.java:1014)
at java.awt.AWTEventMulticaster.mousePressed(AWTEventMulticaster.java:263)
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
package bug;
import javax.swing.JFrame;
import javax.swing.JTable;
import javax.swing.JCheckBox;
import javax.swing.DefaultCellEditor;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;
import java.awt.Component;
public class DefaultCellEditorTest {
public static void main(String[] args) {
final JFrame frame = new JFrame();
// A simplistic TableModel implementation that uses custom classes that will be
// rendered and editable like boolean values using JCheckBox instances.
final JTable table = new JTable(new AbstractTableModel() {
// the table data
MyBoolValueHolder myBoolValueHolder = new MyBoolValueHolder(true);
public int getRowCount() {
return 1;
}
public int getColumnCount() {
return 1;
}
public Object getValueAt(int rowIndex, int columnIndex) {
return myBoolValueHolder;
}
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
this.myBoolValueHolder = (MyBoolValueHolder) aValue;
}
public boolean isCellEditable(int rowIndex, int columnIndex) {
return true;
}
public Class<?> getColumnClass(int columnIndex) {
return MyBoolValueHolder.class;
}
});
// use a simple TableCellRenderer
table.setDefaultRenderer(MyBoolValueHolder.class, new TableCellRenderer() {
private JCheckBox cb = new JCheckBox();
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
int row, int column) {
// this is were the below code will trigger DefaultCellEditor to pass in a Boolean instance
// instead of a MyBoolValueHolder instance -> ClassCastException
cb.setSelected(((MyBoolValueHolder)value).isTrue());
return cb;
}
});
// Reuse the available DefaultCellEditor for editing booleans:
// For simplicity this code simply subclasses DefaultCellEditor, the original
// code uses delegation and implements TableCellEditor more cleanly, but the problem remains.
table.setDefaultEditor(MyBoolValueHolder.class, new DefaultCellEditor(new JCheckBox()) {
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
final Boolean boolValue = ((MyBoolValueHolder) value).isTrue() ? Boolean.TRUE : Boolean.FALSE;
// this call will trigger the ClassCasException in the above renderer
return super.getTableCellEditorComponent(table, boolValue, isSelected, row, column);
}
public Object getCellEditorValue() {
return new MyBoolValueHolder(Boolean.TRUE.equals(super.getCellEditorValue()));
}
});
frame.getContentPane().add(table);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
static class MyBoolValueHolder {
private boolean b;
public MyBoolValueHolder(boolean b) {
this.b = b;
}
public boolean isTrue() {
return b;
}
public void setTrue(boolean b) {
this.b = b;
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
The workaround is to simulate the old behavior of the class by overriding DefaultCellEditor like so:
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
delegate.setValue(value);
return editorComponent;
}
java version "1.6.0_10"
Java(TM) SE Runtime Environment (build 1.6.0_10-b33)
Java HotSpot(TM) Client VM (build 11.0-b15, mixed mode, sharing)
ADDITIONAL OS VERSION INFORMATION :
Windows Vista, however should be irrelevant
EXTRA RELEVANT SYSTEM CONFIGURATION :
irrelevant
A DESCRIPTION OF THE PROBLEM :
DefaultCellEditor in the JCheckBox configuration will callback to the JTable in the call to getTableCellEditorComponent in order to retrieve the TableCellRenderer for the specific cell being edited. However it will forward its own value parameter to the TableCellRenderer's getTableCellRendererComponent instead of querying the table for the value at the given location.
This can lead to all kind of severe problems and exceptions because the DefaultCellEditor instance will be given a Boolean value as the value even though the TableModel does not actually contain a plain Boolean at that cell:
Consider the following scenario:
The TableModel contains instances of a custom class which shall be visualized as a Boolean value but of course they are not of type Boolean.
Now the developer will register a custom CellRenderer implementation that downcasts the object to the custom class and return a JCheckBox to indicate the boolean state of the object.
In order to make the model editable, the developer chooses to implement a custom CellEditor implementation that also downcasts the object to the custom class, retrieves the boolean state and passes a new Boolean to a DefaultCellEditor instance to delegate the actual work to.
The problem now is that during the delegation call to DefaultCellEditor's getTableCellEditorComponent, DefaultCellEditor will obtain the renderer for that cell from the table and pass the Boolean to the custom renderer which would actually expect an instance of the custom class. This will lead to a ClassCastException and thus a crash in the table cell editing process.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Execute the below code using jdk6u10 and try clicking on the checkbox to edit the value in the JTable.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The checkbox should toggle its value.
ACTUAL -
Nothing happens in the GUI, instead an exception will be thrown.
ERROR MESSAGES/STACK TRACES THAT OCCUR :
Exception in thread "AWT-EventQueue-0" java.lang.ClassCastException: java.lang.Boolean cannot be cast to demo.option.DefaultCellEditorTest$MyBoolValueHolder
at demo.option.DefaultCellEditorTest$2.getTableCellRendererComponent(DefaultCellEditorTest.java:49)
at javax.swing.DefaultCellEditor.getTableCellEditorComponent(DefaultCellEditor.java:259)
at demo.option.DefaultCellEditorTest$3.getTableCellEditorComponent(DefaultCellEditorTest.java:57)
at javax.swing.JTable.prepareEditor(JTable.java:5784)
at javax.swing.JTable.editCellAt(JTable.java:3492)
at javax.swing.plaf.basic.BasicTableUI$Handler.adjustSelection(BasicTableUI.java:1084)
at javax.swing.plaf.basic.BasicTableUI$Handler.mousePressed(BasicTableUI.java:1014)
at java.awt.AWTEventMulticaster.mousePressed(AWTEventMulticaster.java:263)
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
package bug;
import javax.swing.JFrame;
import javax.swing.JTable;
import javax.swing.JCheckBox;
import javax.swing.DefaultCellEditor;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;
import java.awt.Component;
public class DefaultCellEditorTest {
public static void main(String[] args) {
final JFrame frame = new JFrame();
// A simplistic TableModel implementation that uses custom classes that will be
// rendered and editable like boolean values using JCheckBox instances.
final JTable table = new JTable(new AbstractTableModel() {
// the table data
MyBoolValueHolder myBoolValueHolder = new MyBoolValueHolder(true);
public int getRowCount() {
return 1;
}
public int getColumnCount() {
return 1;
}
public Object getValueAt(int rowIndex, int columnIndex) {
return myBoolValueHolder;
}
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
this.myBoolValueHolder = (MyBoolValueHolder) aValue;
}
public boolean isCellEditable(int rowIndex, int columnIndex) {
return true;
}
public Class<?> getColumnClass(int columnIndex) {
return MyBoolValueHolder.class;
}
});
// use a simple TableCellRenderer
table.setDefaultRenderer(MyBoolValueHolder.class, new TableCellRenderer() {
private JCheckBox cb = new JCheckBox();
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
int row, int column) {
// this is were the below code will trigger DefaultCellEditor to pass in a Boolean instance
// instead of a MyBoolValueHolder instance -> ClassCastException
cb.setSelected(((MyBoolValueHolder)value).isTrue());
return cb;
}
});
// Reuse the available DefaultCellEditor for editing booleans:
// For simplicity this code simply subclasses DefaultCellEditor, the original
// code uses delegation and implements TableCellEditor more cleanly, but the problem remains.
table.setDefaultEditor(MyBoolValueHolder.class, new DefaultCellEditor(new JCheckBox()) {
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
final Boolean boolValue = ((MyBoolValueHolder) value).isTrue() ? Boolean.TRUE : Boolean.FALSE;
// this call will trigger the ClassCasException in the above renderer
return super.getTableCellEditorComponent(table, boolValue, isSelected, row, column);
}
public Object getCellEditorValue() {
return new MyBoolValueHolder(Boolean.TRUE.equals(super.getCellEditorValue()));
}
});
frame.getContentPane().add(table);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
static class MyBoolValueHolder {
private boolean b;
public MyBoolValueHolder(boolean b) {
this.b = b;
}
public boolean isTrue() {
return b;
}
public void setTrue(boolean b) {
this.b = b;
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
The workaround is to simulate the old behavior of the class by overriding DefaultCellEditor like so:
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
delegate.setValue(value);
return editorComponent;
}