diff --git a/pom.xml b/pom.xml
index aa7a1d9c..0e0e5d7c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
4.0.0com.univocityunivocity-parsers
- 2.7.3-SNAPSHOT
+ 2.7.3univocity-parsersjarunivocity's open source parsers for processing different text formats using a consistent API
diff --git a/src/main/java/com/univocity/parsers/annotations/Validate.java b/src/main/java/com/univocity/parsers/annotations/Validate.java
index 92a4e4cf..9da99d92 100644
--- a/src/main/java/com/univocity/parsers/annotations/Validate.java
+++ b/src/main/java/com/univocity/parsers/annotations/Validate.java
@@ -74,4 +74,12 @@
* @return the sequence of disallowed values
*/
String[] noneOf() default {};
+
+ /**
+ * User provided implementations of {@link Validator} which will be executed
+ * in sequence after the validations specified in this annotation execute.
+ *
+ * @return custom classes to be used to validate any value associated with this field.
+ */
+ Class extends Validator>[] validators() default {};
}
diff --git a/src/main/java/com/univocity/parsers/annotations/helpers/AnnotationHelper.java b/src/main/java/com/univocity/parsers/annotations/helpers/AnnotationHelper.java
index d94f879c..d9c20882 100644
--- a/src/main/java/com/univocity/parsers/annotations/helpers/AnnotationHelper.java
+++ b/src/main/java/com/univocity/parsers/annotations/helpers/AnnotationHelper.java
@@ -125,7 +125,8 @@ private static Conversion getConversion(Class fieldType, AnnotatedElement target
String[] noneOf = AnnotationRegistry.getValue(target, validate, "noneOf", validate.noneOf());
String matches = AnnotationRegistry.getValue(target, validate, "matches", validate.matches());
- return Conversions.validate(nullable, allowBlanks, oneOf, noneOf, matches);
+ Class[] validators = AnnotationRegistry.getValue(target, validate, "validators", validate.validators());
+ return new ValidatedConversion(nullable, allowBlanks, oneOf, noneOf, matches, validators);
} else if (annType == EnumOptions.class) {
if (!fieldType.isEnum()) {
if (target == null) {
diff --git a/src/main/java/com/univocity/parsers/common/DefaultConversionProcessor.java b/src/main/java/com/univocity/parsers/common/DefaultConversionProcessor.java
index 5f394bd4..b3cc6380 100644
--- a/src/main/java/com/univocity/parsers/common/DefaultConversionProcessor.java
+++ b/src/main/java/com/univocity/parsers/common/DefaultConversionProcessor.java
@@ -130,13 +130,28 @@ public final Object[] applyConversions(String[] row, Context context) {
keepRow = applyConversionsByType(false, objectRow, convertedFlags);
}
- if (keepRow) {
+ if (keepRow && validateAllValues(objectRow)) {
return objectRow;
}
return null;
}
+ private boolean validateAllValues(Object[] objectRow) {
+ if (conversions != null && conversions.validatedIndexes != null) {
+ boolean keepRow = true;
+ for (int i = 0; keepRow && i < conversions.validatedIndexes.length && i < objectRow.length; i++) {
+ try {
+ conversions.executeValidations(i, objectRow[i]);
+ } catch (Throwable ex) {
+ keepRow = handleConversionError(ex, objectRow, i);
+ }
+ }
+ return keepRow;
+ }
+ return true;
+ }
+
/**
* Executes the sequences of reverse conversions defined using {@link DefaultConversionProcessor#convertFields(Conversion...)}, {@link DefaultConversionProcessor#convertIndexes(Conversion...)} and {@link DefaultConversionProcessor#convertAll(Conversion...)}, for every field in the given row.
*
@@ -180,7 +195,7 @@ public final boolean reverseConversions(boolean executeInReverseOrder, Object[]
keepRow = applyConversionsByType(true, row, convertedFlags);
}
- return keepRow;
+ return keepRow && validateAllValues(row);
}
private boolean applyConversionsByType(boolean reverse, Object[] row, boolean[] convertedFlags) {
diff --git a/src/main/java/com/univocity/parsers/common/fields/FieldConversionMapping.java b/src/main/java/com/univocity/parsers/common/fields/FieldConversionMapping.java
index a34b8f36..907b9ed6 100644
--- a/src/main/java/com/univocity/parsers/common/fields/FieldConversionMapping.java
+++ b/src/main/java/com/univocity/parsers/common/fields/FieldConversionMapping.java
@@ -31,6 +31,8 @@ public class FieldConversionMapping {
@SuppressWarnings("rawtypes")
private static final Conversion[] EMPTY_CONVERSION_ARRAY = new Conversion[0];
+ public int[] validatedIndexes;
+
/**
* This list contains the sequence of conversions applied to sets of fields over multiple calls.
*
It is shared by {@link FieldConversionMapping#fieldNameConversionMapping}, {@link FieldConversionMapping#fieldIndexConversionMapping} and {@link FieldConversionMapping#convertAllMapping}.
@@ -72,6 +74,8 @@ protected FieldSelector newFieldSelector() {
*/
private Map>> conversionsByIndex = Collections.emptyMap();
+ private Map> validationsByIndex = Collections.emptyMap();
+
/**
* Prepares the conversions registered in this object to be executed against a given sequence of fields
*
@@ -99,6 +103,36 @@ public void prepareExecution(boolean writing, String[] values) {
fieldEnumConversionMapping.prepareExecution(writing, next, conversionsByIndex, values);
convertAllMapping.prepareExecution(writing, next, conversionsByIndex, values);
}
+
+
+ Iterator>>> entryIterator = conversionsByIndex.entrySet().iterator();
+
+ while (entryIterator.hasNext()) {
+ Map.Entry>> e = entryIterator.next();
+ Iterator> it = e.getValue().iterator();
+ while (it.hasNext()) {
+ Conversion conversion = it.next();
+ if (conversion instanceof ValidatedConversion) {
+ if (validationsByIndex.isEmpty()) {
+ validationsByIndex = new TreeMap>();
+ }
+
+ it.remove();
+ List validations = validationsByIndex.get(e.getKey());
+ if (validations == null) {
+ validations = new ArrayList(1);
+ validationsByIndex.put(e.getKey(), validations);
+ }
+ validations.add((ValidatedConversion) conversion);
+ }
+ }
+
+ if (e.getValue().isEmpty()) {
+ entryIterator.remove();
+ }
+ }
+
+ validatedIndexes = ArgumentUtils.toIntArray(validationsByIndex.keySet());
}
/**
@@ -144,6 +178,20 @@ public FieldSet applyConversionsOnFieldEnums(Conversion... conv
return fieldEnumConversionMapping.registerConversions(conversions);
}
+ /**
+ * Applies any validations associated with a field at a given index in a record
+ * @param index The index of parsed value in a record
+ * @param value The value of the record at the given index
+ */
+ public void executeValidations(int index, Object value) {
+ List validations = validationsByIndex.get(index);
+ if (validations != null) {
+ for (int i = 0; i < validations.size(); i++) {
+ validations.get(i).execute(value);
+ }
+ }
+ }
+
/**
* Applies a sequence of conversions associated with an Object value at a given index in a record.
*
diff --git a/src/main/java/com/univocity/parsers/conversions/ValidatedConversion.java b/src/main/java/com/univocity/parsers/conversions/ValidatedConversion.java
index 474597c2..c55ecf88 100644
--- a/src/main/java/com/univocity/parsers/conversions/ValidatedConversion.java
+++ b/src/main/java/com/univocity/parsers/conversions/ValidatedConversion.java
@@ -16,6 +16,7 @@
package com.univocity.parsers.conversions;
+import com.univocity.parsers.annotations.helpers.*;
import com.univocity.parsers.common.*;
import java.util.*;
@@ -32,6 +33,7 @@ public class ValidatedConversion implements Conversion