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