Jsr305AnnotationsCheck.java

////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2019 the original author or authors.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
////////////////////////////////////////////////////////////////////////////////

package com.github.sevntu.checkstyle.checks.coding;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import antlr.collections.AST;
import com.github.sevntu.checkstyle.SevntuUtil;
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.FullIdent;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;

/**
 * <p>
 * The <a href="https://jcp.org/en/jsr/detail?id=305">Jsr305 annotations</a> (annotations for
 * software defect detection) contain a subset of "nullness" annotations that can be used to mark
 * parameters as possibly null ({@code @Nullable}) or always non-null ({@code @Nonnull}), function
 * return values as to be checked for null ({@code @CheckForNull}) or always non-null
 * ({@code @Nonnull}) including defaults on class level ({@code @ParametersAreNonnullByDefault},
 * {@code @ParametersAreNullableByDefault}, {@code @ReturnValuesAreNonnullByDefault}).
 * </p>
 * <p>
 * Using these annotations a programmer can declare how the code is meant to behave, and static code
 * analysis (like e.g. FindBugs) can be used to verify that this is actually true. Also these
 * annotations help others understanding code more easily, e.g. if confrontend with an annotated
 * interface the necessity for null checks can easily be deducted from the annotations.
 * </p>
 * <p>
 * The Jsr305AnnotationsCheck supports enforcing the following code style:
 * </p>
 * <ul>
 * <li>Every method declaration, implementation or lambda requires nullness annotations for all
 * parameters and return values except for primitive types (because a void or an int can never be
 * null anyway).</li>
 * <li>The annotation can be made directly or applied through either inheritance from an already
 * annotated class or a annotation for class-wide defaults.</li>
 * <li>Annotations need to make sense. For instance, a class-scope annotation cannot be used for a
 * method, and a method annotation cannot be used for a class.</li>
 * <li>In overridden methods, the following rule applies (regardless of what was annotated in the
 * parent method): For parameter definitions {@code @Nonnull} annotation is always illegal because
 * being less "nullable" cannot be assumed for a parameter in an inherited method. Conversely
 * {@code @Nullable} is always allowed. For return values it is the other way round:
 * {@code @CheckForNull} is always illegal while {@code @Nonnull} is always legal.</li>
 * </ul>
 * <p>
 * The following configuration properties are supported:
 * </p>
 * <dl>
 * <dt>{@code packages = com.github.sevntu.pkg1,com.github.sevntu.pkg2}</dt>
 * <dd>Activate this check for a list of parent packages and their children.</dd>
 * <dt>{@code excludePackages = com.github.sevntu.pkg1.sub1,com.github.sevntu.pkg1.sub2}</dt>
 * <dd>Set packages excluded from checking. This setting can be useful if under the parent package
 * set with "packages" there are subpackages which should not be checked.</dd>
 * <dt>{@code allowOverridingReturnValue = true}</dt>
 * <dd>Annotating return values "@CheckForNull" in overridden methods is flagged as an error. When
 * setting the this property to true, this error will be ignored (useful for upgrading).</dd>
 * <dt>{@code allowOverridingParameters = true}</dt>
 * <dd>Annotating parameters "@Nonnull" in overridden methods is flagged as an error. When setting
 * this property to true, this error will be ignored (useful for upgrading).</dd>
 * </dl>
 * <p>
 * Example code:
 * </p>
 * <p>
 * Configure the check so that it scans the packages of the classes we want to run this on:
 * </p>
 *
 * <pre>
 * &lt;module name=&quot;Jsr305Annotations&quot;&gt;
 *   &lt;property name=&quot;packages&quot; value=&quot;org,com&quot;/&gt;
 *   &lt;/module&gt;
 * </pre>
 *
 *
 * <pre>
 * // Example 1: a class without any class-level annotations
 * class Class1 {
 *     &#64;CheckForNull // Violation: obj2 not annotated!
 *     String method1(@Nullable Object obj1, Object obj2) {
 *         return "";
 *     }
 *
 *     // Violation: return value not annotated
 *     String method2() {
 *         return "";
 *     }
 * }
 *
 * // Example 2: a class with class-level annotations for parameters
 * &#64;ParametersAreNonnullByDefault
 * class Class2 {
 *     &#64;CheckForNull // Legal
 *     String method1(Object obj1, Object obj2) {
 *         return "";
 *     }
 *
 *     &#64;Nonnull // Legal
 *     String method2(Object obj1, @Nullable Object obj2) {
 *         return "";
 *     }
 *
 *     &#64;Nonnull // Violation, redefinition of obj2's nullness
 *     String method3(Object obj1, @Nonnull Object obj2) {
 *         return "";
 *     }
 *
 *     // Violation: return value not annotated
 *     String method4() {
 *         return "";
 *     }
 * }
 *
 * // Example 3: a class overriding some methods
 * class Class3 implements Interface1 {
 *     &#64;Override // Legal
 *     public Object method1(Object obj1, Object obj2) {
 *         return "";
 *     }
 *
 *     &#64;Override
 *     &#64;Nonnull // Legal, return value becomes less "nullable"
 *     public Object method2(Object obj1, Object obj2) {
 *         return "";
 *     }
 *
 *     &#64;Override // Violation: Setting a parameter to non-null in an overridden method
 *     public Object method3(@Nonnull Object obj1, Object obj2) {
 *         return "";
 *     }
 *
 *     &#64;Override // Legal: parameter obj2 becomes more "nullable"
 *     public Object method4(Object obj1, @Nullable Object obj2) {
 *         return "";
 *     }
 *
 *     &#64;Override
 *     &#64;CheckForNull // Violation: return value becomes more "nullable"
 *     public Object method5() {
 *         return "";
 *     }
 * }
 * </pre>
 */
