Skip to content

Commit 1d5840d

Browse files
marko-bekhtagsmet
authored andcommitted
HV-1395 Adding type unwrapping checks for OptionalInt/OptionalLong/OptionalDouble
1 parent 5c4294f commit 1d5840d

File tree

6 files changed

+162
-24
lines changed

6 files changed

+162
-24
lines changed

annotation-processor/src/main/java/org/hibernate/validator/ap/internal/checks/ConstraintCheckFactory.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ public ConstraintCheckFactory(Types typeUtils, Elements elementUtils, Constraint
102102
AnnotationType.CONSTRAINT_ANNOTATION,
103103
new SingleValuedChecks(
104104
new StaticCheck(),
105-
new TypeCheck( constraintHelper ),
105+
new TypeCheck( constraintHelper, typeUtils, annotationApiHelper ),
106106
new AnnotationParametersSizeLengthCheck( annotationApiHelper ),
107107
new AnnotationParametersPatternCheck( annotationApiHelper ),
108108
new AnnotationParametersScriptAssertCheck( annotationApiHelper ),
@@ -118,7 +118,7 @@ public ConstraintCheckFactory(Types typeUtils, Elements elementUtils, Constraint
118118
new MultiValuedChecks(
119119
constraintHelper,
120120
new StaticCheck(),
121-
new TypeCheck( constraintHelper ),
121+
new TypeCheck( constraintHelper, typeUtils, annotationApiHelper ),
122122
new AnnotationParametersSizeLengthCheck( annotationApiHelper ),
123123
new AnnotationParametersPatternCheck( annotationApiHelper ),
124124
new AnnotationParametersScriptAssertCheck( annotationApiHelper ),
@@ -142,7 +142,7 @@ public ConstraintCheckFactory(Types typeUtils, Elements elementUtils, Constraint
142142
new GetterCheck( methodConstraintsSupported ),
143143
new StaticCheck(),
144144
new MethodAnnotationCheck( constraintHelper ),
145-
new TypeCheck( constraintHelper ),
145+
new TypeCheck( constraintHelper, typeUtils, annotationApiHelper ),
146146
new AnnotationParametersSizeLengthCheck( annotationApiHelper ),
147147
new AnnotationParametersPatternCheck( annotationApiHelper ),
148148
new AnnotationParametersScriptAssertCheck( annotationApiHelper ),
@@ -160,7 +160,7 @@ public ConstraintCheckFactory(Types typeUtils, Elements elementUtils, Constraint
160160
new GetterCheck( methodConstraintsSupported ),
161161
new StaticCheck(),
162162
new MethodAnnotationCheck( constraintHelper ),
163-
new TypeCheck( constraintHelper ),
163+
new TypeCheck( constraintHelper, typeUtils, annotationApiHelper ),
164164
new AnnotationParametersSizeLengthCheck( annotationApiHelper ),
165165
new AnnotationParametersPatternCheck( annotationApiHelper ),
166166
new AnnotationParametersScriptAssertCheck( annotationApiHelper ),
@@ -224,7 +224,7 @@ public ConstraintCheckFactory(Types typeUtils, Elements elementUtils, Constraint
224224
nonAnnotationTypeChecks.put(
225225
AnnotationType.CONSTRAINT_ANNOTATION,
226226
new SingleValuedChecks(
227-
new TypeCheck( constraintHelper ),
227+
new TypeCheck( constraintHelper, typeUtils, annotationApiHelper ),
228228
new AnnotationParametersSizeLengthCheck( annotationApiHelper ),
229229
new AnnotationParametersPatternCheck( annotationApiHelper ),
230230
new AnnotationParametersScriptAssertCheck( annotationApiHelper ),
@@ -237,7 +237,7 @@ public ConstraintCheckFactory(Types typeUtils, Elements elementUtils, Constraint
237237
AnnotationType.MULTI_VALUED_CONSTRAINT_ANNOTATION,
238238
new MultiValuedChecks(
239239
constraintHelper,
240-
new TypeCheck( constraintHelper ),
240+
new TypeCheck( constraintHelper, typeUtils, annotationApiHelper ),
241241
new AnnotationParametersSizeLengthCheck( annotationApiHelper ),
242242
new AnnotationParametersPatternCheck( annotationApiHelper ),
243243
new AnnotationParametersScriptAssertCheck( annotationApiHelper ),

annotation-processor/src/main/java/org/hibernate/validator/ap/internal/checks/TypeCheck.java

Lines changed: 62 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,21 @@
77
package org.hibernate.validator.ap.internal.checks;
88

99
import java.util.Collections;
10+
import java.util.Optional;
1011
import java.util.Set;
1112

1213
import javax.lang.model.element.AnnotationMirror;
14+
import javax.lang.model.element.AnnotationValue;
1315
import javax.lang.model.element.Element;
1416
import javax.lang.model.element.ExecutableElement;
17+
import javax.lang.model.element.Name;
1518
import javax.lang.model.element.TypeElement;
1619
import javax.lang.model.element.VariableElement;
20+
import javax.lang.model.type.TypeKind;
1721
import javax.lang.model.type.TypeMirror;
22+
import javax.lang.model.util.Types;
1823

24+
import org.hibernate.validator.ap.internal.util.AnnotationApiHelper;
1925
import org.hibernate.validator.ap.internal.util.CollectionHelper;
2026
import org.hibernate.validator.ap.internal.util.ConstraintHelper;
2127
import org.hibernate.validator.ap.internal.util.ConstraintHelper.AnnotationProcessorValidationTarget;
@@ -26,26 +32,29 @@
2632
* fields, methods and non-annotation type declarations.
2733
*
2834
* @author Gunnar Morling
35+
* @author Marko Bekhta
2936
*/
3037
public class TypeCheck extends AbstractConstraintCheck {
3138

32-
private ConstraintHelper constraintHelper;
39+
private final ConstraintHelper constraintHelper;
3340

34-
public TypeCheck(ConstraintHelper constraintHelper) {
41+
private final Types typeUtils;
42+
43+
private final AnnotationApiHelper annotationApiHelper;
44+
45+
public TypeCheck(ConstraintHelper constraintHelper, Types typeUtils, AnnotationApiHelper annotationApiHelper) {
3546
this.constraintHelper = constraintHelper;
47+
this.typeUtils = typeUtils;
48+
this.annotationApiHelper = annotationApiHelper;
3649
}
3750

3851
@Override
39-
public Set<ConstraintCheckIssue> checkField(VariableElement element,
40-
AnnotationMirror annotation) {
41-
52+
public Set<ConstraintCheckIssue> checkField(VariableElement element, AnnotationMirror annotation) {
4253
return checkInternal( element, annotation, element.asType(), "NOT_SUPPORTED_TYPE" );
4354
}
4455

4556
@Override
46-
public Set<ConstraintCheckIssue> checkMethod(ExecutableElement element,
47-
AnnotationMirror annotation) {
48-
57+
public Set<ConstraintCheckIssue> checkMethod(ExecutableElement element, AnnotationMirror annotation) {
4958
AnnotationProcessorValidationTarget target = AnnotationProcessorValidationTarget.ANNOTATED_ELEMENT;
5059
if ( constraintHelper.isConstraintAnnotation( annotation.getAnnotationType().asElement() ) ) {
5160
target = constraintHelper.resolveValidationTarget( element, annotation );
@@ -60,25 +69,60 @@ public Set<ConstraintCheckIssue> checkMethod(ExecutableElement element,
6069
}
6170

6271
@Override
63-
public Set<ConstraintCheckIssue> checkNonAnnotationType(
64-
TypeElement element, AnnotationMirror annotation) {
65-
72+
public Set<ConstraintCheckIssue> checkNonAnnotationType(TypeElement element, AnnotationMirror annotation) {
6673
return checkInternal( element, annotation, element.asType(), "NOT_SUPPORTED_TYPE" );
6774
}
6875

69-
private Set<ConstraintCheckIssue> checkInternal(Element element,
70-
AnnotationMirror annotation, TypeMirror type, String messageKey) {
71-
72-
if ( constraintHelper.checkConstraint(
73-
annotation.getAnnotationType(), type ) != ConstraintCheckResult.ALLOWED ) {
74-
76+
private Set<ConstraintCheckIssue> checkInternal(Element element, AnnotationMirror annotation, TypeMirror type, String messageKey) {
77+
TypeMirror typeToCheck = usesUnwrapping( annotation, type ) ? getUnwrappedType( type ) : type;
78+
if ( !isAnnotationAllowedForType( annotation, typeToCheck ) ) {
7579
return CollectionHelper.asSet(
7680
ConstraintCheckIssue.error(
7781
element, annotation, messageKey,
78-
annotation.getAnnotationType().asElement().getSimpleName() ) );
82+
annotation.getAnnotationType().asElement().getSimpleName()
83+
) );
7984
}
8085

8186
return Collections.emptySet();
8287
}
8388

89+
private boolean isAnnotationAllowedForType(AnnotationMirror annotation, TypeMirror type) {
90+
return ConstraintCheckResult.ALLOWED.equals( constraintHelper.checkConstraint( annotation.getAnnotationType(), type ) );
91+
}
92+
93+
private boolean usesUnwrapping(AnnotationMirror annotationMirror, TypeMirror typeMirror) {
94+
//need to check if this annotation is not on one of the types that are automatically unwrapped:
95+
if ( constraintHelper.isSupportedForUnwrappingByDefault( getQualifiedName( typeMirror ) ) ) {
96+
return true;
97+
}
98+
99+
//otherwise look for Unwrapping type in payload:
100+
return annotationApiHelper.getAnnotationArrayValue( annotationMirror, "payload" ).stream()
101+
.map( AnnotationValue::getValue )
102+
.map( type -> (TypeMirror) type )
103+
.map( typeUtils::asElement )
104+
.map( elem -> ( (TypeElement) elem ).getQualifiedName() )
105+
.filter( name -> name.toString().equals( "javax.validation.valueextraction.Unwrapping.Unwrap" ) )
106+
.findAny().isPresent();
107+
}
108+
109+
private TypeMirror getUnwrappedType(TypeMirror type) {
110+
Optional<TypeMirror> optional = constraintHelper.getUnwrappedToByDefault( getQualifiedName( type ) );
111+
if ( optional.isPresent() ) {
112+
return optional.get();
113+
}
114+
else {
115+
//TODO: need to find a way to check for unwrapping
116+
return type;
117+
}
118+
}
119+
120+
private Name getQualifiedName(TypeMirror typeMirror) {
121+
if ( TypeKind.DECLARED.equals( typeMirror.getKind() ) ) {
122+
return ( (TypeElement) typeUtils.asElement( typeMirror ) ).getQualifiedName();
123+
}
124+
else {
125+
return null;
126+
}
127+
}
84128
}

annotation-processor/src/main/java/org/hibernate/validator/ap/internal/util/ConstraintHelper.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.util.HashSet;
3030
import java.util.List;
3131
import java.util.Map;
32+
import java.util.Optional;
3233
import java.util.Set;
3334

3435
import javax.lang.model.element.AnnotationMirror;
@@ -52,6 +53,7 @@
5253
import org.hibernate.validator.ap.internal.util.TypeNames.HibernateValidatorTypes;
5354
import org.hibernate.validator.ap.internal.util.TypeNames.JavaMoneyTypes;
5455
import org.hibernate.validator.ap.internal.util.TypeNames.JodaTypes;
56+
import org.hibernate.validator.ap.internal.util.TypeNames.SupportedForUnwrapTypes;
5557

5658
/**
5759
* Helper class that deals with all constraint-related stuff, such as
@@ -223,6 +225,8 @@ public enum AnnotationProcessorConstraintTarget {
223225

224226
private final Map<Name, AnnotationType> annotationTypeCache;
225227

228+
private final Map<Name, TypeMirror> supportedTypesUnwrappedByDefault;
229+
226230
/**
227231
* Caches composing constraints.
228232
*/
@@ -240,6 +244,7 @@ public ConstraintHelper(Types typeUtils, AnnotationApiHelper annotationApiHelper
240244
annotationTypeCache = CollectionHelper.newHashMap();
241245
supportedTypesByConstraint = CollectionHelper.newHashMap();
242246
composingConstraintsByConstraints = CollectionHelper.newHashMap();
247+
supportedTypesUnwrappedByDefault = CollectionHelper.newHashMap();
243248

244249
//register BV-defined constraints
245250
registerAllowedTypesForBuiltInConstraint( BeanValidationTypes.ASSERT_FALSE, Boolean.class );
@@ -299,6 +304,10 @@ public ConstraintHelper(Types typeUtils, AnnotationApiHelper annotationApiHelper
299304
registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.SAFE_HTML, CharSequence.class );
300305
registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.SCRIPT_ASSERT, Object.class );
301306
registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.URL, CharSequence.class );
307+
308+
registerSupportedTypesUnwrappedByDefault( SupportedForUnwrapTypes.OPTIONAL_INT, Integer.class.getName() );
309+
registerSupportedTypesUnwrappedByDefault( SupportedForUnwrapTypes.OPTIONAL_LONG, Long.class.getName() );
310+
registerSupportedTypesUnwrappedByDefault( SupportedForUnwrapTypes.OPTIONAL_DOUBLE, Double.class.getName() );
302311
}
303312

304313
/**
@@ -570,6 +579,14 @@ public Types getTypeUtils() {
570579
return typeUtils;
571580
}
572581

582+
public boolean isSupportedForUnwrappingByDefault(Name typeName) {
583+
return supportedTypesUnwrappedByDefault.containsKey( typeName );
584+
}
585+
586+
public Optional<TypeMirror> getUnwrappedToByDefault(Name typeName) {
587+
return Optional.ofNullable( supportedTypesUnwrappedByDefault.get( typeName ) );
588+
}
589+
573590
// ==================================
574591
// private API below
575592
// ==================================
@@ -1051,6 +1068,19 @@ private void registerAllowedTypesForBuiltInConstraint(String annotationType, Lis
10511068
}
10521069
}
10531070

1071+
private void registerSupportedTypesUnwrappedByDefault(String typeName, String unwrappedToTypeName) {
1072+
DeclaredType typeToUnwrap = annotationApiHelper.getDeclaredTypeByName( typeName );
1073+
1074+
if ( typeToUnwrap == null ) {
1075+
return;
1076+
}
1077+
1078+
supportedTypesUnwrappedByDefault.put(
1079+
getName( typeToUnwrap ),
1080+
annotationApiHelper.getDeclaredTypeByName( unwrappedToTypeName )
1081+
);
1082+
}
1083+
10541084
private Name getName(DeclaredType type) {
10551085
return getName( type.asElement() );
10561086
}

annotation-processor/src/main/java/org/hibernate/validator/ap/internal/util/TypeNames.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,10 @@ public static class JavaMoneyTypes {
101101

102102
}
103103

104+
public static class SupportedForUnwrapTypes {
105+
public static final String OPTIONAL_INT = "java.util.OptionalInt";
106+
public static final String OPTIONAL_LONG = "java.util.OptionalLong";
107+
public static final String OPTIONAL_DOUBLE = "java.util.OptionalDouble";
108+
}
109+
104110
}

annotation-processor/src/test/java/org/hibernate/validator/ap/ConstraintValidationProcessorTest.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
import org.hibernate.validator.ap.testmodel.customconstraints.CheckCaseValidator;
5858
import org.hibernate.validator.ap.testmodel.customconstraints.FieldLevelValidationUsingCustomConstraints;
5959
import org.hibernate.validator.ap.testmodel.customconstraints.HibernateValidatorProvidedCustomConstraints;
60+
import org.hibernate.validator.ap.testmodel.customconstraints.UnwrappingConstraints;
6061
import org.hibernate.validator.ap.testmodel.groupsequenceprovider.BazDefaultGroupSequenceProvider;
6162
import org.hibernate.validator.ap.testmodel.groupsequenceprovider.FooBarBazDefaultGroupSequenceProvider;
6263
import org.hibernate.validator.ap.testmodel.groupsequenceprovider.FooBarDefaultGroupSequenceProvider;
@@ -663,4 +664,21 @@ public void crossParameterConstraintsAllowed() {
663664
);
664665

665666
}
667+
668+
@Test
669+
@TestForIssue(jiraKey = "HV-1395")
670+
public void unwrappingConstraints() {
671+
File sourceFile = compilerHelper.getSourceFile( UnwrappingConstraints.class );
672+
673+
boolean compilationResult =
674+
compilerHelper.compile( new ConstraintValidationProcessor(), diagnostics, sourceFile );
675+
676+
assertFalse( compilationResult );
677+
assertThatDiagnosticsMatch(
678+
diagnostics,
679+
new DiagnosticExpectation( Kind.ERROR, 32 ),
680+
new DiagnosticExpectation( Kind.ERROR, 35 ),
681+
new DiagnosticExpectation( Kind.ERROR, 38 )
682+
);
683+
}
666684
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Hibernate Validator, declare and validate application constraints
3+
*
4+
* License: Apache License, Version 2.0
5+
* See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
6+
*/
7+
package org.hibernate.validator.ap.testmodel.customconstraints;
8+
9+
import java.util.OptionalDouble;
10+
import java.util.OptionalInt;
11+
import java.util.OptionalLong;
12+
13+
import javax.validation.constraints.Min;
14+
import javax.validation.constraints.Past;
15+
16+
public class UnwrappingConstraints {
17+
18+
/**
19+
* Allowed.
20+
*/
21+
@Min(value = 5)
22+
public OptionalInt optionalInt;
23+
24+
@Min(value = 5)
25+
public OptionalLong optionalLong;
26+
27+
@Min(value = 5)
28+
public OptionalDouble optionalDouble;
29+
/**
30+
* Not allowed.
31+
*/
32+
@Past
33+
public OptionalInt badOptionalInt;
34+
35+
@Past
36+
public OptionalLong badOptionalLong;
37+
38+
@Past
39+
public OptionalDouble badOptionalDouble;
40+
}

0 commit comments

Comments
 (0)