Java DefaultTableModel gather data from multiple tables

1.2k Views Asked by At

I have a data relation

Person-Attends-Course

Person
-------
id: integer primary key
name: string
prename: string
age: integer

Course
-------
courseName: string primary key
hours: integer

Attends
--------
id: integer primary key references Person
courseName: string primary key references Course

I need to have a data validation. After a long search on the Internet, I decided to implement it by extending DefaultTableModel. So far I created the code below and I have some questions:

Class Person

class Person{
  private String name, prename,id;
  private int age;

  public Person(){}

  public String getName(){return this.name;}
  public String getPrename(){return this.prename;}
  public int getAge(){return this.age;}
  public String getId(){return this.id;}

  public void setName(String name){
    try{
        if(name.equals(""))
            JOptionPane.showMessageDialog(null, "Must insert name");
        else
            this.name = name;
    }
    catch(NullPointerException e){
        this.name = "";
    }
}

  public void setPrename(String prename){
    if(prename.equals(""))
        JOptionPane.showMessageDialog(null, "Must insert prename");
    else
        this.prename = prename;
  }

  public void setAge(int age){
    try{
        if(age <0 || age >=100)
            JOptionPane.showMessageDialog(null, "Must insert valid age");
        else
            this.age = age;
    }
    catch(Exception e){
        this.age = 0;
    }
  }

  public void setId(String id){
    this.id = id;
  }
}

Class PersonTM

class PersonTM extends DefaultTableModel{
  private final List<Person> personlist;
  private final String[] columnNames = {"id", "name", "prename","age"};
  private final Class[] columnClass = {String.class, String.class, String.class, Integer.class};

  public PersonTM(){this.personlist = new ArrayList<>();}

@Override
public Class<?> getColumnClass(int columnIndex){
    return columnClass[columnIndex];
}

@Override
public String getColumnName(int column){
    return columnNames[column];
}

public String[] getColumnNames(){return this.columnNames;}

@Override
public int getColumnCount() {
    return columnNames.length;
}

@Override
public Object getValueAt(int rowIndex, int columnIndex) {
    Person row = personlist.get(rowIndex);
    switch(columnIndex){
        case 0:
            return row.getId();
        case 1:
            return row.getName();
        case 2:
            return row.getPrename();
        case 3:
            return row.getAge();
        default:
            return null;
    }
}

@Override
public void setValueAt(Object obj, int rowIndex, int columnIndex){
    Person row = personlist.get(rowIndex);
    switch(columnIndex){
        case 0:
            row.setId((String)obj);
            break;
        case 1:
            row.setName((String)obj);
            break;
        case 2:
            row.setPrename((String)obj);
            break;
        case 3:
            row.setAge((Integer)obj);
            break;
    }
  }
}

Class Course

class Courses{
  private String courseName;
  private int hours;

  public Courses(){}

  public String getCourseName(){return this.courseName;}
  public int getHours() {return this.hours;}

  public void setCourseName(String courseName){
    if(courseName.equals(""))
        JOptionPane.showMessageDialog(null, "Must insert courseName");
    else
        this.courseName = courseName;
  }

  public void setHours(int hours){
    if(hours <=0 || hours >=50)
        JOptionPane.showMessageDialog(null, "Must insert valid hours");
    else
        this.hours = hours;
  }
}

Class CourseTM

class CoursesTM extends DefaultTableModel{
  private final List<Courses> courseslist;
  private final String[] columnNames = {"course name","hours"};
  private final Class[] columnClass = {String.class,Integer.class};

  public CoursesTM(){this.courseslist = new ArrayList<>();}

  @Override
  public Class<?> getColumnClass(int columnIndex){
    return columnClass[columnIndex];
  }

  @Override
  public String getColumnName(int column){
    return columnNames[column];
  }

  public String[] getColumnNames(){return this.columnNames;}

  @Override
  public int getColumnCount() {
    return columnNames.length;
  }

