Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Now write default annotation values to adapter Attributes #33

Merged
merged 3 commits into from
May 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions blackbox-test/src/test/java/example/avaje/ACustomer.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package example.avaje;

import io.avaje.validation.constraints.NotBlank;
import io.avaje.validation.constraints.Size;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

@Valid
public class ACustomer {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package io.avaje.validation.constraints;

import java.lang.annotation.*;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
Expand All @@ -10,9 +15,9 @@

Class<?>[] groups() default {};

int min();
int min() default 0;

int max();
int max() default Integer.MAX_VALUE;

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
import static java.util.stream.Collectors.joining;

import java.util.List;
import java.util.Optional;

import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.ElementFilter;

final class AnnotationUtil {
private AnnotationUtil() {}
Expand All @@ -20,12 +23,22 @@ public static String getAnnotationAttributMap(AnnotationMirror annotationMirror)
patternOp.ifPresent(p -> pattern(sb, p));
return sb.toString();
}
for (final var entry : annotationMirror.getElementValues().entrySet()) {

for (final ExecutableElement member :
ElementFilter.methodsIn(
annotationMirror.getAnnotationType().asElement().getEnclosedElements())) {

final var value =
Optional.<AnnotationValue>ofNullable(annotationMirror.getElementValues().get(member))
.orElseGet(member::getDefaultValue);
if (value == null) {
continue;
}
if (!first) {
sb.append(", ");
}
sb.append("\"" + entry.getKey().getSimpleName() + "\"").append(",");
writeVal(sb, entry.getValue());
sb.append("\"" + member.getSimpleName() + "\"").append(",");
writeVal(sb, value);
first = false;
}
sb.append(")");
Expand Down Expand Up @@ -65,8 +78,7 @@ private static void writeVal(final StringBuilder sb, final AnnotationValue annot
}
sb.append(")");
// Handle enum values
} else if (value instanceof VariableElement) {
final var element = (VariableElement) value;
} else if (value instanceof final VariableElement element) {
sb.append(element.asType().toString() + "." + element.toString());
// handle annotation values
} else if (value instanceof AnnotationMirror) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,15 @@ public interface ValidationContext {
*/
String message(String key, Map<String, Object> attributes);

/**
* Return the message object held by a validation adapter
*/
Message message2(String key, Map<String, Object> attributes);
/**
* Return the message object held by a validation adapter
*/
Message message2(Map<String, Object> attributes);

/**
* Return the message object held by a validation adapter
*/
Message message2(String defaultKey, Map<String, Object> attributes);

interface Message {

Expand Down
36 changes: 25 additions & 11 deletions validator/src/main/java/io/avaje/validation/core/DValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import io.avaje.lang.Nullable;
Expand All @@ -32,7 +37,7 @@ final class DValidator implements Validator, ValidationContext {
MessageInterpolator interpolator, LocaleResolver localeResolver) {
this.localeResolver = localeResolver;

var defaultResourceBundle = new DResourceBundleManager("io.avaje.validation.Messages", localeResolver);
final var defaultResourceBundle = new DResourceBundleManager("io.avaje.validation.Messages", localeResolver);
this.templateLookup = new DTemplateLookup(defaultResourceBundle);

this.interpolator = interpolator;
Expand Down Expand Up @@ -74,13 +79,22 @@ public String message(String key, Map<String, Object> attributes) {
return msg;
}

@Override
public Message message2(Map<String, Object> attributes) {
final String keyOrTemplate = (String)attributes.get("message");

// if configured to support only 1 Locale then we can do the lookup and message translation once and early
// otherwise we defer as the final message is locale specific
return new DMessage(keyOrTemplate, attributes);
}

@Override
public Message message2(String defaultKey, Map<String, Object> attributes) {
String keyOrTemplate = (String)attributes.get("message");
if (keyOrTemplate == null) {
// lookup default message for the given key
keyOrTemplate = defaultKey;
}
// lookup default message for the given key
keyOrTemplate = defaultKey;
}
// if configured to support only 1 Locale then we can do the lookup and message translation once and early
// otherwise we defer as the final message is locale specific
return new DMessage(keyOrTemplate, attributes);
Expand Down Expand Up @@ -119,14 +133,14 @@ ValidationRequest request(@Nullable Locale locale) {

String interpolate(Message msg, Locale requestLocale) {
// resolve the locale to use to produce the message
Locale locale = localeResolver.resolve(requestLocale);
final Locale locale = localeResolver.resolve(requestLocale);
// lookup in resource bundles using resolved locale and template
String template = templateLookup.lookup(msg.template(), locale);
final String template = templateLookup.lookup(msg.template(), locale);
// translate the template using msg attributes
Map<String, Object> attributes = msg.attributes();
Set<Map.Entry<String, Object>> entries = attributes.entrySet();
final Map<String, Object> attributes = msg.attributes();
final Set<Map.Entry<String, Object>> entries = attributes.entrySet();
String result = template;
for (Map.Entry<String, Object> entry : entries) {
for (final Map.Entry<String, Object> entry : entries) {
// needs work here to improve functionality, support local specific value formatting eg Duration Max
result = result.replace('{' + entry.getKey() + '}', String.valueOf(entry.getValue()));
}
Expand Down Expand Up @@ -188,7 +202,7 @@ public DValidator build() {
registerComponents();

//todo: sort out LocaleResolver initialisation, just hard coded EN and DE for now ...
LocaleResolver localeResolver = new DLocaleResolver(Locale.ENGLISH, Locale.GERMAN);
final LocaleResolver localeResolver = new DLocaleResolver(Locale.ENGLISH, Locale.GERMAN);
final var interpolator =
ServiceLoader.load(MessageInterpolator.class)
.findFirst()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package io.avaje.validation.core.adapters;

import java.lang.reflect.Array;
import java.util.*;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.regex.Pattern;

Expand All @@ -15,29 +18,21 @@ private BasicAdapters() {}

public static final ValidationContext.AnnotationFactory FACTORY = (annotationType, context, attributes) ->
switch (annotationType.getSimpleName()) {
case "Email" -> new EmailAdapter(context.message("Email", attributes), attributes);
case "Null" -> new NullAdapter(context.message2("{avaje.Null.message}", attributes));
case "NotNull", "NonNull" -> new NotNullAdapter(context.message2("{avaje.NotNull.message}", attributes));
case "AssertTrue" -> new AssertBooleanAdapter(context.message2("{avaje.AssertTrue.message}", attributes), false);
case "AssertFalse" -> new AssertBooleanAdapter(context.message2("{avaje.AssertFalse.message}", attributes), true);
case "NotBlank" -> new NotBlankAdapter(context.message2("{avaje.NotBlank.message}", attributes));
case "NotEmpty" -> new NotEmptyAdapter(context.message2("{avaje.NotEmpty.message}", attributes));
case "Past" -> new FuturePastAdapter(context.message("Past", attributes), true, false);
case "PastOrPresent" -> new FuturePastAdapter(context.message("PastOrPresent", attributes), true, true);
case "Future" -> new FuturePastAdapter(context.message("Future", attributes), false, false);
case "FutureOrPresent" -> new FuturePastAdapter(context.message("FutureOrPresent", attributes), false, true);
case "Pattern" -> new PatternAdapter(context.message2("{avaje.Pattern.message}", attributes), attributes);
case "Size" -> createSize(context, attributes);
default -> null;
};

private static ValidationAdapter<?> createSize(ValidationContext context, Map<String, Object> attributes) {
if (!attributes.containsKey("min")) {
attributes = new LinkedHashMap<>(attributes);
attributes.put("min", 0);
}
return new SizeAdapter(context.message2("{avaje.Size.message}", attributes), attributes);
}
case "Email" -> new EmailAdapter(context.message("Email", attributes), attributes);
case "Null" -> new NullAdapter(context.message2("{avaje.validation.constraints.Null.message}", attributes));
case "NotNull", "NonNull" -> new NotNullAdapter(context.message2("{avaje.validation.constraints.NotNull.message}", attributes));
case "AssertTrue" -> new AssertBooleanAdapter(context.message2("{avaje.validation.constraints.AssertTrue.message}", attributes), false);
case "AssertFalse" -> new AssertBooleanAdapter(context.message2("{avaje.validation.constraints.AssertFalse.message}", attributes), true);
case "NotBlank" -> new NotBlankAdapter(context.message2("{avaje.validation.constraints.NotBlank.message}", attributes));
case "NotEmpty" -> new NotEmptyAdapter(context.message2("{avaje.validation.constraints.NotEmpty.message}", attributes));
case "Past" -> new FuturePastAdapter(context.message("Past", attributes), true, false);
case "PastOrPresent" -> new FuturePastAdapter(context.message("PastOrPresent", attributes), true, true);
case "Future" -> new FuturePastAdapter(context.message("Future", attributes), false, false);
case "FutureOrPresent" -> new FuturePastAdapter(context.message("FutureOrPresent", attributes), false, true);
case "Pattern" -> new PatternAdapter(context.message2("{avaje.validation.constraints.Pattern.message}", attributes), attributes);
case "Size" -> new SizeAdapter(context.message2("{avaje.validation.constraints.Size.message}", attributes), attributes);
default -> null;
};

private static final class PatternAdapter implements ValidationAdapter<CharSequence> {

Expand All @@ -49,7 +44,7 @@ public PatternAdapter(ValidationContext.Message message, Map<String, Object> att
this.message = message;
int flags = 0;

for (final var flag : Optional.ofNullable((List<RegexFlag>) attributes.get("flags")).orElseGet(List::of)) {
for (final var flag : (List<RegexFlag>) attributes.get("flags")) {
flags |= flag.getValue();
}
this.pattern = Pattern.compile((String) attributes.get("regexp"), flags).asMatchPredicate().negate();
Expand All @@ -73,8 +68,8 @@ private static final class SizeAdapter implements ValidationAdapter<Object> {

public SizeAdapter(ValidationContext.Message message, Map<String, Object> attributes) {
this.message = message;
this.min = Optional.ofNullable((Integer) attributes.get("min")).orElse(0);
this.max = Optional.ofNullable((Integer) attributes.get("max")).orElse(Integer.MAX_VALUE);
this.min = (int) attributes.get("min");
this.max = (int) attributes.get("max");
}

@Override
Expand Down
Loading