Search code examples
javaswingjtable

Why am I getting an ArrayIndex Out of Bounds exception for trying to remove elements in a JTable?


I'm trying to implement a GUI employee manager on Jswing where data is stored on a text file that I treat as my database and I created a GUI interface to interact with it.

Upon opening up the program, I'm able to use the remove feature and it works fine. But once I click the search button (table searches for values and only displays the values I searched for), and then when I try to remove something, it throws the error you see below.

This is the error I'm getting once I search for something:

Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: 2 >= 2
    at java.base/java.util.Vector.elementAt(Vector.java:466)
    at java.desktop/javax.swing.table.DefaultTableModel.getValueAt(DefaultTableModel.java:660)
    at java.desktop/javax.swing.JTable.getValueAt(JTable.java:2763)
    at java.desktop/javax.swing.JTable.prepareRenderer(JTable.java:5780)
    at java.desktop/javax.swing.plaf.basic.BasicTableUI.paintCell(BasicTableUI.java:2210)
    at java.desktop/javax.swing.plaf.basic.BasicTableUI.paintCells(BasicTableUI.java:2112)
    at java.desktop/javax.swing.plaf.basic.BasicTableUI.paint(BasicTableUI.java:1908)
    at java.desktop/javax.swing.plaf.ComponentUI.update(ComponentUI.java:161)
    at java.desktop/javax.swing.JComponent.paintComponent(JComponent.java:842)
    at java.desktop/javax.swing.JComponent.paint(JComponent.java:1119)
    at java.desktop/javax.swing.JComponent.paintToOffscreen(JComponent.java:5311)
    at java.desktop/javax.swing.BufferStrategyPaintManager.paint(BufferStrategyPaintManager.java:246)
    at java.desktop/javax.swing.RepaintManager.paint(RepaintManager.java:1337)
    at java.desktop/javax.swing.JComponent._paintImmediately(JComponent.java:5259)
    at java.desktop/javax.swing.JComponent.paintImmediately(JComponent.java:5069)
    at 

------------------EDIT----------------------

Here is my minimal reproducible code:

import java.awt.*;
    import java.awt.event.*;
    import javax.swing.*;
    import java.util.*;
    import javax.swing.JTable;
    import java.io.IOException;  // Import the IOException class to handle errors
    import java.io.File;  // Import the File class
    import java.io.FileNotFoundException;  // Import this class to handle errors
    import javax.swing.table.DefaultTableModel;
    import java.io.FileWriter;
    import javax.swing.event.*;
    import javax.swing.table.*;
class UserManager extends JFrame implements ActionListener {
  // Initializing class variables
  private JTextField firstNameField, lastNameField, salaryField, textField;
  private JButton addButton, removeButton, sortButton, button;
  private JList<Employee> userList;
  private ArrayList<Employee> users;
  private JTable j;
  private DefaultTableModel model;
  private JTextField searchField;

