By Chris Browne


2012-02-03 17:40:49 8 Comments

I have a JTable populated with a custom DataModel (pasted below) and when I call the populate() method, it appears to populate the table with duplicate data - each row is filled with the same value over and over again. However, on closer inspection (by simply println()ing the 'data' field), the data model isn't at fault - it holds correct data, in the format I expect. What gives?

import java.util.ArrayList;    
import javax.swing.table.AbstractTableModel;

@SuppressWarnings("serial") // we don't expect this app to ever use serialized classes.  EVER.
public class CollectionDataModel extends AbstractTableModel {
    private ArrayList<ArrayList<String>> data;

    public CollectionDataModel() {
        data = new ArrayList<ArrayList<String>>();
    }

    @Override
    public int getColumnCount() {
        if(data.isEmpty()) return 0;
        return data.get(0).size();
    }

    @Override
    public int getRowCount() {
        return data.size();
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        if(rowIndex > getRowCount()) return null;
        if(columnIndex > getColumnCount()) return null;
        return data.get(rowIndex).get(columnIndex);
    }

    public void populate(Collection c) {
        data.clear();
        for(Item i : c.getItems()) {
            ArrayList<String> row = new ArrayList<String>();
            for(Property p : i.getProperties().values()) {
                row.add(p.toString());
            }
            data.add(row);
        }
        fireTableDataChanged();
    }

}

3 comments

@trashgod 2012-02-03 19:28:49

Here's a complete example that may prove helpful. As the sample Map is unmodifiable, I refer you to @mKorbel's example on how to override isCellEditable() and setValueAt().

import java.awt.EventQueue;
import java.awt.GridLayout;
import java.util.Map;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;

/** @see https://stackoverflow.com/questions/9132987 */
public class EnvTableTest extends JPanel {

    public EnvTableTest() {
        this.setLayout(new GridLayout());
        this.add(new JScrollPane(new JTable(new EnvDataModel())));
    }

    private static class EnvDataModel extends AbstractTableModel {

        private Map<String, String> data = System.getenv();
        private String[] keys;

        public EnvDataModel() {
            keys = data.keySet().toArray(new String[data.size()]);
        }

        @Override
        public String getColumnName(int col) {
            if (col == 0) {
                return "Key";
            } else {
                return "Value";
            }
        }

        @Override
        public int getColumnCount() {
            return 2;
        }

        @Override
        public int getRowCount() {
            return data.size();
        }

        @Override
        public Object getValueAt(int row, int col) {
            if (col == 0) {
                return keys[row];
            } else {
                return data.get(keys[row]);
            }
        }
    }

    private void display() {
        JFrame f = new JFrame("EnvTableTest");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.add(this);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                new EnvTableTest().display();
            }
        });
    }
}

@Chris Browne 2012-02-03 20:07:11

This example assumes I know (at least) the number of columns upfront. My code does not require foreknowledge of column count (for a reason). Thank you for your help nonetheless. I will implement the missing methods and rearrange my datatypes thanks to this answer. Accepting in lieu of anything better, going to use the advice given here to build my own solution. Thanks again.

@trashgod 2012-02-03 20:27:52

Excellent. Indeed, my example was contrived to be minimal, but it may serve as an sscce foundation for any future questions that may arise. Also consider List<List<String>> data = new ArrayList<List<String>>(). Looking closer, I see your column count is data.get(0).size(). Does it matter if the inner lists have differing lengths?

@Chris Browne 2012-02-04 18:40:57

Good point, the inner lists shouldn't have different lengths but may do (in the case of corrupt data being loaded, I do have safeguards against that but it's better to be safe than sorry I suppose) - I shall fix that. Also, thank you for your tips on List vs ArrayList, I think it's obvious I'm new to OOP! Thanks again for your help! :)

@Joop Eggen 2012-02-03 17:54:56

You could try to make the changes of populate more atomic.

public void populate(Collection c) {
    ArrayList<ArrayList<String>> data2 = new  ArrayList<ArrayList<String>>();
    for(Item i : c.getItems()) {
        ArrayList<String> row = new ArrayList<String>();
        for(Property p : i.getProperties().values()) {
            row.add(p.toString());
        }
        data2.add(row);
    }
    data = data2;
    fireTableDataChanged();
}

I am guessing that populate is called again before a prior populate call finished. And probably c is changed during its iteration.

@Hovercraft Full Of Eels 2012-02-03 17:56:47

I'm sorry, but how would this help?

@JB Nizet 2012-02-03 17:58:05

I don't see what it would change. Everything in Swing is done in the event dispatch thread, so the table has no way to see any intermediate state.

@Joop Eggen 2012-02-03 18:16:18

populate does not relate to the event thread. It probably is called whenever a cell is changed/added. That leaves the model in disarray, showing only a part. But more seriously, when the collection c is traversed when a change to the collection occurs, one may get things like duplicate values.

