001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2022 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 * To configure the check:
097 * </p>
098 * <pre>
099 * &lt;module name="OperatorWrap"/&gt;
100 * </pre>
101 * <p>
102 * Example:
103 * </p>
104 * <pre>
105 * class Test {
106 *     public static void main(String[] args) {
107 *         String s = "Hello" +
108 *         "World"; // violation, '+' should be on new line
109 *
110 *         if (10 ==
111 *                 20) { // violation, '==' should be on new line.
112 *         // body
113 *         }
114 *         if (10
115 *                 ==
116 *                 20) { // ok
117 *         // body
118 *         }
119 *
120 *         int c = 10 /
121 *                 5; // violation, '/' should be on new line.
122 *
123 *         int d = c
124 *                 + 10; // ok
125 *     }
126 *
127 * }
128 * </pre>
129 * <p>
130 * To configure the check for assignment operators at the end of a line:
131 * </p>
132 * <pre>
133 * &lt;module name="OperatorWrap"&gt;
134 *   &lt;property name="tokens"
135 *     value="ASSIGN,DIV_ASSIGN,PLUS_ASSIGN,MINUS_ASSIGN,STAR_ASSIGN,MOD_ASSIGN,
136 *            SR_ASSIGN,BSR_ASSIGN,SL_ASSIGN,BXOR_ASSIGN,BOR_ASSIGN,BAND_ASSIGN"/&gt;
137 *   &lt;property name="option" value="eol"/&gt;
138 * &lt;/module&gt;
139 * </pre>
140 * <p>
141 * Example:
142 * </p>
143 * <pre>
144 * class Test {
145 *     public static void main(String[] args) {
146 *             int b
147 *                     = 10; // violation, '=' should be on previous line
148 *             int c =
149 *                     10; // ok
150 *             b
151 *                     += 10; // violation, '+=' should be on previous line
152 *             b +=
153 *                     10; // ok
154 *             c
155 *                     *= 10; // violation, '*=' should be on previous line
156 *             c *=
157 *                     10; // ok
158 *             c
159 *                     -= 5; // violation, '-=' should be on previous line
160 *             c -=
161 *                     5; // ok
162 *             c
163 *                     /= 2; // violation, '/=' should be on previous line
164 *             c
165 *                     %= 1; // violation, '%=' should be on previous line
166 *             c
167 *                     &gt;&gt;= 1; // violation, '&gt;&gt;=' should be on previous line
168 *             c
169 *                 &gt;&gt;&gt;= 1; // violation, '&gt;&gt;&gt;=' should be on previous line
170 *         }
171 *         public void myFunction() {
172 *             c
173 *                     ^= 1; // violation, '^=' should be on previous line
174 *             c
175 *                     |= 1; // violation, '|=' should be on previous line
176 *             c
177 *                     &amp;=1 ; // violation, '&amp;=' should be on previous line
178 *             c
179 *                     &lt;&lt;= 1; // violation, '&lt;&lt;=' should be on previous line
180 *     }
181 * }
182 * </pre>
183 * <p>
184 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
185 * </p>
186 * <p>
187 * Violation Message Keys:
188 * </p>
189 * <ul>
190 * <li>
191 * {@code line.new}
192 * </li>
193 * <li>
194 * {@code line.previous}
195 * </li>
196 * </ul>
197 *
198 * @since 3.0
199 */
200@StatelessCheck
201public class OperatorWrapCheck
202    extends AbstractCheck {
203
204    /**
205     * A key is pointing to the warning message text in "messages.properties"
206     * file.
207     */
208    public static final String MSG_LINE_NEW = "line.new";
209
210    /**
211     * A key is pointing to the warning message text in "messages.properties"
212     * file.
213     */
214    public static final String MSG_LINE_PREVIOUS = "line.previous";
215
216    /** Specify policy on how to wrap lines. */
217    private WrapOption option = WrapOption.NL;
218
219    /**
220     * Setter to specify policy on how to wrap lines.
221     *
222     * @param optionStr string to decode option from
223     * @throws IllegalArgumentException if unable to decode
224     */
225    public void setOption(String optionStr) {
226        option = WrapOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
227    }
228
229    @Override
230    public int[] getDefaultTokens() {
231        return new int[] {
232            TokenTypes.QUESTION,          // '?'
233            TokenTypes.COLON,             // ':' (not reported for a case)
234            TokenTypes.EQUAL,             // "=="
235            TokenTypes.NOT_EQUAL,         // "!="
236            TokenTypes.DIV,               // '/'
237            TokenTypes.PLUS,              // '+' (unary plus is UNARY_PLUS)
238            TokenTypes.MINUS,             // '-' (unary minus is UNARY_MINUS)
239            TokenTypes.STAR,              // '*'
240            TokenTypes.MOD,               // '%'
241            TokenTypes.SR,                // ">>"
242            TokenTypes.BSR,               // ">>>"
243            TokenTypes.GE,                // ">="
244            TokenTypes.GT,                // ">"
245            TokenTypes.SL,                // "<<"
246            TokenTypes.LE,                // "<="
247            TokenTypes.LT,                // '<'
248            TokenTypes.BXOR,              // '^'
249            TokenTypes.BOR,               // '|'
250            TokenTypes.LOR,               // "||"
251            TokenTypes.BAND,              // '&'
252            TokenTypes.LAND,              // "&&"
253            TokenTypes.TYPE_EXTENSION_AND,
254            TokenTypes.LITERAL_INSTANCEOF,
255        };
256    }
257
258    @Override
259    public int[] getAcceptableTokens() {
260        return new int[] {
261            TokenTypes.QUESTION,          // '?'
262            TokenTypes.COLON,             // ':' (not reported for a case)
263            TokenTypes.EQUAL,             // "=="
264            TokenTypes.NOT_EQUAL,         // "!="
265            TokenTypes.DIV,               // '/'
266            TokenTypes.PLUS,              // '+' (unary plus is UNARY_PLUS)
267            TokenTypes.MINUS,             // '-' (unary minus is UNARY_MINUS)
268            TokenTypes.STAR,              // '*'
269            TokenTypes.MOD,               // '%'
270            TokenTypes.SR,                // ">>"
271            TokenTypes.BSR,               // ">>>"
272            TokenTypes.GE,                // ">="
273            TokenTypes.GT,                // ">"
274            TokenTypes.SL,                // "<<"
275            TokenTypes.LE,                // "<="
276            TokenTypes.LT,                // '<'
277            TokenTypes.BXOR,              // '^'
278            TokenTypes.BOR,               // '|'
279            TokenTypes.LOR,               // "||"
280            TokenTypes.BAND,              // '&'
281            TokenTypes.LAND,              // "&&"
282            TokenTypes.LITERAL_INSTANCEOF,
283            TokenTypes.TYPE_EXTENSION_AND,
284            TokenTypes.ASSIGN,            // '='
285            TokenTypes.DIV_ASSIGN,        // "/="
286            TokenTypes.PLUS_ASSIGN,       // "+="
287            TokenTypes.MINUS_ASSIGN,      // "-="
288            TokenTypes.STAR_ASSIGN,       // "*="
289            TokenTypes.MOD_ASSIGN,        // "%="
290            TokenTypes.SR_ASSIGN,         // ">>="
291            TokenTypes.BSR_ASSIGN,        // ">>>="
292            TokenTypes.SL_ASSIGN,         // "<<="
293            TokenTypes.BXOR_ASSIGN,       // "^="
294            TokenTypes.BOR_ASSIGN,        // "|="
295            TokenTypes.BAND_ASSIGN,       // "&="
296            TokenTypes.METHOD_REF,        // "::"
297        };
298    }
299
300    @Override
301    public int[] getRequiredTokens() {
302        return CommonUtil.EMPTY_INT_ARRAY;
303    }
304
305    @Override
306    public void visitToken(DetailAST ast) {
307        if (isTargetNode(ast)) {
308            if (option == WrapOption.NL && isNewLineModeViolation(ast)) {
309                log(ast, MSG_LINE_NEW, ast.getText());
310            }
311            else if (option == WrapOption.EOL && isEndOfLineModeViolation(ast)) {
312                log(ast, MSG_LINE_PREVIOUS, ast.getText());
313            }
314        }
315    }
316
317    /**
318     * Filters some false tokens that this check should ignore.
319     *
320     * @param node the node to check
321     * @return {@code true} for all nodes this check should validate
322     */
323    private static boolean isTargetNode(DetailAST node) {
324        final boolean result;
325        if (node.getType() == TokenTypes.COLON) {
326            result = !isColonFromLabel(node);
327        }
328        else if (node.getType() == TokenTypes.STAR) {
329            // Unlike the import statement, the multiply operator always has children
330            result = node.hasChildren();
331        }
332        else {
333            result = true;
334        }
335        return result;
336    }
337
338    /**
339     * Checks whether operator violates {@link WrapOption#NL} mode.
340     *
341     * @param ast the DetailAst of an operator
342     * @return {@code true} if mode does not match
343     */
344    private static boolean isNewLineModeViolation(DetailAST ast) {
345        return TokenUtil.areOnSameLine(ast, getLeftNode(ast))
346                && !TokenUtil.areOnSameLine(ast, getRightNode(ast));
347    }
348
349    /**
350     * Checks whether operator violates {@link WrapOption#EOL} mode.
351     *
352     * @param ast the DetailAst of an operator
353     * @return {@code true} if mode does not match
354     */
355    private static boolean isEndOfLineModeViolation(DetailAST ast) {
356        return !TokenUtil.areOnSameLine(ast, getLeftNode(ast));
357    }
358
359    /**
360     * Checks if a node is {@link TokenTypes#COLON} from a label, switch case of default.
361     *
362     * @param node the node to check
363     * @return {@code true} if node matches
364     */
365    private static boolean isColonFromLabel(DetailAST node) {
366        return TokenUtil.isOfType(node.getParent(), TokenTypes.LABELED_STAT,
367            TokenTypes.LITERAL_CASE, TokenTypes.LITERAL_DEFAULT);
368    }
369
370    /**
371     * Checks if a node is {@link TokenTypes#ASSIGN} to a variable or resource.
372     *
373     * @param node the node to check
374     * @return {@code true} if node matches
375     */
376    private static boolean isAssignToVariable(DetailAST node) {
377        return TokenUtil.isOfType(node.getParent(), TokenTypes.VARIABLE_DEF, TokenTypes.RESOURCE);
378    }
379
380    /**
381     * Returns the left neighbour of a binary operator. This is the rightmost
382     * grandchild of the left child or sibling. For the assign operator the return value is
383     * the variable name.
384     *
385     * @param node the binary operator
386     * @return nearest node from left
387     */
388    private static DetailAST getLeftNode(DetailAST node) {
389        DetailAST result;
390        if (node.getFirstChild() == null || isAssignToVariable(node)) {
391            result = node.getPreviousSibling();
392        }
393        else if (isInPatternDefinition(node)) {
394            result = node.getFirstChild();
395        }
396        else {
397            result = adjustParens(node.getFirstChild(), DetailAST::getNextSibling);
398        }
399        while (result.getLastChild() != null) {
400            result = result.getLastChild();
401        }
402        return result;
403    }
404
405    /**
406     * Ascends AST to determine if given node is part of a pattern
407     * definition.
408     *
409     * @param node the node to check
410     * @return true if node is in pattern definition
411     */
412    private static boolean isInPatternDefinition(DetailAST node) {
413        DetailAST parent = node;
414        final int[] tokensToStopOn = {
415            // token we are looking for
416            TokenTypes.PATTERN_DEF,
417            // tokens that mean we can stop looking
418            TokenTypes.EXPR,
419            TokenTypes.RESOURCE,
420            TokenTypes.COMPILATION_UNIT,
421        };
422
423        do {
424            parent = parent.getParent();
425        } while (!TokenUtil.isOfType(parent, tokensToStopOn));
426        return parent.getType() == TokenTypes.PATTERN_DEF;
427    }
428
429    /**
430     * Returns the right neighbour of a binary operator. This is the leftmost
431     * grandchild of the right child or sibling. For the ternary operator this
432     * is the node between {@code ?} and {@code :} .
433     *
434     * @param node the binary operator
435     * @return nearest node from right
436     */
437    private static DetailAST getRightNode(DetailAST node) {
438        DetailAST result;
439        if (node.getLastChild() == null) {
440            result = node.getNextSibling();
441        }
442        else {
443            final DetailAST rightNode;
444            if (node.getType() == TokenTypes.QUESTION) {
445                rightNode = node.findFirstToken(TokenTypes.COLON).getPreviousSibling();
446            }
447            else {
448                rightNode = node.getLastChild();
449            }
450            result = adjustParens(rightNode, DetailAST::getPreviousSibling);
451        }
452
453        if (!TokenUtil.isOfType(result, TokenTypes.ARRAY_INIT, TokenTypes.ANNOTATION_ARRAY_INIT)) {
454            while (result.getFirstChild() != null) {
455                result = result.getFirstChild();
456            }
457        }
458        return result;
459    }
460
461    /**
462     * Finds matching parentheses among siblings. If the given node is not
463     * {@link TokenTypes#LPAREN} nor {@link TokenTypes#RPAREN}, the method adjusts nothing.
464     * This method is for handling case like {@code
465     *   (condition && (condition
466     *     || condition2 || condition3) && condition4
467     *     && condition3)
468     * }
469     *
470     * @param node the node to adjust
471     * @param step the node transformer, should be {@link DetailAST#getPreviousSibling}
472     *             or {@link DetailAST#getNextSibling}
473     * @return adjusted node
474     */
475    private static DetailAST adjustParens(DetailAST node, UnaryOperator<DetailAST> step) {
476        DetailAST result = node;
477        int accumulator = 0;
478        while (true) {
479            if (result.getType() == TokenTypes.LPAREN) {
480                accumulator--;
481            }
482            else if (result.getType() == TokenTypes.RPAREN) {
483                accumulator++;
484            }
485            if (accumulator == 0) {
486                break;
487            }
488            result = step.apply(result);
489        }
490        return result;
491    }
492
493}