Skip to content

Commit ec1b439

Browse files
Copilotozlerhakan
andcommitted
Add Java Records support to Poiji library
Co-authored-by: ozlerhakan <[email protected]>
1 parent baa6790 commit ec1b439

File tree

7 files changed

+407
-22
lines changed

7 files changed

+407
-22
lines changed

pom.xml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@
5252

5353
<maven-release-plugin.version>3.0.1</maven-release-plugin.version>
5454
<maven-enforcer-plugin.version>3.4.1</maven-enforcer-plugin.version>
55-
<maven.compiler.source>11</maven.compiler.source>
56-
<maven.compiler.target>11</maven.compiler.target>
57-
<java.version>11</java.version>
55+
<maven.compiler.source>17</maven.compiler.source>
56+
<maven.compiler.target>17</maven.compiler.target>
57+
<java.version>17</java.version>
5858
</properties>
5959

6060
<dependencies>
@@ -96,7 +96,7 @@
9696
<artifactId>maven-compiler-plugin</artifactId>
9797
<version>3.13.0</version>
9898
<configuration>
99-
<release>11</release>
99+
<release>17</release>
100100
</configuration>
101101
</plugin>
102102
<plugin>

src/main/java/com/poiji/bind/mapping/HSSFUnmarshaller.java

Lines changed: 133 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,139 @@ private String getTitleNameForMap(String cellContent, int columnIndex) {
172172
}
173173

