/* * 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.Set; 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.type.TypeMirror; 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.ConstraintHelper; /** * Abstract base class for {@link ClassCheck} implementations that check overridden methods. * * @author Marko Bekhta * @author Guillaume Smet */ public abstract class AbstractMethodOverrideCheck extends AbstractClassCheck { private static final String JAVA_LANG_OBJECT = "java.lang.Object"; private final Elements elementUtils; private final Types typeUtils; protected ConstraintHelper constraintHelper; public AbstractMethodOverrideCheck(Elements elementUtils, Types typeUtils, ConstraintHelper constraintHelper) { this.elementUtils = elementUtils; this.typeUtils = typeUtils; this.constraintHelper = constraintHelper; } @Override public Set<ConstraintCheckIssue> checkMethod(ExecutableElement currentMethod) { if ( !needToPerformAnyChecks( currentMethod ) ) { return Collections.emptySet(); } // find if there's a method that was overridden by the current one. MethodInheritanceTree overriddenMethodsTree = findAllOverriddenElements( currentMethod ); if ( !overriddenMethodsTree.hasOverriddenMethods() ) { return Collections.emptySet(); } return checkMethodInternal( currentMethod, overriddenMethodsTree ); } /** * Performs the check of a method. * * @param currentMethod a method to check * @param overriddenMethodsTree the {@link MethodInheritanceTree} of the method to check * * @return a set of issues if there are any, an empty set otherwise */ protected abstract Set<ConstraintCheckIssue> checkMethodInternal(ExecutableElement currentMethod, MethodInheritanceTree overriddenMethodsTree); /** * There can be situations in which no checks should be performed. In such cases we will not perform any work at all. * * @param currentMethod the method under investigation * * @return {@code true} if we should proceed with checks and {@code false} otherwise */ protected abstract boolean needToPerformAnyChecks(ExecutableElement currentMethod); /** * Find overridden methods from all super classes and all implemented interfaces. Results are returned as a {@link MethodInheritanceTree}. * * @param overridingMethod the method for which we want to find the overridden methods * * @return a {@link MethodInheritanceTree} containing overridden methods */ private MethodInheritanceTree findAllOverriddenElements(ExecutableElement overridingMethod) { TypeElement currentTypeElement = getEnclosingTypeElement( overridingMethod ); MethodInheritanceTree.Builder methodInheritanceTreeBuilder = new MethodInheritanceTree.Builder( overridingMethod ); collectOverriddenMethods( overridingMethod, currentTypeElement, methodInheritanceTreeBuilder ); return methodInheritanceTreeBuilder.build(); } /** * Collect all the overridden elements of the inheritance tree. * * @param overridingMethod the method for which we want to find the overridden methods * @param currentTypeElement the class we are analyzing * @param methodInheritanceTreeBuilder the method inheritance tree builder */ private void collectOverriddenMethods( ExecutableElement overridingMethod, TypeElement currentTypeElement, MethodInheritanceTree.Builder methodInheritanceTreeBuilder) { if ( isJavaLangObjectOrNull( currentTypeElement ) ) { return; } collectOverriddenMethodsInInterfaces( overridingMethod, currentTypeElement, methodInheritanceTreeBuilder ); TypeElement superclassTypeElement = (TypeElement) typeUtils.asElement( currentTypeElement.getSuperclass() ); if ( superclassTypeElement == null ) { return; } ExecutableElement overriddenMethod = getOverriddenMethod( overridingMethod, superclassTypeElement ); if ( overriddenMethod != null ) { methodInheritanceTreeBuilder.addOverriddenMethod( overridingMethod, overriddenMethod ); overridingMethod = overriddenMethod; } collectOverriddenMethods( overridingMethod, superclassTypeElement, methodInheritanceTreeBuilder ); } /** * Collect overridden methods in the interfaces of a given type. * * @param overridingMethod the method for which we want to find the overridden methods * @param currentTypeElement the class we are currently analyzing * @param methodInheritanceTreeBuilder the method inheritance tree builder */ private void collectOverriddenMethodsInInterfaces(ExecutableElement overridingMethod, TypeElement currentTypeElement, MethodInheritanceTree.Builder methodInheritanceTreeBuilder) { for ( TypeMirror implementedInterface : currentTypeElement.getInterfaces() ) { TypeElement interfaceTypeElement = (TypeElement) typeUtils.asElement( implementedInterface ); ExecutableElement overriddenMethod = getOverriddenMethod( overridingMethod, interfaceTypeElement ); ExecutableElement newOverridingMethod; if ( overriddenMethod != null ) { methodInheritanceTreeBuilder.addOverriddenMethod( overridingMethod, overriddenMethod ); newOverridingMethod = overriddenMethod; } else { newOverridingMethod = overridingMethod; } collectOverriddenMethodsInInterfaces( newOverridingMethod, interfaceTypeElement, methodInheritanceTreeBuilder ); } } /** * Find a method that is overridden by the one passed to this function. * * @param currentMethod the method for which we want to find the overridden methods * @param typeElement the class or interface analyzed * @return the overridden method if there is one, and {@code null} otherwise */ private ExecutableElement getOverriddenMethod(ExecutableElement currentMethod, TypeElement typeElement) { if ( typeElement == null ) { return null; } TypeElement enclosingTypeElement = getEnclosingTypeElement( currentMethod ); for ( Element element : elementUtils.getAllMembers( typeElement ) ) { if ( !element.getKind().equals( ElementKind.METHOD ) ) { continue; } if ( elementUtils.overrides( currentMethod, (ExecutableElement) element, enclosingTypeElement ) ) { return (ExecutableElement) element; } } return null; } /** * Find the {@link TypeElement} that contains a given {@link ExecutableElement}. * * @param currentMethod a method * @return the class/interface containing the method represented by a {@link TypeElement} */ private TypeElement getEnclosingTypeElement(ExecutableElement currentMethod) { return (TypeElement) typeUtils.asElement( currentMethod.getEnclosingElement().asType() ); } /** * Find a {@link String} representation of qualified name ({@link Name}) of corresponding {@link TypeElement} that * contains a given {@link ExecutableElement}. * * @param currentMethod a method * @return a class/interface qualified name represented by {@link String} to which a method belongs to */ protected String getEnclosingTypeElementQualifiedName(ExecutableElement currentMethod) { return getEnclosingTypeElement( currentMethod ).getQualifiedName().toString(); } /** * Determine if the provided {@link TypeElement} represents a {@link java.lang.Object} or is {@code null}. * * @param typeElement the element to check * @return {@code true} if the provided element is {@link java.lang.Object} or is {@code null}, {@code false} otherwise */ private boolean isJavaLangObjectOrNull(TypeElement typeElement) { return typeElement == null || JAVA_LANG_OBJECT.contentEquals( typeElement.getQualifiedName() ); } }