  @Override
  public Object getValueAt(int rowIndex, int columnIndex) {
    Courses row = courseslist.get(rowIndex);
    switch(columnIndex){
        case 0:
            return row.getCourseName();
        case 1:
            return row.getHours();
        default:
            return null;
    }
  }

  @Override
  public void setValueAt(Object obj, int rowIndex, int columnIndex){
    Courses row = courseslist.get(rowIndex);
    switch(columnIndex){
        case 0:
            row.setCourseName((String)obj);
            break;
        case 1:
            row.setHours((Integer)obj);
            break;
    }
  }
}

Class Attends

//here I am a little confused

class Attends{
  private final Person p;
  private final Course c;

  public Attends(Person p,Course c){
    this.p = p;
    this.c = c;
  }

  public Person getPerson(){return this.p;}
  public Course getCourse(){return this.c;}
}

Class AttendsTM

class AttendsTM extends DefaultTableModel{
  private final PersonTM p;
  private final CoursesTM c;
  private final Map<Person,List<Course>> attendslist;
  private String[] columnNames;
  private final Class[] columnClass = {Person.class,Courses.class};

  public AttendsTM(Map<Person,List<Courses>> attendslist){
    this.attendslist = attendslist;
    this.p = new PersonTM();
    this.c = new CoursesTM();
  }

  public void setColumnNames(){
    for (int i = 0; i < p.getColumnCount(); i++)
        columnNames[i] = p.getColumnName(i);
    for (int i = p.getColumnCount(); i < c.getColumnCount(); i++)
        columnNames[i] = c.getColumnName(i);
  }
}

Class Viewer

class viewer extends JFrame{
  private final JScrollPane scrollPane;
  private final AttendsTM model;
  private final JTable table;

  public viewer(){
    //collect data from db
    Person p1 = new Person();
    p1.setId("00001");
    p1.setName("John");
    p1.setPrename("Johnson");
    p1.setAge(30);

    Person p2 = new Person();
    p2.setId("00002");
    p2.setName("Jack");
    p2.setPrename("Jackson");
    p2.setAge(30);

    Courses c1 = new Courses();
    c1.setCourseName("History of art");
    c1.setHours(25);

    Courses c2 = new Courses();
    c2.setCourseName("Music");
    c2.setHours(15);

    List<Courses> coursesList = new ArrayList<>();
    coursesList.add(c1);
    coursesList.add(c2);

    Map<Person,List<Courses>> attendsMap = new LinkedHashMap<>();
    attendsMap.put(p1, coursesList);
    attendsMap.put(p2, coursesList);

    model = new AttendsTM(attendsMap);
    table = new JTable(model);
    //add a blank row at the end of Jtable
    model.addRow(new Object[model.getColumnCount()]);
    table.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
    table.setCellSelectionEnabled(true);
    table.setColumnSelectionAllowed(false);
    table.setRowSelectionAllowed(true);
    //resize columns and use horizontal scroll bar to view data
    table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
    //disable column dragging
    table.getTableHeader().setReorderingAllowed(false);
    scrollPane = new JScrollPane(table, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
}

  public void initializeUI(){
    add(scrollPane);
    setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
    setSize(300,300);
    setVisible(true);
  }
}

Class TableModelExample

public class TableModelExample {
  public static void main(String[] af){
    Runnable runnable = new Runnable(){
        @Override
        public void run() {
            new viewer().initializeUI();
        }
    };
    EventQueue.invokeLater(runnable);
  }
}

So my questions are:

  1. How am I supposesd to appear a JTable with all columns from Person and Courses? Should I do this through Attends and how?

  2. How am I gathering data from Person and Course in AttendsTM? Am I doing it right?

Any suggestion would be very wellcome. Thank you in advance.

2

There are 2 best solutions below

3
On
    Vector<Object> columnNames = new Vector<Object>();
    Vector<Object> data = new Vector<Object>();    

Assume you get a Table1List from database, Table1 consist Table2 object as well. In my example I am using JPA
// Get column names

