Skip to content

Commit fa2b7c6

Browse files
committed
feat(validation): replace regex patterns with predicates for digit and email validation
1 parent 9816c21 commit fa2b7c6

29 files changed

Lines changed: 122 additions & 87 deletions

javalidation-jakarta-validator-processor/src/main/java/io/github/raniagus/javalidation/validator/processor/NullUnsafeWriter.java

Lines changed: 63 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -61,29 +61,22 @@ public Stream<String> imports() {
6161

6262
@Override
6363
public void writePropertiesTo(ValidationOutput out) {
64-
String fieldName = constantName(out.getVariable());
6564
switch (kind) {
6665
case BIG_DECIMAL, NUMBER, CHAR_SEQUENCE -> out.write("""
6766
private static final BigDecimal %s = new BigDecimal("%s");
68-
""".formatted(fieldName, value));
67+
""".formatted(constantName(out.getVariable()), value));
6968
case BIG_INTEGER -> out.write("""
7069
private static final BigInteger %s = new BigInteger("%s");
71-
""".formatted(fieldName, value));
70+
""".formatted(constantName(out.getVariable()), value));
7271
case BYTE, SHORT, INTEGER, LONG, DOUBLE, FLOAT -> { /* primitives: no caching needed */ }
7372
}
7473
}
7574