public class Jsr305AnnotationsCheck extends AbstractCheck {

    /** Key for error message. */
    public static final String MSG_ILLEGAL_CLASS_LEVEL_ANNOTATION =
            "jsr305.illegal.class.level.annotation";

    /** Key for error message. */
    public static final String MSG_CONTRADICTING_CLASS_LEVEL_ANNOTATIONS =
            "jsr305.contradicting.class.level.annotations";

    /** Key for error message. */
    public static final String MSG_PARAM_DEFINITIONS_WITH_CHECK =
            "jsr305.param.definitions.with.check.annotation";

    /** Key for error message. */
    public static final String MSG_PARAM_DEFINITION_WITH_OVERRIDE =
            "jsr305.param.definition.with.override.annotation";

    /** Key for error message. */
    public static final String MSG_PARAM_DEFINITION_WITH_NONNULL_BY_DEFAULT =
            "jsr305.param.definition.with.nonnull.by.default.annotation";

    /** Key for error message. */
    public static final String MSG_PARAM_DEFINITION_WITH_NULLABLE_BY_DEFAULT =
            "jsr305.param.definition.with.nullable.by.default.annotation";

    /** Key for error message. */
    public static final String MSG_PARAM_DEFINITION_WITH_RETURN_DEFAULT =
            "jsr305.param.definition.with.return.values.default.annotation";

    /** Key for error message. */
    public static final String MSG_PARAM_NONNULL_AND_NULLABLE =
            "jsr305.param.nonnull.and.nullable.annotation";

    /** Key for error message. */
    public static final String MSG_PRIMITIVES_WITH_NULLNESS_ANNOTATION =
            "jsr305.primitives.with.nullness.annotation";

    /** Key for error message. */
    public static final String MSG_OVERRIDDEN_WITH_INCREASED_CONSTRAINT =
            "jsr305.overridden.definitions.with.increased.param.constraint";

    /** Key for error message. */
    public static final String MSG_REDUNDANT_NONNULL_PARAM_ANNOTATION =
            "jsr305.redundant.nonnull.param.annotation";

    /** Key for error message. */
    public static final String MSG_REDUNDANT_NULLABLE_PARAM_ANNOTATION =
            "jsr305.redundant.nullable.param.annotation";

    /** Key for error message. */
    public static final String MSG_PARAMETER_WITHOUT_NULLNESS_ANNOTATION =
            "jsr305.parameter.without.nullness.annotation";

    /** Key for error message. */
    public static final String MSG_RETURN_VALUE_WITH_NONNULL_BY_DEFAULT =
            "jsr305.return.value.with.nonnull.by.default.annotation";

    /** Key for error message. */
    public static final String MSG_RETURN_VALUE_WITH_NULLABLE =
            "jsr305.return.value.with.nullable.annotation";

    /** Key for error message. */
    public static final String MSG_CONTRADICTING_RETURN_VALUE_ANNOTATIONS =
            "jsr305.contradicting.return.value.annotations";

    /** Key for error message. */
    public static final String MSG_OVERRIDDEN_METHOD_WITH_CHECK_RETURN_VALUE =
            "jsr305.overridden.method.with.check.return.value.annotation";

    /** Key for error message. */
    public static final String MSG_REDUNDANT_NONNULL_BY_DEFAULT_ANNOTATION =
            "jsr305.redundant.nonnull.by.default.annotation";

    /** Key for error message. */
    public static final String MSG_REDUNDANT_NULLABLE_BY_DEFAULT_ANNOTATION =
            "jsr305.redundant.nullable.by.default.annotation";

    /** Key for error message. */
    public static final String MSG_VOID_WITH_CHECK_RETURN_VALUE_ANNOTATION =
            "jsr305.void.with.check.return.value.annotation";

