Skip to content

Commit f3f6a80

Browse files
authored
Merge pull request #108 from SentryMan/class-constraints
Repair Custom Adapters/Class Constraints
2 parents 36a9621 + 2fda257 commit f3f6a80

File tree

19 files changed

+165
-51
lines changed

19 files changed

+165
-51
lines changed

validator-generator/src/main/java/io/avaje/validation/generator/ContraintReader.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ final class ContraintReader implements BeanReader {
2727
importTypes.add("java.util.Map");
2828
importTypes.add("io.avaje.validation.adapter.ConstraintAdapter");
2929
importTypes.add("io.avaje.validation.adapter.ValidationAdapter");
30-
importTypes.add("io.avaje.validation.adapter.ValidationContext");
3130
importTypes.add("io.avaje.validation.adapter.ValidationRequest");
31+
importTypes.add("io.avaje.validation.adapter.ValidationContext.AdapterCreateRequest");
3232
importTypes.add("io.avaje.validation.spi.Generated");
3333

3434
this.annotations =
@@ -121,7 +121,9 @@ public void writeConstructor(Append writer) {
121121

122122
writer.append(
123123
"""
124-
final var message = ctx.<Object>message(attributes).template();
124+
final var message = req.message().template();
125+
final var ctx = req.ctx();
126+
final var groups = req.groups();
125127
this.adapter =
126128
""");
127129

validator-generator/src/main/java/io/avaje/validation/generator/ElementAnnotationContainer.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ static ElementAnnotationContainer create(Element element) {
4949

5050
final var annotations =
5151
element.getAnnotationMirrors().stream()
52+
.filter(m -> !ValidPrism.isInstance(m))
5253
.collect(
5354
toMap(
5455
a -> GenericType.parse(a.getAnnotationType().toString()),
@@ -94,4 +95,8 @@ public void addImports(Set<String> importTypes) {
9495
typeUse1.keySet().forEach(t -> t.addImports(importTypes));
9596
typeUse2.keySet().forEach(t -> t.addImports(importTypes));
9697
}
98+
99+
boolean isEmpty() {
100+
return annotations.isEmpty() && typeUse1.isEmpty() && typeUse2.isEmpty();
101+
}
97102
}

validator-generator/src/main/java/io/avaje/validation/generator/FieldReader.java

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import javax.lang.model.element.Element;
77
import javax.lang.model.element.Modifier;
8+
import javax.lang.model.element.TypeElement;
89

910
final class FieldReader {
1011

@@ -22,8 +23,13 @@ final class FieldReader {
2223
private final boolean optionalValidation;
2324
private final Element element;
2425
private final ElementAnnotationContainer elementAnnotations;
26+
private final boolean classLevel;
2527

2628
FieldReader(Element element, List<String> genericTypeParams) {
29+
this(element, genericTypeParams, false);
30+
}
31+
32+
FieldReader(Element element, List<String> genericTypeParams, boolean classLevel) {
2733
this.genericTypeParams = genericTypeParams;
2834
this.fieldName = element.getSimpleName().toString();
2935
this.publicField = element.getModifiers().contains(Modifier.PUBLIC);
@@ -34,6 +40,7 @@ final class FieldReader {
3440
adapterShortType = initAdapterShortType(shortType);
3541
adapterFieldName = initShortName();
3642
this.optionalValidation = Util.isNullable(element);
43+
this.classLevel = classLevel;
3744
}
3845

3946
private String initAdapterShortType(String shortType) {
@@ -120,7 +127,9 @@ void writeField(Append writer) {
120127
}
121128

122129
private void writeGetValue(Append writer, String suffix) {
123-
if (getter != null) {
130+
if (classLevel) {
131+
// don't need a getter
132+
} else if (getter != null) {
124133
writer.append("value.%s()%s", getter.getName(), suffix);
125134
} else if (publicField) {
126135
writer.append("value.%s%s", fieldName, suffix);
@@ -131,6 +140,17 @@ private void writeGetValue(Append writer, String suffix) {
131140
}
132141

133142
void writeValidate(Append writer) {
143+
if (classLevel) {
144+
writer.append(
145+
"""
146+
if (!request.hasViolations()) {
147+
%s.validate(value, request, field);
148+
}
149+
""",
150+
adapterFieldName);
151+
writer.eol().eol();
152+
return;
153+
}
134154
writer.append(" var _$%s = ", fieldName);
135155
writeGetValue(writer, ";");
136156
writer.eol();
@@ -158,8 +178,20 @@ public void writeConstructor(Append writer) {
158178
writer.append(" this.%s = ", adapterFieldName).eol();
159179

160180
new AdapterHelper(
161-
writer, elementAnnotations, " ", PrimitiveUtil.wrap(genericType.shortType()), genericType)
162-
.write();
181+
writer,
182+
elementAnnotations,
183+
" ",
184+
PrimitiveUtil.wrap(genericType.shortType()),
185+
genericType)
186+
.write();
163187
writer.append(";").eol().eol();
164188
}
189+
190+
public boolean isClassLvl() {
191+
return classLevel;
192+
}
193+
194+
public boolean hasAnnotations() {
195+
return !elementAnnotations.isEmpty();
196+
}
165197
}

validator-generator/src/main/java/io/avaje/validation/generator/SimpleAdapterWriter.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,15 @@ void write() throws IOException {
5050
}
5151

5252
private void writeConstructor() {
53-
writer.append(" public %sValidationAdapter(ValidationContext ctx", adapterShortName);
54-
for (int i = 0; i < genericParamsCount; i++) {
55-
writer.append(", Type param%d", i);
56-
}
5753

58-
if (beanReader instanceof ContraintReader) {
59-
writer.append(", Set<Class<?>> groups, Map<String, Object> attributes");
54+
if (isContraint) {
55+
writer.append(" public %sValidationAdapter(AdapterCreateRequest req", adapterShortName);
56+
} else {
57+
writer.append(" public %sValidationAdapter(ValidationContext ctx", adapterShortName);
58+
for (int i = 0; i < genericParamsCount; i++) {
59+
writer.append(", Type param%d", i);
60+
}
6061
}
61-
6262
writer.append(") {", adapterShortName).eol();
6363
beanReader.writeConstructor(writer);
6464
writer.append(" }").eol();

validator-generator/src/main/java/io/avaje/validation/generator/TypeReader.java

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,14 @@ final class TypeReader {
3232
private final Map<String, MethodReader> allGetterMethods = new LinkedHashMap<>();
3333
private final Map<String, MethodReader> maybeGetterMethods = new LinkedHashMap<>();
3434
private final TypeElement baseType;
35-
private final boolean hasJsonAnnotation;
35+
private final boolean hasValidAnnotation;
3636
private final Set<String> seenFields = new HashSet<>();
3737
private boolean nonAccessibleField;
3838
private final List<String> genericTypeParams;
3939

4040
TypeReader(TypeElement baseType) {
4141
this.baseType = baseType;
42-
this.hasJsonAnnotation = Util.isValid(baseType);
42+
this.hasValidAnnotation = Util.isValid(baseType);
4343
this.genericTypeParams = initTypeParams(baseType);
4444
}
4545

@@ -58,6 +58,11 @@ void read(TypeElement type) {
5858
}
5959
}
6060

61+
final var classAdapter = new FieldReader(type, genericTypeParams, true);
62+
if (classAdapter.hasAnnotations()) {
63+
localFields.add(classAdapter);
64+
}
65+
6166
for (final FieldReader localField : localFields) {
6267
allFields.add(localField);
6368
allFieldMap.put(localField.fieldName(), localField);
@@ -93,10 +98,8 @@ private void readMethod(Element element, TypeElement type, List<FieldReader> loc
9398
final List<? extends VariableElement> parameters = methodElement.getParameters();
9499
final String methodKey = methodElement.getSimpleName().toString();
95100
final MethodReader methodReader = new MethodReader(methodElement, type).read();
96-
if (parameters.size() == 0) {
97-
if (!maybeGetterMethods.containsKey(methodKey)) {
98-
maybeGetterMethods.put(methodKey, methodReader);
99-
}
101+
if (parameters.isEmpty()) {
102+
maybeGetterMethods.putIfAbsent(methodKey, methodReader);
100103
allGetterMethods.put(methodKey.toLowerCase(), methodReader);
101104
}
102105
// for reading methods
@@ -112,6 +115,9 @@ private void readMethod(Element element, TypeElement type, List<FieldReader> loc
112115

113116
private void matchFieldsToGetter() {
114117
for (final FieldReader field : allFields) {
118+
if (field.isClassLvl()) {
119+
continue;
120+
}
115121
matchFieldToGetter(field);
116122
}
117123
}
@@ -121,8 +127,13 @@ private void matchFieldToGetter(FieldReader field) {
121127
&& !matchFieldToGetter2(field, true)
122128
&& !field.isPublicField()) {
123129
nonAccessibleField = true;
124-
if (hasJsonAnnotation) {
125-
logError("Non accessible field " + baseType + " " + field.fieldName() + " with no matching getter?");
130+
if (hasValidAnnotation) {
131+
logError(
132+
"Non accessible field "
133+
+ baseType
134+
+ " "
135+
+ field.fieldName()
136+
+ " with no matching getter?");
126137
} else {
127138
logDebug("Non accessible field " + baseType + " " + field.fieldName());
128139
}

validator-generator/src/main/java/io/avaje/validation/generator/Util.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,9 @@ static String initLower(String name) {
260260
/** Return the base type given the ValidationAdapter type. */
261261
static String baseTypeOfAdapter(String adapterFullName) {
262262
final var element = element(adapterFullName);
263-
263+
if (element == null) {
264+
throw new NullPointerException("Element not found for [" + adapterFullName + "]");
265+
}
264266
return Optional.of(element.getSuperclass())
265267
.filter(t -> t.toString().contains("io.avaje.validation.adapter.AbstractConstraintAdapter"))
266268
.or(validationAdapter(element))

validator-generator/src/main/java/io/avaje/validation/generator/ValidPrism.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.avaje.validation.generator;
22

3+
import javax.lang.model.element.AnnotationMirror;
34
import javax.lang.model.element.Element;
45

56
public interface ValidPrism {
@@ -10,4 +11,11 @@ static boolean isPresent(Element e) {
1011
|| JavaxValidPrism.isPresent(e)
1112
|| HttpValidPrism.isPresent(e);
1213
}
14+
15+
static boolean isInstance(AnnotationMirror e) {
16+
return AvajeValidPrism.getInstance(e) != null
17+
|| JakartaValidPrism.getInstance(e) != null
18+
|| JavaxValidPrism.getInstance(e) != null
19+
|| HttpValidPrism.getInstance(e) != null;
20+
}
1321
}

validator-generator/src/test/java/io/avaje/validation/generator/ValidatorProcessorTest.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import javax.tools.ToolProvider;
2323

2424
import org.junit.jupiter.api.AfterEach;
25-
import org.junit.jupiter.api.Disabled;
2625
import org.junit.jupiter.api.Test;
2726

2827
class ValidatorProcessorTest {
@@ -44,7 +43,6 @@ void deleteGeneratedFiles() throws IOException {
4443
}
4544
}
4645

47-
@Disabled
4846
@Test
4947
void testGeneration() throws Exception {
5048
final String source =
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22

33
import io.avaje.validation.adapter.AbstractConstraintAdapter;
44
import io.avaje.validation.adapter.ConstraintAdapter;
5-
import io.avaje.validation.adapter.ValidationContext;
5+
import io.avaje.validation.adapter.ValidationContext.AdapterCreateRequest;
66
import io.avaje.validation.generator.models.valid.CheckCase.CaseMode;
77

88
@ConstraintAdapter(CheckCase.class)
9-
public final class CustomAnnotationAdapter extends AbstractConstraintAdapter<String> {
9+
public final class CheckCaseAdapter extends AbstractConstraintAdapter<String> {
1010

1111
private final CaseMode caseMode;
1212

13-
public CustomAnnotationAdapter(ValidationContext.AdapterCreateRequest request) {
13+
public CheckCaseAdapter(AdapterCreateRequest request) {
1414
super(request);
1515
final var attributes = request.attributes();
1616
caseMode = (CaseMode) attributes.get("caseMode");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package io.avaje.validation.generator.models.valid.typeconstraint;
2+
3+
import static java.lang.annotation.ElementType.TYPE;
4+
import static java.lang.annotation.RetentionPolicy.SOURCE;
5+
6+
import java.lang.annotation.Retention;
7+
import java.lang.annotation.Target;
8+
9+
import io.avaje.validation.constraints.Constraint;
10+
11+
@Target(TYPE)
12+
@Retention(SOURCE)
13+
@Constraint
14+
public @interface PassingSkill {
15+
String message() default "put these foolish ambitions to rest"; // default error message
16+
17+
Class<?>[] groups() default {}; // groups
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package io.avaje.validation.generator.models.valid.typeconstraint;
2+
3+
import io.avaje.validation.adapter.AbstractConstraintAdapter;
4+
import io.avaje.validation.adapter.ConstraintAdapter;
5+
import io.avaje.validation.adapter.ValidationContext.AdapterCreateRequest;
6+
7+
@ConstraintAdapter(PassingSkill.class)
8+
public final class PassingSkillAdapter extends AbstractConstraintAdapter<Tarnished> {
9+
10+
public PassingSkillAdapter(AdapterCreateRequest request) {
11+
super(request);
12+
}
13+
14+
@Override
15+
public boolean isValid(Tarnished lowlyTarnished) {
16+
if (lowlyTarnished == null) {
17+
return true;
18+
}
19+
return lowlyTarnished.vigor() >= 50 && lowlyTarnished.endurance() >= 50;
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package io.avaje.validation.generator.models.valid.typeconstraint;
2+
3+
import io.avaje.validation.constraints.Valid;
4+
5+
@Valid
6+
@PassingSkill
7+
public record Tarnished(int vigor, int endurance) {}

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

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
package io.avaje.validation;
22

3-
import io.avaje.lang.Nullable;
4-
import io.avaje.validation.adapter.*;
5-
import io.avaje.validation.core.DefaultBootstrap;
6-
import io.avaje.validation.spi.MessageInterpolator;
7-
import io.avaje.validation.spi.ValidatorCustomizer;
8-
93
import java.lang.annotation.Annotation;
104
import java.lang.reflect.Type;
115
import java.time.Clock;
126
import java.time.Duration;
137
import java.util.Locale;
14-
import java.util.Map;
158
import java.util.ResourceBundle;
16-
import java.util.Set;
179
import java.util.function.Supplier;
1810

11+
import io.avaje.lang.Nullable;
12+
import io.avaje.validation.adapter.ValidationAdapter;
13+
import io.avaje.validation.adapter.ValidationContext;
14+
import io.avaje.validation.adapter.ValidationContext.AdapterCreateRequest;
15+
import io.avaje.validation.core.DefaultBootstrap;
16+
import io.avaje.validation.spi.MessageInterpolator;
17+
import io.avaje.validation.spi.ValidatorCustomizer;
18+
1919
/**
2020
* Validate plain Java objects that have been annotated with validation constraints.
2121
*
@@ -145,8 +145,7 @@ interface AdapterBuilder {
145145
interface AnnotationAdapterBuilder {
146146

147147
/** Create a ValidationAdapter given the Validator instance. */
148-
ValidationAdapter<?> build(
149-
ValidationContext ctx, Set<Class<?>> groups, Map<String, Object> attributes);
148+
ValidationAdapter<?> build(AdapterCreateRequest request);
150149
}
151150

152151
/** Components register ValidationAdapters Validator.Builder */

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@
66
*/
77
public abstract class AbstractContainerAdapter<T> implements ValidationAdapter<T> {
88

9-
/**
10-
* Adapter placed on the container type
11-
*/
9+
/** Adapter placed on the container type */
1210
protected final ValidationAdapter<T> initalAdapter;
1311

1412
protected ValidationAdapter<Object> multiAdapter;

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@
1919
*
2020
* String value;
2121
*
22-
* public CustomAnnotationAdapter(ValidationContext ctx, Set<Class<?>> groups, Map<String, Object> attributes) {
22+
* public CustomAnnotationAdapter(AdapterCreateRequest req) {
2323
* //create a message object for error interpolation and set groups
24-
* super(ctx.message(attributes), groups);
24+
* super(req);
2525
*
2626
* //use the attributes to extract the annotation values
27-
* value = (String) attributes.get("value");
27+
* value = (String) req.attribute("value");
2828
* }
2929
*
3030
*

0 commit comments

Comments
 (0)