diff --git a/README.adoc b/README.adoc
index 357715a..4e544d1 100644
--- a/README.adoc
+++ b/README.adoc
@@ -3,7 +3,7 @@
This is an advanced fork of https://github.com/ozlerhakan/poiji. +
Many thanks to ozlerhakan for inspiration.
-Apache POI library required as dependency to work with xlsx and xls. Tested with dependency 'org.apache.poi:poi-ooxml:4.1.2'.
+Apache POI library required as dependency to work with xlsx and xls. Tested with dependency 'org.apache.poi:poi-ooxml:5.2.5'.
In your Maven/Gradle project, first add the corresponding dependency:
@@ -14,7 +14,7 @@ In your Maven/Gradle project, first add the corresponding dependency:
io.github.vaa25
poiji2
- 1.4.0
+ 1.4.1
org.apache.poi
@@ -29,7 +29,7 @@ In your Maven/Gradle project, first add the corresponding dependency:
[source,groovy]
----
dependencies {
- implementation 'io.github.vaa25:poiji2:1.4.0'
+ implementation 'io.github.vaa25:poiji2:1.4.1'
implementation 'org.apache.poi:poi-ooxml:5.2.5'
}
----
@@ -51,3 +51,4 @@ Also:
- Poiji2 can read lists in row (use `@ExcelList` on `List`)
- Poiji2 can read and write huge xlsx files (see HugeTest.java)
- Poiji2 (since v1.4.0) can work with immutable java classes (see IgnoreTest.java). lombok @Value and java records applicable also.
+- Poiji2 (since v1.4.1) can work with immutable java classes with many constructors (see ExcelConstructorTest.java). Apply @ExcelConstructor to choose one of.
diff --git a/src/main/java/com/poiji/annotation/ExcelConstructor.java b/src/main/java/com/poiji/annotation/ExcelConstructor.java
new file mode 100644
index 0000000..b49c378
--- /dev/null
+++ b/src/main/java/com/poiji/annotation/ExcelConstructor.java
@@ -0,0 +1,15 @@
+package com.poiji.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * Marks constructor what should be used by Poiji.
+ *
+ * Created by vaa25 on 16.03.24.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.CONSTRUCTOR)
+@Documented
+public @interface ExcelConstructor {
+
+}
diff --git a/src/main/java/com/poiji/bind/mapping/ReadMappedFields.java b/src/main/java/com/poiji/bind/mapping/ReadMappedFields.java
index 139fa41..b782d3d 100644
--- a/src/main/java/com/poiji/bind/mapping/ReadMappedFields.java
+++ b/src/main/java/com/poiji/bind/mapping/ReadMappedFields.java
@@ -183,9 +183,7 @@ private List parseExcelRow(final List fields) {
final ExcelRow annotation = field.getAnnotation(ExcelRow.class);
if (annotation != null) {
this.excelRow.add(field);
- if (!field.isAccessible()) {
- field.setAccessible(true);
- }
+ ReflectUtil.setAccessible(field);
} else {
rest.add(field);
}
@@ -199,9 +197,7 @@ private List parseExcelError(final List fields) {
final ExcelParseExceptions annotation = field.getAnnotation(ExcelParseExceptions.class);
if (annotation != null) {
this.excelParseException.add(field);
- if (!field.isAccessible()) {
- field.setAccessible(true);
- }
+ ReflectUtil.setAccessible(field);
} else {
rest.add(field);
}
@@ -223,9 +219,7 @@ private List parseExcelCellName(final List fields) {
final String name = options.getFormatting().transform(options, possibleFieldName);
namedFields.put(name, field);
}
- if (!field.isAccessible()) {
- field.setAccessible(true);
- }
+ ReflectUtil.setAccessible(field);
} else {
rest.add(field);
}
@@ -243,9 +237,7 @@ private List parseUnknownCells(final List fields) {
for (final Field field : fields) {
if (field.getAnnotation(ExcelUnknownCells.class) != null && field.getType().isAssignableFrom(Map.class)) {
unknownFields.add(field);
- if (!field.isAccessible()) {
- field.setAccessible(true);
- }
+ ReflectUtil.setAccessible(field);
} else {
rest.add(field);
}
@@ -258,9 +250,7 @@ private List parseExcelCellRange(final List fields) {
for (final Field field : fields) {
if (field.getAnnotation(ExcelCellRange.class) != null) {
rangeFields.put(field, new ReadMappedFields(field.getType(), options).parseEntity());
- if (!field.isAccessible()) {
- field.setAccessible(true);
- }
+ ReflectUtil.setAccessible(field);
} else {
rest.add(field);
}
@@ -275,9 +265,7 @@ private List parseExcelList(final List fields) {
if (field.getType().isAssignableFrom(List.class) && annotation != null) {
final Class entity = (Class) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
listFields.put(field, new ReadMappedList(annotation, entity, options));
- if (!field.isAccessible()) {
- field.setAccessible(true);
- }
+ ReflectUtil.setAccessible(field);
} else {
rest.add(field);
}
@@ -291,9 +279,7 @@ private List parseExcelCell(final List fields) {
if (field.getAnnotation(ExcelCell.class) != null) {
final Integer excelOrder = field.getAnnotation(ExcelCell.class).value();
orderedFields.put(excelOrder, field);
- if (!field.isAccessible()) {
- field.setAccessible(true);
- }
+ ReflectUtil.setAccessible(field);
} else {
rest.add(field);
}
diff --git a/src/main/java/com/poiji/save/MappedFields.java b/src/main/java/com/poiji/save/MappedFields.java
index 24cc78e..01d872a 100644
--- a/src/main/java/com/poiji/save/MappedFields.java
+++ b/src/main/java/com/poiji/save/MappedFields.java
@@ -7,6 +7,8 @@
import com.poiji.bind.mapping.SheetNameExtractor;
import com.poiji.exception.PoijiException;
import com.poiji.option.PoijiOptions;
+import com.poiji.util.ReflectUtil;
+
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
@@ -50,14 +52,10 @@ public MappedFields parseEntity() {
final String name = field.getName();
orders.put(field, excelOrder);
names.put(field, name);
- if (!field.isAccessible()) {
- field.setAccessible(true);
- }
+ ReflectUtil.setAccessible(field);
} else if (field.getAnnotation(ExcelUnknownCells.class) != null) {
unknownCells.add(field);
- if (!field.isAccessible()) {
- field.setAccessible(true);
- }
+ ReflectUtil.setAccessible(field);
} else {
final ExcelCellName annotation = field.getAnnotation(ExcelCellName.class);
if (annotation != null) {
@@ -72,9 +70,7 @@ public MappedFields parseEntity() {
orders.put(field, order);
}
names.put(field, excelName);
- if (!field.isAccessible()) {
- field.setAccessible(true);
- }
+ ReflectUtil.setAccessible(field);
}
}
}
diff --git a/src/main/java/com/poiji/util/ConstructorFieldMapper.java b/src/main/java/com/poiji/util/ConstructorFieldMapper.java
index 6237e4d..ce30b05 100644
--- a/src/main/java/com/poiji/util/ConstructorFieldMapper.java
+++ b/src/main/java/com/poiji/util/ConstructorFieldMapper.java
@@ -2,6 +2,7 @@
import com.poiji.exception.PoijiInstantiationException;
+import java.beans.ConstructorProperties;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
@@ -18,40 +19,85 @@ static Object[] getConstructorFields(Constructor> constructor) {
}
private static Object[] getConstructorMapping(Constructor> constructor) {
+ final Object[] constructorMapping = new Object[constructor.getParameterCount()];
+ mapFieldsWithConstructorProperties(constructor, constructorMapping);
+ findNotMappedFieldsWithExamining(constructor, constructorMapping);
+ fillNotMappedParametersWithDefaults(constructor, constructorMapping);
+ return constructorMapping;
+ }
+
+ /**
+ * ConstructorProperties is used by lombok. It allows to map any custom fields easy without examining.
+ */
+ private static Object[] mapFieldsWithConstructorProperties(Constructor> constructor, Object[] constructorMapping) {
+ final ConstructorProperties constructorProperties = constructor.getAnnotation(ConstructorProperties.class);
+ if (constructorProperties != null) {
+ final Field[] fields = constructor.getDeclaringClass().getDeclaredFields();
+ final String[] parameterNames = constructorProperties.value();
+ final Class>[] parameterTypes = constructor.getParameterTypes();
+ for (int i = 0; i < parameterNames.length; i++) {
+ final String parameterName = parameterNames[i];
+ for (Field field : fields) {
+ if (field.getName().equals(parameterName) && field.getType() == parameterTypes[i]) {
+ ReflectUtil.setAccessible(field);
+ constructorMapping[i] = field;
+ break;
+ }
+ }
+ }
+ }
+ return constructorMapping;
+ }
+
+ /**
+ * The only way to find mapping between constructor parameters and instance fields is to pass special value
+ * into constructor and look it up in every field in instance.
+ * Knowing what parameter was passed and what field was found in we can define one mapping.
+ */
+ private static Object[] findNotMappedFieldsWithExamining(Constructor> constructor, Object[] constructorMapping) {
final Class> entity = constructor.getDeclaringClass();
final Class>[] parameterTypes = constructor.getParameterTypes();
- final Object[] defaults = fieldDefaultsMapping.computeIfAbsent(constructor, ignored -> getDefaultValues(parameterTypes));
- final Object[] constructorMapping = new Object[parameterTypes.length];
+ final Object[] defaultConstructorParameters = fieldDefaultsMapping.computeIfAbsent(constructor, ignored -> getDefaultValues(parameterTypes));
+ final Field[] fields = entity.getDeclaredFields();
for (int i = 0; i < parameterTypes.length; i++) {
- final Object[] clone = defaults.clone();
- final Class> parameterType = parameterTypes[i];
- final Object example = ImmutableInstanceRegistrar.getEmptyInstance(parameterType);
- clone[i] = example;
- try {
- final Object instance = constructor.newInstance(clone);
- final Field[] fields = entity.getDeclaredFields();
- for (Field field : fields) {
- if (!field.isAccessible()) {
- field.setAccessible(true);
- }
- try {
- if (isFieldHasExampleValue(field, instance, example)) {
- constructorMapping[i] = field;
- break;
+ if (constructorMapping[i] == null) {
+ final Object[] instanceConstructorParameters = defaultConstructorParameters.clone();
+ final Class> parameterType = parameterTypes[i];
+ final Object parameterToExamine = ImmutableInstanceRegistrar.getEmptyInstance(parameterType);
+ instanceConstructorParameters[i] = parameterToExamine;
+ try {
+ final Object instance = constructor.newInstance(instanceConstructorParameters);
+ for (Field field : fields) {
+ ReflectUtil.setAccessible(field);
+ try {
+ if (isFieldHasExampleValue(field, instance, parameterToExamine)) {
+ constructorMapping[i] = field;
+ break;
+ }
+ } catch (IllegalAccessException e) {
+ throw new PoijiInstantiationException("Can't get field " + field, e);
}
- } catch (IllegalAccessException e) {
- throw new PoijiInstantiationException("Can't get field " + field, e);
}
+ } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
+ throw new PoijiInstantiationException("Can't create instance " + entity, e);
}
- } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
- throw new PoijiInstantiationException("Can't create instance " + entity, e);
}
+ }
+ return constructorMapping;
+ }
+
+ /**
+ * Sometimes constructor can have parameter that not corresponds to any field.
+ * We have to fill it anyway to construct instance successfully.
+ */
+ private static Object[] fillNotMappedParametersWithDefaults(Constructor> constructor, Object[] constructorMapping) {
+ final Object[] defaultConstructorParameters = fieldDefaultsMapping.computeIfAbsent(constructor, ignored -> getDefaultValues(constructor.getParameterTypes()));
+ for (int i = 0; i < constructorMapping.length; i++) {
if (constructorMapping[i] == null) {
- constructorMapping[i] = defaults[i];
+ constructorMapping[i] = defaultConstructorParameters[i];
}
}
return constructorMapping;
-
}
private static boolean isFieldHasExampleValue(Field field, Object instance, Object example) throws IllegalAccessException {
diff --git a/src/main/java/com/poiji/util/ConstructorSelector.java b/src/main/java/com/poiji/util/ConstructorSelector.java
new file mode 100644
index 0000000..b89f2e5
--- /dev/null
+++ b/src/main/java/com/poiji/util/ConstructorSelector.java
@@ -0,0 +1,53 @@
+package com.poiji.util;
+
+import com.poiji.annotation.ExcelConstructor;
+import com.poiji.exception.PoijiInstantiationException;
+
+import java.lang.reflect.Constructor;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class ConstructorSelector {
+
+ private static final Map, Constructor>> fieldDefaultsMapping = new ConcurrentHashMap<>();
+
+ public static Constructor> selectConstructor(Class> type) {
+ return fieldDefaultsMapping.computeIfAbsent(type, ignored -> setAccessible(defineConstructor(type)));
+ }
+
+ private static Constructor> setAccessible(Constructor> constructor) {
+ if (!constructor.isAccessible()) {
+ constructor.setAccessible(true);
+ }
+ return constructor;
+ }
+
+ private static Constructor> defineConstructor(Class> type) {
+ final Constructor>[] constructors = type.getDeclaredConstructors();
+ if (constructors.length > 1) {
+ final Constructor> constructor = getMarkedConstructor(type, constructors);
+ if (constructor == null) {
+ final String annotation = ExcelConstructor.class.getSimpleName();
+ final String message = String.format("Several constructors were found in %s. Mark one of it with @%s please.", type.getName(), annotation);
+ throw new PoijiInstantiationException(message, null);
+ }
+ return constructor;
+ }
+ return constructors[0];
+ }
+
+ private static Constructor> getMarkedConstructor(Class> type, Constructor>[] constructors) {
+ Constructor> result = null;
+ for (Constructor> constructor : constructors) {
+ if (constructor.isAnnotationPresent(ExcelConstructor.class)) {
+ if (result != null) {
+ final String annotation = ExcelConstructor.class.getSimpleName();
+ final String message = String.format("Several constructors are marked with @%s in %s. Mark only one of it please.", annotation, type.getName());
+ throw new PoijiInstantiationException(message, null);
+ }
+ result = constructor;
+ }
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/com/poiji/util/ImmutableInstanceRegistrar.java b/src/main/java/com/poiji/util/ImmutableInstanceRegistrar.java
index e11e1df..bbb9aa3 100644
--- a/src/main/java/com/poiji/util/ImmutableInstanceRegistrar.java
+++ b/src/main/java/com/poiji/util/ImmutableInstanceRegistrar.java
@@ -1,6 +1,7 @@
package com.poiji.util;
import com.poiji.exception.PoijiException;
+import com.poiji.exception.PoijiInstantiationException;
import java.math.BigDecimal;
import java.time.LocalDate;
@@ -51,6 +52,6 @@ static T getEmptyInstance(Class type) {
}
final String message = String.format("Use %s.register() to register empty instance for '%s' first",
ImmutableInstanceRegistrar.class.getName(), type.getName());
- throw new PoijiException(message);
+ throw new PoijiInstantiationException(message, null);
}
}
diff --git a/src/main/java/com/poiji/util/ReflectUtil.java b/src/main/java/com/poiji/util/ReflectUtil.java
index 9d75894..4d703c8 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.bind.mapping.Data;
import com.poiji.exception.IllegalCastException;
+import com.poiji.exception.PoijiException;
import com.poiji.exception.PoijiInstantiationException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
@@ -39,15 +40,14 @@ public static T newInstanceOf(Data data) {
public static T newInstanceOf(Class> type, Data data) {
try {
- final Constructor> constructor = type.getDeclaredConstructors()[0];
- if (!constructor.isAccessible()) {
- constructor.setAccessible(true);
- }
+ final Constructor> constructor = ConstructorSelector.selectConstructor(type);
if (constructor.getParameterCount() == 0) {
return createInstanceUsingDefaultConstructor(data, constructor);
} else {
return createInstanceUsingParameterizedConstructor(data, constructor);
}
+ } catch (PoijiInstantiationException exception) {
+ throw exception;
} catch (Exception ex) {
throw new PoijiInstantiationException("Cannot create a new instance of " + type.getName(), ex);
}
@@ -158,12 +158,16 @@ public static Collection findRecursivePoijiAnnotati
public static void setFieldData(Field field, Object o, Object instance) {
try {
- if (!field.isAccessible()) {
- field.setAccessible(true);
- }
+ setAccessible(field);
field.set(instance, o);
} catch (IllegalAccessException e) {
throw new IllegalCastException("Unexpected cast type {" + o + "} of field" + field.getName());
}
}
+
+ public static void setAccessible(Field field) {
+ if (!field.isAccessible()) {
+ field.setAccessible(true);
+ }
+ }
}
diff --git a/src/test/java/com/poiji/deserialize/ExcelConstructorTest.java b/src/test/java/com/poiji/deserialize/ExcelConstructorTest.java
new file mode 100644
index 0000000..e53b59f
--- /dev/null
+++ b/src/test/java/com/poiji/deserialize/ExcelConstructorTest.java
@@ -0,0 +1,64 @@
+package com.poiji.deserialize;
+
+import com.poiji.bind.Poiji;
+import com.poiji.deserialize.model.*;
+import com.poiji.exception.PoijiInstantiationException;
+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 java.util.Arrays.asList;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.*;
+
+@RunWith(Parameterized.class)
+public class ExcelConstructorTest {
+
+ private final String path;
+
+ public ExcelConstructorTest(final String path) {
+ this.path = path;
+ }
+
+ @Parameterized.Parameters
+ public static List excel() {
+ return asList(
+ "src/test/resources/excel-list.xlsx",
+ "src/test/resources/excel-list.xls",
+ "src/test/resources/excel-list.csv"
+ );
+ }
+
+ @Test
+ public void selectConstructorWithAnnotation() {
+ final List expected = asList(
+ new OneExcelConstructor(1),
+ new OneExcelConstructor(2)
+ );
+
+ final PoijiOptions options = PoijiOptions.PoijiOptionsBuilder.settings().headerCount(2).preferNullOverDefault(true).build();
+ final List read = Poiji.fromExcel(new File(path), OneExcelConstructor.class, options);
+ assertThat(read, equalTo(expected));
+ }
+
+ @Test
+ public void trySelectConstructorWithoutAnnotation() {
+ final PoijiOptions options = PoijiOptions.PoijiOptionsBuilder.settings().headerCount(2).preferNullOverDefault(true).build();
+ final PoijiInstantiationException exception = assertThrows(PoijiInstantiationException.class,
+ () -> Poiji.fromExcel(new File(path), NoExcelConstructor.class, options));
+ assertEquals("Several constructors were found in com.poiji.deserialize.model.NoExcelConstructor. Mark one of it with @ExcelConstructor please.", exception.getMessage());
+ }
+
+ @Test
+ public void trySelectConstructorWithManyAnnotations() {
+ final PoijiOptions options = PoijiOptions.PoijiOptionsBuilder.settings().headerCount(2).preferNullOverDefault(true).build();
+ final PoijiInstantiationException exception = assertThrows(PoijiInstantiationException.class,
+ () -> Poiji.fromExcel(new File(path), ManyExcelConstructor.class, options));
+ assertEquals("Several constructors are marked with @ExcelConstructor in com.poiji.deserialize.model.ManyExcelConstructor. Mark only one of it please.", exception.getMessage());
+ }
+
+}
diff --git a/src/test/java/com/poiji/deserialize/ReadExcelListTest.java b/src/test/java/com/poiji/deserialize/ReadExcelListTest.java
index f12d374..3e1b35e 100644
--- a/src/test/java/com/poiji/deserialize/ReadExcelListTest.java
+++ b/src/test/java/com/poiji/deserialize/ReadExcelListTest.java
@@ -1,11 +1,8 @@
package com.poiji.deserialize;
import com.poiji.bind.Poiji;
+import com.poiji.deserialize.model.*;
import com.poiji.util.ImmutableInstanceRegistrar;
-import com.poiji.deserialize.model.ListElement;
-import com.poiji.deserialize.model.ListElementImmutable;
-import com.poiji.deserialize.model.ListEntity;
-import com.poiji.deserialize.model.ListImmutable;
import com.poiji.option.PoijiOptions;
import java.io.File;
import java.io.FileNotFoundException;
@@ -71,4 +68,38 @@ public void readImmutable() {
assertThat(read, equalTo(expected));
}
+ @Test
+ public void readConstructorProperties() {
+ final ListElementConstructorProperties person11 = new ListElementConstructorProperties(2, "test", ListElementConstructorProperties.Gender.male, 10);
+ final ListElementConstructorProperties person12 = new ListElementConstructorProperties(2, "gogo", ListElementConstructorProperties.Gender.female,20);
+ final ListElementConstructorProperties person21 = new ListElementConstructorProperties(3, "abc", ListElementConstructorProperties.Gender.male, 30);
+ final ListElementConstructorProperties person22 = new ListElementConstructorProperties(3, "vivi", ListElementConstructorProperties.Gender.female, 40);
+ final ListElementConstructorProperties person23 = new ListElementConstructorProperties(3, "vava", ListElementConstructorProperties.Gender.female, 40);
+ final List expected = asList(
+ new ListConstructorProperties(1, asList(person11, person12)),
+ new ListConstructorProperties(2, asList(person21, person22, person23))
+ );
+
+ final PoijiOptions options = PoijiOptions.PoijiOptionsBuilder.settings().headerCount(2).preferNullOverDefault(true).build();
+ final List read = Poiji.fromExcel(new File(path), ListConstructorProperties.class, options);
+ assertThat(read, equalTo(expected));
+ }
+
+ @Test
+ public void readConstructorProperties2() {
+ final ListElementConstructorProperties2 person11 = new ListElementConstructorProperties2(2, "test", ListElementConstructorProperties2.Gender.male, 10, '0');
+ final ListElementConstructorProperties2 person12 = new ListElementConstructorProperties2(2, "gogo", ListElementConstructorProperties2.Gender.female,20, '0');
+ final ListElementConstructorProperties2 person21 = new ListElementConstructorProperties2(3, "abc", ListElementConstructorProperties2.Gender.male, 30, '0');
+ final ListElementConstructorProperties2 person22 = new ListElementConstructorProperties2(3, "vivi", ListElementConstructorProperties2.Gender.female, 40, '0');
+ final ListElementConstructorProperties2 person23 = new ListElementConstructorProperties2(3, "vava", ListElementConstructorProperties2.Gender.female, 40, '0');
+ final List expected = asList(
+ new ListConstructorProperties2(1, asList(person11, person12)),
+ new ListConstructorProperties2(2, asList(person21, person22, person23))
+ );
+
+ final PoijiOptions options = PoijiOptions.PoijiOptionsBuilder.settings().headerCount(2).preferNullOverDefault(true).build();
+ final List read = Poiji.fromExcel(new File(path), ListConstructorProperties2.class, options);
+ assertThat(read, equalTo(expected));
+ }
+
}
diff --git a/src/test/java/com/poiji/deserialize/model/ListConstructorProperties.java b/src/test/java/com/poiji/deserialize/model/ListConstructorProperties.java
new file mode 100644
index 0000000..1920940
--- /dev/null
+++ b/src/test/java/com/poiji/deserialize/model/ListConstructorProperties.java
@@ -0,0 +1,44 @@
+package com.poiji.deserialize.model;
+
+import com.poiji.annotation.ExcelCell;
+import com.poiji.annotation.ExcelList;
+
+import java.beans.ConstructorProperties;
+import java.util.List;
+import java.util.Objects;
+
+public final class ListConstructorProperties {
+
+ @ExcelCell(0)
+ private final Integer data;
+ @ExcelList(elementSize = 3, listStart = 1)
+ private final List elements;
+
+ @ConstructorProperties({"data", "elements"})
+ public ListConstructorProperties(Integer data, List elements) {
+ this.data = data;
+ this.elements = elements;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final ListConstructorProperties that = (ListConstructorProperties) o;
+ return Objects.equals(data, that.data) && Objects.equals(elements, that.elements);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(data, elements);
+ }
+
+ @Override
+ public String toString() {
+ return "ListEntity{" + "data=" + data + ", elements=" + elements + '}';
+ }
+}
diff --git a/src/test/java/com/poiji/deserialize/model/ListConstructorProperties2.java b/src/test/java/com/poiji/deserialize/model/ListConstructorProperties2.java
new file mode 100644
index 0000000..de227a7
--- /dev/null
+++ b/src/test/java/com/poiji/deserialize/model/ListConstructorProperties2.java
@@ -0,0 +1,44 @@
+package com.poiji.deserialize.model;
+
+import com.poiji.annotation.ExcelCell;
+import com.poiji.annotation.ExcelList;
+
+import java.beans.ConstructorProperties;
+import java.util.List;
+import java.util.Objects;
+
+public final class ListConstructorProperties2 {
+
+ @ExcelCell(0)
+ private final Integer data;
+ @ExcelList(elementSize = 3, listStart = 1)
+ private final List elements;
+
+ @ConstructorProperties({"elements", "elements", "redundant"})
+ public ListConstructorProperties2(Integer data, List elements) {
+ this.data = data;
+ this.elements = elements;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final ListConstructorProperties2 that = (ListConstructorProperties2) o;
+ return Objects.equals(data, that.data) && Objects.equals(elements, that.elements);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(data, elements);
+ }
+
+ @Override
+ public String toString() {
+ return "ListEntity{" + "data=" + data + ", elements=" + elements + '}';
+ }
+}
diff --git a/src/test/java/com/poiji/deserialize/model/ListElementConstructorProperties.java b/src/test/java/com/poiji/deserialize/model/ListElementConstructorProperties.java
new file mode 100644
index 0000000..2477779
--- /dev/null
+++ b/src/test/java/com/poiji/deserialize/model/ListElementConstructorProperties.java
@@ -0,0 +1,57 @@
+package com.poiji.deserialize.model;
+
+import com.poiji.annotation.ExcelCellName;
+import com.poiji.annotation.ExcelRow;
+
+import java.beans.ConstructorProperties;
+import java.util.Objects;
+
+public final class ListElementConstructorProperties {
+
+ @ExcelRow
+ private final int row;
+ @ExcelCellName("Name")
+ private final String name;
+ @ExcelCellName("Gender")
+ private final Gender gender;
+ @ExcelCellName("Age")
+ private final Integer age;
+
+ @ConstructorProperties({"now", "name", "gender", "age"})
+ public ListElementConstructorProperties(int row, String name, Gender gender, Integer age) {
+ this.row = row;
+ this.name = name;
+ this.gender = gender;
+ this.age = age;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final ListElementConstructorProperties that = (ListElementConstructorProperties) o;
+ return row == that.row && Objects.equals(name, that.name) && Objects.equals(gender, that.gender) &&
+ Objects.equals(age, that.age);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(row, name, gender, age);
+ }
+
+ @Override
+ public String toString() {
+ return "ListElement{" + "row=" + row + ", name='" + name + '\'' + ", gender='" + gender + '\'' + ", age='" +
+ age + '\'' + '}';
+ }
+
+ public enum Gender {
+
+ male, female
+
+ }
+}
diff --git a/src/test/java/com/poiji/deserialize/model/ListElementConstructorProperties2.java b/src/test/java/com/poiji/deserialize/model/ListElementConstructorProperties2.java
new file mode 100644
index 0000000..94e19f0
--- /dev/null
+++ b/src/test/java/com/poiji/deserialize/model/ListElementConstructorProperties2.java
@@ -0,0 +1,57 @@
+package com.poiji.deserialize.model;
+
+import com.poiji.annotation.ExcelCellName;
+import com.poiji.annotation.ExcelRow;
+
+import java.beans.ConstructorProperties;
+import java.util.Objects;
+
+public final class ListElementConstructorProperties2 {
+
+ @ExcelRow
+ private final int row;
+ @ExcelCellName("Name")
+ private final String name;
+ @ExcelCellName("Gender")
+ private final Gender gender;
+ @ExcelCellName("Age")
+ private final Integer age;
+
+ @ConstructorProperties({"wrong", "wrong", "gender"})
+ public ListElementConstructorProperties2(int row, String name, Gender gender, Integer age, char column) {
+ this.row = row;
+ this.name = name;
+ this.gender = gender;
+ this.age = age;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final ListElementConstructorProperties2 that = (ListElementConstructorProperties2) o;
+ return row == that.row && Objects.equals(name, that.name) && Objects.equals(gender, that.gender) &&
+ Objects.equals(age, that.age);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(row, name, gender, age);
+ }
+
+ @Override
+ public String toString() {
+ return "ListElement{" + "row=" + row + ", name='" + name + '\'' + ", gender='" + gender + '\'' + ", age='" +
+ age + '\'' + '}';
+ }
+
+ public enum Gender {
+
+ male, female
+
+ }
+}
diff --git a/src/test/java/com/poiji/deserialize/model/ManyExcelConstructor.java b/src/test/java/com/poiji/deserialize/model/ManyExcelConstructor.java
new file mode 100644
index 0000000..1b11be8
--- /dev/null
+++ b/src/test/java/com/poiji/deserialize/model/ManyExcelConstructor.java
@@ -0,0 +1,48 @@
+package com.poiji.deserialize.model;
+
+import com.poiji.annotation.ExcelCell;
+import com.poiji.annotation.ExcelConstructor;
+
+import java.util.Objects;
+
+public final class ManyExcelConstructor {
+
+ @ExcelCell(0)
+ private final Integer data;
+
+ public ManyExcelConstructor(Integer data, String arg2) {
+ throw new RuntimeException("Wrong constructor selected");
+ }
+
+ @ExcelConstructor
+ public ManyExcelConstructor(Integer data) {
+ this.data = data;
+ }
+
+ @ExcelConstructor
+ public ManyExcelConstructor() {
+ throw new RuntimeException("Wrong constructor selected");
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final ManyExcelConstructor that = (ManyExcelConstructor) o;
+ return Objects.equals(data, that.data);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(data);
+ }
+
+ @Override
+ public String toString() {
+ return "ListEntity{" + "data=" + data + '}';
+ }
+}
diff --git a/src/test/java/com/poiji/deserialize/model/NoExcelConstructor.java b/src/test/java/com/poiji/deserialize/model/NoExcelConstructor.java
new file mode 100644
index 0000000..606fb5f
--- /dev/null
+++ b/src/test/java/com/poiji/deserialize/model/NoExcelConstructor.java
@@ -0,0 +1,46 @@
+package com.poiji.deserialize.model;
+
+import com.poiji.annotation.ExcelCell;
+import com.poiji.annotation.ExcelConstructor;
+
+import java.util.Objects;
+
+public final class NoExcelConstructor {
+
+ @ExcelCell(0)
+ private final Integer data;
+
+ public NoExcelConstructor(Integer data, String arg2) {
+ throw new RuntimeException("Wrong constructor selected");
+ }
+
+ public NoExcelConstructor(Integer data) {
+ this.data = data;
+ }
+
+ public NoExcelConstructor() {
+ throw new RuntimeException("Wrong constructor selected");
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final NoExcelConstructor that = (NoExcelConstructor) o;
+ return Objects.equals(data, that.data);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(data);
+ }
+
+ @Override
+ public String toString() {
+ return "ListEntity{" + "data=" + data + '}';
+ }
+}
diff --git a/src/test/java/com/poiji/deserialize/model/OneExcelConstructor.java b/src/test/java/com/poiji/deserialize/model/OneExcelConstructor.java
new file mode 100644
index 0000000..7226569
--- /dev/null
+++ b/src/test/java/com/poiji/deserialize/model/OneExcelConstructor.java
@@ -0,0 +1,47 @@
+package com.poiji.deserialize.model;
+
+import com.poiji.annotation.ExcelCell;
+import com.poiji.annotation.ExcelConstructor;
+
+import java.util.Objects;
+
+public final class OneExcelConstructor {
+
+ @ExcelCell(0)
+ private final Integer data;
+
+ public OneExcelConstructor(Integer data, String arg2) {
+ throw new RuntimeException("Wrong constructor selected");
+ }
+
+ @ExcelConstructor
+ public OneExcelConstructor(Integer data) {
+ this.data = data;
+ }
+
+ public OneExcelConstructor() {
+ throw new RuntimeException("Wrong constructor selected");
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final OneExcelConstructor that = (OneExcelConstructor) o;
+ return Objects.equals(data, that.data);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(data);
+ }
+
+ @Override
+ public String toString() {
+ return "ListEntity{" + "data=" + data + '}';
+ }
+}
diff --git a/src/test/resources/ignore.xls b/src/test/resources/ignore.xls
index 9b037b7..5d1a937 100644
Binary files a/src/test/resources/ignore.xls and b/src/test/resources/ignore.xls differ
diff --git a/src/test/resources/ignore.xlsx b/src/test/resources/ignore.xlsx
index 6ddaf7a..6855322 100644
Binary files a/src/test/resources/ignore.xlsx and b/src/test/resources/ignore.xlsx differ
diff --git a/src/test/resources/write.xls b/src/test/resources/write.xls
index 5ceae6c..bf64ee0 100644
Binary files a/src/test/resources/write.xls and b/src/test/resources/write.xls differ
diff --git a/src/test/resources/writeStream.xls b/src/test/resources/writeStream.xls
index 1614c62..47192ab 100644
Binary files a/src/test/resources/writeStream.xls and b/src/test/resources/writeStream.xls differ