    /** Key for error message. */
    public static final String MSG_REDUNDANT_NONNULL_RETURN_ANNOTATION =
            "jsr305.redundant.nonnull.return.annotation";

    /** Key for error message. */
    public static final String MSG_RETURN_WITHOUT_NULLNESS_ANNOTATION =
            "jsr305.return.without.nullness.annotation";

    /** Key for error message. */
    public static final String MSG_OVERRIDDEN_METHODS_ALLOW_ONLY_NONNULL =
            "jsr305.overridden.methods.allow.only.nonnull";

    /** Key for error message. */
    public static final String MSG_NEED_TO_INHERIT_PARAM_ANNOTATIONS =
            "jsr305.need.to.inherit.param.annotations";

    /** Key for error message. */
    public static final String MSG_CONSTRUCTOR_WITH_RETURN_ANNOTATION =
            "jsr305.constructor.with.return.annotation";

    /** Package name. */
    private static final String PKG_JAVAX_ANNOTATION = "javax.annotation";

    /**
     * Class NullnessAnnotation. The annotations we consider as "nullness-relevant".
     */
    protected enum NullnessAnnotation {

        /** Override. */
        OVERRIDE("Override", "java.lang"),
        /** CheckForNull. */
        CHECK_FOR_NULL("CheckForNull", PKG_JAVAX_ANNOTATION),
        /** Nullable. */
        NULLABLE("Nullable", PKG_JAVAX_ANNOTATION),
        /** Nonnull. */
        NONNULL("Nonnull", PKG_JAVAX_ANNOTATION),
        /** CheckReturnValue. */
        CHECK_RETURN_VALUE("CheckReturnValue", PKG_JAVAX_ANNOTATION),
        /** ParametersAreNonnullByDefault. */
        PARAMETERS_ARE_NONNULL_BY_DEFAULT("ParametersAreNonnullByDefault", PKG_JAVAX_ANNOTATION),
        /** ParametersAreNullableByDefault. */
        PARAMETERS_ARE_NULLABLE_BY_DEFAULT("ParametersAreNullableByDefault", PKG_JAVAX_ANNOTATION),
        /** ReturnValuesAreNonnullByDefault. */
        RETURN_VALUES_ARE_NONNULL_BY_DEFAULT("ReturnValuesAreNonnullByDefault",
                "edu.umd.cs.findbugs.annotations");

        /** The annotation's name. */
        private final String annotationName;

        /** The annotation's fully qualified class name. */
        private final String fullyQualifiedClassName;

        /**
         * Constructor.
         * @param annotationName
         *        the annotation's name
         * @param packageName
         *        the package name
         */
        NullnessAnnotation(final String annotationName, final String packageName) {
            this.annotationName = annotationName;
            this.fullyQualifiedClassName = packageName + "." + annotationName;
        }

    }

    /** The map of annotations against their respective names. */
    private static final Map<String, NullnessAnnotation> STRING2ANNOTATION =
            createString2AnnotationMap();

    /** The modifiers of interest. */
    private static final int[] DEFAULT_MODIFIERS = {
        TokenTypes.PARAMETER_DEF,
        TokenTypes.METHOD_DEF,
        TokenTypes.PACKAGE_DEF,
        TokenTypes.CTOR_DEF,
        TokenTypes.CLASS_DEF,
        TokenTypes.INTERFACE_DEF,
        TokenTypes.ENUM_DEF,
    };

    /** Parameter: packages to check. */
    private String[] packages = new String[0];
    /** Parameter: packages to exclude from checking. */
    private String[] excludePackages = new String[0];
    /** Parameter: overriding return value annotations allowed. */
    private boolean allowOverridingReturnValue;
    /** Parameter: overriding parameter annotations allowed. */
    private boolean allowOverridingParameter;

    /** State, is a package excluded. */
    private boolean packageExcluded;

    @Override
    public final int[] getDefaultTokens() {
        return getAcceptableTokens();
    }

    @Override
    public final int[] getRequiredTokens() {
        return new int[0];
    }

    @Override
    public final int[] getAcceptableTokens() {
        return DEFAULT_MODIFIERS.clone();
    }

    @Override
    public final void visitToken(final DetailAST ast) {
        if (ast.getType() == TokenTypes.PACKAGE_DEF) {
            final DetailAST nameAST = ast.getLastChild().getPreviousSibling();
            packageExcluded = isPackageExcluded(FullIdent.createFullIdent(nameAST));
        }
        else if (!packageExcluded) {
            final AbstractJsr305Handler handler = handleDefinition(ast);
            if (handler != null) {
                handler.check();
            }
        }
    }

    /**
     * Sets the included packages parameter.
     * @param packageNames
     *        the package names, comma separated
     */
    public void setPackages(final String... packageNames) {
        packages = transformToUnique(packageNames);
    }