  /*****************************************
  /*Name: UserManager (constructor)
  /*Method Description: Constructor class that runs once upon creation of the object. Creates the frame of the GUI app. Pulls from the database and displays it on the GUI interface.
  /*Method Inputs/Outputs: Outputs the GUI frame of the app.
  ******************************************/
  public UserManager() {
    setTitle("Employee Manager");
    setSize(400, 400);
    setDefaultCloseOperation(EXIT_ON_CLOSE);

    // Initializing and binding action items
    firstNameField = new JTextField(20);
    lastNameField = new JTextField(20);
    salaryField = new JTextField(20);
    searchField = new JTextField(20);
    addButton = new JButton("Add");
    addButton.addActionListener(this);
    removeButton = new JButton("Remove");
    removeButton.addActionListener(this);
    sortButton = new JButton("Sort Salary");
    sortButton.addActionListener(this);

    // Pulling data from text file database upon start up
    ArrayList<ArrayList<String>> databaseData = ReadFile();

    users = new ArrayList<Employee>();

    // Adding existing databaseData to users
    try {
      if (databaseData.size() > 0) {
        for (int i = 0; i < databaseData.size(); i++) {
          Employee user = new Employee(databaseData.get(i).get(0), databaseData.get(i).get(1), Integer.valueOf(databaseData.get(i).get(2)));
          users.add(user);
        }
      }
    }
    catch (NumberFormatException nfe) {
      nfe.printStackTrace();
      JOptionPane.showMessageDialog(this, "Internal System Error", "Error", JOptionPane.ERROR_MESSAGE);
    }

    // Creating the list of users
    userList = new JList<Employee>(users.toArray(new Employee[0]));

    // Setting up the JPanels
    JPanel firstNamePanel = new JPanel();
    firstNamePanel.add(new JLabel("First Name:"));
    firstNamePanel.add(firstNameField);

    JPanel lastNamePanel = new JPanel();
    lastNamePanel.add(new JLabel("Last Name:"));
    lastNamePanel.add(lastNameField);

    JPanel salaryPanel = new JPanel();
    salaryPanel.add(new JLabel("Salary:"));
    salaryPanel.add(salaryField);

    JPanel buttonPanel = new JPanel();
    buttonPanel.add(addButton);
    buttonPanel.add(removeButton);
    buttonPanel.add(sortButton);

    JPanel searchPanel = new JPanel();

    
    // Converting 2D arraylist to normal 2D array for JTable
    String[][] data = databaseData.stream().map(u -> u.toArray(new String[0])).toArray(String[][]::new);

    // Initializing column names of JTable
    String[] columnNames = { "FName", "LName", "Salary" };

    // Initialize the JTable and TableModel
    model = new DefaultTableModel(data, columnNames);
    j = new JTable(model);
    j.setBounds(1000, 1000, 900, 900);

    // adding it to JScrollPane
    JScrollPane table = new JScrollPane(j);

    
    JLabel label = new JLabel("Search: ");
    textField = new JTextField(20);
    button = new JButton("Go");
    searchPanel.add(label);
    searchPanel.add(textField);
    searchPanel.add(button);
    button.addActionListener(this);


    // Creating main panel and adding JPanels to it
    JPanel mainPanel = new JPanel(new GridLayout(6, 2));
    mainPanel.add(firstNamePanel);
    mainPanel.add(lastNamePanel);
    mainPanel.add(salaryPanel);
    mainPanel.add(buttonPanel);
    mainPanel.add(searchPanel);
    mainPanel.add(table);
    add(mainPanel);

  }

