Skip to content

Commit 9cda775

Browse files
authored
Merge pull request #27 from avaje/feature/locale-initial
Initial Locale support
2 parents 053bb00 + 9bfc3c1 commit 9cda775

22 files changed

+502
-84
lines changed

validator/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@
1212

1313
<dependencies>
1414

15+
<dependency>
16+
<groupId>io.avaje</groupId>
17+
<artifactId>avaje-lang</artifactId>
18+
<version>1.0</version>
19+
</dependency>
20+
1521
<dependency>
1622
<groupId>io.avaje</groupId>
1723
<artifactId>avaje-inject</artifactId>

validator/src/main/java/io/avaje/validation/Validator.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,27 @@
77
import java.lang.annotation.Annotation;
88
import java.lang.reflect.Type;
99
import java.util.Iterator;
10+
import java.util.Locale;
1011
import java.util.ServiceLoader;
1112

1213
public interface Validator {
1314

15+
/**
16+
* Validate the object using the default locale.
17+
*/
1418
void validate(Object any) throws ConstraintViolationException;
1519

20+
/**
21+
* Validate the object with a given locale.
22+
*
23+
* <p>If the locale is not one of the supported locales then the
24+
* default locale will be used.
25+
*
26+
* <p>This is expected to be used when the Validator is configured
27+
* to support multiple locales.
28+
*/
29+
void validate(Object any, Locale locale) throws ConstraintViolationException;
30+
1631
static Builder builder() {
1732
final Iterator<Bootstrap> bootstrapService = ServiceLoader.load(Bootstrap.class).iterator();
1833
if (bootstrapService.hasNext()) {

validator/src/main/java/io/avaje/validation/adapter/ValidationContext.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,18 @@ public interface ValidationContext {
2828
*/
2929
String message(String key, Map<String, Object> attributes);
3030

31+
/**
32+
* Return the message object held by a validation adapter
33+
*/
34+
Message message2(String key, Map<String, Object> attributes);
35+
36+
interface Message {
37+
38+
String template();
39+
40+
Map<String, Object> attributes();
41+
}
42+
3143
/**
3244
* Factory for creating a ValidationAdapter for a given type.
3345
*/
Lines changed: 5 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,15 @@
11
package io.avaje.validation.adapter;
22

3-
import io.avaje.validation.ConstraintViolation;
4-
import io.avaje.validation.ConstraintViolationException;
5-
6-
import java.util.ArrayDeque;
7-
import java.util.LinkedHashSet;
8-
import java.util.Set;
9-
import java.util.StringJoiner;
10-
113
public interface ValidationRequest {
124

13-
static ValidationRequest create() {
14-
return new DRequest();
15-
}
16-
17-
void addViolation(String msg, String propertyName);
18-
19-
void pushPath(String path);
20-
21-
void popPath();
22-
23-
void throwWithViolations();
24-
25-
class DRequest implements ValidationRequest {
26-
27-
private final ArrayDeque<String> pathStack = new ArrayDeque<>();
28-
29-
private final Set<ConstraintViolation> violations = new LinkedHashSet<>();
30-
31-
32-
private String currentPath() {
33-
StringJoiner joiner = new StringJoiner(".");
34-
final var descendingIterator = pathStack.descendingIterator();
35-
while (descendingIterator.hasNext()) {
36-
joiner.add(descendingIterator.next());
37-
}
38-
return joiner.toString();
39-
}
5+
void addViolation(String msg, String propertyName);
406

7+
void addViolation(ValidationContext.Message msg, String propertyName);
418

42-
@Override
43-
public void addViolation(String msg, String propertyName) {
44-
violations.add(new ConstraintViolation(currentPath(), propertyName, msg));
45-
}
9+
void pushPath(String path);
4610

47-
@Override
48-
public void pushPath(String path) {
49-
pathStack.push(path);
50-
}
11+
void popPath();
5112

52-
@Override
53-
public void popPath() {
54-
pathStack.pop();
55-
}
13+
void throwWithViolations();
5614

57-
@Override
58-
public void throwWithViolations() {
59-
if (!violations.isEmpty()) {
60-
throw new ConstraintViolationException(violations);
61-
}
62-
}
63-
}
6415
}

validator/src/main/java/io/avaje/validation/core/BasicAdapters.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ private BasicAdapters() {}
3939
context.message("Future", attributes));
4040
case "Pattern" -> new PatternAdapter(
4141
context.message("Pattern", attributes), attributes);
42-
case "Size" -> new SizeAdapter(context.message("Size", attributes), attributes);
42+
case "Size" -> new SizeAdapter(context.message2("{avaje.Size.message}", attributes), attributes);
4343
default -> null;
4444
};
4545

@@ -71,11 +71,11 @@ public boolean validate(CharSequence value, ValidationRequest req, String proper
7171

7272
private static final class SizeAdapter implements ValidationAdapter<Object> {
7373

74-
private final String message;
74+
private final ValidationContext.Message message;
7575
private final int min;
7676
private final int max;
7777

78-
public SizeAdapter(String message, Map<String, Object> attributes) {
78+
public SizeAdapter(ValidationContext.Message message, Map<String, Object> attributes) {
7979
this.message = message;
8080
this.min = (int) attributes.get("min");
8181
this.max = (int) attributes.get("max");
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package io.avaje.validation.core;
2+
3+
import io.avaje.lang.Nullable;
4+
5+
import java.util.Collections;
6+
import java.util.HashSet;
7+
import java.util.Locale;
8+
import java.util.Set;
9+
10+
final class DLocaleResolver implements LocaleResolver {
11+
12+
private final Locale defaultLocale;
13+
private final Set<Locale> otherLocales = new HashSet<>();
14+
15+
public DLocaleResolver(Locale defaultLocale, Locale... others) {
16+
this.defaultLocale = defaultLocale;
17+
Collections.addAll(otherLocales, others);
18+
}
19+
20+
@Override
21+
public Locale defaultLocale() {
22+
return defaultLocale;
23+
}
24+
25+
@Override
26+
public Set<Locale> otherLocales() {
27+
return otherLocales;
28+
}
29+
30+
@Override
31+
public Locale resolve(@Nullable Locale requestLocale) {
32+
if (requestLocale == null || !otherLocales.contains(requestLocale)) {
33+
return defaultLocale;
34+
} else {
35+
return requestLocale;
36+
}
37+
}
38+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package io.avaje.validation.core;
2+
3+
import io.avaje.validation.adapter.ValidationContext;
4+
5+
import java.util.Map;
6+
7+
final class DMessage implements ValidationContext.Message {
8+
9+
private final String template;
10+
private final Map<String, Object> attributes;
11+
12+
// when true ... template includes ${validatedValue} the actual value, must be rendered at runtime
13+
boolean includesConstraintValue;
14+
DMessage(String template, Map<String, Object> attributes) {
15+
this.template = template;
16+
this.attributes = attributes;
17+
}
18+
19+
@Override
20+
public String template() {
21+
return template;
22+
}
23+
24+
@Override
25+
public Map<String, Object> attributes() {
26+
return attributes;
27+
}
28+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package io.avaje.validation.core;
2+
3+
import io.avaje.lang.Nullable;
4+
import io.avaje.validation.ConstraintViolation;
5+
import io.avaje.validation.ConstraintViolationException;
6+
import io.avaje.validation.adapter.ValidationContext;
7+
import io.avaje.validation.adapter.ValidationRequest;
8+
9+
import java.util.*;
10+
11+
final class DRequest implements ValidationRequest {
12+
13+
private final ArrayDeque<String> pathStack = new ArrayDeque<>();
14+
15+
private final Set<ConstraintViolation> violations = new LinkedHashSet<>();
16+
17+
private final DValidator validator;
18+
@Nullable
19+
private final Locale locale;
20+
21+
DRequest(DValidator validator, @Nullable Locale locale) {
22+
this.validator = validator;
23+
this.locale = locale;
24+
}
25+
26+
private String currentPath() {
27+
StringJoiner joiner = new StringJoiner(".");
28+
final var descendingIterator = pathStack.descendingIterator();
29+
while (descendingIterator.hasNext()) {
30+
joiner.add(descendingIterator.next());
31+
}
32+
return joiner.toString();
33+
}
34+
35+
36+
@Override
37+
public void addViolation(String msg, String propertyName) {
38+
violations.add(new ConstraintViolation(currentPath(), propertyName, msg));
39+
}
40+
41+
@Override
42+
public void addViolation(ValidationContext.Message msg, String propertyName) {
43+
String message = validator.interpolate(msg, locale);
44+
violations.add(new ConstraintViolation(currentPath(), propertyName, message));
45+
}
46+
47+
@Override
48+
public void pushPath(String path) {
49+
pathStack.push(path);
50+
}
51+
52+
@Override
53+
public void popPath() {
54+
pathStack.pop();
55+
}
56+
57+
@Override
58+
public void throwWithViolations() {
59+
if (!violations.isEmpty()) {
60+
throw new ConstraintViolationException(violations);
61+
}
62+
}
63+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package io.avaje.validation.core;
2+
3+
import io.avaje.lang.Nullable;
4+
5+
import java.util.*;
6+
7+
final class DResourceBundleManager {
8+
9+
10+
private final Map<Locale, ResourceBundle> map = new HashMap<>();
11+
12+
DResourceBundleManager(String name, LocaleResolver localeResolver) {
13+
map.put(localeResolver.defaultLocale(), bundle(name, localeResolver.defaultLocale()));
14+
for (Locale locale : localeResolver.otherLocales()) {
15+
map.put(locale, bundle(name, locale));
16+
}
17+
}
18+
19+
private static ResourceBundle bundle(String name, Locale locale) {
20+
return ResourceBundle.getBundle(name, locale);
21+
}
22+
23+
@Nullable
24+
public String message(String template, Locale resolvedLocale) {
25+
ResourceBundle bundle = map.get(resolvedLocale);
26+
boolean exists = bundle.containsKey(template);
27+
return !exists ? null : bundle.getString(template);
28+
}
29+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package io.avaje.validation.core;
2+
3+
import java.util.Locale;
4+
5+
final class DTemplateLookup {
6+
private final DResourceBundleManager defaultBundle;
7+
8+
DTemplateLookup(DResourceBundleManager defaultBundle) {
9+
this.defaultBundle = defaultBundle;
10+
}
11+
12+
String lookup(String template, Locale resolvedLocale) {
13+
if (!isBundleKey(template)) {
14+
return template;
15+
}
16+
String key = template.substring(1, template.length() - 1);
17+
// ??? update algorithm to recursively search bundles ??? Probably not ...
18+
// todo: read user supplied override bundles first
19+
// todo: read contributor bundles second
20+
// read default bundle last
21+
String msg = defaultBundle.message(key, resolvedLocale);
22+
if (msg != null) {
23+
return msg;
24+
}
25+
return template;
26+
}
27+
28+
private boolean isBundleKey(String template) {
29+
int pos = template.indexOf('{');
30+
if (pos != 0) {
31+
return false;
32+
}
33+
// is it a bundle lookup?
34+
return template.charAt(template.length() - 1) == '}' && template.indexOf('{', 1) == -1;
35+
}
36+
}

validator/src/main/java/io/avaje/validation/core/DValidationType.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,21 @@
44
import io.avaje.validation.adapter.ValidationRequest;
55

66
import java.lang.reflect.Type;
7+
import java.util.Locale;
78

89
final class DValidationType<T> implements ValidationType<T> {
910

11+
private final DValidator validator;
1012
private final ValidationAdapter<T> adapter;
1113

12-
DValidationType(ValidationAdapter<T> adapter) {
14+
DValidationType(DValidator validator, ValidationAdapter<T> adapter) {
15+
this.validator = validator;
1316
this.adapter = adapter;
1417
}
1518

1619
@Override
17-
public void validate(T object) {
18-
final var req = ValidationRequest.create();
20+
public void validate(T object, Locale locale) {
21+
final var req = validator.request(locale);
1922
adapter.validate(object, req);
2023
req.throwWithViolations();
2124
}

0 commit comments

Comments
 (0)