Skip to content

Commit

Permalink
add ignoring errors modes
Browse files Browse the repository at this point in the history
  • Loading branch information
g0ddest committed Sep 30, 2023
1 parent e4604ed commit 55f162c
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 72 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,17 @@ List<Employee> parse = new FixedLength<Employee>()
.registerLineType(Employee.class);
```

### Ignoring errors

If there is errors on your line format there are two modes that you could skip these errors if you want to:

* `skipErroneousLines` — line with error will not be added to result.
* `skipErroneousFields` — fields with errors will be `null`.

In both cases warnings will be raised in logs.

By default, exception will be raised for entire process.

### Cases to use

In the case if you have 2 different records in one line and there is a split index you can add a method in your entity that should return index of the next record and mark it with annotation `SplitLineAfter`.
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ plugins {
}

group 'name.velikodniy.vitaliy'
version '0.8'
version '0.9'

repositories {
mavenLocal()
Expand Down
180 changes: 111 additions & 69 deletions src/main/java/name/velikodniy/vitaliy/fixedlength/FixedLength.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package name.velikodniy.vitaliy.fixedlength;

import java.util.Comparator;
import name.velikodniy.vitaliy.fixedlength.annotation.FixedField;
import name.velikodniy.vitaliy.fixedlength.annotation.FixedLine;
import name.velikodniy.vitaliy.fixedlength.annotation.SplitLineAfter;
Expand All @@ -12,15 +11,17 @@
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Scanner;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Scanner;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.Map;
import java.util.List;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
Expand All @@ -30,13 +31,17 @@

public class FixedLength<T> {

private static final Logger LOGGER = Logger.getLogger(FixedLength.class.getName());

private static final Map<
Class<? extends Serializable>,
Class<? extends Formatter<? extends Serializable>>
> FORMATTERS
= Formatter.getDefaultFormatters();
private final List<FixedFormatLine<? extends T>> lineTypes = new ArrayList<>();
private boolean skipUnknownLines = true;
private boolean skipErroneousFields = false;
private boolean skipErroneousLines = false;
private Charset charset = Charset.defaultCharset();
private String delimiterString = "\n";
private Pattern delimiter = Pattern.compile(delimiterString);
Expand Down Expand Up @@ -90,6 +95,16 @@ public FixedLength<T> stopSkipUnknownLines() {
return this;
}

public FixedLength<T> skipErroneousFields() {
skipErroneousFields = true;
return this;
}

public FixedLength<T> skipErroneousLines() {
skipErroneousLines = true;
return this;
}

public FixedLength<T> registerLineTypes(final List<Class<T>> lineClasses) {
lineTypes.addAll(
lineClasses.stream()
Expand Down Expand Up @@ -156,8 +171,8 @@ private T lineToObject(FixedFormatRecord fixedFormatRecord) {
} catch (NoSuchMethodException e) {
throw new FixedLengthException("No empty constructor in class", e);
} catch (IllegalAccessException
| InstantiationException
| InvocationTargetException e) {
| InstantiationException
| InvocationTargetException e) {
throw new FixedLengthException(
"Unable to instantiate " + clazz.getName(), e
);
Expand All @@ -176,21 +191,37 @@ private T lineToObject(FixedFormatRecord fixedFormatRecord) {
endOfFieldIndex
), fieldAnnotation.padding());
if (acceptFieldContent(str, fieldAnnotation)) {
field.setAccessible(true);

try {
field.set(
lineAsObject,
Formatter.instance(FORMATTERS, field.getType()).asObject(str, fieldAnnotation)
);
} catch (IllegalAccessException e) {
throw new FixedLengthException("Access to field failed", e);
}
fillField(field, lineAsObject, str, fieldAnnotation);
}
}
return lineAsObject;
}

private void fillField(Field field, T lineAsObject, String str, FixedField fieldAnnotation) {
field.setAccessible(true);

try {
field.set(
lineAsObject,
Formatter.instance(FORMATTERS, field.getType()).asObject(str, fieldAnnotation)
);
} catch (IllegalAccessException e) {
throw new FixedLengthException("Access to field failed", e);
} catch (Exception e) {
if (e instanceof FixedLengthException) {
throw e;
}
if (!skipErroneousFields) {
throw e;
}
LOGGER.warning(String.format(
"Skipping field of type %s with error in value %s",
field.getType(),
str
));
}
}

private boolean acceptFieldContent(String content, FixedField fieldAnnotation) {
if (content == null) {
return false;
Expand All @@ -199,38 +230,49 @@ private boolean acceptFieldContent(String content, FixedField fieldAnnotation) {
return false;
}
if (fieldAnnotation.ignore().isEmpty()) {
// No ignore cotent defined, accepting
// No ignore content defined, accepting
return true;
}
// Ignore cotent defined: accepting if not matching ignore regular expression
// Ignore content defined: accepting if not matching ignore regular expression
Pattern pattern = Pattern.compile(fieldAnnotation.ignore());
return !pattern.matcher(content).matches();
}

private List<T> lineToObjects(FixedFormatRecord fixedFormatRecord) {
T lineAsObject = this.lineToObject(fixedFormatRecord);
Method splitMethod = fixedFormatRecord.fixedFormatLine.splitAfterMethod;
if (splitMethod == null) {
return Collections.singletonList(lineAsObject);
}
int splitIndex;
try {
splitIndex = (Integer) splitMethod.invoke(lineAsObject);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new FixedLengthException("Access to method failed", e);
}
if (splitIndex <= 0 || splitIndex >= fixedFormatRecord.rawLine.length()) {
return Collections.singletonList(lineAsObject);
}
String subRawLine = fixedFormatRecord.rawLine.substring(splitIndex);
FixedFormatRecord subRecord = this.fixedFormatLine(subRawLine);
if (subRecord == null) {
return Collections.singletonList(lineAsObject);
T lineAsObject = this.lineToObject(fixedFormatRecord);
Method splitMethod = fixedFormatRecord.fixedFormatLine.splitAfterMethod;
if (splitMethod == null) {
return Collections.singletonList(lineAsObject);
}
int splitIndex;
try {
splitIndex = (Integer) splitMethod.invoke(lineAsObject);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new FixedLengthException("Access to method failed", e);
}
if (splitIndex <= 0 || splitIndex >= fixedFormatRecord.rawLine.length()) {
return Collections.singletonList(lineAsObject);
}
String subRawLine = fixedFormatRecord.rawLine.substring(splitIndex);
FixedFormatRecord subRecord = this.fixedFormatLine(subRawLine);
if (subRecord == null) {
return Collections.singletonList(lineAsObject);
}
List<T> lineAsObjects = new ArrayList<>();
lineAsObjects.add(lineAsObject);
lineAsObjects.addAll(lineToObjects(subRecord));
return lineAsObjects;
} catch (Exception e) {
if (e instanceof FixedLengthException) {
throw e;
}
if (!skipErroneousLines) {
throw e;
}
LOGGER.warning("Skipping line with error");
return Collections.emptyList();
}
List<T> lineAsObjects = new ArrayList<>();
lineAsObjects.add(lineAsObject);
lineAsObjects.addAll(lineToObjects(subRecord));
return lineAsObjects;
}

public List<T> parse(InputStream stream) throws FixedLengthException {
Expand Down Expand Up @@ -265,34 +307,34 @@ public String format(List<T> lines) {
for (T line : lines) {

Arrays.stream(line.getClass().getDeclaredFields())
.filter(
f ->
f.getAnnotation(FixedField.class) != null
)
.sorted(Comparator.comparingInt(f -> f.getAnnotation(FixedField.class).offset()))
.forEach(f -> {
FixedField fixedFieldAnnotation = f.getAnnotation(FixedField.class);

Formatter<T> formatter = (Formatter<T>) Formatter.instance(FORMATTERS, f.getType());

f.setAccessible(true);

T value;
try {
value = (T) f.get(line);
} catch (IllegalAccessException e) {
throw new FixedLengthException(e.getMessage(), e);
}

if (value != null) {
builder.append(
fixedFieldAnnotation.align().make(
formatter.asString(value, fixedFieldAnnotation),
fixedFieldAnnotation.length(),
fixedFieldAnnotation.padding())
);
}
});
.filter(
f ->
f.getAnnotation(FixedField.class) != null
)
.sorted(Comparator.comparingInt(f -> f.getAnnotation(FixedField.class).offset()))
.forEach(f -> {
FixedField fixedFieldAnnotation = f.getAnnotation(FixedField.class);

Formatter<T> formatter = (Formatter<T>) Formatter.instance(FORMATTERS, f.getType());

f.setAccessible(true);

T value;
try {
value = (T) f.get(line);
} catch (IllegalAccessException e) {
throw new FixedLengthException(e.getMessage(), e);
}

if (value != null) {
builder.append(
fixedFieldAnnotation.align().make(
formatter.asString(value, fixedFieldAnnotation),
fixedFieldAnnotation.length(),
fixedFieldAnnotation.padding())
);
}
});

if (lines.size() != currentLine++) {
builder.append(this.delimiterString);
Expand Down
40 changes: 38 additions & 2 deletions src/test/java/ParserTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,24 @@
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.format.DateTimeParseException;
import java.util.List;
import java.util.regex.Pattern;

import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.*;

class ParserTest {

String singleTypeExample =
"Joe1 Smith Developer 07500010012009\n" +
"Joe3 Smith Developer ";

String singleTypeWithErrorExample =
"Joe1 Smith Developer 07500010012009\n" +
"Joe1 Smith Developer 07500013012009";

String mixedTypesExample =
"EmplJoe1 Smith Developer 07500010012009\n" +
"CatSnowball 20200103\n" +
Expand Down Expand Up @@ -51,6 +55,38 @@ void testParseOneLineType() throws FixedLengthException {
assertEquals(2, parse.size());
}

@Test
@DisplayName("Parse as input stream with throwing exception when format erroneous fields")
void testParseThrowsExceptionOnInvalidFormat() throws FixedLengthException {
assertThrows(DateTimeParseException.class, () ->
new FixedLength<Row>()
.registerLineType(Employee.class)
.parse(new ByteArrayInputStream(singleTypeWithErrorExample.getBytes())));
}

@Test
@DisplayName("Parse as input stream with skipping format erroneous fields")
void testParseWithSkippingErroneousFields() throws FixedLengthException {
List<Row> parse = new FixedLength<Row>()
.registerLineType(Employee.class)
.skipErroneousFields()
.parse(new ByteArrayInputStream(singleTypeWithErrorExample.getBytes()));

assertEquals(2, parse.size());
assertNull(((Employee) parse.get(1)).hireDate);
}

@Test
@DisplayName("Parse as input stream with skipping format erroneous lines")
void testParseWithSkippingErroneousLines() throws FixedLengthException {
List<Row> parse = new FixedLength<Row>()
.registerLineType(Employee.class)
.skipErroneousLines()
.parse(new ByteArrayInputStream(singleTypeWithErrorExample.getBytes()));

assertEquals(1, parse.size());
}

@Test
@DisplayName("Parse as input stream with default charset and one line type")
void testParseOneLineTypeUS_ACII() throws FixedLengthException {
Expand Down

0 comments on commit 55f162c

Please sign in to comment.