174174
<T> T deserializeRowToInstance(Row currentRow, Class<T> type) {
175-
T instance = ReflectUtil.newInstanceOf(type);
176-
return setFieldValuesFromRowIntoInstance(currentRow, type, instance);
175+
if (ReflectUtil.isRecord(type)) {
176+
return deserializeRowToRecordInstance(currentRow, type);
177+
} else {
178+
T instance = ReflectUtil.newInstanceOf(type);
179+
return setFieldValuesFromRowIntoInstance(currentRow, type, instance);
180+
}
181+
}
182+
183+
private <T> T deserializeRowToRecordInstance(Row currentRow, Class<T> type) {
184+
Map<String, Object> recordValues = new HashMap<>();
185+
setFieldValuesFromRowIntoRecordMap(currentRow, type, recordValues);
186+
return ReflectUtil.newRecordInstance(type, recordValues);
187+
}
188+
189+
private <T> void setFieldValuesFromRowIntoRecordMap(Row currentRow, Class<? super T> subclass, Map<String, Object> recordValues) {
190+
if (subclass == null) {
191+
return;
192+
}
193+
setFieldValuesFromRowIntoRecordMap(currentRow, subclass.getSuperclass(), recordValues);
194+
tailSetFieldValueForRecord(currentRow, subclass, recordValues);
195+
}
196+
197+
private <T> void tailSetFieldValueForRecord(Row currentRow, Class<? super T> type, Map<String, Object> recordValues) {
198+
List<Integer> mappedColumnIndices = new ArrayList<>();
199+
List<Field> unknownCells = new ArrayList<>();
200+
List<PoijiRowSpecificException> errors = new ArrayList<>();
201+
202+
for (Field field : type.getDeclaredFields()) {
203+
if (field.getModifiers() == 25) {
204+
continue;
205+
}
206+
if (field.getAnnotation(ExcelRow.class) != null) {
207+
final int rowNum = currentRow.getRowNum();
208+
final Object data = casting.castValue(field, valueOf(rowNum), rowNum, -1, options);
209+
recordValues.put(field.getName(), data);
210+
} else if (field.getAnnotation(ExcelCellRange.class) != null) {
211+
Class<?> fieldType = field.getType();
212+
Object fieldInstance = ReflectUtil.newInstanceOf(fieldType);
213+
for (Field fieldField : fieldType.getDeclaredFields()) {
214+
mapColumns(currentRow, fieldInstance, mappedColumnIndices, errors, fieldField);
215+
}
216+
recordValues.put(field.getName(), fieldInstance);
217+
} else if (field.getAnnotation(ExcelUnknownCells.class) != null) {
218+
unknownCells.add(field);
219+
} else {
220+
mapColumnsForRecord(currentRow, recordValues, mappedColumnIndices, errors, field);
221+
}
222+
}
223+
if (!errors.isEmpty()) {
224+
throw new PoijiMultiRowException("Problem(s) occurred while reading data", errors);
225+
}
226+
227+
if (unknownCells.isEmpty()) {
228+
return;
229+
}
230+
231+
if (!indexToTitle.isEmpty()) {
232+
Map<String, String> excelUnknownCellsMap = StreamSupport
233+
.stream(Spliterators.spliteratorUnknownSize(currentRow.cellIterator(), Spliterator.ORDERED), false)
234+
.filter(cell -> !mappedColumnIndices.contains(cell.getColumnIndex()))
235+
.collect(Collectors.toMap(
236+
cell -> indexToTitle.get(cell.getColumnIndex()),
237+
Object::toString));
238+
unknownCells.forEach(field -> recordValues.put(field.getName(), excelUnknownCellsMap));
239+
} else {
240+
Map<String, String> excelUnknownCellsMap = StreamSupport
241+
.stream(Spliterators.spliteratorUnknownSize(currentRow.cellIterator(), Spliterator.ORDERED), false)
242+
.filter(cell -> !mappedColumnIndices.contains(cell.getColumnIndex()))
243+
.collect(Collectors.toMap(
244+
cell -> valueOf(cell.getColumnIndex()),
245+
Object::toString));
246+
unknownCells.forEach(field -> recordValues.put(field.getName(), excelUnknownCellsMap));
247+
}
248+
}
249+
250+
private void mapColumnsForRecord(
251+
Row currentRow,
252+
Map<String, Object> recordValues,
253+
List<Integer> mappedColumnIndices,
254+
List<PoijiRowSpecificException> errors,
255+
Field field) {
256+
try {
257+
mappedColumnIndices.add(tailSetFieldValueForRecord(currentRow, recordValues, field));
258+
} catch (PoijiRowSpecificException poijiRowException) {
259+
errors.add(poijiRowException);
260+
}
261+
}
262+
263+
private Integer tailSetFieldValueForRecord(Row currentRow, Map<String, Object> recordValues, Field field) {
264+
final FieldAnnotationDetail annotationDetail = getFieldColumn(field);
265+
if (annotationDetail.getColumn() != null) {
266+
constructTypeValueForRecord(currentRow, recordValues, field, annotationDetail);
267+
}
268+
269+
if (CollectionUtils.isNotEmpty(annotationDetail.getColumns())) {
270+
for (Integer column : annotationDetail.getColumns()) {
271+
annotationDetail.setColumn(column);
272+
constructTypeValueForRecord(currentRow, recordValues, field, annotationDetail);
273+
}
274+
}
275+
276+
return annotationDetail.getColumn();
277+
}
278+
279+
private <T> void constructTypeValueForRecord(Row currentRow, Map<String, Object> recordValues, Field field, FieldAnnotationDetail annotationDetail) {
280+
Cell cell = currentRow.getCell(annotationDetail.getColumn());
281+
282+
if (cell != null) {
283+
if (annotationDetail.isDisabledCellFormat()) {
284+
cell.setCellStyle(null);
285+
}
286+
String value;
287+
if (options.isRawData() && isCellNumeric(cell)) {
288+
value = NumberToTextConverter.toText(cell.getNumericCellValue());
289+
} else {
290+
value = dataFormatter.formatCellValue(cell, baseFormulaEvaluator);
291+
}
292+
Object data = casting.castValue(field, value, currentRow.getRowNum(), annotationDetail.getColumn(),
293+
options);
294+
295+
if (!annotationDetail.isMultiValueMap()) {
296+
recordValues.put(field.getName(), data);
297+
} else {
298+
String titleColumn = indexToTitle.get(annotationDetail.getColumn());
299+
titleColumn = titleColumn.replaceAll("@[0-9]+", "");
300+
// For records with MultiValuedMap, we can't use the same approach
301+
// Store it as a simple value for now
302+
recordValues.put(field.getName(), data);
303+
}
304+
} else if (annotationDetail.isMandatoryCell()) {
305+
throw new PoijiRowSpecificException(annotationDetail.getColumnName(), field.getName(),
306+
currentRow.getRowNum());
307+
}
177308
}
178309