@Chris Browne 2012-02-03 18:19:57

but I don't get duplicate values in data, I said that in my question - I only get duplicate values when the JTable is displayed, the actual data variable, when printed, is intact. It's the difference between the 'data' variable and what the JTable shows that I'm trying to debug.

@Joop Eggen 2012-02-03 18:23:25

@HovercraftFullOfEels you are right, the code solves an other concurrency fault; but the text mentions the fallacy of naively using c under concurrency.

@Joop Eggen 2012-02-03 18:33:06

@ChrisBrowne sorry, -1 deserved. Could you give my code a try? Maybe even do public synchronized void populate? I still think that repeatedly doing data.clear and refilling data has something to do with it. Simply logging would help to debug.

@Chris Browne 2012-02-03 18:39:18

Any suggestions on what to log, other than 'data'? I did give your code a try, it didn't improve the situation, sorry.

@mKorbel 2012-02-03 18:07:14

1) your TableModel is un_completed, I miss there lots or required methods for JTable's life_cycle, starting with TableHeader etc.

2) since there are lots of AbstactTableModels based on HashMap, I'd suggest to return arrays type implemented in API directly

Vector<Vector<Object or String>> data; 

String[][] or Object[][] 

instead of

ArrayList<ArrayList<String>> data;

simple explanations is that XxxList returs column and Vector or String[] returns Row

3) I'd suggest to use DefaultTableModel directly then you'll never need to solve duplicates or missed column/row

@Chris Browne 2012-02-03 18:13:35

I'm terribly sorry, but I don't really understand your answer. Are you saying I'm missing methods from my TableModel? If so, which methods, specifically? I'm not getting any warnings or errors about unimplemented methods...

@mKorbel 2012-02-03 18:17:39

hmmm wait minute I have to look for AbstractTableModel based on Vector on this forum, wrote AbstractTableModel (override methods from DefaultTableModel) not easy job and required basic knowledge about JTable and DefaultTableModel

@Chris Browne 2012-02-03 18:20:55

OK. I'm not really following you, but it sounds like you're going to try and update your answer with more information so I'll check back in a few minutes. Thanks for trying to help me :)

@mKorbel 2012-02-03 18:22:16

not 100pct correct AbstractTableModel stackoverflow.com/a/6656279/714968

@Chris Browne 2012-02-03 18:25:25

are you suggesting I create a custom renderer for the AbstractTableModel I created? Is this really necessary?

@mKorbel 2012-02-03 18:28:20

Renderer has nothing to do with Model on the bottom is AbstractTableModel, with minimum methods and events fired

@Chris Browne 2012-02-03 18:31:36

I'm really sorry, I'm finding it very difficult to understand you. I don't get any warnings for the AbstractTableModel I created, are you saying the extra methods in the one you linked to are necessary but not in the interface? I find that a little difficult to believe, which is why I'm inclined to believe I'm simply misunderstanding you.

@mKorbel 2012-02-03 18:41:56

again before thinking about override methods from DefaultTableModel into own AbstractTableModel you have to know more than something about JTable / how TableModel works

@Chris Browne 2012-02-03 18:44:02

That last comment was unhelpful and patronising. Please keep your comments on-topic and constructive in future.

@mKorbel 2012-02-03 19:15:18

simple I tried to be on-topic, but you're mixing methods, too hard starting from the end

@trashgod 2012-02-03 19:32:04

@mKorbel: +1 for DefaultTableModel as an example of implementing AbstractTableModel correctly. I took the liberty of citing your example in my answer.

@trashgod 2012-02-03 19:41:00

@ChrisBrowne: I'm certain that mKorbel's comment was meant to be helpful. Native language may separate us, but we can share a common lingua Java.

Related Questions

Sponsored Content

86 Answered Questions

52 Answered Questions

[SOLVED] Sort a Map<Key, Value> by values

28 Answered Questions

[SOLVED] How do I determine whether an array contains a particular value in Java?

  • 2009-07-15 00:03:21
  • Mike Sickler
  • 1885378 View
  • 2185 Score
  • 28 Answer
  • Tags:   java arrays

26 Answered Questions

[SOLVED] How to get an enum value from a string value in Java?

  • 2009-03-02 22:56:34
  • Malachi
  • 1057386 View
  • 1885 Score
  • 26 Answer
  • Tags:   java enums

1 Answered Questions

4 Answered Questions

[SOLVED] How to override getValueAt for HashSet?

0 Answered Questions

Stop cell editing in JTable with JRadioButton as cell renderers

1 Answered Questions

Java Serialization/De-serialization giving null object references

2 Answered Questions

[SOLVED] JTable, TableModel and TableColumnModel - Weird thing going on

Sponsored Content