          columnNames.addElement("Table1 Attribute 1");
          columnNames.addElement("Table1 Attribute 2");
          columnNames.addElement("Table1 Attribute 3");
          columnNames.addElement("Other Table Attribute 1");
          columnNames.addElement("Other Table Attribute 2");
            // Display Row value
            for (Table1 Table1Obj: Table1List) {
                Vector<Object> row = new Vector<Object>();
                Table2 Table2Obj = getTable2DataFromDatabase(Table1Obj.getTable1Id());
                row.addElement(Table1Obj.getTable1 Name());
                row.addElement(Table1Obj.getTable1 Email());
                row.addElement(Table1Obj.getTable1 Phone());
                row.addElement(Table2Obj.getTable1 ID());
                row.addElement(Table1Obj.getTable1 Name());

                data.addElement(row);

// Create table with database data

final DefaultTableModel model = new DefaultTableModel(data, columnNames) {

    private static final long serialVersionUID = 1L;

    @Override
    public Class getColumnClass(int column) {
        for (int row = 0; row < getRowCount(); row++) {
            Object o = getValueAt(row, column);
            if (o != null) {
                return o.getClass();
            }
        }

        return Object.class;
    }
};
9
On

I didn't thoroughly read your code (it's quite a lot) but basically you'd do the following:

If you're using DefaultTableModel you'd most probably use the constructor DefaultTableModel(Object[][] data, Object[] columnNames). In that case you'd load your persons and courses and build a 2D array of the data that you need to be displayed. Additionally you'd just pass column names as needed, e.g. if the first element of each row in your 2D array contains the person's name you'd pass something like "name" as the first element in the column name array.

The layout of your 2D array depends on how you want to display your data. Each element of a row would correspond to a cell in the table. If you want to have a row per course and person you'd just generate multiple rows most probably getting redundant/duplicate cells. Of course you could just fill the first cells and empty any redundant elements in order to render empty cells where needed, but in that case you should not allow sorting.

If you want to put all courses a person takes into a single cell you could either use a pre-formatted string (AFAIK the default renderers can display html-formatted text) or provide your own renderer.

Example for the simple approach using duplicate cells:

Object[][] data = new Object[numrows][];
int curRow = 0;
for( Person p : persons ) { //assuming you have loaded the persons already
  for( Course c : p.getCourses() ) { //loop over all courses a person has taken
    Object[] row = new Object[numcols];  //create a new row array

    //just an example of how to fill the elements
    row[0] = p.getName(); //column 1 will contain the person's name
    row[1] = c.getCourseName(); //column 2 will contain the course name
    ...

    data[curRow] = row;
    curRow++; 
  }
}

Then you just pass that array to the DefaultTableModel constructor. The result might look like this:

+-------------+------------------------+
| Person name |  Course                |
+=============+========================+
| Vassilis De | Java Programming       |
+-------------+------------------------+
| Thomas      | Java Programming       |
+-------------+------------------------+
| Thomas      | How to answer in SO    |
+-------------+------------------------+
| Thomas      | Writing good examples  |
+-------------+------------------------+
| Thomas      | Being polite           |
+-------------+------------------------+

Please note that this just covers how to build the table model from already loaded data. How you load the data would depend on your application but in the examples I assume you're using JPA or something similar.

Update:

If I understand your comment correctly, there are two basic questions:

  1. where to load the data
  2. how to add new columns

I'd do both in a controller class which then just passes the data to the table model, i.e. you might not even need AttendsTM etc. Have a look at the MVC pattern for more information on how to create and use controllers.

When saving entered data the controller would then read the data from the table model and interpret in a way that allows you to add new entries into your database. Again how this is done depends on your application.

There might be one reason to use a custom table model (e.g. as a subclass of DefaultTableModel): if you want to add some keys to the model which allow you to identify entities but which should not be displayed in the table, you could then override getValueAt() etc. in order to hide certain columns.