Skip to content

Commit

Permalink
Merge f11e08c into 6e73e56
Browse files Browse the repository at this point in the history
  • Loading branch information
Brutus5000 authored Apr 30, 2020
2 parents 6e73e56 + f11e08c commit b7497d2
Show file tree
Hide file tree
Showing 9 changed files with 245 additions and 4 deletions.
33 changes: 33 additions & 0 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,39 @@ This is the excel file we want to parse:
The object corresponding to the first row of the excel sheet then has a map with `{ENCODING=mp3, BITRATE=256}`
and the one for the second row has `{ENCODING=flac, BITRATE=1500}`.

=== Example 9

Poiji 2.6.0 introduced the Option `namedHeaderMandatory`. If set to true, Poiji will check that all field annotated with `@ExcelCellName` must have a corresponding column in the Excel sheet. If any column is missing a `HeaderMissingException` will be thrown.

[source,java]
----
public class MusicTrack {
@ExcelCellName("ID")
private String employeeId;
@ExcelCellName("AUTHOR")
private String author;
}
----

This is the excel file we want to parse:

|===
|ID | Artist

|123923
|Joe Doe

|56437
|Jane Doe
|===

In the default setting of Poiji (`namedHeaderMandatory=false`), the author field will be null for both objects.
With `namedHeaderMandatory=true`, a `HeaderMissingException` will be thrown.


== Try with JShell

Since we have a new pedagogic tool, Java 9 REPL, you can try Poiji in JShell. Clone the repo and follow the steps below. JShell should open up a new jshell session once loading the startup scripts and the specified jars that must be in the classpath. You must first import and create related packages and classes before using Poiji. We are able to use directly Poiji and Employee classes because they are already imported from `jshell/snippets` with `try-with-jshell.sh`.
Expand Down
37 changes: 37 additions & 0 deletions src/main/java/com/poiji/bind/Poiji.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
package com.poiji.bind;

import com.poiji.annotation.ExcelCellName;
import com.poiji.bind.mapping.UnmarshallerHelper;
import com.poiji.exception.HeaderMissingException;
import com.poiji.exception.IllegalCastException;
import com.poiji.exception.InvalidExcelFileExtension;
import com.poiji.exception.PoijiExcelType;
import com.poiji.exception.PoijiException;
import com.poiji.option.PoijiOptions;
import com.poiji.option.PoijiOptions.PoijiOptionsBuilder;
import com.poiji.util.Files;
import com.poiji.util.ReflectUtil;

import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import static com.poiji.util.PoijiConstants.XLSX_EXTENSION;
import static com.poiji.util.PoijiConstants.XLS_EXTENSION;
Expand Down Expand Up @@ -233,4 +240,34 @@ private static Unmarshaller deserializer(final InputStream inputStream, PoijiExc
throw new InvalidExcelFileExtension("Invalid file extension (" + excelType + "), excepted .xls or .xlsx");
}
}

