3
\$\begingroup\$

EDIT: Found an issue myself - the SwingWorkers were not printing the tasks in the same order. I fixed it feeding just one SwingWorker a list of reports and looping over it inside the doInBackground() method. I am not sure about it's performance though. Anyone has any thoughts on it? New code for the ProcessAndPrintTask is:

class ProcessAndPrintTask extends SwingWorker<Void, Void> {
        private List<Report> reports;
        Integer reportResult;

        ProcessAndPrintTask(List<Report> reports) {
            this.reports = reports;

        }

        @Override
        protected Void doInBackground() {
            for (Report report : reports) {
                try {
                    reportResult = report.getComparator().compareTwoFiles(new FileInputStream(new File(pathToReportsA + report.getFilename())),
                            new FileInputStream(new File(pathToReportsB + report.getFilename())));
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
                String message = report.getFilename() + ": ";
                if (reportResult != null) {
                    switch (reportResult) {
                        case 1:
                            StyleConstants.setBackground(style, Color.GREEN);
                            try {
                                doc.insertString(doc.getLength(), message + "MATCH\n", style);
                            } catch (BadLocationException ex) {
                                ex.printStackTrace();
                            }
                            break;
                        case 0:
                            StyleConstants.setBackground(style, Color.RED);
                            try {
                                doc.insertString(doc.getLength(), message + "NO MATCH\n\n", style);
                                try {
                                    for (String s : report.getComparator().getDifferences(
                                            new FileInputStream(new File(pathToReportsA + report.getFilename())),
                                            new FileInputStream(new File(pathToReportsB + report.getFilename())))) {
                                        doc.insertString(doc.getLength(), s + "\n", style);
                                    }
                                } catch (Exception ex) {
                                    ex.printStackTrace();
                                }
                            } catch (BadLocationException ex) {
                                ex.printStackTrace();
                            }
                            break;
                        case -1:
                            StyleConstants.setBackground(style, Color.CYAN);
                            try {
                                doc.insertString(doc.getLength(), message + "BOTH FILES EMPTY\n", style);
                            } catch (BadLocationException ex) {
                                ex.printStackTrace();
                            }
                            break;
                        default:
                            StyleConstants.setBackground(style, Color.ORANGE);
                            try {
                                doc.insertString(doc.getLength(), message + "PROBLEM\n", style);
                            } catch (BadLocationException ex) {
                                ex.printStackTrace();
                            }

                    }
                }
                else {
                    StyleConstants.setBackground(style, Color.ORANGE);
                    try {
                        doc.insertString(doc.getLength(), message + "FILE OR FILES NOT FOUND\n", style);
                    }
                    catch (BadLocationException ex) {
                        ex.printStackTrace();
                    }
                }
            }
            return null;
        }
    }

-------------------------------------------------------------------------

This is an app that loads an .xlsx list of report names, compares two instances of each report, prints the result (green - for a match, red - no match, blue - both files empty, yellow - some problem) and differences if any. I used XMLUnit, Apache POI and OpenCSV for accessing the files. It is my first app using Swing as well, so critique is most welcome. OK, this is gonna be a long one...

Report.java

public class Report {
    private String name, format;
    private ReportComparator comparator;

    public Report(String name, String format) {
        this.name = name;
        this.format = format;
        switch (format) {
            case "xml":
                this.comparator = new XMLReportComparator();
                break;
            case "csv":
                this.comparator = new CSVReportComparator();
                break;
            case "xlsx":
                this.comparator = new XLSXReportComparator();
                break;
            default:
                throw new IllegalArgumentException("invalid format");
        }
    }

    String getFilename() {
        return this.name + "." + this.format;
    }

