javaswingjtablecomparatortablerowsorter

How to sort a column by data from another column in JTable


I need to sort the column with "File creation date", but if the column "File type" == FyleType.DIRECTORY this row should be at the top. That is, first sorting should be by type, and then by date.

My JTable:


Type Filename Date
DIRECTORY filename1 2025.04.03
DIRECTORY filename2 2025.02.01
FILE filename3.txt 2025.01.01
FILE filename4.txt 2025.02.03

I tried using

TableRowSorter<FileTableModel> tableRowSorter = new TableRowSorter<>(tableModel);
tableRowSorter.setComparator(2, ((o1, o2) -> {}

Comparator but it only gets the value of the current cell and I can't get the file type because the date is not unique


Solution

  • Another option is to create a JTable that contains a File copy of every data column. This way, you would need to change the TableCellRenderer to display the file type, file name, or file creation date from the File depending on the column, but the comparators for each column would be able to get the file type and file creation date from the File at the same time.

    import java.awt.*;
    import java.io.File;
    import java.io.IOException;
    import java.nio.file.Files;
    import java.nio.file.attribute.BasicFileAttributes;
    import java.nio.file.attribute.FileTime;
    import java.text.DateFormat;
    import java.text.SimpleDateFormat;
    import java.util.Arrays;
    import java.util.Collections;
    import java.util.Comparator;
    import java.util.List;
    import java.util.Optional;
    import javax.swing.*;
    import javax.swing.table.DefaultTableCellRenderer;
    import javax.swing.table.DefaultTableModel;
    import javax.swing.table.TableModel;
    import javax.swing.table.TableRowSorter;
    
    public class GroupDirectoriesFirstComparatorTest {
      private Component makeUI() {
        String[] columnNames = {"Type", "Filename", "Date"};
        DefaultTableModel model = new DefaultTableModel(columnNames, 0) {
          @Override
          public Class<?> getColumnClass(int column) {
            return File.class;
          }
        };
        JTable table = new JTable(model);
        table.setAutoCreateRowSorter(true);
        table.setDefaultRenderer(File.class, new FileCellRenderer());
        RowSorter<? extends TableModel> sorter = table.getRowSorter();
        if (sorter instanceof TableRowSorter) {
          TableRowSorter<? extends TableModel> rs =
              (TableRowSorter<? extends TableModel>) sorter;
          for (int i = 0; i < model.getColumnCount(); i++) {
            rs.setComparator(i, new GroupDirectoriesFirstComparator(table, i));
          }
        }
        JButton button = new JButton("Choose directory");
        button.addActionListener(e -> {
          JFileChooser chooser = new JFileChooser();
          chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
          int ret = chooser.showOpenDialog(button.getRootPane());
          if (ret == JFileChooser.APPROVE_OPTION) {
            model.setRowCount(0);
            File[] files = chooser.getSelectedFile().listFiles();
            if (files != null) {
              Arrays.stream(files)
                  .map(f -> Collections.nCopies(3, f).toArray())
                  .forEach(model::addRow);
            }
          }
        });
        JPanel p = new JPanel(new BorderLayout());
        p.add(new JScrollPane(table));
        p.add(button, BorderLayout.SOUTH);
        return p;
      }
    
      public static void main(String[] args) {
        EventQueue.invokeLater(() -> {
          JFrame frame = new JFrame();
          frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
          frame.getContentPane().add(
              new GroupDirectoriesFirstComparatorTest().makeUI());
          frame.pack();
          frame.setLocationRelativeTo(null);
          frame.setVisible(true);
        });
      }
    }
    
    class FileCellRenderer extends DefaultTableCellRenderer {
      private final DateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd");
    
      @Override
      public Component getTableCellRendererComponent(
          JTable table, Object value,
          boolean isSelected, boolean hasFocus,
          int row, int column) {
        Component c = super.getTableCellRendererComponent(
            table, value, isSelected, hasFocus, row, column);
        if (c instanceof JLabel && value instanceof File) {
          JLabel l = (JLabel) c;
          l.setHorizontalAlignment(LEFT);
          File file = (File) value;
          switch (table.convertColumnIndexToModel(column)) {
            case 0:
              l.setText(file.isDirectory() ? "DIRECTORY" : "FILE");
              break;
            case 1:
              l.setText(file.getName());
              break;
            case 2:
              l.setHorizontalAlignment(RIGHT);
              try {
                BasicFileAttributes attr =
                    Files.readAttributes(file.toPath(), BasicFileAttributes.class);
                l.setText(dateFormat.format(attr.creationTime().toMillis()));
              } catch (IOException e) {
                throw new RuntimeException(e);
              }
              break;
            default:
              break;
          }
        }
        return c;
      }
    }
    
    class FileComparator implements Comparator<File> {
      private final int column;
    
      public FileComparator(int column) {
        this.column = column;
      }
    
      @Override
      public int compare(File a, File b) {
        switch (column) {
          case 0: return getWeight(a) - getWeight(b);
          case 2: return Long.compare(getCreationTime(a), getCreationTime(b));
          default: return a.getName().compareToIgnoreCase(b.getName());
        }
      }
    
      public int getColumn() {
        return column;
      }
    
      public static int getWeight(File file) {
        return file.isDirectory() ? 1 : 2;
      }
    
      public static Long getCreationTime(File file) {
        BasicFileAttributes attr;
        try {
          attr = Files.readAttributes(file.toPath(), BasicFileAttributes.class);
        } catch (IOException e) {
          throw new RuntimeException(e);
        }
        return Optional.ofNullable(attr)
            .map(BasicFileAttributes::creationTime)
            .map(FileTime::toMillis)
            .orElse(0L);
      }
    }
    
    // https://java-swing-tips.blogspot.com/2011/11/jtable-group-directories-first-sorting.html
    class GroupDirectoriesFirstComparator extends FileComparator {
      private final JTable table;
    
      public GroupDirectoriesFirstComparator(JTable table, int column) {
        super(column);
        this.table = table;
      }
    
      @Override
      public int compare(File a, File b) {
        int v = getWeight(a) - getWeight(b);
        return v == 0 ? super.compare(a, b) : v * getSortOrderDirection();
      }
    
      private int getSortOrderDirection() {
        int dir = 1;
        List<? extends RowSorter.SortKey> keys = table.getRowSorter().getSortKeys();
        if (!keys.isEmpty()) {
          RowSorter.SortKey sortKey = keys.get(0);
          if (sortKey.getColumn() == getColumn() &&
              sortKey.getSortOrder() == SortOrder.DESCENDING) {
            dir = -1;
          }
        }
        return dir;
      }
    }