    /**
     * Maps annotations to their respective names.
     * @return the map
     */
    private static Map<String, NullnessAnnotation> createString2AnnotationMap() {
        final Map<String, NullnessAnnotation> result = new HashMap<>();

        for (final NullnessAnnotation annotation : NullnessAnnotation.values()) {
            result.put(annotation.annotationName, annotation);
            result.put(annotation.fullyQualifiedClassName, annotation);
        }

        return Collections.unmodifiableMap(result);
    }

    /**
     * Sets the excluded packages parameter.
     * @param packageNames
     *        the package names, comma separated
     */
    public void setExcludePackages(final String... packageNames) {
        excludePackages = transformToUnique(packageNames);
    }

    /**
     * Removes duplicates from an array of strings.
     * @param input
     *        the array
     * @return a new, duplicate-free array
     */
    private static String[] transformToUnique(final String... input) {
        final Set<String> inputSet = new HashSet<>(Arrays.asList(input));
        return inputSet.toArray(new String[0]);
    }

    /**
     * Checks whether a package is excluded.
     * @param fullIdent
     *        the identifier
     * @return true if yes
     */
    protected boolean isPackageExcluded(final FullIdent fullIdent) {
        Boolean result = null;
        final String packageName = fullIdent.getText();
        for (final String excludesPackageName : excludePackages) {
            if (packageName.startsWith(excludesPackageName)) {
                result = true;
                break;
            }
        }
        if (result == null) {
            for (final String includePackageName : packages) {
                if (packageName.startsWith(includePackageName)) {
                    result = false;
                    break;
                }
            }
        }
        if (result == null) {
            result = true;
        }
        return result;
    }

    /**
     * Returns the check to use for a given definition.
     * @param ast
     *        the ast
     * @return the check.
     */
    protected AbstractJsr305Handler handleDefinition(final DetailAST ast) {

        // no definition in catch clause
        final DetailAST parent = ast.getParent();
        AbstractJsr305Handler result = null;
        if (parent == null || parent.getType() != TokenTypes.LITERAL_CATCH) {
            // search modifiers
            final int type = ast.getType();
            switch (type) {
                case TokenTypes.METHOD_DEF:
                    result = new MethodJsr305Handler(ast);
                    break;
                case TokenTypes.CTOR_DEF:
                    result = new ConstructorJsr305Handler(ast);
                    break;
                case TokenTypes.PARAMETER_DEF:
                    result = new ParameterJsr305Handler(ast);
                    break;
                case TokenTypes.CLASS_DEF:
                case TokenTypes.INTERFACE_DEF:
                case TokenTypes.ENUM_DEF:
                    result = new ClassJsr305Handler(ast);
                    break;
                default:
                    SevntuUtil.reportInvalidToken(ast.getType());
                    break;
            }
        }
        return result;
    }

    /**
     * Sets the parameter for allowing overriding return values.
     * @param newAllowOverridingReturnValue
     *        true if yes
     */
    public void setAllowOverridingReturnValue(final boolean newAllowOverridingReturnValue) {
        this.allowOverridingReturnValue = newAllowOverridingReturnValue;
    }

    /**
     * Sets the parameter for allowing overriding parameters.
     * @param newAllowOverridingParameter
     *        true if yes
     */
    public void setAllowOverridingParameter(final boolean newAllowOverridingParameter) {
        this.allowOverridingParameter = newAllowOverridingParameter;
    }

    /**
     * Find the nullness annotations.
     * @param ast
     *        the ast
     * @return the annotations.
     */
    private static Set<NullnessAnnotation> findAnnotations(final DetailAST ast) {
        final Set<NullnessAnnotation> result = new HashSet<>();

        final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
        for (AST child = modifiers.getFirstChild(); child != null; child = child.getNextSibling()) {
            if (child.getType() == TokenTypes.ANNOTATION) {
                addNextNullnessAnnotation(result, (DetailAST) child);
            }
        }

        return result;
    }

    /**
     * Adds the nullness annotation from the argument's first token if any.
     * @param result
     *        the result
     * @param ast
     *        the ast
     */
    private static void addNextNullnessAnnotation(final Set<NullnessAnnotation> result,
            DetailAST ast) {
        final DetailAST identifier = ast.findFirstToken(TokenTypes.IDENT);
        final String annotationName = identifier.getText();
        final NullnessAnnotation annotation = STRING2ANNOTATION.get(annotationName);
        if (annotation != null) {
            result.add(annotation);
        }
    }

    /**
     * Class ClassJsr305Handler. Checks a class.
     */
    private final class ClassJsr305Handler extends AbstractJsr305Handler {

        /**
         * Constructor.
         * @param ast
         *        the ast
         */
        protected ClassJsr305Handler(final DetailAST ast) {
            super(ast);
        }