    ReportComparator getComparator() {
        return this.comparator;
    }
}

ReportComparator.java

public interface ReportComparator {
    // compare files (1 - match, 0 - no match, -1 - both files empty)
    int compareTwoFiles(InputStream fileA, InputStream fileB) throws IOException;
    // gets a list of format-specific differences between two files
    ArrayList<String> getDifferences(InputStream fileA, InputStream fileB) throws IOException;
}

XLSXReportComparator.java

public class XLSXReportComparator implements ReportComparator {
    private String xlsxCellDifferenceFormat, xlsxRowDifferenceFormatA,
            xlsxRowDifferenceFormatB, xlsxSheetDifferenceFormatA,
            xlsxSheetDifferenceFormatB, xlsxFileDifferenceFormatA,
            xlsxFileDifferenceFormatB;

    XLSXReportComparator() {
        Properties prop = new Properties();
        String differencesFormats = "..\\resources\\differencesFormats.properties";
        try (InputStream input = new FileInputStream(new File(differencesFormats))){
            prop.load(input);
            xlsxCellDifferenceFormat = prop.getProperty("xlsxCellDifferenceFormat");
            xlsxRowDifferenceFormatA = prop.getProperty("xlsxRowDifferenceFormatA");
            xlsxRowDifferenceFormatB = prop.getProperty("xlsxRowDifferenceFormatB");
            xlsxSheetDifferenceFormatA = prop.getProperty("xlsxSheetDifferenceFormatA");
            xlsxSheetDifferenceFormatB = prop.getProperty("xlsxSheetDifferenceFormatB");
            xlsxFileDifferenceFormatA = prop.getProperty("xlsxFileDifferenceFormatA");
            xlsxFileDifferenceFormatB = prop.getProperty("xlsxFileDifferenceFormatB");
        }

        catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    @Override
    public int compareTwoFiles(InputStream fileA, InputStream fileB) throws IOException{

        Workbook workbookA= StreamingReader.builder()
                .rowCacheSize(100)    // number of rows to keep in memory (defaults to 10)
                .bufferSize(4096)     // buffer size to use when reading InputStream to file (defaults to 1024)
                .open(fileA);

        Workbook workbookB= StreamingReader.builder()
                .rowCacheSize(100)    // number of rows to keep in memory (defaults to 10)
                .bufferSize(4096)     // buffer size to use w
                // hen reading InputStream to file (defaults to 1024)
                .open(fileB);

        Iterator<Sheet> sheetIteratorA = workbookA.iterator();
        Iterator<Sheet> sheetIteratorB = workbookB.iterator();

        // empty file flag
        boolean empty = true;

        while (sheetIteratorA.hasNext() && sheetIteratorB.hasNext()) {
            Iterator<Row> rowIteratorA = sheetIteratorA.next().iterator();
            Iterator<Row> rowIteratorB = sheetIteratorB.next().iterator();
            while (rowIteratorA.hasNext() && rowIteratorB.hasNext()) {
                Iterator<Cell> cellIteratorA = rowIteratorA.next().iterator();
                Iterator<Cell> cellIteratorB = rowIteratorB.next().iterator();
                while (cellIteratorA.hasNext() && cellIteratorB.hasNext()) {
                    Cell cellA = cellIteratorA.next();
                    Cell cellB = cellIteratorB.next();

                    // if the already checked part of file is empty, check if current cells are empty
                    if (empty) {
                        // if one of the cells isn't empty, change empty flag
                        if (!cellA.getStringCellValue().isEmpty() || !cellB.getStringCellValue().isEmpty())
                            empty = false;
                    }
                    // if values are different, return
                    if (!cellA.getStringCellValue().equals(cellB.getStringCellValue()))
                            return 0;
                    }
                // check if any cells remain in either iterator, indicating uneven rows
                if (cellIteratorA.hasNext() || cellIteratorB.hasNext())
                    return 0;
            }
            // check if any rows remain in either iterator, indicating uneven sheets
            if (rowIteratorA.hasNext() || rowIteratorB.hasNext())
                return 0;
        }
        // check if any sheets remain in either iterator, indicating uneven files
        if (sheetIteratorA.hasNext() || sheetIteratorB.hasNext())
            return 0;

        if (empty) {
             return -1;
        }
        return 1;
    }

    @Override
    public ArrayList<String> getDifferences(InputStream fileA, InputStream fileB) throws IOException {

        ArrayList<String> differences = new ArrayList<>();
        Workbook workbookA= StreamingReader.builder()
                .rowCacheSize(100)    // number of rows to keep in memory (defaults to 10)
                .bufferSize(4096)     // buffer size to use when reading InputStream to file (defaults to 1024)
                .open(fileA);

        Workbook workbookB= StreamingReader.builder()
                .rowCacheSize(100)    // number of rows to keep in memory (defaults to 10)
                .bufferSize(4096)     // buffer size to use w
                // hen reading InputStream to file (defaults to 1024)
                .open(fileB);

        Iterator<Sheet> sheetIteratorA = workbookA.iterator();
        Iterator<Sheet> sheetIteratorB = workbookB.iterator();

        while (sheetIteratorA.hasNext() && sheetIteratorB.hasNext()) {
            Sheet currentSheetA = sheetIteratorA.next();
            Sheet currentSheetB = sheetIteratorB.next();
            Iterator<Row> rowIteratorA = currentSheetA.iterator();
            Iterator<Row> rowIteratorB = currentSheetB.iterator();
            while (rowIteratorA.hasNext() && rowIteratorB.hasNext()) {
                Row currentRowA = rowIteratorA.next();
                Row currentRowB = rowIteratorB.next();
                Iterator<Cell> cellIteratorA = currentRowA.iterator();
                Iterator<Cell> cellIteratorB = currentRowB.iterator();
                while (cellIteratorA.hasNext() && cellIteratorB.hasNext()) {
                    Cell currentCellA = cellIteratorA.next();
                    Cell currentCellB = cellIteratorB.next();

                    // if values are different, add to list
                    if (!currentCellA.getStringCellValue().equals(currentCellB.getStringCellValue()))
                        differences.add(String.format(xlsxCellDifferenceFormat,
                                                      currentSheetA.getSheetName(),
                                                      currentRowA.getRowNum(),
                                                      currentCellA.getColumnIndex(),
                                                      currentCellA.getStringCellValue(),
                                                      currentCellB.getStringCellValue())
                                        );
                }

                // check if any cells remain in either iterator, indicating uneven rows
                if (cellIteratorA.hasNext())
                    differences.add(String.format(xlsxRowDifferenceFormatA,
                                                  currentSheetA.getSheetName(),
                                                  currentRowA.getRowNum())
                                    );
                if (cellIteratorB.hasNext())
                    differences.add(String.format(xlsxRowDifferenceFormatB,
                                                  currentSheetA.getSheetName(),
                                                  currentRowA.getRowNum())
                                    );
            }
            // check if any rows remain in either iterator, indicating uneven sheets
            if (rowIteratorA.hasNext())
                differences.add(String.format(xlsxSheetDifferenceFormatA,
                                              currentSheetA.getSheetName())
                );
            if (rowIteratorB.hasNext())
                differences.add(String.format(xlsxSheetDifferenceFormatB,
                                              currentSheetB.getSheetName())
                );
        }
        // check if any sheets remain in either iterator, indicating sheet number
        if (sheetIteratorA.hasNext())
            differences.add(xlsxFileDifferenceFormatA);

        if (sheetIteratorB.hasNext())
            differences.add(xlsxFileDifferenceFormatB);

        return differences;
    }
}

XMLReportComparator.java

public class XMLReportComparator implements ReportComparator {

