001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2014  Oliver Burn
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 afer 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#ARRAY_DECLARATOR ARRAY_DECLARATOR},
043 *  {@link TokenTypes#UNARY_PLUS UNARY_PLUS}. It also supports the operator
044 *  {@link TokenTypes#TYPECAST TYPECAST}.
045 * </p>
046 * <p>
047 * An example of how to configure the check is:
048 * </p>
049 * <pre>
050 * &lt;module name="NoWhitespaceAfter"/&gt;
051 * </pre>
052 * <p> An example of how to configure the check to forbid linebreaks after
053 * a {@link TokenTypes#DOT DOT} token is:
054 * </p>
055 * <pre>
056 * &lt;module name="NoWhitespaceAfter"&gt;
057 *     &lt;property name="tokens" value="DOT"/&gt;
058 *     &lt;property name="allowLineBreaks" value="false"/&gt;
059 * &lt;/module&gt;
060 * </pre>
061 * @author Rick Giles
062 * @author lkuehne
063 * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a>
064 * @version 1.0
065 */
066public class NoWhitespaceAfterCheck extends Check
067{
068    /** Whether whitespace is allowed if the AST is at a linebreak */
069    private boolean allowLineBreaks = true;
070
071    @Override
072    public int[] getDefaultTokens()
073    {
074        return new int[] {
075            TokenTypes.ARRAY_INIT,
076            TokenTypes.INC,
077            TokenTypes.DEC,
078            TokenTypes.UNARY_MINUS,
079            TokenTypes.UNARY_PLUS,
080            TokenTypes.BNOT,
081            TokenTypes.LNOT,
082            TokenTypes.DOT,
083            TokenTypes.ARRAY_DECLARATOR,
084        };
085    }
086
087    @Override
088    public int[] getAcceptableTokens()
089    {
090        return new int[] {
091            TokenTypes.ARRAY_INIT,
092            TokenTypes.INC,
093            TokenTypes.DEC,
094            TokenTypes.UNARY_MINUS,
095            TokenTypes.UNARY_PLUS,
096            TokenTypes.BNOT,
097            TokenTypes.LNOT,
098            TokenTypes.DOT,
099            TokenTypes.TYPECAST,
100            TokenTypes.ARRAY_DECLARATOR,
101        };
102    }
103
104    @Override
105    public void visitToken(DetailAST ast)
106    {
107        DetailAST astNode = ast;
108        if (ast.getType() == TokenTypes.ARRAY_DECLARATOR
109                 || ast.getType() == TokenTypes.TYPECAST)
110        {
111            astNode = getPreceded(ast);
112        }
113
114        final String line = getLine(ast.getLineNo() - 1);
115        final int after = getPositionAfter(astNode);
116
117        if ((after >= line.length() || Character.isWhitespace(line.charAt(after)))
118                 && hasRedundantWhitespace(line, after))
119        {
120            log(astNode.getLineNo(), after,
121                "ws.followed", astNode.getText());
122        }
123    }
124
125    /**
126     * Gets possible place where redundant whitespace could be.
127     * @param arrayOrTypeCast {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR}
128     *  or {@link TokenTypes#TYPECAST TYPECAST}.
129     * @return possible place of redundant whitespace.
130     */
131    private static DetailAST getPreceded(DetailAST arrayOrTypeCast)
132    {
133        DetailAST preceded = arrayOrTypeCast;
134        switch (arrayOrTypeCast.getType()) {
135            case TokenTypes.TYPECAST:
136                preceded = arrayOrTypeCast.findFirstToken(TokenTypes.RPAREN);
137                break;
138            case TokenTypes.ARRAY_DECLARATOR:
139                preceded = getArrayTypeOrIdentifier(arrayOrTypeCast);
140                break;
141            default:
142                throw new IllegalStateException(arrayOrTypeCast.toString());
143        }
144        return preceded;
145    }
146
147    /**
148     * Gets position after token (place of possible redundant whitespace).
149     * @param ast Node representing token.
150     * @return position after token.
151     */
152    private static int getPositionAfter(DetailAST ast)
153    {
154        int after;
155        //If target of possible redundant whitespace is in method definition
156        if (ast.getType() == TokenTypes.IDENT
157                && ast.getNextSibling() != null
158                && ast.getNextSibling().getType() == TokenTypes.LPAREN)
159        {
160            final DetailAST methodDef = ast.getParent();
161            final DetailAST endOfParams = methodDef.findFirstToken(TokenTypes.RPAREN);
162            after = endOfParams.getColumnNo() + 1;
163        }
164        else {
165            after = ast.getColumnNo() + ast.getText().length();
166        }
167        return after;
168    }
169
170    /**
171     * Gets target place of possible redundant whitespace (array's type or identifier)
172     *  after which {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR} is set.
173     * @param arrayDeclarator {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR}
174     * @return target place before possible redundant whitespace.
175     */
176    private static DetailAST getArrayTypeOrIdentifier(DetailAST arrayDeclarator)
177    {
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                typeOrIdent = arrayDeclarator.getFirstChild();
202            }
203        }
204        return typeOrIdent;
205    }
206
207    /**
208     * Gets array identifier, e.g.:
209     * <p>
210     * <code>
211     * int[] someArray;
212     * <code>
213     * </p>
214     * <p>
215     * someArray is identifier.
216     * </p>
217     * @param arrayDeclarator {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR}
218     * @return array identifier.
219     */
220    private static DetailAST getArrayIdentifier(DetailAST arrayDeclarator)
221    {
222        return arrayDeclarator.getParent().getNextSibling();
223    }
224
225    /**
226     * Checks if current array is multidimensional.
227     * @param arrayDeclaration {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR}
228     * @return true if current array is multidimensional.
229     */
230    private static boolean isMultiDimensionalArray(DetailAST arrayDeclaration)
231    {
232        return arrayDeclaration.getParent().getType() == TokenTypes.ARRAY_DECLARATOR
233                || arrayDeclaration.getFirstChild().getType() == TokenTypes.ARRAY_DECLARATOR;
234    }
235
236    /**
237     * Checks if current array declaration is part of array instantiation.
238     * @param arrayDeclaration {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR}
239     * @return true if current array declaration is part of array instantiation.
240     */
241    private static boolean isArrayInstantiation(DetailAST arrayDeclaration)
242    {
243        return arrayDeclaration.getParent().getType() == TokenTypes.LITERAL_NEW;
244    }
245
246    /**
247     * Control whether whitespace is flagged at linebreaks.
248     * @param allowLineBreaks whether whitespace should be
249     * flagged at linebreaks.
250     */
251    public void setAllowLineBreaks(boolean allowLineBreaks)
252    {
253        this.allowLineBreaks = allowLineBreaks;
254    }
255
256    /**
257     * Checks if current array is declared in C style, e.g.:
258     * <p>
259     * <code>
260     * int array[] = { ... }; //C style
261     * </code>
262     * </p>
263     * <p>
264     * <code>
265     * int[] array = { ... }; //Java style
266     * </code>
267     * </p>
268     * @param arrayDeclaration {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR}
269     * @return true if array is declared in C style
270     */
271    private static boolean isCstyleArrayDeclaration(DetailAST arrayDeclaration)
272    {
273        boolean result = false;
274        final DetailAST identifier = getArrayIdentifier(arrayDeclaration);
275        if (identifier != null) {
276            final int arrayDeclarationStart = arrayDeclaration.getColumnNo();
277            final int identifierEnd = identifier.getColumnNo() + identifier.getText().length();
278            result = arrayDeclarationStart == identifierEnd
279                     || arrayDeclarationStart > identifierEnd;
280        }
281        return result;
282    }
283
284    /**
285     * Works with multidimensional arrays.
286     * @param arrayDeclaration {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR}
287     * @return true if multidimensional array is declared in C style.
288     */
289    private static boolean isCstyleMultiDimensionalArrayDeclaration(DetailAST arrayDeclaration)
290    {
291        boolean result = false;
292        DetailAST parentArrayDeclaration = arrayDeclaration;
293        while (parentArrayDeclaration != null) {
294            if (parentArrayDeclaration.getParent() != null
295                    && parentArrayDeclaration.getParent().getType() == TokenTypes.TYPE)
296            {
297                result = isCstyleArrayDeclaration(parentArrayDeclaration);
298            }
299            parentArrayDeclaration = parentArrayDeclaration.getParent();
300        }
301        return result;
302    }
303
304    /**
305     * Checks if current line has redundant whitespace after specified index.
306     * @param line line of java source.
307     * @param after specified index.
308     * @return true if line contains redundant whitespace.
309     */
310    private boolean hasRedundantWhitespace(String line, int after)
311    {
312        boolean result = !allowLineBreaks;
313        for (int i = after + 1; !result && (i < line.length()); i++) {
314            if (!Character.isWhitespace(line.charAt(i))) {
315                result = true;
316            }
317        }
318        return result;
319    }
320}