View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2026 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.utils;
21  
22  import java.nio.file.Path;
23  import java.util.ArrayList;
24  import java.util.Arrays;
25  import java.util.Collection;
26  import java.util.HashSet;
27  import java.util.List;
28  import java.util.Optional;
29  import java.util.Set;
30  import java.util.function.Predicate;
31  import java.util.regex.Pattern;
32  import java.util.stream.Collectors;
33  import java.util.stream.Stream;
34  
35  import com.puppycrawl.tools.checkstyle.api.DetailAST;
36  import com.puppycrawl.tools.checkstyle.api.FullIdent;
37  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
38  import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption;
39  
40  /**
41   * Contains utility methods for the checks.
42   *
43   */
44  public final class CheckUtil {
45  
46      // constants for parseDouble()
47      /** Binary radix. */
48      private static final int BASE_2 = 2;
49  
50      /** Octal radix. */
51      private static final int BASE_8 = 8;
52  
53      /** Decimal radix. */
54      private static final int BASE_10 = 10;
55  
56      /** Hex radix. */
57      private static final int BASE_16 = 16;
58  
59      /** Pattern matching underscore characters ('_'). */
60      private static final Pattern UNDERSCORE_PATTERN = Pattern.compile("_");
61  
62      /** Compiled pattern for all system newlines. */
63      private static final Pattern ALL_NEW_LINES = Pattern.compile("\\R");
64  
65      /** Package separator. */
66      private static final char PACKAGE_SEPARATOR = '.';
67  
68      /** Prevent instances. */
69      private CheckUtil() {
70      }
71  
72      /**
73       * Tests whether a method definition AST defines an equals covariant.
74       *
75       * @param ast the method definition AST to test.
76       *     Precondition: ast is a TokenTypes.METHOD_DEF node.
77       * @return true if ast defines an equals covariant.
78       */
79      public static boolean isEqualsMethod(DetailAST ast) {
80          boolean equalsMethod = false;
81  
82          if (ast.getType() == TokenTypes.METHOD_DEF) {
83              final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
84              final boolean staticOrAbstract =
85                      modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null
86                      || modifiers.findFirstToken(TokenTypes.ABSTRACT) != null;
87  
88              if (!staticOrAbstract) {
89                  final DetailAST nameNode = ast.findFirstToken(TokenTypes.IDENT);
90                  final String name = nameNode.getText();
91  
92                  if ("equals".equals(name)) {
93                      // one parameter?
94                      final DetailAST paramsNode = ast.findFirstToken(TokenTypes.PARAMETERS);
95                      equalsMethod = paramsNode.getChildCount() == 1;
96                  }
97              }
98          }
99          return equalsMethod;
100     }
101 
102     /**
103      * Returns the value represented by the specified string of the specified
104      * type. Returns 0 for types other than float, double, int, and long.
105      *
106      * @param text the string to be parsed.
107      * @param type the token type of the text. Should be a constant of
108      *     {@link TokenTypes}.
109      * @return the double value represented by the string argument.
110      */
111     public static double parseDouble(String text, int type) {
112         String txt = UNDERSCORE_PATTERN.matcher(text).replaceAll("");
113 
114         return switch (type) {
115             case TokenTypes.NUM_FLOAT, TokenTypes.NUM_DOUBLE -> Double.parseDouble(txt);
116 
117             case TokenTypes.NUM_INT, TokenTypes.NUM_LONG -> {
118                 int radix = BASE_10;
119                 if (txt.startsWith("0x") || txt.startsWith("0X")) {
120                     radix = BASE_16;
121                     txt = txt.substring(2);
122                 }
123                 else if (txt.startsWith("0b") || txt.startsWith("0B")) {
124                     radix = BASE_2;
125                     txt = txt.substring(2);
126                 }
127                 else if (txt.startsWith("0")) {
128                     radix = BASE_8;
129                 }
130                 yield parseNumber(txt, radix, type);
131             }
132 
133             default -> Double.NaN;
134         };
135     }
136 
137     /**
138      * Parses the string argument as an integer or a long in the radix specified by
139      * the second argument. The characters in the string must all be digits of the specified radix.
140      *
141      * @param text the String containing the integer representation to be
142      *     parsed. Precondition: text contains a parsable int.
143      * @param radix the radix to be used while parsing text.
144      * @param type the token type of the text. Should be a constant of
145      *     {@link TokenTypes}.
146      * @return the number represented by the string argument in the specified radix.
147      */
148     private static double parseNumber(final String text, final int radix, final int type) {
149         String txt = text;
150         if (txt.endsWith("L") || txt.endsWith("l")) {
151             txt = txt.substring(0, txt.length() - 1);
152         }
153         final double result;
154 
155         final boolean negative = txt.charAt(0) == '-';
156         if (type == TokenTypes.NUM_INT) {
157             if (negative) {
158                 result = Integer.parseInt(txt, radix);
159             }
160             else {
161                 result = Integer.parseUnsignedInt(txt, radix);
162             }
163         }
164         else {
165             if (negative) {
166                 result = Long.parseLong(txt, radix);
167             }
168             else {
169                 result = Long.parseUnsignedLong(txt, radix);
170             }
171         }
172 
173         return result;
174     }
175 
176     /**
177      * Finds sub-node for given node minimal (line, column) pair.
178      *
179      * @param node the root of tree for search.
180      * @return sub-node with minimal (line, column) pair.
181      */
182     public static DetailAST getFirstNode(final DetailAST node) {
183         DetailAST currentNode = node;
184         DetailAST child = node.getFirstChild();
185         while (child != null) {
186             final DetailAST newNode = getFirstNode(child);
187             if (isBeforeInSource(newNode, currentNode)) {
188                 currentNode = newNode;
189             }
190             child = child.getNextSibling();
191         }
192 
193         return currentNode;
194     }
195 
196     /**
197      * Retrieves whether ast1 is located before ast2.
198      *
199      * @param ast1 the first node.
200      * @param ast2 the second node.
201      * @return true, if ast1 is located before ast2.
202      */
203     public static boolean isBeforeInSource(DetailAST ast1, DetailAST ast2) {
204         return ast1.getLineNo() < ast2.getLineNo()
205             || TokenUtil.areOnSameLine(ast1, ast2)
206                 && ast1.getColumnNo() < ast2.getColumnNo();
207     }
208 
209     /**
210      * Retrieves the names of the type parameters to the node.
211      *
212      * @param node the parameterized AST node
213      * @return a list of type parameter names
214      */
215     public static List<String> getTypeParameterNames(final DetailAST node) {
216         final DetailAST typeParameters =
217             node.findFirstToken(TokenTypes.TYPE_PARAMETERS);
218 
219         final List<String> typeParameterNames = new ArrayList<>();
220         if (typeParameters != null) {
221             final DetailAST typeParam =
222                 typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER);
223             typeParameterNames.add(
224                     typeParam.findFirstToken(TokenTypes.IDENT).getText());
225 
226             DetailAST sibling = typeParam.getNextSibling();
227             while (sibling != null) {
228                 if (sibling.getType() == TokenTypes.TYPE_PARAMETER) {
229                     typeParameterNames.add(
230                             sibling.findFirstToken(TokenTypes.IDENT).getText());
231                 }
232                 sibling = sibling.getNextSibling();
233             }
234         }
235 
236         return typeParameterNames;
237     }
238 
239     /**
240      * Retrieves the type parameters to the node.
241      *
242      * @param node the parameterized AST node
243      * @return a list of type parameter names
244      */
245     public static List<DetailAST> getTypeParameters(final DetailAST node) {
246         final DetailAST typeParameters =
247             node.findFirstToken(TokenTypes.TYPE_PARAMETERS);
248 
249         final List<DetailAST> typeParams = new ArrayList<>();
250         if (typeParameters != null) {
251             final DetailAST typeParam =
252                 typeParameters.findFirstToken(TokenTypes.TYPE_PARAMETER);
253             typeParams.add(typeParam);
254 
255             DetailAST sibling = typeParam.getNextSibling();
256             while (sibling != null) {
257                 if (sibling.getType() == TokenTypes.TYPE_PARAMETER) {
258                     typeParams.add(sibling);
259                 }
260                 sibling = sibling.getNextSibling();
261             }
262         }
263 
264         return typeParams;
265     }
266 
267     /**
268      * Checks whether a method is a not void one.
269      *
270      * @param methodDefAst the method node.
271      * @return true if method is a not void one.
272      */
273     public static boolean isNonVoidMethod(DetailAST methodDefAst) {
274         boolean returnValue = false;
275         if (methodDefAst.getType() == TokenTypes.METHOD_DEF) {
276             final DetailAST typeAST = methodDefAst.findFirstToken(TokenTypes.TYPE);
277             if (typeAST.findFirstToken(TokenTypes.LITERAL_VOID) == null) {
278                 returnValue = true;
279             }
280         }
281         return returnValue;
282     }
283 
284     /**
285      * Checks whether a parameter is a receiver.
286      *
287      * <p>A receiver parameter is a special parameter that
288      * represents the object for which the method is invoked.
289      * It is denoted by the reserved keyword {@code this} in the method declaration. Check
290      * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PARAMETER_DEF">
291      * PARAMETER_DEF</a>
292      * </p>
293      *
294      * @param parameterDefAst the parameter node.
295      * @return true if the parameter is a receiver.
296      * @see <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.1">
297      *     ReceiverParameter</a>
298      */
299     public static boolean isReceiverParameter(DetailAST parameterDefAst) {
300         return parameterDefAst.findFirstToken(TokenTypes.IDENT) == null;
301     }
302 
303     /**
304      * Returns the access modifier of the method/constructor at the specified AST. If
305      * the method is in an interface or annotation block, the access modifier is assumed
306      * to be public.
307      *
308      * @param ast the token of the method/constructor.
309      * @return the access modifier of the method/constructor.
310      */
311     public static AccessModifierOption getAccessModifierFromModifiersToken(DetailAST ast) {
312         AccessModifierOption accessModifier;
313         if (ast.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
314             accessModifier = AccessModifierOption.PUBLIC;
315         }
316         else {
317             final DetailAST modsToken = ast.findFirstToken(TokenTypes.MODIFIERS);
318             accessModifier = getAccessModifierFromModifiersTokenDirectly(modsToken);
319         }
320 
321         if (accessModifier == AccessModifierOption.PACKAGE) {
322             if (ScopeUtil.isInEnumBlock(ast) && ast.getType() == TokenTypes.CTOR_DEF) {
323                 accessModifier = AccessModifierOption.PRIVATE;
324             }
325             else if (ScopeUtil.isInInterfaceOrAnnotationBlock(ast)) {
326                 accessModifier = AccessModifierOption.PUBLIC;
327             }
328         }
329 
330         return accessModifier;
331     }
332 
333     /**
334      * Returns {@link AccessModifierOption} based on the information about access modifier
335      * taken from the given token of type {@link TokenTypes#MODIFIERS}.
336      *
337      * @param modifiersToken token of type {@code TokenTypes#MODIFIERS}.
338      * @return {@code AccessModifierOption}.
339      * @throws IllegalArgumentException when expected non-null modifiersToken with type 'MODIFIERS'
340      */
341     private static AccessModifierOption getAccessModifierFromModifiersTokenDirectly(
342             DetailAST modifiersToken) {
343         if (modifiersToken == null) {
344             throw new IllegalArgumentException("expected non-null AST-token with type 'MODIFIERS'");
345         }
346 
347         AccessModifierOption accessModifier = AccessModifierOption.PACKAGE;
348         for (DetailAST token = modifiersToken.getFirstChild(); token != null;
349              token = token.getNextSibling()) {
350             final int tokenType = token.getType();
351             if (tokenType == TokenTypes.LITERAL_PUBLIC) {
352                 accessModifier = AccessModifierOption.PUBLIC;
353             }
354             else if (tokenType == TokenTypes.LITERAL_PROTECTED) {
355                 accessModifier = AccessModifierOption.PROTECTED;
356             }
357             else if (tokenType == TokenTypes.LITERAL_PRIVATE) {
358                 accessModifier = AccessModifierOption.PRIVATE;
359             }
360         }
361         return accessModifier;
362     }
363 
364     /**
365      * Returns the access modifier of the surrounding "block".
366      *
367      * @param node the node to return the access modifier for
368      * @return the access modifier of the surrounding block
369      */
370     public static Optional<AccessModifierOption> getSurroundingAccessModifier(DetailAST node) {
371         Optional<AccessModifierOption> returnValue = Optional.empty();
372         for (DetailAST token = node;
373              returnValue.isEmpty() && !TokenUtil.isRootNode(token);
374              token = token.getParent()) {
375             final int type = token.getType();
376             if (type == TokenTypes.CLASS_DEF
377                 || type == TokenTypes.INTERFACE_DEF
378                 || type == TokenTypes.ANNOTATION_DEF
379                 || type == TokenTypes.ENUM_DEF) {
380                 returnValue = Optional.ofNullable(getAccessModifierFromModifiersToken(token));
381             }
382             else if (type == TokenTypes.LITERAL_NEW) {
383                 break;
384             }
385         }
386 
387         return returnValue;
388     }
389 
390     /**
391      * Create set of class names and short class names.
392      *
393      * @param classNames array of class names.
394      * @return set of class names and short class names.
395      */
396     public static Set<String> parseClassNames(String... classNames) {
397         return Arrays.stream(classNames)
398                 .flatMap(className -> Stream.of(className, CommonUtil.baseClassName(className)))
399                 .filter(Predicate.not(String::isEmpty))
400                 .collect(Collectors.toUnmodifiableSet());
401     }
402 
403     /**
404      * Strip initial newline and preceding whitespace on each line from text block content.
405      * In order to be consistent with how javac handles this task, we have modeled this
406      * implementation after the code from:
407      * github.com/openjdk/jdk14u/blob/master/src/java.base/share/classes/java/lang/String.java
408      *
409      * @param textBlockContent the actual content of the text block.
410      * @return string consistent with javac representation.
411      */
412     public static String stripIndentAndInitialNewLineFromTextBlock(String textBlockContent) {
413         final String contentWithInitialNewLineRemoved =
414             ALL_NEW_LINES.matcher(textBlockContent).replaceFirst("");
415         final List<String> lines =
416             Arrays.asList(ALL_NEW_LINES.split(contentWithInitialNewLineRemoved));
417         final int indent = getSmallestIndent(lines);
418         final String suffix = "";
419 
420         return lines.stream()
421                 .map(line -> stripIndentAndTrailingWhitespaceFromLine(line, indent))
422                 .collect(Collectors.joining(System.lineSeparator(), suffix, suffix));
423     }
424 
425     /**
426      * Helper method for stripIndentAndInitialNewLineFromTextBlock, strips correct indent
427      * from string, and trailing whitespace, or returns empty string if no text.
428      *
429      * @param line the string to strip indent and trailing whitespace from
430      * @param indent the amount of indent to remove
431      * @return modified string with removed indent and trailing whitespace, or empty string.
432      */
433     private static String stripIndentAndTrailingWhitespaceFromLine(String line, int indent) {
434         final int lastNonWhitespace = lastIndexOfNonWhitespace(line);
435         String returnString = "";
436         if (lastNonWhitespace > 0) {
437             returnString = line.substring(indent, lastNonWhitespace);
438         }
439         return returnString;
440     }
441 
442     /**
443      * Helper method for stripIndentAndInitialNewLineFromTextBlock, to determine the smallest
444      * indent in a text block string literal.
445      *
446      * @param lines collection of actual text block content, split by line.
447      * @return number of spaces representing the smallest indent in this text block.
448      */
449     private static int getSmallestIndent(Collection<String> lines) {
450         return lines.stream()
451             .mapToInt(CommonUtil::indexOfNonWhitespace)
452             .min()
453             .orElse(0);
454     }
455 
456     /**
457      * Helper method to find the index of the last non-whitespace character in a string.
458      *
459      * @param line the string to find the last index of a non-whitespace character for.
460      * @return the index of the last non-whitespace character.
461      */
462     private static int lastIndexOfNonWhitespace(String line) {
463         int length;
464         for (length = line.length(); length > 0; length--) {
465             if (!Character.isWhitespace(line.charAt(length - 1))) {
466                 break;
467             }
468         }
469         return length;
470     }
471 
472     /**
473      * Calculates and returns the type declaration name matching count.
474      *
475      * <p>
476      * Suppose our pattern class is {@code foo.a.b} and class to be matched is
477      * {@code foo.a.ball} then type declaration name matching count would be calculated by
478      * comparing every character, and updating main counter when we hit "." to prevent matching
479      * "a.b" with "a.ball". In this case type declaration name matching count
480      * would be equal to 6 and not 7 (b of ball is not counted).
481      * </p>
482      *
483      * @param patternClass class against which the given class has to be matched
484      * @param classToBeMatched class to be matched
485      * @return class name matching count
486      */
487     public static int typeDeclarationNameMatchingCount(String patternClass,
488                                                        String classToBeMatched) {
489         final int length = Math.min(classToBeMatched.length(), patternClass.length());
490         int result = 0;
491         for (int index = 0;
492                 index < length
493                     && patternClass.charAt(index) == classToBeMatched.charAt(index);
494                 index++) {
495             if (patternClass.charAt(index) == PACKAGE_SEPARATOR) {
496                 result = index;
497             }
498         }
499         return result;
500     }
501 
502     /**
503      * Get the qualified name of type declaration by combining {@code packageName},
504      * {@code outerClassQualifiedName} and {@code className}.
505      *
506      * @param packageName packageName
507      * @param outerClassQualifiedName outerClassQualifiedName
508      * @param className className
509      * @return the qualified name of type declaration by combining {@code packageName},
510      *         {@code outerClassQualifiedName} and {@code className}
511      */
512     public static String getQualifiedTypeDeclarationName(String packageName,
513                                                          String outerClassQualifiedName,
514                                                          String className) {
515         final String qualifiedClassName;
516 
517         if (outerClassQualifiedName == null) {
518             if (packageName == null) {
519                 qualifiedClassName = className;
520             }
521             else {
522                 qualifiedClassName = packageName + PACKAGE_SEPARATOR + className;
523             }
524         }
525         else {
526             qualifiedClassName = outerClassQualifiedName + PACKAGE_SEPARATOR + className;
527         }
528         return qualifiedClassName;
529     }
530 
531     /**
532      * Get name of package and super class of anon inner class by concatenating
533      * the identifier values under {@link TokenTypes#DOT}.
534      *
535      * @param ast ast to extract superclass or package name from
536      * @return qualified name
537      */
538     public static String extractQualifiedName(DetailAST ast) {
539         return FullIdent.createFullIdent(ast).getText();
540     }
541 
542     /**
543      * Get the short name of super class of anonymous inner class.
544      * Example:
545      * {@snippet lang="text" :
546      * TestClass.NestedClass obj = new Test().new NestedClass() {};
547      * // Short name will be Test.NestedClass
548      * }
549      *
550      * @param literalNewAst ast node of type {@link TokenTypes#LITERAL_NEW}
551      * @return short name of base class of anonymous inner class
552      */
553     public static String getShortNameOfAnonInnerClass(DetailAST literalNewAst) {
554         DetailAST parentAst = literalNewAst;
555         while (TokenUtil.isOfType(parentAst, TokenTypes.LITERAL_NEW, TokenTypes.DOT)) {
556             parentAst = parentAst.getParent();
557         }
558         final DetailAST firstChild = parentAst.getFirstChild();
559         return extractQualifiedName(firstChild);
560     }
561 
562     /**
563      * Checks if the given file path is a package-info.java file.
564      *
565      * @param filePath path to the file.
566      * @return true if the package file.
567      */
568     public static boolean isPackageInfo(String filePath) {
569         final Path filename = Path.of(filePath).getFileName();
570         return filename != null && "package-info.java".equals(filename.toString());
571     }
572 
573     /**
574      * Checks if a given subtree is terminated by return, throw, break, continue, or yield.
575      *
576      * @param ast root of given subtree
577      * @return true if the subtree is terminated.
578      */
579     public static boolean isTerminated(final DetailAST ast) {
580         return isTerminated(ast, true, true, new HashSet<>());
581     }
582 
583     /**
584      * Checks if a given subtree terminated by return, throw, yield or,
585      * if allowed break, continue.
586      * When analyzing fall-through cases in switch statements, a Set of String labels
587      * is used to keep track of the labels encountered in the enclosing switch statements.
588      *
589      * @param ast root of given subtree
590      * @param useBreak should we consider break as terminator
591      * @param useContinue should we consider continue as terminator
592      * @param labelsForCurrentSwitchScope the Set labels for the current scope of the switch
593      * @return true if the subtree is terminated.
594      */
595     private static boolean isTerminated(final DetailAST ast, boolean useBreak, boolean useContinue,
596                                         Set<String> labelsForCurrentSwitchScope) {
597 
598         return switch (ast.getType()) {
599             case TokenTypes.LITERAL_RETURN, TokenTypes.LITERAL_YIELD,
600                     TokenTypes.LITERAL_THROW -> true;
601             case TokenTypes.LITERAL_BREAK -> useBreak
602                     || hasLabel(ast, labelsForCurrentSwitchScope);
603             case TokenTypes.LITERAL_CONTINUE -> useContinue
604                     || hasLabel(ast, labelsForCurrentSwitchScope);
605             case TokenTypes.SLIST -> checkSlist(ast, useBreak, useContinue,
606                     labelsForCurrentSwitchScope);
607             case TokenTypes.LITERAL_IF -> checkIf(ast, useBreak, useContinue,
608                     labelsForCurrentSwitchScope);
609             case TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_WHILE, TokenTypes.LITERAL_DO ->
610                 checkLoop(ast, labelsForCurrentSwitchScope);
611             case TokenTypes.LITERAL_TRY -> checkTry(ast, useBreak, useContinue,
612                     labelsForCurrentSwitchScope);
613             case TokenTypes.LITERAL_SWITCH -> checkSwitch(ast, useContinue,
614                     labelsForCurrentSwitchScope);
615             case TokenTypes.LITERAL_SYNCHRONIZED ->
616                 checkSynchronized(ast, useBreak, useContinue,
617                     labelsForCurrentSwitchScope);
618             case TokenTypes.LABELED_STAT -> {
619                 labelsForCurrentSwitchScope.add(ast.getFirstChild().getText());
620                 yield isTerminated(ast.getLastChild(), useBreak, useContinue,
621                         labelsForCurrentSwitchScope);
622             }
623             default -> false;
624         };
625     }
626 
627     /**
628      * Checks if given break or continue ast has outer label.
629      *
630      * @param statement break or continue node
631      * @param labelsForCurrentSwitchScope the Set labels for the current scope of the switch
632      * @return true if local label used
633      */
634     private static boolean hasLabel(DetailAST statement, Set<String> labelsForCurrentSwitchScope) {
635         return Optional.ofNullable(statement)
636                 .map(DetailAST::getFirstChild)
637                 .filter(child -> child.getType() == TokenTypes.IDENT)
638                 .map(DetailAST::getText)
639                 .filter(label -> !labelsForCurrentSwitchScope.contains(label))
640                 .isPresent();
641     }
642 
643     /**
644      * Checks if a given SLIST terminated by return, throw or,
645      * if allowed break, continue.
646      *
647      * @param slistAst SLIST to check
648      * @param useBreak should we consider break as terminator
649      * @param useContinue should we consider continue as terminator
650      * @param labels label names
651      * @return true if SLIST is terminated.
652      */
653     private static boolean checkSlist(final DetailAST slistAst, boolean useBreak,
654                                       boolean useContinue, Set<String> labels) {
655         DetailAST lastStmt = slistAst.getLastChild();
656 
657         if (lastStmt.getType() == TokenTypes.RCURLY) {
658             lastStmt = lastStmt.getPreviousSibling();
659         }
660 
661         while (TokenUtil.isOfType(lastStmt, TokenTypes.SINGLE_LINE_COMMENT,
662                 TokenTypes.BLOCK_COMMENT_BEGIN)) {
663             lastStmt = lastStmt.getPreviousSibling();
664         }
665 
666         return lastStmt != null
667             && isTerminated(lastStmt, useBreak, useContinue, labels);
668     }
669 
670     /**
671      * Checks if a given IF terminated by return, throw or,
672      * if allowed break, continue.
673      *
674      * @param ast IF to check
675      * @param useBreak should we consider break as terminator
676      * @param useContinue should we consider continue as terminator
677      * @param labels label names
678      * @return true if IF is terminated.
679      */
680     private static boolean checkIf(final DetailAST ast, boolean useBreak,
681                                    boolean useContinue, Set<String> labels) {
682         final DetailAST thenStmt = getNextNonCommentAst(ast.findFirstToken(TokenTypes.RPAREN));
683 
684         final DetailAST elseStmt = ast.findFirstToken(TokenTypes.LITERAL_ELSE);
685 
686         final boolean isTerminated;
687 
688         if (checkInfiniteLoop(ast.getParent().getParent())) {
689             isTerminated = isTerminated(thenStmt, useBreak, useContinue, labels)
690                     || elseStmt != null
691                     && isTerminated(elseStmt.getLastChild(), useBreak, useContinue, labels);
692         }
693         else {
694             isTerminated = elseStmt != null
695                     && isTerminated(thenStmt, useBreak, useContinue, labels)
696                     && isTerminated(elseStmt.getLastChild(), useBreak, useContinue, labels);
697         }
698 
699         return isTerminated;
700     }
701 
702     /**
703      * This method will skip the comment content while finding the next ast of current ast.
704      *
705      * @param ast current ast
706      * @return next ast after skipping comment
707      */
708     public static DetailAST getNextNonCommentAst(DetailAST ast) {
709         DetailAST nextSibling = ast.getNextSibling();
710         while (TokenUtil.isOfType(nextSibling, TokenTypes.SINGLE_LINE_COMMENT,
711                 TokenTypes.BLOCK_COMMENT_BEGIN)) {
712             nextSibling = nextSibling.getNextSibling();
713         }
714         return nextSibling;
715     }
716 
717     /**
718      * Checks if a given loop terminated by return, throw or,
719      * if allowed break, continue.
720      *
721      * @param ast loop to check
722      * @param labels label names
723      * @return true if loop is terminated.
724      */
725     private static boolean checkLoop(final DetailAST ast, Set<String> labels) {
726         final DetailAST loopBody;
727         if (ast.getType() == TokenTypes.LITERAL_DO) {
728             final DetailAST lparen = ast.findFirstToken(TokenTypes.DO_WHILE);
729             loopBody = lparen.getPreviousSibling();
730         }
731         else {
732             final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN);
733             loopBody = rparen.getNextSibling();
734         }
735 
736         return checkInfiniteLoop(ast) && !isTerminated(loopBody, true, false, labels)
737                 || isTerminated(loopBody, false, false, labels);
738     }
739 
740     /**
741      * Checks if a given try/catch/finally block terminated by return, throw or,
742      * if allowed break, continue.
743      *
744      * @param ast loop to check
745      * @param useBreak should we consider break as terminator
746      * @param useContinue should we consider continue as terminator
747      * @param labels label names
748      * @return true if try/catch/finally block is terminated
749      */
750     private static boolean checkTry(final DetailAST ast, boolean useBreak,
751                                     boolean useContinue, Set<String> labels) {
752         final DetailAST finalStmt = ast.getLastChild();
753         boolean isTerminated = finalStmt.getType() == TokenTypes.LITERAL_FINALLY
754                 && isTerminated(finalStmt.findFirstToken(TokenTypes.SLIST),
755                 useBreak, useContinue, labels);
756 
757         if (!isTerminated) {
758             DetailAST firstChild = ast.getFirstChild();
759 
760             if (firstChild.getType() == TokenTypes.RESOURCE_SPECIFICATION) {
761                 firstChild = firstChild.getNextSibling();
762             }
763 
764             isTerminated = isTerminated(firstChild,
765                     useBreak, useContinue, labels);
766 
767             DetailAST catchStmt = ast.findFirstToken(TokenTypes.LITERAL_CATCH);
768             while (catchStmt != null
769                     && isTerminated
770                     && catchStmt.getType() == TokenTypes.LITERAL_CATCH) {
771                 final DetailAST catchBody =
772                         catchStmt.findFirstToken(TokenTypes.SLIST);
773                 isTerminated = isTerminated(catchBody, useBreak, useContinue, labels);
774                 catchStmt = catchStmt.getNextSibling();
775             }
776         }
777         return isTerminated;
778     }
779 
780     /**
781      * Checks if a given switch terminated by return, throw or,
782      * if allowed break, continue.
783      *
784      * @param literalSwitchAst loop to check
785      * @param useContinue should we consider continue as terminator
786      * @param labels label names
787      * @return true if switch is terminated
788      */
789     private static boolean checkSwitch(DetailAST literalSwitchAst,
790                                        boolean useContinue, Set<String> labels) {
791         DetailAST caseGroup = literalSwitchAst.findFirstToken(TokenTypes.CASE_GROUP);
792         boolean isTerminated = caseGroup != null;
793         while (isTerminated && caseGroup.getType() != TokenTypes.RCURLY) {
794             final DetailAST caseBody =
795                 caseGroup.findFirstToken(TokenTypes.SLIST);
796             isTerminated = caseBody != null
797                     && isTerminated(caseBody, false, useContinue, labels);
798             caseGroup = caseGroup.getNextSibling();
799         }
800         return isTerminated;
801     }
802 
803     /**
804      * Checks if a given synchronized block terminated by return, throw or,
805      * if allowed break, continue.
806      *
807      * @param synchronizedAst synchronized block to check.
808      * @param useBreak should we consider break as terminator
809      * @param useContinue should we consider continue as terminator
810      * @param labels label names
811      * @return true if synchronized block is terminated
812      */
813     private static boolean checkSynchronized(final DetailAST synchronizedAst, boolean useBreak,
814                                       boolean useContinue, Set<String> labels) {
815         return isTerminated(
816             synchronizedAst.findFirstToken(TokenTypes.SLIST), useBreak, useContinue, labels);
817     }
818 
819     /**
820      * Checks whether the given ast is an infinite loop.
821      *
822      * @param ast the token to check
823      * @return true if the token is an infinite, false otherwise
824      */
825     private static boolean checkInfiniteLoop(DetailAST ast) {
826         boolean isInfiniteLoop = false;
827         if (ast.getType() == TokenTypes.LITERAL_FOR) {
828             isInfiniteLoop = checkForLoop(ast);
829         }
830         else if (ast.getType() == TokenTypes.LITERAL_WHILE
831                 || ast.getType() == TokenTypes.LITERAL_DO) {
832             final DetailAST lparen = NullUtil.notNull(ast.findFirstToken(TokenTypes.LPAREN));
833             final DetailAST expression = NullUtil.notNull(lparen.getNextSibling());
834             final DetailAST firstChild = NullUtil.notNull(expression.getFirstChild());
835             isInfiniteLoop = firstChild.getType() == TokenTypes.LITERAL_TRUE;
836         }
837         return isInfiniteLoop;
838     }
839 
840     /**
841      * Checks if a for loop is infinite (empty or {@code true} condition).
842      *
843      * @param forLoop the for loop to check
844      * @return true if the for loop is infinite
845      */
846     private static boolean checkForLoop(DetailAST forLoop) {
847         boolean isInfiniteLoop = false;
848         if (forLoop.findFirstToken(TokenTypes.FOR_EACH_CLAUSE) == null) {
849             final DetailAST condition =
850                     NullUtil.notNull(forLoop.findFirstToken(TokenTypes.FOR_CONDITION));
851             DetailAST child = condition.getFirstChild();
852             if (child != null) {
853                 // EXPR node always has child
854                 child = NullUtil.notNull(child.getFirstChild());
855             }
856             isInfiniteLoop = child == null || child.getType() == TokenTypes.LITERAL_TRUE;
857         }
858         return isInfiniteLoop;
859     }
860 
861 }