Search code examples
javaswingconcurrencycardlayout

CardLayout + Concurrency


I'm quite new to SWING and after some tests I decided to go with CardLayout for a very simple program: I have 3 cards (one for login, one for adding data and another to display it). The problem is that I have a list that's shared among those cards (passed the reference) and it can be modified by more than one card. For some reason, after I play around a little, the program breaks while attempting to iterate over the list, as can be seen on the below error:

Exception in thread "AWT-EventQueue-0" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:819)

There are no threads aside modifying the list or anything, so I'm not sure why the exception is brought up. I even tried to sync the part of the code that operates over the list but had no good results. I'm not completely sure if I made myself clear, please feel free to ask questions. (The code is not provided since it's not just a snippet, but if anyone finds it's necessary I will put it up).

Thanks in advance.

-- EDIT -- Here's the code: Main class:

package cantodasletras.br.views;

import java.awt.CardLayout;

public class CantoDasLetrasMain {

private JFrame frmCantodasletras;
private static int DEBUG = 0;
private final String LOGINMENU = "The user login view";
private final String MENUVIEW = "The user menu view";
private final String SEARCHVIEW = "The search view";
private final String ADDNEWVIEW = "The create new order view";

private ArrayList<Order> listOfOrders = null;

private JPanel renderPanel;


/**
 * Launch the application.
 */
public static void main(final String[] args) {
EventQueue.invokeLater(new Runnable() {
  public void run() {
    if (args.length > 0) 
        DEBUG = 1;


    try {
      CantoDasLetrasMain window = new CantoDasLetrasMain();
      window.frmCantodasletras.setVisible(true);
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
});
}

/**
 * Create the application.
 */
public CantoDasLetrasMain() {
  initialize();
}

/**
 * Initialize the contents of the frame.
 */
private void initialize() {

listOfOrders = new ArrayList<Order>();

UIManager.put("Button.defaultButtonFollowsFocus", Boolean.TRUE);
frmCantodasletras = new JFrame();
frmCantodasletras.setTitle("CantoDasLetras 1.0");
frmCantodasletras.setBounds(100, 100, 754, 530);
frmCantodasletras.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frmCantodasletras.getContentPane().setLayout(null);

renderPanel = new JPanel();
renderPanel.setBounds(0, 0, 738, 472);
frmCantodasletras.getContentPane().add(renderPanel);
renderPanel.setLayout(new CardLayout(0, 0));
CardLayout cl = (CardLayout) renderPanel.getLayout();

//Inicializar todas as views

LoginView lv = new LoginView(cl,renderPanel);
renderPanel.add(lv, LOGINMENU);

MenuView mv = new MenuView(cl, renderPanel);
renderPanel.add(mv,MENUVIEW);

SearchView sv = new SearchView(cl,renderPanel,listOfOrders);
renderPanel.add(sv,SEARCHVIEW);

AddNewView anv = new AddNewView(cl, renderPanel,listOfOrders);
renderPanel.add(anv,ADDNEWVIEW);


  }
}

Here're the cards:

package cantodasletras.br.views;

import java.awt.CardLayout;

public class AddNewView extends JPanel {

  private final String MENUVIEW = "The user menu view";
  private JTextField fieldNome;
  private JTextField fieldTelefone;
  private JTextField fieldLivro;
  private JTextField fieldQuantidade;
  private JTextField fieldData;
  private JComboBox cBSit;
  private JTextArea fieldObs;

  /**
   * Create the panel.
   */
  public AddNewView(final CardLayout cl, final JPanel renderPanel,
    final ArrayList<Order> orders) {
    setLayout(null);

    JButton btnNewButton = new JButton("Cancelar");
    btnNewButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        cl.show(renderPanel, MENUVIEW);
      }
    });
    btnNewButton.setBounds(541, 429, 89, 33);
    add(btnNewButton);

