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 * Default value is {@code nl}.
038 * </li>
039 * <li>
040 * Property {@code tokens} - tokens to check
041 * Default value is:
042 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#QUESTION">
043 * QUESTION</a>,
044 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COLON">
045 * COLON</a>,
046 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#EQUAL">
047 * EQUAL</a>,
048 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NOT_EQUAL">
049 * NOT_EQUAL</a>,
050 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#DIV">
051 * DIV</a>,
052 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PLUS">
053 * PLUS</a>,
054 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#MINUS">
055 * MINUS</a>,
056 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STAR">
057 * STAR</a>,
058 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#MOD">
059 * MOD</a>,
060 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SR">
061 * SR</a>,
062 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BSR">
063 * BSR</a>,
064 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#GE">
065 * GE</a>,
066 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#GT">
067 * GT</a>,
068 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SL">
069 * SL</a>,
070 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LE">
071 * LE</a>,
072 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LT">
073 * LT</a>,
074 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BXOR">
075 * BXOR</a>,
076 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BOR">
077 * BOR</a>,
078 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LOR">
079 * LOR</a>,
080 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BAND">
081 * BAND</a>,
082 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAND">
083 * LAND</a>,
084 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#TYPE_EXTENSION_AND">
085 * TYPE_EXTENSION_AND</a>,
086 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_INSTANCEOF">
087 * LITERAL_INSTANCEOF</a>.
088 * </li>
089 * </ul>
090 * <p>
091 * To configure the check:
092 * </p>
093 * <pre>
094 * &lt;module name="OperatorWrap"/&gt;
095 * </pre>
096 * <p>
097 * To configure the check for assignment operators at the end of a line:
098 * </p>
099 * <pre>
100 * &lt;module name="OperatorWrap"&gt;
101 *   &lt;property name="tokens"
102 *     value="ASSIGN,DIV_ASSIGN,PLUS_ASSIGN,MINUS_ASSIGN,STAR_ASSIGN,MOD_ASSIGN,
103 *            SR_ASSIGN,BSR_ASSIGN,SL_ASSIGN,BXOR_ASSIGN,BOR_ASSIGN,BAND_ASSIGN"/&gt;
104 *   &lt;property name="option" value="eol"/&gt;
105 * &lt;/module&gt;
106 * </pre>
107 *
108 * @since 3.0
109 */
110@StatelessCheck
111public class OperatorWrapCheck
112    extends AbstractCheck {
113
114    /**
115     * A key is pointing to the warning message text in "messages.properties"
116     * file.
117     */
118    public static final String MSG_LINE_NEW = "line.new";
119
120    /**
121     * A key is pointing to the warning message text in "messages.properties"
122     * file.
123     */
124    public static final String MSG_LINE_PREVIOUS = "line.previous";
125
126    /** Specify policy on how to wrap lines. */
127    private WrapOption option = WrapOption.NL;
128
129    /**
130     * Setter to specify policy on how to wrap lines.
131     * @param optionStr string to decode option from
132     * @throws IllegalArgumentException if unable to decode
133     */
134    public void setOption(String optionStr) {
135        option = WrapOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
136    }
137
138    @Override
139    public int[] getDefaultTokens() {
140        return new int[] {
141            TokenTypes.QUESTION,          // '?'
142            TokenTypes.COLON,             // ':' (not reported for a case)
143            TokenTypes.EQUAL,             // "=="
144            TokenTypes.NOT_EQUAL,         // "!="
145            TokenTypes.DIV,               // '/'
146            TokenTypes.PLUS,              //' +' (unary plus is UNARY_PLUS)
147            TokenTypes.MINUS,             // '-' (unary minus is UNARY_MINUS)
148            TokenTypes.STAR,              // '*'
149            TokenTypes.MOD,               // '%'
150            TokenTypes.SR,                // ">>"
151            TokenTypes.BSR,               // ">>>"
152            TokenTypes.GE,                // ">="
153            TokenTypes.GT,                // ">"
154            TokenTypes.SL,                // "<<"
155            TokenTypes.LE,                // "<="
156            TokenTypes.LT,                // '<'
157            TokenTypes.BXOR,              // '^'
158            TokenTypes.BOR,               // '|'
159            TokenTypes.LOR,               // "||"
160            TokenTypes.BAND,              // '&'
161            TokenTypes.LAND,              // "&&"
162            TokenTypes.TYPE_EXTENSION_AND,
163            TokenTypes.LITERAL_INSTANCEOF,
164        };
165    }
166
167    @Override
168    public int[] getAcceptableTokens() {
169        return new int[] {
170            TokenTypes.QUESTION,          // '?'
171            TokenTypes.COLON,             // ':' (not reported for a case)
172            TokenTypes.EQUAL,             // "=="
173            TokenTypes.NOT_EQUAL,         // "!="
174            TokenTypes.DIV,               // '/'
175            TokenTypes.PLUS,              //' +' (unary plus is UNARY_PLUS)
176            TokenTypes.MINUS,             // '-' (unary minus is UNARY_MINUS)
177            TokenTypes.STAR,              // '*'
178            TokenTypes.MOD,               // '%'
179            TokenTypes.SR,                // ">>"
180            TokenTypes.BSR,               // ">>>"
181            TokenTypes.GE,                // ">="
182            TokenTypes.GT,                // ">"
183            TokenTypes.SL,                // "<<"
184            TokenTypes.LE,                // "<="
185            TokenTypes.LT,                // '<'
186            TokenTypes.BXOR,              // '^'
187            TokenTypes.BOR,               // '|'
188            TokenTypes.LOR,               // "||"
189            TokenTypes.BAND,              // '&'
190            TokenTypes.LAND,              // "&&"
191            TokenTypes.LITERAL_INSTANCEOF,
192            TokenTypes.TYPE_EXTENSION_AND,
193            TokenTypes.ASSIGN,            // '='
194            TokenTypes.DIV_ASSIGN,        // "/="
195            TokenTypes.PLUS_ASSIGN,       // "+="
196            TokenTypes.MINUS_ASSIGN,      //"-="
197            TokenTypes.STAR_ASSIGN,       // "*="
198            TokenTypes.MOD_ASSIGN,        // "%="
199            TokenTypes.SR_ASSIGN,         // ">>="
200            TokenTypes.BSR_ASSIGN,        // ">>>="
201            TokenTypes.SL_ASSIGN,         // "<<="
202            TokenTypes.BXOR_ASSIGN,       // "^="
203            TokenTypes.BOR_ASSIGN,        // "|="
204            TokenTypes.BAND_ASSIGN,       // "&="
205            TokenTypes.METHOD_REF,        // "::"
206        };
207    }
208
209    @Override
210    public int[] getRequiredTokens() {
211        return CommonUtil.EMPTY_INT_ARRAY;
212    }
213
214    @Override
215    public void visitToken(DetailAST ast) {
216        final DetailAST parent = ast.getParent();
217        //we do not want to check colon for cases and defaults
218        if (parent.getType() != TokenTypes.LITERAL_DEFAULT
219                && parent.getType() != TokenTypes.LITERAL_CASE) {
220            final String text = ast.getText();
221            final int colNo = ast.getColumnNo();
222            final int lineNo = ast.getLineNo();
223            final String currentLine = getLine(lineNo - 1);
224
225            // Check if rest of line is whitespace, and not just the operator
226            // by itself. This last bit is to handle the operator on a line by
227            // itself.
228            if (option == WrapOption.NL
229                    && !text.equals(currentLine.trim())
230                    && CommonUtil.isBlank(currentLine.substring(colNo + text.length()))) {
231                log(ast, MSG_LINE_NEW, text);
232            }
233            else if (option == WrapOption.EOL
234                    && CommonUtil.hasWhitespaceBefore(colNo - 1, currentLine)) {
235                log(ast, MSG_LINE_PREVIOUS, text);
236            }
237        }
238    }
239
240}