001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2020 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;
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;
028import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
029
030/**
031 * <p>
032 * Checks the policy on how to wrap lines on operators.
033 * </p>
034 * <ul>
035 * <li>
036 * Property {@code option} - Specify policy on how to wrap lines.
037 * Type is {@code com.puppycrawl.tools.checkstyle.checks.whitespace.WrapOption}.
038 * Default value is {@code nl}.
039 * </li>
040 * <li>
041 * Property {@code tokens} - tokens to check
042 * Type is {@code int[]}.
043 * Default value is:
044 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#QUESTION">
045 * QUESTION</a>,
046 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COLON">
047 * COLON</a>,
048 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#EQUAL">
049 * EQUAL</a>,
050 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NOT_EQUAL">
051 * NOT_EQUAL</a>,
052 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#DIV">
053 * DIV</a>,
054 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PLUS">
055 * PLUS</a>,
056 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#MINUS">
057 * MINUS</a>,
058 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STAR">
059 * STAR</a>,
060 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#MOD">
061 * MOD</a>,
062 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SR">
063 * SR</a>,
064 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BSR">
065 * BSR</a>,
066 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#GE">
067 * GE</a>,
068 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#GT">
069 * GT</a>,
070 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SL">
071 * SL</a>,
072 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LE">
073 * LE</a>,
074 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LT">
075 * LT</a>,
076 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BXOR">
077 * BXOR</a>,
078 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BOR">
079 * BOR</a>,
080 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LOR">
081 * LOR</a>,
082 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BAND">
083 * BAND</a>,
084 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAND">
085 * LAND</a>,
086 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#TYPE_EXTENSION_AND">
087 * TYPE_EXTENSION_AND</a>,
088 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_INSTANCEOF">
089 * LITERAL_INSTANCEOF</a>.
090 * </li>
091 * </ul>
092 * <p>
093 * To configure the check:
094 * </p>
095 * <pre>
096 * &lt;module name="OperatorWrap"/&gt;
097 * </pre>
098 * <p>
099 * Example:
100 * </p>
101 * <pre>
102 * class Test {
103 *     public static void main(String[] args) {
104 *         String s = "Hello" +
105 *         "World"; // violation, '+' should be on new line
106 *
107 *         if (10 ==
108 *                 20) { // violation, '==' should be on new line.
109 *         // body
110 *         }
111 *         if (10
112 *                 ==
113 *                 20) { // ok
114 *         // body
115 *         }
116 *
117 *         int c = 10 /
118 *                 5; // violation, '/' should be on new line.
119 *
120 *         int d = c
121 *                 + 10; // ok
122 *     }
123 *
124 * }
125 * </pre>
126 * <p>
127 * To configure the check for assignment operators at the end of a line:
128 * </p>
129 * <pre>
130 * &lt;module name="OperatorWrap"&gt;
131 *   &lt;property name="tokens"
132 *     value="ASSIGN,DIV_ASSIGN,PLUS_ASSIGN,MINUS_ASSIGN,STAR_ASSIGN,MOD_ASSIGN,
133 *            SR_ASSIGN,BSR_ASSIGN,SL_ASSIGN,BXOR_ASSIGN,BOR_ASSIGN,BAND_ASSIGN"/&gt;
134 *   &lt;property name="option" value="eol"/&gt;
135 * &lt;/module&gt;
136 * </pre>
137 * <p>
138 * Example:
139 * </p>
140 * <pre>
141 * class Test {
142 *     public static void main(String[] args) {
143 *             int b
144 *                     = 10; // violation, '=' should be on previous line
145 *             int c =
146 *                     10; // ok
147 *             b
148 *                     += 10; // violation, '+=' should be on previous line
149 *             b +=
150 *                     10; // ok
151 *             c
152 *                     *= 10; // violation, '*=' should be on previous line
153 *             c *=
154 *                     10; // ok
155 *             c
156 *                     -= 5; // violation, '-=' should be on previous line
157 *             c -=
158 *                     5; // ok
159 *             c
160 *                     /= 2; // violation, '/=' should be on previous line
161 *             c
162 *                     %= 1; // violation, '%=' should be on previous line
163 *             c
164 *                     &gt;&gt;= 1; // violation, '&gt;&gt;=' should be on previous line
165 *             c
166 *                 &gt;&gt;&gt;= 1; // violation, '&gt;&gt;&gt;=' should be on previous line
167 *         }
168 *         public void myFunction() {
169 *             c
170 *                     ^= 1; // violation, '^=' should be on previous line
171 *             c
172 *                     |= 1; // violation, '|=' should be on previous line
173 *             c
174 *                     &amp;=1 ; // violation, '&amp;=' should be on previous line
175 *             c
176 *                     &lt;&lt;= 1; // violation, '&lt;&lt;=' should be on previous line
177 *     }
178 * }
179 * </pre>
180 * <p>
181 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
182 * </p>
183 * <p>
184 * Violation Message Keys:
185 * </p>
186 * <ul>
187 * <li>
188 * {@code line.new}
189 * </li>
190 * <li>
191 * {@code line.previous}
192 * </li>
193 * </ul>
194 *
195 * @since 3.0
196 */
197@StatelessCheck
198public class OperatorWrapCheck
199    extends AbstractCheck {
200
201    /**
202     * A key is pointing to the warning message text in "messages.properties"
203     * file.
204     */
205    public static final String MSG_LINE_NEW = "line.new";
206
207    /**
208     * A key is pointing to the warning message text in "messages.properties"
209     * file.
210     */
211    public static final String MSG_LINE_PREVIOUS = "line.previous";
212
213    /** Specify policy on how to wrap lines. */
214    private WrapOption option = WrapOption.NL;
215
216    /**
217     * Setter to specify policy on how to wrap lines.
218     *
219     * @param optionStr string to decode option from
220     * @throws IllegalArgumentException if unable to decode
221     */
222    public void setOption(String optionStr) {
223        option = WrapOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
224    }
225
226    @Override
227    public int[] getDefaultTokens() {
228        return new int[] {
229            TokenTypes.QUESTION,          // '?'
230            TokenTypes.COLON,             // ':' (not reported for a case)
231            TokenTypes.EQUAL,             // "=="
232            TokenTypes.NOT_EQUAL,         // "!="
233            TokenTypes.DIV,               // '/'
234            TokenTypes.PLUS,              // '+' (unary plus is UNARY_PLUS)
235            TokenTypes.MINUS,             // '-' (unary minus is UNARY_MINUS)
236            TokenTypes.STAR,              // '*'
237            TokenTypes.MOD,               // '%'
238            TokenTypes.SR,                // ">>"
239            TokenTypes.BSR,               // ">>>"
240            TokenTypes.GE,                // ">="
241            TokenTypes.GT,                // ">"
242            TokenTypes.SL,                // "<<"
243            TokenTypes.LE,                // "<="
244            TokenTypes.LT,                // '<'
245            TokenTypes.BXOR,              // '^'
246            TokenTypes.BOR,               // '|'
247            TokenTypes.LOR,               // "||"
248            TokenTypes.BAND,              // '&'
249            TokenTypes.LAND,              // "&&"
250            TokenTypes.TYPE_EXTENSION_AND,
251            TokenTypes.LITERAL_INSTANCEOF,
252        };
253    }
254
255    @Override
256    public int[] getAcceptableTokens() {
257        return new int[] {
258            TokenTypes.QUESTION,          // '?'
259            TokenTypes.COLON,             // ':' (not reported for a case)
260            TokenTypes.EQUAL,             // "=="
261            TokenTypes.NOT_EQUAL,         // "!="
262            TokenTypes.DIV,               // '/'
263            TokenTypes.PLUS,              // '+' (unary plus is UNARY_PLUS)
264            TokenTypes.MINUS,             // '-' (unary minus is UNARY_MINUS)
265            TokenTypes.STAR,              // '*'
266            TokenTypes.MOD,               // '%'
267            TokenTypes.SR,                // ">>"
268            TokenTypes.BSR,               // ">>>"
269            TokenTypes.GE,                // ">="
270            TokenTypes.GT,                // ">"
271            TokenTypes.SL,                // "<<"
272            TokenTypes.LE,                // "<="
273            TokenTypes.LT,                // '<'
274            TokenTypes.BXOR,              // '^'
275            TokenTypes.BOR,               // '|'
276            TokenTypes.LOR,               // "||"
277            TokenTypes.BAND,              // '&'
278            TokenTypes.LAND,              // "&&"
279            TokenTypes.LITERAL_INSTANCEOF,
280            TokenTypes.TYPE_EXTENSION_AND,
281            TokenTypes.ASSIGN,            // '='
282            TokenTypes.DIV_ASSIGN,        // "/="
283            TokenTypes.PLUS_ASSIGN,       // "+="
284            TokenTypes.MINUS_ASSIGN,      // "-="
285            TokenTypes.STAR_ASSIGN,       // "*="
286            TokenTypes.MOD_ASSIGN,        // "%="
287            TokenTypes.SR_ASSIGN,         // ">>="
288            TokenTypes.BSR_ASSIGN,        // ">>>="
289            TokenTypes.SL_ASSIGN,         // "<<="
290            TokenTypes.BXOR_ASSIGN,       // "^="
291            TokenTypes.BOR_ASSIGN,        // "|="
292            TokenTypes.BAND_ASSIGN,       // "&="
293            TokenTypes.METHOD_REF,        // "::"
294        };
295    }
296
297    @Override
298    public int[] getRequiredTokens() {
299        return CommonUtil.EMPTY_INT_ARRAY;
300    }
301
302    @Override
303    public void visitToken(DetailAST ast) {
304        final DetailAST parent = ast.getParent();
305        // we do not want to check colon for cases and defaults
306        if (parent.getType() != TokenTypes.LITERAL_DEFAULT
307                && parent.getType() != TokenTypes.LITERAL_CASE) {
308            final String text = ast.getText();
309            final int colNo = ast.getColumnNo();
310            final int lineNo = ast.getLineNo();
311            final String currentLine = getLine(lineNo - 1);
312
313            // Check if rest of line is whitespace, and not just the operator
314            // by itself. This last bit is to handle the operator on a line by
315            // itself.
316            if (option == WrapOption.NL
317                    && !text.equals(currentLine.trim())
318                    && CommonUtil.isBlank(currentLine.substring(colNo + text.length()))) {
319                log(ast, MSG_LINE_NEW, text);
320            }
321            else if (option == WrapOption.EOL
322                    && CommonUtil.hasWhitespaceBefore(colNo - 1, currentLine)) {
323                log(ast, MSG_LINE_PREVIOUS, text);
324            }
325        }
326    }
327
328}