title: The right approach to update complex JTables, TableModel and others
body: <p>My GUI shows the vehicles in my <em>park</em>, and vehicles that I want to set <em>availables</em> in two different <strong>VehicleTables</strong> (classes that extend JTable). 
  For availables I intend that these vehicles can be observed from an <em>agent</em> (third-part software).
  Both the tables show the descriptions of Vehicles in the rows...for this I have created <strong>VehicleTableModel</strong> and <strong>Vehicle</strong> classes.
  The Vehicle class is an abstract class and his subclasses are: Car, Truck, Trailer, etc. .</p>
  
  <p>You can see a snapshot of my software:
  <img src="https://i.stack.imgur.com/f5vS4.png" alt="enter image description here"></p>
  
  <p>My problems are these:
  In my current implementation I don't think of manage really good the <strong><em>updates</strong> of the rows</em>. You can see in VehicleTableModel (fire...() methods) and in ShipperAgentGUI (coordinators and listeners). I think I have partially resolved this problem with the use of the <strong>Coordinator</strong> inner class for the updates between tables, but I don't know how optimize these.
  For example in case of delete or update of a row I make <em>xxxTable.repaint()</em>; ... the WHOLE table...</p>
  
  <p>...another way?</p>
  
  <p>ShipperAgentGUI.java</p>
  
  <pre><code>public class ShipperAgentGUI extends JFrame implements ActionListener {
  
      // Graphics variables..
      // bla bla...
  
      // Headers, TableModels, JTables for the tables
      private COLUMNS[] parkModelHeader = {COLUMNS.IMAGE_COLUMN, COLUMNS.TARGA_COLUMN,
          COLUMNS.CAR_TYPE_COLUMN, COLUMNS.MARCA_COLUMN, COLUMNS.STATE_COLUMN, COLUMNS.PTT_COLUMN };
      private COLUMNS[] availablesModelHeader = {COLUMNS.IMAGE_COLUMN, COLUMNS.TARGA_COLUMN,
          COLUMNS.CAR_TYPE_COLUMN, COLUMNS.MARCA_COLUMN };
  
      private VehicleTableModel parkModel = new VehicleTableModel(parkModelHeader);
      private VehicleTableModel availablesModel = new VehicleTableModel(availablesModelHeader);
  
      private VehicleTable parkTable;
      private VehicleTable availablesTable;
  
      // My third-part software, a JADE agent:
      protected ShipperAgent shipperAgent;
  
  
      // --------------------------------------------------------------------------
  
      // CONSTRUCTOR
  
      ShipperAgentGUI(ShipperAgent agent) {
  
          shipperAgent = agent; // valorizes the agent
  
          setTitle("Shipper Agent: "+agent.getLocalName()+" GUI");
  
          // graphic bla bla...
  
          // Park Table and Available Table:
          parkTable = new VehicleTable(parkModel);
              // bla bla...
          availablesTable = new VehicleTable(availablesModel);
              // bla bla...
  
          // JButtons: add/remove vehicle in Park Table and Available Table
          btnPM_plus = new JButton();
              btnPM_plus.setToolTipText("Add vehicle");
              btnPM_plus.setIcon(...);
              btnPM_plus.setActionCommand("+park");
              btnPM_plus.addActionListener(this);
  
          // similar things for other three buttons:
          // remove from parkTable, add and remove from availablesTable
  
          //bla bla...
  
          // Data from agent:
          Vector&lt;Vehicle&gt; veicoli = shipperAgent.getVehicles();
          Iterator&lt;Vehicle&gt; I = veicoli.iterator();
          while (I.hasNext()){
              addVehicle(parkCoordinator, I.next());
          }
  
          showGui();
      }
  
  
  
      ///////////////////////////////////////////////////////////////////////
      // Methods:
  
      public void showGui() {
          // bla bla
      }
  
  
      //////////////////////////////////////////////
      // actionPerformed method
  
      @Override
      public void actionPerformed(ActionEvent e) {
          switch (e.getActionCommand()) {
          case "+park": {
              new InsertVehicleJDialog(this, parkCoordinator);
          } break;
  
          case "-park": {
              int selectedRow = parkTable.getSelectedRow();
              if (selectedRow != -1)
                  removeVehicle(parkCoordinator, selectedRow);
          } break;
  
          case "+available": {
              int selectedRow = parkTable.getSelectedRow();
              if (selectedRow != -1){
                  addVehicle(availablesCoordinator, parkModel.getVehicleAt(selectedRow)); 
              }
          } break;
  
          case "-available": {
              int selectedRow = availablesTable.getSelectedRow();
              if (selectedRow != -1)
                  removeVehicle(availablesCoordinator, selectedRow);
          } break;
          }
      }
  
  
      ///////////////////////////////////////
      // Add/Remove vehicle methods:
  
      void addVehicle(Coordinator coordinator, Vehicle v) {
          coordinator.notifyAndAddRow(v);
      }
  
      // mhm...
      void removeVehicle(Coordinator coordinator, Vehicle v) {
          int row = coordinator.indexOf(v);
          if (row!=-1)
              coordinator.notifyAndDeleteRow(row);
      }
  
      void removeVehicle(Coordinator coordinator, int index) {
          coordinator.notifyAndDeleteRow(index);
      }
  
  
      // on dispose, delete the agent
      public void dispose() {
          super.dispose();
          shipperAgent.doDelete(); 
      }
  
  
  
  
      ///////////////////////////////////////
      // INNER CLASS COORDINATOR:
  
      protected abstract class Coordinator {
          private VehicleTableModel tableModel;
  
          public Coordinator(VehicleTableModel tm) {
              tableModel = tm;
              notifyRowUpdated();
          }
  
          public abstract void notifyAndAddRow(Vehicle vehicle);
          public abstract void notifyAndDeleteRow(int rowIndex);
          public abstract void notifyRowUpdated();
  
          public int indexOf(Vehicle v) {
              return tableModel.indexOf(v);
          }
  
          boolean vehicleExists(Vehicle vehicle){
              int bool = indexOf(vehicle);
              if (bool==-1) return false;
              else return true;
          }
      }
  
  
      // Coordinator for parkTable
      Coordinator parkCoordinator = new Coordinator(parkModel) {
  
          @Override
          public void notifyAndAddRow(final Vehicle vehicle) {
              if (!vehicleExists(vehicle)){ // is this the right control? Or in VehicleTableModel ?
                  shipperAgent.newTruck(vehicle.getPlate());
  
                  SwingUtilities.invokeLater(new Runnable() {
                      @Override
                      public void run() {
                          parkModel.addRow(vehicle);
                          if (vehicle.getState().equals(Stato.DISPONIBILE))
                              availablesModel.addRow(vehicle); 
                              // or with availablesCoordinator.notifyAndAddRow(vehicle) ?
                              // or with addVehicle(availablesCoordinator, vehicle) ?
                              // or with a kind of listener on vehicle's state ?
                      }
                  });
              }
          }
  
          @Override
          public void notifyAndDeleteRow(final int rowIndex) {
              final Vehicle v = parkModel.getVehicleAt(rowIndex);
  
              removeVehicle(availablesCoordinator, v); // Remove also from the "availables"
  
              shipperAgent.removeTruck(v.getPlate());
  
              SwingUtilities.invokeLater(new Runnable() {
                  @Override
                  public void run() {
                      parkModel.removeRow(rowIndex);
                  }
              });
          }
  
          @Override
          public void notifyRowUpdated() {
              parkModel.addTableModelListener(new TableModelListener() {
                  public void tableChanged(TableModelEvent e) {
                      switch (e.getType()) {
                          case (TableModelEvent.DELETE):
                              parkTable.repaint();
                              break;
                          case (TableModelEvent.UPDATE):
                              int row = e.getLastRow();
                              Vehicle v = parkModel.getVehicleAt(row);
                              if (v.getState().equals(Stato.DISPONIBILE)){
                                  addVehicle(availablesCoordinator, v);
                                  availablesTable.repaint();
                              } else
                                  removeVehicle(availablesCoordinator, v);
                              parkTable.repaint();
                              break;
                      }
                  }
              });
          }
      };
  
  
  
      // Coordinator for availablesTable
      Coordinator availablesCoordinator = new Coordinator(availablesModel) {
  
          @Override
          public void notifyAndAddRow(final Vehicle vehicle) {
              if (!vehicleExists(vehicle)){ // is this the right control? Or in VehicleTableModel ?
                  vehicle.setStato(Stato.DISPONIBILE);
                  parkTable.repaint();
  
                  shipperAgent.activateTruck(vehicle.getPlate());
                  SwingUtilities.invokeLater(new Runnable() {
                      @Override
                      public void run() {
                          availablesModel.addRow(vehicle);
                      }
                  });
              }
          }
  
          @Override
          public void notifyAndDeleteRow(final int rowIndex) {
              Vehicle v = availablesModel.getVehicleAt(rowIndex);
              if (v!=null){
                  v.setStato(Stato.NON_DISPONIBILE); // mhm
                  shipperAgent.deactivateTruck(v.getPlate());
  
                  SwingUtilities.invokeLater(new Runnable() {
                      @Override
                      public void run() {
                          availablesModel.removeRow(rowIndex);
                      }
                  });
              }
          }
  
          @Override
          public void notifyRowUpdated() {
              availablesModel.addTableModelListener(new TableModelListener() {
                  public void tableChanged(TableModelEvent e) {
                      switch (e.getType()) {
                      case (TableModelEvent.DELETE):
                          parkTable.repaint();
                          break;
                      case (TableModelEvent.UPDATE):
                          parkTable.repaint();
                          break;
                      }
                  }
              });
          }
      };
  
  }
  </code></pre>
  
  <p>VehicleTableModel.java</p>
  
  <pre><code>public class VehicleTableModel extends AbstractTableModel {
  
      private ArrayList&lt;Vehicle&gt; vehicles ;
      private COLUMNS[] header;
  
      // possible column names:
      public enum COLUMNS {
          IMAGE_COLUMN,
          TARGA_COLUMN,
          CAR_TYPE_COLUMN,
          MARCA_COLUMN,
          STATE_COLUMN,
          PTT_COLUMN,
      };
  
      ///////////////////////////////////////////////////////
      // Constructor:
  
      public VehicleTableModel(COLUMNS[] headerTable) {
          this.vehicles = new ArrayList&lt;Vehicle&gt;();
          this.header = headerTable;
      }
  
  
      ///////////////////////////////////////////////////////
      // obligatory override methods (from AbstractTableModel):
  
      @Override
      public int getColumnCount() {
          return header.length;
      }
  
      @Override
      public int getRowCount() {
          return vehicles.size();
      }
  
      @Override
      public Object getValueAt(int row, int col) {
          Object value = "?";
          Vehicle v = vehicles.get(row);
          if (v!=null) {
              COLUMNS column = header[col];
              switch (column) {
                  case IMAGE_COLUMN:
                      value = VehicleUtils.findImageByColumnCarType(v.getType());
                      break;
                  case TARGA_COLUMN:
                      value = v.getPlate();
                      break;
                  case CAR_TYPE_COLUMN:
                      value = VehicleUtils.findStringByColumnCarType(v.getType());
                      break;
                  // other cases... bla bla...
              }
          }
          return value;
      }
  
  
  
      ///////////////////////////////////////////////////////
      // My methods:
  
      public void addRow(Vehicle vehicle) {
          vehicles.add(vehicle);
          fireTableRowsInserted(0, getRowCount()); // is right?
      }
  
      /*public boolean removeRow(Vehicle vehicle) {
          boolean flag = vehicles.remove(vehicle);
          fireTableRowsDeleted(0, getRowCount()); // is right?
          return flag;
      }*/
  
      public void removeRow(int row) {
          vehicles.remove(row);
          fireTableRowsDeleted(row, row); // is right?
      }
  
      public Vehicle getVehicleAt(int row) {
          return vehicles.get(row);
      }
  
      public int indexOf(Vehicle v){
          return vehicles.indexOf(v);
      }
  
      // found the corresponding column index
      public int findColumn(COLUMNS columnName) {
          for (int i=0; i&lt;getColumnCount(); i++)
              if (columnName.equals(header[i])) 
                  return i;
          return -1;
      }
  
  
      // a value in that column exist in the table?
      private boolean controllIfExist(Object value, int col) {
          boolean bool = false;
          for (int i=0; i&lt;getRowCount();i++){
              if (value.equals(getValueAt(i, col))){
                  bool=true;
                  break;
              }
          }
          return bool;
      }
  
      public int getColumnIndex(COLUMNS column){
          for(int i=0;i&lt;header.length;i++){
              if (column.equals(header[i])){
                  return i;
              }
          }
          return -1;
      }
  
  
  
      ///////////////////////////////////////////////////////
      // other methods (from AbstractTableModel) to override:
  
  
      @Override
      public Class&lt;?&gt; getColumnClass(int col) {
          Class&lt;?&gt; c;
          COLUMNS column = header[col];
          if (column.equals(COLUMNS.IMAGE_COLUMN))
              c = ImageIcon.class;
          else if (column.equals(COLUMNS.STATE_COLUMN))
              c =  JComboBox.class;
          else c = super.getColumnClass(col);
          return c;
      }
  
  
      @Override
      public String getColumnName(int col) {
          COLUMNS column = header[col];
          if (column.equals(COLUMNS.IMAGE_COLUMN))
              return " ";
          else if (column.equals(COLUMNS.TARGA_COLUMN))
              return "Targa";
          // others... bla bla...
          return super.getColumnName(col);
      };
  
  
      @Override
      public boolean isCellEditable(int row, int col) {
          return true;
      }
  
  
      @Override
      public void setValueAt(Object value, int row, int col) {
          Vehicle v = vehicles.get(row);
          boolean flag = false;
          if (v!=null) {
              COLUMNS column = header[col];
              switch (column) {
                  case TARGA_COLUMN:
                      if (!v.getPlate().equals(value)){
                          if (!controllIfExist(value, col)){  // mhm...
                              v.setPlate((String) value);
                              flag = true;
                          }
                      }
                      break;
                  case MARCA_COLUMN:
                      if (!v.getMark().equals(value)){
                          v.setMark((String) value);
                          flag = true;
                      }
                      break;
  
                  // others ... bla bla...
              }
              // update ONLY if necessary:
              if (flag) fireTableRowsUpdated(row, row); // is right?
          }
      }
  }
  </code></pre>