  /*****************************************
  /*Method Name: actionPerformed
  /*Method Description: Performs functions depending on what action is called. Adds users to the table and db if add button is clicked and removes users from the table and db if remove button is clicked.
  /*Method Inputs/Outputs: Refreshes the table and outputs the new array with the updates.
  ******************************************/
  public void actionPerformed(ActionEvent e) {
    // "Add" button is clicked
    if (e.getSource() == addButton) {
      // Initializing and setting variables
      String firstName = firstNameField.getText();
      String lastName = lastNameField.getText();
      int salary = 0;

      // Checks to see if salary entered is an integer
      try {
          salary = Integer.parseInt(salaryField.getText());
      } 
      catch (NumberFormatException nfe) {
        JOptionPane.showMessageDialog(this, "Please enter a valid salary", "Error", JOptionPane.ERROR_MESSAGE);
        return;
      }

      // Error check to see if full name and age is entered
      if (!firstName.equals("") && !lastName.equals("")) {
        Employee user = new Employee(firstName, lastName, salary);

        // Add the user to the arraylist
        users.add(user);

        // Creating new array with new information
        String[] newEmployeeArr = {firstName, lastName, String.valueOf(salary)};

        // Add new user to GUI
        model.addRow(newEmployeeArr);

        // Update user list
        updateList(users);

        // Resetting input fields
        firstNameField.setText("");
        lastNameField.setText("");
        salaryField.setText("");
      }
      else {
        JOptionPane.showMessageDialog(this, "Please enter a valid full name", "Error", JOptionPane.ERROR_MESSAGE);
      }

    } 

    // "Remove" button is clicked
    else if (e.getSource() == removeButton) {
      try {
        if(j.getSelectedRow() != -1) {
          // remove selected row from the model
          String value = j.getValueAt(j.getSelectedRow(), 0).toString();
          String value1 = j.getValueAt(j.getSelectedRow(), 1).toString();
          String value2 = j.getValueAt(j.getSelectedRow(), 2).toString();

          System.out.println(j.getSelectedRow() + " " + value + value1 + value2);

          // Accounting for if the table was sorted and looking to see where the removed var is in the model
          model.removeRow(removeUserFromTable(value, value1));

          
          // finds the index of the user to remove
          int selectedIndex = removeUser(value, value1, Integer.valueOf(value2));

          // Error checks to see if valid user
          if (selectedIndex != -1) {
            // Remove the selected employee from the users arraylist
            users.remove(selectedIndex);
            JOptionPane.showMessageDialog(null, "Selected row deleted successfully");
            
            // Update the list
            updateList(users);
    
            // Clear inputs
            firstNameField.setText("");
            lastNameField.setText("");
            salaryField.setText("");
          }
        }
        else {
          JOptionPane.showMessageDialog(this, "Employee not selected.", "Error", JOptionPane.ERROR_MESSAGE);
        }
      }
      catch (NumberFormatException nfe) {
        JOptionPane.showMessageDialog(this, "Select a valid row.", "Error", JOptionPane.ERROR_MESSAGE);
      }
    }

    // "Sort" button is clicked
    else if (e.getSource() == sortButton) {
      BubbleSort();
    }

      // "Go" button is clicked to search
    else if (e.getSource() == button) {
      // Creating a searching tool
      TableRowSorter<TableModel> sorter = new TableRowSorter<>(model);
      j.setRowSorter(sorter);
      String text = textField.getText();
      if (text.length() == 0) {
        sorter.setRowFilter(null);
      } else {
        sorter.setRowFilter(RowFilter.regexFilter("(?i)" + text));
      }
    }
  }


  /*****************************************
  /*Method Name: updateList
  /*Method Description: Performs the update depending on what operation was made. Updates all lists inside the class and saves the change to the database.
  /*Method Inputs/Outputs: The new list with the updated list is inputted and an updated database text file is the output.
  ******************************************/
  private void updateList(ArrayList<Employee> u) {
    userList.setListData(u.toArray(new Employee[0]));

    // Log update to console
    System.out.println("Updating Database");

    // Overwriting db.txt file with new information
    try {
      // Making changes to the existed db.txt file
      FileWriter fw = new FileWriter("db.txt", false);

      // Loop through each student and write to the text file
      for (int i = 0; i < u.size(); i++) {
        // Re-writing the database file with the updates list
        fw.write(toString(u.get(i).getFirstName(), u.get(i).getLastName(), u.get(i).getSalary()));
      }
      fw.close();
    }
    catch (IOException io) {
      JOptionPane.showMessageDialog(this, "Internal System Error", "Error", JOptionPane.ERROR_MESSAGE);
      return;
    }
  }

  
  /*****************************************
  /*Method Name: removeUser
  /*Method Description: Searches for the selected user in the users arraylist and finds the index if the user exists
  /*Method Inputs/Outputs: The users arraylist and inputted user information is inputted and then outputs where the user is located in the array.
  ******************************************/
  private int removeUser(String firstName, String lastName, int salary) {
    // Loops through users arraylist
    for (int i = 0; i <  users.size(); i++) {
      // If the user exists in the database, remove them
      if (users.get(i).getFirstName().equals(firstName) && users.get(i).getLastName().equals(lastName) && users.get(i).getSalary() == salary) {
        return i;
      }
    }
    // No user by the name and salary was found
    return -1;
  }

