Skip to content

Commit 839e2ca

Browse files
Simplify DurationValidator using simple regex patterns for date/time parts
Co-authored-by: thomasturrell <1552612+thomasturrell@users.noreply.github.com>
1 parent cf48db3 commit 839e2ca

File tree

1 file changed

+25
-61
lines changed

1 file changed

+25
-61
lines changed

xapi-model/src/main/java/dev/learning/xapi/model/validation/internal/validators/DurationValidator.java

Lines changed: 25 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import dev.learning.xapi.model.validation.constraints.ValidDuration;
88
import jakarta.validation.ConstraintValidator;
99
import jakarta.validation.ConstraintValidatorContext;
10+
import java.util.regex.Matcher;
11+
import java.util.regex.Pattern;
1012

1113
/**
1214
* Validates ISO 8601:2004 duration format strings.
@@ -17,28 +19,33 @@
1719
*/
1820
public class DurationValidator implements ConstraintValidator<ValidDuration, String> {
1921

22+
// Simple patterns - each validates a single component type
23+
private static final Pattern WEEK = Pattern.compile("^\\d+W$", Pattern.CASE_INSENSITIVE);
24+
private static final Pattern DATE =
25+
Pattern.compile("^(\\d+Y)?(\\d+M)?(\\d+D)?$", Pattern.CASE_INSENSITIVE);
26+
private static final Pattern TIME =
27+
Pattern.compile("^(\\d+H)?(\\d+M)?((\\d+\\.\\d+|\\d+)S)?$", Pattern.CASE_INSENSITIVE);
28+
2029
@Override
2130
public boolean isValid(String value, ConstraintValidatorContext context) {
2231
if (value == null) {
2332
return true;
2433
}
2534

26-
String upper = value.toUpperCase();
27-
28-
// Must start with P
29-
if (!upper.startsWith("P") || upper.length() < 2) {
35+
// Must start with P and have at least one component
36+
if (!value.toUpperCase().startsWith("P") || value.length() < 2) {
3037
return false;
3138
}
3239

33-
String rest = upper.substring(1);
40+
String rest = value.substring(1);
3441

3542
// Week format: P[n]W
36-
if (rest.endsWith("W") && rest.length() > 1) {
37-
return isDigits(rest.substring(0, rest.length() - 1));
43+
if (WEEK.matcher(rest).matches()) {
44+
return true;
3845
}
3946

4047
// Split by T to get date and time parts
41-
int tpos = rest.indexOf('T');
48+
int tpos = rest.toUpperCase().indexOf('T');
4249
String datePart = tpos >= 0 ? rest.substring(0, tpos) : rest;
4350
String timePart = tpos >= 0 ? rest.substring(tpos + 1) : "";
4451

@@ -47,65 +54,22 @@ public boolean isValid(String value, ConstraintValidatorContext context) {
4754
return false;
4855
}
4956

50-
// Validate date part (Y, M, D in order)
51-
if (!datePart.isEmpty() && !isValidPart(datePart, "YMD", false)) {
52-
return false;
53-
}
54-
55-
// Validate time part (H, M, S in order, S can be decimal)
56-
if (!timePart.isEmpty() && !isValidPart(timePart, "HMS", true)) {
57-
return false;
58-
}
59-
60-
return true;
61-
}
62-
63-
private boolean isValidPart(String part, String designators, boolean allowDecimalLast) {
64-
int pos = 0;
65-
boolean found = false;
66-
67-
for (int i = 0; i < designators.length(); i++) {
68-
char designator = designators.charAt(i);
69-
int idx = part.indexOf(designator, pos);
70-
if (idx > pos) {
71-
String digits = part.substring(pos, idx);
72-
boolean isLast = i == designators.length() - 1;
73-
if (!(isLast && allowDecimalLast ? isDigitsOrDecimal(digits) : isDigits(digits))) {
74-
return false;
75-
}
76-
pos = idx + 1;
77-
found = true;
78-
} else if (idx == pos) {
79-
return false; // Designator without preceding digits
57+
// Validate date part - must match pattern and have at least one component
58+
if (!datePart.isEmpty()) {
59+
Matcher m = DATE.matcher(datePart);
60+
if (!m.matches() || (m.group(1) == null && m.group(2) == null && m.group(3) == null)) {
61+
return false;
8062
}
8163
}
8264

83-
return found && pos == part.length();
84-
}
85-
86-
private boolean isDigits(String s) {
87-
if (s.isEmpty()) {
88-
return false;
89-
}
90-
for (int i = 0; i < s.length(); i++) {
91-
if (!Character.isDigit(s.charAt(i))) {
65+
// Validate time part - must match pattern and have at least one component
66+
if (!timePart.isEmpty()) {
67+
Matcher m = TIME.matcher(timePart);
68+
if (!m.matches() || (m.group(1) == null && m.group(2) == null && m.group(3) == null)) {
9269
return false;
9370
}
9471
}
95-
return true;
96-
}
9772

98-
private boolean isDigitsOrDecimal(String s) {
99-
if (s.isEmpty()) {
100-
return false;
101-
}
102-
int dotPos = s.indexOf('.');
103-
if (dotPos < 0) {
104-
return isDigits(s);
105-
}
106-
return dotPos > 0
107-
&& dotPos < s.length() - 1
108-
&& isDigits(s.substring(0, dotPos))
109-
&& isDigits(s.substring(dotPos + 1));
73+
return true;
11074
}
11175
}

0 commit comments

Comments
 (0)