0

I wrote a custom table model for a JTable:

class MessageTableModel{

    private static Set<Message> messages = Collections.synchronizedSet(new TreeSet<Message>());

    .
    .
    .

    public void setMessages(List<Message> newMessages) {
        Collections.sort(newMessages);
        Iterator<Message> it = messages.iterator();
        while (it.hasNext()) {
            Message mess = it.next();
            if (!newMessages.contains(mess)) {
                it.remove();
                this.fireTableDataChanged();
            }
        }
        for (Message message : newMessages)
            if (message.isOrderStatusMessage())
                if (!messages.contains(message)) {
                    addMessage(message);
                }
        this.fireTableDataChanged();
    }

    public Message getMessageAtRow(int row){
        return (Message) messages.toArray()[row];
    }
}

The problem is that there's a thread that updates the table values, calling setMessages() method perodically. If I try to get a row during this update:

Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: -1

in this line:

return (Message) messages.toArray()[row];

There's a way to make the method getMessageAtRow() waits for the modifications to be done, or another solution for this problem?

mKorbel
  • 109,525
  • 20
  • 134
  • 319
elias
  • 15,010
  • 4
  • 40
  • 65

1 Answers1

3

Swing is single threaded. You can't modify the model on a thread outside of the event thread. The easiest way to fix this would be:

public void setMessages(List<Message> newMessages) {
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
            Collections.sort(newMessages);
        Iterator<Message> it = messages.iterator();
        while (it.hasNext()) {
            Message mess = it.next();
            if (!newMessages.contains(mess)) {
                it.remove();
                this.fireTableDataChanged();
            }
        }
        for (Message message : newMessages)
            if (message.isOrderStatusMessage())
                if (!messages.contains(message)) {
                    addMessage(message);
                }
        this.fireTableDataChanged();
    }
  )};
}

Also,fireTableDateChanged() should also only be called on the event thread.

Reverend Gonzo
  • 39,701
  • 6
  • 59
  • 77
  • 2
    See also [Concurrency in Swing](http://docs.oracle.com/javase/tutorial/uiswing/concurrency/) for more details. – Andrew Thompson Apr 16 '12 at 19:42
  • i figured out that, indeed, the problem is that the method table.getSelectedRow() sometimes returns -1, but I don't know why... – elias Apr 16 '12 at 20:16
  • 1
    @Elias you have to test `if (table.getSelectedRow() != -1)` before – mKorbel Apr 16 '12 at 20:27
  • This error is very strange, because every time at least one row is selected. However, I did an 'if' checking this condition. Maybe it's not the best solution, but works. Thanks. – elias Apr 16 '12 at 20:35
  • Another note: whenever you do `fireTableDateChanged()` it thinks everything changed, so it resets selected row. Instead you should do fireReowAdded/Removed, etc. – Reverend Gonzo Apr 17 '12 at 15:57