  /*****************************************
  /*Method Name: removeUserFromTable
  /*Method Description: Searches for the selected user in the table model and finds the index if the user exists
  /*Method Inputs/Outputs: The user information is inputted and then outputs where the user is located in the model that controls the GUI table.
  ******************************************/
  private int removeUserFromTable(String firstName, String lastName) {
    int ind = 0;
    for (int i = 0; i < model.getRowCount(); i++){
      if (model.getValueAt(i, 0).toString().equals(firstName) && model.getValueAt(i, 1).toString().equals(lastName)) {
          ind = i;
          break;
      }
    }  
    return ind;
  }


  /*****************************************
  /*Method Name: toString
  /*Method Description: Converts three variables into one long string varaible
  /*Method Inputs/Outputs: The user information is inputted and the string that contains all three variables is outputted.
  ******************************************/
  public String toString(String firstName, String lastName, int salary) {
      return firstName + ", " + lastName + ", " + salary + "\n";
  }

  /*****************************************
  /*Method Name: BubbleSort
  /*Method Description: Performs bubble sort on the employees by salary and then updates the jtable
  /*Method Inputs/Outputs: No inputs or outputs, the JTable is updated
  ******************************************/
  public void BubbleSort() {
    try {
      // Array to hold a copy of the users list
      ArrayList<Employee> tempUsers = new ArrayList<Employee>();
    
      // Adding all the initial users to the new array to perform bubble sort
      for (int i =0; i < users.size(); i++) {
        tempUsers.add(users.get(i));
      }
    
      // Performing bubble sort
      for (int i = 0; i < users.size() - 1; i++) {
        // Looping through indexes
        for (int j = 0; j < users.size() - 1; j++) {
          // Comapare the salaries of each indiviual and see if the previous is bigger than the next
          if (users.get(j).getSalary() > users.get(j+1).getSalary()) {
            // Initializing a temp employee to hold value before switching
            Employee temp = users.get(j);
    
            // Swap the employees
            users.set(j, users.get(j+1));
            users.set(j+1,temp);
          }
        }
      }

      int[] selection = j.getSelectedRows();
      System.out.println(Arrays.toString(j.getSelectedRows()));
       for (int i = 0; i < selection.length; i++) {
         selection[i] = j.convertRowIndexToModel(selection[i]);
         System.out.println(selection[i]);
       }

      // Setting the jtable model to the sorted model
      j.setModel(model);
    }
    catch (NumberFormatException nfe) {
      JOptionPane.showMessageDialog(this, "Not Enough Users", "Error", JOptionPane.ERROR_MESSAGE);
      return;
    }
  }
  

  /*****************************************
  /*Method Name: ReadFile
  /*Method Description: Reads the db textfile and stores the values in a 2d arraylist for manipulation.
  /*Method Inputs/Outputs: The 2d arraylist with all the db information is outputted
  ******************************************/
  public static ArrayList<ArrayList<String>> ReadFile() {
    try {
      // Choose db.txt file to look at
      File myObj = new File("db.txt");

      // Create scanner object
      Scanner myReader = new Scanner(myObj);

      // Create 2d list array to hold all the single list arrays of single information
      ArrayList<ArrayList<String>> combinedArr = new ArrayList<ArrayList<String>>();
    
      // While the file reader is still reading lines in the text
      while (myReader.hasNextLine()) {
        // Read strings of text in txt file
        String data = myReader.nextLine();

        // Get user information into an array
        ArrayList<String> temp = GetInfo(data); 

        // Add the person and their salary to the combined array that holds everyones
        combinedArr.add(temp);
      }

      // Close file once there are no more lines to read
      myReader.close();

      return combinedArr;
    } 
    catch (FileNotFoundException e) {
      System.out.println("An error occurred.");
      e.printStackTrace();
    }

    // Return invalid list string with nothing if error
    ArrayList<ArrayList<String>> Invalid = new ArrayList<ArrayList<String>>();
    return Invalid;
  }

