Skip to content

Commit

Permalink
feat(core): support MultiValueMap mapping for columns using Regex
Browse files Browse the repository at this point in the history
Feature request

Refs: 304
  • Loading branch information
aerfus committed Feb 20, 2024
1 parent c4c38b7 commit cebe9a0
Show file tree
Hide file tree
Showing 9 changed files with 236 additions and 3 deletions.
20 changes: 20 additions & 0 deletions src/main/java/com/poiji/annotation/ExcelCellsJoinedByName.java
Original file line number Diff line number Diff line change
@@ -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();
}
70 changes: 67 additions & 3 deletions src/main/java/com/poiji/bind/mapping/HSSFUnmarshaller.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -247,6 +250,14 @@ private <T> 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();
}

Expand All @@ -271,6 +282,23 @@ private FieldAnnotationDetail getFieldColumn(final Field field) {
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<Integer> 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;
}

Expand All @@ -294,7 +322,7 @@ public Integer findTitleColumn(ExcelCellName excelCellName) {
}

private <T> void constructTypeValue(Row currentRow, T instance, Field field,
FieldAnnotationDetail annotationDetail) {
FieldAnnotationDetail annotationDetail) {
Cell cell = currentRow.getCell(annotationDetail.getColumn());

if (cell != null) {
Expand All @@ -309,7 +337,14 @@ private <T> 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());
Expand All @@ -331,11 +366,21 @@ private <T> void setFieldData(T instance, Field field, Object data) {
}
}

public void putFieldMultiValueMapData(Object instance, Field field, String columnName, Object o) {
try {
field.setAccessible(true);
MultiValuedMap<String, Object> multiValuedMap = (MultiValuedMap<String, Object>) field.get(instance);
multiValuedMap.put(columnName, o);
} catch (ClassCastException | IllegalAccessException e) {
throw new IllegalCastException("Unexpected cast type {" + o + "} of field" + field.getName());
}
}

private <T> T setFieldValuesFromRowIntoInstance(Row currentRow, Class<? super T> subclass, T instance) {
return subclass == null
? instance
: tailSetFieldValue(currentRow, subclass,
setFieldValuesFromRowIntoInstance(currentRow, subclass.getSuperclass(), instance));
setFieldValuesFromRowIntoInstance(currentRow, subclass.getSuperclass(), instance));
}

boolean skip(final Row currentRow, int skip) {
Expand All @@ -358,6 +403,10 @@ private static class FieldAnnotationDetail {
private boolean disabledCellFormat;
private boolean mandatoryCell;

private List<Integer> columns;

private boolean multiValueMap;

Integer getColumn() {
return column;
}
Expand Down Expand Up @@ -390,6 +439,21 @@ public void setMandatoryCell(boolean mandatoryCell) {
this.mandatoryCell = mandatoryCell;
}

public List<Integer> getColumns() {
return columns;
}

public void setColumns(List<Integer> columns) {
this.columns = columns;
}

public boolean isMultiValueMap() {
return multiValueMap;
}

public void setMultiValueMap(boolean multiValueMap) {
this.multiValueMap = multiValueMap;
}
}

}
14 changes: 14 additions & 0 deletions src/main/java/com/poiji/bind/mapping/PoijiHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -183,6 +184,19 @@ private boolean setValue(Field field, int column, String content, Object ins) {
}
}

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;
}

Expand Down
12 changes: 12 additions & 0 deletions src/main/java/com/poiji/util/ReflectUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<String, Object> multiValuedMap = (MultiValuedMap<String, Object>) field.get(instance);
multiValuedMap.put(columnName, o);
} catch (ClassCastException | IllegalAccessException e) {
throw new IllegalCastException("Unexpected cast type {" + o + "} of field" + field.getName());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.poiji.deserialize;

import com.poiji.bind.Poiji;
import com.poiji.deserialize.model.Album;
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 com.poiji.option.PoijiOptions.PoijiOptionsBuilder.settings;
import static com.poiji.util.Data.unmarshallingAlbums;
import static org.junit.Assert.assertEquals;

/**
* Test for Reading Excel columns with Regular expression (Regex)
*/
@RunWith(Parameterized.class)
public class ReadExcelWithRegexAndJoinValuesTest {
private final String path;
private final List<Album> expectedData;
private final PoijiOptions options;

public ReadExcelWithRegexAndJoinValuesTest(String path, List<Album> expectedData, PoijiOptions options) {
this.path = path;
this.expectedData = expectedData;
this.options = options;
}

@Parameterized.Parameters(name = "{index}: ({0})={1}")
public static Iterable<Object[]> queries() {
return List.of(new Object[][]{
{
"src/test/resources/regex/album.xlsx",
unmarshallingAlbums(),
settings().sheetName("Sheet 1").build()
},
{
"src/test/resources/regex/album.xls",
unmarshallingAlbums(),
settings().sheetName("Sheet 1").build()
},
});
}

@Test
public void shouldReadAlbumData() {
List<Album> actualData = Poiji.fromExcel(new File(path), Album.class, options);

assertEquals(expectedData, actualData);
}

}
47 changes: 47 additions & 0 deletions src/test/java/com/poiji/deserialize/model/Album.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.poiji.deserialize.model;

import com.poiji.annotation.ExcelCellsJoinedByName;
import org.apache.commons.collections4.MultiValuedMap;
import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;

import java.util.Objects;

/**
* An Album POJO.
*/
public class Album {
@ExcelCellsJoinedByName(expression = "Artist")
private MultiValuedMap<String, String> artists = new ArrayListValuedHashMap<>();

@ExcelCellsJoinedByName(expression = "Track[0-9]+")
private MultiValuedMap<String, String> tracks = new ArrayListValuedHashMap<>();

public void setArtists(MultiValuedMap<String, String> artists) {
this.artists = artists;
}

public void setTracks(MultiValuedMap<String, String> tracks) {
this.tracks = tracks;
}

@Override
public String toString() {
return "Album{" +
"artists=" + artists +
", tracks=" + tracks +
'}';
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Album album = (Album) o;
return Objects.equals(artists, album.artists) && Objects.equals(tracks, album.tracks);
}

@Override
public int hashCode() {
return Objects.hash(artists, tracks);
}
}
21 changes: 21 additions & 0 deletions src/test/java/com/poiji/util/Data.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package com.poiji.util;

import com.poiji.deserialize.model.Album;
import com.poiji.deserialize.model.InventoryData;
import com.poiji.deserialize.model.Student;
import com.poiji.deserialize.model.byid.Employee;
import com.poiji.deserialize.model.byid.Person;
import com.poiji.deserialize.model.byid.Sample;
import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;

import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -140,4 +142,23 @@ public static List<InventoryData> unmarshallingInventoryData() {

return List.of(record1, record2);
}


public static List<Album> unmarshallingAlbums() {
ArrayListValuedHashMap<String, String> artists = new ArrayListValuedHashMap<>();

artists.put("Artist", "Michael Jackson");
artists.put("Artist", "Lionel Richie");
artists.put("Artist", "Stevie Wonder");

ArrayListValuedHashMap<String, String> tracks = new ArrayListValuedHashMap<>();
tracks.put("Track1", "We are the World");
tracks.put("Track2", "We are the World (instrumental)");

Album album = new Album();
album.setArtists(artists);
album.setTracks(tracks);

return List.of(album);
}
}
Binary file added src/test/resources/regex/album.xls
Binary file not shown.
Binary file added src/test/resources/regex/album.xlsx
Binary file not shown.

0 comments on commit cebe9a0

Please sign in to comment.