001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2025 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018///////////////////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks.coding;
021
022import java.util.Optional;
023
024import com.puppycrawl.tools.checkstyle.StatelessCheck;
025import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
026import com.puppycrawl.tools.checkstyle.api.DetailAST;
027import com.puppycrawl.tools.checkstyle.api.TokenTypes;
028
029/**
030 * <div>
031 * Checks for redundant null checks with the instanceof operator.
032 * </div>
033 *
034 * <p>
035 * The instanceof operator inherently returns false when the left operand is null,
036 * making explicit null checks redundant in boolean expressions with instanceof.
037 * </p>
038 *
039 * @since 10.25.0
040 */
041@StatelessCheck
042public class UnnecessaryNullCheckWithInstanceOfCheck extends AbstractCheck {
043
044    /**
045     * The error message key for reporting unnecessary null checks.
046     */
047    public static final String MSG_UNNECESSARY_NULLCHECK = "unnecessary.nullcheck.with.instanceof";
048
049    @Override
050    public int[] getDefaultTokens() {
051        return getRequiredTokens();
052    }
053
054    @Override
055    public int[] getAcceptableTokens() {
056        return getRequiredTokens();
057    }
058
059    @Override
060    public int[] getRequiredTokens() {
061        return new int[] {TokenTypes.LITERAL_INSTANCEOF};
062    }
063
064    @Override
065    public void visitToken(DetailAST instanceofNode) {
066        findUnnecessaryNullCheck(instanceofNode)
067                .ifPresent(violationNode -> log(violationNode, MSG_UNNECESSARY_NULLCHECK));
068    }
069
070    /**
071     * Checks for an unnecessary null check within a logical AND expression.
072     *
073     * @param instanceOfNode the AST node representing the instanceof expression
074     * @return the identifier if the check is redundant, otherwise {@code null}
075     */
076    private static Optional<DetailAST> findUnnecessaryNullCheck(DetailAST instanceOfNode) {
077        DetailAST currentParent = instanceOfNode;
078
079        while (currentParent.getParent().getType() == TokenTypes.LAND) {
080            currentParent = currentParent.getParent();
081        }
082        return findRedundantNullCheck(currentParent, instanceOfNode)
083            .map(DetailAST::getFirstChild);
084    }
085
086    /**
087     * Finds a redundant null check in a logical AND expression combined with an instanceof check.
088     *
089     * @param logicalAndNode the root node of the logical AND expression
090     * @param instanceOfNode the instanceof expression node
091     * @return the AST node representing the redundant null check, or null if not found
092     */
093    private static Optional<DetailAST> findRedundantNullCheck(DetailAST logicalAndNode,
094        DetailAST instanceOfNode) {
095
096        DetailAST nullCheckNode = null;
097        final DetailAST instanceOfIdent = instanceOfNode.findFirstToken(TokenTypes.IDENT);
098
099        if (instanceOfIdent != null
100            && !containsVariableDereference(logicalAndNode, instanceOfIdent.getText())) {
101
102            DetailAST currentChild = logicalAndNode.getFirstChild();
103            while (currentChild != null) {
104                if (isNotEqual(currentChild)
105                        && isNullCheckRedundant(instanceOfIdent, currentChild)) {
106                    nullCheckNode = currentChild;
107                }
108                else if (nullCheckNode == null && currentChild.getType() == TokenTypes.LAND) {
109                    nullCheckNode = findRedundantNullCheck(currentChild, instanceOfNode)
110                            .orElse(null);
111                }
112                currentChild = currentChild.getNextSibling();
113            }
114        }
115        return Optional.ofNullable(nullCheckNode);
116    }
117
118    /**
119     * Checks if the given AST node contains a method call or field access
120     * on the specified variable.
121     *
122     * @param node the AST node to check
123     * @param variableName the name of the variable
124     * @return true if the variable is dereferenced, false otherwise
125     */
126    private static boolean containsVariableDereference(DetailAST node, String variableName) {
127
128        boolean found = false;
129
130        if (node.getType() == TokenTypes.DOT
131            || node.getType() == TokenTypes.METHOD_CALL || node.getType() == TokenTypes.LAND) {
132
133            DetailAST firstChild = node.getFirstChild();
134
135            while (firstChild != null) {
136                if (variableName.equals(firstChild.getText())
137                        && firstChild.getNextSibling().getType() != TokenTypes.ELIST
138                            || containsVariableDereference(firstChild, variableName)) {
139                    found = true;
140                    break;
141                }
142                firstChild = firstChild.getNextSibling();
143            }
144        }
145        return found;
146    }
147
148    /**
149     * Checks if the given AST node represents a {@code !=} (not equal) operator.
150     *
151     * @param node the AST node to check
152     * @return {@code true} if the node is a not equal operator, otherwise {@code false}
153     */
154    private static boolean isNotEqual(DetailAST node) {
155        return node.getType() == TokenTypes.NOT_EQUAL;
156    }
157
158    /**
159     * Checks if the given AST node is a null literal.
160     *
161     * @param node AST node to check
162     * @return true if the node is a null literal, false otherwise
163     */
164    private static boolean isNullLiteral(DetailAST node) {
165        return node.getType() == TokenTypes.LITERAL_NULL;
166    }
167
168    /**
169     * Determines if the null check is redundant with the instanceof check.
170     *
171     * @param instanceOfIdent the identifier from the instanceof check
172     * @param nullCheckNode the node representing the null check
173     * @return true if the null check is unnecessary, false otherwise
174     */
175    private static boolean isNullCheckRedundant(DetailAST instanceOfIdent,
176        final DetailAST nullCheckNode) {
177
178        final DetailAST nullCheckIdent = nullCheckNode.findFirstToken(TokenTypes.IDENT);
179        return nullCheckIdent != null
180                && (isNullLiteral(nullCheckNode.getFirstChild().getNextSibling())
181                    || isNullLiteral(nullCheckNode.getFirstChild()))
182                && instanceOfIdent.getText().equals(nullCheckIdent.getText());
183    }
184}