Have a look at the code shown below. When I add two rows to the table and afterwards try to perform an undo operation I get a java.lang.ArrayIndexOutOfBoundsException: 11 >= 11. Can anyone please tell me what is wrong with the code?
import java.awt.*;
import java.awt.event.*;
import java.util.Vector;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
import javax.swing.text.TabExpander;
import javax.swing.undo.*;
public class UndoTable
{
public static void main(String[] args)
{
Object data[][] = {
{"AMZN", "Amazon", 41.28, "BUY"},
{"EBAY", "eBay", 41.57, "BUY"},
{"GOOG", "Google", 388.33, "SELL"},
{"MSFT", "Microsoft", 26.56, "SELL"},
{"NOK", "Nokia Corp", 17.13, "BUY"},
{"ORCL", "Oracle Corp.", 12.52, "BUY"},
{"SUNW", "Sun Microsystems", 3.86, "BUY"},
{"TWX", "Time Warner", 17.66, "SELL"},
{"VOD", "Vodafone Group", 26.02, "SELL"},
{"YHOO", "Yahoo!", 37.69, "BUY"}
};
String columns[] = {"Symbol", "Name", "Price", "Guidance"};
final JvUndoableTableModel tableModel = new JvUndoableTableModel(data, columns);
final JTable table = new JTable(tableModel);
JScrollPane pane = new JScrollPane(table);
JvUndoManager undoManager = new JvUndoManager();
tableModel.addUndoableEditListener(undoManager);
JMenu editMenu = new JMenu("Edit");
Action addrowaction = new AbstractAction("Add Row") {
private static final long serialVersionUID = 1433684360133156145L;
public void actionPerformed(ActionEvent e) {
tableModel.insertRow(table.getRowCount(), new Object[]{"YHOO", "Yahoo!", 37.69, "BUY"});
}
};
editMenu.add(undoManager.getUndoAction());
//editMenu.add(undoManager.getRedoAction());
JMenuBar menuBar = new JMenuBar();
menuBar.add(editMenu);
editMenu.add(addrowaction);
JFrame frame = new JFrame("Undoable JTable");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setJMenuBar(menuBar);
frame.add(pane, BorderLayout.CENTER);
frame.setSize(300, 150);
frame.setLocation(200, 300);
frame.setVisible(true);
}
}
class JvUndoableTableModel extends DefaultTableModel
{
public JvUndoableTableModel(Object[][] data, Object[] columnNames)
{
super(data, columnNames);
}
public Class getColumnClass(int column)
{
if (column >= 0 && column < getColumnCount())
return getValueAt(0, column).getClass();
return Object.class;
}
@Override
public void setValueAt(Object value, int row, int column)
{
setValueAt(value, row, column, true);
}
public void setValueAt(Object value, int row, int column, boolean undoable)
{
UndoableEditListener listeners[] = getListeners(UndoableEditListener.class);
if (undoable == false || listeners == null)
{
super.setValueAt(value, row, column);
return;
}
Object oldValue = getValueAt(row, column);
super.setValueAt(value, row, column);
JvCellEdit cellEdit = new JvCellEdit(this, oldValue, value, row, column);
UndoableEditEvent editEvent = new UndoableEditEvent(this, cellEdit);
for (UndoableEditListener listener : listeners)
listener.undoableEditHappened(editEvent);
}
//adding new cell to the table
public void insertRow(int row, Object[] rowData){
insertRow(row, rowData, true);
}
public void insertRow(int row,
Object[] rowData,boolean undoable){
UndoableEditListener listeners[] = getListeners(UndoableEditListener.class);
if (undoable == false || listeners == null)
{
super.insertRow(row, rowData);
return;
}
super.insertRow(row, rowData);
JvCellNew cellNew = new JvCellNew(this, rowData, row);
UndoableEditEvent editEvent = new UndoableEditEvent(this, cellNew);
for (UndoableEditListener listener : listeners)
listener.undoableEditHappened(editEvent);
}
//removing row from the table
public void removeRow(int row){
removeRow(row, true);
}
public void removeRow(int row, boolean undoable){
UndoableEditListener listeners[] = getListeners(UndoableEditListener.class);
if (undoable == false || listeners == null)
{
super.removeRow(row);
return;
}
super.removeRow(row);
JvCellNew cellNew = new JvCellNew(this, row);
UndoableEditEvent editEvent = new UndoableEditEvent(this, cellNew);
for (UndoableEditListener listener : listeners)
listener.undoableEditHappened(editEvent);
}
public void addUndoableEditListener(UndoableEditListener listener)
{
listenerList.add(UndoableEditListener.class, listener);
}
}
class JvCellEdit extends AbstractUndoableEdit
{
protected JvUndoableTableModel tableModel;
protected Object oldValue;
protected Object newValue;
protected int row;
protected int column;
public JvCellEdit(JvUndoableTableModel tableModel, Object oldValue, Object newValue, int row, int column)
{
this.tableModel = tableModel;
this.oldValue = oldValue;
this.newValue = newValue;
this.row = row;
this.column = column;
}
@Override
public String getPresentationName()
{
return "Cell Edit";
}
@Override
public void undo() throws CannotUndoException
{
super.undo();
tableModel.setValueAt(oldValue, row, column, false);
}
}
class JvCellNew extends AbstractUndoableEdit
{
/**
*
*/
private static final long serialVersionUID = 1L;
protected JvUndoableTableModel tableModel;
protected Object[] rowData;
protected int row;
public JvCellNew(JvUndoableTableModel tableModel, Object[] rowData, int row)
{
this.tableModel = tableModel;
this.rowData = rowData;
this.row = row;
}
public JvCellNew(JvUndoableTableModel tableModel, int row)
{
this.tableModel = tableModel;
this.row = row;
}
@Override
public String getPresentationName()
{
return "Cell New";
}
public void undo() throws CannotUndoException
{
super.undo();
tableModel.removeRow(row);
}
}
class JvUndoManager extends UndoManager
{
protected Action undoAction;
// protected Action redoAction;
public JvUndoManager()
{
this.undoAction = new JvUndoAction(this);
synchronizeActions(); // to set initial names
}
public Action getUndoAction()
{
return undoAction;
}
@Override
public boolean addEdit(UndoableEdit anEdit)
{
try
{
return super.addEdit(anEdit);
}
finally
{
synchronizeActions();
}
}
@Override
protected void undoTo(UndoableEdit edit) throws CannotUndoException
{
try
{
super.undoTo(edit);
}
finally
{
synchronizeActions();
}
}
protected void synchronizeActions()
{
undoAction.setEnabled(canUndo());
undoAction.putValue(Action.NAME, getUndoPresentationName());
}
}
class JvUndoAction extends AbstractAction
{
protected final UndoManager manager;
public JvUndoAction(UndoManager manager)
{
this.manager = manager;
}
public void actionPerformed(ActionEvent e)
{
try
{
manager.undo();
}
catch (CannotUndoException ex)
{
ex.printStackTrace();
}
}
}
Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: 11 >= 11 at java.util.Vector.removeElementAt(Unknown Source) at javax.swing.table.DefaultTableModel.removeRow(Unknown Source) at JvUndoableTableModel.removeRow(UndoTable.java:151) at JvUndoableTableModel.removeRow(UndoTable.java:142) at JvCellNew.undo(UndoTable.java:233) at javax.swing.undo.UndoManager.undoTo(Unknown Source) at JvUndoManager.undoTo(UndoTable.java:279) at javax.swing.undo.UndoManager.undo(Unknown Source) at JvUndoAction.actionPerformed(UndoTable.java:311) at javax.swing.AbstractButton.fireActionPerformed(Unknown Source) at javax.swing.AbstractButton$Handler.actionPerformed(Unknown Source) at javax.swing.DefaultButtonModel.fireActionPerformed(Unknown Source) at javax.swing.DefaultButtonModel.setPressed(Unknown Source) at javax.swing.AbstractButton.doClick(Unknown Source) at javax.swing.plaf.basic.BasicMenuItemUI.doClick(Unknown Source) at javax.swing.plaf.basic.BasicMenuItemUI$Handler.mouseReleased(Unknown Source) at java.awt.Component.processMouseEvent(Unknown Source) at javax.swing.JComponent.processMouseEvent(Unknown Source) at java.awt.Component.processEvent(Unknown Source) at java.awt.Container.processEvent(Unknown Source) at java.awt.Component.dispatchEventImpl(Unknown Source) at java.awt.Container.dispatchEventImpl(Unknown Source) at java.awt.Component.dispatchEvent(Unknown Source) at java.awt.LightweightDispatcher.retargetMouseEvent(Unknown Source) at java.awt.LightweightDispatcher.processMouseEvent(Unknown Source) at java.awt.LightweightDispatcher.dispatchEvent(Unknown Source) at java.awt.Container.dispatchEventImpl(Unknown Source) at java.awt.Window.dispatchEventImpl(Unknown Source) at java.awt.Component.dispatchEvent(Unknown Source) at java.awt.EventQueue.dispatchEventImpl(Unknown Source) at java.awt.EventQueue.access$200(Unknown Source) at java.awt.EventQueue$3.run(Unknown Source) at java.awt.EventQueue$3.run(Unknown Source) at java.security.AccessController.doPrivileged(Native Method) at java.security.ProtectionDomain$1.doIntersectionPrivilege(Unknown Source) at java.security.ProtectionDomain$1.doIntersectionPrivilege(Unknown Source) at java.awt.EventQueue$4.run(Unknown Source) at java.awt.EventQueue$4.run(Unknown Source) at java.security.AccessController.doPrivileged(Native Method) at java.security.ProtectionDomain$1.doIntersectionPrivilege(Unknown Source) at java.awt.EventQueue.dispatchEvent(Unknown Source) at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source) at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source) at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source) at java.awt.EventDispatchThread.pumpEvents(Unknown Source) at java.awt.EventDispatchThread.pumpEvents(Unknown Source) at java.awt.EventDispatchThread.run(Unknown Source)
The problem seems to be related to the fact that the execution of an undo causes an action that is stored as an undoable action again. (This is particularly confusing due to the fact that removing a row creates a new JvCellNew, which, according to the name, should probably indicate that a new row was added)
The "symptom" may be solved by specifying that an action that is undone may not be undone again:
class JvCellNew
{
....
public void undo() throws CannotUndoException
{
super.undo();
// Pass in "false" as the second argument here, to indicate
// that this row removal should NOT cause an undoable edit
tableModel.removeRow(row, false);
}
}
Something like this can be found out more easily when printing some debug information, e.g. with
class JvCellNew extends AbstractUndoableEdit
{
....
@Override
public String getPresentationName()
{
return "Cell New "+row; // Print the row number
}
// Provide a useful toString implementation
@Override
public String toString()
{
return getPresentationName();
}
}
class JvUndoManager extends UndoManager
{
....
@Override
public boolean addEdit(UndoableEdit anEdit)
{
try
{
boolean b = super.addEdit(anEdit);
// Print the current state of this manager
System.out.println("After adding "+anEdit);
for (UndoableEdit e : this.edits)
{
System.out.println(e);
}
return b;
}
finally
{
synchronizeActions();
}
}
@Override
protected void undoTo(UndoableEdit edit) throws CannotUndoException
{
try
{
super.undoTo(edit);
// Print the current state of this manager
System.out.println("After undo to "+edit);
for (UndoableEdit e : this.edits)
{
System.out.println(e);
}
}
finally
{
synchronizeActions();
}
}
}
Additionally, you should at least consider differentiating between an undoable edit that describes adding a row, and an undoable edit that describes removing a row.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With