001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2025 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.CommonUtil;
027
028/**
029 * <div>
030 * Checks that a token is surrounded by whitespace. Empty constructor,
031 * method, class, enum, interface, loop bodies (blocks), lambdas of the form
032 * </div>
033 * <div class="wrapper"><pre class="prettyprint"><code class="language-java">
034 * public MyClass() {}      // empty constructor
035 * public void func() {}    // empty method
036 * public interface Foo {} // empty interface
037 * public class Foo {} // empty class
038 * public enum Foo {} // empty enum
039 * MyClass c = new MyClass() {}; // empty anonymous class
040 * while (i = 1) {} // empty while loop
041 * for (int i = 1; i &gt; 1; i++) {} // empty for loop
042 * do {} while (i = 1); // empty do-while loop
043 * Runnable noop = () -&gt; {}; // empty lambda
044 * public @interface Beta {} // empty annotation type
045 * </code></pre></div>
046 *
047 * <p>
048 * may optionally be exempted from the policy using the {@code allowEmptyMethods},
049 * {@code allowEmptyConstructors}, {@code allowEmptyTypes}, {@code allowEmptyLoops},
050 * {@code allowEmptyLambdas}, {@code allowEmptyCatches}
051 * and {@code allowEmptySwitchBlockStatements} properties.
052 * </p>
053 *
054 * <p>
055 * This check does not flag as violation double brace initialization like:
056 * </p>
057 * <div class="wrapper"><pre class="prettyprint"><code class="language-java">
058 * new Properties() {{
059 *     setProperty("key", "value");
060 * }};
061 * </code></pre></div>
062 *
063 * <p>
064 * Parameter allowEmptyCatches allows to suppress violations when token list
065 * contains SLIST to check if beginning of block is surrounded by whitespace
066 * and catch block is empty, for example:
067 * </p>
068 * <div class="wrapper"><pre class="prettyprint"><code class="language-java">
069 * try {
070 *     k = 5 / i;
071 * } catch (ArithmeticException ex) {}
072 * </code></pre></div>
073 *
074 * <p>
075 * With this property turned off, this raises violation because the beginning
076 * of the catch block (left curly bracket) is not separated from the end
077 * of the catch block (right curly bracket).
078 * </p>
079 *
080 * <p>
081 * Note: <a href="https://openjdk.org/jeps/361">
082 * Switch expressions</a> are ignored by this check.
083 * </p>
084 * <ul>
085 * <li>
086 * Property {@code allowEmptyCatches} - Allow empty catch bodies.
087 * Type is {@code boolean}.
088 * Default value is {@code false}.
089 * </li>
090 * <li>
091 * Property {@code allowEmptyConstructors} - Allow empty constructor bodies.
092 * Type is {@code boolean}.
093 * Default value is {@code false}.
094 * </li>
095 * <li>
096 * Property {@code allowEmptyLambdas} - Allow empty lambda bodies.
097 * Type is {@code boolean}.
098 * Default value is {@code false}.
099 * </li>
100 * <li>
101 * Property {@code allowEmptyLoops} - Allow empty loop bodies.
102 * Type is {@code boolean}.
103 * Default value is {@code false}.
104 * </li>
105 * <li>
106 * Property {@code allowEmptyMethods} - Allow empty method bodies.
107 * Type is {@code boolean}.
108 * Default value is {@code false}.
109 * </li>
110 * <li>
111 * Property {@code allowEmptySwitchBlockStatements} - Allow empty switch blocks
112 * and block statements.
113 * Type is {@code boolean}.
114 * Default value is {@code false}.
115 * </li>
116 * <li>
117 * Property {@code allowEmptyTypes} - Allow empty class, interface and enum bodies.
118 * Type is {@code boolean}.
119 * Default value is {@code false}.
120 * </li>
121 * <li>
122 * Property {@code ignoreEnhancedForColon} - Ignore whitespace around colon in
123 * <a href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-14.14.2">
124 * enhanced for</a> loop.
125 * Type is {@code boolean}.
126 * Default value is {@code true}.
127 * </li>
128 * <li>
129 * Property {@code tokens} - tokens to check
130 * Type is {@code java.lang.String[]}.
131 * Validation type is {@code tokenSet}.
132 * Default value is:
133 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ASSIGN">
134 * ASSIGN</a>,
135 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BAND">
136 * BAND</a>,
137 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BAND_ASSIGN">
138 * BAND_ASSIGN</a>,
139 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BOR">
140 * BOR</a>,
141 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BOR_ASSIGN">
142 * BOR_ASSIGN</a>,
143 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BSR">
144 * BSR</a>,
145 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BSR_ASSIGN">
146 * BSR_ASSIGN</a>,
147 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BXOR">
148 * BXOR</a>,
149 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BXOR_ASSIGN">
150 * BXOR_ASSIGN</a>,
151 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COLON">
152 * COLON</a>,
153 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#DIV">
154 * DIV</a>,
155 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#DIV_ASSIGN">
156 * DIV_ASSIGN</a>,
157 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#DO_WHILE">
158 * DO_WHILE</a>,
159 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#EQUAL">
160 * EQUAL</a>,
161 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#GE">
162 * GE</a>,
163 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#GT">
164 * GT</a>,
165 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAMBDA">
166 * LAMBDA</a>,
167 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAND">
168 * LAND</a>,
169 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LCURLY">
170 * LCURLY</a>,
171 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LE">
172 * LE</a>,
173 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_CATCH">
174 * LITERAL_CATCH</a>,
175 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_DO">
176 * LITERAL_DO</a>,
177 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_ELSE">
178 * LITERAL_ELSE</a>,
179 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FINALLY">
180 * LITERAL_FINALLY</a>,
181 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FOR">
182 * LITERAL_FOR</a>,
183 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_IF">
184 * LITERAL_IF</a>,
185 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_RETURN">
186 * LITERAL_RETURN</a>,
187 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SWITCH">
188 * LITERAL_SWITCH</a>,
189 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SYNCHRONIZED">
190 * LITERAL_SYNCHRONIZED</a>,
191 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_TRY">
192 * LITERAL_TRY</a>,
193 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_WHILE">
194 * LITERAL_WHILE</a>,
195 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LOR">
196 * LOR</a>,
197 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LT">
198 * LT</a>,
199 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#MINUS">
200 * MINUS</a>,
201 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#MINUS_ASSIGN">
202 * MINUS_ASSIGN</a>,
203 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#MOD">
204 * MOD</a>,
205 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#MOD_ASSIGN">
206 * MOD_ASSIGN</a>,
207 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#NOT_EQUAL">
208 * NOT_EQUAL</a>,
209 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PLUS">
210 * PLUS</a>,
211 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PLUS_ASSIGN">
212 * PLUS_ASSIGN</a>,
213 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#QUESTION">
214 * QUESTION</a>,
215 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RCURLY">
216 * RCURLY</a>,
217 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SL">
218 * SL</a>,
219 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SLIST">
220 * SLIST</a>,
221 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SL_ASSIGN">
222 * SL_ASSIGN</a>,
223 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SR">
224 * SR</a>,
225 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SR_ASSIGN">
226 * SR_ASSIGN</a>,
227 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STAR">
228 * STAR</a>,
229 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STAR_ASSIGN">
230 * STAR_ASSIGN</a>,
231 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_ASSERT">
232 * LITERAL_ASSERT</a>,
233 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#TYPE_EXTENSION_AND">
234 * TYPE_EXTENSION_AND</a>,
235 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_WHEN">
236 * LITERAL_WHEN</a>.
237 * </li>
238 * </ul>
239 *
240 * <p>
241 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
242 * </p>
243 *
244 * <p>
245 * Violation Message Keys:
246 * </p>
247 * <ul>
248 * <li>
249 * {@code ws.notFollowed}
250 * </li>
251 * <li>
252 * {@code ws.notPreceded}
253 * </li>
254 * </ul>
255 *
256 * @since 3.0
257 */
258@StatelessCheck
259public class WhitespaceAroundCheck extends AbstractCheck {
260
261    /**
262     * A key is pointing to the warning message text in "messages.properties"
263     * file.
264     */
265    public static final String MSG_WS_NOT_PRECEDED = "ws.notPreceded";
266
267    /**
268     * A key is pointing to the warning message text in "messages.properties"
269     * file.
270     */
271    public static final String MSG_WS_NOT_FOLLOWED = "ws.notFollowed";
272
273    /** Allow empty constructor bodies. */
274    private boolean allowEmptyConstructors;
275    /** Allow empty method bodies. */
276    private boolean allowEmptyMethods;
277    /** Allow empty class, interface and enum bodies. */
278    private boolean allowEmptyTypes;
279    /** Allow empty loop bodies. */
280    private boolean allowEmptyLoops;
281    /** Allow empty lambda bodies. */
282    private boolean allowEmptyLambdas;
283    /** Allow empty catch bodies. */
284    private boolean allowEmptyCatches;
285    /** Allow empty switch blocks and block statements. */
286    private boolean allowEmptySwitchBlockStatements;
287    /**
288     * Ignore whitespace around colon in
289     * <a href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-14.14.2">
290     * enhanced for</a> loop.
291     */
292    private boolean ignoreEnhancedForColon = true;
293
294    @Override
295    public int[] getDefaultTokens() {
296        return new int[] {
297            TokenTypes.ASSIGN,
298            TokenTypes.BAND,
299            TokenTypes.BAND_ASSIGN,
300            TokenTypes.BOR,
301            TokenTypes.BOR_ASSIGN,
302            TokenTypes.BSR,
303            TokenTypes.BSR_ASSIGN,
304            TokenTypes.BXOR,
305            TokenTypes.BXOR_ASSIGN,
306            TokenTypes.COLON,
307            TokenTypes.DIV,
308            TokenTypes.DIV_ASSIGN,
309            TokenTypes.DO_WHILE,
310            TokenTypes.EQUAL,
311            TokenTypes.GE,
312            TokenTypes.GT,
313            TokenTypes.LAMBDA,
314            TokenTypes.LAND,
315            TokenTypes.LCURLY,
316            TokenTypes.LE,
317            TokenTypes.LITERAL_CATCH,
318            TokenTypes.LITERAL_DO,
319            TokenTypes.LITERAL_ELSE,
320            TokenTypes.LITERAL_FINALLY,
321            TokenTypes.LITERAL_FOR,
322            TokenTypes.LITERAL_IF,
323            TokenTypes.LITERAL_RETURN,
324            TokenTypes.LITERAL_SWITCH,
325            TokenTypes.LITERAL_SYNCHRONIZED,
326            TokenTypes.LITERAL_TRY,
327            TokenTypes.LITERAL_WHILE,
328            TokenTypes.LOR,
329            TokenTypes.LT,
330            TokenTypes.MINUS,
331            TokenTypes.MINUS_ASSIGN,
332            TokenTypes.MOD,
333            TokenTypes.MOD_ASSIGN,
334            TokenTypes.NOT_EQUAL,
335            TokenTypes.PLUS,
336            TokenTypes.PLUS_ASSIGN,
337            TokenTypes.QUESTION,
338            TokenTypes.RCURLY,
339            TokenTypes.SL,
340            TokenTypes.SLIST,
341            TokenTypes.SL_ASSIGN,
342            TokenTypes.SR,
343            TokenTypes.SR_ASSIGN,
344            TokenTypes.STAR,
345            TokenTypes.STAR_ASSIGN,
346            TokenTypes.LITERAL_ASSERT,
347            TokenTypes.TYPE_EXTENSION_AND,
348            TokenTypes.LITERAL_WHEN,
349        };
350    }
351
352    @Override
353    public int[] getAcceptableTokens() {
354        return new int[] {
355            TokenTypes.ASSIGN,
356            TokenTypes.ARRAY_INIT,
357            TokenTypes.BAND,
358            TokenTypes.BAND_ASSIGN,
359            TokenTypes.BOR,
360            TokenTypes.BOR_ASSIGN,
361            TokenTypes.BSR,
362            TokenTypes.BSR_ASSIGN,
363            TokenTypes.BXOR,
364            TokenTypes.BXOR_ASSIGN,
365            TokenTypes.COLON,
366            TokenTypes.DIV,
367            TokenTypes.DIV_ASSIGN,
368            TokenTypes.DO_WHILE,
369            TokenTypes.EQUAL,
370            TokenTypes.GE,
371            TokenTypes.GT,
372            TokenTypes.LAMBDA,
373            TokenTypes.LAND,
374            TokenTypes.LCURLY,
375            TokenTypes.LE,
376            TokenTypes.LITERAL_CATCH,
377            TokenTypes.LITERAL_DO,
378            TokenTypes.LITERAL_ELSE,
379            TokenTypes.LITERAL_FINALLY,
380            TokenTypes.LITERAL_FOR,
381            TokenTypes.LITERAL_IF,
382            TokenTypes.LITERAL_RETURN,
383            TokenTypes.LITERAL_SWITCH,
384            TokenTypes.LITERAL_SYNCHRONIZED,
385            TokenTypes.LITERAL_TRY,
386            TokenTypes.LITERAL_WHILE,
387            TokenTypes.LOR,
388            TokenTypes.LT,
389            TokenTypes.MINUS,
390            TokenTypes.MINUS_ASSIGN,
391            TokenTypes.MOD,
392            TokenTypes.MOD_ASSIGN,
393            TokenTypes.NOT_EQUAL,
394            TokenTypes.PLUS,
395            TokenTypes.PLUS_ASSIGN,
396            TokenTypes.QUESTION,
397            TokenTypes.RCURLY,
398            TokenTypes.SL,
399            TokenTypes.SLIST,
400            TokenTypes.SL_ASSIGN,
401            TokenTypes.SR,
402            TokenTypes.SR_ASSIGN,
403            TokenTypes.STAR,
404            TokenTypes.STAR_ASSIGN,
405            TokenTypes.LITERAL_ASSERT,
406            TokenTypes.TYPE_EXTENSION_AND,
407            TokenTypes.WILDCARD_TYPE,
408            TokenTypes.GENERIC_START,
409            TokenTypes.GENERIC_END,
410            TokenTypes.ELLIPSIS,
411            TokenTypes.LITERAL_WHEN,
412        };
413    }
414
415    @Override
416    public int[] getRequiredTokens() {
417        return CommonUtil.EMPTY_INT_ARRAY;
418    }
419
420    /**
421     * Setter to allow empty method bodies.
422     *
423     * @param allow {@code true} to allow empty method bodies.
424     * @since 4.0
425     */
426    public void setAllowEmptyMethods(boolean allow) {
427        allowEmptyMethods = allow;
428    }
429
430    /**
431     * Setter to allow empty constructor bodies.
432     *
433     * @param allow {@code true} to allow empty constructor bodies.
434     * @since 4.0
435     */
436    public void setAllowEmptyConstructors(boolean allow) {
437        allowEmptyConstructors = allow;
438    }
439
440    /**
441     * Setter to ignore whitespace around colon in
442     * <a href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-14.14.2">
443     * enhanced for</a> loop.
444     *
445     * @param ignore {@code true} to ignore enhanced for colon.
446     * @since 5.5
447     */
448    public void setIgnoreEnhancedForColon(boolean ignore) {
449        ignoreEnhancedForColon = ignore;
450    }
451
452    /**
453     * Setter to allow empty class, interface and enum bodies.
454     *
455     * @param allow {@code true} to allow empty type bodies.
456     * @since 5.8
457     */
458    public void setAllowEmptyTypes(boolean allow) {
459        allowEmptyTypes = allow;
460    }
461
462    /**
463     * Setter to allow empty loop bodies.
464     *
465     * @param allow {@code true} to allow empty loops bodies.
466     * @since 5.8
467     */
468    public void setAllowEmptyLoops(boolean allow) {
469        allowEmptyLoops = allow;
470    }
471
472    /**
473     * Setter to allow empty lambda bodies.
474     *
475     * @param allow {@code true} to allow empty lambda expressions.
476     * @since 6.14
477     */
478    public void setAllowEmptyLambdas(boolean allow) {
479        allowEmptyLambdas = allow;
480    }
481
482    /**
483     * Setter to allow empty catch bodies.
484     *
485     * @param allow {@code true} to allow empty catch blocks.
486     * @since 7.6
487     */
488    public void setAllowEmptyCatches(boolean allow) {
489        allowEmptyCatches = allow;
490    }
491
492    /**
493     * Setter to allow empty switch blocks and block statements.
494     *
495     * @param allow {@code true} to allow empty switch case and default blocks.
496     * @since 10.19.0
497     */
498    public void setAllowEmptySwitchBlockStatements(boolean allow) {
499        allowEmptySwitchBlockStatements = allow;
500    }
501
502    @Override
503    public void visitToken(DetailAST ast) {
504        final int currentType = ast.getType();
505        if (!isNotRelevantSituation(ast, currentType)) {
506            final int[] line = getLineCodePoints(ast.getLineNo() - 1);
507            final int before = ast.getColumnNo() - 1;
508            final int after = ast.getColumnNo() + ast.getText().length();
509
510            if (before >= 0 && shouldCheckSeparationFromPreviousToken(ast)
511                        && !CommonUtil.isCodePointWhitespace(line, before)) {
512                log(ast, MSG_WS_NOT_PRECEDED, ast.getText());
513            }
514
515            if (after < line.length) {
516                final char nextChar = Character.toChars(line[after])[0];
517                if (shouldCheckSeparationFromNextToken(ast, nextChar)
518                        && !Character.isWhitespace(nextChar)) {
519                    log(ast, MSG_WS_NOT_FOLLOWED, ast.getText());
520                }
521            }
522        }
523    }
524
525    /**
526     * Is ast not a target of Check.
527     *
528     * @param ast ast
529     * @param currentType type of ast
530     * @return true is ok to skip validation
531     */
532    private boolean isNotRelevantSituation(DetailAST ast, int currentType) {
533        final int parentType = ast.getParent().getType();
534        return switch (parentType) {
535            case TokenTypes.DOT -> currentType == TokenTypes.STAR;
536            case TokenTypes.LITERAL_DEFAULT, TokenTypes.LITERAL_CASE, TokenTypes.CASE_GROUP -> true;
537            case TokenTypes.FOR_EACH_CLAUSE -> ignoreEnhancedForColon;
538            case TokenTypes.EXPR -> currentType == TokenTypes.LITERAL_SWITCH;
539            case TokenTypes.ARRAY_INIT, TokenTypes.ANNOTATION_ARRAY_INIT ->
540                currentType == TokenTypes.RCURLY;
541            default -> isEmptyBlock(ast, parentType)
542                    || allowEmptyTypes && isEmptyType(ast);
543        };
544    }
545
546    /**
547     * Check if it should be checked if previous token is separated from current by
548     * whitespace.
549     * This function is needed to recognise double brace initialization as valid,
550     * unfortunately it's not possible to implement this functionality
551     * in isNotRelevantSituation method, because in this method when we return
552     * true(is not relevant) ast is later doesn't check at all. For example:
553     * new Properties() {{setProperty("double curly braces", "are not a style violation");
554     * }};
555     * For second left curly brace in first line when we would return true from
556     * isNotRelevantSituation it wouldn't later check that the next token(setProperty)
557     * is not separated from previous token.
558     *
559     * @param ast current AST.
560     * @return true if it should be checked if previous token is separated by whitespace,
561     *      false otherwise.
562     */
563    private static boolean shouldCheckSeparationFromPreviousToken(DetailAST ast) {
564        return !isPartOfDoubleBraceInitializerForPreviousToken(ast);
565    }
566
567    /**
568     * Check if it should be checked if next token is separated from current by
569     * whitespace. Explanation why this method is needed is identical to one
570     * included in shouldCheckSeparationFromPreviousToken method.
571     *
572     * @param ast current AST.
573     * @param nextChar next character.
574     * @return true if it should be checked if next token is separated by whitespace,
575     *      false otherwise.
576     */
577    private boolean shouldCheckSeparationFromNextToken(DetailAST ast, char nextChar) {
578        return !isEmptyCtorBlockCheckedFromSlist(ast)
579                && !(ast.getType() == TokenTypes.LITERAL_RETURN
580                && ast.getFirstChild().getType() == TokenTypes.SEMI)
581                && ast.getType() != TokenTypes.ARRAY_INIT
582                && !isAnonymousInnerClassEnd(ast.getType(), nextChar)
583                && !isPartOfDoubleBraceInitializerForNextToken(ast);
584    }
585
586    /**
587     * Check for "})" or "};" or "},". Happens with anon-inners
588     *
589     * @param currentType token
590     * @param nextChar next symbol
591     * @return true is that is end of anon inner class
592     */
593    private static boolean isAnonymousInnerClassEnd(int currentType, char nextChar) {
594        return currentType == TokenTypes.RCURLY
595                && (nextChar == ')'
596                        || nextChar == ';'
597                        || nextChar == ','
598                        || nextChar == '.');
599    }
600
601    /**
602     * Is empty block.
603     *
604     * @param ast ast
605     * @param parentType parent
606     * @return true is block is empty
607     */
608    private boolean isEmptyBlock(DetailAST ast, int parentType) {
609        return isEmptyMethodBlock(ast, parentType)
610                || isEmptyCtorBlockCheckedFromRcurly(ast)
611                || isEmptyLoop(ast, parentType)
612                || isEmptyLambda(ast, parentType)
613                || isEmptyCatch(ast, parentType)
614                || isEmptySwitchBlockStatement(ast);
615    }
616
617    /**
618     * Tests if a given {@code DetailAST} is part of an empty block.
619     * An example empty block might look like the following
620     * <pre>   public void myMethod(int val) {}</pre>
621     * In the above, the method body is an empty block ("{}").
622     *
623     * @param ast the {@code DetailAST} to test.
624     * @param parentType the token type of {@code ast}'s parent.
625     * @param match the parent token type we're looking to match.
626     * @return {@code true} if {@code ast} makes up part of an
627     *         empty block contained under a {@code match} token type
628     *         node.
629     */
630    private static boolean isEmptyBlock(DetailAST ast, int parentType, int match) {
631        final boolean result;
632        final int type = ast.getType();
633        if (type == TokenTypes.RCURLY) {
634            final DetailAST parent = ast.getParent();
635            final DetailAST grandParent = ast.getParent().getParent();
636            result = parent.getFirstChild().getType() == TokenTypes.RCURLY
637                    && grandParent.getType() == match;
638        }
639        else {
640            result = type == TokenTypes.SLIST
641                && parentType == match
642                && ast.getFirstChild().getType() == TokenTypes.RCURLY;
643        }
644        return result;
645    }
646
647    /**
648     * Test if the given {@code DetailAST} is part of an allowed empty
649     * method block.
650     *
651     * @param ast the {@code DetailAST} to test.
652     * @param parentType the token type of {@code ast}'s parent.
653     * @return {@code true} if {@code ast} makes up part of an
654     *         allowed empty method block.
655     */
656    private boolean isEmptyMethodBlock(DetailAST ast, int parentType) {
657        return allowEmptyMethods
658                && isEmptyBlock(ast, parentType, TokenTypes.METHOD_DEF);
659    }
660
661    /**
662     * Test if the given {@code DetailAST} is part of an allowed empty
663     * constructor (ctor) block checked from RCURLY.
664     *
665     * @param ast the {@code DetailAST} to test.
666     * @return {@code true} if {@code ast} makes up part of an
667     *         allowed empty constructor block.
668     */
669    private boolean isEmptyCtorBlockCheckedFromRcurly(DetailAST ast) {
670        final DetailAST parent = ast.getParent();
671        final DetailAST grandParent = ast.getParent().getParent();
672        return allowEmptyConstructors
673                && parent.getFirstChild().getType() == TokenTypes.RCURLY
674                && (grandParent.getType() == TokenTypes.CTOR_DEF
675                        || grandParent.getType() == TokenTypes.COMPACT_CTOR_DEF);
676
677    }
678
679    /**
680     * Test if the given {@code DetailAST} is a part of an allowed
681     * empty constructor checked from SLIST token.
682     *
683     * @param ast the {@code DetailAST} to test.
684     * @return {@code true} if {@code ast} makes up part of an
685     *          empty constructor block.
686     */
687    private boolean isEmptyCtorBlockCheckedFromSlist(DetailAST ast) {
688        return allowEmptyConstructors
689                && (ast.getParent().getType() == TokenTypes.CTOR_DEF
690                        || ast.getParent().getType() == TokenTypes.COMPACT_CTOR_DEF)
691                && ast.getFirstChild().getType() == TokenTypes.RCURLY;
692    }
693
694    /**
695     * Checks if loop is empty.
696     *
697     * @param ast ast the {@code DetailAST} to test.
698     * @param parentType the token type of {@code ast}'s parent.
699     * @return {@code true} if {@code ast} makes up part of an
700     *         allowed empty loop block.
701     */
702    private boolean isEmptyLoop(DetailAST ast, int parentType) {
703        return allowEmptyLoops
704                && (isEmptyBlock(ast, parentType, TokenTypes.LITERAL_FOR)
705                        || isEmptyBlock(ast, parentType, TokenTypes.LITERAL_WHILE)
706                        || isEmptyBlock(ast, parentType, TokenTypes.LITERAL_DO));
707    }
708
709    /**
710     * Test if the given {@code DetailAST} is part of an allowed empty
711     * lambda block.
712     *
713     * @param ast the {@code DetailAST} to test.
714     * @param parentType the token type of {@code ast}'s parent.
715     * @return {@code true} if {@code ast} makes up part of an
716     *         allowed empty lambda block.
717     */
718    private boolean isEmptyLambda(DetailAST ast, int parentType) {
719        return allowEmptyLambdas && isEmptyBlock(ast, parentType, TokenTypes.LAMBDA);
720    }
721
722    /**
723     * Tests if the given {@code DetailAst} is part of an allowed empty
724     * catch block.
725     *
726     * @param ast the {@code DetailAst} to test.
727     * @param parentType the token type of {@code ast}'s parent
728     * @return {@code true} if {@code ast} makes up part of an
729     *         allowed empty catch block.
730     */
731    private boolean isEmptyCatch(DetailAST ast, int parentType) {
732        return allowEmptyCatches && isEmptyBlock(ast, parentType, TokenTypes.LITERAL_CATCH);
733    }
734
735    /**
736     * Tests if the given {@code DetailAst} is part of an allowed empty
737     * switch case or default block.
738     *
739     * @param ast the {@code DetailAst} to test.
740     * @return {@code true} if {@code ast} makes up part of an allowed
741     *         empty switch case or default block.
742     */
743    private boolean isEmptySwitchBlockStatement(DetailAST ast) {
744        final boolean isEmptySwitchBlockStatement;
745
746        if (allowEmptySwitchBlockStatements) {
747            final DetailAST parent = ast.getParent();
748            final DetailAST grandParent = parent.getParent();
749
750            final boolean isEmptyCaseInSwitchRule =
751                    isEmptyBlock(ast, parent.getType(), TokenTypes.SWITCH_RULE);
752
753            final boolean isEmptyCaseGroupCheckedFromLcurly =
754                    isEmptyBlock(ast, grandParent.getType(), TokenTypes.CASE_GROUP);
755
756            final boolean isEmptyCaseGroupCheckedFromRcurly =
757                    parent.getFirstChild().getType() == TokenTypes.RCURLY
758                      && grandParent.getParent().getType() == TokenTypes.CASE_GROUP;
759
760            isEmptySwitchBlockStatement = isEmptyCaseInSwitchRule
761                    || isEmptyCaseGroupCheckedFromLcurly || isEmptyCaseGroupCheckedFromRcurly;
762        }
763        else {
764            isEmptySwitchBlockStatement = false;
765        }
766
767        return isEmptySwitchBlockStatement;
768    }
769
770    /**
771     * Test if the given {@code DetailAST} is part of an empty block.
772     * An example empty block might look like the following
773     * <pre>   class Foo {}</pre>
774     *
775     * @param ast ast the {@code DetailAST} to test.
776     * @return {@code true} if {@code ast} makes up part of an
777     *         empty block contained under a {@code match} token type
778     *         node.
779     */
780    private static boolean isEmptyType(DetailAST ast) {
781        final int type = ast.getType();
782        final DetailAST nextSibling = ast.getNextSibling();
783        final DetailAST previousSibling = ast.getPreviousSibling();
784        return type == TokenTypes.LCURLY
785                    && nextSibling.getType() == TokenTypes.RCURLY
786                || previousSibling != null
787                    && previousSibling.getType() == TokenTypes.LCURLY;
788    }
789
790    /**
791     * Check if given ast is part of double brace initializer and if it
792     * should omit checking if previous token is separated by whitespace.
793     *
794     * @param ast ast to check
795     * @return true if it should omit checking for previous token, false otherwise
796     */
797    private static boolean isPartOfDoubleBraceInitializerForPreviousToken(DetailAST ast) {
798        final boolean initializerBeginsAfterClassBegins =
799                ast.getParent().getType() == TokenTypes.INSTANCE_INIT;
800        final boolean classEndsAfterInitializerEnds = ast.getPreviousSibling() != null
801                && ast.getPreviousSibling().getType() == TokenTypes.INSTANCE_INIT;
802        return initializerBeginsAfterClassBegins || classEndsAfterInitializerEnds;
803    }
804
805    /**
806     * Check if given ast is part of double brace initializer and if it
807     * should omit checking if next token is separated by whitespace.
808     * See <a href="https://github.com/checkstyle/checkstyle/pull/2845">
809     * PR#2845</a> for more information why this function was needed.
810     *
811     * @param ast ast to check
812     * @return true if it should omit checking for next token, false otherwise
813     */
814    private static boolean isPartOfDoubleBraceInitializerForNextToken(DetailAST ast) {
815        final boolean classBeginBeforeInitializerBegin = ast.getType() == TokenTypes.LCURLY
816            && ast.getNextSibling().getType() == TokenTypes.INSTANCE_INIT;
817        final boolean initializerEndsBeforeClassEnds =
818            ast.getParent().getParent().getType() == TokenTypes.INSTANCE_INIT
819            && ast.getParent().getParent().getNextSibling().getType() == TokenTypes.RCURLY;
820        return classBeginBeforeInitializerBegin || initializerEndsBeforeClassEnds;
821    }
822
823}