From 46d32f7626371aacb19b463fe3ae4db9d27bbf7b Mon Sep 17 00:00:00 2001 From: Pascal Date: Wed, 4 Dec 2019 12:41:27 +0100 Subject: [PATCH 1/2] Fixed @ExcelRow returning offset index on missing line Fixes #111 --- .../com/poiji/bind/mapping/PoijiHandler.java | 4 +- .../deserialize/MissingRowIndexTest.java | 53 ++++++++++++++ .../model/byname/Organisation.java | 67 ++++++++++++++++++ src/test/resources/missing-row-1.xlsx | Bin 0 -> 10361 bytes src/test/resources/missing-row-2.xlsx | Bin 0 -> 8987 bytes 5 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 src/test/java/com/poiji/deserialize/MissingRowIndexTest.java create mode 100644 src/test/java/com/poiji/deserialize/model/byname/Organisation.java create mode 100644 src/test/resources/missing-row-1.xlsx create mode 100644 src/test/resources/missing-row-2.xlsx diff --git a/src/main/java/com/poiji/bind/mapping/PoijiHandler.java b/src/main/java/com/poiji/bind/mapping/PoijiHandler.java index acec623..924389c 100644 --- a/src/main/java/com/poiji/bind/mapping/PoijiHandler.java +++ b/src/main/java/com/poiji/bind/mapping/PoijiHandler.java @@ -83,7 +83,7 @@ private boolean setValue(String content, Class type, int column) { for (Field field : type.getDeclaredFields()) { ExcelRow excelRow = field.getAnnotation(ExcelRow.class); if (excelRow != null) { - Object o = casting.castValue(field.getType(), valueOf(internalCount), options); + Object o = casting.castValue(field.getType(), valueOf(internalRow), options); setFieldData(field, o, instance); columnToField.put(-1, field); } @@ -110,7 +110,7 @@ private boolean setValue(String content, Class type, int column) { // For ExcelRow annotation if(columnToField.containsKey(-1)) { Field field = columnToField.get(-1); - Object o = casting.castValue(field.getType(), valueOf(internalCount), options); + Object o = casting.castValue(field.getType(), valueOf(internalRow), options); setFieldData(field, o, instance); } if(columnToField.containsKey(column)) { diff --git a/src/test/java/com/poiji/deserialize/MissingRowIndexTest.java b/src/test/java/com/poiji/deserialize/MissingRowIndexTest.java new file mode 100644 index 0000000..e609a15 --- /dev/null +++ b/src/test/java/com/poiji/deserialize/MissingRowIndexTest.java @@ -0,0 +1,53 @@ +package com.poiji.deserialize; + +import com.poiji.bind.Poiji; +import com.poiji.deserialize.model.byid.Person; +import com.poiji.deserialize.model.byname.Organisation; +import com.poiji.option.PoijiOptions; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.io.File; +import java.util.List; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; +import static org.junit.runners.Parameterized.Parameters; + +public class MissingRowIndexTest { + + @Test + public void emptyLine() { + + List organisations = Poiji.fromExcel( + new File("src/test/resources/missing-row-1.xlsx"), + Organisation.class, + PoijiOptions.PoijiOptionsBuilder.settings() + .sheetName("Organisation") + .build() + ); + assertThat(organisations, notNullValue()); + assertThat(organisations.size(), is(2)); + assertThat(organisations.stream().map(Organisation::getRowIndex).min(Integer::compareTo).get(), is(2)); + assertThat(organisations.stream().map(Organisation::getRowIndex).max(Integer::compareTo).get(), is(3)); + } + + @Test + public void nullLine() { + + List organisations = Poiji.fromExcel( + new File("src/test/resources/missing-row-2.xlsx"), + Organisation.class, + PoijiOptions.PoijiOptionsBuilder.settings() + .sheetName("Organisation") + .build() + ); + + assertThat(organisations, notNullValue()); + assertThat(organisations.size(), is(4)); + assertThat(organisations.stream().map(Organisation::getRowIndex).min(Integer::compareTo).get(), is(2)); + assertThat(organisations.stream().map(Organisation::getRowIndex).max(Integer::compareTo).get(), is(5)); + } +} diff --git a/src/test/java/com/poiji/deserialize/model/byname/Organisation.java b/src/test/java/com/poiji/deserialize/model/byname/Organisation.java new file mode 100644 index 0000000..1ab7a52 --- /dev/null +++ b/src/test/java/com/poiji/deserialize/model/byname/Organisation.java @@ -0,0 +1,67 @@ +package com.poiji.deserialize.model.byname; + +import com.poiji.annotation.ExcelCellName; +import com.poiji.annotation.ExcelRow; + +public class Organisation { + + public static final String HEADER_ORGANISATION_ID = "Organisation ID"; + public static final String HEADER_CUSTOMER_EXTERNAL_ID = "Customer External ID"; + public static final String HEADER_ORGANISATION_EXTERNAL_ID = "Organisation External ID"; + public static final String HEADER_ORGANISATION_NAME = "Organisation Name"; + + @ExcelRow + private int rowIndex; + + @ExcelCellName(HEADER_ORGANISATION_ID) + private String id; + + @ExcelCellName(HEADER_ORGANISATION_EXTERNAL_ID) + private String externalId; + + @ExcelCellName(HEADER_ORGANISATION_NAME) + private String name; + + @ExcelCellName(HEADER_CUSTOMER_EXTERNAL_ID) + private String customerExternalId; + + public int getRowIndex() { + return rowIndex; + } + + public void setRowIndex(int rowIndex) { + this.rowIndex = rowIndex; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getExternalId() { + return externalId; + } + + public void setExternalId(String externalId) { + this.externalId = externalId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getCustomerExternalId() { + return customerExternalId; + } + + public void setCustomerExternalId(String customerExternalId) { + this.customerExternalId = customerExternalId; + } +} diff --git a/src/test/resources/missing-row-1.xlsx b/src/test/resources/missing-row-1.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..5f4b8c22beb635f8e473b26ac30a35fa1f59d11f GIT binary patch literal 10361 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 F{{ehq_7VU9 literal 0 HcmV?d00001 diff --git a/src/test/resources/missing-row-2.xlsx b/src/test/resources/missing-row-2.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..5697ebf49ffdafd5ed28484925163133671f23e4 GIT binary patch literal 8987 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 literal 0 HcmV?d00001 From ae7c21af005e98bf6264d860fe102e6bd025ffd9 Mon Sep 17 00:00:00 2001 From: Pascal Breuer Date: Fri, 6 Dec 2019 16:44:29 +0100 Subject: [PATCH 2/2] Added @ExcelUnknownCells to map non-annotated cells to map --- .gitignore | 190 ++++++++++++++++++ .../poiji/annotation/ExcelUnknownCells.java | 18 ++ .../poiji/bind/mapping/HSSFUnmarshaller.java | 103 +++++++--- .../com/poiji/bind/mapping/PoijiHandler.java | 98 ++++++--- .../deserialize/MissingRowIndexTest.java | 22 +- .../poiji/deserialize/UnknownCellsTest.java | 93 +++++++++ .../model/byid/OrgWithUnknownCells.java | 72 +++++++ .../byname/OrgWithUnknownCellsByName.java | 77 +++++++ ...anisation.java => OrganisationByName.java} | 2 +- src/test/resources/unknown-cells.xls | Bin 0 -> 26112 bytes src/test/resources/unknown-cells.xlsx | Bin 0 -> 10395 bytes 11 files changed, 604 insertions(+), 71 deletions(-) create mode 100644 src/main/java/com/poiji/annotation/ExcelUnknownCells.java create mode 100644 src/test/java/com/poiji/deserialize/UnknownCellsTest.java create mode 100644 src/test/java/com/poiji/deserialize/model/byid/OrgWithUnknownCells.java create mode 100644 src/test/java/com/poiji/deserialize/model/byname/OrgWithUnknownCellsByName.java rename src/test/java/com/poiji/deserialize/model/byname/{Organisation.java => OrganisationByName.java} (97%) create mode 100644 src/test/resources/unknown-cells.xls create mode 100644 src/test/resources/unknown-cells.xlsx diff --git a/.gitignore b/.gitignore index 68f0d82..c66539f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,193 @@ + +# Created by https://www.gitignore.io/api/linux,macos,windows,intellij,libreoffice,microsoftoffice +# Edit at https://www.gitignore.io/?templates=linux,macos,windows,intellij,libreoffice,microsoftoffice + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Intellij Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +.idea/**/sonarlint/ + +# SonarQube Plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator/ + +### LibreOffice ### +# LibreOffice locks +.~lock.*# + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### MicrosoftOffice ### +*.tmp + +# Word temporary +~$*.doc* + +# Word Auto Backup File +Backup of *.doc* + +# Excel temporary +~$*.xls* + +# Excel Backup File +*.xlk + +# PowerPoint temporary +~$*.ppt* + +# Visio autosave temporary files +*.~vsd* + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.gitignore.io/api/linux,macos,windows,intellij,libreoffice,microsoftoffice + +# User added + .idea p2o.iml target/ diff --git a/src/main/java/com/poiji/annotation/ExcelUnknownCells.java b/src/main/java/com/poiji/annotation/ExcelUnknownCells.java new file mode 100644 index 0000000..fabd30b --- /dev/null +++ b/src/main/java/com/poiji/annotation/ExcelUnknownCells.java @@ -0,0 +1,18 @@ +package com.poiji.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotations allows you to put every unknown cell (neither mapped by name, nor by index) into a {@code Map} + * + * @author Pascal Breuer + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +@Documented +public @interface ExcelUnknownCells { +} diff --git a/src/main/java/com/poiji/bind/mapping/HSSFUnmarshaller.java b/src/main/java/com/poiji/bind/mapping/HSSFUnmarshaller.java index e9ac2e3..9b46019 100644 --- a/src/main/java/com/poiji/bind/mapping/HSSFUnmarshaller.java +++ b/src/main/java/com/poiji/bind/mapping/HSSFUnmarshaller.java @@ -4,6 +4,7 @@ import com.poiji.annotation.ExcelCellName; import com.poiji.annotation.ExcelCellRange; import com.poiji.annotation.ExcelRow; +import com.poiji.annotation.ExcelUnknownCells; import com.poiji.bind.Unmarshaller; import com.poiji.config.Casting; import com.poiji.exception.IllegalCastException; @@ -17,10 +18,16 @@ import org.apache.poi.ss.usermodel.Workbook; import java.lang.reflect.Field; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Spliterator; +import java.util.Spliterators; import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import static java.lang.String.valueOf; @@ -33,7 +40,8 @@ abstract class HSSFUnmarshaller implements Unmarshaller { private final DataFormatter dataFormatter; protected final PoijiOptions options; private final Casting casting; - private Map titles; + private Map columnIndexPerTitle; + private Map titlePerColumnIndex; private int limit; private int internalCount; @@ -41,7 +49,8 @@ abstract class HSSFUnmarshaller implements Unmarshaller { this.options = options; this.limit = options.getLimit(); dataFormatter = new DataFormatter(); - titles = new HashMap<>(); + columnIndexPerTitle = new HashMap<>(); + titlePerColumnIndex = new HashMap<>(); casting = options.getCasting(); } @@ -61,7 +70,7 @@ public void unmarshal(Class type, Consumer consumer) { if (!skip(currentRow, skip) && !isRowEmpty(currentRow)) { internalCount += 1; - if(limit != 0 && internalCount > limit) + if (limit != 0 && internalCount > limit) return; T t = deserialize0(currentRow, type); @@ -75,81 +84,117 @@ private Sheet getSheetToProcess(Workbook workbook, PoijiOptions options, String int requestedIndex = options.sheetIndex(); Sheet sheet = null; if (options.ignoreHiddenSheets()) { - for (int i = 0; i < workbook.getNumberOfSheets(); i++) { - if (!workbook.isSheetHidden(i) && !workbook.isSheetVeryHidden(i)) { - if (sheetName == null) { - if (nonHiddenSheetIndex == requestedIndex) { - return workbook.getSheetAt(i); + for (int i = 0; i < workbook.getNumberOfSheets(); i++) { + if (!workbook.isSheetHidden(i) && !workbook.isSheetVeryHidden(i)) { + if (sheetName == null) { + if (nonHiddenSheetIndex == requestedIndex) { + return workbook.getSheetAt(i); + } + } else { + if (workbook.getSheetName(i).equalsIgnoreCase(sheetName)) { + return workbook.getSheetAt(i); + } + } + nonHiddenSheetIndex++; } - } else { - if (workbook.getSheetName(i).equalsIgnoreCase(sheetName)) { - return workbook.getSheetAt(i); - } - } - nonHiddenSheetIndex++; } - } } else { - if (sheetName == null) { - sheet = workbook.getSheetAt(requestedIndex); - } else { - sheet = workbook.getSheet(sheetName); - } + if (sheetName == null) { + sheet = workbook.getSheetAt(requestedIndex); + } else { + sheet = workbook.getSheet(sheetName); + } } return sheet; - } + } private void loadColumnTitles(Sheet sheet, int maxPhysicalNumberOfRows) { if (maxPhysicalNumberOfRows > 0) { int row = options.getHeaderStart(); Row firstRow = sheet.getRow(row); for (Cell cell : firstRow) { - titles.put(cell.getStringCellValue(), cell.getColumnIndex()); + columnIndexPerTitle.put(cell.getStringCellValue(), cell.getColumnIndex()); + + titlePerColumnIndex.put(cell.getColumnIndex(), + getTitleNameForMap(cell.getStringCellValue(), cell.getColumnIndex())); } } } + private String getTitleNameForMap(String cellContent, int columnIndex) { + String titleName; + if (titlePerColumnIndex.containsValue(cellContent) + || cellContent.isEmpty()) { + titleName = cellContent + "@" + columnIndex; + } else { + titleName = cellContent; + } + return titleName; + } + private T deserialize0(Row currentRow, Class type) { T instance = ReflectUtil.newInstanceOf(type); return setFieldValue(currentRow, type, instance); } private T tailSetFieldValue(Row currentRow, Class type, T instance) { + List mappedColumnIndices = new ArrayList<>(); + List unknownCells = new ArrayList<>(); + for (Field field : type.getDeclaredFields()) { ExcelRow excelRow = field.getAnnotation(ExcelRow.class); + ExcelCellRange excelCellRange = field.getAnnotation(ExcelCellRange.class); + ExcelUnknownCells excelUnknownCells = field.getAnnotation(ExcelUnknownCells.class); if (excelRow != null) { Object o; o = casting.castValue(field.getType(), valueOf(currentRow.getRowNum()), currentRow.getRowNum(), -1, options); setFieldData(instance, field, o); - } - ExcelCellRange excelCellRange = field.getAnnotation(ExcelCellRange.class); - if (excelCellRange != null) { + } else if (excelCellRange != null) { + Class o = field.getType(); Object ins = ReflectUtil.newInstanceOf(o); for (Field f : o.getDeclaredFields()) { - tailSetFieldValue(currentRow, ins, f); + mappedColumnIndices.add(tailSetFieldValue(currentRow, ins, f)); } setFieldData(instance, field, ins); + } else if (excelUnknownCells != null) { + unknownCells.add(field); } else { - tailSetFieldValue(currentRow, instance, field); + mappedColumnIndices.add(tailSetFieldValue(currentRow, instance, field)); } } + + Map excelUnknownCellsMap = StreamSupport + .stream(Spliterators.spliteratorUnknownSize(currentRow.cellIterator(), Spliterator.ORDERED), false) + .filter(cell -> !mappedColumnIndices.contains(cell.getColumnIndex())) + .filter(cell -> !cell.toString().isEmpty()) + .collect(Collectors.toMap( + cell -> titlePerColumnIndex.get(cell.getColumnIndex()), + Object::toString + )); + + unknownCells.forEach(field -> setFieldData(instance, field, excelUnknownCellsMap)); + return instance; } - private void tailSetFieldValue(Row currentRow, T instance, Field field) { + private Integer tailSetFieldValue(Row currentRow, T instance, Field field) { ExcelCell index = field.getAnnotation(ExcelCell.class); if (index != null) { constructTypeValue(currentRow, instance, field, index.value()); + return index.value(); } else { ExcelCellName excelCellName = field.getAnnotation(ExcelCellName.class); if (excelCellName != null) { - Integer titleColumn = titles.get(excelCellName.value()); + Integer titleColumn = columnIndexPerTitle.get(excelCellName.value()); if (titleColumn != null) { constructTypeValue(currentRow, instance, field, titleColumn); + return titleColumn; } } } + + return null; } private void constructTypeValue(Row currentRow, T instance, Field field, int column) { diff --git a/src/main/java/com/poiji/bind/mapping/PoijiHandler.java b/src/main/java/com/poiji/bind/mapping/PoijiHandler.java index 924389c..14dfc60 100644 --- a/src/main/java/com/poiji/bind/mapping/PoijiHandler.java +++ b/src/main/java/com/poiji/bind/mapping/PoijiHandler.java @@ -4,6 +4,7 @@ import com.poiji.annotation.ExcelCellName; import com.poiji.annotation.ExcelCellRange; import com.poiji.annotation.ExcelRow; +import com.poiji.annotation.ExcelUnknownCells; import com.poiji.config.Casting; import com.poiji.exception.IllegalCastException; import com.poiji.option.PoijiOptions; @@ -16,6 +17,7 @@ import java.util.HashMap; import java.util.Map; import java.util.function.Consumer; +import java.util.stream.Stream; import static java.lang.String.valueOf; @@ -37,7 +39,8 @@ final class PoijiHandler implements SheetContentsHandler { private PoijiOptions options; private final Casting casting; - private Map titles; + private Map columnIndexPerTitle; + private Map titlePerColumnIndex; // New maps used to speed up computing and handle inner objects private Map fieldInstances; private Map columnToField; @@ -50,7 +53,8 @@ final class PoijiHandler implements SheetContentsHandler { this.limit = options.getLimit(); casting = options.getCasting(); - titles = new HashMap<>(); + columnIndexPerTitle = new HashMap<>(); + titlePerColumnIndex = new HashMap<>(); columnToField = new HashMap<>(); columnToSuperClassField = new HashMap<>(); } @@ -80,32 +84,56 @@ private Object getInstance(Field field) { private boolean setValue(String content, Class type, int column) { - for (Field field : type.getDeclaredFields()) { - ExcelRow excelRow = field.getAnnotation(ExcelRow.class); - if (excelRow != null) { - Object o = casting.castValue(field.getType(), valueOf(internalRow), options); - setFieldData(field, o, instance); - columnToField.put(-1, field); - } - ExcelCellRange range = field.getAnnotation(ExcelCellRange.class); - if (range != null) { - Object ins = null; - ins = getInstance(field); - for (Field f : field.getType().getDeclaredFields()) { - if (setValue(f, column, content, ins)) { - setFieldData(field, ins, instance); - columnToField.put(column, f); - columnToSuperClassField.put(column, field); -// return true; + + Stream.of(type.getDeclaredFields()) + .filter(field -> field.getAnnotation(ExcelUnknownCells.class) == null) + .forEach(field -> { + ExcelRow excelRow = field.getAnnotation(ExcelRow.class); + if (excelRow != null) { + Object o = casting.castValue(field.getType(), valueOf(internalRow), options); + setFieldData(field, o, instance); + columnToField.put(-1, field); } - } - } else { - if(setValue(field, column, content, instance)) { - columnToField.put(column, field); + ExcelCellRange range = field.getAnnotation(ExcelCellRange.class); + if (range != null) { + Object ins = null; + ins = getInstance(field); + for (Field f : field.getType().getDeclaredFields()) { + if (setValue(f, column, content, ins)) { + setFieldData(field, ins, instance); + columnToField.put(column, f); + columnToSuperClassField.put(column, field); +// return true; + } + } + } else { + if(setValue(field, column, content, instance)) { + columnToField.put(column, field); // return true; - } - } - } + } + } + }); + + Stream.of(type.getDeclaredFields()) + .filter(field -> field.getAnnotation(ExcelUnknownCells.class) != null) + .forEach(field -> { + if (!columnToField.containsKey(column)) { + try { + Map excelUnknownCellsMap; + field.setAccessible(true); + if (field.get(instance) == null) { + excelUnknownCellsMap = new HashMap<>(); + setFieldData(field, excelUnknownCellsMap, instance); + } else { + excelUnknownCellsMap = (Map) field.get(instance); + } + + excelUnknownCellsMap.put(titlePerColumnIndex.get(column), content); + } catch (IllegalAccessException e) { + throw new IllegalCastException("Could not read content of field " + field.getName() + " on Object {" + instance + "}"); + } + } + }); // For ExcelRow annotation if(columnToField.containsKey(-1)) { @@ -136,12 +164,13 @@ private boolean setValue(Field field, int column, String content, Object ins) { if (column == index.value()) { Object o = casting.castValue(fieldType, content, options); setFieldData(field, o, ins); + return true; } } else { ExcelCellName excelCellName = field.getAnnotation(ExcelCellName.class); if (excelCellName != null) { Class fieldType = field.getType(); - Integer titleColumn = titles.get(excelCellName.value() ); + Integer titleColumn = columnIndexPerTitle.get(excelCellName.value() ); //Fix both columns mapped to name passing this condition below if (titleColumn != null && titleColumn == column) { Object o = casting.castValue(fieldType, content, options); @@ -191,7 +220,9 @@ public void cell(String cellReference, String formattedValue, XSSFComment commen int column = cellAddress.getColumn(); if (row <= headers) { - titles.put(formattedValue, column); + columnIndexPerTitle.put(formattedValue, column); + + titlePerColumnIndex.put(column, getTitleNameForMap(formattedValue, column)); } if (row + 1 <= options.skip()) { @@ -207,6 +238,17 @@ public void cell(String cellReference, String formattedValue, XSSFComment commen setFieldValue(formattedValue, type, column); } + private String getTitleNameForMap(String cellContent, int columnIndex) { + String titleName; + if (titlePerColumnIndex.containsValue(cellContent) + || cellContent.isEmpty()) { + titleName = cellContent + "@" + columnIndex; + } else { + titleName = cellContent; + } + return titleName; + } + @Override public void headerFooter(String text, boolean isHeader, String tagName) { //no-op diff --git a/src/test/java/com/poiji/deserialize/MissingRowIndexTest.java b/src/test/java/com/poiji/deserialize/MissingRowIndexTest.java index e609a15..f093b75 100644 --- a/src/test/java/com/poiji/deserialize/MissingRowIndexTest.java +++ b/src/test/java/com/poiji/deserialize/MissingRowIndexTest.java @@ -1,12 +1,9 @@ package com.poiji.deserialize; import com.poiji.bind.Poiji; -import com.poiji.deserialize.model.byid.Person; -import com.poiji.deserialize.model.byname.Organisation; +import com.poiji.deserialize.model.byname.OrganisationByName; import com.poiji.option.PoijiOptions; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; import java.io.File; import java.util.List; @@ -14,32 +11,31 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.junit.Assert.assertThat; -import static org.junit.runners.Parameterized.Parameters; public class MissingRowIndexTest { @Test public void emptyLine() { - List organisations = Poiji.fromExcel( + List organisations = Poiji.fromExcel( new File("src/test/resources/missing-row-1.xlsx"), - Organisation.class, + OrganisationByName.class, PoijiOptions.PoijiOptionsBuilder.settings() .sheetName("Organisation") .build() ); assertThat(organisations, notNullValue()); assertThat(organisations.size(), is(2)); - assertThat(organisations.stream().map(Organisation::getRowIndex).min(Integer::compareTo).get(), is(2)); - assertThat(organisations.stream().map(Organisation::getRowIndex).max(Integer::compareTo).get(), is(3)); + assertThat(organisations.stream().map(OrganisationByName::getRowIndex).min(Integer::compareTo).get(), is(2)); + assertThat(organisations.stream().map(OrganisationByName::getRowIndex).max(Integer::compareTo).get(), is(3)); } @Test public void nullLine() { - List organisations = Poiji.fromExcel( + List organisations = Poiji.fromExcel( new File("src/test/resources/missing-row-2.xlsx"), - Organisation.class, + OrganisationByName.class, PoijiOptions.PoijiOptionsBuilder.settings() .sheetName("Organisation") .build() @@ -47,7 +43,7 @@ public void nullLine() { assertThat(organisations, notNullValue()); assertThat(organisations.size(), is(4)); - assertThat(organisations.stream().map(Organisation::getRowIndex).min(Integer::compareTo).get(), is(2)); - assertThat(organisations.stream().map(Organisation::getRowIndex).max(Integer::compareTo).get(), is(5)); + assertThat(organisations.stream().map(OrganisationByName::getRowIndex).min(Integer::compareTo).get(), is(2)); + assertThat(organisations.stream().map(OrganisationByName::getRowIndex).max(Integer::compareTo).get(), is(5)); } } diff --git a/src/test/java/com/poiji/deserialize/UnknownCellsTest.java b/src/test/java/com/poiji/deserialize/UnknownCellsTest.java new file mode 100644 index 0000000..3d2ee01 --- /dev/null +++ b/src/test/java/com/poiji/deserialize/UnknownCellsTest.java @@ -0,0 +1,93 @@ +package com.poiji.deserialize; + +import com.poiji.bind.Poiji; +import com.poiji.deserialize.model.byid.OrgWithUnknownCells; +import com.poiji.deserialize.model.byname.OrgWithUnknownCellsByName; +import com.poiji.option.PoijiOptions; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.io.File; +import java.util.Arrays; +import java.util.List; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; + +@RunWith(Parameterized.class) +public class UnknownCellsTest { + + private String path; + + public UnknownCellsTest(String path) { + this.path = path; + } + + @Parameterized.Parameters + public static List excel() { + return Arrays.asList( + "src/test/resources/unknown-cells.xlsx", + "src/test/resources/unknown-cells.xls" + ); + } + + @Test + public void byName() { + List organisations = Poiji.fromExcel( + new File(path), + OrgWithUnknownCellsByName.class, + PoijiOptions.PoijiOptionsBuilder.settings() + .sheetName("Organisation") + .build() + ); + + assertThat(organisations, notNullValue()); + assertThat(organisations.size(), is(2)); + + OrgWithUnknownCellsByName firstRow = organisations.stream() + .filter(org -> org.getId().equals("CrEaTe")) + .findFirst() + .get(); + assertThat(firstRow.getUnknownCells().size(), is(1)); + assertThat(firstRow.getUnknownCells().get("Region"), is("EMEA")); + + + OrgWithUnknownCellsByName secondRow = organisations.stream() + .filter(org -> org.getId().equals("8d9e6430-8626-4556-8004-079085d2df2d")) + .findFirst() + .get(); + assertThat(secondRow.getUnknownCells().size(), is(1)); + assertThat(secondRow.getUnknownCells().get("Region"), is("NA")); + } + + @Test + public void byIndex() { + List organisations = Poiji.fromExcel( + new File(path), + OrgWithUnknownCells.class, + PoijiOptions.PoijiOptionsBuilder.settings() + .sheetName("Organisation") + .build() + ); + + assertThat(organisations, notNullValue()); + assertThat(organisations.size(), is(2)); + + OrgWithUnknownCells firstRow = organisations.stream() + .filter(org -> org.getId().equals("CrEaTe")) + .findFirst() + .get(); + assertThat(firstRow.getUnknownCells().size(), is(1)); + assertThat(firstRow.getUnknownCells().get("Region"), is("EMEA")); + + + OrgWithUnknownCells secondRow = organisations.stream() + .filter(org -> org.getId().equals("8d9e6430-8626-4556-8004-079085d2df2d")) + .findFirst() + .get(); + assertThat(secondRow.getUnknownCells().size(), is(1)); + assertThat(secondRow.getUnknownCells().get("Region"), is("NA")); + } +} diff --git a/src/test/java/com/poiji/deserialize/model/byid/OrgWithUnknownCells.java b/src/test/java/com/poiji/deserialize/model/byid/OrgWithUnknownCells.java new file mode 100644 index 0000000..c14b77f --- /dev/null +++ b/src/test/java/com/poiji/deserialize/model/byid/OrgWithUnknownCells.java @@ -0,0 +1,72 @@ +package com.poiji.deserialize.model.byid; + +import com.poiji.annotation.ExcelCell; +import com.poiji.annotation.ExcelRow; +import com.poiji.annotation.ExcelUnknownCells; + +import java.util.Map; + +public class OrgWithUnknownCells { + + @ExcelRow + private int rowIndex; + + @ExcelCell(0) + private String id; + + @ExcelCell(1) + private String externalId; + + @ExcelUnknownCells + private Map unknownCells; + + public Map getUnknownCells() { + return unknownCells; + } + + @ExcelCell(2) + private String name; + + @ExcelCell(3) + private String customerExternalId; + + public int getRowIndex() { + return rowIndex; + } + + public void setRowIndex(int rowIndex) { + this.rowIndex = rowIndex; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getExternalId() { + return externalId; + } + + public void setExternalId(String externalId) { + this.externalId = externalId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getCustomerExternalId() { + return customerExternalId; + } + + public void setCustomerExternalId(String customerExternalId) { + this.customerExternalId = customerExternalId; + } +} diff --git a/src/test/java/com/poiji/deserialize/model/byname/OrgWithUnknownCellsByName.java b/src/test/java/com/poiji/deserialize/model/byname/OrgWithUnknownCellsByName.java new file mode 100644 index 0000000..66464ee --- /dev/null +++ b/src/test/java/com/poiji/deserialize/model/byname/OrgWithUnknownCellsByName.java @@ -0,0 +1,77 @@ +package com.poiji.deserialize.model.byname; + +import com.poiji.annotation.ExcelCellName; +import com.poiji.annotation.ExcelRow; +import com.poiji.annotation.ExcelUnknownCells; + +import java.util.Map; + +public class OrgWithUnknownCellsByName { + + public static final String HEADER_ORGANISATION_ID = "Organisation ID"; + public static final String HEADER_CUSTOMER_EXTERNAL_ID = "Customer External ID"; + public static final String HEADER_ORGANISATION_EXTERNAL_ID = "Organisation External ID"; + public static final String HEADER_ORGANISATION_NAME = "Organisation Name"; + + @ExcelRow + private int rowIndex; + + @ExcelCellName(HEADER_ORGANISATION_ID) + private String id; + + @ExcelCellName(HEADER_ORGANISATION_EXTERNAL_ID) + private String externalId; + + @ExcelUnknownCells + private Map unknownCells; + + public Map getUnknownCells() { + return unknownCells; + } + + @ExcelCellName(HEADER_ORGANISATION_NAME) + private String name; + + @ExcelCellName(HEADER_CUSTOMER_EXTERNAL_ID) + private String customerExternalId; + + public int getRowIndex() { + return rowIndex; + } + + public void setRowIndex(int rowIndex) { + this.rowIndex = rowIndex; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getExternalId() { + return externalId; + } + + public void setExternalId(String externalId) { + this.externalId = externalId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getCustomerExternalId() { + return customerExternalId; + } + + public void setCustomerExternalId(String customerExternalId) { + this.customerExternalId = customerExternalId; + } +} diff --git a/src/test/java/com/poiji/deserialize/model/byname/Organisation.java b/src/test/java/com/poiji/deserialize/model/byname/OrganisationByName.java similarity index 97% rename from src/test/java/com/poiji/deserialize/model/byname/Organisation.java rename to src/test/java/com/poiji/deserialize/model/byname/OrganisationByName.java index 1ab7a52..d9c436e 100644 --- a/src/test/java/com/poiji/deserialize/model/byname/Organisation.java +++ b/src/test/java/com/poiji/deserialize/model/byname/OrganisationByName.java @@ -3,7 +3,7 @@ import com.poiji.annotation.ExcelCellName; import com.poiji.annotation.ExcelRow; -public class Organisation { +public class OrganisationByName { public static final String HEADER_ORGANISATION_ID = "Organisation ID"; public static final String HEADER_CUSTOMER_EXTERNAL_ID = "Customer External ID"; diff --git a/src/test/resources/unknown-cells.xls b/src/test/resources/unknown-cells.xls new file mode 100644 index 0000000000000000000000000000000000000000..632bbc06ececc80c1713b04b7c5d56ed9af24df9 GIT binary patch literal 26112 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 B27mwn literal 0 HcmV?d00001