001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2024 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.whitespace;
021
022import java.util.Locale;
023import java.util.function.UnaryOperator;
024
025import com.puppycrawl.tools.checkstyle.StatelessCheck;
026import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
027import com.puppycrawl.tools.checkstyle.api.DetailAST;
028import com.puppycrawl.tools.checkstyle.api.TokenTypes;
029import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
030import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
031
032/**
033 * <div>
034 * Checks the policy on how to wrap lines on
035 * <a href="https://docs.oracle.com/javase/tutorial/java/nutsandbolts/opsummary.html">
036 * operators</a>.
037 * </div>
038 *
039 * <p>
040 * See the <a href="https://docs.oracle.com/javase/specs/jls/se22/html/jls-15.html#jls-15.20.2">
041 * Java Language Specification</a> for more information about {@code instanceof} operator.
042 * </p>
043 * <ul>
044 * <li>
045 * Property {@code option} - Specify policy on how to wrap lines.
046 * Type is {@code com.puppycrawl.tools.checkstyle.checks.whitespace.WrapOption}.
047 * Default value is {@code nl}.
048 * </li>
049 * <li>
050 * Property {@code tokens} - tokens to check
051 * Type is {@code java.lang.String[]}.
052 * Validation type is {@code tokenSet}.
053 * Default value is:
054 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#QUESTION">
055 * QUESTION</a>,
056 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COLON">
057 * COLON</a>,
058 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#EQUAL">
059 * EQUAL</a>,
060 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NOT_EQUAL">
061 * NOT_EQUAL</a>,
062 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#DIV">
063 * DIV</a>,
064 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PLUS">
065 * PLUS</a>,
066 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#MINUS">
067 * MINUS</a>,
068 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STAR">
069 * STAR</a>,
070 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#MOD">
071 * MOD</a>,
072 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SR">
073 * SR</a>,
074 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BSR">
075 * BSR</a>,
076 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#GE">
077 * GE</a>,
078 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#GT">
079 * GT</a>,
080 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SL">
081 * SL</a>,
082 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LE">
083 * LE</a>,
084 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LT">
085 * LT</a>,
086 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BXOR">
087 * BXOR</a>,
088 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BOR">
089 * BOR</a>,
090 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LOR">
091 * LOR</a>,
092 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BAND">
093 * BAND</a>,
094 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAND">
095 * LAND</a>,
096 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#TYPE_EXTENSION_AND">
097 * TYPE_EXTENSION_AND</a>,
098 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_INSTANCEOF">
099 * LITERAL_INSTANCEOF</a>.
100 * </li>
101 * </ul>
102 *
103 * <p>
104 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
105 * </p>
106 *
107 * <p>
108 * Violation Message Keys:
109 * </p>
110 * <ul>
111 * <li>
112 * {@code line.new}
113 * </li>
114 * <li>
115 * {@code line.previous}
116 * </li>
117 * </ul>
118 *
119 * @since 3.0
120 */
121@StatelessCheck
122public class OperatorWrapCheck
123    extends AbstractCheck {
124
125    /**
126     * A key is pointing to the warning message text in "messages.properties"
127     * file.
128     */
129    public static final String MSG_LINE_NEW = "line.new";
130
131    /**
132     * A key is pointing to the warning message text in "messages.properties"
133     * file.
134     */
135    public static final String MSG_LINE_PREVIOUS = "line.previous";
136
137    /** Specify policy on how to wrap lines. */
138    private WrapOption option = WrapOption.NL;
139
140    /**
141     * Setter to specify policy on how to wrap lines.
142     *
143     * @param optionStr string to decode option from
144     * @throws IllegalArgumentException if unable to decode
145     * @since 3.0
146     */
147    public void setOption(String optionStr) {
148        option = WrapOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
149    }
150
151    @Override
152    public int[] getDefaultTokens() {
153        return new int[] {
154            TokenTypes.QUESTION,          // '?'
155            TokenTypes.COLON,             // ':' (not reported for a case)
156            TokenTypes.EQUAL,             // "=="
157            TokenTypes.NOT_EQUAL,         // "!="
158            TokenTypes.DIV,               // '/'
159            TokenTypes.PLUS,              // '+' (unary plus is UNARY_PLUS)
160            TokenTypes.MINUS,             // '-' (unary minus is UNARY_MINUS)
161            TokenTypes.STAR,              // '*'
162            TokenTypes.MOD,               // '%'
163            TokenTypes.SR,                // ">>"
164            TokenTypes.BSR,               // ">>>"
165            TokenTypes.GE,                // ">="
166            TokenTypes.GT,                // ">"
167            TokenTypes.SL,                // "<<"
168            TokenTypes.LE,                // "<="
169            TokenTypes.LT,                // '<'
170            TokenTypes.BXOR,              // '^'
171            TokenTypes.BOR,               // '|'
172            TokenTypes.LOR,               // "||"
173            TokenTypes.BAND,              // '&'
174            TokenTypes.LAND,              // "&&"
175            TokenTypes.TYPE_EXTENSION_AND,
176            TokenTypes.LITERAL_INSTANCEOF,
177        };
178    }
179
180    @Override
181    public int[] getAcceptableTokens() {
182        return new int[] {
183            TokenTypes.QUESTION,          // '?'
184            TokenTypes.COLON,             // ':' (not reported for a case)
185            TokenTypes.EQUAL,             // "=="
186            TokenTypes.NOT_EQUAL,         // "!="
187            TokenTypes.DIV,               // '/'
188            TokenTypes.PLUS,              // '+' (unary plus is UNARY_PLUS)
189            TokenTypes.MINUS,             // '-' (unary minus is UNARY_MINUS)
190            TokenTypes.STAR,              // '*'
191            TokenTypes.MOD,               // '%'
192            TokenTypes.SR,                // ">>"
193            TokenTypes.BSR,               // ">>>"
194            TokenTypes.GE,                // ">="
195            TokenTypes.GT,                // ">"
196            TokenTypes.SL,                // "<<"
197            TokenTypes.LE,                // "<="
198            TokenTypes.LT,                // '<'
199            TokenTypes.BXOR,              // '^'
200            TokenTypes.BOR,               // '|'
201            TokenTypes.LOR,               // "||"
202            TokenTypes.BAND,              // '&'
203            TokenTypes.LAND,              // "&&"
204            TokenTypes.LITERAL_INSTANCEOF,
205            TokenTypes.TYPE_EXTENSION_AND,
206            TokenTypes.ASSIGN,            // '='
207            TokenTypes.DIV_ASSIGN,        // "/="
208            TokenTypes.PLUS_ASSIGN,       // "+="
209            TokenTypes.MINUS_ASSIGN,      // "-="
210            TokenTypes.STAR_ASSIGN,       // "*="
211            TokenTypes.MOD_ASSIGN,        // "%="
212            TokenTypes.SR_ASSIGN,         // ">>="
213            TokenTypes.BSR_ASSIGN,        // ">>>="
214            TokenTypes.SL_ASSIGN,         // "<<="
215            TokenTypes.BXOR_ASSIGN,       // "^="
216            TokenTypes.BOR_ASSIGN,        // "|="
217            TokenTypes.BAND_ASSIGN,       // "&="
218            TokenTypes.METHOD_REF,        // "::"
219        };
220    }
221
222    @Override
223    public int[] getRequiredTokens() {
224        return CommonUtil.EMPTY_INT_ARRAY;
225    }
226
227    @Override
228    public void visitToken(DetailAST ast) {
229        if (isTargetNode(ast)) {
230            if (option == WrapOption.NL && isNewLineModeViolation(ast)) {
231                log(ast, MSG_LINE_NEW, ast.getText());
232            }
233            else if (option == WrapOption.EOL && isEndOfLineModeViolation(ast)) {
234                log(ast, MSG_LINE_PREVIOUS, ast.getText());
235            }
236        }
237    }
238
239    /**
240     * Filters some false tokens that this check should ignore.
241     *
242     * @param node the node to check
243     * @return {@code true} for all nodes this check should validate
244     */
245    private static boolean isTargetNode(DetailAST node) {
246        final boolean result;
247        if (node.getType() == TokenTypes.COLON) {
248            result = !isColonFromLabel(node);
249        }
250        else if (node.getType() == TokenTypes.STAR) {
251            // Unlike the import statement, the multiply operator always has children
252            result = node.hasChildren();
253        }
254        else {
255            result = true;
256        }
257        return result;
258    }
259
260    /**
261     * Checks whether operator violates {@link WrapOption#NL} mode.
262     *
263     * @param ast the DetailAst of an operator
264     * @return {@code true} if mode does not match
265     */
266    private static boolean isNewLineModeViolation(DetailAST ast) {
267        return TokenUtil.areOnSameLine(ast, getLeftNode(ast))
268                && !TokenUtil.areOnSameLine(ast, getRightNode(ast));
269    }
270
271    /**
272     * Checks whether operator violates {@link WrapOption#EOL} mode.
273     *
274     * @param ast the DetailAst of an operator
275     * @return {@code true} if mode does not match
276     */
277    private static boolean isEndOfLineModeViolation(DetailAST ast) {
278        return !TokenUtil.areOnSameLine(ast, getLeftNode(ast));
279    }
280
281    /**
282     * Checks if a node is {@link TokenTypes#COLON} from a label, switch case of default.
283     *
284     * @param node the node to check
285     * @return {@code true} if node matches
286     */
287    private static boolean isColonFromLabel(DetailAST node) {
288        return TokenUtil.isOfType(node.getParent(), TokenTypes.LABELED_STAT,
289            TokenTypes.LITERAL_CASE, TokenTypes.LITERAL_DEFAULT);
290    }
291
292    /**
293     * Checks if a node is {@link TokenTypes#ASSIGN} to a variable or resource.
294     *
295     * @param node the node to check
296     * @return {@code true} if node matches
297     */
298    private static boolean isAssignToVariable(DetailAST node) {
299        return TokenUtil.isOfType(node.getParent(), TokenTypes.VARIABLE_DEF, TokenTypes.RESOURCE);
300    }
301
302    /**
303     * Returns the left neighbour of a binary operator. This is the rightmost
304     * grandchild of the left child or sibling. For the assign operator the return value is
305     * the variable name.
306     *
307     * @param node the binary operator
308     * @return nearest node from left
309     */
310    private static DetailAST getLeftNode(DetailAST node) {
311        DetailAST result;
312        if (node.getFirstChild() == null || isAssignToVariable(node)) {
313            result = node.getPreviousSibling();
314        }
315        else if (isInPatternDefinition(node)) {
316            result = node.getFirstChild();
317        }
318        else {
319            result = adjustParens(node.getFirstChild(), DetailAST::getNextSibling);
320        }
321        while (result.getLastChild() != null) {
322            result = result.getLastChild();
323        }
324        return result;
325    }
326
327    /**
328     * Ascends AST to determine if given node is part of a pattern
329     * definition.
330     *
331     * @param node the node to check
332     * @return true if node is in pattern definition
333     */
334    private static boolean isInPatternDefinition(DetailAST node) {
335        DetailAST parent = node;
336        final int[] tokensToStopOn = {
337            // token we are looking for
338            TokenTypes.PATTERN_DEF,
339            // tokens that mean we can stop looking
340            TokenTypes.EXPR,
341            TokenTypes.RESOURCE,
342            TokenTypes.COMPILATION_UNIT,
343        };
344
345        do {
346            parent = parent.getParent();
347        } while (!TokenUtil.isOfType(parent, tokensToStopOn));
348        return parent.getType() == TokenTypes.PATTERN_DEF;
349    }
350
351    /**
352     * Returns the right neighbour of a binary operator. This is the leftmost
353     * grandchild of the right child or sibling. For the ternary operator this
354     * is the node between {@code ?} and {@code :} .
355     *
356     * @param node the binary operator
357     * @return nearest node from right
358     */
359    private static DetailAST getRightNode(DetailAST node) {
360        DetailAST result;
361        if (node.getLastChild() == null) {
362            result = node.getNextSibling();
363        }
364        else {
365            final DetailAST rightNode;
366            if (node.getType() == TokenTypes.QUESTION) {
367                rightNode = node.findFirstToken(TokenTypes.COLON).getPreviousSibling();
368            }
369            else {
370                rightNode = node.getLastChild();
371            }
372            result = adjustParens(rightNode, DetailAST::getPreviousSibling);
373        }
374
375        if (!TokenUtil.isOfType(result, TokenTypes.ARRAY_INIT, TokenTypes.ANNOTATION_ARRAY_INIT)) {
376            while (result.getFirstChild() != null) {
377                result = result.getFirstChild();
378            }
379        }
380        return result;
381    }
382
383    /**
384     * Finds matching parentheses among siblings. If the given node is not
385     * {@link TokenTypes#LPAREN} nor {@link TokenTypes#RPAREN}, the method adjusts nothing.
386     * This method is for handling case like {@code
387     *   (condition && (condition
388     *     || condition2 || condition3) && condition4
389     *     && condition3)
390     * }
391     *
392     * @param node the node to adjust
393     * @param step the node transformer, should be {@link DetailAST#getPreviousSibling}
394     *             or {@link DetailAST#getNextSibling}
395     * @return adjusted node
396     */
397    private static DetailAST adjustParens(DetailAST node, UnaryOperator<DetailAST> step) {
398        DetailAST result = node;
399        int accumulator = 0;
400        while (true) {
401            if (result.getType() == TokenTypes.LPAREN) {
402                accumulator--;
403            }
404            else if (result.getType() == TokenTypes.RPAREN) {
405                accumulator++;
406            }
407            if (accumulator == 0) {
408                break;
409            }
410            result = step.apply(result);
411        }
412        return result;
413    }
414
415}