        /**
         * Run the actual check.
         */
        @Override
        protected void runcheck() {
            checkContainsAny(MSG_ILLEGAL_CLASS_LEVEL_ANNOTATION,
                    NullnessAnnotation.CHECK_FOR_NULL, NullnessAnnotation.CHECK_RETURN_VALUE,
                    NullnessAnnotation.NONNULL, NullnessAnnotation.NULLABLE);
            checkContainsAll(MSG_CONTRADICTING_CLASS_LEVEL_ANNOTATIONS,
                    NullnessAnnotation.PARAMETERS_ARE_NONNULL_BY_DEFAULT,
                    NullnessAnnotation.PARAMETERS_ARE_NULLABLE_BY_DEFAULT);
        }

    }

    /**
     * Class ParameterJsr305Handler. Checks a parameter.
     */
    private final class ParameterJsr305Handler extends AbstractJsr305Handler {

        /**
         * Constructor.
         * @param ast
         *        the ast
         */
        protected ParameterJsr305Handler(final DetailAST ast) {
            super(ast);
        }

        /**
         * Run the actual check.
         */
        @Override
        protected void runcheck() {
            checkContainsAny(MSG_PARAM_DEFINITIONS_WITH_CHECK,
                    NullnessAnnotation.CHECK_FOR_NULL, NullnessAnnotation.CHECK_RETURN_VALUE);
            checkContainsAny(MSG_PARAM_DEFINITION_WITH_OVERRIDE,
                    NullnessAnnotation.OVERRIDE);
            checkContainsAny(MSG_PARAM_DEFINITION_WITH_NONNULL_BY_DEFAULT,
                    NullnessAnnotation.PARAMETERS_ARE_NONNULL_BY_DEFAULT);
            checkContainsAny(MSG_PARAM_DEFINITION_WITH_NULLABLE_BY_DEFAULT,
                    NullnessAnnotation.PARAMETERS_ARE_NULLABLE_BY_DEFAULT);
            checkContainsAny(MSG_PARAM_DEFINITION_WITH_RETURN_DEFAULT,
                    NullnessAnnotation.RETURN_VALUES_ARE_NONNULL_BY_DEFAULT);
            checkContainsAll(MSG_PARAM_NONNULL_AND_NULLABLE,
                    NullnessAnnotation.NONNULL, NullnessAnnotation.NULLABLE);

            if (isPrimitiveType()) {
                checkContainsAny(MSG_PRIMITIVES_WITH_NULLNESS_ANNOTATION,
                        NullnessAnnotation.CHECK_FOR_NULL, NullnessAnnotation.NONNULL,
                        NullnessAnnotation.NULLABLE);
            }
            else {
                final NullnessAnnotation firstAncestorAnnotation =
                        getParentMethodOrClassAnnotation(
                                    NullnessAnnotation.PARAMETERS_ARE_NONNULL_BY_DEFAULT,
                                    NullnessAnnotation.PARAMETERS_ARE_NULLABLE_BY_DEFAULT);
                final boolean isMethodOverridden = isMethodOverridden();
                final boolean parametersAreNonnullByDefault = firstAncestorAnnotation
                        == NullnessAnnotation.PARAMETERS_ARE_NONNULL_BY_DEFAULT;
                final boolean parametersAreNullableByDefault = firstAncestorAnnotation
                        == NullnessAnnotation.PARAMETERS_ARE_NULLABLE_BY_DEFAULT;

                if (isMethodOverridden && !allowOverridingParameter) {
                    checkContainsAny(MSG_OVERRIDDEN_WITH_INCREASED_CONSTRAINT,
                            NullnessAnnotation.NONNULL);
                }
                if (parametersAreNonnullByDefault) {
                    checkContainsAny(MSG_REDUNDANT_NONNULL_PARAM_ANNOTATION,
                            NullnessAnnotation.NONNULL);
                }
                if (parametersAreNullableByDefault) {
                    checkContainsAny(MSG_REDUNDANT_NULLABLE_PARAM_ANNOTATION,
                            NullnessAnnotation.NULLABLE);
                }

                if (!isMethodOverridden && !parametersAreNonnullByDefault
                        && !parametersAreNullableByDefault) {
                    checkContainsNone(MSG_PARAMETER_WITHOUT_NULLNESS_ANNOTATION,
                            NullnessAnnotation.NONNULL, NullnessAnnotation.NULLABLE);
                }
            }
        }
    }

    /**
     * Class AbstractMethodJsr305Handler. A check on a method or constructor (special case).
     */
    private abstract class AbstractMethodJsr305Handler extends AbstractJsr305Handler {

        /**
         * Constructor.
         * @param ast
         *        the ast
         */
        protected AbstractMethodJsr305Handler(final DetailAST ast) {
            super(ast);
        }

