From afa0afc63d7166c1686561b2400fefcf1b4dd102 Mon Sep 17 00:00:00 2001 From: ghostpencil Date: Mon, 16 Jan 2023 08:54:34 -0500 Subject: [PATCH 1/5] Added support for logging into SpreadSheetDiffer and the UnifiedDiffSpreadSheetDiffCallback. Added new main class ExcelSorter that can be used to sort Excel sheets by multiple columns. --- .../spreadsheet/diff/SpreadSheetDiffer.java | 7 +- .../UnifiedDiffSpreadSheetDiffCallback.java | 6 +- .../com/ka/spreadsheet/sort/ColumnSort.java | 22 ++ .../com/ka/spreadsheet/sort/ExcelSorter.java | 275 ++++++++++++++++++ 4 files changed, 306 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/ka/spreadsheet/sort/ColumnSort.java create mode 100644 src/main/java/com/ka/spreadsheet/sort/ExcelSorter.java diff --git a/src/main/java/com/ka/spreadsheet/diff/SpreadSheetDiffer.java b/src/main/java/com/ka/spreadsheet/diff/SpreadSheetDiffer.java index 4a870e4..76f0ad9 100644 --- a/src/main/java/com/ka/spreadsheet/diff/SpreadSheetDiffer.java +++ b/src/main/java/com/ka/spreadsheet/diff/SpreadSheetDiffer.java @@ -1,5 +1,8 @@ package com.ka.spreadsheet.diff; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + import static com.ka.spreadsheet.diff.Flags.WORKBOOK1; import static com.ka.spreadsheet.diff.Flags.WORKBOOK2; @@ -12,7 +15,7 @@ public class SpreadSheetDiffer { - + private static final Logger logger = LogManager.getLogger(SpreadSheetDiffer.class); public static void main(String[] args) { int ret = doDiff(args); System.exit(ret); @@ -42,7 +45,7 @@ public static int doDiff(String[] args) { System.err.println("Diff failed: " + e.getMessage()); } } - return ret; + return 0; } public static int doDiff(SpreadSheetDiffCallback diffCallback) throws Exception { diff --git a/src/main/java/com/ka/spreadsheet/diff/UnifiedDiffSpreadSheetDiffCallback.java b/src/main/java/com/ka/spreadsheet/diff/UnifiedDiffSpreadSheetDiffCallback.java index 3a00e1a..a8ef7cc 100644 --- a/src/main/java/com/ka/spreadsheet/diff/UnifiedDiffSpreadSheetDiffCallback.java +++ b/src/main/java/com/ka/spreadsheet/diff/UnifiedDiffSpreadSheetDiffCallback.java @@ -1,6 +1,8 @@ package com.ka.spreadsheet.diff; -import java.io.File; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + import java.util.ArrayList; import java.util.List; @@ -25,7 +27,7 @@ // ... // Each cell data line is always present, even if the data is empty (thus just "-" or "+). public class UnifiedDiffSpreadSheetDiffCallback extends SpreadSheetDiffCallbackBase { - + private static final Logger logger = LogManager.getLogger(UnifiedDiffSpreadSheetDiffCallback.class); private final String lineSeparator = System.getProperty("line.separator"); private String file1; private String file2; diff --git a/src/main/java/com/ka/spreadsheet/sort/ColumnSort.java b/src/main/java/com/ka/spreadsheet/sort/ColumnSort.java new file mode 100644 index 0000000..ae0388c --- /dev/null +++ b/src/main/java/com/ka/spreadsheet/sort/ColumnSort.java @@ -0,0 +1,22 @@ +package com.ka.spreadsheet.sort; + +public class ColumnSort { + public enum SortDirection{ + Ascending,Descending + } + private int columnIndex; + private SortDirection sortDirection; + + public ColumnSort(int columnIndex,SortDirection sortDirection){ + this.columnIndex=columnIndex; + this.sortDirection=sortDirection; + } + + public int getColumnIndex() { + return columnIndex; + } + + public SortDirection getSortDirection() { + return sortDirection; + } +} diff --git a/src/main/java/com/ka/spreadsheet/sort/ExcelSorter.java b/src/main/java/com/ka/spreadsheet/sort/ExcelSorter.java new file mode 100644 index 0000000..6925728 --- /dev/null +++ b/src/main/java/com/ka/spreadsheet/sort/ExcelSorter.java @@ -0,0 +1,275 @@ +package com.ka.spreadsheet.sort; + +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.xssf.usermodel.*; +import org.apache.commons.io.FilenameUtils; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.List; + + +public class ExcelSorter { + + private static String WORKBOOK_FILENAME; +/* + FilePath: + SheetSort: A formatted value that indicates the sheet to be sorted, the columns to sort by and the starting row. + [Sheet Index:{Column Index!A (Ascending) or D (Descending),...},Start Row Index] + Here are a few examples of the above format: + Sort the first sheet by the first column in ascending order and the second column in descending order. Start the sort at row 1. + [0:{0!A,1!D}:1] + Sort the second sheet by the first column in ascending order. Start the sort at row 3. + [1:{0!A}:2] + Sort the fourth sheet by the first column in ascending order, the second column in descending order and the fifth column in ascending order. Start the sort at row 1. + [3:{0!A,1!D,4!A}:0] + */ + + public static void main(String[] args) throws Exception { + WORKBOOK_FILENAME = args[0]; + SortSheetParams sortSheetParams1 = parseArgument(args[1]); + FileInputStream inputStream = new FileInputStream(WORKBOOK_FILENAME); + XSSFWorkbook workbook = new XSSFWorkbook(inputStream); + try { + sortSheet(workbook, sortSheetParams1); + inputStream.close(); + // Write the output to a new file + FileOutputStream outputStream = new FileOutputStream(FilenameUtils.getFullPath(WORKBOOK_FILENAME) + "sorted_" + + FilenameUtils.getBaseName(WORKBOOK_FILENAME) + + "." + FilenameUtils.getExtension(WORKBOOK_FILENAME)); + workbook.write(outputStream); + outputStream.close(); + } catch (RuntimeException rex){ + rex.printStackTrace(); + } catch (Exception ex){ + ex.printStackTrace(); + } + } + + private static SortSheetParams parseArgument(String sheetSortArg){ + SortSheetParams sortSheetParams = new SortSheetParams(); + // Regular expression to match the format + String pattern = "\\[(\\d+)\\:\\{(.*?)\\}\\:(\\d+)\\]"; + // Compile the regular expression + Pattern r = Pattern.compile(pattern); + // Match the input against the regular expression + Matcher m = r.matcher(sheetSortArg); + System.out.println("Parse Argument:"); + if (m.find()) { + sortSheetParams.sheetIndex = Integer.parseInt(m.group(1)); + sortSheetParams.columnSortList = parseColumnSortArg(m.group(2)); + sortSheetParams.rowStartIndex = Integer.parseInt(m.group(3)); + System.out.println("....sheetIndex:" + sortSheetParams.sheetIndex); + System.out.println("....columnSortList:" + m.group(2)); + System.out.println("....rowStartIndex:" + sortSheetParams.rowStartIndex); + } + return sortSheetParams; + } + + private static List parseColumnSortArg(String columnSortArg){ + List columnSortList = new ArrayList<>(); + // Split the second value string by "," + String[] secondValuePairs = columnSortArg.split(","); + ColumnSort colSort; + for (String pair : secondValuePairs) { + // Split the pair by "!" + String[] keyValue = pair.split("!"); + int key = Integer.parseInt(keyValue[0]); + String value = keyValue[1]; + colSort = new ColumnSort(key,value.compareToIgnoreCase("A") == 0 ? ColumnSort.SortDirection.Ascending: ColumnSort.SortDirection.Descending); + columnSortList.add(colSort); + } + return columnSortList; + } + + /** + * Sorts (A-Z) rows by String column + * @param workbook - Workbook to sort + * @param params - Sheet sort parameters that include the row start index and the list of columns to sort by + */ + private static void sortSheet(XSSFWorkbook workbook, SortSheetParams params) { + boolean sorting = true; + XSSFSheet sheet = workbook.getSheetAt(params.sheetIndex); + int lastRow = sheet.getLastRowNum(); + while (sorting) { + sorting = false; + for(int i = params.rowStartIndex;i < lastRow;i++) { + System.out.println(i); + Row row = sheet.getRow(i); + if (row == null) continue; + // end if this is last row + if (lastRow==row.getRowNum()) break; + Row row2 = sheet.getRow(row.getRowNum()+1); + if (row2 == null) continue; + //compare cell from current row and next row - and switch if secondValue should be before first + if (compareRows(params.columnSortList,row,row2)) { + System.out.println("ROW:" + row.getRowNum()); + swapRows(workbook,sheet,row,row2); + sorting = true; + } + } + } + } + + private static void swapRows(XSSFWorkbook workbook, XSSFSheet sheet,Row row1,Row row2){ + int lastRow = sheet.getLastRowNum(); + //Create a row after row2 as placeholder + sheet.shiftRows(row2.getRowNum()+1,lastRow,1,true,true); + //Copy row 1 into the new row + copyRow(workbook,sheet,row1.getRowNum(),row2.getRowNum()+1); + //Remove row 1 + removeRow(sheet,row1.getRowNum()); + } + + private static boolean compareRows(List columnSortList,Row row,Row row2) { + boolean ret = false; + for (ColumnSort columnSort : columnSortList) { + Cell cell1 = row.getCell(columnSort.getColumnIndex()); + Cell cell2 = row2.getCell(columnSort.getColumnIndex()); + ret = compareCells(cell1,cell2,columnSort.getSortDirection()); + if(ret) break; + } + return ret; + } + + private static boolean compareCells(Cell row1Cell, Cell row2Cell, ColumnSort.SortDirection sortDirection) { + if (row1Cell == null || row2Cell == null) return false; + boolean ret; + switch(row1Cell.getCellType()){ + case Cell.CELL_TYPE_BOOLEAN: + boolean bvalue1 = row1Cell.getBooleanCellValue(); + boolean bvalue2 = row2Cell.getBooleanCellValue(); + ret = sortDirection == ColumnSort.SortDirection.Ascending ? + Boolean.compare(bvalue2,bvalue1) <0: + Boolean.compare(bvalue2,bvalue1) >0; + break; + case Cell.CELL_TYPE_STRING: + String svalue1 = row1Cell.getStringCellValue(); + String svalue2 = row2Cell.getStringCellValue(); + ret = sortDirection == ColumnSort.SortDirection.Ascending ? + svalue2.compareToIgnoreCase(svalue1) < 0: + svalue2.compareToIgnoreCase(svalue1) > 0; + break; + case Cell.CELL_TYPE_NUMERIC: + Double dvalue1 = row1Cell.getNumericCellValue(); + Double dvalue2 = row2Cell.getNumericCellValue(); + ret = sortDirection == ColumnSort.SortDirection.Ascending ? + dvalue2.compareTo(dvalue1) < 0: + dvalue2.compareTo(dvalue1) > 0; + break; + default: + ret = false; + } + return ret; + } + + private static void copyRow(XSSFWorkbook workbook, XSSFSheet worksheet, int sourceRowNum, int destinationRowNum) { + // Get the source / new row + XSSFRow newRow = worksheet.getRow(destinationRowNum); + XSSFRow sourceRow = worksheet.getRow(sourceRowNum); + + // If the row exist in destination, push down all rows by 1 else create a new row + if (newRow != null) { + worksheet.shiftRows(destinationRowNum, worksheet.getLastRowNum(), 1); + } else { + newRow = worksheet.createRow(destinationRowNum); + } + + // Loop through source columns to add to new row + for (int i = 0; i < sourceRow.getLastCellNum(); i++) { + // Grab a copy of the old/new cell + XSSFCell oldCell = sourceRow.getCell(i); + XSSFCell newCell = newRow.createCell(i); + + // If the old cell is null jump to next cell + if (oldCell == null) { + newCell = null; + continue; + } + + // Copy style from old cell and apply to new cell + XSSFCellStyle newCellStyle = workbook.createCellStyle(); + newCellStyle.cloneStyleFrom(oldCell.getCellStyle()); + + newCell.setCellStyle(newCellStyle); + + // If there is a cell comment, copy + if (oldCell.getCellComment() != null) { + newCell.setCellComment(oldCell.getCellComment()); + } + + // If there is a cell hyperlink, copy + if (oldCell.getHyperlink() != null) { + newCell.setHyperlink(oldCell.getHyperlink()); + } + + // Set the cell data type + newCell.setCellType(oldCell.getCellType()); + + // Set the cell data value + switch (oldCell.getCellType()) { + case Cell.CELL_TYPE_BLANK: + newCell.setCellValue(oldCell.getStringCellValue()); + break; + case Cell.CELL_TYPE_BOOLEAN: + newCell.setCellValue(oldCell.getBooleanCellValue()); + break; + case Cell.CELL_TYPE_ERROR: + newCell.setCellErrorValue(oldCell.getErrorCellValue()); + break; + case Cell.CELL_TYPE_FORMULA: + newCell.setCellFormula(oldCell.getCellFormula()); + break; + case Cell.CELL_TYPE_NUMERIC: + newCell.setCellValue(oldCell.getNumericCellValue()); + break; + case Cell.CELL_TYPE_STRING: + newCell.setCellValue(oldCell.getRichStringCellValue()); + break; + } + } + + // If there are are any merged regions in the source row, copy to new row + for (int i = 0; i < worksheet.getNumMergedRegions(); i++) { + CellRangeAddress cellRangeAddress = worksheet.getMergedRegion(i); + if (cellRangeAddress.getFirstRow() == sourceRow.getRowNum()) { + CellRangeAddress newCellRangeAddress = new CellRangeAddress(newRow.getRowNum(), + (newRow.getRowNum() + + (cellRangeAddress.getLastRow() - cellRangeAddress.getFirstRow() + )), + cellRangeAddress.getFirstColumn(), + cellRangeAddress.getLastColumn()); + worksheet.addMergedRegion(newCellRangeAddress); + } + } + } + + /** + * Remove a row by its index + * @param sheet a Excel sheet + * @param rowIndex a 0 based index of removing row + */ + public static void removeRow(XSSFSheet sheet, int rowIndex) { + int lastRowNum=sheet.getLastRowNum(); + if(rowIndex>=0&&rowIndex columnSortList; + int rowStartIndex; + } +} From d157f2b9949edd3fb5ecf1fffe461aa37da89b25 Mon Sep 17 00:00:00 2001 From: ghostpencil Date: Sat, 21 Jan 2023 11:15:18 -0500 Subject: [PATCH 2/5] Changed the approach to sorting used by ExcelSorter. Swapping rows was inefficient. Sorting is now achieved using the following steps: 1) Add the rows that require sorting to a list. 2) Sort the list. 3) Write the sorted list to a new temporary sheet. 4) Delete the sorted rows from the original sheet. 5) Copy the rows from the temporary sheet to the original sheet. 6) Delete the temp sheet. A log file name parameter was added to both SpreadSheetDiffer and ExcelSorter. --- build.gradle | 2 + .../java/com/ka/spreadsheet/diff/Flags.java | 6 +- .../spreadsheet/diff/SpreadSheetDiffer.java | 2 + .../com/ka/spreadsheet/sort/ExcelSorter.java | 198 ++++++++++-------- .../java/com/ka/spreadsheet/util/LogUtil.java | 23 ++ 5 files changed, 146 insertions(+), 85 deletions(-) create mode 100644 src/main/java/com/ka/spreadsheet/util/LogUtil.java diff --git a/build.gradle b/build.gradle index 1231158..dd89a56 100644 --- a/build.gradle +++ b/build.gradle @@ -30,7 +30,9 @@ distributions { dependencies { implementation("org.apache.poi:poi-ooxml:3.12") implementation("org.apache.odftoolkit:simple-odf:0.8.2-incubating") + implementation("org.apache.commons:commons-io:1.3.2") implementation("com.google.code.findbugs:jsr305:3.0.2") + implementation('org.apache.logging.log4j:log4j-api:2.19.0') } sourceSets { diff --git a/src/main/java/com/ka/spreadsheet/diff/Flags.java b/src/main/java/com/ka/spreadsheet/diff/Flags.java index ed33fa7..b94ddef 100644 --- a/src/main/java/com/ka/spreadsheet/diff/Flags.java +++ b/src/main/java/com/ka/spreadsheet/diff/Flags.java @@ -15,6 +15,7 @@ public class Flags { public static Double DIFF_NUMERIC_PRECISION; public static boolean DIFF_IGNORE_FORMULAS; public static DiffFormatter DIFF_FORMAT; + public static String LOG_FILENAME; public static File WORKBOOK1; public static File WORKBOOK2; public static WorkbookIgnores WORKBOOK_IGNORES1; @@ -58,8 +59,9 @@ public static boolean parseFlags(String[] args) { System.out.println(usage()); return false; } - WORKBOOK1 = new File(args[0]); - WORKBOOK2 = new File(args[1]); + LOG_FILENAME = args[0]; + WORKBOOK1 = new File(args[1]); + WORKBOOK2 = new File(args[2]); WORKBOOK_IGNORES1 = WorkbookIgnores.parseWorkbookIgnores(args, "--ignore1"); WORKBOOK_IGNORES2 = WorkbookIgnores.parseWorkbookIgnores(args, "--ignore2"); return true; diff --git a/src/main/java/com/ka/spreadsheet/diff/SpreadSheetDiffer.java b/src/main/java/com/ka/spreadsheet/diff/SpreadSheetDiffer.java index 76f0ad9..66206b3 100644 --- a/src/main/java/com/ka/spreadsheet/diff/SpreadSheetDiffer.java +++ b/src/main/java/com/ka/spreadsheet/diff/SpreadSheetDiffer.java @@ -1,5 +1,6 @@ package com.ka.spreadsheet.diff; +import com.ka.spreadsheet.util.LogUtil; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -25,6 +26,7 @@ public static int doDiff(String[] args) { int ret = -1; try { if (Flags.parseFlags(args)) { + LogUtil.initLogging(Flags.LOG_FILENAME); SpreadSheetDiffCallback formatter; switch (Flags.DIFF_FORMAT) { case EXCEL_CMP: diff --git a/src/main/java/com/ka/spreadsheet/sort/ExcelSorter.java b/src/main/java/com/ka/spreadsheet/sort/ExcelSorter.java index 6925728..77e49f7 100644 --- a/src/main/java/com/ka/spreadsheet/sort/ExcelSorter.java +++ b/src/main/java/com/ka/spreadsheet/sort/ExcelSorter.java @@ -1,5 +1,7 @@ package com.ka.spreadsheet.sort; +import com.ka.spreadsheet.util.LogUtil; +import org.apache.log4j.Logger; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.util.CellRangeAddress; @@ -9,34 +11,51 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.util.ArrayList; +import java.util.Collections; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.List; public class ExcelSorter { - + private static Logger log = Logger.getLogger(ExcelSorter.class); private static String WORKBOOK_FILENAME; -/* - FilePath: - SheetSort: A formatted value that indicates the sheet to be sorted, the columns to sort by and the starting row. - [Sheet Index:{Column Index!A (Ascending) or D (Descending),...},Start Row Index] - Here are a few examples of the above format: - Sort the first sheet by the first column in ascending order and the second column in descending order. Start the sort at row 1. - [0:{0!A,1!D}:1] - Sort the second sheet by the first column in ascending order. Start the sort at row 3. - [1:{0!A}:2] - Sort the fourth sheet by the first column in ascending order, the second column in descending order and the fifth column in ascending order. Start the sort at row 1. - [3:{0!A,1!D,4!A}:0] - */ + private static String LOG_FILENAME; + private static final String instructions = "FilePath: The fully qualified path to the Excel workbook file you want to sort.\n" + + "LogFile: The name of the log file to use for the application.\n"+ + "SheetSort: A formatted value that indicates the sheet to be sorted, the columns to sort by and the starting row.\n" + + " Each sheet sort argument has the following format:\n"+ + " [Sheet Index:{Column Index!A (Ascending) or D (Descending),...},Start Row Index]\n" + + " Here are a few examples of the above format:\n" + + " Sort the first sheet by the first column in ascending order and the second column in descending order. Start the sort at row 1.\n" + + " [0:{0!A,1!D}:1]\n" + + " Sort the second sheet by the first column in ascending order. Start the sort at row 3.\n" + + " [1:{0!A}:2]\n" + + " Sort the fourth sheet by the first column in ascending order, the second column in descending order and the fifth column in ascending order. Start the sort at row 1.\n" + + " [3:{0!A,1!D,4!A}:0]"; public static void main(String[] args) throws Exception { - WORKBOOK_FILENAME = args[0]; - SortSheetParams sortSheetParams1 = parseArgument(args[1]); - FileInputStream inputStream = new FileInputStream(WORKBOOK_FILENAME); - XSSFWorkbook workbook = new XSSFWorkbook(inputStream); + FileInputStream inputStream = null; + List sortSheetParamsList = new ArrayList<>(); try { - sortSheet(workbook, sortSheetParams1); + //Parse the arguments + for(int i=0;i< args.length;i++){ + switch(i){ + case 0: + LOG_FILENAME = args[i]; + LogUtil.initLogging(LOG_FILENAME); + break; + case 1: + WORKBOOK_FILENAME = args[i]; + inputStream = new FileInputStream(WORKBOOK_FILENAME); + break; + default: + sortSheetParamsList.add(parseArgument(args[i])); + } + } + XSSFWorkbook workbook = new XSSFWorkbook(inputStream); + //Sort the workbook sheets + sortSheetParamsList.forEach(sortSheetParams -> {sortSheet(workbook, sortSheetParams);}); inputStream.close(); // Write the output to a new file FileOutputStream outputStream = new FileOutputStream(FilenameUtils.getFullPath(WORKBOOK_FILENAME) + "sorted_" + @@ -44,11 +63,56 @@ public static void main(String[] args) throws Exception { "." + FilenameUtils.getExtension(WORKBOOK_FILENAME)); workbook.write(outputStream); outputStream.close(); - } catch (RuntimeException rex){ - rex.printStackTrace(); + }catch(IllegalArgumentException iex){ + log.error("Unable to process the arguments.",iex); + System.out.println("Unable to process the arguments."); + System.out.println(instructions); + System.exit(100); + } + catch (RuntimeException rex){ + log.error("An unexpected runtime exception occurred.",rex); + System.out.println("An unexpected runtime exception occurred."); + rex.printStackTrace(); + System.exit(200); } catch (Exception ex){ - ex.printStackTrace(); + log.error("An unexpected exception occurred.",ex); + System.exit(300); } + System.exit(0); + } + + private static void sortSheet(XSSFWorkbook workbook,SortSheetParams params){ + XSSFSheet sheet = workbook.getSheetAt(params.sheetIndex); + XSSFSheet newSheet = workbook.createSheet("sorted" + '_' + sheet.getSheetName()); + //Collect the rows in an array list + ArrayList rows = new ArrayList<>(); + for(Row row:sheet){ + if(row.getRowNum() < params.rowStartIndex) + continue; + rows.add(row); + } + //Sort the array list by the criteria + Collections.sort(rows, + (row1, row2) -> compareRows(params.columnSortList,row2,row1) + ); + //Write the sorted rows to the new sheet + int i = 0; + for(Row row:rows){ + copyRow(workbook,sheet,newSheet,row.getRowNum(),i); + i++; + } + //Remove rows from the original sheet + for(Row row:rows){ + sheet.removeRow(row); + } + //Copy sorted rows back to the original sheet. + i=params.rowStartIndex; + for(Row row:newSheet){ + copyRow(workbook,newSheet,sheet,row.getRowNum(),i); + i++; + } + //Delete the new sheet + workbook.removeSheetAt(workbook.getNumberOfSheets()-1); } private static SortSheetParams parseArgument(String sheetSortArg){ @@ -59,14 +123,14 @@ private static SortSheetParams parseArgument(String sheetSortArg){ Pattern r = Pattern.compile(pattern); // Match the input against the regular expression Matcher m = r.matcher(sheetSortArg); - System.out.println("Parse Argument:"); + log.info("Parse Argument:"); if (m.find()) { sortSheetParams.sheetIndex = Integer.parseInt(m.group(1)); sortSheetParams.columnSortList = parseColumnSortArg(m.group(2)); sortSheetParams.rowStartIndex = Integer.parseInt(m.group(3)); - System.out.println("....sheetIndex:" + sortSheetParams.sheetIndex); - System.out.println("....columnSortList:" + m.group(2)); - System.out.println("....rowStartIndex:" + sortSheetParams.rowStartIndex); + log.info("....sheetIndex:" + sortSheetParams.sheetIndex); + log.info("....columnSortList:" + m.group(2)); + log.info("....rowStartIndex:" + sortSheetParams.rowStartIndex); } return sortSheetParams; } @@ -87,97 +151,65 @@ private static List parseColumnSortArg(String columnSortArg){ return columnSortList; } - /** - * Sorts (A-Z) rows by String column - * @param workbook - Workbook to sort - * @param params - Sheet sort parameters that include the row start index and the list of columns to sort by - */ - private static void sortSheet(XSSFWorkbook workbook, SortSheetParams params) { - boolean sorting = true; - XSSFSheet sheet = workbook.getSheetAt(params.sheetIndex); - int lastRow = sheet.getLastRowNum(); - while (sorting) { - sorting = false; - for(int i = params.rowStartIndex;i < lastRow;i++) { - System.out.println(i); - Row row = sheet.getRow(i); - if (row == null) continue; - // end if this is last row - if (lastRow==row.getRowNum()) break; - Row row2 = sheet.getRow(row.getRowNum()+1); - if (row2 == null) continue; - //compare cell from current row and next row - and switch if secondValue should be before first - if (compareRows(params.columnSortList,row,row2)) { - System.out.println("ROW:" + row.getRowNum()); - swapRows(workbook,sheet,row,row2); - sorting = true; - } - } - } - } private static void swapRows(XSSFWorkbook workbook, XSSFSheet sheet,Row row1,Row row2){ + int row2Index = row2.getRowNum(); int lastRow = sheet.getLastRowNum(); //Create a row after row2 as placeholder - sheet.shiftRows(row2.getRowNum()+1,lastRow,1,true,true); + sheet.shiftRows(row2.getRowNum() + 1, row2Index == lastRow ? lastRow+1:lastRow, + 1, true, true); //Copy row 1 into the new row - copyRow(workbook,sheet,row1.getRowNum(),row2.getRowNum()+1); + copyRow(workbook,sheet,sheet,row1.getRowNum(), row2.getRowNum() + 1); //Remove row 1 - removeRow(sheet,row1.getRowNum()); + removeRow(sheet, row1.getRowNum()); } - private static boolean compareRows(List columnSortList,Row row,Row row2) { - boolean ret = false; + private static int compareRows(List columnSortList,Row row,Row row2) { + int ret = 0; for (ColumnSort columnSort : columnSortList) { Cell cell1 = row.getCell(columnSort.getColumnIndex()); Cell cell2 = row2.getCell(columnSort.getColumnIndex()); ret = compareCells(cell1,cell2,columnSort.getSortDirection()); - if(ret) break; + if(ret != 0) break; } return ret; } - private static boolean compareCells(Cell row1Cell, Cell row2Cell, ColumnSort.SortDirection sortDirection) { - if (row1Cell == null || row2Cell == null) return false; - boolean ret; + private static int compareCells(Cell row1Cell, Cell row2Cell, ColumnSort.SortDirection sortDirection) { + if (row1Cell == null || row2Cell == null) return 0; + int ret; switch(row1Cell.getCellType()){ case Cell.CELL_TYPE_BOOLEAN: boolean bvalue1 = row1Cell.getBooleanCellValue(); boolean bvalue2 = row2Cell.getBooleanCellValue(); - ret = sortDirection == ColumnSort.SortDirection.Ascending ? - Boolean.compare(bvalue2,bvalue1) <0: - Boolean.compare(bvalue2,bvalue1) >0; + ret = Boolean.compare(bvalue2,bvalue1); break; case Cell.CELL_TYPE_STRING: String svalue1 = row1Cell.getStringCellValue(); String svalue2 = row2Cell.getStringCellValue(); - ret = sortDirection == ColumnSort.SortDirection.Ascending ? - svalue2.compareToIgnoreCase(svalue1) < 0: - svalue2.compareToIgnoreCase(svalue1) > 0; + ret = svalue2.compareToIgnoreCase(svalue1); break; case Cell.CELL_TYPE_NUMERIC: Double dvalue1 = row1Cell.getNumericCellValue(); Double dvalue2 = row2Cell.getNumericCellValue(); - ret = sortDirection == ColumnSort.SortDirection.Ascending ? - dvalue2.compareTo(dvalue1) < 0: - dvalue2.compareTo(dvalue1) > 0; + ret = dvalue2.compareTo(dvalue1); break; default: - ret = false; + ret = 0; } - return ret; + return sortDirection == ColumnSort.SortDirection.Ascending ? ret : -ret; } - private static void copyRow(XSSFWorkbook workbook, XSSFSheet worksheet, int sourceRowNum, int destinationRowNum) { + private static void copyRow(XSSFWorkbook workbook, XSSFSheet srcWorksheet, XSSFSheet destWorksheet, int sourceRowNum, int destinationRowNum) { // Get the source / new row - XSSFRow newRow = worksheet.getRow(destinationRowNum); - XSSFRow sourceRow = worksheet.getRow(sourceRowNum); + XSSFRow newRow = destWorksheet.getRow(destinationRowNum); + XSSFRow sourceRow = srcWorksheet.getRow(sourceRowNum); // If the row exist in destination, push down all rows by 1 else create a new row if (newRow != null) { - worksheet.shiftRows(destinationRowNum, worksheet.getLastRowNum(), 1); + destWorksheet.shiftRows(destinationRowNum, destWorksheet.getLastRowNum(), 1); } else { - newRow = worksheet.createRow(destinationRowNum); + newRow = destWorksheet.createRow(destinationRowNum); } // Loop through source columns to add to new row @@ -235,8 +267,8 @@ private static void copyRow(XSSFWorkbook workbook, XSSFSheet worksheet, int sour } // If there are are any merged regions in the source row, copy to new row - for (int i = 0; i < worksheet.getNumMergedRegions(); i++) { - CellRangeAddress cellRangeAddress = worksheet.getMergedRegion(i); + for (int i = 0; i < srcWorksheet.getNumMergedRegions(); i++) { + CellRangeAddress cellRangeAddress = srcWorksheet.getMergedRegion(i); if (cellRangeAddress.getFirstRow() == sourceRow.getRowNum()) { CellRangeAddress newCellRangeAddress = new CellRangeAddress(newRow.getRowNum(), (newRow.getRowNum() + @@ -244,15 +276,15 @@ private static void copyRow(XSSFWorkbook workbook, XSSFSheet worksheet, int sour )), cellRangeAddress.getFirstColumn(), cellRangeAddress.getLastColumn()); - worksheet.addMergedRegion(newCellRangeAddress); + destWorksheet.addMergedRegion(newCellRangeAddress); } } } /** * Remove a row by its index - * @param sheet a Excel sheet - * @param rowIndex a 0 based index of removing row + * @param sheet an Excel sheet + * @param rowIndex a 0 based index of the row being removed */ public static void removeRow(XSSFSheet sheet, int rowIndex) { int lastRowNum=sheet.getLastRowNum(); diff --git a/src/main/java/com/ka/spreadsheet/util/LogUtil.java b/src/main/java/com/ka/spreadsheet/util/LogUtil.java new file mode 100644 index 0000000..2dbffe8 --- /dev/null +++ b/src/main/java/com/ka/spreadsheet/util/LogUtil.java @@ -0,0 +1,23 @@ +package com.ka.spreadsheet.util; + +import org.apache.log4j.FileAppender; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.log4j.PatternLayout; + +public class LogUtil { + public static final String ROOT_DIRECTORY = "root_directory"; + + public static void initLogging(String logFileName){ + FileAppender fa = new FileAppender(); + fa.setName("FileLogger"); + String rd = System.getProperty(ROOT_DIRECTORY); + rd = rd == null ? "." : rd; + fa.setFile(rd + "/logs/" +logFileName); + fa.setLayout(new PatternLayout("%d %-5p [%c{1}] %m%n")); + fa.setThreshold(Level.DEBUG); + fa.setAppend(false); + fa.activateOptions(); + Logger.getRootLogger().addAppender(fa); + } +} \ No newline at end of file From 46eb181fe67ed3764a67e8a976afddde0ac29d64 Mon Sep 17 00:00:00 2001 From: ghostpencil Date: Sun, 22 Jan 2023 16:00:34 -0500 Subject: [PATCH 3/5] Added specialized logging to the ExcelCompare diff output. There is a LoggerSpreadSheetDiffCallback class and a LoggerUnifiedDiffSpreadSheetDiffCallback class. --- build.gradle | 1 - .../java/com/ka/spreadsheet/diff/Flags.java | 4 +- .../diff/LoggerSpreadSheetDiffCallback.java | 97 +++++++++++ ...gerUnifiedDiffSpreadSheetDiffCallback.java | 156 ++++++++++++++++++ .../spreadsheet/diff/SpreadSheetDiffer.java | 12 +- .../UnifiedDiffSpreadSheetDiffCallback.java | 3 - .../com/ka/spreadsheet/sort/ExcelSorter.java | 1 + .../java/com/ka/spreadsheet/util/LogUtil.java | 25 ++- 8 files changed, 288 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/ka/spreadsheet/diff/LoggerSpreadSheetDiffCallback.java create mode 100644 src/main/java/com/ka/spreadsheet/diff/LoggerUnifiedDiffSpreadSheetDiffCallback.java diff --git a/build.gradle b/build.gradle index dd89a56..9d33db7 100644 --- a/build.gradle +++ b/build.gradle @@ -32,7 +32,6 @@ dependencies { implementation("org.apache.odftoolkit:simple-odf:0.8.2-incubating") implementation("org.apache.commons:commons-io:1.3.2") implementation("com.google.code.findbugs:jsr305:3.0.2") - implementation('org.apache.logging.log4j:log4j-api:2.19.0') } sourceSets { diff --git a/src/main/java/com/ka/spreadsheet/diff/Flags.java b/src/main/java/com/ka/spreadsheet/diff/Flags.java index b94ddef..5e1bd65 100644 --- a/src/main/java/com/ka/spreadsheet/diff/Flags.java +++ b/src/main/java/com/ka/spreadsheet/diff/Flags.java @@ -23,7 +23,9 @@ public class Flags { public enum DiffFormatter { EXCEL_CMP, - UNIFIED + UNIFIED, + LOGGER, + LOGGER_UNIFIED } public static boolean parseFlags(String[] args) { diff --git a/src/main/java/com/ka/spreadsheet/diff/LoggerSpreadSheetDiffCallback.java b/src/main/java/com/ka/spreadsheet/diff/LoggerSpreadSheetDiffCallback.java new file mode 100644 index 0000000..f1491d8 --- /dev/null +++ b/src/main/java/com/ka/spreadsheet/diff/LoggerSpreadSheetDiffCallback.java @@ -0,0 +1,97 @@ +package com.ka.spreadsheet.diff; + + +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; + +import java.util.LinkedHashSet; +import java.util.Set; + +public class LoggerSpreadSheetDiffCallback extends SpreadSheetDiffCallbackBase { + private static final Logger cleanLogger = LogManager.getLogger("DisplayLogger"); + private final Set sheets = new LinkedHashSet(); + private final Set rows = new LinkedHashSet(); + private final Set cols = new LinkedHashSet(); + private final Set macros = new LinkedHashSet(); + + private final Set sheets1 = new LinkedHashSet(); + private final Set rows1 = new LinkedHashSet(); + private final Set cols1 = new LinkedHashSet(); + + private final Set sheets2 = new LinkedHashSet(); + private final Set rows2 = new LinkedHashSet(); + private final Set cols2 = new LinkedHashSet(); + + private final Set macros1 = new LinkedHashSet(); + private final Set macros2 = new LinkedHashSet(); + + private String file1; + private String file2; + + @Override + public void init(String file1, String file2) { + super.init(file1, file2); + this.file1 = file1; + this.file2 = file2; + } + + @Override + public void reportWorkbooksDiffer(boolean differ) { + super.reportWorkbooksDiffer(differ); + reportSummary("DIFF", sheets, rows, cols, macros); + reportSummary("EXTRA WB1", sheets1, rows1, cols1, macros1); + reportSummary("EXTRA WB2", sheets2, rows2, cols2, macros2); + cleanLogger.info("-----------------------------------------"); + cleanLogger.info("Excel files " + file1 + " and " + file2 + " " + + (differ ? "differ" : "match")); + } + + @Override + public void reportMacroOnlyIn(boolean inFirstSpreadSheet) { + super.reportMacroOnlyIn(inFirstSpreadSheet); + String name = "unknown"; + (inFirstSpreadSheet ? macros1 : macros2).add(name); + cleanLogger.info("EXTRA macro name: " + name + " found only in " + wb(inFirstSpreadSheet)); + } + + @Override + public void reportExtraCell(boolean inFirstSpreadSheet, CellPos c) { + super.reportExtraCell(inFirstSpreadSheet, c); + if (inFirstSpreadSheet) { + sheets1.add(c.getSheetName()); + rows1.add(c.getRow()); + cols1.add(c.getColumn()); + } else { + sheets2.add(c.getSheetName()); + rows2.add(c.getRow()); + cols2.add(c.getColumn()); + } + cleanLogger.info("EXTRA Cell in " + wb(inFirstSpreadSheet) + " " + c.getCellPosition() + + " => '" + c.getCellValue() + "'"); + } + + @Override + public void reportDiffCell(CellPos c1, CellPos c2) { + super.reportDiffCell(c1, c2); + sheets.add(c1.getSheetName()); + rows.add(c1.getRow()); + cols.add(c1.getColumn()); + cleanLogger.info("DIFF Cell at " + c1.getCellPosition() + " => '" + c1.getCellValue() + + "' v/s '" + c2.getCellValue() + "'"); + } + + private void reportSummary(String what, Set sheets, Set rows, Set cols, + Set macros) { + cleanLogger.info("----------------- " + what + " -------------------"); + cleanLogger.info("Sheets: " + sheets); + cleanLogger.info("Rows: " + rows); + cleanLogger.info("Cols: " + cols); + if (!macros.isEmpty()) { + cleanLogger.info("Macros: " + macros); + } + } + + private String wb(boolean inFirstSpreadSheet) { + return inFirstSpreadSheet ? "WB1" : "WB2"; + } +} diff --git a/src/main/java/com/ka/spreadsheet/diff/LoggerUnifiedDiffSpreadSheetDiffCallback.java b/src/main/java/com/ka/spreadsheet/diff/LoggerUnifiedDiffSpreadSheetDiffCallback.java new file mode 100644 index 0000000..b3486cf --- /dev/null +++ b/src/main/java/com/ka/spreadsheet/diff/LoggerUnifiedDiffSpreadSheetDiffCallback.java @@ -0,0 +1,156 @@ +package com.ka.spreadsheet.diff; + + +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; + +import java.util.ArrayList; +import java.util.List; + +import static com.ka.spreadsheet.diff.SpreadSheetUtils.CELL_INTERNAL_TO_USER; + +// Output format, inspired by the traditional Unified Diff format (but using cell references instead of line numbers): +// +--- column 1 +// V +// --- file_1_name!sheet_1_name +// +++ file_2_name!sheet_1_name +// @@ cell_1_ref,cell_n_ref cell_1_ref,cell_n_ref @@ +// -old_cell_1_data +// -... +// -old_cell_n_data +// +new_cell_1_data +// +... +// +new_cell_n_data +// @@@ ... +// ... +// --- file_1_name!sheet_2_name +// +++ file_2_name!sheet_2_name +// ... +// Each cell data line is always present, even if the data is empty (thus just "-" or "+). +public class LoggerUnifiedDiffSpreadSheetDiffCallback extends SpreadSheetDiffCallbackBase { + private static final Logger cleanLogger = LogManager.getLogger("DisplayLogger"); + private final String lineSeparator = System.getProperty("line.separator"); + private String file1; + private String file2; + private DiffCell prevDiffCell = new DiffCell("", -2, -2, null, null); + private List currentCellBlock = new ArrayList(); + + @Override + public void init(String file1, String file2) { + super.init(file1, file2); + this.file1 = file1; + this.file2 = file2; + } + + @Override + public void finish() { + super.finish(); + printAndEmptyCellBlock(); + } + + @Override + public void reportMacroOnlyIn(boolean inFirstSpreadSheet) { + super.reportMacroOnlyIn(inFirstSpreadSheet); + cleanLogger.info("Unified diff format does not support macros, however WB" + (inFirstSpreadSheet ? "1" : "2") + " contains at least one macro that is not in the other workbook."); + } + + @Override + public void reportExtraCell(boolean inFirstSpreadSheet, CellPos c) { + super.reportExtraCell(inFirstSpreadSheet, c); + accumulateAndMaybePrint(new DiffCell( + c.getSheetName(), + c.getRowIndex(), + c.getColumnIndex(), + (inFirstSpreadSheet ? c.getCellValue() : null), + (!inFirstSpreadSheet ? c.getCellValue() : null) + )); + } + + @Override + public void reportDiffCell(CellPos c1, CellPos c2) { + super.reportDiffCell(c1, c2); + accumulateAndMaybePrint(new DiffCell( + c1.getSheetName(), + c1.getRowIndex(), + c1.getColumnIndex(), + c1.getCellValue(), + c2.getCellValue() + )); + } + + private void accumulateAndMaybePrint(DiffCell diffCell) { + if (!isSameSheet(prevDiffCell, diffCell)) { + printAndEmptyCellBlock(); + cleanLogger.info("--- " + file1 + "!" + diffCell.sheetName); + cleanLogger.info("+++ " + file2 + "!" + diffCell.sheetName); + } + if (!isSameRow(prevDiffCell, diffCell)) { + printAndEmptyCellBlock(); + } + if (!isSameCellBlock(prevDiffCell, diffCell)) { + printAndEmptyCellBlock(); + } + currentCellBlock.add(diffCell); + prevDiffCell = diffCell; + } + + private boolean isSameSheet(DiffCell c1, DiffCell c2) { + return c1.sheetName.equals(c2.sheetName); + } + + private boolean isSameRow(DiffCell c1, DiffCell c2) { + return c1.rowIndex == c2.rowIndex; + } + + private boolean isSameCellBlock(DiffCell c1, DiffCell c2) { + return c1.colIndex == (c2.colIndex - 1); + } + + // TODO: Make this handle multiple rows, maybe. What would that output look like? This might be a bad idea. + private void printAndEmptyCellBlock() { + if (currentCellBlock.size() > 0) { + StringBuilder sheet1Lines = new StringBuilder(); + StringBuilder sheet2Lines = new StringBuilder(); + String cellRange = CELL_INTERNAL_TO_USER(currentCellBlock.get(0).rowIndex, currentCellBlock.get(0).colIndex); + if (currentCellBlock.size() > 1) { + cellRange = cellRange + "," + + CELL_INTERNAL_TO_USER(currentCellBlock.get(0).rowIndex, currentCellBlock.get(currentCellBlock.size()-1).colIndex); + } + cleanLogger.info("@@ -" + cellRange + " +" + cellRange + " @@"); + int prevCol = -1; + for (DiffCell rowCell : currentCellBlock) { + assert currentCellBlock.get(0).rowIndex == rowCell.rowIndex : "printAndEmptyCellBlock() only supports one row at a time."; + assert prevCol == -1 || prevCol == (rowCell.colIndex -1 ) : "printAndEmptyCellBlock() only supports a contiguous range of cells at a time."; + sheet1Lines.append("-"); + sheet2Lines.append("+"); + if (rowCell.c1Value != null) { + sheet1Lines.append(rowCell.c1Value); + } + if (rowCell.c2Value != null) { + sheet2Lines.append(rowCell.c2Value); + } + sheet1Lines.append(lineSeparator); + sheet2Lines.append(lineSeparator); + } + cleanLogger.info(sheet1Lines.toString()); + cleanLogger.info(sheet2Lines.toString()); + } + currentCellBlock = new ArrayList(); + } + + private class DiffCell { + String sheetName; + int rowIndex; + int colIndex; + CellValue c1Value; + CellValue c2Value; + + public DiffCell(String _sheetName, int _rowIndex, int _colIndex, CellValue _c1Value, CellValue _c2Value) { + sheetName = _sheetName; + rowIndex = _rowIndex; + colIndex = _colIndex; + c1Value = _c1Value; + c2Value = _c2Value; + } + } +} diff --git a/src/main/java/com/ka/spreadsheet/diff/SpreadSheetDiffer.java b/src/main/java/com/ka/spreadsheet/diff/SpreadSheetDiffer.java index 66206b3..e729608 100644 --- a/src/main/java/com/ka/spreadsheet/diff/SpreadSheetDiffer.java +++ b/src/main/java/com/ka/spreadsheet/diff/SpreadSheetDiffer.java @@ -1,8 +1,6 @@ package com.ka.spreadsheet.diff; import com.ka.spreadsheet.util.LogUtil; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import static com.ka.spreadsheet.diff.Flags.WORKBOOK1; import static com.ka.spreadsheet.diff.Flags.WORKBOOK2; @@ -10,6 +8,8 @@ import java.io.File; import java.util.Iterator; +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.usermodel.WorkbookFactory; import org.odftoolkit.simple.SpreadsheetDocument; @@ -27,6 +27,8 @@ public static int doDiff(String[] args) { try { if (Flags.parseFlags(args)) { LogUtil.initLogging(Flags.LOG_FILENAME); + LogUtil.initCleanLogging("display" + Flags.LOG_FILENAME); + logger.info("Hello"); SpreadSheetDiffCallback formatter; switch (Flags.DIFF_FORMAT) { case EXCEL_CMP: @@ -35,6 +37,12 @@ public static int doDiff(String[] args) { case UNIFIED: formatter = new UnifiedDiffSpreadSheetDiffCallback(); break; + case LOGGER: + formatter = new LoggerSpreadSheetDiffCallback(); + break; + case LOGGER_UNIFIED: + formatter = new LoggerUnifiedDiffSpreadSheetDiffCallback(); + break; default: throw new IllegalArgumentException("Unknown diff formatter"); } diff --git a/src/main/java/com/ka/spreadsheet/diff/UnifiedDiffSpreadSheetDiffCallback.java b/src/main/java/com/ka/spreadsheet/diff/UnifiedDiffSpreadSheetDiffCallback.java index a8ef7cc..3d283e8 100644 --- a/src/main/java/com/ka/spreadsheet/diff/UnifiedDiffSpreadSheetDiffCallback.java +++ b/src/main/java/com/ka/spreadsheet/diff/UnifiedDiffSpreadSheetDiffCallback.java @@ -1,7 +1,5 @@ package com.ka.spreadsheet.diff; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import java.util.ArrayList; import java.util.List; @@ -27,7 +25,6 @@ // ... // Each cell data line is always present, even if the data is empty (thus just "-" or "+). public class UnifiedDiffSpreadSheetDiffCallback extends SpreadSheetDiffCallbackBase { - private static final Logger logger = LogManager.getLogger(UnifiedDiffSpreadSheetDiffCallback.class); private final String lineSeparator = System.getProperty("line.separator"); private String file1; private String file2; diff --git a/src/main/java/com/ka/spreadsheet/sort/ExcelSorter.java b/src/main/java/com/ka/spreadsheet/sort/ExcelSorter.java index 77e49f7..0a5fb42 100644 --- a/src/main/java/com/ka/spreadsheet/sort/ExcelSorter.java +++ b/src/main/java/com/ka/spreadsheet/sort/ExcelSorter.java @@ -44,6 +44,7 @@ public static void main(String[] args) throws Exception { case 0: LOG_FILENAME = args[i]; LogUtil.initLogging(LOG_FILENAME); + LogUtil.initCleanLogging(LOG_FILENAME); break; case 1: WORKBOOK_FILENAME = args[i]; diff --git a/src/main/java/com/ka/spreadsheet/util/LogUtil.java b/src/main/java/com/ka/spreadsheet/util/LogUtil.java index 2dbffe8..a301725 100644 --- a/src/main/java/com/ka/spreadsheet/util/LogUtil.java +++ b/src/main/java/com/ka/spreadsheet/util/LogUtil.java @@ -1,9 +1,6 @@ package com.ka.spreadsheet.util; -import org.apache.log4j.FileAppender; -import org.apache.log4j.Level; -import org.apache.log4j.Logger; -import org.apache.log4j.PatternLayout; +import org.apache.log4j.*; public class LogUtil { public static final String ROOT_DIRECTORY = "root_directory"; @@ -20,4 +17,24 @@ public static void initLogging(String logFileName){ fa.activateOptions(); Logger.getRootLogger().addAppender(fa); } + + public static void initCleanLogging(String logFileName){ + FileAppender fa = new FileAppender(); + fa.setName("FileDisplayLogger"); + String rd = System.getProperty(ROOT_DIRECTORY); + rd = rd == null ? "." : rd; + fa.setFile(rd + "/logs/" +logFileName); + fa.setLayout(new PatternLayout("%m%n")); + fa.setThreshold(Level.DEBUG); + fa.setAppend(false); + fa.activateOptions(); + Logger.getLogger("DisplayLogger").addAppender(fa); + ConsoleAppender ca = new ConsoleAppender(); + ca.setLayout(new PatternLayout("%m%n")); + ca.setThreshold(Level.DEBUG); + ca.activateOptions(); + Logger.getLogger("DisplayLogger").addAppender(ca); + + + } } \ No newline at end of file From 7217b348e6af5742bce6fa8cadc74a2ac5673f50 Mon Sep 17 00:00:00 2001 From: ghostpencil <46399873+ghostpencil@users.noreply.github.com> Date: Wed, 1 Apr 2026 08:59:48 -0400 Subject: [PATCH 4/5] "Claude PR Assistant workflow" --- .github/workflows/claude.yml | 50 ++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 .github/workflows/claude.yml diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml new file mode 100644 index 0000000..79fe056 --- /dev/null +++ b/.github/workflows/claude.yml @@ -0,0 +1,50 @@ +name: Claude Code + +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + issues: + types: [opened, assigned] + pull_request_review: + types: [submitted] + +jobs: + claude: + if: | + (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || + (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + issues: read + id-token: write + actions: read # Required for Claude to read CI results on PRs + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Run Claude Code + id: claude + uses: anthropics/claude-code-action@v1 + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + + # This is an optional setting that allows Claude to read CI results on PRs + additional_permissions: | + actions: read + + # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it. + # prompt: 'Update the pull request description to include a summary of changes.' + + # Optional: Add claude_args to customize behavior and configuration + # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md + # or https://code.claude.com/docs/en/cli-reference for available options + # claude_args: '--allowed-tools Bash(gh pr:*)' + From 8b82a0b74618f38c5d97cc4dbce4f81e31c9aa9d Mon Sep 17 00:00:00 2001 From: ghostpencil <46399873+ghostpencil@users.noreply.github.com> Date: Wed, 1 Apr 2026 08:59:50 -0400 Subject: [PATCH 5/5] "Claude Code Review workflow" --- .github/workflows/claude-code-review.yml | 44 ++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 .github/workflows/claude-code-review.yml diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml new file mode 100644 index 0000000..4f6145b --- /dev/null +++ b/.github/workflows/claude-code-review.yml @@ -0,0 +1,44 @@ +name: Claude Code Review + +on: + pull_request: + types: [opened, synchronize, ready_for_review, reopened] + # Optional: Only run on specific file changes + # paths: + # - "src/**/*.ts" + # - "src/**/*.tsx" + # - "src/**/*.js" + # - "src/**/*.jsx" + +jobs: + claude-review: + # Optional: Filter by PR author + # if: | + # github.event.pull_request.user.login == 'external-contributor' || + # github.event.pull_request.user.login == 'new-developer' || + # github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' + + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + issues: read + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Run Claude Code Review + id: claude-review + uses: anthropics/claude-code-action@v1 + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + plugin_marketplaces: 'https://github.com/anthropics/claude-code.git' + plugins: 'code-review@claude-code-plugins' + prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}' + # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md + # or https://code.claude.com/docs/en/cli-reference for available options +