  /*****************************************
  /*Method Name: GetInfo
  /*Method Description: Takes in a string of data and parses the three variables in it which are separated by commas
  /*Method Inputs/Outputs: The data string that needs to be parsed is inputted and arraylist containuing the data from the string is outputted.
  ******************************************/
  public static ArrayList<String> GetInfo(String data) {
    String first = "";
    String last = "";
    String sal = "";
    // System.out.println(data[0])
    for (int i = 0; i < data.length(); i++) {
      if (data.charAt(i) == ',') {
        // Start from 2 indexes after the first occurance of the comma
        for (int j = i+2; j < data.length(); j++) {
          if (data.charAt(j) == ',') {
            // Start from 2 indexes after the occurance of the comma
            for (int n = j+2; n < data.length(); n++) {
              sal += data.charAt(n);
            }
            break;
          }
          last += data.charAt(j);
        }
        break;
      }
      first += data.charAt(i);
    }

    // Initializing package array to send all values
    ArrayList<String> arr = new ArrayList<String>();
    arr.add(first);
    arr.add(last);
    arr.add(sal);
    
    return arr;
  }


  /*****************************************
  /*Method Name: main
  /*Method Description: Runs the GUI frame
  /*Method Inputs/Outputs: Ouputs the GUI 
  ******************************************/
  public static void main(String[] args) {
      UserManager frame = new UserManager();
      frame.setVisible(true);
  }
}


class Employee {
  // Initalizing variables
  private String firstName;
  private String lastName;
  private int salary;

  public Employee(String firstName, String lastName, int salary) {
      this.firstName = firstName;
      this.lastName = lastName;
      this.salary = salary;
  }

  public String getFirstName() {
    return firstName;
  }

  public String getLastName() {
    return lastName;
  }

  public int getSalary() {
    return salary;
  }
}