        /**
         * Run the actual check.
         */
        @Override
        protected void runcheck() {
            checkContainsAll(MSG_CONTRADICTING_CLASS_LEVEL_ANNOTATIONS,
                    NullnessAnnotation.PARAMETERS_ARE_NONNULL_BY_DEFAULT,
                    NullnessAnnotation.PARAMETERS_ARE_NULLABLE_BY_DEFAULT);
            runReturnAnnotationCheck();
        }

        /**
         * Run annotation check for return.
         */
        protected abstract void runReturnAnnotationCheck();

    }

    /**
     * Class MethodJsr305Handler. A check for a method.
     */
    private final class MethodJsr305Handler extends AbstractMethodJsr305Handler {

        /**
         * Constructor.
         * @param ast
         *        the ast
         */
        protected MethodJsr305Handler(final DetailAST ast) {
            super(ast);
        }

        /**
         * Run annotation check for return.
         */
        @Override
        protected void runReturnAnnotationCheck() {
            checkContainsAny(MSG_RETURN_VALUE_WITH_NONNULL_BY_DEFAULT,
                    NullnessAnnotation.RETURN_VALUES_ARE_NONNULL_BY_DEFAULT);
            checkContainsAny(MSG_RETURN_VALUE_WITH_NULLABLE,
                    NullnessAnnotation.NULLABLE);
            checkContainsAll(MSG_CONTRADICTING_RETURN_VALUE_ANNOTATIONS, NullnessAnnotation.NONNULL,
                    NullnessAnnotation.CHECK_FOR_NULL);
            checkContainsAll(MSG_OVERRIDDEN_METHOD_WITH_CHECK_RETURN_VALUE,
                    NullnessAnnotation.CHECK_RETURN_VALUE, NullnessAnnotation.OVERRIDE);
            checkRedundancyDueToClassLevelAnnotation(MSG_REDUNDANT_NONNULL_BY_DEFAULT_ANNOTATION,
                    NullnessAnnotation.PARAMETERS_ARE_NONNULL_BY_DEFAULT);
            checkRedundancyDueToClassLevelAnnotation(MSG_REDUNDANT_NULLABLE_BY_DEFAULT_ANNOTATION,
                    NullnessAnnotation.PARAMETERS_ARE_NULLABLE_BY_DEFAULT);

            if (isVoid()) {
                checkContainsAny(MSG_VOID_WITH_CHECK_RETURN_VALUE_ANNOTATION,
                        NullnessAnnotation.CHECK_RETURN_VALUE);
            }
            if (isPrimitiveType()) {
                checkContainsAny(MSG_PRIMITIVES_WITH_NULLNESS_ANNOTATION,
                        NullnessAnnotation.CHECK_FOR_NULL, NullnessAnnotation.NONNULL,
                        NullnessAnnotation.NULLABLE);
            }
            else {
                final NullnessAnnotation annotation = getParentMethodOrClassAnnotation(
                        NullnessAnnotation.RETURN_VALUES_ARE_NONNULL_BY_DEFAULT);
                final boolean returnValuesAreNonnullByDefault = annotation
                        == NullnessAnnotation.RETURN_VALUES_ARE_NONNULL_BY_DEFAULT;
                final boolean isMethodOverridden = isMethodOverridden();

                if (returnValuesAreNonnullByDefault) {
                    if (!isMethodOverridden) {
                        checkContainsAny(MSG_REDUNDANT_NONNULL_RETURN_ANNOTATION,
                                NullnessAnnotation.NONNULL);
                    }
                }
                else {
                    checkContainsNone(MSG_RETURN_WITHOUT_NULLNESS_ANNOTATION,
                            NullnessAnnotation.CHECK_FOR_NULL, NullnessAnnotation.NONNULL,
                            NullnessAnnotation.OVERRIDE);
                }

                if (isMethodOverridden && !allowOverridingReturnValue) {
                    checkContainsAny(MSG_OVERRIDDEN_METHODS_ALLOW_ONLY_NONNULL,
                            NullnessAnnotation.CHECK_FOR_NULL, NullnessAnnotation.NULLABLE);
                }

                if (isMethodOverridden) {
                    checkContainsAny(MSG_NEED_TO_INHERIT_PARAM_ANNOTATIONS,
                            NullnessAnnotation.PARAMETERS_ARE_NONNULL_BY_DEFAULT);
                }
            }
        }

    }

    /**
     * Class ConstructorJsr305Handler. Check a constructor.
     */
    private final class ConstructorJsr305Handler extends AbstractMethodJsr305Handler {

        /**
         * Constructor.
         * @param ast
         *        the ast
         */
        protected ConstructorJsr305Handler(final DetailAST ast) {
            super(ast);
        }