    JButton btnCadastrar = new JButton("Cadastrar");
    btnCadastrar.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {

        String nome = fieldNome.getText();
        String livro = fieldLivro.getText();
        String telefone = fieldTelefone.getText();
        String situacao = (String) cBSit.getSelectedItem();
        Integer quantidade = Integer.parseInt(fieldQuantidade.getText());
        String obs = fieldObs.getText();
        String data = fieldData.getText();

        fieldNome.setText("");
        fieldLivro.setText("");
        fieldTelefone.setText("");
        cBSit.setSelectedIndex(0);
        fieldQuantidade.setText("");
        fieldObs.setText("");
        fieldData.setText("");



        orders.add(new Order(nome, data, livro, telefone, situacao, quantidade,
          obs));

        cl.show(renderPanel, MENUVIEW);

        return;

      }
    });
    btnCadastrar.setBounds(424, 429, 89, 33);
    add(btnCadastrar);

    JLabel lblNome = new JLabel("Nome:");
    lblNome.setBounds(44, 64, 46, 14);
    add(lblNome);

    JLabel lblTelefone = new JLabel("Telefone:");
    lblTelefone.setBounds(44, 108, 46, 14);
    add(lblTelefone);

    JLabel lblLivro = new JLabel("Livro:");
    lblLivro.setBounds(44, 150, 46, 14);
    add(lblLivro);

    fieldNome = new JTextField();
    fieldNome.setBounds(108, 61, 498, 20);
    add(fieldNome);
    fieldNome.setColumns(10);

    fieldTelefone = new JTextField();
    fieldTelefone.setBounds(108, 105, 498, 20);
    add(fieldTelefone);
    fieldTelefone.setColumns(10);

    fieldLivro = new JTextField();
    fieldLivro.setBounds(108, 147, 498, 20);
    add(fieldLivro);
    fieldLivro.setColumns(10);

    JLabel lblSituao = new JLabel("Situa\u00E7\u00E3o:");
    lblSituao.setBounds(44, 186, 46, 14);
    add(lblSituao);

    JLabel lblNewLabel = new JLabel("Quantidade:");
    lblNewLabel.setBounds(44, 224, 60, 14);
    add(lblNewLabel);

    fieldQuantidade = new JTextField();
    fieldQuantidade.setBounds(108, 221, 498, 20);
    add(fieldQuantidade);
    fieldQuantidade.setColumns(10);

    JLabel lblObs = new JLabel("Obs:");
    lblObs.setBounds(44, 300, 46, 14);
    add(lblObs);

    JLabel lblCadastroDeNovo = new JLabel("Cadastro de Novo Pedido");
    lblCadastroDeNovo.setFont(new Font("Tahoma", Font.PLAIN, 17));
    lblCadastroDeNovo.setBounds(253, 21, 190, 20);
    add(lblCadastroDeNovo);

    cBSit = new JComboBox();
    cBSit.setModel(new DefaultComboBoxModel(new String[] { "A pedir",
        "Pedido Feito", "Avisado", "ND", "Esgotado", "Fora de Cat\u00E1logo",
        "N\u00E3o Encontrado", "Lan\u00E7amento Previsto", "Entregue",
        "Desisdente" }));
    cBSit.setSelectedIndex(0);
    cBSit.setBounds(108, 183, 498, 20);
    add(cBSit);

    JLabel lblData = new JLabel("Data:");
    lblData.setBounds(44, 261, 46, 14);
    add(lblData);

    fieldData = new JTextField();
    fieldData.setBounds(108, 261, 498, 20);
    add(fieldData);
    fieldData.setColumns(10);

    fieldObs = new JTextArea();
    fieldObs.setText("Sem obs;");
    fieldObs.setBounds(108, 300, 498, 96);
    add(fieldObs);

  }
}

Last one:

package cantodasletras.br.views;

import java.awt.BorderLayout;

public class SearchView extends JPanel {
  private final String MENUVIEW = "The user menu view";
  private JTable table;
  private JTextField textField_1;