    @Override
    public int compareTwoFiles(InputStream fileA, InputStream fileB) throws IOException {
        if (fileA.available() == 0 && fileB.available() == 0)
            return -1;
        Diff diff = DiffBuilder
                .compare(fileA)
                .withTest(fileB)
                .withComparisonController(ComparisonControllers.StopWhenDifferent)
                .build();

        if (diff.hasDifferences())
            return 0;
        else
            return 1;
    }

    @Override
    public ArrayList<String> getDifferences(InputStream fileA, InputStream fileB) throws IOException {
        ArrayList<String> differences = new ArrayList<>();
        Diff diff = DiffBuilder.compare(fileA).withTest(fileB).build();
        Iterator<Difference> iter = diff.getDifferences().iterator();
        while (iter.hasNext()) {
            differences.add(iter.next().toString());
        }
        return differences;
    }
}

CSVReportComparator.java

public class CSVReportComparator implements ReportComparator {
    private String csvCellDifferenceFormat, csvRowDifferenceFormatA,
            csvRowDifferenceFormatB, csvFileDifferenceFormatA,
            csvFileDifferenceFormatB;


    CSVReportComparator() {

        Properties p = new Properties();

        String differencesFormats = "..\\resources\\differencesFormats.properties";
        try (InputStream input = new FileInputStream(new File(differencesFormats))){
            p.load(input);
            csvCellDifferenceFormat = p.getProperty("csvCellDifferenceFormat");
            csvRowDifferenceFormatA = p.getProperty("csvRowDifferenceFormatA");
            csvRowDifferenceFormatB = p.getProperty("csvRowDifferenceFormatB");
            csvFileDifferenceFormatA = p.getProperty("csvFileDifferenceFormatA");
            csvFileDifferenceFormatB = p.getProperty("csvFileDifferenceFormatB");
        }

        catch (IOException ex) {
            ex.printStackTrace();
        }
    }
    @Override
    public int compareTwoFiles(InputStream fileA, InputStream fileB) throws IOException {

        CSVReader readerA = new CSVReader(new InputStreamReader(fileA));
        CSVReader readerB = new CSVReader(new InputStreamReader(fileB));
        // empty file flag
        boolean empty = true;
        Iterator<String[]> iteratorA = readerA.iterator();
        Iterator<String[]> iteratorB = readerB.iterator();

        while (iteratorA.hasNext() && iteratorB.hasNext()) {
            String[] currentLineA = iteratorA.next();
            String[] currentLineB = iteratorB.next();
            // if lines length doesn't match - return 0
            if (currentLineA.length != currentLineB.length) {
                return 0;
            }

            else {
                for (int index = 0; index < currentLineA.length; index++) {
                    // if the already checked part of file is empty, check if current cells are empty
                    if (empty) {
                        // if one of the fields isn't empty, change empty flag
                        if (!currentLineA[index].equals("") || !currentLineB[index].equals("")) {
                            empty = false;
                        }
                    }

                    // if fields don't match - return 0
                    if (!currentLineA[index].equals(currentLineB[index])) {
                        return 0;
                    }
                }
            }
        }

        if (iteratorA.hasNext() ^ iteratorB.hasNext()) {
            return 0;
        }

        if (empty) {
            return -1;
        }

        return 1;
    }