        /**
         * Check for return type nullness annotations (which are illegal).
         */
        @Override
        protected void runReturnAnnotationCheck() {
            checkContainsAny(MSG_CONSTRUCTOR_WITH_RETURN_ANNOTATION,
                    NullnessAnnotation.CHECK_FOR_NULL, NullnessAnnotation.CHECK_RETURN_VALUE,
                    NullnessAnnotation.NONNULL, NullnessAnnotation.NULLABLE,
                    NullnessAnnotation.OVERRIDE);
        }

    }

    /**
     * An abstract check, the base for checks on parameters, methods, classes. Class
     * AbstractJsr305Check.
     */
    public abstract class AbstractJsr305Handler {

        /** Has an error been found. */
        private boolean errorFound;
        /** The found annotations. */
        private final Set<NullnessAnnotation> annotations;
        /** The ast. */
        private final DetailAST ast;

        /**
         * Construtor.
         * @param ast
         *        the ast
         */
        protected AbstractJsr305Handler(final DetailAST ast) {
            this.ast = ast;
            this.errorFound = false;
            annotations = findAnnotation();
        }

        /**
         * Run the actual check.
         */
        public final void check() {
            runcheck();
        }

        /**
         * Run the actual check.
         */
        protected abstract void runcheck();

        /**
         * Emits an error if any of the given annotations are found.
         * @param msg
         *        the error message to emit
         * @param search
         *        the annotations to look for
         */
        protected void checkContainsAny(final String msg, final NullnessAnnotation... search) {
            if (!errorFound && containsAny(search)) {
                error(msg);
            }
        }

        /**
         * Check whether any of the given annotations are found.
         * @param search
         *        the annotations to look for
         * @return true if yes
         */
        protected boolean containsAny(final NullnessAnnotation... search) {
            Boolean result = null;
            if (this.annotations.isEmpty()) {
                result = false;
            }
            else {
                for (final NullnessAnnotation obj : search) {
                    if (this.annotations.contains(obj)) {
                        result = true;
                        break;
                    }
                }
            }
            if (result == null) {
                result = false;
            }
            return result;
        }

        /**
         * Emits an error if all given annotations are found.
         * @param msg
         *        the error message to emit
         * @param search
         *        the annotations to look for
         */
        protected void checkContainsAll(final String msg, final NullnessAnnotation... search) {
            if (!errorFound && containsAll(search)) {
                error(msg);
            }
        }

        /**
         * Emits an error if both this and the parent class have redundant nullness annotations.
         * @param msg
         *        the error message to emit
         * @param search
         *        the annotations to look for
         */
        protected void checkRedundancyDueToClassLevelAnnotation(final String msg,
                final NullnessAnnotation... search) {
            if (!errorFound) {
                for (final NullnessAnnotation nullnessAnnotation : search) {
                    final boolean thisIsAnnotated = this.annotations.contains(nullnessAnnotation);
                    final boolean parentIsAnnotated =
                            getParentMethodOrClassAnnotation(nullnessAnnotation) != null;
                    if (thisIsAnnotated && parentIsAnnotated) {
                        error(msg);
                        break;
                    }
                }
            }
        }

        /**
         * Check whether all the given annotations are present.
         * @param search
         *        the annotations to look for
         * @return true if yes
         */
        protected boolean containsAll(final NullnessAnnotation... search) {
            Boolean result = null;
            if (this.annotations.isEmpty()) {
                // an empty list of annotations can never contain all
                result = false;
            }
            else {
                for (final NullnessAnnotation obj : search) {
                    if (!this.annotations.contains(obj)) {
                        result = false;
                        break;
                    }
                }
            }
            if (result == null) {
                result = true;
            }
            return result;
        }

        /**
         * Make sure that none of the given annotations are present.
         * @param msg
         *        the error message to emit if one of the given annotations was found
         * @param search
         *        the annotations to look for
         */
        protected void checkContainsNone(final String msg,
                final NullnessAnnotation... search) {
            if (!errorFound && !containsAny(search)) {
                error(msg);
            }
        }

        /**
         * Logs an error.
         *
         * @param msg
         *        the message
         */
        protected void reportError(final String msg) {
            log(ast, msg);
        }

        /**
         * Handle an error (log and set 'errorFound' to 'true').
         *
         * @param msg
         *        the error message
         */
        protected void error(final String msg) {
            reportError(msg);
            errorFound = true;
        }

