/* * 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.annotationparameters; import java.util.List; 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.TypeElement; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Types; import org.hibernate.validator.ap.checks.ConstraintCheckIssue; 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.TypeNames; /** * Checks that the GroupSequence definition is valid. * <ul> * <li>the class list contains only interfaces (except for the hosting bean in the case of default group sequence redefinition)</li> * <li>the defined group sequence is expandable (no cyclic definition)</li> * <li>the group sequence does not extend other interfaces</li> * </ul> * * @author Marko Bekhta * @author Guillaume Smet */ public class GroupSequenceCheck extends AnnotationParametersAbstractCheck { private final Types typeUtils; private final ConstraintHelper constraintHelper; public GroupSequenceCheck(AnnotationApiHelper annotationApiHelper, Types typeUtils, ConstraintHelper constraintHelper) { super( annotationApiHelper, TypeNames.BeanValidationTypes.GROUP_SEQUENCE ); this.typeUtils = typeUtils; this.constraintHelper = constraintHelper; } @Override protected Set<ConstraintCheckIssue> doCheck(Element element, AnnotationMirror annotation) { List<? extends AnnotationValue> annotationValue = annotationApiHelper.getAnnotationArrayValue( annotation, "value" ); TypeElement annotatedElement = (TypeElement) element; boolean isDefaultGroupSequenceRedefinition = false; Set<String> qualifiedNames = CollectionHelper.newHashSet(); Set<ConstraintCheckIssue> issues = CollectionHelper.newHashSet(); for ( AnnotationValue value : annotationValue ) { TypeMirror typeMirror = (TypeMirror) value.getValue(); // 1. if it is a class, it has to be the hosting bean class (for default group sequence redefinition) if ( annotationApiHelper.isClass( typeMirror ) && redefinesDefaultGroupSequence( annotatedElement, typeMirror ) ) { isDefaultGroupSequenceRedefinition = true; } else if ( !annotationApiHelper.isInterface( typeMirror ) ) { issues.add( ConstraintCheckIssue.error( element, annotation, "INVALID_GROUP_SEQUENCE_VALUE_NOT_INTERFACES" ) ); // it is not an interface or a class used to redefine the default group, we will not do any other // checks on it and report the error straight away continue; } // 2. an interface should not be declared multiple times in a group sequence String qualifiedName = ( (TypeElement) typeUtils.asElement( typeMirror ) ).getQualifiedName().toString(); if ( qualifiedNames.contains( qualifiedName ) ) { issues.add( ConstraintCheckIssue.error( element, annotation, "INVALID_GROUP_SEQUENCE_VALUE_MULTIPLE_DECLARATIONS_OF_THE_SAME_INTERFACE", qualifiedName ) ); } qualifiedNames.add( qualifiedName ); } // 3. if the group sequence extends other interfaces we need to produce a warning if ( ElementKind.INTERFACE.equals( annotatedElement.getKind() ) && !annotatedElement.getInterfaces().isEmpty() ) { issues.add( ConstraintCheckIssue.warning( element, annotation, "INVALID_GROUP_SEQUENCE_EXTEND_INTERFACES" ) ); } // 4. if the annotated element is a class and the group sequence does not contain it then the group sequence redefinition is invalid if ( ElementKind.CLASS.equals( annotatedElement.getKind() ) && !isDefaultGroupSequenceRedefinition ) { issues.add( ConstraintCheckIssue.error( element, annotation, "INVALID_GROUP_SEQUENCE_VALUE_MISSING_HOSTING_BEAN_DECLARATION" ) ); } // 5. the defined group sequence is expandable (no cyclic definition) ConstraintCheckIssue cyclicDefinitionIssue = checkForCyclicDefinition( CollectionHelper.newHashSet(), annotatedElement.asType(), annotatedElement, annotation ); if ( cyclicDefinitionIssue != null ) { issues.add( cyclicDefinitionIssue ); } return issues; } /** * Checks if there are any cyclic definitions for a given type element {@link TypeElement}. * * @param processedTypes for initial call of this functions this can be an empty {@link Set}. It contains all * already processed types. Used for recursion * @param currentTypeMirror the current type mirror under investigation * @param originalElement an original annotated element to be used in reported error if there is one * @param annotation an original annotation mirror passed to a check method to be used in reported error if there is * one * @return {@code null} if there was no cyclic issue found, {@link ConstraintCheckIssue} describing a cyclic issue * if one was found. */ private ConstraintCheckIssue checkForCyclicDefinition(Set<TypeMirror> processedTypes, TypeMirror currentTypeMirror, TypeElement originalElement, AnnotationMirror annotation) { // if the passed currentTypeMirror is not a class/interface then we simply ignore it as such errors should have // already been processed earlier. if ( !TypeKind.DECLARED.equals( currentTypeMirror.getKind() ) ) { return null; } if ( processedTypes.contains( currentTypeMirror ) ) { if ( !redefinesDefaultGroupSequence( originalElement, currentTypeMirror ) ) { return ConstraintCheckIssue.error( originalElement, annotation, "INVALID_GROUP_SEQUENCE_VALUE_CYCLIC_DEFINITION" ); } else { return null; } } else { processedTypes.add( currentTypeMirror ); // check if there is a @GroupSequence annotation on a currentTypeMirror and check its values if present List<? extends AnnotationValue> annotationArrayValue = annotationApiHelper.getAnnotationArrayValue( getGroupSequence( currentTypeMirror ), "value" ); if ( annotationArrayValue != null ) { for ( AnnotationValue value : annotationArrayValue ) { TypeMirror groupTypeMirror = (TypeMirror) value.getValue(); ConstraintCheckIssue issue = checkForCyclicDefinition( processedTypes, groupTypeMirror, originalElement, annotation ); if ( issue != null ) { return issue; } } } // check if currentTypeMirror extends any other interfaces and if so check if they are not in the sequence already for ( TypeMirror extendedInterfaceTypeMirror : ( (TypeElement) typeUtils.asElement( currentTypeMirror ) ).getInterfaces() ) { ConstraintCheckIssue issue = checkForCyclicDefinition( processedTypes, extendedInterfaceTypeMirror, originalElement, annotation ); if ( issue != null ) { return issue; } } } return null; } /** * Find a {@code javax.validation.GroupSequence} annotation if one is present on given type ({@link TypeMirror}). */ private AnnotationMirror getGroupSequence(TypeMirror typeMirror) { // the annotation can be present only on TypeKind.DECLARED elements if ( TypeKind.DECLARED.equals( typeMirror.getKind() ) ) { for ( AnnotationMirror annotationMirror : typeUtils.asElement( typeMirror ).getAnnotationMirrors() ) { if ( ConstraintHelper.AnnotationType.GROUP_SEQUENCE_ANNOTATION.equals( constraintHelper.getAnnotationType( annotationMirror ) ) ) { return annotationMirror; } } } return null; } /** * Check if the given {@link TypeMirror} redefines the default group sequence for the annotated class. * <p> * Note that it is only the case if the annotated element is a class. */ private boolean redefinesDefaultGroupSequence(TypeElement annotatedElement, TypeMirror typeMirror) { return ElementKind.CLASS.equals( annotatedElement.getKind() ) && typeUtils.isSameType( annotatedElement.asType(), typeMirror ); } }