179310
private <T> T tailSetFieldValue(Row currentRow, Class<? super T> type, T instance) {

src/main/java/com/poiji/bind/mapping/PoijiHandler.java

Lines changed: 68 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ final class PoijiHandler<T> implements SheetContentsHandler {
5151
private final Map<Integer, Field> columnToField;
5252
private final Map<Integer, Field> columnToSuperClassField;
5353
private final Set<ExcelCellName> excelCellNameAnnotations;
54+
// Record support
55+
private final boolean isRecord;
56+
private Map<String, Object> recordValues;
5457

5558
PoijiHandler(Class<T> type, PoijiOptions options, Consumer<? super T> consumer) {
5659
this.type = type;
@@ -64,6 +67,7 @@ final class PoijiHandler<T> implements SheetContentsHandler {
6467
columnToField = new HashMap<>();
6568
columnToSuperClassField = new HashMap<>();
6669
excelCellNameAnnotations = new HashSet<>();
70+
this.isRecord = ReflectUtil.isRecord(type);
6771
}
6872

6973
private void setFieldValue(String content, Class<? super T> subclass, int column) {
@@ -97,7 +101,11 @@ private boolean setValue(String content, Class<? super T> type, int column) {
97101
ExcelRow excelRow = field.getAnnotation(ExcelRow.class);
98102
if (excelRow != null) {
99103
Object o = casting.castValue(field, valueOf(internalRow), internalRow, column, options);
100-
ReflectUtil.setFieldData(field, o, instance);
104+
if (isRecord) {
105+
recordValues.put(field.getName(), o);
106+
} else {
107+
ReflectUtil.setFieldData(field, o, instance);
108+
}
101109
columnToField.put(-1, field);
102110
}
103111
ExcelCellRange range = field.getAnnotation(ExcelCellRange.class);
@@ -106,13 +114,17 @@ private boolean setValue(String content, Class<? super T> type, int column) {
106114
ins = getInstance(field);
107115
for (Field f : field.getType().getDeclaredFields()) {
108116
if (setValue(f, column, content, ins)) {
109-
ReflectUtil.setFieldData(field, ins, instance);
117+
if (isRecord) {
118+
recordValues.put(field.getName(), ins);
119+
} else {
120+
ReflectUtil.setFieldData(field, ins, instance);
121+
}
110122
columnToField.put(column, f);
111123
columnToSuperClassField.put(column, field);
112124
}
113125
}
114126
} else {
115-
if (setValue(field, column, content, instance)) {
127+
if (setValue(field, column, content, isRecord ? null : instance)) {
116128
columnToField.put(column, field);
117129
}
118130
}
@@ -123,12 +135,22 @@ private boolean setValue(String content, Class<? super T> type, int column) {
123135
if (!columnToField.containsKey(column)) {
124136
try {
125137
Map<String, String> excelUnknownCellsMap;
126-
field.setAccessible(true);
127-
if (field.get(instance) == null) {
128-
excelUnknownCellsMap = new HashMap<>();
129-
ReflectUtil.setFieldData(field, excelUnknownCellsMap, instance);
138+
if (isRecord) {
139+
// For records, we need to get or create the map from recordValues
140+
if (recordValues.containsKey(field.getName())) {
141+
excelUnknownCellsMap = (Map<String, String>) recordValues.get(field.getName());
142+
} else {
143+
excelUnknownCellsMap = new HashMap<>();
144+
recordValues.put(field.getName(), excelUnknownCellsMap);
145+
}
130146
} else {
131-
excelUnknownCellsMap = (Map<String, String>) field.get(instance);
147+
field.setAccessible(true);
148+
if (field.get(instance) == null) {
149+
excelUnknownCellsMap = new HashMap<>();
150+
ReflectUtil.setFieldData(field, excelUnknownCellsMap, instance);
151+
} else {
152+
excelUnknownCellsMap = (Map<String, String>) field.get(instance);
153+
}
132154
}
133155
String index = indexToTitle.get(column);
134156
if (index == null) {
@@ -146,28 +168,46 @@ private boolean setValue(String content, Class<? super T> type, int column) {
146168
if (columnToField.containsKey(-1)) {
147169
Field field = columnToField.get(-1);
148170
Object o = casting.castValue(field, valueOf(internalRow), internalRow, column, options);
149-
ReflectUtil.setFieldData(field, o, instance);
171+
if (isRecord) {
172+
recordValues.put(field.getName(), o);
173+
} else {
174+
ReflectUtil.setFieldData(field, o, instance);
175+
}
150176
}
151177
if (columnToField.containsKey(column) && columnToSuperClassField.containsKey(column)) {
152178
Field field = columnToField.get(column);
153179
Object ins;
154180
ins = getInstance(columnToSuperClassField.get(column));
155181
if (setValue(field, column, content, ins)) {
156-
ReflectUtil.setFieldData(columnToSuperClassField.get(column), ins, instance);
182+
if (isRecord) {
183+
recordValues.put(columnToSuperClassField.get(column).getName(), ins);
184+
} else {
185+
ReflectUtil.setFieldData(columnToSuperClassField.get(column), ins, instance);
186+
}
157187
return true;
158188
}
159-
return setValue(field, column, content, instance);
189+
return setValue(field, column, content, isRecord ? null : instance);
160190
}
161191
return false;
162192
}
163193

194+
private void setFieldValue(Field field, Object value, Object ins) {
195+
if (isRecord && ins == null) {
196+
// For records, store value in recordValues map
197+
recordValues.put(field.getName(), value);
198+
} else {
199+
// For regular classes, set field directly
200+
ReflectUtil.setFieldData(field, value, ins);
201+
}
202+
}
203+
164204
private boolean setValue(Field field, int column, String content, Object ins) {
165205
ExcelCell index = field.getAnnotation(ExcelCell.class);
166206

167207
if (index != null) {
168208
if (column == index.value()) {
169209
Object o = casting.castValue(field, content, internalRow, column, options);
170-
ReflectUtil.setFieldData(field, o, ins);
210+
setFieldValue(field, o, ins);
171211
return true;
172212
}
173213
}
@@ -179,7 +219,7 @@ private boolean setValue(Field field, int column, String content, Object ins) {
179219
// Fix both columns mapped to name passing this condition below
180220
if (titleColumn != null && titleColumn == column) {
181221
Object o = casting.castValue(field, content, internalRow, column, options);
182-
ReflectUtil.setFieldData(field, o, ins);
222+
setFieldValue(field, o, ins);
183223
return true;
184224
}
185225
}
@@ -192,7 +232,12 @@ private boolean setValue(Field field, int column, String content, Object ins) {
192232
Pattern pattern = Pattern.compile(expression);
193233
if (pattern.matcher(titleColumn).matches()) {
194234
Object o = casting.castValue(field, content, internalRow, column, options);
195-
ReflectUtil.putFieldMultiValueMapData(field, titleColumn, o, ins);
235+
if (isRecord && ins == null) {
236+
// For records, MultiValuedMap is not supported in the same way
237+
recordValues.put(field.getName(), o);
238+
} else {
239+
ReflectUtil.putFieldMultiValueMapData(field, titleColumn, o, ins);
240+
}
196241
return true;
197242
}
198243
}
@@ -223,8 +268,12 @@ public Integer findTitleColumn(ExcelCellName excelCellName) {
223268
public void startRow(int rowNum) {
224269
if (rowNum + 1 > options.skip()) {
225270
internalCount += 1;
226-
instance = ReflectUtil.newInstanceOf(type);
227-
fieldInstances = new HashMap<>();
271+
if (isRecord) {
272+
recordValues = new HashMap<>();
273+
} else {
274+
instance = ReflectUtil.newInstanceOf(type);
275+
fieldInstances = new HashMap<>();
276+
}
228277
}
229278
}
230279

@@ -234,6 +283,9 @@ public void endRow(int rowNum) {
234283
return;
235284

236285
if (rowNum + 1 > options.skip()) {
286+
if (isRecord) {
287+
instance = ReflectUtil.newRecordInstance(type, recordValues);
288+
}
237289
consumer.accept(instance);
238290
}
239291
}

0 commit comments

Comments
 (0)