/* * 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.checks; import java.util.Collections; import java.util.Set; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; 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.AnnotationApiHelper; import org.hibernate.validator.ap.util.CollectionHelper; import org.hibernate.validator.ap.util.ConstraintHelper; import org.hibernate.validator.ap.util.ConstraintHelper.AnnotationProcessorValidationTarget; import org.hibernate.validator.ap.util.ConstraintHelper.ConstraintCheckResult; import org.hibernate.validator.ap.util.TypeNames.BeanValidationTypes; /** * Checks that a cross-parameter constraint is defined correctly with reference to the specifications. * <ul> * <li>It must have at most one cross-parameter validator.</li> * <li>The cross-parameter validator must resolve to Object or Object[].</li> * <li>If the constraint is both normal and cross-parameter, it must define a 'validationAppliesTo()' attribute.</li> * <li>The 'validationAppliesTo' method, if any, must return a {@code ConstraintTarget}.</li> * <li>The 'validationAppliesTo' method, if any, must declare {@code ConstraintTarget#IMPLICIT} as default return value.</li> * </ul> * * @author Nicola Ferraro */ public class CrossParameterConstraintCheck extends AbstractConstraintCheck { private final AnnotationApiHelper annotationApiHelper; private final ConstraintHelper constraintHelper; private final Types typeUtils; public CrossParameterConstraintCheck(AnnotationApiHelper annotationApiHelper, ConstraintHelper constraintHelper, Types typeUtils) { this.annotationApiHelper = annotationApiHelper; this.constraintHelper = constraintHelper; this.typeUtils = typeUtils; } @Override public Set<ConstraintCheckIssue> checkAnnotationType(TypeElement element, AnnotationMirror annotation) { // this check applies to constraint annotations if ( !constraintHelper.isConstraintAnnotation( element ) ) { return Collections.emptySet(); } DeclaredType elementType = element.asType().accept( new TypeKindVisitor6<DeclaredType, Void>() { @Override public DeclaredType visitDeclared(DeclaredType t, Void p) { return t; } }, null ); Set<AnnotationProcessorValidationTarget> targets = constraintHelper.getSupportedValidationTargets( elementType ); if ( !targets.contains( AnnotationProcessorValidationTarget.PARAMETERS ) ) { return Collections.emptySet(); } // Check cross parameter validators ConstraintCheckResult res = constraintHelper.checkCrossParameterTypes( elementType ); if ( res == ConstraintCheckResult.MULTIPLE_VALIDATORS_FOUND ) { return CollectionHelper.asSet( ConstraintCheckIssue.error( element, annotation, "CROSS_PARAMETER_CONSTRAINT_MULTIPLE_VALIDATORS", element.getSimpleName().toString() ) ); } else if ( res == ConstraintCheckResult.DISALLOWED ) { return CollectionHelper.asSet( ConstraintCheckIssue.error( element, annotation, "CROSS_PARAMETER_CONSTRAINT_VALIDATOR_HAS_INVALID_TYPE", element.getSimpleName() ) ); } // Check validationAppliesTo method ExecutableElement validationAppliesTo = getValidationAppliesToMethod( element ); if ( validationAppliesTo == null && targets.size() > 1 ) { // validationAppliesTo is required to let the user specify the constraint target return CollectionHelper.asSet( ConstraintCheckIssue.error( element, annotation, "CROSS_PARAMETER_VALIDATION_APPLIES_TO_REQUIRED", element.getSimpleName() ) ); } if ( validationAppliesTo != null ) { if ( !checkValidationAppliesToReturnType( validationAppliesTo ) ) { return CollectionHelper.asSet( ConstraintCheckIssue.error( element, annotation, "CROSS_PARAMETER_VALIDATION_APPLIES_TO_MUST_HAVE_CONSTRAINT_TARGET_RETURN_TYPE", element.getSimpleName() ) ); } else if ( !checkValidationAppliesToDefaultValue( validationAppliesTo ) ) { return CollectionHelper.asSet( ConstraintCheckIssue.error( element, annotation, "CROSS_PARAMETER_VALIDATION_APPLIES_TO_MUST_HAVE_IMPLICIT_DEFAULT_VALUE", element.getSimpleName() ) ); } } return Collections.emptySet(); } private boolean checkValidationAppliesToReturnType(ExecutableElement validationAppliesToMethod) { final DeclaredType constraintTargetType = annotationApiHelper.getDeclaredTypeByName( BeanValidationTypes.CONSTRAINT_TARGET ); // Check the return type return validationAppliesToMethod.getReturnType().accept( new TypeKindVisitor6<Boolean, Void>() { @Override public Boolean visitDeclared(DeclaredType t, Void p) { if ( typeUtils.isSameType( constraintTargetType, t ) ) { return true; } return false; } }, null ); } private boolean checkValidationAppliesToDefaultValue(ExecutableElement validationAppliesToMethod) { final DeclaredType constraintTargetType = annotationApiHelper.getDeclaredTypeByName( BeanValidationTypes.CONSTRAINT_TARGET ); // Check the return type return validationAppliesToMethod.getDefaultValue().accept( new SimpleAnnotationValueVisitor6<Boolean, Void>() { @Override public Boolean visitEnumConstant(VariableElement c, Void p) { if ( typeUtils.isSameType( constraintTargetType, c.asType() ) ) { return c.getSimpleName().contentEquals( "IMPLICIT" ); } return false; } }, null ); } private ExecutableElement getValidationAppliesToMethod(Element annotation) { for ( Element e : annotation.getEnclosedElements() ) { ExecutableElement method = e.accept( new ElementKindVisitor6<ExecutableElement, Void>() { @Override public ExecutableElement visitExecutableAsMethod(ExecutableElement e, Void p) { if ( e.getSimpleName().contentEquals( "validationAppliesTo" ) ) { return e; } return null; } }, null ); if ( method != null ) { return method; } } return null; } }