        /**
         * Is the current symbol a primitive type.
         * @return true if yes
         */
        protected boolean isPrimitiveType() {
            final DetailAST parameterType = ast.findFirstToken(TokenTypes.TYPE);
            final boolean result;
            final DetailAST identToken = parameterType.getFirstChild();

            switch (identToken.getType()) {
                case TokenTypes.LITERAL_BOOLEAN:
                case TokenTypes.LITERAL_INT:
                case TokenTypes.LITERAL_LONG:
                case TokenTypes.LITERAL_SHORT:
                case TokenTypes.LITERAL_BYTE:
                case TokenTypes.LITERAL_CHAR:
                case TokenTypes.LITERAL_VOID:
                case TokenTypes.LITERAL_DOUBLE:
                case TokenTypes.LITERAL_FLOAT:
                    result = !isArrayOrElipsis(parameterType);
                    break;
                default:
                    result = false;
            }
            return result;
        }

        /**
         * Checks whether token is array or elipsis.
         * @param identToken
         *        the token
         * @return true if yes
         */
        protected boolean isArrayOrElipsis(final DetailAST identToken) {
            final DetailAST next = identToken.getNextSibling();
            final boolean result;
            switch (next.getType()) {
                case TokenTypes.ARRAY_DECLARATOR:
                case TokenTypes.ELLIPSIS:
                    result = true;
                    break;
                default:
                    result = false;
            }
            return result;
        }

        /**
         * Is the current symbol of void type.
         * @return true if yes
         */
        protected boolean isVoid() {
            final DetailAST parameterType = ast.findFirstToken(TokenTypes.TYPE);
            final boolean result;
            final DetailAST identToken = parameterType.getFirstChild();
            result = identToken.getType() == TokenTypes.LITERAL_VOID;
            return result;
        }

        /**
         * Find the nullness annotations.
         * @return the annotations.
         */
        private Set<NullnessAnnotation> findAnnotation() {
            return findAnnotations(ast);
        }

        /**
         * Gets the nullness annotation for the parent method or class.
         * @param annotationsToLookFor
         *        the annotations to look for.
         * @return the annotation or null if none was found
         */
        protected NullnessAnnotation
                getParentMethodOrClassAnnotation(final NullnessAnnotation... annotationsToLookFor) {

            boolean finished = false;
            NullnessAnnotation result = null;
            for (DetailAST current = ast.getParent(); current != null && !finished; current =
                    current.getParent()) {
                final int tokenType = current.getType();

                if (isPossibleTokenTypeForNullnessAnnotations(tokenType)) {
                    final Set<NullnessAnnotation> foundAndLookedFor =
                            collectLookedForAnnotations(current, annotationsToLookFor);
                    if (foundAndLookedFor.size() == 1) {
                        result = foundAndLookedFor.iterator().next();
                        finished = true;
                    }
                    else if (!foundAndLookedFor.isEmpty()) {
                        finished = true;
                    }
                }
                // break on inner and anonymous classes/interfaces, we can't
                // handle inheritance correctly
                if (tokenType == TokenTypes.LITERAL_NEW || tokenType == TokenTypes.CLASS_DEF
                        || tokenType == TokenTypes.INTERFACE_DEF
                        || tokenType == TokenTypes.ENUM_DEF) {
                    finished = true;
                }
            }
            return result;
        }

        /**
         * Is the current tokenType a possible type for nullness annotations.
         * @param tokenType the token type
         * @return true if yes
         */
        private boolean isPossibleTokenTypeForNullnessAnnotations(final int tokenType) {
            return tokenType == TokenTypes.CLASS_DEF || tokenType == TokenTypes.INTERFACE_DEF
                    || tokenType == TokenTypes.METHOD_DEF || tokenType == TokenTypes.CTOR_DEF
                    || tokenType == TokenTypes.ENUM_DEF;
        }

        /**
         * Extracts all given annotations from the current ast.
         * @param current the current ast
         * @param annotationsToLookFor the annotations we are looking for
         * @return the annotations
         */
        protected Set<NullnessAnnotation> collectLookedForAnnotations(DetailAST current,
                final NullnessAnnotation... annotationsToLookFor) {
            final Set<NullnessAnnotation> foundAnnotations = findAnnotations(current);
            final Set<NullnessAnnotation> foundAndLookedFor = new HashSet<>();
            for (final NullnessAnnotation nullnessAnnotation : annotationsToLookFor) {
                if (foundAnnotations.contains(nullnessAnnotation)) {
                    foundAndLookedFor.add(nullnessAnnotation);
                }
            }
            return foundAndLookedFor;
        }

        /**
         * Is the current method overridden.
         * @return true if yes
         */
        protected boolean isMethodOverridden() {
            DetailAST current = ast;
            Boolean result = null;
            for (; current != null && result == null; current = current.getParent()) {
                switch (current.getType()) {
                    case TokenTypes.METHOD_DEF:
                        result = findAnnotations(current).contains(NullnessAnnotation.OVERRIDE);
                        break;
                    case TokenTypes.LAMBDA:
                        result = true;
                        break;
                    default:
                }
            }
            if (result == null) {
                result = false;
            }
            return result;
        }

    }

}