3

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)

Dipti
  • 91
  • 1
  • 5
  • Please post the full stacktrace. Present your problem as clear as possible. All I know is "something about undo and ArrayIndexOutOfBounds", not much. Have you debugged your code? You should at least point out where it is failing and why you don't understand/know how to fix it. Don't make (each of) us spend extra time figuring out something you could show from the beginning. Posting a code and saying "go find out what's wrong with it" is not time-efficient for us (and you don't create any empathy with the reader) so you'll get less people involved with your problem. – DSquare Apr 09 '14 at 11:50
  • Stacktrace tells you where it fails, post it. – Marco Acierno Apr 09 '14 at 11:56

1 Answers1

2

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.

Marco13
  • 53,703
  • 9
  • 80
  • 159