diff --git a/README.md b/README.md index c2465bd..6456ae6 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ To install manually, please check the [releases](https://github.com/millij/poi-o #### Dependencies -The current implementation uses **POI version 3.17**. +The current implementation uses **POI version 4.0.1**. ## Usage diff --git a/build.gradle b/build.gradle index b6513a8..efaefcf 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ repositories { } group = 'io.github.millij' -version = '1.0.0' +version = '2.0.0-SNAPSHOT' dependencies { @@ -27,7 +27,8 @@ dependencies { compile group: 'commons-beanutils', name: 'commons-beanutils', version: '1.9.3' // Apache POI - compile group: 'org.apache.poi', name: 'poi-ooxml', version: '3.17' + compile group: 'org.apache.poi', name: 'poi', version: '4.0.1' + compile group: 'org.apache.poi', name: 'poi-ooxml', version: '4.0.1' // Test compile @@ -51,8 +52,8 @@ test { // ---------------------------------------------------------------------------- tasks.withType(JavaCompile) { - sourceCompatibility = 1.7 - targetCompatibility = 1.7 + sourceCompatibility = 1.8 + targetCompatibility = 1.8 } task javadocJar(type: Jar) { diff --git a/src/main/java/io/github/millij/poi/ss/reader/XlsxReader.java b/src/main/java/io/github/millij/poi/ss/reader/XlsxReader.java index 852d5ec..0c66f8a 100644 --- a/src/main/java/io/github/millij/poi/ss/reader/XlsxReader.java +++ b/src/main/java/io/github/millij/poi/ss/reader/XlsxReader.java @@ -1,15 +1,12 @@ package io.github.millij.poi.ss.reader; import static io.github.millij.poi.util.Beans.isInstantiableType; -import io.github.millij.poi.SpreadsheetReadException; -import io.github.millij.poi.ss.handler.RowContentsHandler; -import io.github.millij.poi.ss.handler.RowListener; import java.io.InputStream; +import org.apache.poi.ooxml.util.SAXHelper; import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.ss.usermodel.Workbook; -import org.apache.poi.util.SAXHelper; import org.apache.poi.xssf.eventusermodel.ReadOnlySharedStringsTable; import org.apache.poi.xssf.eventusermodel.XSSFReader; import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler; @@ -21,6 +18,10 @@ import org.xml.sax.InputSource; import org.xml.sax.XMLReader; +import io.github.millij.poi.SpreadsheetReadException; +import io.github.millij.poi.ss.handler.RowContentsHandler; +import io.github.millij.poi.ss.handler.RowListener; + /** * Reader impletementation of {@link Workbook} for an OOXML .xlsx file. This implementation is diff --git a/src/main/java/io/github/millij/poi/ss/writer/SpreadsheetWriter.java b/src/main/java/io/github/millij/poi/ss/writer/SpreadsheetWriter.java index ddbdfa4..267227d 100644 --- a/src/main/java/io/github/millij/poi/ss/writer/SpreadsheetWriter.java +++ b/src/main/java/io/github/millij/poi/ss/writer/SpreadsheetWriter.java @@ -1,17 +1,19 @@ package io.github.millij.poi.ss.writer; -import io.github.millij.poi.ss.model.annotations.Sheet; -import io.github.millij.poi.util.Spreadsheet; - import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.stream.Collectors; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; @@ -23,6 +25,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import io.github.millij.poi.ss.model.annotations.Sheet; +import io.github.millij.poi.util.Spreadsheet; @Deprecated public class SpreadsheetWriter { @@ -32,145 +36,153 @@ public class SpreadsheetWriter { private final XSSFWorkbook workbook; private final OutputStream outputStrem; - + // Constructors // ------------------------------------------------------------------------ public SpreadsheetWriter(String filepath) throws FileNotFoundException { - this(new File(filepath)); + this(new File(filepath)); } public SpreadsheetWriter(File file) throws FileNotFoundException { - this(new FileOutputStream(file)); + this(new FileOutputStream(file)); } public SpreadsheetWriter(OutputStream outputStream) { - super(); + super(); - this.workbook = new XSSFWorkbook(); - this.outputStrem = outputStream; + this.workbook = new XSSFWorkbook(); + this.outputStrem = outputStream; } - + // Methods // ------------------------------------------------------------------------ - // Sheet :: Add - + public void addSheet(Class beanType, List rowObjects) { - // Sheet Headers - List headers = Spreadsheet.getColumnNames(beanType); + // Sheet Headers + Map headerMap = Spreadsheet.getPropertyToColumnNameMap(beanType); + + this.addSheet(beanType, rowObjects, headerMap); + } - this.addSheet(beanType, rowObjects, headers); + public void addSheet(Class beanType, List rowObjects, Map headerMap) { + // SheetName + Sheet sheet = beanType.getAnnotation(Sheet.class); + String sheetName = sheet != null ? sheet.value() : null; + + this.addSheet(beanType, rowObjects, headerMap, sheetName); } - public void addSheet(Class beanType, List rowObjects, List headers) { - // SheetName - Sheet sheet = beanType.getAnnotation(Sheet.class); - String sheetName = sheet != null ? sheet.value() : null; + public void addSheet(Class beanType, List rowObjects, Set headers) { + // SheetName + Sheet sheet = beanType.getAnnotation(Sheet.class); + String sheetName = sheet != null ? sheet.value() : null; + + // Sheet Headers + Map headerMap = Spreadsheet.getPropertyToColumnNameMap(beanType); + Map headerMapCopy = headerMap.entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + headerMapCopy.values().retainAll(headers); - this.addSheet(beanType, rowObjects, headers, sheetName); + this.addSheet(beanType, rowObjects, Collections.unmodifiableMap(headerMapCopy), sheetName); } public void addSheet(Class beanType, List rowObjects, String sheetName) { // Sheet Headers - List headers = Spreadsheet.getColumnNames(beanType); + Map headerMap = Spreadsheet.getPropertyToColumnNameMap(beanType); - this.addSheet(beanType, rowObjects, headers, sheetName); + this.addSheet(beanType, rowObjects, headerMap, sheetName); } - public void addSheet(Class beanType, List rowObjects, List headers, - String sheetName) { - // Sanity checks - if (beanType == null) { - throw new IllegalArgumentException("GenericExcelWriter :: ExcelBean type should not be null"); - } - - if (CollectionUtils.isEmpty(rowObjects)) { - LOGGER.error("Skipping excel sheet writing as the ExcelBean collection is empty"); - return; - } - - if (CollectionUtils.isEmpty(headers)) { - LOGGER.error("Skipping excel sheet writing as the headers collection is empty"); - return; - } - - try { - XSSFSheet exSheet = workbook.getSheet(sheetName); - if (exSheet != null) { - String errMsg = String.format("A Sheet with the passed name already exists : %s", sheetName); - throw new IllegalArgumentException(errMsg); - } - - XSSFSheet sheet = StringUtils.isEmpty(sheetName) ? workbook.createSheet() : workbook.createSheet(sheetName); - LOGGER.debug("Added new Sheet[name] to the workbook : {}", sheet.getSheetName()); - - // Header - XSSFRow headerRow = sheet.createRow(0); - for (int i = 0; i < headers.size(); i++) { - XSSFCell cell = headerRow.createCell(i); - cell.setCellValue(headers.get(i)); - } - - // Data Rows - Map> rowsData = this.prepareSheetRowsData(headers, rowObjects); - for (int i = 0, rowNum = 1; i < rowObjects.size(); i++, rowNum++) { - final XSSFRow row = sheet.createRow(rowNum); - - int cellNo = 0; - for (String key : rowsData.keySet()) { - Cell cell = row.createCell(cellNo); - String value = rowsData.get(key).get(i); - cell.setCellValue(value); - cellNo++; - } - } - - } catch (Exception ex) { - String errMsg = String.format("Error while preparing sheet with passed row objects : %s", ex.getMessage()); - LOGGER.error(errMsg, ex); - } + public void addSheet(Class beanType, List rowObjects, Map headerMap, + String sheetName) { + // Sanity checks + if (beanType == null) { + throw new IllegalArgumentException("GenericExcelWriter :: ExcelBean type should not be null"); + } + + if (CollectionUtils.isEmpty(rowObjects)) { + LOGGER.error("Skipping excel sheet writing as the ExcelBean collection is empty"); + return; + } + + if (headerMap == null | headerMap.isEmpty()) { + LOGGER.error("Skipping excel sheet writing as the headers collection is empty"); + return; + } + + try { + XSSFSheet exSheet = workbook.getSheet(sheetName); + if (exSheet != null) { + String errMsg = String.format("A Sheet with the passed name already exists : %s", sheetName); + throw new IllegalArgumentException(errMsg); + } + + XSSFSheet sheet = StringUtils.isEmpty(sheetName) ? workbook.createSheet() : workbook.createSheet(sheetName); + LOGGER.debug("Added new Sheet[name] to the workbook : {}", sheet.getSheetName()); + + // Header + XSSFRow headerRow = sheet.createRow(0); + Iterator> iterator = headerMap.entrySet().iterator(); + for (int i = 0; iterator.hasNext(); i++) { + XSSFCell cell = headerRow.createCell(i); + cell.setCellValue(headerMap.get(iterator.next().getKey())); + } + + // Data Rows + Map> rowsData = this.prepareSheetRowsData(headerMap, rowObjects); + for (int i = 0, rowNum = 1; i < rowObjects.size(); i++, rowNum++) { + final XSSFRow row = sheet.createRow(rowNum); + + int cellNo = 0; + for (String key : rowsData.keySet()) { + Cell cell = row.createCell(cellNo); + String value = rowsData.get(key).get(i); + cell.setCellValue(value); + cellNo++; + } + } + + } catch (Exception ex) { + String errMsg = String.format("Error while preparing sheet with passed row objects : %s", ex.getMessage()); + LOGGER.error(errMsg, ex); + } } - // Sheet :: Append to existing - - // Write public void write() throws IOException { - workbook.write(outputStrem); - workbook.close(); + workbook.write(outputStrem); + workbook.close(); } - // Private Methods // ------------------------------------------------------------------------ - private Map> prepareSheetRowsData(List headers, - List rowObjects) throws Exception { + private Map> prepareSheetRowsData(Map headerMap, List rowObjects) + throws Exception { - final Map> sheetData = new LinkedHashMap>(); + final Map> sheetData = new LinkedHashMap>(); - // Iterate over Objects - for (EB excelBean : rowObjects) { - Map row = Spreadsheet.asRowDataMap(excelBean, headers); + // Iterate over Objects + for (EB excelBean : rowObjects) { + Map row = Spreadsheet.asRowDataMap(excelBean, headerMap); - for (String header : headers) { - List data = sheetData.containsKey(header) ? sheetData.get(header) : new ArrayList(); - String value = row.get(header) != null ? row.get(header) : ""; - data.add(value); + for (String header : headerMap.values()) { + List data = sheetData.containsKey(header) ? sheetData.get(header) : new ArrayList(); + String value = row.get(header) != null ? row.get(header) : ""; + data.add(value); - sheetData.put(header, data); - } - } + sheetData.put(header, data); + } + } - return sheetData; + return sheetData; } - - } diff --git a/src/main/java/io/github/millij/poi/util/Spreadsheet.java b/src/main/java/io/github/millij/poi/util/Spreadsheet.java index 5412bf5..20245a9 100644 --- a/src/main/java/io/github/millij/poi/util/Spreadsheet.java +++ b/src/main/java/io/github/millij/poi/util/Spreadsheet.java @@ -6,10 +6,12 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import org.apache.commons.beanutils.BeanUtils; import org.apache.commons.lang3.StringUtils; @@ -68,9 +70,14 @@ public static Map getPropertyToColumnNameMap(Class beanType) } // Methods + List fieldList = Arrays.stream(fields).map(Field::getName).collect(Collectors.toList()); Method[] methods = beanType.getDeclaredMethods(); + for (Method m : methods) { String fieldName = Beans.getFieldName(m); + if (!fieldList.contains(fieldName)) { + continue; + } if (!mapping.containsKey(fieldName)) { mapping.put(fieldName, fieldName); } @@ -113,7 +120,7 @@ public static List getColumnNames(Class beanType) { // Read from Bean : as Row Data // ------------------------------------------------------------------------ - public static Map asRowDataMap(Object beanObj, List colHeaders) throws Exception { + public static Map asRowDataMap(Object beanObj, Map colHeaders) throws Exception { // Excel Bean Type final Class beanType = beanObj.getClass(); @@ -122,40 +129,19 @@ public static Map asRowDataMap(Object beanObj, List colH // Fields for (Field f : beanType.getDeclaredFields()) { - if (!f.isAnnotationPresent(SheetColumn.class)) { - continue; - } - - String fieldName = f.getName(); - - SheetColumn ec = f.getAnnotation(SheetColumn.class); - String header = StringUtils.isEmpty(ec.value()) ? fieldName : ec.value(); - if (!colHeaders.contains(header)) { - continue; - } - - rowDataMap.put(header, Beans.getFieldValueAsString(beanObj, fieldName)); - } - - // Methods - for (Method m : beanType.getDeclaredMethods()) { - if (!m.isAnnotationPresent(SheetColumn.class)) { - continue; - } - String fieldName = Beans.getFieldName(m); + String fieldName = f.getName(); - SheetColumn ec = m.getAnnotation(SheetColumn.class); - String header = StringUtils.isEmpty(ec.value()) ? fieldName : ec.value(); - if (!colHeaders.contains(header)) { - continue; - } + if (!colHeaders.containsKey(fieldName)) { + continue; + } - rowDataMap.put(header, Beans.getFieldValueAsString(beanObj, fieldName)); + rowDataMap.put(colHeaders.get(fieldName), Beans.getFieldValueAsString(beanObj, fieldName)); } return rowDataMap; } + diff --git a/src/test/java/io/github/millij/bean/Department.java b/src/test/java/io/github/millij/bean/Department.java new file mode 100644 index 0000000..53b833a --- /dev/null +++ b/src/test/java/io/github/millij/bean/Department.java @@ -0,0 +1,53 @@ +package io.github.millij.bean; + +import io.github.millij.poi.ss.model.annotations.SheetColumn; + +public class Department { + + @SheetColumn("Company Name") + private String name; + + @SheetColumn("# of Employees") + private Integer noOfEmployees; + + // Constructors + // ------------------------------------------------------------------------ + + public Department() { + // Default + } + + public Department(String name, Integer noOfEmployees) { + super(); + + this.name = name; + this.noOfEmployees = noOfEmployees; + } + + // Getters and Setters + // ------------------------------------------------------------------------ + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getNoOfEmployees() { + return noOfEmployees; + } + + public void setNoOfEmployees(Integer noOfEmployees) { + this.noOfEmployees = noOfEmployees; + } + + // Object Methods + // ------------------------------------------------------------------------ + + @Override + public String toString() { + return "Department [name=" + name + ", noOfEmployees=" + noOfEmployees + "]"; + } +} diff --git a/src/test/java/io/github/millij/poi/ss/writer/SpreadsheetWriterTest.java b/src/test/java/io/github/millij/poi/ss/writer/SpreadsheetWriterTest.java index dd3b1c6..46d4532 100644 --- a/src/test/java/io/github/millij/poi/ss/writer/SpreadsheetWriterTest.java +++ b/src/test/java/io/github/millij/poi/ss/writer/SpreadsheetWriterTest.java @@ -1,14 +1,11 @@ package io.github.millij.poi.ss.writer; -import io.github.millij.bean.Company; -import io.github.millij.bean.Employee; -import io.github.millij.poi.ss.writer.SpreadsheetWriter; - import java.io.File; import java.io.IOException; import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; import org.junit.After; @@ -17,6 +14,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import io.github.millij.bean.Company; +import io.github.millij.bean.Department; +import io.github.millij.bean.Employee; + public class SpreadsheetWriterTest { @@ -81,7 +82,7 @@ public void test_write_xlsx_single_sheet_custom_headers() throws IOException { List headers = Arrays.asList("ID", "Age", "Name", "Address"); // Add Sheets - gew.addSheet(Employee.class, employees, headers); + gew.addSheet(Employee.class, employees, new HashSet(headers)); // Write gew.write(); @@ -115,5 +116,82 @@ public void test_write_xlsx_multiple_sheets() throws IOException { // Write gew.write(); } + + + @Test + public void test_write_xlsx_single_sheet_custom_sheet_name() throws IOException { + final String filepath_output_file = _path_test_output.concat("custom_sheet.xlsx"); + SpreadsheetWriter gew = new SpreadsheetWriter(filepath_output_file); + + List departments = new ArrayList(); + departments.add(new Department("Google", 12000)); + departments.add(new Department("Facebook", null)); + departments.add(new Department("SpaceX", null)); + + String sheetName = "Department sheet"; + gew.addSheet(Department.class, departments, sheetName); + gew.write(); + } + + + @Test + public void test_write_xlsx_multiple_sheets_repeated_sheet_name() throws IOException { + final String filepath_output_file = _path_test_output.concat("multiple_sheets_repeated_sheet_name.xlsx"); + + // Excel Writer + LOGGER.info("test_write_xlsx_single_sheet :: Writing to file - {}", filepath_output_file); + SpreadsheetWriter gew = new SpreadsheetWriter(filepath_output_file); + + // Employees + List employees = new ArrayList(); + employees.add(new Employee("1", "foo", 12, "MALE", 1.68)); + employees.add(new Employee("2", "bar", null, "MALE", 1.68)); + employees.add(new Employee("3", "foo bar", null, null, null)); + + // Campanies + List companies = new ArrayList(); + companies.add(new Company("Google", 12000, "Palo Alto, CA")); + companies.add(new Company("Facebook", null, "Mountain View, CA")); + companies.add(new Company("SpaceX", null, null)); + + // Add Sheets + gew.addSheet(Employee.class, employees, "sheet0"); + gew.addSheet(Company.class, companies, "sheet0"); + // Write + gew.write(); + } + + + @Test + public void test_write_xlsx_single_sheet_with_sheet_annotation_absence() throws IOException { + final String filepath_output_file = _path_test_output.concat("absent_sheet_annotation_sheet.xlsx"); + SpreadsheetWriter gew = new SpreadsheetWriter(filepath_output_file); + + List departments = new ArrayList(); + departments.add(new Department("Google", 12000)); + departments.add(new Department("Facebook", null)); + departments.add(new Department("SpaceX", null)); + + gew.addSheet(Department.class, departments); + gew.write(); + } + + + @Test + public void test_write_xlsx_single_sheet_with_empty_bean_collection() throws IOException { + final String filepath_output_file = _path_test_output.concat("no_sheet.xlsx"); + SpreadsheetWriter gew = new SpreadsheetWriter(filepath_output_file); + + gew.addSheet(Department.class, null); + } + + + @Test(expected = IllegalArgumentException.class) + public void test_write_xlsx_single_sheet_with_empty_bean_type_is_null() throws IOException { + final String filepath_output_file = _path_test_output.concat("no_sheet.xlsx"); + SpreadsheetWriter gew = new SpreadsheetWriter(filepath_output_file); + + gew.addSheet(null, null); + } } diff --git a/src/test/resources/log4j.properties b/src/test/resources/log4j.properties index 36af21f..90e6e62 100644 --- a/src/test/resources/log4j.properties +++ b/src/test/resources/log4j.properties @@ -1,5 +1,5 @@ # Root logger option -log4j.rootLogger=INFO, stdout +log4j.rootLogger=DEBUG, stdout # Direct log messages to stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender