001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2015 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 com.puppycrawl.tools.checkstyle.api.Check;
023import com.puppycrawl.tools.checkstyle.api.DetailAST;
024import com.puppycrawl.tools.checkstyle.api.TokenTypes;
025
026/**
027 * <p>
028 * Checks that there is no whitespace after a token.
029 * More specifically, it checks that it is not followed by whitespace,
030 * or (if linebreaks are allowed) all characters on the line after are
031 * whitespace. To forbid linebreaks after a token, set property
032 * allowLineBreaks to false.
033 * </p>
034  * <p> By default the check will check the following operators:
035 *  {@link TokenTypes#ARRAY_INIT ARRAY_INIT},
036 *  {@link TokenTypes#BNOT BNOT},
037 *  {@link TokenTypes#DEC DEC},
038 *  {@link TokenTypes#DOT DOT},
039 *  {@link TokenTypes#INC INC},
040 *  {@link TokenTypes#LNOT LNOT},
041 *  {@link TokenTypes#UNARY_MINUS UNARY_MINUS},
042 *  {@link TokenTypes#UNARY_PLUS UNARY_PLUS},
043 *  {@link TokenTypes#TYPECAST TYPECAST},
044 *  {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR},
045 *  {@link TokenTypes#INDEX_OP INDEX_OP}.
046 * </p>
047 * <p>
048 * The check processes
049 * {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR},
050 * {@link TokenTypes#INDEX_OP INDEX_OP}
051 * specially from other tokens. Actually it is checked that there is
052 * no whitespace before this tokens, not after them.
053 * </p>
054 * <p>
055 * An example of how to configure the check is:
056 * </p>
057 * <pre>
058 * &lt;module name="NoWhitespaceAfter"/&gt;
059 * </pre>
060 * <p> An example of how to configure the check to forbid linebreaks after
061 * a {@link TokenTypes#DOT DOT} token is:
062 * </p>
063 * <pre>
064 * &lt;module name="NoWhitespaceAfter"&gt;
065 *     &lt;property name="tokens" value="DOT"/&gt;
066 *     &lt;property name="allowLineBreaks" value="false"/&gt;
067 * &lt;/module&gt;
068 * </pre>
069 * @author Rick Giles
070 * @author lkuehne
071 * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a>
072 * @author attatrol
073 */
074public class NoWhitespaceAfterCheck extends Check {
075
076    /**
077     * A key is pointing to the warning message text in "messages.properties"
078     * file.
079     */
080    public static final String MSG_KEY = "ws.followed";
081
082    /** Whether whitespace is allowed if the AST is at a linebreak. */
083    private boolean allowLineBreaks = true;
084
085    @Override
086    public int[] getDefaultTokens() {
087        return new int[] {
088            TokenTypes.ARRAY_INIT,
089            TokenTypes.INC,
090            TokenTypes.DEC,
091            TokenTypes.UNARY_MINUS,
092            TokenTypes.UNARY_PLUS,
093            TokenTypes.BNOT,
094            TokenTypes.LNOT,
095            TokenTypes.DOT,
096            TokenTypes.ARRAY_DECLARATOR,
097            TokenTypes.INDEX_OP,
098        };
099    }
100
101    @Override
102    public int[] getAcceptableTokens() {
103        return new int[] {
104            TokenTypes.ARRAY_INIT,
105            TokenTypes.INC,
106            TokenTypes.DEC,
107            TokenTypes.UNARY_MINUS,
108            TokenTypes.UNARY_PLUS,
109            TokenTypes.BNOT,
110            TokenTypes.LNOT,
111            TokenTypes.DOT,
112            TokenTypes.TYPECAST,
113            TokenTypes.ARRAY_DECLARATOR,
114            TokenTypes.INDEX_OP,
115        };
116    }
117
118    /**
119     * Control whether whitespace is flagged at linebreaks.
120     * @param allowLineBreaks whether whitespace should be
121     *     flagged at linebreaks.
122     */
123    public void setAllowLineBreaks(boolean allowLineBreaks) {
124        this.allowLineBreaks = allowLineBreaks;
125    }
126
127    @Override
128    public void visitToken(DetailAST ast) {
129        final DetailAST whitespaceFollowedAst = getWhitespaceFollowedNode(ast);
130
131        final int whitespaceColumnNo = getPositionAfter(whitespaceFollowedAst);
132        final int whitespaceLineNo = whitespaceFollowedAst.getLineNo();
133
134        if (hasTrailingWhitespace(ast, whitespaceColumnNo, whitespaceLineNo)) {
135            log(whitespaceLineNo, whitespaceColumnNo,
136                MSG_KEY, whitespaceFollowedAst.getText());
137        }
138    }
139
140    /**
141     * For a visited ast node returns node that should be checked
142     * for not being followed by whitespace.
143     * @param ast
144     *        , visited node.
145     * @return node before ast.
146     */
147    private static DetailAST getWhitespaceFollowedNode(DetailAST ast) {
148        DetailAST whitespaceFollowedAst;
149        switch (ast.getType()) {
150            case TokenTypes.TYPECAST:
151                whitespaceFollowedAst = ast.findFirstToken(TokenTypes.RPAREN);
152                break;
153            case TokenTypes.ARRAY_DECLARATOR:
154                whitespaceFollowedAst = getArrayDeclaratorPreviousElement(ast);
155                break;
156            case TokenTypes.INDEX_OP:
157                whitespaceFollowedAst = getIndexOpPreviousElement(ast);
158                break;
159            default:
160                whitespaceFollowedAst = ast;
161        }
162        return whitespaceFollowedAst;
163    }
164
165    /**
166     * Gets position after token (place of possible redundant whitespace).
167     * @param ast Node representing token.
168     * @return position after token.
169     */
170    private static int getPositionAfter(DetailAST ast) {
171        final int after;
172        //If target of possible redundant whitespace is in method definition.
173        if (ast.getType() == TokenTypes.IDENT
174                && ast.getNextSibling() != null
175                && ast.getNextSibling().getType() == TokenTypes.LPAREN) {
176            final DetailAST methodDef = ast.getParent();
177            final DetailAST endOfParams = methodDef.findFirstToken(TokenTypes.RPAREN);
178            after = endOfParams.getColumnNo() + 1;
179        }
180        else {
181            after = ast.getColumnNo() + ast.getText().length();
182        }
183        return after;
184    }
185
186    /**
187     * Checks if there is unwanted whitespace after the visited node.
188     * @param ast
189     *        , visited node.
190     * @param whitespaceColumnNo
191     *        , column number of a possible whitespace.
192     * @param whitespaceLineNo
193     *        , line number of a possible whitespace.
194     * @return true if whitespace found.
195     */
196    boolean hasTrailingWhitespace(DetailAST ast,
197        int whitespaceColumnNo, int whitespaceLineNo) {
198        final boolean result;
199        final int astLineNo = ast.getLineNo();
200        final String line = getLine(astLineNo - 1);
201        if (astLineNo == whitespaceLineNo && whitespaceColumnNo < line.length()) {
202            result = Character.isWhitespace(line.charAt(whitespaceColumnNo));
203        }
204        else {
205            result = !allowLineBreaks;
206        }
207        return result;
208    }
209
210    /**
211     * Returns proper argument for getPositionAfter method, it is a token after
212     * {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR}, in can be {@link TokenTypes#RBRACK
213     * RBRACK}, {@link TokenTypes#IDENT IDENT} or an array type definition (literal).
214     * @param ast
215     *        , {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR} node.
216     * @return previous node by text order.
217     */
218    private static DetailAST getArrayDeclaratorPreviousElement(DetailAST ast) {
219        final DetailAST previousElement;
220        final DetailAST firstChild = ast.getFirstChild();
221        if (firstChild.getType() == TokenTypes.ARRAY_DECLARATOR) {
222            // second or higher array index
223            previousElement = firstChild.findFirstToken(TokenTypes.RBRACK);
224        }
225        else {
226            // first array index, is preceded with identifier or type
227            final DetailAST parent = getFirstNonArrayDeclaratorParent(ast);
228            switch (parent.getType()) {
229                // generics
230                case TokenTypes.TYPE_ARGUMENT:
231                    final DetailAST wildcard = parent.findFirstToken(TokenTypes.WILDCARD_TYPE);
232                    if (wildcard == null) {
233                        // usual generic type argument like <char[]>
234                        previousElement = getTypeLastNode(ast);
235                    }
236                    else {
237                        // constructions with wildcard like <? extends String[]>
238                        previousElement = getTypeLastNode(ast.getFirstChild());
239                    }
240                    break;
241                // 'new' is a special case with its own subtree structure
242                case TokenTypes.LITERAL_NEW:
243                    previousElement = getTypeLastNode(parent);
244                    break;
245                // mundane array declaration, can be either java style or C style
246                case TokenTypes.TYPE:
247                    previousElement = getPreviousNodeWithParentOfTypeAst(ast, parent);
248                    break;
249                // i.e. boolean[].class
250                case TokenTypes.DOT:
251                    previousElement = getTypeLastNode(ast);
252                    break;
253                // java 8 method reference
254                case TokenTypes.METHOD_REF:
255                    final DetailAST ident = getIdentLastToken(ast);
256                    if (ident == null) {
257                        //i.e. int[]::new
258                        previousElement = ast.getFirstChild();
259                    }
260                    else {
261                        previousElement = ident;
262                    }
263                    break;
264                default:
265                    throw new IllegalStateException("unexpected ast syntax" + parent);
266            }
267        }
268        return previousElement;
269    }
270
271    /**
272     * Gets previous node for {@link TokenTypes#INDEX_OP INDEX_OP} token
273     * for usage in getPositionAfter method, it is a simplified copy of
274     * getArrayDeclaratorPreviousElement method.
275     * @param ast
276     *        , {@link TokenTypes#INDEX_OP INDEX_OP} node.
277     * @return previous node by text order.
278     */
279    private static DetailAST getIndexOpPreviousElement(DetailAST ast) {
280        DetailAST result;
281        final DetailAST firstChild = ast.getFirstChild();
282        if (firstChild.getType() == TokenTypes.INDEX_OP) {
283            // second or higher array index
284            result = firstChild.findFirstToken(TokenTypes.RBRACK);
285        }
286        else {
287            final DetailAST ident = getIdentLastToken(ast);
288            if (ident == null) {
289                // construction like ((byte[]) pixels)[0]
290                result = ast.findFirstToken(TokenTypes.RPAREN);
291            }
292            else {
293                result = ident;
294            }
295        }
296        return result;
297    }
298
299    /**
300     * Get node that owns {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR} sequence.
301     * @param ast
302     *        , {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR} node.
303     * @return owner node.
304     */
305    private static DetailAST getFirstNonArrayDeclaratorParent(DetailAST ast) {
306        DetailAST parent = ast.getParent();
307        while (parent.getType() == TokenTypes.ARRAY_DECLARATOR) {
308            parent = parent.getParent();
309        }
310        return parent;
311    }
312
313    /**
314     * Searches parameter node for a type node.
315     * Returns it or its last node if it has an extended structure.
316     * @param ast
317     *        , subject node.
318     * @return type node.
319     */
320    private static DetailAST getTypeLastNode(DetailAST ast) {
321        DetailAST result = ast.findFirstToken(TokenTypes.TYPE_ARGUMENTS);
322        if (result == null) {
323            result = getIdentLastToken(ast);
324            if (result == null) {
325                //primitive literal expected
326                result = ast.getFirstChild();
327            }
328        }
329        else {
330            result = result.findFirstToken(TokenTypes.GENERIC_END);
331        }
332        return result;
333    }
334
335    /**
336     * Finds previous node by text order for an array declarator,
337     * which parent type is {@link TokenTypes#TYPE TYPE}.
338     * @param ast
339     *        , array declarator node.
340     * @param parent
341     *        , its parent node.
342     * @return previous node by text order.
343     */
344    private static DetailAST getPreviousNodeWithParentOfTypeAst(DetailAST ast, DetailAST parent) {
345        final DetailAST previousElement;
346        final DetailAST ident = getIdentLastToken(parent.getParent());
347        final DetailAST lastTypeNode = getTypeLastNode(ast);
348        // sometimes there are ident-less sentences
349        // i.e. "(Object[]) null", but in casual case should be
350        // checked whether ident or lastTypeNode has preceding position
351        // determining if it is java style or C style
352        if (ident == null || ident.getLineNo() > ast.getLineNo()) {
353            previousElement = lastTypeNode;
354        }
355        else if (ident.getLineNo() < ast.getLineNo()) {
356            previousElement = ident;
357        }
358        //ident and lastTypeNode lay on one line
359        else {
360            if (ident.getColumnNo() > ast.getColumnNo()
361                || lastTypeNode.getColumnNo() > ident.getColumnNo()) {
362                previousElement = lastTypeNode;
363            }
364            else {
365                previousElement = ident;
366            }
367        }
368        return previousElement;
369    }
370
371    /**
372     * Gets leftmost token of identifier.
373     * @param ast
374     *        , token possibly possessing an identifier.
375     * @return leftmost token of identifier.
376     */
377    private static DetailAST getIdentLastToken(DetailAST ast) {
378        // single identifier token as a name is the most common case
379        DetailAST result = ast.findFirstToken(TokenTypes.IDENT);
380        if (result == null) {
381            final DetailAST dot = ast.findFirstToken(TokenTypes.DOT);
382            // method call case
383            if (dot == null) {
384                final DetailAST methodCall = ast.findFirstToken(TokenTypes.METHOD_CALL);
385                if (methodCall != null) {
386                    result = methodCall.findFirstToken(TokenTypes.RPAREN);
387                }
388            }
389            // qualified name case
390            else {
391                if (dot.findFirstToken(TokenTypes.DOT) == null) {
392                    result = dot.getFirstChild().getNextSibling();
393                }
394                else {
395                    result = dot.findFirstToken(TokenTypes.IDENT);
396                }
397            }
398        }
399        return result;
400    }
401
402}