javaswingmodel-view-controllerjtablelistselectionlistener

When selecting row 0 in JTable the selection handler goes mad


I'm having problems with the selection handler for a JTable.

The table is updated every 15th second from a refresh thread in the Control.

I want to select a row in my JTable and extract a column contents that I will use to build a filename from.

As long as row 0 is not selected during a refresh everything works ok. But if row 0 is selected when the refresh is triggered it looks like it jumps between the setAppserverData procedure and the event handler until it exceeds the rowCount for the table and I get an IndexOutOfBoundsException.

I'm a Java newbee and this is on the edge of my knowledge, so I'm having a hard time figuring out what is wrong with the code. My gut feeling is that it has something to do with me trying to achieve a MVC structure and I fail to separate the code in the correct way.

// The controller

// Appserver worker
class AppserverWorker extends SwingWorker<Integer, Integer>{
    protected Integer doInBackground() throws Exception{

        // Check appservers
        while(true){
            theModel.readAppservData();

            // Update action buttons
            try {
                if(!theModel.isStatusOk()){
                    theGui.actionButtonPanel.setAppServBtnColor("RED");
                    //              theGui.actionButtonPanel.setButtonFlashOn();
                }else theGui.actionButtonPanel.setAppServBtnColor("GREEN");
            } catch (IllegalArgumentException e1) {
                sysLogger.logMsg("SEVERE",e1.getMessage());
                JOptionPane.showMessageDialog(null, e1.getMessage());
            }

            // Update GUI
            theGui.extAppServPanel.setAppserverData(theModel.getAppservData());

            // Sleep refresh time
            // TODO read refresh from init table
            try {
                Thread.sleep(appServRefreshTime);
            } catch (InterruptedException e) {}
        }
    }

// List selection handler
class SharedListSelectionHandler implements ListSelectionListener {
    public void valueChanged(ListSelectionEvent e) {
        if (!e.getValueIsAdjusting()){                  // To avoid double trigger of listener
            System.out.println(">>>>>>>>>  Selection list handler >>>>>>>>");
            String fileName = null;
            String fileExt  = ".txt";
            ListSelectionModel lsm = (ListSelectionModel)e.getSource();
            try {       
                int selectedRow = lsm.getAnchorSelectionIndex();                                // Get the row clicked
                if (selectedRow>=0){                                                            // If -1 no row selected
                    fileName = theGui.extAppServPanel.getAppservName(selectedRow)+fileExt;      // Get appserver name and build file name
                    theModel.readCollectFile(InitParameters.collectFilePath, fileName);         // Read collect file
                    theGui.extAppServPanel.setAppservInfo(theModel.getCollectFileContent());    // Fill the text panel with collect file
                }
            }
            catch(FileNotFoundException e1){
                sysLogger.logMsg("SEVERE",this.getClass().getSimpleName()+": Collect file "+fileName+" is missing");
                JOptionPane.showMessageDialog(null, "Collect file "+fileName+" is missing");
            }

            catch(IOException e1){
                sysLogger.logMsg("SEVERE",this.getClass().getSimpleName()+": System error: An I/O error occurred");
                System.err.println("System error: An I/O error occurred");
                System.exit(0);;
            }
        }
    }
}

// The view

public class ExtAppServPanel extends JPanel {

private JTable table;
private DefaultTableModel model;

private JTextArea textAreaInfo;
private JButton btnSaveInfo;
private List colData;

public boolean ignoreTableChanges = false;

/**
 * Constructor
 */
public ExtAppServPanel() {
    System.out.println(this.getClass().getSimpleName()+": Constructor");

    setLayout(new MigLayout("", "[350:376.00,grow,leading]", "[100px:100,grow][][48.00,grow][]"));
    this.setBorder(new TitledBorder(UIManager.getBorder("TitledBorder.border"), "Application servers info", TitledBorder.LEADING, TitledBorder.TOP, null, new Color(0, 0, 0)));

    // Set up table
    String[] columnNames = {"Server name", "Type","Status"};
    model = new DefaultTableModel(null,columnNames);
    table = new  JTable(model){ 

        // Color code rows
        @Override
        public Component prepareRenderer(TableCellRenderer renderer, int row, int col) {
            Component comp = super.prepareRenderer(renderer, row, col);

            if (!isRowSelected(row)){
                comp.setBackground(getBackground());
                int modelRow = convertRowIndexToModel(row);
                String type = (String)getModel().getValueAt(modelRow, 2);
                if ("FAIL".equals(type)) comp.setBackground(Color.RED);
                if ("WARNING".equals(type)) comp.setBackground(Color.YELLOW);
            }
            return comp;
        }
    };

    // Set grid
    table.setGridColor(Color.LIGHT_GRAY);

    // Set width and alignment
    table.getColumnModel().getColumn(1).setMinWidth(200);
    table.getColumnModel().getColumn(1).setMaxWidth(200);
    table.getColumnModel().getColumn(1).setPreferredWidth(200);

    table.getColumnModel().getColumn(2).setMinWidth(75);
    table.getColumnModel().getColumn(2).setMaxWidth(75);
    table.getColumnModel().getColumn(2).setPreferredWidth(75);


    // Add scrollpane
    JScrollPane scrollPane = new JScrollPane(table);
    scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
    scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
    table.setFillsViewportHeight(true);
    this.add(scrollPane, "cell 0 0,grow");

    // Set up text area
    JLabel lblDetails = new JLabel("Details");
    this.add(lblDetails, "cell 0 1,alignx leading");

    textAreaInfo = new JTextArea();
    textAreaInfo.setBackground(Color.BLACK);
    textAreaInfo.setForeground(Color.GREEN);
    textAreaInfo.setEditable(false);
    textAreaInfo.setFont(new Font("Lucida Console",Font.PLAIN,10));

    JScrollPane scrollPane_1 = new JScrollPane();
    scrollPane_1.setViewportView(textAreaInfo);
    this.add(scrollPane_1, "cell 0 2,grow");

    // Save button
    btnSaveInfo = new JButton("Save");
    add(btnSaveInfo, "cell 0 3,alignx right");
}

/*
 * Setters and getters
 */
public void setAppserverData(ArrayList<ArrayList<String>> dataRecord) {
    ArrayList<String> dataCol = new ArrayList<String>();

    // Init values array from result set
    try {
        model.setRowCount(0);   // <<<<<<<<<<  Problem
        String[] arrayRow;
        String status = "Ok";

        // Get first row from list
        dataCol = dataRecord.get(0);                    
        arrayRow = new String[dataCol.size()];  
        arrayRow[0] = dataCol.get(2);
        arrayRow[1] = dataCol.get(1);
        arrayRow[2] = dataCol.get(4);

        for (int i = 0; i < dataRecord.size(); i++){
            dataCol = dataRecord.get(i);

            // Check status
            if (dataCol.get(4).toUpperCase().equals("FAIL")){
                status = "FAIL";
            }else if (dataCol.get(4).toUpperCase().equals("WARNING")){
                status = "WARNING";
            }

            if (!dataCol.get(2).toUpperCase().equals(arrayRow[0].toUpperCase())){   
                arrayRow[2] = status;                           // Override status
                System.out.println(">>>>>>>>>  Updating table >>>>>>>>");
                model.addRow(arrayRow);                         // Add row to table
                arrayRow = new String[dataCol.size()];  
                arrayRow[0] = dataCol.get(2);
                arrayRow[1] = dataCol.get(1);
                arrayRow[2] = dataCol.get(4);
                status = "Ok";
            }
        }
        setAppservName();
    } catch (Exception e) {
        e.printStackTrace();
    }   
}

//  Get row count
public int getRowCount(){   
    return model.getRowCount();
}

//  Set table data
public void setAppservName(){   
    Vector data = model.getDataVector();
    Vector row = (Vector) data.elementAt(1);

    // Copy the first column
    int mColIndex = 0;-
    colData = new ArrayList(table.getRowCount()+1);

    for (int i = 0; i < table.getRowCount(); i++) {
      row = (Vector) data.elementAt(i);
      colData.add(row.get(mColIndex));  
    }
}

//  Get table data
public String getAppservName(int rowNum){   
    return (String) colData.get(rowNum);
}

// Set appserver info
public void setAppservInfo(String appservInfo){
    textAreaInfo.setText(appservInfo);
    textAreaInfo.setCaretPosition(0);       // Set cursor at top of text
}

// Get appserver info
public String getAppservInfo(){
    return textAreaInfo.getText();
}

/**
 * Action listeners
 */

public void addTableRowListener(ListSelectionListener listSelectionEvent) {
    table.getSelectionModel().addListSelectionListener(listSelectionEvent);
}

public void addButtonListener(ActionListener buttonEvent) {
    btnSaveInfo.addActionListener(buttonEvent);
    btnSaveInfo.setActionCommand("saveAppServInfo");
}

}

Trace output when a non zero row is selected and the refresh thread is run

** Appserver worker **
CmtModel: readAppservData
>>>>>>>>>  Updating the table >>>>>>>>
>>>>>>>>>  Updating the table >>>>>>>>
>>>>>>>>>  Updating the table >>>>>>>>
>>>>>>>>>  Updating the table >>>>>>>>
>>>>>>>>>  Updating the table >>>>>>>>
>>>>>>>>>  Updating the table >>>>>>>>
>>>>>>>>>  Updating the table >>>>>>>>
>>>>>>>>>  Updating the table >>>>>>>>
>>>>>>>  Rowcount = 8

Trace output when the zero row is selected and the refresh thread is run

** Appserver worker **
CmtModel: readAppservData
>>>>>>>>>  Selection list handler ! >>>>>>>>
>> Controll: selectedRow = 0 <<
>>>>>>>>>  Updating the table >>>>>>>>
>>>>>>>>>  Selection list handler ! >>>>>>>>
>> Controll: selectedRow = 1 <<
>>>>>>>>>  Updating the table >>>>>>>>
>>>>>>>>>  Selection list handler ! >>>>>>>>
>> Controll: selectedRow = 2 <<
>>>>>>>>>  Updating the table >>>>>>>>
>>>>>>>>>  Selection list handler ! >>>>>>>>
>> Controll: selectedRow = 3 <<
>>>>>>>>>  Updating the table >>>>>>>>
>>>>>>>>>  Selection list handler ! >>>>>>>>
>> Controll: selectedRow = 4 <<
>>>>>>>>>  Updating the table >>>>>>>>
>>>>>>>>>  Selection list handler ! >>>>>>>>
>> Controll: selectedRow = 5 <<
>>>>>>>>>  Updating the table >>>>>>>>
>>>>>>>>>  Selection list handler ! >>>>>>>>
>> Controll: selectedRow = 6 <<
>>>>>>>>>  Updating the table >>>>>>>>
>>>>>>>>>  Selection list handler ! >>>>>>>>
>> Controll: selectedRow = 7 <<
>>>>>>>>>  Updating the table >>>>>>>>
>>>>>>>>>  Selection list handler ! >>>>>>>>
>> Controll: selectedRow = 8 <<
java.lang.IndexOutOfBoundsException: Index: 8, Size: 8
    at java.util.ArrayList.rangeCheck(Unknown Source)
    at java.util.ArrayList.get(Unknown Source)
    at panels.ExtAppServPanel.getAppservName(ExtAppServPanel.java:219)
    at Control$SharedListSelectionHandler.valueChanged(Control.java:330)
    at javax.swing.DefaultListSelectionModel.fireValueChanged(Unknown Source)
    at javax.swing.DefaultListSelectionModel.fireValueChanged(Unknown Source)
    at javax.swing.DefaultListSelectionModel.fireValueChanged(Unknown Source)
    at javax.swing.DefaultListSelectionModel.insertIndexInterval(Unknown Source)
    at javax.swing.JTable.tableRowsInserted(Unknown Source)
    at javax.swing.JTable.tableChanged(Unknown Source)
    at javax.swing.table.AbstractTableModel.fireTableChanged(Unknown Source)
    at javax.swing.table.AbstractTableModel.fireTableRowsInserted(Unknown Source)
    at javax.swing.table.DefaultTableModel.insertRow(Unknown Source)
    at javax.swing.table.DefaultTableModel.addRow(Unknown Source)
    at javax.swing.table.DefaultTableModel.addRow(Unknown Source)
    at panels.ExtAppServPanel.setAppserverData(ExtAppServPanel.java:172)
    at Control$AppserverWorker.doInBackground(Control.java:434)
    at Control$AppserverWorker.doInBackground(Control.java:1)
    at javax.swing.SwingWorker$1.call(Unknown Source)
    at java.util.concurrent.FutureTask.run(Unknown Source)
    at javax.swing.SwingWorker.run(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at java.lang.Thread.run(Unknown Source)

Solution

  • // Update GUI
    theGui.extAppServPanel.setAppserverData(theModel.getAppservData());
    

    Don't update the model or the GUI in the doInBackground() method.

    All updates to the GUI should be done on the EventDispatchThread (EDT).

    Instead when the data changes you need to "publish" the results so the code can be executed in the process(...) method of the SwingWorker which does execute on the EDT and therefore the GUI will be updated on the EDT.

    Read the section from the Swing tutorial on Concurrency for more information. The section on Tasks That Have Intermediate Results has an example of the publish approach.