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