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 * <p>
040 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
041 * </p>
042 *
043 * <p>
044 * Violation Message Keys:
045 * </p>
046 *
047 * <ul>
048 *   <li>
049 *     {@code unnecessary.nullcheck.with.instanceof}
050 *   </li>
051 * </ul>
052 *
053 * @since 10.25.0
054 */
055@StatelessCheck
056public class UnnecessaryNullCheckWithInstanceOfCheck extends AbstractCheck {
057
058    /**
059     * The error message key for reporting unnecessary null checks.
060     */
061    public static final String MSG_UNNECESSARY_NULLCHECK = "unnecessary.nullcheck.with.instanceof";
062
063    @Override
064    public int[] getDefaultTokens() {
065        return getRequiredTokens();
066    }
067
068    @Override
069    public int[] getAcceptableTokens() {
070        return getRequiredTokens();
071    }
072
073    @Override
074    public int[] getRequiredTokens() {
075        return new int[] {TokenTypes.LITERAL_INSTANCEOF};
076    }
077
078    @Override
079    public void visitToken(DetailAST instanceofNode) {
080        findUnnecessaryNullCheck(instanceofNode)
081                .ifPresent(violationNode -> log(violationNode, MSG_UNNECESSARY_NULLCHECK));
082    }
083
084    /**
085     * Checks for an unnecessary null check within a logical AND expression.
086     *
087     * @param instanceOfNode the AST node representing the instanceof expression
088     * @return the identifier if the check is redundant, otherwise {@code null}
089     */
090    private static Optional<DetailAST> findUnnecessaryNullCheck(DetailAST instanceOfNode) {
091        DetailAST currentParent = instanceOfNode;
092
093        while (currentParent.getParent().getType() == TokenTypes.LAND) {
094            currentParent = currentParent.getParent();
095        }
096        return findRedundantNullCheck(currentParent, instanceOfNode)
097            .map(DetailAST::getFirstChild);
098    }
099
100    /**
101     * Finds a redundant null check in a logical AND expression combined with an instanceof check.
102     *
103     * @param logicalAndNode the root node of the logical AND expression
104     * @param instanceOfNode the instanceof expression node
105     * @return the AST node representing the redundant null check, or null if not found
106     */
107    private static Optional<DetailAST> findRedundantNullCheck(DetailAST logicalAndNode,
108        DetailAST instanceOfNode) {
109
110        DetailAST nullCheckNode = null;
111        final DetailAST instanceOfIdent = instanceOfNode.findFirstToken(TokenTypes.IDENT);
112
113        if (instanceOfIdent != null
114            && !containsVariableDereference(logicalAndNode, instanceOfIdent.getText())) {
115
116            DetailAST currentChild = logicalAndNode.getFirstChild();
117            while (currentChild != null) {
118                if (isNotEqual(currentChild)
119                        && isNullCheckRedundant(instanceOfIdent, currentChild)) {
120                    nullCheckNode = currentChild;
121                }
122                else if (nullCheckNode == null && currentChild.getType() == TokenTypes.LAND) {
123                    nullCheckNode = findRedundantNullCheck(currentChild, instanceOfNode)
124                            .orElse(null);
125                }
126                currentChild = currentChild.getNextSibling();
127            }
128        }
129        return Optional.ofNullable(nullCheckNode);
130    }
131
132    /**
133     * Checks if the given AST node contains a method call or field access
134     * on the specified variable.
135     *
136     * @param node the AST node to check
137     * @param variableName the name of the variable
138     * @return true if the variable is dereferenced, false otherwise
139     */
140    private static boolean containsVariableDereference(DetailAST node, String variableName) {
141
142        boolean found = false;
143
144        if (node.getType() == TokenTypes.DOT
145            || node.getType() == TokenTypes.METHOD_CALL || node.getType() == TokenTypes.LAND) {
146
147            DetailAST firstChild = node.getFirstChild();
148
149            while (firstChild != null) {
150                if (variableName.equals(firstChild.getText())
151                        && firstChild.getNextSibling().getType() != TokenTypes.ELIST
152                            || containsVariableDereference(firstChild, variableName)) {
153                    found = true;
154                    break;
155                }
156                firstChild = firstChild.getNextSibling();
157            }
158        }
159        return found;
160    }
161
162    /**
163     * Checks if the given AST node represents a {@code !=} (not equal) operator.
164     *
165     * @param node the AST node to check
166     * @return {@code true} if the node is a not equal operator, otherwise {@code false}
167     */
168    private static boolean isNotEqual(DetailAST node) {
169        return node.getType() == TokenTypes.NOT_EQUAL;
170    }
171
172    /**
173     * Checks if the given AST node is a null literal.
174     *
175     * @param node AST node to check
176     * @return true if the node is a null literal, false otherwise
177     */
178    private static boolean isNullLiteral(DetailAST node) {
179        return node.getType() == TokenTypes.LITERAL_NULL;
180    }
181
182    /**
183     * Determines if the null check is redundant with the instanceof check.
184     *
185     * @param instanceOfIdent the identifier from the instanceof check
186     * @param nullCheckNode the node representing the null check
187     * @return true if the null check is unnecessary, false otherwise
188     */
189    private static boolean isNullCheckRedundant(DetailAST instanceOfIdent,
190        final DetailAST nullCheckNode) {
191
192        final DetailAST nullCheckIdent = nullCheckNode.findFirstToken(TokenTypes.IDENT);
193        return nullCheckIdent != null
194                && (isNullLiteral(nullCheckNode.getFirstChild().getNextSibling())
195                    || isNullLiteral(nullCheckNode.getFirstChild()))
196                && instanceOfIdent.getText().equals(nullCheckIdent.getText());
197    }
198}