7675
@Override
7776
public void writeBodyTo(ValidationOutput out) {
78-
String fieldName = constantName(out.getVariable());
79-
String comparison = switch (kind) {
80-
case BIG_DECIMAL, BIG_INTEGER -> "%s.compareTo(%s) %s 0".formatted(out.getVariable(), fieldName, operator);
81-
case BYTE, SHORT, INTEGER, LONG, DOUBLE, FLOAT -> "%s %s %s".formatted(out.getVariable(), operator, value);
82-
case NUMBER, CHAR_SEQUENCE -> "new BigDecimal(%s.toString()).compareTo(%s) %s 0".formatted(out.getVariable(), fieldName, operator);
83-
};
8477
out.write("""
8578
if (!(%s)) {\
86-
""".formatted(comparison));
79+
""".formatted(comparison(out)));
8780
out.incrementIndentationLevel();
8881
out.write("""
8982
validation.addError("%s"%s);\
@@ -92,13 +85,24 @@ public void writeBodyTo(ValidationOutput out) {
9285
out.write("}");
9386
}
9487

88+
private String comparison(ValidationOutput out) {
89+
return switch (kind) {
90+
case BIG_DECIMAL, BIG_INTEGER -> "%s.compareTo(%s) %s 0".formatted(
91+
out.getVariable(), constantName(out.getVariable()), operator);
92+
case BYTE, SHORT, INTEGER, LONG, DOUBLE, FLOAT -> "%s %s %s".formatted(
93+
out.getVariable(), operator, value);
94+
case NUMBER, CHAR_SEQUENCE -> "new BigDecimal(%s.toString()).compareTo(%s) %s 0".formatted(
95+
out.getVariable(), constantName(out.getVariable()), operator);
96+
};
97+
}
98+
9599
private String constantName(String variable) {
96100
String opSuffix = switch (operator) {
97101
case ">=" -> "GE";
98102
case ">" -> "GT";
99103
case "<=" -> "LE";
100104
case "<" -> "LT";
101-
default -> operator;
105+
default -> operator;
102106
};
103107
String valueSuffix = value.toString().replace("-", "N").replace(".", "_");
104108
return variable.toUpperCase() + "_" + opSuffix + "_" + valueSuffix;
@@ -140,9 +144,15 @@ public Stream<String> imports() {
140144

141145
@Override
142146
public void writeBodyTo(ValidationOutput out) {
143-
out.write("""
144-
if (!(%s.%s(%s) == %s)) {\
145-
""".formatted(normalized(out.getVariable()), accessor, now(), result));
147+
if (result) {
148+
out.write("""
149+
if (!%s.%s(%s)) {\
150+
""".formatted(normalized(out.getVariable()), accessor, now()));
151+
} else {
152+
out.write("""
153+
if (%s.%s(%s)) {\
154+
""".formatted(normalized(out.getVariable()), accessor, now()));
155+
}
146156
out.incrementIndentationLevel();
147157
out.write("""
148158
validation.addError("%s");\
@@ -182,19 +192,17 @@ private String normalized(String variable) {
182192
}
183193
}
184194

185-
String EMAIL_REGEX = "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$";
186-
187195
record EmailPattern(@Nullable String regexp, List<String> flags, String message) implements NullUnsafeWriter {
188196
@Override
189197
public Stream<String> imports() {
190-
return Stream.of("java.util.regex.Pattern");
198+
return Stream.concat(
199+
Stream.of("io.github.raniagus.javalidation.validator.Predicates"),
200+
regexp != null ? Stream.of("java.util.regex.Pattern") : Stream.empty()
201+
);
191202
}
192203

193204
@Override
194205
public void writePropertiesTo(ValidationOutput out) {
195-
out.write("""
196-
private static final Pattern %s_EMAIL_PATTERN = Pattern.compile("%s");
197-
""".formatted(out.getVariable().toUpperCase(), EMAIL_REGEX));
198206
if (regexp != null) {
199207
out.write("""
200208
private static final Pattern %1$s_REGEXP_PATTERN = Pattern.compile("%2$s"%3$s);
@@ -206,12 +214,11 @@ public void writePropertiesTo(ValidationOutput out) {
206214
public void writeBodyTo(ValidationOutput out) {
207215
if (regexp == null) {
208216
out.write("""
209-
if (!%s_EMAIL_PATTERN.matcher(%s.toString()).matches()) {\
210-
""".formatted(out.getVariable().toUpperCase(), out.getVariable()));
217+
if (!Predicates.isEmail(%s)) {\
218+
""".formatted(out.getVariable()));
211219
} else {
212220
out.write("""
213-
if (!%1$s_EMAIL_PATTERN.matcher(%2$s.toString()).matches()
214-
|| !%1$s_REGEXP_PATTERN.matcher(%2$s.toString()).matches()) {\
221+
if (!Predicates.isEmail(%2$s) || !%1$s_REGEXP_PATTERN.matcher(%2$s).matches()) {\
215222
""".formatted(out.getVariable().toUpperCase(), out.getVariable()));
216223
}
217224
out.incrementIndentationLevel();
@@ -282,40 +289,49 @@ record Digits(
282289
@Override
283290
public Stream<String> imports() {
284291
return switch (kind) {
285-
case CHAR_SEQUENCE -> Stream.of("java.util.regex.Pattern");
286-
case BIG_DECIMAL -> Stream.empty();
287-
case BIG_INTEGER, NUMBER, BYTE, SHORT, INTEGER, LONG, DOUBLE, FLOAT -> Stream.of("java.math.BigDecimal");
292+
case CHAR_SEQUENCE -> Stream.of("io.github.raniagus.javalidation.validator.Predicates", "java.util.function.Predicate");
293+
case BIG_INTEGER, NUMBER, BYTE, SHORT, INTEGER, LONG, DOUBLE, FLOAT -> Stream.of(
294+
"io.github.raniagus.javalidation.validator.Predicates",
295+
"java.math.BigDecimal"
296+
);
297+
case BIG_DECIMAL -> Stream.of("io.github.raniagus.javalidation.validator.Predicates");
288298
};
289299
}
290300

291301
@Override
292302
public void writePropertiesTo(ValidationOutput out) {
293-
if (kind == NumericKind.CHAR_SEQUENCE) {
294-
String pattern = "^-?\\\\d{0," + integer + "}(\\\\.\\\\d{0," + fraction + "})?$";
295-
out.write("""
296-
private static final Pattern %s_DIGITS_PATTERN = Pattern.compile("%s");
297-
""".formatted(out.getVariable().toUpperCase(), pattern));
303+
switch (kind) {
304+
case CHAR_SEQUENCE ->
305+
out.write("""
306+
private static final Predicate<CharSequence> %s_DIGITS_PREDICATE = Predicates.digits(%d, %d);
307+
""".formatted(out.getVariable().toUpperCase(), integer, fraction));
308+
case BIG_INTEGER, NUMBER, BYTE, SHORT, INTEGER, LONG, DOUBLE, FLOAT, BIG_DECIMAL -> {
309+
// No Predicate to precompile
310+
}
298311
}
299312
}
300313

301314
@Override
302315
public void writeBodyTo(ValidationOutput out) {
303-
if (kind == NumericKind.CHAR_SEQUENCE) {
304-
out.write("""
305-
if (!%s_DIGITS_PATTERN.matcher(%s.toString()).matches()) {\
306-
""".formatted(out.getVariable().toUpperCase(), out.getVariable()));
307-
} else {
308-
String bdExpr = switch (kind) {
309-
case CHAR_SEQUENCE -> throw new IllegalStateException("CHAR_SEQUENCE should be handled separately");
310-
case BIG_DECIMAL -> "%s.stripTrailingZeros()".formatted(out.getVariable());
311-
case BIG_INTEGER, NUMBER -> "new BigDecimal(%s.toString()).stripTrailingZeros()".formatted(out.getVariable());
312-
case BYTE, SHORT, INTEGER, LONG, DOUBLE, FLOAT -> "BigDecimal.valueOf(%s)".formatted(out.getVariable());
313-
};
314-
out.write("var %s_bd = %s;".formatted(out.getVariable(), bdExpr));
315-
out.write("""
316-
if (!(%1$s_bd.precision() - %1$s_bd.scale() <= %2$s && Math.max(%1$s_bd.scale(), 0) <= %3$s)) {\
317-
""".formatted(out.getVariable(), integer, fraction));
316+
switch (kind) {
317+
case CHAR_SEQUENCE ->
318+
out.write("""
319+
if (!%s_DIGITS_PREDICATE.test(%s)) {\
320+
""".formatted(out.getVariable().toUpperCase(), out.getVariable()));
321+
case BIG_INTEGER, NUMBER ->
322+
out.write("""
323+
if (!Predicates.digits(new BigDecimal(%s.toString()), %d, %d)) {\
324+
""".formatted(out.getVariable(), integer, fraction));
325+
case BYTE, SHORT, INTEGER, LONG, DOUBLE, FLOAT ->
326+
out.write("""
327+
if (!Predicates.digits(BigDecimal.valueOf(%s), %d, %d)) {\
328+
""".formatted(out.getVariable(), integer, fraction));
329+
case BIG_DECIMAL ->
330+
out.write("""
331+
if (!Predicates.digits(%s, %d, %d)) {\
332+
""".formatted(out.getVariable(), integer, fraction));
318333
}
334+
319335
out.incrementIndentationLevel();
320336
out.write("""
321337
validation.addError("%s", %s, %s);\

javalidation-jakarta-validator-processor/src/test/java/test/jakarta/DigitsCharSequenceRecordValidator.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@
22

33
import io.github.raniagus.javalidation.Validation;
44
import io.github.raniagus.javalidation.validator.InitializableValidator;
5+
import io.github.raniagus.javalidation.validator.Predicates;
56
import io.github.raniagus.javalidation.validator.ValidatorsHolder;
6-
import java.util.regex.Pattern;
7+
import java.util.function.Predicate;
78
import javax.annotation.processing.Generated;
89
import org.jspecify.annotations.NullMarked;
910

1011
@NullMarked
1112
@Generated("io.github.raniagus.javalidation.validator.processor.ValidatorProcessor")
1213
public class DigitsCharSequenceRecordValidator implements InitializableValidator<DigitsCharSequenceRecord> {
13-
private static final Pattern VALUE_DIGITS_PATTERN = Pattern.compile("^-?\\d{0,5}(\\.\\d{0,2})?$");
14+
private static final Predicate<CharSequence> VALUE_DIGITS_PREDICATE = Predicates.digits(5, 2);
1415

1516
@Override
1617
public void initialize(ValidatorsHolder holder) {
@@ -21,7 +22,7 @@ public void validate(Validation validation, DigitsCharSequenceRecord root) {
2122
validation.withField("value", () -> {
2223
var value = root.value();
2324
if (value == null) return;
24-
if (!VALUE_DIGITS_PATTERN.matcher(value.toString()).matches()) {
25+
if (!VALUE_DIGITS_PREDICATE.test(value)) {
2526
validation.addError("io.github.raniagus.javalidation.constraints.Digits.message", 5, 2);
2627
}
2728
});

javalidation-jakarta-validator-processor/src/test/java/test/jakarta/DigitsNumberRecordValidator.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import io.github.raniagus.javalidation.Validation;
44
import io.github.raniagus.javalidation.validator.InitializableValidator;
5+
import io.github.raniagus.javalidation.validator.Predicates;
56
import io.github.raniagus.javalidation.validator.ValidatorsHolder;
67
import java.math.BigDecimal;
78
import javax.annotation.processing.Generated;
@@ -20,8 +21,7 @@ public void validate(Validation validation, DigitsNumberRecord root) {
2021
validation.withField("value", () -> {
2122
var value = root.value();
2223
if (value == null) return;
23-
var value_bd = new BigDecimal(value.toString()).stripTrailingZeros();
24-
if (!(value_bd.precision() - value_bd.scale() <= 5 && Math.max(value_bd.scale(), 0) <= 2)) {
24+
if (!Predicates.digits(new BigDecimal(value.toString()), 5, 2)) {
2525
validation.addError("io.github.raniagus.javalidation.constraints.Digits.message", 5, 2);
2626
}
2727
});

javalidation-jakarta-validator-processor/src/test/java/test/jakarta/DigitsPrimitiveRecordValidator.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import io.github.raniagus.javalidation.Validation;
44
import io.github.raniagus.javalidation.validator.InitializableValidator;
5+
import io.github.raniagus.javalidation.validator.Predicates;
56
import io.github.raniagus.javalidation.validator.ValidatorsHolder;
67
import java.math.BigDecimal;
78
import javax.annotation.processing.Generated;
@@ -10,7 +11,6 @@
1011
@NullMarked
1112
@Generated("io.github.raniagus.javalidation.validator.processor.ValidatorProcessor")
1213
public class DigitsPrimitiveRecordValidator implements InitializableValidator<DigitsPrimitiveRecord> {
13-
1414
@Override
1515
public void initialize(ValidatorsHolder holder) {
1616
}
@@ -20,8 +20,7 @@ public void validate(Validation validation, DigitsPrimitiveRecord root) {
2020
validation.withField("value", () -> {
2121
var value = root.value();
2222
if (value == null) return;
23-
var value_bd = BigDecimal.valueOf(value);
24-
if (!(value_bd.precision() - value_bd.scale() <= 5 && Math.max(value_bd.scale(), 0) <= 2)) {
23+
if (!Predicates.digits(BigDecimal.valueOf(value), 5, 2)) {
2524
validation.addError("io.github.raniagus.javalidation.constraints.Digits.message", 5, 2);
2625
}
2726
});

javalidation-jakarta-validator-processor/src/test/java/test/jakarta/DigitsRecordValidator.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import io.github.raniagus.javalidation.Validation;
44
import io.github.raniagus.javalidation.validator.InitializableValidator;
5+
import io.github.raniagus.javalidation.validator.Predicates;
56
import io.github.raniagus.javalidation.validator.ValidatorsHolder;
67
import javax.annotation.processing.Generated;
78
import org.jspecify.annotations.NullMarked;
@@ -19,8 +20,7 @@ public void validate(Validation validation, DigitsRecord root) {
1920
validation.withField("value", () -> {
2021
var value = root.value();
2122
if (value == null) return;
22-
var value_bd = value.stripTrailingZeros();
23-
if (!(value_bd.precision() - value_bd.scale() <= 5 && Math.max(value_bd.scale(), 0) <= 2)) {
23+
if (!Predicates.digits(value, 5, 2)) {
2424
validation.addError("io.github.raniagus.javalidation.constraints.Digits.message", 5, 2);
2525
}
2626
});

javalidation-jakarta-validator-processor/src/test/java/test/jakarta/EmailRecordValidator.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,14 @@
22

33
import io.github.raniagus.javalidation.Validation;
44
import io.github.raniagus.javalidation.validator.InitializableValidator;
5+
import io.github.raniagus.javalidation.validator.Predicates;
56
import io.github.raniagus.javalidation.validator.ValidatorsHolder;
6-
import java.util.regex.Pattern;
77
import javax.annotation.processing.Generated;
88
import org.jspecify.annotations.NullMarked;
99

1010
@NullMarked
1111
@Generated("io.github.raniagus.javalidation.validator.processor.ValidatorProcessor")
1212
public class EmailRecordValidator implements InitializableValidator<EmailRecord> {
13-
private static final Pattern VALUE_EMAIL_PATTERN = Pattern.compile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$");
14-
1513
@Override
1614
public void initialize(ValidatorsHolder holder) {
1715
}
@@ -21,7 +19,7 @@ public void validate(Validation validation, EmailRecord root) {
2119
validation.withField("value", () -> {
2220
var value = root.value();
2321
if (value == null) return;
24-
if (!VALUE_EMAIL_PATTERN.matcher(value.toString()).matches()) {
22+
if (!Predicates.isEmail(value)) {
2523
validation.addError("io.github.raniagus.javalidation.constraints.Email.message");
2624
}
2725
});

javalidation-jakarta-validator-processor/src/test/java/test/jakarta/EmailRegexpFlagsRecordValidator.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import io.github.raniagus.javalidation.Validation;
44
import io.github.raniagus.javalidation.validator.InitializableValidator;
5+
import io.github.raniagus.javalidation.validator.Predicates;
56
import io.github.raniagus.javalidation.validator.ValidatorsHolder;
67
import java.util.regex.Pattern;
78
import javax.annotation.processing.Generated;
@@ -10,7 +11,6 @@
1011
@NullMarked
1112
@Generated("io.github.raniagus.javalidation.validator.processor.ValidatorProcessor")
1213
public class EmailRegexpFlagsRecordValidator implements InitializableValidator<EmailRegexpFlagsRecord> {
13-
private static final Pattern VALUE_EMAIL_PATTERN = Pattern.compile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$");
1414
private static final Pattern VALUE_REGEXP_PATTERN = Pattern.compile(".*example.*", Pattern.CASE_INSENSITIVE);
1515

1616
@Override
@@ -22,8 +22,7 @@ public void validate(Validation validation, EmailRegexpFlagsRecord root) {
2222
validation.withField("value", () -> {
2323
var value = root.value();
2424
if (value == null) return;
25-
if (!VALUE_EMAIL_PATTERN.matcher(value.toString()).matches()
26-
|| !VALUE_REGEXP_PATTERN.matcher(value.toString()).matches()) {
25+
if (!Predicates.isEmail(value) || !VALUE_REGEXP_PATTERN.matcher(value).matches()) {
2726
validation.addError("io.github.raniagus.javalidation.constraints.Email.message");
2827
}
2928
});

javalidation-jakarta-validator-processor/src/test/java/test/jakarta/EmailRegexpRecordValidator.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import io.github.raniagus.javalidation.Validation;
44
import io.github.raniagus.javalidation.validator.InitializableValidator;
5+
import io.github.raniagus.javalidation.validator.Predicates;
56
import io.github.raniagus.javalidation.validator.ValidatorsHolder;
67
import java.util.regex.Pattern;
78
import javax.annotation.processing.Generated;
@@ -10,7 +11,6 @@
1011
@NullMarked
1112
@Generated("io.github.raniagus.javalidation.validator.processor.ValidatorProcessor")
1213
public class EmailRegexpRecordValidator implements InitializableValidator<EmailRegexpRecord> {
13-
private static final Pattern VALUE_EMAIL_PATTERN = Pattern.compile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$");
1414
private static final Pattern VALUE_REGEXP_PATTERN = Pattern.compile(".*example.*");
1515

1616
@Override
@@ -22,8 +22,7 @@ public void validate(Validation validation, EmailRegexpRecord root) {
2222
validation.withField("value", () -> {
2323
var value = root.value();
2424
if (value == null) return;
25-
if (!VALUE_EMAIL_PATTERN.matcher(value.toString()).matches()
26-
|| !VALUE_REGEXP_PATTERN.matcher(value.toString()).matches()) {
25+
if (!Predicates.isEmail(value) || !VALUE_REGEXP_PATTERN.matcher(value).matches()) {
2726
validation.addError("io.github.raniagus.javalidation.constraints.Email.message");
2827
}
2928
});

javalidation-jakarta-validator-processor/src/test/java/test/jakarta/FutureOrPresentRecordValidator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public void validate(Validation validation, FutureOrPresentRecord root) {
2020
validation.withField("value", () -> {
2121
var value = root.value();
2222
if (value == null) return;
23-
if (!(value.isBefore(Instant.now()) == false)) {
23+
if (value.isBefore(Instant.now())) {
2424
validation.addError("io.github.raniagus.javalidation.constraints.FutureOrPresent.message");
2525
}
2626
});

javalidation-jakarta-validator-processor/src/test/java/test/jakarta/FutureRecordValidator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public void validate(Validation validation, FutureRecord root) {
2020
validation.withField("value", () -> {
2121
var value = root.value();
2222
if (value == null) return;
23-
if (!(value.isAfter(Instant.now()) == true)) {
23+
if (!value.isAfter(Instant.now())) {
2424
validation.addError("io.github.raniagus.javalidation.constraints.Future.message");
2525
}
2626
});

0 commit comments

Comments
 (0)