/* * Hibernate Validator, declare and validate application constraints * * License: Apache License, Version 2.0 * See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>. */ package org.hibernate.validator.ap.util; import java.time.Duration; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.MonthDay; import java.time.OffsetDateTime; import java.time.OffsetTime; import java.time.Year; import java.time.YearMonth; import java.time.ZonedDateTime; import java.time.chrono.HijrahDate; import java.time.chrono.JapaneseDate; import java.time.chrono.MinguoDate; import java.time.chrono.ThaiBuddhistDate; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Date; import java.util.EnumSet; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Name; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementKindVisitor6; import javax.lang.model.util.SimpleAnnotationValueVisitor6; import javax.lang.model.util.TypeKindVisitor6; import javax.lang.model.util.Types; import org.hibernate.validator.ap.util.TypeNames.BeanValidationTypes; import org.hibernate.validator.ap.util.TypeNames.HibernateValidatorTypes; import org.hibernate.validator.ap.util.TypeNames.JavaMoneyTypes; import org.hibernate.validator.ap.util.TypeNames.JodaTypes; /** * Helper class that deals with all constraint-related stuff, such as * determining whether a given annotation represents a constraint annotation or * whether a given annotation is allowed to be declared at a given element. * * @author Gunnar Morling * @author Kevin Pollet <kevin.pollet@serli.com> (C) 2011 SERLI * @author Guillaume Smet */ public class ConstraintHelper { /** * Possible results of a constraint check as returned by * {@link ConstraintHelper#checkConstraint(DeclaredType, TypeMirror)}. * * @author Gunnar Morling */ public enum ConstraintCheckResult { /** * The checked constraint is allowed at the evaluated type. */ ALLOWED, /** * The checked constraint is not allowed at the evaluated type. */ DISALLOWED, /** * Multiple validators were found, that could validate the checked * constrained at the evaluated type. */ MULTIPLE_VALIDATORS_FOUND } /** * The type of an annotation with respect to the BV API. * * @author Gunnar Morling * @author Kevin Pollet <kevin.pollet@serli.com> (C) 2011 SERLI */ public enum AnnotationType { /** * Given annotation is a constraint annotation (e.g. @Min). */ CONSTRAINT_ANNOTATION, /** * Given annotation is a multi-valued annotation (e.g. * <code> * @List({ * @Min(10), * @Min(value=20, groups= Special.class}) * }) * </code>. */ MULTI_VALUED_CONSTRAINT_ANNOTATION, /** * Given annotation is the {@code @Valid} annotation. */ GRAPH_VALIDATION_ANNOTATION, /** * Given annotation is the @Constraint meta-annotation. */ CONSTRAINT_META_ANNOTATION, /** * Given annotation is the @GroupSequence annotation. */ GROUP_SEQUENCE_ANNOTATION, /** * Given annotation is the @GroupSequenceProvider annotation. */ GROUP_SEQUENCE_PROVIDER_ANNOTATION, /** * Given annotation is not related to the BV API (e.g. @Resource). */ NO_CONSTRAINT_ANNOTATION } /** * Defines the object on which a validation is targeted. */ public enum AnnotationProcessorValidationTarget { /** * The validation targets the parameters of a method/constructor. */ PARAMETERS, /** * The validation targets the value on which it is annotated or the return type of a method/constructor. */ ANNOTATED_ELEMENT } /** * The validation target of a constraint annotation. */ public enum AnnotationProcessorConstraintTarget { /** * Constraint applies to the parameters of a method or a constructor. */ PARAMETERS, /** * Constraint applies to the return value of a method or a constructor. */ RETURN_VALUE, /** * Discover the type when no ambiguity is present if neither on a method nor a constructor. */ IMPLICIT } /** * {@code java.time} types supported by {@code @Past} and {@Future} annotations. */ private static final Class<?>[] JAVA_TIME_TYPES_SUPPORTED_BY_FUTURE_AND_PAST_ANNOTATIONS = new Class<?>[] { HijrahDate.class, Instant.class, JapaneseDate.class, LocalDate.class, LocalDateTime.class, LocalTime.class, MinguoDate.class, MonthDay.class, OffsetDateTime.class, OffsetTime.class, ThaiBuddhistDate.class, Year.class, YearMonth.class, ZonedDateTime.class }; /** * Types supported by {@code @Size} and {@NotEmpty} annotations. */ private static final Class<?>[] TYPES_SUPPORTED_BY_SIZE_AND_NOT_EMPTY_ANNOTATIONS = new Class<?>[] { Object[].class, boolean[].class, byte[].class, char[].class, double[].class, float[].class, int[].class, long[].class, short[].class, Collection.class, Map.class, CharSequence.class }; /** * Contains the supported types for given constraints. Keyed by constraint * annotation type names, each value is a set with the allowed types for the * mapped constraint. The map is pre-populated with the supported * constraints for the built-in constraints. Constraints for custom * constraints are determined and stored on demand. */ private final Map<Name, Set<TypeMirror>> supportedTypesByConstraint; private final Map<Name, AnnotationType> annotationTypeCache; /** * Caches composing constraints. */ private final Map<Name, Set<AnnotationMirror>> composingConstraintsByConstraints; private final Types typeUtils; private final AnnotationApiHelper annotationApiHelper; public ConstraintHelper(Types typeUtils, AnnotationApiHelper annotationApiHelper) { this.typeUtils = typeUtils; this.annotationApiHelper = annotationApiHelper; annotationTypeCache = CollectionHelper.newHashMap(); supportedTypesByConstraint = CollectionHelper.newHashMap(); composingConstraintsByConstraints = CollectionHelper.newHashMap(); //register BV-defined constraints registerAllowedTypesForBuiltInConstraint( BeanValidationTypes.ASSERT_FALSE, Boolean.class ); registerAllowedTypesForBuiltInConstraint( BeanValidationTypes.ASSERT_TRUE, Boolean.class ); registerAllowedTypesForBuiltInConstraint( BeanValidationTypes.DECIMAL_MAX, Number.class, String.class ); registerAllowedTypesForBuiltInConstraint( BeanValidationTypes.DECIMAL_MAX, JavaMoneyTypes.MONETARY_AMOUNT ); registerAllowedTypesForBuiltInConstraint( BeanValidationTypes.DECIMAL_MIN, Number.class, String.class ); registerAllowedTypesForBuiltInConstraint( BeanValidationTypes.DECIMAL_MIN, JavaMoneyTypes.MONETARY_AMOUNT ); registerAllowedTypesForBuiltInConstraint( BeanValidationTypes.DIGITS, Number.class, String.class ); registerAllowedTypesForBuiltInConstraint( BeanValidationTypes.EMAIL, CharSequence.class ); registerAllowedTypesForBuiltInConstraint( BeanValidationTypes.FUTURE, Calendar.class, Date.class ); registerAllowedTypesForBuiltInConstraint( BeanValidationTypes.FUTURE, JodaTypes.READABLE_PARTIAL, JodaTypes.READABLE_INSTANT ); registerAllowedTypesForBuiltInConstraint( BeanValidationTypes.FUTURE, JAVA_TIME_TYPES_SUPPORTED_BY_FUTURE_AND_PAST_ANNOTATIONS ); registerAllowedTypesForBuiltInConstraint( BeanValidationTypes.MAX, Number.class, String.class ); registerAllowedTypesForBuiltInConstraint( BeanValidationTypes.MAX, JavaMoneyTypes.MONETARY_AMOUNT ); registerAllowedTypesForBuiltInConstraint( BeanValidationTypes.MIN, Number.class, String.class ); registerAllowedTypesForBuiltInConstraint( BeanValidationTypes.MIN, JavaMoneyTypes.MONETARY_AMOUNT ); registerAllowedTypesForBuiltInConstraint( BeanValidationTypes.NEGATIVE, Number.class ); registerAllowedTypesForBuiltInConstraint( BeanValidationTypes.NEGATIVE, JavaMoneyTypes.MONETARY_AMOUNT ); registerAllowedTypesForBuiltInConstraint( BeanValidationTypes.NOT_BLANK, CharSequence.class ); registerAllowedTypesForBuiltInConstraint( BeanValidationTypes.NOT_EMPTY, TYPES_SUPPORTED_BY_SIZE_AND_NOT_EMPTY_ANNOTATIONS ); registerAllowedTypesForBuiltInConstraint( BeanValidationTypes.NOT_NULL, Object.class ); registerAllowedTypesForBuiltInConstraint( BeanValidationTypes.NULL, Object.class ); registerAllowedTypesForBuiltInConstraint( BeanValidationTypes.PAST, Calendar.class, Date.class ); registerAllowedTypesForBuiltInConstraint( BeanValidationTypes.PAST, JodaTypes.READABLE_PARTIAL, JodaTypes.READABLE_INSTANT ); registerAllowedTypesForBuiltInConstraint( BeanValidationTypes.PAST, JAVA_TIME_TYPES_SUPPORTED_BY_FUTURE_AND_PAST_ANNOTATIONS ); registerAllowedTypesForBuiltInConstraint( BeanValidationTypes.PATTERN, String.class ); registerAllowedTypesForBuiltInConstraint( BeanValidationTypes.POSITIVE, Number.class ); registerAllowedTypesForBuiltInConstraint( BeanValidationTypes.POSITIVE, JavaMoneyTypes.MONETARY_AMOUNT ); registerAllowedTypesForBuiltInConstraint( BeanValidationTypes.SIZE, TYPES_SUPPORTED_BY_SIZE_AND_NOT_EMPTY_ANNOTATIONS ); //register HV-specific constraints registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.CURRENCY, JavaMoneyTypes.MONETARY_AMOUNT ); registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.DURATION_MAX, Duration.class ); registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.DURATION_MIN, Duration.class ); registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.EMAIL, CharSequence.class ); registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.LENGTH, CharSequence.class ); registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.MOD_CHECK, CharSequence.class ); registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.LUHN_CHECK, CharSequence.class ); registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.MOD_10_CHECK, CharSequence.class ); registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.MOD_11_CHECK, CharSequence.class ); registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.REGON_CHECK, CharSequence.class ); registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.NIP_CHECK, CharSequence.class ); registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.PESEL_CHECK, CharSequence.class ); registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.NOT_BLANK, CharSequence.class ); registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.NOT_EMPTY, TYPES_SUPPORTED_BY_SIZE_AND_NOT_EMPTY_ANNOTATIONS ); registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.SAFE_HTML, CharSequence.class ); registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.SCRIPT_ASSERT, Object.class ); registerAllowedTypesForBuiltInConstraint( HibernateValidatorTypes.URL, CharSequence.class ); } /** * Checks whether the given type element represents a constraint annotation * or not. That's the case, if the given element is annotated with the * {@code @Constraint} meta-annotation (which is only allowed at annotation * declarations). * * @param element The element of interest. * * @return True, if the given element is a constraint annotation type, false * otherwise. */ public boolean isConstraintAnnotation(Element element) { return annotationApiHelper.getMirror( element.getAnnotationMirrors(), BeanValidationTypes.CONSTRAINT ) != null; } /** * Returns the {@link AnnotationType} of the given annotation. * * @param annotationMirror The annotation mirror of interest. * * @return The given mirror's annotation type. */ public AnnotationType getAnnotationType(AnnotationMirror annotationMirror) { Name key = getName( annotationMirror.getAnnotationType() ); AnnotationType annotationType = annotationTypeCache.get( key ); if ( annotationType != null ) { return annotationType; } if ( isConstraintAnnotation( annotationMirror ) ) { annotationType = AnnotationType.CONSTRAINT_ANNOTATION; } else if ( isMultiValuedConstraint( annotationMirror ) ) { annotationType = AnnotationType.MULTI_VALUED_CONSTRAINT_ANNOTATION; } else if ( isGraphValidationAnnotation( annotationMirror ) ) { annotationType = AnnotationType.GRAPH_VALIDATION_ANNOTATION; } else if ( isConstraintMetaAnnotation( annotationMirror ) ) { annotationType = AnnotationType.CONSTRAINT_META_ANNOTATION; } else if ( isGroupSequenceAnnotation( annotationMirror ) ) { annotationType = AnnotationType.GROUP_SEQUENCE_ANNOTATION; } else if ( isGroupSequenceProviderAnnotation( annotationMirror ) ) { annotationType = AnnotationType.GROUP_SEQUENCE_PROVIDER_ANNOTATION; } else { annotationType = AnnotationType.NO_CONSTRAINT_ANNOTATION; } annotationTypeCache.put( key, annotationType ); return annotationType; } /** * Returns a list with the constraint annotations contained in the given * array-valued annotation mirror. * * @param annotationMirror An array-valued annotation mirror (meaning it has an * array-typed attribute with name "value"). * * @return A list with the constraint annotations part of the given * multi-valued constraint annotation. Will return an empty list if * the given annotation is no multi-valued annotation or if no * constraint annotations are contained within the given * array-valued annotation. */ public List<AnnotationMirror> getPartsOfMultiValuedConstraint( AnnotationMirror annotationMirror) { final List<AnnotationMirror> theValue = CollectionHelper.newArrayList(); for ( AnnotationValue oneValuePart : annotationApiHelper .getAnnotationArrayValue( annotationMirror, "value" ) ) { oneValuePart.accept( new SimpleAnnotationValueVisitor6<Void, Void>() { @Override public Void visitAnnotation(AnnotationMirror a, Void p) { if ( isConstraintAnnotation( a.getAnnotationType() .asElement() ) ) { theValue.add( a ); } return null; } }, null ); } return theValue; } /** * Checks whether the given annotation type (which <b>must</b> be a * constraint annotation type) may be specified at elements of the specified * type. * * @param constraintAnnotationType A constraint annotation type. * @param typeOfAnnotatedElement A type which with an element is annotated. * * @return Whether the given constraint annotation may be specified at * elements of the given type. */ public ConstraintCheckResult checkConstraint(DeclaredType constraintAnnotationType, TypeMirror typeOfAnnotatedElement) { //recursively check the composing constraints ConstraintCheckResult composingConstraintsCheck = checkComposingConstraints( constraintAnnotationType, typeOfAnnotatedElement ); if ( composingConstraintsCheck != ConstraintCheckResult.ALLOWED ) { return composingConstraintsCheck; } //check the supported types of the constraint itself return checkSupportedTypes( constraintAnnotationType, typeOfAnnotatedElement ); } /** * Checks whether the given type element represents a composed constraint or not. * * @param element The type element of interest. Must not be null. * * @return True if the given element represents a composed constraint, false otherwise. */ public boolean isComposedConstraint(TypeElement element) { return Boolean.TRUE.equals( element.asType().accept( new TypeKindVisitor6<Boolean, Void>() { @Override public Boolean visitDeclared(DeclaredType constraintValidatorImplementation, Void p) { return !getComposingConstraints( constraintValidatorImplementation ).isEmpty(); } }, null ) ); } /** * Resolve the actual {@code AnnotationProcessorValidationTarget} of a constraint annotation, when applied to a method/constructor. * <p> * When the annotation supports multiple {@link AnnotationProcessorValidationTarget}s (i.e. it is both cross-parameter and generic), the actual target is resolved using the * 'validationAppliesTo()' attribute of the annotation. * * @param element the method/constructor on which the annotation is applied * @param annotation the constraint annotation * @return the resolved {@code AnnotationProcessorValidationTarget}, null if the target cannot be inferred */ public AnnotationProcessorValidationTarget resolveValidationTarget(ExecutableElement element, AnnotationMirror annotation) { Set<AnnotationProcessorValidationTarget> allowedTargets = getSupportedValidationTargets( annotation.getAnnotationType() ); // assume that at least one target (AnnotationProcessorValidationTarget.ANNOTATED_ELEMENT) is present if ( allowedTargets.size() == 1 ) { return allowedTargets.toArray( new AnnotationProcessorValidationTarget[1] )[0]; } AnnotationProcessorConstraintTarget constrTarget = getConstraintTarget( annotation ); if ( constrTarget == null ) { return null; } switch ( constrTarget ) { case PARAMETERS: return AnnotationProcessorValidationTarget.PARAMETERS; case RETURN_VALUE: return AnnotationProcessorValidationTarget.ANNOTATED_ELEMENT; case IMPLICIT: default: return resolveImplicitValidationTarget( element ); } } /** * Returns the set of {@code AnnotationProcessorValidationTarget} supported by the given constraint annotation type. * <p> * A constraint annotation can support {@link AnnotationProcessorValidationTarget#ANNOTATED_ELEMENT}, {@link AnnotationProcessorValidationTarget#PARAMETERS} or both. * * @param constraintAnnotationType the constraint annotation type * @return the set of supported {@code AnnotationProcessorValidationTarget}s */ public Set<AnnotationProcessorValidationTarget> getSupportedValidationTargets(DeclaredType constraintAnnotationType) { AnnotationMirror constraintMetaAnnotation = getConstraintMetaAnnotation( constraintAnnotationType ); List<? extends AnnotationValue> validatorClassReferences = getValidatorClassesFromConstraintMetaAnnotation( constraintMetaAnnotation ); EnumSet<AnnotationProcessorValidationTarget> supported = EnumSet.noneOf( AnnotationProcessorValidationTarget.class ); for ( AnnotationValue oneValidatorClassReference : validatorClassReferences ) { supported.addAll( getSupportedValidationTargets( oneValidatorClassReference ) ); } if ( supported.isEmpty() ) { // case of built-in validation constraints supported.add( AnnotationProcessorValidationTarget.ANNOTATED_ELEMENT ); } return supported; } /** * Check that a constraint has at most one cross-parameter validator that resolves to Object or Object[]. * * @param constraintAnnotationType the constraint type * @return {@code ConstraintCheckResult#MULTIPLE_VALIDATORS_FOUND} if the constraint has more than one cross-parameter validator, * {@code ConstraintCheckResult#DISALLOWED} if the constraint has one cross-parameter validator with a wrong generic type, * {@code ConstraintCheckResult#ALLOWED} otherwise */ public ConstraintCheckResult checkCrossParameterTypes(DeclaredType constraintAnnotationType) { AnnotationMirror constraintMetaAnnotation = getConstraintMetaAnnotation( constraintAnnotationType ); List<? extends AnnotationValue> validatorClassReferences = getValidatorClassesFromConstraintMetaAnnotation( constraintMetaAnnotation ); AnnotationValue crossParameterValidator = null; for ( AnnotationValue oneValidatorClassReference : validatorClassReferences ) { Set<AnnotationProcessorValidationTarget> targets = getSupportedValidationTargets( oneValidatorClassReference ); if ( targets.contains( AnnotationProcessorValidationTarget.PARAMETERS ) ) { if ( crossParameterValidator != null ) { return ConstraintCheckResult.MULTIPLE_VALIDATORS_FOUND; } crossParameterValidator = oneValidatorClassReference; } } if ( crossParameterValidator != null ) { // Cross-parameter contraints must accept Object or Object[] as validated type final TypeMirror objectMirror = annotationApiHelper.getMirrorForType( Object.class ); TypeMirror type = determineSupportedType( crossParameterValidator ); Boolean supported = type.accept( new TypeKindVisitor6<Boolean, Void>() { @Override public Boolean visitArray(ArrayType t, Void p) { return typeUtils.isSameType( t.getComponentType(), objectMirror ); } @Override public Boolean visitDeclared(DeclaredType t, Void p) { return typeUtils.isSameType( t, objectMirror ); } }, null ); if ( !supported ) { return ConstraintCheckResult.DISALLOWED; } } return ConstraintCheckResult.ALLOWED; } public Types getTypeUtils() { return typeUtils; } // ================================== // private API below // ================================== /** * Checks whether the given annotation mirror represents a constraint * annotation or not. That's the case, if the given mirror is annotated with * the {@code @Constraint} meta-annotation (which is only allowed at * annotation declarations). * * @param annotationMirror The annotation mirror of interest. * * @return True, if the given mirror represents a constraint annotation * type, false otherwise. */ private boolean isConstraintAnnotation(AnnotationMirror annotationMirror) { return isConstraintAnnotation( annotationMirror.getAnnotationType().asElement() ); } /** * Checks whether the given annotation mirror represents the {@code @Constraint} * meta-annotation or not. * * @param annotationMirror The annotation mirror of interest. * * @return True, if the given mirror represents the {@code @Constraint} meta-annotation * type, false otherwise. */ private boolean isConstraintMetaAnnotation(AnnotationMirror annotationMirror) { return getName( annotationMirror.getAnnotationType() ).contentEquals( BeanValidationTypes.CONSTRAINT ); } /** * Checks whether the given annotation mirror represents a multi-valued * constraint such as {@link javax.validation.constraints.Pattern.List}. * That is the case if the annotation has an array-typed attribute with name * "value", that exclusively contains constraint annotations. * * @param annotationMirror The annotation mirror of interest. * * @return True, if the given mirror represents a multi-valued constraint, * false otherwise. */ private boolean isMultiValuedConstraint(AnnotationMirror annotationMirror) { List<? extends AnnotationValue> annotationArrayValue = annotationApiHelper.getAnnotationArrayValue( annotationMirror, "value" ); // a multi-valued constraint must have at least one value if ( annotationArrayValue.isEmpty() ) { return false; } for ( AnnotationValue oneAnnotationValue : annotationArrayValue ) { Boolean isConstraintAnnotation = oneAnnotationValue.accept( new SimpleAnnotationValueVisitor6<Boolean, Void>() { @Override public Boolean visitAnnotation( AnnotationMirror a, Void p) { return isConstraintAnnotation( a.getAnnotationType().asElement() ); } }, null ); //TODO GM: have all parts of the array to be constraint annotations? if ( Boolean.TRUE != isConstraintAnnotation ) { return false; } } return true; } /** * Checks whether the given mirror represents the {@code Valid} annotation. * * @param annotationMirror The annotation mirror of interest. * * @return True, if the given mirror represents the {@code Valid} annotation, false * otherwise. */ private boolean isGraphValidationAnnotation( AnnotationMirror annotationMirror) { return getName( annotationMirror.getAnnotationType() ).contentEquals( BeanValidationTypes.VALID ); } /** * Checks whether the given mirror represents the {@code @GroupSequence} annotation. * * @param annotationMirror the annotation mirror of interest * @return true, if the given mirror represents the {@code @GroupSequence} annotation, false otherwise */ private boolean isGroupSequenceAnnotation( AnnotationMirror annotationMirror) { return getName( annotationMirror.getAnnotationType() ).contentEquals( BeanValidationTypes.GROUP_SEQUENCE ); } /** * Checks whether the given mirror represents the {@code @GroupSequenceProvider} annotation. * * @param annotationMirror The annotation mirror of interest. * * @return True, if the given mirror represents the {@code @GroupSequenceProvider} annotation, false * otherwise. */ private boolean isGroupSequenceProviderAnnotation( AnnotationMirror annotationMirror) { return getName( annotationMirror.getAnnotationType() ).contentEquals( HibernateValidatorTypes.GROUP_SEQUENCE_PROVIDER ); } private ConstraintCheckResult checkComposingConstraints(DeclaredType constraintAnnotationType, TypeMirror typeOfAnnotatedElement) { for ( AnnotationMirror oneComposingConstraint : getComposingConstraints( constraintAnnotationType ) ) { ConstraintCheckResult annotationCheckResult = checkConstraint( oneComposingConstraint.getAnnotationType(), typeOfAnnotatedElement ); if ( annotationCheckResult != ConstraintCheckResult.ALLOWED ) { return annotationCheckResult; } } return ConstraintCheckResult.ALLOWED; } private ConstraintCheckResult checkSupportedTypes(DeclaredType constraintAnnotationType, TypeMirror typeOfAnnotatedElement) { Set<TypeMirror> supportedTypes = getSupportedTypes( constraintAnnotationType ); Set<TypeMirror> assignableTypes = getAssignableTypes( supportedTypes, typeOfAnnotatedElement ); //found more than one matching validator if ( assignableTypes.size() > 1 ) { return ConstraintCheckResult.MULTIPLE_VALIDATORS_FOUND; } //found exactly one matching validator OR the constraint is completely composed else if ( assignableTypes.size() == 1 || ( supportedTypes.size() == 0 && !getComposingConstraints( constraintAnnotationType ).isEmpty() ) ) { return ConstraintCheckResult.ALLOWED; } //found no matching validator else { return ConstraintCheckResult.DISALLOWED; } } /** * <p> * Returns a set with the types supported by the given constraint annotation * type (as either retrieved from the validators given in the * {@code @Constraint} meta-annotation or from the map with built-in * constraints. * </p> * <p> * * @param constraintAnnotationType A constraint annotation type. * * @return A set with the supported types. */ private Set<TypeMirror> getSupportedTypes(DeclaredType constraintAnnotationType) { Name key = getName( constraintAnnotationType ); Set<TypeMirror> supportedTypes = supportedTypesByConstraint.get( key ); // create a mapping for the given annotation type if required if ( supportedTypes == null ) { supportedTypes = determineSupportedTypes( constraintAnnotationType ); supportedTypesByConstraint.put( key, supportedTypes ); } return supportedTypes; } private AnnotationProcessorValidationTarget resolveImplicitValidationTarget(ExecutableElement e) { if ( e.getParameters().isEmpty() ) { return AnnotationProcessorValidationTarget.ANNOTATED_ELEMENT; } else if ( e.getReturnType().getKind() == TypeKind.VOID ) { return AnnotationProcessorValidationTarget.PARAMETERS; } return null; } /** * Determines the {@code AnnotationProcessorConstraintTarget} of the annotation. * * @param annotation the cross-parameter annotation * @return the {@code AnnotationProcessorConstraintTarget}, if it defined using the 'validationAppliesTo()' attribute of the annotation, the default value if a valid 'validationAppliesTo()' attribute is defined, or null */ private AnnotationProcessorConstraintTarget getConstraintTarget(AnnotationMirror annotation) { AnnotationValue validationAppliesTo = annotationApiHelper.getAnnotationValue( annotation, "validationAppliesTo" ); if ( validationAppliesTo == null ) { // validationAppliesTo not found on the annotation for ( Element e : annotation.getAnnotationType().asElement().getEnclosedElements() ) { Boolean isValidationAppliesToMethod = e.accept( new ElementKindVisitor6<Boolean, Void>() { @Override public Boolean visitExecutableAsMethod(ExecutableElement e, Void p) { if ( e.getSimpleName().contentEquals( "validationAppliesTo" ) ) { return true; } return false; } }, null ); if ( Boolean.TRUE.equals( isValidationAppliesToMethod ) ) { // validationAppliesTo method is present, so the default value is returned (IMPLICIT) return AnnotationProcessorConstraintTarget.IMPLICIT; } } // cannot find the ConstraintTarget return null; } return validationAppliesTo.accept( new SimpleAnnotationValueVisitor6<AnnotationProcessorConstraintTarget, Void>() { private final TypeMirror constraintTargetMirror = annotationApiHelper.getDeclaredTypeByName( BeanValidationTypes.CONSTRAINT_TARGET ); @Override public AnnotationProcessorConstraintTarget visitEnumConstant(VariableElement c, Void p) { if ( typeUtils.isSameType( c.asType(), constraintTargetMirror ) ) { return AnnotationProcessorConstraintTarget.valueOf( c.getSimpleName().toString() ); } return null; } }, null ); } private Set<TypeMirror> determineSupportedTypes(DeclaredType constraintAnnotationType) { return determineSupportedTypes( constraintAnnotationType, AnnotationProcessorValidationTarget.ANNOTATED_ELEMENT ); } private Set<TypeMirror> determineSupportedTypes(DeclaredType constraintAnnotationType, AnnotationProcessorValidationTarget target) { //the Constraint meta-annotation at the type declaration, e.g. "@Constraint(validatedBy = CheckCaseValidator.class)" AnnotationMirror constraintMetaAnnotation = getConstraintMetaAnnotation( constraintAnnotationType ); //the validator classes, e.g. [CheckCaseValidator.class] List<? extends AnnotationValue> validatorClassReferences = getValidatorClassesFromConstraintMetaAnnotation( constraintMetaAnnotation ); Set<TypeMirror> supportedTypes = CollectionHelper.newHashSet(); for ( AnnotationValue oneValidatorClassReference : validatorClassReferences ) { if ( isValidationTargetSupported( oneValidatorClassReference, target ) ) { TypeMirror supportedType = determineSupportedType( oneValidatorClassReference ); supportedTypes.add( supportedType ); } } return supportedTypes; } private TypeMirror determineSupportedType(AnnotationValue validatorClassReference) { // contains the bindings of the type parameters from the implemented // ConstraintValidator interface, e.g. "ConstraintValidator<CheckCase, String>" TypeMirror constraintValidatorImplementation = getConstraintValidatorSuperType( validatorClassReference ); return constraintValidatorImplementation.accept( new TypeKindVisitor6<TypeMirror, Void>() { @Override public TypeMirror visitDeclared(DeclaredType constraintValidatorImplementation, Void p) { // 2nd type parameter contains the data type supported by current validator class, e.g. "String" return constraintValidatorImplementation.getTypeArguments().get( 1 ); } }, null ); } private boolean isValidationTargetSupported(AnnotationValue oneValidatorClassReference, AnnotationProcessorValidationTarget target) { return getSupportedValidationTargets( oneValidatorClassReference ).contains( target ); } private Set<AnnotationProcessorValidationTarget> getSupportedValidationTargets(AnnotationValue oneValidatorClassReference) { // determine the class that could contain the @SupportedValidationTarget annotation. TypeMirror validatorClass = oneValidatorClassReference.accept( new SimpleAnnotationValueVisitor6<TypeMirror, Void>() { @Override public TypeMirror visitType(TypeMirror t, Void p) { return t; } }, null ); DeclaredType validatorType = validatorClass.accept( new TypeKindVisitor6<DeclaredType, Void>() { @Override public DeclaredType visitDeclared(DeclaredType t, Void p) { return t; } }, null ); DeclaredType supportedValidationTargetType = annotationApiHelper.getDeclaredTypeByName( BeanValidationTypes.SUPPORTED_VALIDATION_TARGET ); AnnotationMirror supportedTargetDecl = null; for ( AnnotationMirror mirr : validatorType.asElement().getAnnotationMirrors() ) { if ( typeUtils.isSameType( mirr.getAnnotationType(), supportedValidationTargetType ) ) { supportedTargetDecl = mirr; break; } } EnumSet<AnnotationProcessorValidationTarget> allowedTargets = EnumSet.noneOf( AnnotationProcessorValidationTarget.class ); if ( supportedTargetDecl == null ) { // If @SupportedValidationTarget is not present, the ConstraintValidator targets the (returned) element annotated by the constraint. allowedTargets.add( AnnotationProcessorValidationTarget.ANNOTATED_ELEMENT ); } else { List<? extends AnnotationValue> values = annotationApiHelper.getAnnotationArrayValue( supportedTargetDecl, "value" ); for ( AnnotationValue val : values ) { AnnotationProcessorValidationTarget target = val.accept( new SimpleAnnotationValueVisitor6<AnnotationProcessorValidationTarget, Void>() { @Override public AnnotationProcessorValidationTarget visitEnumConstant(VariableElement c, Void p) { return AnnotationProcessorValidationTarget.valueOf( c.getSimpleName().toString() ); } }, null ); allowedTargets.add( target ); } } return allowedTargets; } private TypeMirror getConstraintValidatorSuperType(AnnotationValue oneValidatorClassReference) { TypeMirror type = oneValidatorClassReference.accept( new SimpleAnnotationValueVisitor6<TypeMirror, Void>() { @Override public TypeMirror visitType(TypeMirror t, Void p) { return t; } }, null ); List<? extends TypeMirror> superTypes = typeUtils.directSupertypes( type ); List<TypeMirror> nextSuperTypes = CollectionHelper.newArrayList(); //follow the type hierarchy upwards, until we have found the ConstraintValidator IF while ( !superTypes.isEmpty() ) { for ( TypeMirror oneSuperType : superTypes ) { if ( getName( typeUtils.asElement( oneSuperType ) ).contentEquals( BeanValidationTypes.CONSTRAINT_VALIDATOR ) ) { return oneSuperType; } nextSuperTypes.addAll( typeUtils.directSupertypes( oneSuperType ) ); } superTypes = nextSuperTypes; nextSuperTypes = CollectionHelper.newArrayList(); } //HV-293: Actually this should never happen, as we can have only ConstraintValidator implementations //here. The Eclipse JSR 269 implementation unfortunately doesn't always create the type hierarchy //properly though. //TODO GM: create and report an isolated test case throw new IllegalStateException( "Expected type " + type + " to implement javax.validation.ConstraintValidator, but it doesn't." ); } /** * Retrieves the {@code @Constraint} meta-annotation from the given * constraint annotation. * * @param annotationType A constraint type. * * @return The Constraint meta-annotation. * * @throws IllegalArgumentException If the given constraint annotation type isn't annotated with * the {@code @Constraint} meta-annotation. */ private AnnotationMirror getConstraintMetaAnnotation(DeclaredType annotationType) { List<? extends AnnotationMirror> annotationMirrors = annotationType.asElement().getAnnotationMirrors(); AnnotationMirror constraintMetaAnnotation = annotationApiHelper.getMirror( annotationMirrors, BeanValidationTypes.CONSTRAINT ); if ( constraintMetaAnnotation == null ) { throw new IllegalArgumentException( "Given type " + annotationType + " isn't a constraint annotation type." ); } return constraintMetaAnnotation; } private List<? extends AnnotationValue> getValidatorClassesFromConstraintMetaAnnotation(AnnotationMirror constraintMetaAnnotation) { AnnotationValue validatedBy = annotationApiHelper.getAnnotationValue( constraintMetaAnnotation, "validatedBy" ); return validatedBy.accept( new SimpleAnnotationValueVisitor6<List<? extends AnnotationValue>, Void>() { @Override public List<? extends AnnotationValue> visitArray(List<? extends AnnotationValue> values, Void p) { return values; } }, null ); } /** * Returns a set containing those types from the given set of types to which * the given type is assignable. If the given type can be assigned to * multiple types from the same inheritance hierarchy (e.g. Collection and * Set), only the "lowest" one (e.g. Set) will be part of the result. * * @param types The types to check. * @param type The type to check. * * @return A set containing those types from the given set of types to which * the given type is assignable. */ private Set<TypeMirror> getAssignableTypes(Set<TypeMirror> types, TypeMirror type) { Set<TypeMirror> theValue = CollectionHelper.newHashSet(); for ( TypeMirror supportedType : types ) { if ( typeUtils.isAssignable( type, supportedType ) ) { theValue.add( supportedType ); } } return annotationApiHelper.keepLowestTypePerHierarchy( theValue ); } private void registerAllowedTypesForBuiltInConstraint(String annotationType, Class<?>... types) { registerAllowedTypesForBuiltInConstraint( annotationType, asMirrors( types ) ); } private void registerAllowedTypesForBuiltInConstraint(String annotationType, String... typeNames) { registerAllowedTypesForBuiltInConstraint( annotationType, asMirrors( typeNames ) ); } private void registerAllowedTypesForBuiltInConstraint(String annotationType, List<TypeMirror> supportedTypes) { DeclaredType annotation = annotationApiHelper.getDeclaredTypeByName( annotationType ); if ( annotation == null ) { return; } Name key = getName( annotation ); Set<TypeMirror> types = supportedTypesByConstraint.get( key ); if ( types == null ) { supportedTypesByConstraint.put( key, new HashSet<>( supportedTypes ) ); } else { types.addAll( supportedTypes ); } } private Name getName(DeclaredType type) { return getName( type.asElement() ); } private Name getName(Element element) { if ( element.getKind() == ElementKind.CLASS || element.getKind() == ElementKind.INTERFACE || element.getKind() == ElementKind.ANNOTATION_TYPE ) { return ( (TypeElement) element ).getQualifiedName(); } else { return element.getSimpleName(); } } private Set<AnnotationMirror> getComposingConstraints(DeclaredType constraintAnnotationType) { Name key = getName( constraintAnnotationType ); Set<AnnotationMirror> composingConstraints = composingConstraintsByConstraints.get( key ); if ( composingConstraints != null ) { return composingConstraints; } composingConstraints = CollectionHelper.newHashSet(); List<? extends AnnotationMirror> annotationMirrors = constraintAnnotationType.asElement() .getAnnotationMirrors(); for ( AnnotationMirror oneAnnotationMirror : annotationMirrors ) { AnnotationType annotationType = getAnnotationType( oneAnnotationMirror ); if ( annotationType == AnnotationType.CONSTRAINT_ANNOTATION ) { composingConstraints.add( oneAnnotationMirror ); } else if ( annotationType == AnnotationType.MULTI_VALUED_CONSTRAINT_ANNOTATION ) { List<? extends AnnotationValue> value = annotationApiHelper.getAnnotationArrayValue( oneAnnotationMirror, "value" ); for ( AnnotationValue annotationValue : value ) { composingConstraints.add( (AnnotationMirror) annotationValue ); } } } composingConstraintsByConstraints.put( key, composingConstraints ); return composingConstraints; } private List<TypeMirror> asMirrors(Class<?>... types) { List<TypeMirror> mirrors = new ArrayList<>( types.length ); for ( Class<?> oneType : types ) { TypeMirror oneMirror = annotationApiHelper.getMirrorForType( oneType ); if ( oneMirror != null ) { mirrors.add( oneMirror ); } } return mirrors; } private List<TypeMirror> asMirrors(String... typeNames) { List<TypeMirror> mirrors = new ArrayList<>( typeNames.length ); for ( String oneTypeName : typeNames ) { TypeMirror oneMirror = annotationApiHelper.getDeclaredTypeByName( oneTypeName ); if ( oneMirror != null ) { mirrors.add( oneMirror ); } } return mirrors; } }