diff --git a/README.adoc b/README.adoc
index 6e5ca83..7e66a19 100644
--- a/README.adoc
+++ b/README.adoc
@@ -2,8 +2,8 @@
:toclevels: 2
= Poiji
-:version: v4.2.0
-:branch: 4.2.0
+:version: v4.3.0
+:branch: 4.3.0
image:https://github.com/ozlerhakan/poiji/actions/workflows/maven.yml/badge.svg["Build Status"] image:https://app.codacy.com/project/badge/Grade/64f7e2cb9e604807b62334a4cfc3952d["Codacy code quality",link="https://www.codacy.com/gh/ozlerhakan/poiji/dashboard?utm_source=github.com&utm_medium=referral&utm_content=ozlerhakan/poiji&utm_campaign=Badge_Grade"]
image:https://codecov.io/gh/ozlerhakan/poiji/branch/{branch}/graph/badge.svg?token=MN6V6xOWBq["Codecov",link="https://codecov.io/gh/ozlerhakan/poiji"] image:https://img.shields.io/badge/apache.poi-5.2.3-brightgreen.svg[] image:https://app.fossa.com/api/projects/git%2Bgithub.com%2Fozlerhakan%2Fpoiji.svg?type=shield["FOSSA Status",link="https://app.fossa.com/projects/git%2Bgithub.com%2Fozlerhakan%2Fpoiji?ref=badge_shield"]
@@ -25,7 +25,7 @@ In your Maven/Gradle project, first add the corresponding dependency:
com.github.ozlerhakan
poiji
- 4.2.0
+ 4.3.0
----
diff --git a/pom.xml b/pom.xml
index bf595ca..b1af229 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
com.github.ozlerhakan
poiji
- 4.2.0
+ 4.3.0
jar
poiji
diff --git a/src/main/java/com/poiji/annotation/ExcelCellName.java b/src/main/java/com/poiji/annotation/ExcelCellName.java
index 9bd09ca..8d51cbc 100644
--- a/src/main/java/com/poiji/annotation/ExcelCellName.java
+++ b/src/main/java/com/poiji/annotation/ExcelCellName.java
@@ -16,7 +16,7 @@
/**
* Specifies the column name where the corresponding value is mapped from the
- * excel data
+ * Excel data
*
* @return column name
*/
@@ -35,4 +35,12 @@
* @return mandatory cell signal. Default is false.
*/
boolean mandatoryCell() default false;
+
+ /**
+ * Specifies the column regular expression where the corresponding value is mapped from the
+ * Excel data
+ *
+ * @return column regular expression
+ */
+ String expression() default "";
}
diff --git a/src/main/java/com/poiji/annotation/ExcelCellsJoinedByName.java b/src/main/java/com/poiji/annotation/ExcelCellsJoinedByName.java
new file mode 100644
index 0000000..7f5fedd
--- /dev/null
+++ b/src/main/java/com/poiji/annotation/ExcelCellsJoinedByName.java
@@ -0,0 +1,20 @@
+package com.poiji.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * Created by aerfus on 18/02/2024
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+@Documented
+public @interface ExcelCellsJoinedByName {
+
+ /**
+ * Specifies the column regular expression where the corresponding values are mapped from the
+ * Excel data
+ *
+ * @return column regular expression
+ */
+ String expression();
+}
diff --git a/src/main/java/com/poiji/bind/mapping/HSSFUnmarshaller.java b/src/main/java/com/poiji/bind/mapping/HSSFUnmarshaller.java
index eeece4d..d48f01e 100644
--- a/src/main/java/com/poiji/bind/mapping/HSSFUnmarshaller.java
+++ b/src/main/java/com/poiji/bind/mapping/HSSFUnmarshaller.java
@@ -6,6 +6,7 @@
import com.poiji.annotation.ExcelCellRange;
import com.poiji.annotation.ExcelRow;
import com.poiji.annotation.ExcelUnknownCells;
+import com.poiji.annotation.ExcelCellsJoinedByName;
import com.poiji.bind.Unmarshaller;
import com.poiji.config.Casting;
import com.poiji.config.Formatting;
@@ -15,6 +16,8 @@
import com.poiji.option.PoijiOptions;
import com.poiji.util.AnnotationUtil;
import com.poiji.util.ReflectUtil;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.MultiValuedMap;
import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.formula.BaseFormulaEvaluator;
@@ -25,6 +28,7 @@
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.NumberToTextConverter;
+import org.apache.poi.util.StringUtil;
import java.lang.reflect.Field;
import java.util.ArrayList;
@@ -35,6 +39,7 @@
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Consumer;
+import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
@@ -245,11 +250,18 @@ private Integer tailSetFieldValue(Row currentRow, T instance, Field field) {
if (annotationDetail.getColumn() != null) {
constructTypeValue(currentRow, instance, field, annotationDetail);
}
+
+ if (CollectionUtils.isNotEmpty(annotationDetail.getColumns())) {
+ for (Integer column : annotationDetail.getColumns()) {
+ annotationDetail.setColumn(column);
+ constructTypeValue(currentRow, instance, field, annotationDetail);
+ }
+ }
+
return annotationDetail.getColumn();
}
private FieldAnnotationDetail getFieldColumn(final Field field) {
- ExcelCell index = field.getAnnotation(ExcelCell.class);
DisableCellFormatXLS disableCellFormat = field.getAnnotation(DisableCellFormatXLS.class);
final FieldAnnotationDetail annotationDetail = new FieldAnnotationDetail();
@@ -257,22 +269,57 @@ private FieldAnnotationDetail getFieldColumn(final Field field) {
annotationDetail.setDisabledCellFormat(disableCellFormat.value());
}
+ ExcelCell index = field.getAnnotation(ExcelCell.class);
if (index != null) {
annotationDetail.setColumn(index.value());
annotationDetail.setMandatoryCell(index.mandatoryCell());
- } else {
- ExcelCellName excelCellName = field.getAnnotation(ExcelCellName.class);
- if (excelCellName != null) {
- annotationDetail.setMandatoryCell(excelCellName.mandatoryCell());
- annotationDetail.setColumnName(excelCellName.value());
- final String titleName = formatting.transform(options, excelCellName.value());
- Integer column = titleToIndex.get(titleName);
- annotationDetail.setColumn(column);
- }
}
+
+ ExcelCellName excelCellName = field.getAnnotation(ExcelCellName.class);
+ if (excelCellName != null) {
+ annotationDetail.setMandatoryCell(excelCellName.mandatoryCell());
+ annotationDetail.setColumnName(excelCellName.value());
+ Integer column = findTitleColumn(excelCellName);
+ annotationDetail.setColumn(column);
+ }
+
+ ExcelCellsJoinedByName excelCellsJoinedByName = field.getAnnotation(ExcelCellsJoinedByName.class);
+ if (excelCellsJoinedByName != null) {
+ String expression = excelCellsJoinedByName.expression();
+ Pattern pattern = Pattern.compile(expression);
+
+ List columns = indexToTitle.entrySet().stream()
+ .filter(entry -> pattern.matcher(
+ entry.getValue().replaceAll("@[0-9]+", ""))
+ .matches())
+ .map(Map.Entry::getKey)
+ .collect(Collectors.toList());
+
+ annotationDetail.setColumns(columns);
+ annotationDetail.setMultiValueMap(CollectionUtils.isNotEmpty(columns));
+ }
+
return annotationDetail;
}
+ public Integer findTitleColumn(ExcelCellName excelCellName) {
+ if (!StringUtil.isBlank(excelCellName.value())) {
+ final String titleName = formatting.transform(options, excelCellName.value());
+ return titleToIndex.get(titleName);
+ }
+
+ if (!StringUtil.isBlank(excelCellName.expression())) {
+ final String titleName = formatting.transform(options, excelCellName.expression());
+ Pattern pattern = Pattern.compile(titleName);
+ return titleToIndex.entrySet().stream()
+ .filter(entry -> pattern.matcher(entry.getKey()).matches())
+ .findFirst()
+ .map(Map.Entry::getValue)
+ .orElse(null);
+ }
+ return null;
+ }
+
private void constructTypeValue(Row currentRow, T instance, Field field,
FieldAnnotationDetail annotationDetail) {
Cell cell = currentRow.getCell(annotationDetail.getColumn());
@@ -289,7 +336,14 @@ private void constructTypeValue(Row currentRow, T instance, Field field,
}
Object data = casting.castValue(field, value, currentRow.getRowNum(), annotationDetail.getColumn(),
options);
- setFieldData(instance, field, data);
+
+ if (!annotationDetail.isMultiValueMap()) {
+ setFieldData(instance, field, data);
+ } else {
+ String titleColumn = indexToTitle.get(annotationDetail.getColumn());
+ titleColumn = titleColumn.replaceAll("@[0-9]+", "");
+ putFieldMultiValueMapData(instance, field, titleColumn, data);
+ }
} else if (annotationDetail.isMandatoryCell()) {
throw new PoijiRowSpecificException(annotationDetail.getColumnName(), field.getName(),
currentRow.getRowNum());
@@ -311,6 +365,16 @@ private void setFieldData(T instance, Field field, Object data) {
}
}
+ public void putFieldMultiValueMapData(Object instance, Field field, String columnName, Object o) {
+ try {
+ field.setAccessible(true);
+ MultiValuedMap multiValuedMap = (MultiValuedMap) field.get(instance);
+ multiValuedMap.put(columnName, o);
+ } catch (ClassCastException | IllegalAccessException e) {
+ throw new IllegalCastException("Unexpected cast type {" + o + "} of field" + field.getName());
+ }
+ }
+
private T setFieldValuesFromRowIntoInstance(Row currentRow, Class super T> subclass, T instance) {
return subclass == null
? instance
@@ -338,6 +402,10 @@ private static class FieldAnnotationDetail {
private boolean disabledCellFormat;
private boolean mandatoryCell;
+ private List columns;
+
+ private boolean multiValueMap;
+
Integer getColumn() {
return column;
}
@@ -370,6 +438,21 @@ public void setMandatoryCell(boolean mandatoryCell) {
this.mandatoryCell = mandatoryCell;
}
+ public List getColumns() {
+ return columns;
+ }
+
+ public void setColumns(List columns) {
+ this.columns = columns;
+ }
+
+ public boolean isMultiValueMap() {
+ return multiValueMap;
+ }
+
+ public void setMultiValueMap(boolean multiValueMap) {
+ this.multiValueMap = multiValueMap;
+ }
}
}
diff --git a/src/main/java/com/poiji/bind/mapping/PoijiHandler.java b/src/main/java/com/poiji/bind/mapping/PoijiHandler.java
index cf8e080..065718f 100644
--- a/src/main/java/com/poiji/bind/mapping/PoijiHandler.java
+++ b/src/main/java/com/poiji/bind/mapping/PoijiHandler.java
@@ -5,6 +5,7 @@
import com.poiji.annotation.ExcelCellRange;
import com.poiji.annotation.ExcelRow;
import com.poiji.annotation.ExcelUnknownCells;
+import com.poiji.annotation.ExcelCellsJoinedByName;
import com.poiji.config.Casting;
import com.poiji.config.Formatting;
import com.poiji.exception.IllegalCastException;
@@ -12,6 +13,7 @@
import com.poiji.util.AnnotationUtil;
import com.poiji.util.ReflectUtil;
import org.apache.poi.ss.util.CellAddress;
+import org.apache.poi.util.StringUtil;
import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler.SheetContentsHandler;
import org.apache.poi.xssf.usermodel.XSSFComment;
@@ -21,6 +23,7 @@
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
+import java.util.regex.Pattern;
import java.util.stream.Stream;
import static java.lang.String.valueOf;
@@ -167,23 +170,55 @@ private boolean setValue(Field field, int column, String content, Object ins) {
ReflectUtil.setFieldData(field, o, ins);
return true;
}
- } else {
- ExcelCellName excelCellName = field.getAnnotation(ExcelCellName.class);
- if (excelCellName != null) {
- excelCellNameAnnotations.add(excelCellName);
- final String titleName = formatting.transform(options, excelCellName.value());
- final Integer titleColumn = titleToIndex.get(titleName);
- // Fix both columns mapped to name passing this condition below
- if (titleColumn != null && titleColumn == column) {
- Object o = casting.castValue(field, content, internalRow, column, options);
- ReflectUtil.setFieldData(field, o, ins);
- return true;
- }
+ }
+
+ ExcelCellName excelCellName = field.getAnnotation(ExcelCellName.class);
+ if (excelCellName != null) {
+ excelCellNameAnnotations.add(excelCellName);
+ final Integer titleColumn = findTitleColumn(excelCellName);
+ // Fix both columns mapped to name passing this condition below
+ if (titleColumn != null && titleColumn == column) {
+ Object o = casting.castValue(field, content, internalRow, column, options);
+ ReflectUtil.setFieldData(field, o, ins);
+ return true;
}
}
+
+ ExcelCellsJoinedByName excelCellsJoinedByName = field.getAnnotation(ExcelCellsJoinedByName.class);
+ if (excelCellsJoinedByName != null) {
+ String titleColumn = indexToTitle.get(column).replaceAll("@[0-9]+", "");
+
+ String expression = excelCellsJoinedByName.expression();
+ Pattern pattern = Pattern.compile(expression);
+ if (pattern.matcher(titleColumn).matches()) {
+ Object o = casting.castValue(field, content, internalRow, column, options);
+ ReflectUtil.putFieldMultiValueMapData(field, titleColumn, o, ins);
+ return true;
+ }
+ }
+
return false;
}
+ public Integer findTitleColumn(ExcelCellName excelCellName) {
+ if (!StringUtil.isBlank(excelCellName.value())) {
+ final String titleName = formatting.transform(options, excelCellName.value());
+ return titleToIndex.get(titleName);
+ }
+
+ if (!StringUtil.isBlank(excelCellName.expression())) {
+ final String titleName = formatting.transform(options, excelCellName.expression());
+ Pattern pattern = Pattern.compile(titleName);
+ return titleToIndex.entrySet().stream()
+ .filter(entry -> pattern.matcher(entry.getKey()).matches())
+ .findFirst()
+ .map(Map.Entry::getValue)
+ .orElse(null);
+ }
+
+ return null;
+ }
+
@Override
public void startRow(int rowNum) {
if (rowNum + 1 > options.skip()) {
diff --git a/src/main/java/com/poiji/util/ReflectUtil.java b/src/main/java/com/poiji/util/ReflectUtil.java
index 3f95382..278c3d0 100644
--- a/src/main/java/com/poiji/util/ReflectUtil.java
+++ b/src/main/java/com/poiji/util/ReflectUtil.java
@@ -3,6 +3,7 @@
import com.poiji.annotation.ExcelCellRange;
import com.poiji.exception.IllegalCastException;
import com.poiji.exception.PoijiInstantiationException;
+import org.apache.commons.collections4.MultiValuedMap;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
@@ -64,4 +65,15 @@ public static void setFieldData(Field field, Object o, Object instance) {
throw new IllegalCastException("Unexpected cast type {" + o + "} of field" + field.getName());
}
}
+
+ @SuppressWarnings("unchecked")
+ public static void putFieldMultiValueMapData(Field field, String columnName, Object o, Object instance) {
+ try {
+ field.setAccessible(true);
+ MultiValuedMap multiValuedMap = (MultiValuedMap) field.get(instance);
+ multiValuedMap.put(columnName, o);
+ } catch (IllegalAccessException | IllegalAccessError e) {
+ throw new IllegalCastException("Unexpected cast type {" + o + "} of field" + field.getName());
+ }
+ }
}
diff --git a/src/test/java/com/poiji/bind/mapping/HSSFUnmarshallerTest.java b/src/test/java/com/poiji/bind/mapping/HSSFUnmarshallerTest.java
new file mode 100644
index 0000000..7f39071
--- /dev/null
+++ b/src/test/java/com/poiji/bind/mapping/HSSFUnmarshallerTest.java
@@ -0,0 +1,47 @@
+package com.poiji.bind.mapping;
+
+import com.poiji.annotation.ExcelCellName;
+import com.poiji.deserialize.model.InventoryData;
+import com.poiji.option.PoijiOptions;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.lang.reflect.Field;
+import java.util.List;
+
+import static org.junit.Assert.assertNull;
+
+@RunWith(Parameterized.class)
+public class HSSFUnmarshallerTest {
+
+ private final ExcelCellName annotation;
+
+ public HSSFUnmarshallerTest(String fieldName) throws NoSuchFieldException {
+ Field author = InventoryData.class.getDeclaredField(fieldName);
+ annotation = author.getAnnotation(ExcelCellName.class);
+ }
+
+ @Parameterized.Parameters(name = "{index}: ({0})={1}")
+ public static Iterable