/**
* Validate that all headers specified via @ExcelCellName annotations are present in the list of header names.
* <p>
* Validation is only performed if it is set in the PoijiOptions
*
* @throws HeaderMissingException if one or more headers are missing
*/
public static <T> void validateMandatoryNameColumns(PoijiOptions options,
Class<T> modelType,
Collection<String> headerNames) {
if (options.getNamedHeaderMandatory()) {
Collection<ExcelCellName> excelCellNames = ReflectUtil.findRecursivePoijiAnnotations(modelType,
ExcelCellName.class);

BiPredicate<String, String> comparator = options.getCaseInsensitive()
? String::equalsIgnoreCase
: String::equals;

Set<String> missingHeaders = excelCellNames.stream()
.filter(excelCellName -> headerNames.stream()
.noneMatch(title -> comparator.test(excelCellName.value(), title)))
.map(ExcelCellName::value)
.collect(Collectors.toSet());

if (!missingHeaders.isEmpty()) {
throw new HeaderMissingException("Some headers are missing in the sheet: " + missingHeaders);
}
}
}
}
2 changes: 2 additions & 0 deletions src/main/java/com/poiji/bind/mapping/HSSFUnmarshaller.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.bind.Poiji;
import com.poiji.bind.Unmarshaller;
import com.poiji.config.Casting;
import com.poiji.exception.IllegalCastException;
Expand Down Expand Up @@ -67,6 +68,7 @@ public <T> void unmarshal(Class<T> type, Consumer<? super T> consumer) {
int maxPhysicalNumberOfRows = sheet.getPhysicalNumberOfRows() + 1 - skip;

loadColumnTitles(sheet, maxPhysicalNumberOfRows);
Poiji.validateMandatoryNameColumns(options, type, columnIndexPerTitle.keySet());

for (Row currentRow : sheet) {
if (!skip(currentRow, skip) && !isRowEmpty(currentRow)) {
Expand Down
13 changes: 12 additions & 1 deletion 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.bind.Poiji;
import com.poiji.config.Casting;
import com.poiji.exception.IllegalCastException;
import com.poiji.option.PoijiOptions;
Expand All @@ -15,7 +16,9 @@

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Stream;

Expand All @@ -42,6 +45,7 @@ final class PoijiHandler<T> implements SheetContentsHandler {
private Map<String, Object> fieldInstances;
private Map<Integer, Field> columnToField;
private Map<Integer, Field> columnToSuperClassField;
private Set<ExcelCellName> excelCellNames;

PoijiHandler(Class<T> type, PoijiOptions options, Consumer<? super T> consumer) {
this.type = type;
Expand All @@ -53,6 +57,7 @@ final class PoijiHandler<T> implements SheetContentsHandler {
titlePerColumnIndex = new HashMap<>();
columnToField = new HashMap<>();
columnToSuperClassField = new HashMap<>();
excelCellNames = new HashSet<>();
}

private void setFieldValue(String content, Class<? super T> subclass, int column) {
Expand Down Expand Up @@ -155,6 +160,7 @@ private boolean setValue(Field field, int column, String content, Object ins) {
} else {
ExcelCellName excelCellName = field.getAnnotation(ExcelCellName.class);
if (excelCellName != null) {
excelCellNames.add(excelCellName);
Class<?> fieldType = field.getType();
final String titleName = options.getCaseInsensitive()
? excelCellName.value().toLowerCase()
Expand Down Expand Up @@ -237,4 +243,9 @@ private String getTitleNameForMap(String cellContent, int columnIndex) {
public void headerFooter(String text, boolean isHeader, String tagName) {
//no-op
}
}

@Override
public void endSheet() {
Poiji.validateMandatoryNameColumns(options, type, columnIndexPerTitle.keySet());
}
}
11 changes: 11 additions & 0 deletions src/main/java/com/poiji/exception/HeaderMissingException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.poiji.exception;

/**
* Exception thrown if namedHeaderMandatory is set in the options, but a header specified in an @ExcelCellName
* is missing in the sheet.
*/
public class HeaderMissingException extends PoijiException {
public HeaderMissingException(String message) {
super(message);
}
}
19 changes: 18 additions & 1 deletion src/main/java/com/poiji/option/PoijiOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public final class PoijiOptions {
private int headerStart;
private String sheetName;
private boolean caseInsensitive;
private boolean namedHeaderMandatory;

private PoijiOptions() {
super();
Expand Down Expand Up @@ -197,6 +198,15 @@ public PoijiOptions setCaseInsensitive(final boolean caseInsensitive) {
return this;
}

public boolean getNamedHeaderMandatory() {
return namedHeaderMandatory;
}

public PoijiOptions setNamedHeaderMandatory(boolean namedHeaderMandatory) {
this.namedHeaderMandatory = namedHeaderMandatory;
return this;
}

public static class PoijiOptionsBuilder {

private int sheetIndex;
Expand All @@ -216,6 +226,7 @@ public static class PoijiOptionsBuilder {
private int limit = 0;
private String sheetName;
private boolean caseInsensitive;
private boolean namedHeaderMandatory;

private PoijiOptionsBuilder() {
}
Expand Down Expand Up @@ -307,7 +318,8 @@ public PoijiOptions build() {
.setHeaderStart(headerStart)
.setCasting(casting)
.setLimit(limit)
.setCaseInsensitive(caseInsensitive);
.setCaseInsensitive(caseInsensitive)
.setNamedHeaderMandatory(namedHeaderMandatory);
}

/**
Expand Down Expand Up @@ -474,6 +486,11 @@ public PoijiOptionsBuilder caseInsensitive(final boolean caseInsensitive) {
this.caseInsensitive = caseInsensitive;
return this;
}

public PoijiOptionsBuilder namedHeaderMandatory(boolean namedHeaderMandatory) {
this.namedHeaderMandatory = namedHeaderMandatory;
return this;
}
}

}
34 changes: 32 additions & 2 deletions src/main/java/com/poiji/util/ReflectUtil.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package com.poiji.util;

import java.lang.reflect.Constructor;

import com.poiji.annotation.ExcelCellRange;
import com.poiji.exception.PoijiInstantiationException;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class ReflectUtil {
public static <T> T newInstanceOf(Class<T> type) {
T obj;
Expand All @@ -19,4 +25,28 @@ public static <T> T newInstanceOf(Class<T> type) {

return obj;
}

/**
* Finds a particular annotation on a class and checks subtypes marked with ExcelCellRange recursively.
* <p>
* Recursively does not refer to super classes.
*/
public static <T, A extends Annotation> Collection<A> findRecursivePoijiAnnotations(Class<T> typeToInspect,
Class<A> annotationType) {
List<A> annotations = new ArrayList<>();

for (Field field : typeToInspect.getDeclaredFields()) {
Annotation excelCellRange = field.getAnnotation(ExcelCellRange.class);
if (excelCellRange != null) {
annotations.addAll(findRecursivePoijiAnnotations(field.getType(), annotationType));
} else {
A fieldAnnotation = field.getAnnotation(annotationType);
if (fieldAnnotation != null) {
annotations.add(fieldAnnotation);
}
}
}

return annotations;
}
}
55 changes: 55 additions & 0 deletions src/test/java/com/poiji/deserialize/MandatoryNamedColumnsTest.java
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.byid.Person;
import com.poiji.deserialize.model.byname.PersonByNameWithMissingColumn;
import com.poiji.exception.HeaderMissingException;
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 com.poiji.util.Data.unmarshallingPersons;

@RunWith(Parameterized.class)
public class MandatoryNamedColumnsTest {

private String path;
private List<Person> expectedPersonList;
private Class<?> clazz;

public MandatoryNamedColumnsTest(String path, List<Person> expectedPersonList, Class<?> clazz) {
this.path = path;
this.expectedPersonList = expectedPersonList;
this.clazz = clazz;
}

@Parameterized.Parameters(name = "{index}: ({0})={1}")
public static Iterable<Object[]> queries() {
return Arrays.asList(new Object[][]{
{"src/test/resources/person.xlsx", unmarshallingPersons(), PersonByNameWithMissingColumn.class},
{"src/test/resources/person.xls", unmarshallingPersons(), PersonByNameWithMissingColumn.class}
});
}

@Test
public void testExcelSuccess() {
Poiji.fromExcel(new File(path), PersonByNameWithMissingColumn.class, PoijiOptions.PoijiOptionsBuilder
.settings()
.namedHeaderMandatory(false)
.build());
}

@Test(expected = HeaderMissingException.class)
public void testExcelFail() {

Poiji.fromExcel(new File(path), PersonByNameWithMissingColumn.class, PoijiOptions.PoijiOptionsBuilder
.settings()
.namedHeaderMandatory(true)
.build());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.poiji.deserialize.model.byname;

import com.poiji.annotation.ExcelCellName;
import com.poiji.annotation.ExcelRow;

/**
* Created by ar on 9/03/2018.
*/
public class PersonByNameWithMissingColumn {

@ExcelCellName("Name")
protected String name;

@ExcelCellName("Address")
protected String address;

@ExcelCellName("Mobile")
protected String mobile;

@ExcelCellName("Email")
protected String email;

@ExcelCellName("This column will be missing")
protected String missingColumn;

@ExcelRow
protected int row;

public int getRow() {
return row;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public void setAddress(String address) {
this.address = address;
}

}

0 comments on commit b7497d2

Please sign in to comment.