001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2018 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.StatelessCheck;
023import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
024import com.puppycrawl.tools.checkstyle.api.DetailAST;
025import com.puppycrawl.tools.checkstyle.api.TokenTypes;
026import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
027
028/**
029 * <p>
030 * Checks that a token is followed by whitespace, with the exception that it
031 * does not check for whitespace after the semicolon of an empty for iterator.
032 * Use Check {@link EmptyForIteratorPadCheck EmptyForIteratorPad} to validate
033 * empty for iterators.
034 * </p>
035 * <p> By default the check will check the following tokens:
036 *  {@link TokenTypes#COMMA COMMA},
037 *  {@link TokenTypes#SEMI SEMI},
038 *  {@link TokenTypes#TYPECAST TYPECAST},
039 *  {@link TokenTypes#LITERAL_IF LITERAL_IF},
040 *  {@link TokenTypes#LITERAL_ELSE LITERAL_ELSE},
041 *  {@link TokenTypes#LITERAL_WHILE LITERAL_WHILE},
042 *  {@link TokenTypes#LITERAL_FOR LITERAL_FOR},
043 *  {@link TokenTypes#LITERAL_DO LITERAL_DO},
044 *  {@link TokenTypes#DO_WHILE DO_WHILE}.
045 * </p>
046 * <p>
047 * An example of how to configure the check is:
048 * </p>
049 * <pre>
050 * &lt;module name="WhitespaceAfter"/&gt;
051 * </pre>
052 * <p> An example of how to configure the check for whitespace only after
053 * {@link TokenTypes#COMMA COMMA} and {@link TokenTypes#SEMI SEMI} tokens is:
054 * </p>
055 * <pre>
056 * &lt;module name="WhitespaceAfter"&gt;
057 *     &lt;property name="tokens" value="COMMA, SEMI"/&gt;
058 * &lt;/module&gt;
059 * </pre>
060 * @author Oliver Burn
061 * @author Rick Giles
062 */
063@StatelessCheck
064public class WhitespaceAfterCheck
065    extends AbstractCheck {
066
067    /**
068     * A key is pointing to the warning message text in "messages.properties"
069     * file.
070     */
071    public static final String MSG_WS_NOT_FOLLOWED = "ws.notFollowed";
072
073    /**
074     * A key is pointing to the warning message text in "messages.properties"
075     * file.
076     */
077    public static final String MSG_WS_TYPECAST = "ws.typeCast";
078
079    @Override
080    public int[] getDefaultTokens() {
081        return getAcceptableTokens();
082    }
083
084    @Override
085    public int[] getAcceptableTokens() {
086        return new int[] {
087            TokenTypes.COMMA,
088            TokenTypes.SEMI,
089            TokenTypes.TYPECAST,
090            TokenTypes.LITERAL_IF,
091            TokenTypes.LITERAL_ELSE,
092            TokenTypes.LITERAL_WHILE,
093            TokenTypes.LITERAL_DO,
094            TokenTypes.LITERAL_FOR,
095            TokenTypes.DO_WHILE,
096        };
097    }
098
099    @Override
100    public int[] getRequiredTokens() {
101        return CommonUtils.EMPTY_INT_ARRAY;
102    }
103
104    @Override
105    public void visitToken(DetailAST ast) {
106        if (ast.getType() == TokenTypes.TYPECAST) {
107            final DetailAST targetAST = ast.findFirstToken(TokenTypes.RPAREN);
108            final String line = getLine(targetAST.getLineNo() - 1);
109            if (!isFollowedByWhitespace(targetAST, line)) {
110                log(targetAST.getLineNo(),
111                    targetAST.getColumnNo() + targetAST.getText().length(),
112                    MSG_WS_TYPECAST);
113            }
114        }
115        else {
116            final String line = getLine(ast.getLineNo() - 1);
117            if (!isFollowedByWhitespace(ast, line)) {
118                final Object[] message = {ast.getText()};
119                log(ast.getLineNo(),
120                    ast.getColumnNo() + ast.getText().length(),
121                    MSG_WS_NOT_FOLLOWED,
122                    message);
123            }
124        }
125    }
126
127    /**
128     * Checks whether token is followed by a whitespace.
129     * @param targetAST Ast token.
130     * @param line The line associated with the ast token.
131     * @return true if ast token is followed by a whitespace.
132     */
133    private static boolean isFollowedByWhitespace(DetailAST targetAST, String line) {
134        final int after =
135            targetAST.getColumnNo() + targetAST.getText().length();
136        boolean followedByWhitespace = true;
137
138        if (after < line.length()) {
139            final char charAfter = line.charAt(after);
140            followedByWhitespace = charAfter == ';'
141                || charAfter == ')'
142                || Character.isWhitespace(charAfter);
143        }
144        return followedByWhitespace;
145    }
146
147}