  /**
   * Create the panel.
   */
  public SearchView(final CardLayout cl, final JPanel renderPanel,
    final ArrayList<Order> orders) {
    GridBagLayout gridBagLayout = new GridBagLayout();
    gridBagLayout.columnWidths =
      new int[] { 0, 0, 17, 224, 66, 333, 77, 0, 0, 0, 0, 0, 0, 0, 0 };
    gridBagLayout.rowHeights = new int[] { 0, 0, 0, 0, 0, 0, 0 };
    gridBagLayout.columnWeights =
      new double[] { 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0,
          0.0, 0.0, 0.0, Double.MIN_VALUE };
    gridBagLayout.rowWeights =
      new double[] { 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, Double.MIN_VALUE };
    setLayout(gridBagLayout);

    JLabel lblBuscarCadastro = new JLabel("Buscar Cadastro");
    lblBuscarCadastro.setFont(new Font("Tahoma", Font.PLAIN, 17));
    GridBagConstraints gbc_lblBuscarCadastro = new GridBagConstraints();
    gbc_lblBuscarCadastro.insets = new Insets(0, 0, 5, 5);
    gbc_lblBuscarCadastro.gridx = 5;
    gbc_lblBuscarCadastro.gridy = 0;
    add(lblBuscarCadastro, gbc_lblBuscarCadastro);

    JLabel lblFiltro = new JLabel("Filtro:");
    GridBagConstraints gbc_lblFiltro = new GridBagConstraints();
    gbc_lblFiltro.anchor = GridBagConstraints.SOUTH;
    gbc_lblFiltro.insets = new Insets(0, 0, 5, 5);
    gbc_lblFiltro.gridx = 3;
    gbc_lblFiltro.gridy = 1;
    add(lblFiltro, gbc_lblFiltro);

    JComboBox comboBox = new JComboBox();
    comboBox.setModel(new DefaultComboBoxModel(new String[] { "Nome", "Livro",
        "A pedir", "Pedido Feito", "Avisado", "ND", "Esgotado",
        "Fora de Cat\u00E1logo", "N\u00E3o Encontrado",
        "Lan\u00E7amento Previsto", "Entregue", "Cancelado" }));
    GridBagConstraints gbc_comboBox = new GridBagConstraints();
    gbc_comboBox.insets = new Insets(0, 0, 5, 5);
    gbc_comboBox.gridx = 3;
    gbc_comboBox.gridy = 2;
    add(comboBox, gbc_comboBox);

    textField_1 = new JTextField();
    GridBagConstraints gbc_textField_1 = new GridBagConstraints();
    gbc_textField_1.insets = new Insets(0, 0, 5, 5);
    gbc_textField_1.fill = GridBagConstraints.HORIZONTAL;
    gbc_textField_1.gridx = 5;
    gbc_textField_1.gridy = 2;
    add(textField_1, gbc_textField_1);
    textField_1.setColumns(10);

    JButton btnProcurar = new JButton("Procurar");
    btnProcurar.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        DefaultTableModel dm = (DefaultTableModel) table.getModel();
        dm.setRowCount(0);
        initTable(orders);
      }
    });
    GridBagConstraints gbc_btnProcurar = new GridBagConstraints();
    gbc_btnProcurar.insets = new Insets(0, 0, 5, 5);
    gbc_btnProcurar.gridx = 7;
    gbc_btnProcurar.gridy = 2;
    add(btnProcurar, gbc_btnProcurar);

    JButton btnVoltar = new JButton("Voltar");
    btnVoltar.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        cl.show(renderPanel, MENUVIEW);

      }
    });
    GridBagConstraints gbc_btnVoltar = new GridBagConstraints();
    gbc_btnVoltar.insets = new Insets(0, 0, 5, 5);
    gbc_btnVoltar.gridx = 9;
    gbc_btnVoltar.gridy = 2;
    add(btnVoltar, gbc_btnVoltar);

    JPanel panel_1 = new JPanel();
    GridBagConstraints gbc_panel_1 = new GridBagConstraints();
    gbc_panel_1.gridheight = 3;
    gbc_panel_1.gridwidth = 9;
    gbc_panel_1.insets = new Insets(0, 0, 5, 5);
    gbc_panel_1.fill = GridBagConstraints.BOTH;
    gbc_panel_1.gridx = 3;
    gbc_panel_1.gridy = 3;
    add(panel_1, gbc_panel_1);

    JComboBox combo = new JComboBox();
    combo.setModel(new DefaultComboBoxModel(new String[] { "A pedir",
        "Pedido Feito", "Avisado", "ND", "Esgotado", "Fora de Cat\u00E1logo",
        "N\u00E3o Encontrado", "Lan\u00E7amento Previsto", "Entregue",
        "Desisdente" }));
    combo.setSelectedIndex(0);

    panel_1.setLayout(new BorderLayout());
    table = new JTable();
    table
      .setModel(new DefaultTableModel(new Object[][] {},
        new String[] { "Telefone", "Nome", "Data", "Livro", "Quantidade",
            "Situacao", "Obs" }));

    table.getColumn(table.getColumnName(5)).setCellEditor(
      new javax.swing.DefaultCellEditor(combo));

    table.getModel().addTableModelListener(new TableModelListener() {

      public void tableChanged(TableModelEvent e) {

        if (!(table.getModel().getRowCount() > 0))
          return;

        if (e.getType() == TableModelEvent.UPDATE) {

          int col = e.getColumn();
          int row = e.getFirstRow();

          String telefone = (String) table.getValueAt(row, 0);
          String nome = (String) table.getValueAt(row, 1);
          String data = (String) table.getValueAt(row, 2);
          String livro = (String) table.getValueAt(row, 3);
          String quantidade = (String) table.getValueAt(row, 4);
          String situacao = ((String) table.getValueAt(row, 5));
          String obs = (String) table.getValueAt(row, 6);

          Order nOrd =
            new Order(nome, data, livro, telefone, situacao, Integer
              .parseInt(quantidade), obs);

          if (col == 0 || col == 1) {
            int result =
              JOptionPane
                .showConfirmDialog(
                  (Component) null,
                  "Alterar o nome ou telefone de um cadastro irá gerar um novo cadastro. Deseja continuar?",
                  "Alerta!", JOptionPane.OK_CANCEL_OPTION);

            if (result == 0) {

              if (!orders.contains(nOrd))
                orders.add(nOrd);

            }
            return;
          }

          for (Order or : orders) {

            if (or.equals(nOrd))
              orders.remove(or);
            orders.add(nOrd);

          }
        }

      }
    });

    JScrollPane tableContainer = new JScrollPane(table);

    panel_1.add(tableContainer, BorderLayout.CENTER);

  }

  void initTable(ArrayList<Order> orders) {
    // Initialize table

    DefaultTableModel dtm = (DefaultTableModel) table.getModel();

    for (Order order : orders) {

      dtm.addRow(new Object[] { order.getTelefone(), order.getNome(),
          order.getData(), order.getLivro(), order.getQuantidade().toString(),
          order.getSituacao(), order.getObs() });

    }

  }
}

Solution

  • It is hard to understand where the error occurs as you are not providing a complete stack trace, and the code does not compile (as posted).

    However, the way you remove elements from the list (in SearchView) while iterating is not correct and may be the cause for the exception:

      for (Order or : orders) {
        if (or.equals(nOrd))
          orders.remove(or);
      }
    

    You should use iterator and its remove() method. For example:

    for (Iterator<Order> iterator = orders.iterator(); iterator.hasNext();) {
        Order order = iterator.next();
        if (order.equals(nOrd)) {
            iterator.remove();
        }
    }
    

    See The Collection Interface for details. It states:

    Note that Iterator.remove is the only safe way to modify a collection during iteration; the behavior is unspecified if the underlying collection is modified in any other way while the iteration is in progress.

    Use Iterator instead of the for-each construct when you need to:

    Remove the current element. The for-each construct hides the iterator, so you cannot call remove.