Solution

  • Instead of trying to treat your data and model as two seperate things, you should be wrapping your model around your data and allow to manage it, for example...

    public class EmployeeTableModel extends AbstractTableModel {
    
        private List<Employee> employees;
        private String[] columnNames = {"FName", "LName", "Salary"};
        private Class[] columnClasses = {String.class, String.class, Integer.class};
    
        public EmployeeTableModel(List<Employee> employees) {
            this.employees = employees;
        }
    
        public List<Employee> getEmployees() {
            return employees;
        }
    
        public String[] getColumnNames() {
            return columnNames;
        }
    
        public Class[] getColumnClasses() {
            return columnClasses;
        }
    
        @Override
        public int getRowCount() {
            return getEmployees().size();
        }
    
        @Override
        public int getColumnCount() {
            return getColumnNames().length;
        }
    
        @Override
        public String getColumnName(int column) {
            return getColumnNames()[column];
        }
    
        @Override
        public Class<?> getColumnClass(int columnIndex) {
            return getColumnClasses()[columnIndex];
        }
    
        public void sortBySalary() {
            BubbleSort.sort(getEmployees(), new Comparator<Employee>() {
                @Override
                public int compare(Employee o1, Employee o2) {
                    // This is decending...
                    return o2.getSalary() - o1.getSalary();
                    // This is acending
                    //return o1.getSalary() - o2.getSalary();
                }
            });
            // You could use
            //fireTableRowsUpdated(0, getRowCount() - 1);
            // But this will work just fine and will force a complete 
            // redraw of the table
            fireTableDataChanged();
        }
    
        public void sortByFirstName() {
            BubbleSort.sort(getEmployees(), new Comparator<Employee>() {
                @Override
                public int compare(Employee o1, Employee o2) {
                    return o1.getFirstName().compareTo(o2.getFirstName());
                }
            });
            fireTableDataChanged();
        }
    
        public void sortByLastName() {
            BubbleSort.sort(getEmployees(), new Comparator<Employee>() {
                @Override
                public int compare(Employee o1, Employee o2) {
                    return o1.getLastName().compareTo(o2.getLastName());
                }
            });
            fireTableDataChanged();
        }
    
        public void add(Employee employee) {
            int rowCount = getRowCount();
            getEmployees().add(employee);
            fireTableRowsInserted(rowCount, rowCount);
        }
    
        public void delete(Employee employee) {
            List<Employee> employees = getEmployees();
            for (int index = 0; index < employees.size(); index++) {
                if (employees.get(index).equals(employee)) {
                    employees.remove(index);
                    fireTableRowsDeleted(index, index);
                    break;
                }
            }
        }
    
        @Override
        public Object getValueAt(int rowIndex, int columnIndex) {
            Employee employee = getEmployees().get(rowIndex);
            switch (columnIndex) {
                case 0:
                    return employee.getFirstName();
                case 1:
                    return employee.getLastName();
                case 2:
                    return employee.getSalary();
            }
            throw new ArrayIndexOutOfBoundsException(rowIndex + "x" + columnIndex + " is out of bounds");
        }
    }
    

    This is a pretty basic concept of a custom TableModel. It takes in a List of Employees and provides the core implementation of the TableModel but also provides some helper methods, like add and delete which will trigger the appropriate events to allow a JTable to update itself.

    You could easily back this with a "database manager" instead, so that sort/delete/add/update actions would all be delegated to it and those updates could be made to the "database" values as well, but I'm going to leave that up to you to figure out.

    Now, I separated your BubbleSort into it's own, self contained and re-usable class...

    public class BubbleSort {
        public static <T> void sort(List<T> list, Comparator<? super T> comparator) {
            // Performing bubble sort
            for (int i = 0; i < list.size() - 1; i++) {
                // Looping through indexes
                for (int j = 0; j < list.size() - 1; j++) {
                    // Comapare the salaries of each indiviual and see if the previous is bigger than the next
                    if (comparator.compare(list.get(j), list.get(j + 1)) > 0) {
                        // Initializing a temp employee to hold value before switching
                        T temp = list.get(j);
    
                        // Swap the employees
                        list.set(j, list.get(j + 1));
                        list.set(j + 1, temp);
                    }
                }
            }
        }
    }
    

    The only constraint is that you need to pass in a Comparator which is used to compare the two values in order to determine if they should be swapped. "But why do this?" I hear you ask, because it's self contained and re-usable.

    So, you want to sort by the salary right? Do you want to sort in ascending...

    BubbleSort.sort(getEmployees(), new Comparator<Employee>() {
        @Override
        public int compare(Employee o1, Employee o2) {
            return o1.getSalary() - o2.getSalary();
        }
    });
    

    or decending order?

    BubbleSort.sort(getEmployees(), new Comparator<Employee>() {
        @Override
        public int compare(Employee o1, Employee o2) {
            return o2.getSalary() - o1.getSalary();
        }
    });
    

    Surprisingly, you can now do both, without much of an effort. But wait, what if you want to sort by the first name?!

    BubbleSort.sort(getEmployees(), new Comparator<Employee>() {
        @Override
        public int compare(Employee o1, Employee o2) {
            return o1.getFirstName().compareTo(o2.getFirstName());
        }
    });
    

    or last name?!

    BubbleSort.sort(getEmployees(), new Comparator<Employee>() {
        @Override
        public int compare(Employee o1, Employee o2) {
            return o1.getLastName().compareTo(o2.getLastName());
        }
    });
    

    See, re-usable.

    You could even come with some combination of those, first name and salary, sure, doable, just write a new Comparator. Want to sort some other type of value, sure, so long as you supply a compatible Comparator, not a problem.

    Runnable example

    I've not bothered with your "database", as it's really not part of the problem, instead, I've focused on getting the table model and sorting to work together (and deleting, because you know, why not).

    import java.awt.*;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Comparator;
    import javax.swing.*;
    import java.util.List;
    import java.util.Objects;
    import java.util.Random;
    import javax.swing.border.EmptyBorder;
    import javax.swing.table.*;
    
    public class Main {
    
        public static void main(String[] args) {
            new Main();
        }
    
        public Main() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    JFrame frame = new JFrame();
                    frame.add(new MainPane());
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                }
            });
        }
    
        public class MainPane extends JPanel {
    
            private EmployeeTableModel employeeTableModel;
            private JTable table;
    
            public MainPane() {
                Random rnd = new Random();
                List<Employee> employees = new ArrayList<>(
                        Arrays.asList(
                                new Employee[]{
                                    new Employee("Alesha", "Golden", rnd.nextInt(999) + 1),
                                    new Employee("Gerald", "Guerrero", rnd.nextInt(999) + 1),
                                    new Employee("Georgina", "Delacruz", rnd.nextInt(999) + 1),
                                    new Employee("Michael", "Delgado", rnd.nextInt(999) + 1),
                                    new Employee("Aysha", "Zimmerman", rnd.nextInt(999) + 1),
                                    new Employee("Yahya", "Moreno", rnd.nextInt(999) + 1),
                                    new Employee("Max", "Reyes", rnd.nextInt(999) + 1),
                                    new Employee("Julia", "Salinas", rnd.nextInt(999) + 1),
                                    new Employee("Aleeza", "Flores", rnd.nextInt(999) + 1),
                                    new Employee("Milton", "Frye", rnd.nextInt(999) + 1),}
                        )
                );
                employeeTableModel = new EmployeeTableModel(employees);
                table = new JTable(employeeTableModel);
    
                setLayout(new BorderLayout());
                add(new JScrollPane(table));
    
                JButton sortBySalaryButton = new JButton("Sort by Salary");
                sortBySalaryButton.addActionListener(new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        employeeTableModel.sortBySalary();
                    }
                });
                JButton sortByFirstNameButton = new JButton("Sort by First name");
                sortByFirstNameButton.addActionListener(new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        employeeTableModel.sortByFirstName();
                    }
                });
                JButton sortLastNameButton = new JButton("Sort by Last name");
                sortLastNameButton.addActionListener(new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        employeeTableModel.sortByLastName();
                    }
                });
                // I'd normally use a SelectionListener to monitor
                // changes to the table in order to enable/disable this
                // button, but that's beyond the scope
                JButton deleteButton = new JButton("Delete");
                deleteButton.addActionListener(new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        int selectedRow = table.getSelectedRow();
                        if (selectedRow < 0) {
                            return;
                        }
                        Employee employee = employeeTableModel.getEmployeeAt(selectedRow);
                        employeeTableModel.delete(employee);
                    }
                });
    
                JPanel actionPane = new JPanel(new GridBagLayout());
                actionPane.setBorder(new EmptyBorder(8, 8, 8, 8));
                actionPane.add(sortBySalaryButton);
                actionPane.add(sortByFirstNameButton);
                actionPane.add(sortLastNameButton);
                actionPane.add(deleteButton);
                add(actionPane, BorderLayout.SOUTH);
            }
    
        }
    
        class Employee {
            // Initalizing variables
            private String firstName;
            private String lastName;
            private int salary;
    
            public Employee(String firstName, String lastName, int salary) {
                this.firstName = firstName;
                this.lastName = lastName;
                this.salary = salary;
            }
    
            public String getFirstName() {
                return firstName;
            }
    
            public String getLastName() {
                return lastName;
            }
    
            public int getSalary() {
                return salary;
            }
    
            @Override
            public int hashCode() {
                int hash = 7;
                hash = 59 * hash + Objects.hashCode(this.firstName);
                hash = 59 * hash + Objects.hashCode(this.lastName);
                hash = 59 * hash + this.salary;
                return hash;
            }
    
            @Override
            public boolean equals(Object obj) {
                if (!(obj instanceof Employee)) {
                    return false;
                }
                Employee other = (Employee) obj;
                return getFirstName().equals(other.getFirstName())
                        && getLastName().equals(other.getLastName())
                        && getSalary() == other.getSalary();
            }
        }
    
        public class EmployeeTableModel extends AbstractTableModel {
    
            private List<Employee> employees;
            private String[] columnNames = {"FName", "LName", "Salary"};
            private Class[] columnClasses = {String.class, String.class, Integer.class};
    
            public EmployeeTableModel(List<Employee> employees) {
                this.employees = employees;
            }
    
            public List<Employee> getEmployees() {
                return employees;
            }
    
            public String[] getColumnNames() {
                return columnNames;
            }
    
            public Class[] getColumnClasses() {
                return columnClasses;
            }
    
            public Employee getEmployeeAt(int row) {
                return getEmployees().get(row);
            }
    
            @Override
            public int getRowCount() {
                return getEmployees().size();
            }
    
            @Override
            public int getColumnCount() {
                return getColumnNames().length;
            }
    
            @Override
            public String getColumnName(int column) {
                return getColumnNames()[column];
            }
    
            @Override
            public Class<?> getColumnClass(int columnIndex) {
                return getColumnClasses()[columnIndex];
            }
    
            public void sortBySalary() {
                BubbleSort.sort(getEmployees(), new Comparator<Employee>() {
                    @Override
                    public int compare(Employee o1, Employee o2) {
                        // This is decending...
                        return o2.getSalary() - o1.getSalary();
                        // This is acending
                        //return o1.getSalary() - o2.getSalary();
                    }
                });
                // You could use
                //fireTableRowsUpdated(0, getRowCount() - 1);
                // But this will work just fine and will force a complete 
                // redraw of the table
                fireTableDataChanged();
            }
    
            public void sortByFirstName() {
                BubbleSort.sort(getEmployees(), new Comparator<Employee>() {
                    @Override
                    public int compare(Employee o1, Employee o2) {
                        return o1.getFirstName().compareTo(o2.getFirstName());
                    }
                });
                fireTableDataChanged();
            }
    
            public void sortByLastName() {
                BubbleSort.sort(getEmployees(), new Comparator<Employee>() {
                    @Override
                    public int compare(Employee o1, Employee o2) {
                        return o1.getLastName().compareTo(o2.getLastName());
                    }
                });
                fireTableDataChanged();
            }
    
            public void add(Employee employee) {
                int rowCount = getRowCount();
                getEmployees().add(employee);
                fireTableRowsInserted(rowCount, rowCount);
            }
    
            public void delete(Employee employee) {
                List<Employee> employees = getEmployees();
                for (int index = 0; index < employees.size(); index++) {
                    if (employees.get(index).equals(employee)) {
                        employees.remove(index);
                        fireTableRowsDeleted(index, index);
                        break;
                    }
                }
            }
    
            @Override
            public Object getValueAt(int rowIndex, int columnIndex) {
                Employee employee = getEmployees().get(rowIndex);
                switch (columnIndex) {
                    case 0:
                        return employee.getFirstName();
                    case 1:
                        return employee.getLastName();
                    case 2:
                        return employee.getSalary();
                }
                throw new ArrayIndexOutOfBoundsException(rowIndex + "x" + columnIndex + " is out of bounds");
            }
        }
    
        public class BubbleSort {
            public static <T> void sort(List<T> list, Comparator<? super T> comparator) {
                // Performing bubble sort
                for (int i = 0; i < list.size() - 1; i++) {
                    // Looping through indexes
                    for (int j = 0; j < list.size() - 1; j++) {
                        // Comapare the salaries of each indiviual and see if the previous is bigger than the next
                        if (comparator.compare(list.get(j), list.get(j + 1)) > 0) {
                            // Initializing a temp employee to hold value before switching
                            T temp = list.get(j);
    
                            // Swap the employees
                            list.set(j, list.get(j + 1));
                            list.set(j + 1, temp);
                        }
                    }
                }
            }
        }
    }