/*
* 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.classchecks;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import org.hibernate.validator.ap.checks.ConstraintCheckIssue;
import org.hibernate.validator.ap.util.CollectionHelper;
import org.hibernate.validator.ap.util.ConstraintHelper;
import org.hibernate.validator.ap.util.StringHelper;
/**
* Checks if the parameters of overridden and overriding methods have correctly placed annotations.
* Parameter constraints must not be strengthened in subtypes. The two rules implemented in this check are:
* <ul>
* <li>
* In subtypes (be it sub classes/interfaces or interface implementations), no parameter constraints may be declared on overridden or
* implemented methods, nor may parameters be marked for cascaded validation. This would pose a strengthening of preconditions to be fulfilled by the caller.
* </li>
* <li>
* If a subtype overrides/implements a method originally defined in several parallel types of the hierarchy (e.g. two interfaces not extending each other,
* or a class and an interface not implemented by said class), no parameter constraints may be declared for that method at all nor parameters be marked for
* cascaded validation. This again is to avoid an unexpected strengthening of preconditions to be fulfilled by the caller.
* </li>
* </ul>
*
* @author Marko Bekhta
*/
public class ParametersMethodOverrideCheck extends AbstractMethodOverrideCheck {
public ParametersMethodOverrideCheck(Elements elementUtils, Types typeUtils, ConstraintHelper constraintHelper) {
super( elementUtils, typeUtils, constraintHelper );
}
@Override
protected Set<ConstraintCheckIssue> checkMethodInternal(ExecutableElement currentMethod, MethodInheritanceTree methodInheritanceTree) {
// if you have 2 parallel hierarchies both of which implementing the same method,
// you can't define a parameter constraint at all for this method (anywhere in the hierarchy, not even once)
if ( methodInheritanceTree.hasParallelDefinitions() ) {
// it means we have more than one top level method and as a result there cannot be any annotations present in the hierarchy
Set<ConstraintCheckIssue> issues = CollectionHelper.newHashSet();
for ( ExecutableElement method : methodInheritanceTree.getAllMethods() ) {
if ( hasAnnotationsOnParameters( method ) ) {
issues.add( ConstraintCheckIssue.error(
currentMethod,
null,
"INCORRECT_METHOD_PARAMETERS_PARALLEL_IMPLEMENTATION_OVERRIDING",
getEnclosingTypeElementQualifiedNames( methodInheritanceTree.getTopLevelMethods() )
) );
}
}
if ( !issues.isEmpty() ) {
return issues;
}
}
// you can't define a constraint on a parameter of an overriding/implementing method or mark it for cascaded validation
if ( hasAnnotationsOnParameters( currentMethod ) ) {
Set<ExecutableElement> overriddenMethods = methodInheritanceTree.getOverriddenMethods();
return CollectionHelper.asSet( ConstraintCheckIssue.error(
currentMethod,
null,
"INCORRECT_METHOD_PARAMETERS_OVERRIDING",
getEnclosingTypeElementQualifiedNames( overriddenMethods )
) );
}
return Collections.emptySet();
}
@Override
protected boolean needToPerformAnyChecks(ExecutableElement currentMethod) {
// if the method doesn't have any parameters, there's no need to check it
return !currentMethod.getParameters().isEmpty();
}
/**
* Checks if a given method has any constraint or cascaded validation annotations on its parameters.
*
* @param method the method to check
* @return {@code true} if a constraint or cascaded annotations are present on any of the method parameters,
* {@code false} otherwise
*/
private boolean hasAnnotationsOnParameters(ExecutableElement method) {
for ( VariableElement parameter : method.getParameters() ) {
for ( AnnotationMirror annotationMirror : parameter.getAnnotationMirrors() ) {
ConstraintHelper.AnnotationType annotationType = constraintHelper.getAnnotationType( annotationMirror );
if ( ConstraintHelper.AnnotationType.CONSTRAINT_ANNOTATION.equals( annotationType )
|| ConstraintHelper.AnnotationType.MULTI_VALUED_CONSTRAINT_ANNOTATION.equals( annotationType )
|| ConstraintHelper.AnnotationType.GRAPH_VALIDATION_ANNOTATION.equals( annotationType ) ) {
return true;
}
}
}
return false;
}
/**
* Provides a formatted string containing qualified names of enclosing types of provided methods.
*
* @param methods a collection of methods to convert to string of qualified names of enclosing types
* @return string of qualified names of enclosing types
*/
private String getEnclosingTypeElementQualifiedNames(Set<ExecutableElement> methods) {
List<String> enclosingTypeElementQualifiedNames = CollectionHelper.newArrayList();
for ( ExecutableElement method : methods ) {
enclosingTypeElementQualifiedNames.add( getEnclosingTypeElementQualifiedName( method ) );
}
Collections.sort( enclosingTypeElementQualifiedNames );
return StringHelper.join( enclosingTypeElementQualifiedNames, ", " );
}
}