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();
}
});
}
}