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