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 org.apache.commons.lang3.ArrayUtils;
023
024import com.puppycrawl.tools.checkstyle.api.Check;
025import com.puppycrawl.tools.checkstyle.api.DetailAST;
026import com.puppycrawl.tools.checkstyle.api.TokenTypes;
027
028/**
029 * <p>
030 * Checks that there is no whitespace after a token.
031 * More specifically, it checks that it is not followed by whitespace,
032 * or (if line breaks are allowed) all characters on the line after are
033 * whitespace. To forbid line breaks after a token, set property
034 * allowLineBreaks to false.
035 * </p>
036  * <p> By default the check will check the following operators:
037 *  {@link TokenTypes#ARRAY_INIT ARRAY_INIT},
038 *  {@link TokenTypes#BNOT BNOT},
039 *  {@link TokenTypes#DEC DEC},
040 *  {@link TokenTypes#DOT DOT},
041 *  {@link TokenTypes#INC INC},
042 *  {@link TokenTypes#LNOT LNOT},
043 *  {@link TokenTypes#UNARY_MINUS UNARY_MINUS},
044 *  {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR},
045 *  {@link TokenTypes#UNARY_PLUS UNARY_PLUS}. It also supports the operator
046 *  {@link TokenTypes#TYPECAST TYPECAST}.
047 * </p>
048 * <p>
049 * An example of how to configure the check is:
050 * </p>
051 * <pre>
052 * &lt;module name="NoWhitespaceAfter"/&gt;
053 * </pre>
054 * <p> An example of how to configure the check to forbid line breaks after
055 * a {@link TokenTypes#DOT DOT} token is:
056 * </p>
057 * <pre>
058 * &lt;module name="NoWhitespaceAfter"&gt;
059 *     &lt;property name="tokens" value="DOT"/&gt;
060 *     &lt;property name="allowLineBreaks" value="false"/&gt;
061 * &lt;/module&gt;
062 * </pre>
063 * @author Rick Giles
064 * @author lkuehne
065 * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a>
066 */
067public class NoWhitespaceAfterCheck extends Check {
068
069    /**
070     * A key is pointing to the warning message text in "messages.properties"
071     * file.
072     */
073    public static final String MSG_KEY = "ws.followed";
074
075    /** Whether whitespace is allowed if the AST is at a linebreak. */
076    private boolean allowLineBreaks = true;
077
078    @Override
079    public int[] getDefaultTokens() {
080        return new int[] {
081            TokenTypes.ARRAY_INIT,
082            TokenTypes.INC,
083            TokenTypes.DEC,
084            TokenTypes.UNARY_MINUS,
085            TokenTypes.UNARY_PLUS,
086            TokenTypes.BNOT,
087            TokenTypes.LNOT,
088            TokenTypes.DOT,
089            TokenTypes.ARRAY_DECLARATOR,
090        };
091    }
092
093    @Override
094    public int[] getAcceptableTokens() {
095        return new int[] {
096            TokenTypes.ARRAY_INIT,
097            TokenTypes.INC,
098            TokenTypes.DEC,
099            TokenTypes.UNARY_MINUS,
100            TokenTypes.UNARY_PLUS,
101            TokenTypes.BNOT,
102            TokenTypes.LNOT,
103            TokenTypes.DOT,
104            TokenTypes.TYPECAST,
105            TokenTypes.ARRAY_DECLARATOR,
106            TokenTypes.GENERIC_START,
107            TokenTypes.GENERIC_END,
108        };
109    }
110
111    @Override
112    public int[] getRequiredTokens() {
113        return ArrayUtils.EMPTY_INT_ARRAY;
114    }
115
116    @Override
117    public void visitToken(DetailAST ast) {
118        final DetailAST astNode = getPreceded(ast);
119        final String line = getLine(ast.getLineNo() - 1);
120        final int after = getPositionAfter(astNode);
121
122        if ((after >= line.length() || Character.isWhitespace(line.charAt(after)))
123                 && hasRedundantWhitespace(line, after)) {
124            log(astNode.getLineNo(), after,
125                MSG_KEY, astNode.getText());
126        }
127    }
128
129    /**
130     * Gets possible place where redundant whitespace could be.
131     * @param ast Node representing token.
132     * @return possible place of redundant whitespace.
133     */
134    private static DetailAST getPreceded(DetailAST ast) {
135        DetailAST preceded;
136
137        switch (ast.getType()) {
138            case TokenTypes.TYPECAST:
139                preceded = ast.findFirstToken(TokenTypes.RPAREN);
140                break;
141            case TokenTypes.ARRAY_DECLARATOR:
142                preceded = getArrayTypeOrIdentifier(ast);
143                break;
144            default:
145                preceded = ast;
146        }
147        return preceded;
148    }
149
150    /**
151     * Gets position after token (place of possible redundant whitespace).
152     * @param ast Node representing token.
153     * @return position after token.
154     */
155    private static int getPositionAfter(DetailAST ast) {
156        int after;
157        //If target of possible redundant whitespace is in method definition
158        if (ast.getType() == TokenTypes.IDENT
159                && ast.getNextSibling() != null
160                && ast.getNextSibling().getType() == TokenTypes.LPAREN) {
161            final DetailAST methodDef = ast.getParent();
162            final DetailAST endOfParams = methodDef.findFirstToken(TokenTypes.RPAREN);
163            after = endOfParams.getColumnNo() + 1;
164        }
165        else {
166            after = ast.getColumnNo() + ast.getText().length();
167        }
168        return after;
169    }
170
171    /**
172     * Gets target place of possible redundant whitespace (array's type or identifier)
173     *  after which {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR} is set.
174     * @param arrayDeclarator {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR}
175     * @return target place before possible redundant whitespace.
176     */
177    private static DetailAST getArrayTypeOrIdentifier(DetailAST arrayDeclarator) {
178        DetailAST typeOrIdent = arrayDeclarator;
179        if (isArrayInstantiation(arrayDeclarator)) {
180            typeOrIdent = arrayDeclarator.getParent().getFirstChild();
181        }
182        else if (isMultiDimensionalArray(arrayDeclarator)) {
183            if (isCStyleMultiDimensionalArrayDeclaration(arrayDeclarator)) {
184                if (arrayDeclarator.getParent().getType() != TokenTypes.ARRAY_DECLARATOR) {
185                    typeOrIdent = getArrayIdentifier(arrayDeclarator);
186                }
187            }
188            else {
189                DetailAST arrayIdentifier = arrayDeclarator.getFirstChild();
190                while (arrayIdentifier != null) {
191                    typeOrIdent = arrayIdentifier;
192                    arrayIdentifier = arrayIdentifier.getFirstChild();
193                }
194            }
195        }
196        else {
197            if (isCStyleArrayDeclaration(arrayDeclarator)) {
198                typeOrIdent = getArrayIdentifier(arrayDeclarator);
199            }
200            else {
201                if (isArrayUsedAsTypeForGenericBoundedWildcard(arrayDeclarator)) {
202                    typeOrIdent = arrayDeclarator.getParent();
203                }
204                else {
205                    typeOrIdent = arrayDeclarator.getFirstChild();
206                }
207            }
208        }
209        return typeOrIdent;
210    }
211
212    /**
213     * Gets array identifier, e.g.:
214     * <p>
215     * {@code
216     * int[] someArray;
217     * }
218     * </p>
219     * <p>
220     * someArray is identifier.
221     * </p>
222     * @param arrayDeclarator {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR}
223     * @return array identifier.
224     */
225    private static DetailAST getArrayIdentifier(DetailAST arrayDeclarator) {
226        return arrayDeclarator.getParent().getNextSibling();
227    }
228
229    /**
230     * Checks if current array is multidimensional.
231     * @param arrayDeclaration {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR}
232     * @return true if current array is multidimensional.
233     */
234    private static boolean isMultiDimensionalArray(DetailAST arrayDeclaration) {
235        return arrayDeclaration.getParent().getType() == TokenTypes.ARRAY_DECLARATOR
236                || arrayDeclaration.getFirstChild().getType() == TokenTypes.ARRAY_DECLARATOR;
237    }
238
239    /**
240     * Checks if current array declaration is part of array instantiation.
241     * @param arrayDeclaration {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR}
242     * @return true if current array declaration is part of array instantiation.
243     */
244    private static boolean isArrayInstantiation(DetailAST arrayDeclaration) {
245        return arrayDeclaration.getParent().getType() == TokenTypes.LITERAL_NEW;
246    }
247
248    /**
249     * Checks if current array is used as type for generic bounded wildcard.
250     * <p>
251     * E.g. {@code <? extends String[]>} or {@code <? super Object[]>}.
252     * </p>
253     * @param arrayDeclarator {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR}
254     * @return true if current array is used as type for generic bounded wildcard.
255     */
256    private static boolean isArrayUsedAsTypeForGenericBoundedWildcard(DetailAST arrayDeclarator) {
257        final int firstChildType = arrayDeclarator.getFirstChild().getType();
258        return firstChildType == TokenTypes.TYPE_UPPER_BOUNDS
259                || firstChildType == TokenTypes.TYPE_LOWER_BOUNDS;
260    }
261
262    /**
263     * Control whether whitespace is flagged at line breaks.
264     * @param allowLineBreaks whether whitespace should be
265     *     flagged at line breaks.
266     */
267    public void setAllowLineBreaks(boolean allowLineBreaks) {
268        this.allowLineBreaks = allowLineBreaks;
269    }
270
271    /**
272     * Checks if current array is declared in C style, e.g.:
273     * <p>
274     * {@code
275     * int array[] = { ... }; //C style
276     * }
277     * </p>
278     * <p>
279     * {@code
280     * int[] array = { ... }; //Java style
281     * }
282     * </p>
283     * @param arrayDeclaration {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR}
284     * @return true if array is declared in C style
285     */
286    private static boolean isCStyleArrayDeclaration(DetailAST arrayDeclaration) {
287        boolean result = false;
288        final DetailAST identifier = getArrayIdentifier(arrayDeclaration);
289        if (identifier != null) {
290            final int arrayDeclarationStart = arrayDeclaration.getColumnNo();
291            final int identifierEnd = identifier.getColumnNo() + identifier.getText().length();
292            result = arrayDeclarationStart == identifierEnd
293                     || arrayDeclarationStart > identifierEnd;
294        }
295        return result;
296    }
297
298    /**
299     * Works with multidimensional arrays.
300     * @param arrayDeclaration {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR}
301     * @return true if multidimensional array is declared in C style.
302     */
303    private static boolean isCStyleMultiDimensionalArrayDeclaration(DetailAST arrayDeclaration) {
304        boolean result = false;
305        DetailAST parentArrayDeclaration = arrayDeclaration;
306        while (parentArrayDeclaration != null) {
307            if (parentArrayDeclaration.getParent() != null
308                    && parentArrayDeclaration.getParent().getType() == TokenTypes.TYPE) {
309                result = isCStyleArrayDeclaration(parentArrayDeclaration);
310            }
311            parentArrayDeclaration = parentArrayDeclaration.getParent();
312        }
313        return result;
314    }
315
316    /**
317     * Checks if current line has redundant whitespace after specified index.
318     * @param line line of java source.
319     * @param after specified index.
320     * @return true if line contains redundant whitespace.
321     */
322    private boolean hasRedundantWhitespace(String line, int after) {
323        boolean result = !allowLineBreaks;
324        for (int i = after + 1; !result && i < line.length(); i++) {
325            if (!Character.isWhitespace(line.charAt(i))) {
326                result = true;
327            }
328        }
329        return result;
330    }
331}