1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.whitespace;
21  
22  import com.puppycrawl.tools.checkstyle.StatelessCheck;
23  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
24  import com.puppycrawl.tools.checkstyle.api.DetailAST;
25  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
26  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
27  
28  /**
29   * <p>
30   * Checks that a token is followed by whitespace, with the exception that it
31   * does not check for whitespace after the semicolon of an empty for iterator.
32   * Use Check {@link EmptyForIteratorPadCheck EmptyForIteratorPad} to validate
33   * empty for iterators.
34   * </p>
35   * <p> By default the check will check the following tokens:
36   *  {@link TokenTypes#COMMA COMMA},
37   *  {@link TokenTypes#SEMI SEMI},
38   *  {@link TokenTypes#TYPECAST TYPECAST},
39   *  {@link TokenTypes#LITERAL_IF LITERAL_IF},
40   *  {@link TokenTypes#LITERAL_ELSE LITERAL_ELSE},
41   *  {@link TokenTypes#LITERAL_WHILE LITERAL_WHILE},
42   *  {@link TokenTypes#LITERAL_FOR LITERAL_FOR},
43   *  {@link TokenTypes#LITERAL_DO LITERAL_DO},
44   *  {@link TokenTypes#DO_WHILE DO_WHILE}.
45   * </p>
46   * <p>
47   * An example of how to configure the check is:
48   * </p>
49   * <pre>
50   * &lt;module name="WhitespaceAfter"/&gt;
51   * </pre>
52   * <p> An example of how to configure the check for whitespace only after
53   * {@link TokenTypes#COMMA COMMA} and {@link TokenTypes#SEMI SEMI} tokens is:
54   * </p>
55   * <pre>
56   * &lt;module name="WhitespaceAfter"&gt;
57   *     &lt;property name="tokens" value="COMMA, SEMI"/&gt;
58   * &lt;/module&gt;
59   * </pre>
60   */
61  @StatelessCheck
62  public class WhitespaceAfterCheck
63      extends AbstractCheck {
64  
65      /**
66       * A key is pointing to the warning message text in "messages.properties"
67       * file.
68       */
69      public static final String MSG_WS_NOT_FOLLOWED = "ws.notFollowed";
70  
71      /**
72       * A key is pointing to the warning message text in "messages.properties"
73       * file.
74       */
75      public static final String MSG_WS_TYPECAST = "ws.typeCast";
76  
77      @Override
78      public int[] getDefaultTokens() {
79          return getAcceptableTokens();
80      }
81  
82      @Override
83      public int[] getAcceptableTokens() {
84          return new int[] {
85              TokenTypes.COMMA,
86              TokenTypes.SEMI,
87              TokenTypes.TYPECAST,
88              TokenTypes.LITERAL_IF,
89              TokenTypes.LITERAL_ELSE,
90              TokenTypes.LITERAL_WHILE,
91              TokenTypes.LITERAL_DO,
92              TokenTypes.LITERAL_FOR,
93              TokenTypes.DO_WHILE,
94          };
95      }
96  
97      @Override
98      public int[] getRequiredTokens() {
99          return CommonUtil.EMPTY_INT_ARRAY;
100     }
101 
102     @Override
103     public void visitToken(DetailAST ast) {
104         if (ast.getType() == TokenTypes.TYPECAST) {
105             final DetailAST targetAST = ast.findFirstToken(TokenTypes.RPAREN);
106             final String line = getLine(targetAST.getLineNo() - 1);
107             if (!isFollowedByWhitespace(targetAST, line)) {
108                 log(targetAST, MSG_WS_TYPECAST);
109             }
110         }
111         else {
112             final String line = getLine(ast.getLineNo() - 1);
113             if (!isFollowedByWhitespace(ast, line)) {
114                 final Object[] message = {ast.getText()};
115                 log(ast, MSG_WS_NOT_FOLLOWED, message);
116             }
117         }
118     }
119 
120     /**
121      * Checks whether token is followed by a whitespace.
122      * @param targetAST Ast token.
123      * @param line The line associated with the ast token.
124      * @return true if ast token is followed by a whitespace.
125      */
126     private static boolean isFollowedByWhitespace(DetailAST targetAST, String line) {
127         final int after =
128             targetAST.getColumnNo() + targetAST.getText().length();
129         boolean followedByWhitespace = true;
130 
131         if (after < line.length()) {
132             final char charAfter = line.charAt(after);
133             followedByWhitespace = charAfter == ';'
134                 || charAfter == ')'
135                 || Character.isWhitespace(charAfter);
136         }
137         return followedByWhitespace;
138     }
139 
140 }