    @Override
    public ArrayList<String> getDifferences(InputStream fileA, InputStream fileB) throws IOException {

        ArrayList<String> differences = new ArrayList<>();

        CSVReader readerA = new CSVReader(new InputStreamReader(fileA));
        CSVReader readerB = new CSVReader(new InputStreamReader(fileB));

        Iterator<String[]> iteratorA = readerA.iterator();
        Iterator<String[]> iteratorB = readerB.iterator();
        int iteratorIndex = 0;
        while (iteratorA.hasNext() && iteratorB.hasNext()) {
            String[] currentLineA = iteratorA.next();
            String[] currentLineB = iteratorB.next();
            // check whether rows have the same length
            if (currentLineA.length > currentLineB.length) {
                differences.add(String.format(csvRowDifferenceFormatA, iteratorIndex));
            }

            else if (currentLineA.length < currentLineB.length) {
                differences.add(String.format(csvRowDifferenceFormatB, iteratorIndex));
            }

            // check for differences in fields even if lengths don't match
            for (int index = 0; index < currentLineA.length && index < currentLineB.length; index++) {
                // if fields don't match - add to list
                if (!currentLineA[index].equals(currentLineB[index])) {
                    differences.add(String.format(csvCellDifferenceFormat, iteratorIndex, index, currentLineA[index], currentLineB[index]));
                }
            }
            iteratorIndex++;
        }

        if (iteratorA.hasNext()) {
            differences.add(csvFileDifferenceFormatA);
        }

        if (iteratorB.hasNext()) {
            differences.add(csvFileDifferenceFormatB);
        }

        return differences;
    }
}

GUI Client ReportTestGUI.java

public class ReportTestGUI extends JPanel
                           implements ActionListener {
    private String reportListPath, pathToReportsA, pathToReportsB;
    JButton chooseListButton, processReportsButton;
    JTextPane messagePane;
    JFileChooser fileChooser;
    StyledDocument doc;
    Style style;


    class ProcessAndPrintTask extends SwingWorker<Void, Void> {
        private Report report;
        Integer reportResult;
        public ProcessAndPrintTask(Report report) {
            this.report = report;
            reportResult = null;
        }

        @Override
        protected Void doInBackground() {

            try {
                reportResult = report.getComparator().compareTwoFiles(new FileInputStream(new File(pathToReportsA + report.getFilename())),
                        new FileInputStream(new File(pathToReportsB + report.getFilename())));
            }

            catch (IOException ex) {
                ex.printStackTrace();
            }
            return null;
        }

        @Override
        protected void done() {
            String message = report.getFilename() + ": ";
            if (reportResult != null) {
                // print result with style according to result code
                switch (reportResult) {
                    case 1:
                        StyleConstants.setBackground(style, Color.GREEN);
                        try {
                            doc.insertString(doc.getLength(), message + "MATCH\n", style);
                        }
                        catch (BadLocationException ex) {}
                        break;
                    case 0:
                        StyleConstants.setBackground(style, Color.RED);
                        try {
                            doc.insertString(doc.getLength(), message + "NO MATCH\n\n", style);
                            try {
                                for (String s : report.getComparator().getDifferences(
                                        new FileInputStream(new File(pathToReportsA + report.getFilename())),
                                        new FileInputStream(new File(pathToReportsB + report.getFilename())))) {
                                    doc.insertString(doc.getLength(), s + "\n", style);
                                }
                            } catch (Exception ex) {
                                ex.printStackTrace();
                            }
                        }
                        catch (BadLocationException ex) {}
                        break;
                    case -1:
                        StyleConstants.setBackground(style, Color.CYAN);
                        try {
                            doc.insertString(doc.getLength(), message + "BOTH FILES EMPTY\n", style);
                        }
                        catch (BadLocationException ex) {}
                        break;
                    default:
                        StyleConstants.setBackground(style, Color.ORANGE);
                        try {
                            doc.insertString(doc.getLength(), message + "PROBLEM\n", style);
                        }
                        catch (BadLocationException ex) {}

                }
            }
            else {
                StyleConstants.setBackground(style, Color.ORANGE);
                try {
                    doc.insertString(doc.getLength(), message + "FILE OR FILES NOT FOUND\n", style);
                }
                catch (BadLocationException ex) {}
            }

        }
    }
    public ReportTestGUI() {
        super(new BorderLayout());
        messagePane = new JTextPane();
        messagePane.setMargin(new Insets(5, 5, 5, 5));
        messagePane.setEditable(false);
        doc = messagePane.getStyledDocument();
        style = messagePane.addStyle("Style", null);
        JScrollPane messageScrollPane = new JScrollPane(messagePane);
        fileChooser = new JFileChooser();
        chooseListButton = new JButton("Choose report list");
        processReportsButton = new JButton("Process records");
        chooseListButton.addActionListener(this);
        chooseListButton.setEnabled(false);
        processReportsButton.addActionListener(this);
        JPanel buttonPanel = new JPanel();
        buttonPanel.add(chooseListButton);
        buttonPanel.add(processReportsButton);
        add(buttonPanel, BorderLayout.PAGE_START);
        add(messageScrollPane, BorderLayout.CENTER);
        loadDefaultData();
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        if (e.getSource() == chooseListButton) {
            int returnVal = fileChooser.showOpenDialog(ReportTestGUI.this);

            if (returnVal == JFileChooser.APPROVE_OPTION) {
                reportListPath = fileChooser.getSelectedFile().getAbsolutePath();
            }
        }
        else if (e.getSource() == processReportsButton) {

            StyleConstants.setBackground(style, Color.GREEN);
            try (FileInputStream reportListExcelFile = new FileInputStream(new File(reportListPath))) {
                Workbook workbook = new XSSFWorkbook(reportListExcelFile);
                Sheet sheet = workbook.getSheetAt(0);
                Iterator<Row> iter = sheet.iterator();

                // skip first row that contains columns names
                iter.next();

                while (iter.hasNext()) {

                    Row r = iter.next();
                    String name = r.getCell(0).getStringCellValue();
                    String format = r.getCell(1).getStringCellValue();

                    Report currentReport = new Report(name, format);
                    new ProcessAndPrintTask(currentReport).execute();
                }
            }

            catch (IOException exc) {
                exc.printStackTrace();
            }
        }
    }

    private static void createAndShowGUI() {
        JFrame mainFrame = new JFrame("Report Comparator");
        mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        mainFrame.add(new ReportTestGUI());
        mainFrame.pack();
        mainFrame.setVisible(true);
        mainFrame.setVisible(true);
    }

    private void loadDefaultData() {
        Properties prop = new Properties();
        String configFilePath = "..\\resources\\config.properties";
        try (InputStream input = new FileInputStream(new File(configFilePath))) {
            prop.load(input);
            String resourcesPath = prop.getProperty("resourcesPath");
            reportListPath = resourcesPath + prop.getProperty("reportListFilename");
            pathToReportsA = prop.getProperty("pathToReportsA");
            pathToReportsB = prop.getProperty("pathToReportsB");
        }

        catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main (String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }
}
\$\endgroup\$
2
\$\begingroup\$

I was looking at this

           for (int index = 0; index < currentLineA.length; index++) {
                // if the already checked part of file is empty, check if current cells are empty
                if (empty) {
                    // if one of the fields isn't empty, change empty flag
                    if (!currentLineA[index].equals("") || !currentLineB[index].equals("")) {
                        empty = false;
                    }
                }

                // if fields don't match - return 0
                if (!currentLineA[index].equals(currentLineB[index])) {
                    return 0;
                }
            }

if both are equal to "" then they would be equal, right? then we don't want to run the next if statement. we should add a continue to that if statement so that we can go to the next cell and check it.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Thank you, I missed that. It probably wouldn't be an issue for most of the real-life cases I am dealing with at the moment but if a file begins with a lot of empty fields, performance would suffer. \$\endgroup\$
    – DCzo
    Aug 11 '17 at 8:29
2
\$\begingroup\$

I did not read everything in detail.

You do the heavy work in a SwingWorker not to the block the UI thread which is good. You might have used JavaFX instead of Swing since the latter is more or less deprecated.

In Report I would have made all members final. I would have made the getters public instead of package default; I'm not sure if there was a reason for that.

When you create an ArrayList, it's better to define the variable as a List and also the returned values. It's better to use the most general interface since you can then change the implementation by only modifying the point of creation without changing any other code. Also the code is easier to read if it's just List, without wondering if there is some reason why it's an ArrayList and not a List.

\$\endgroup\$
3
  • \$\begingroup\$ Thank you for your insight! About the Report class - actually, the lack of access modifiers is not intentional, I simply forgot to add one ;). However, I seriously doubt that it is going to be used outside this application, so I don't know if making them public is necessary. What are your thoughts about it? \$\endgroup\$
    – DCzo
    Aug 11 '17 at 8:27
  • \$\begingroup\$ I would make them public. The reason might seem silly but it's just because it's easier to read. If it's not public, people will stop and think about why it's not public (that's what I did). Java should have made public the default. Kotlin tries to correct Java's mistakes and they made accessors public by default. \$\endgroup\$
    – toto2
    Aug 11 '17 at 12:58
  • \$\begingroup\$ That is not a silly reason at all - after all time is money :) \$\endgroup\$
    – DCzo
    Aug 11 '17 at 13:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.