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 {@link TokenTypes#MODIFIERS}.
340      * @return {@link 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 i = 0; i < length && patternClass.charAt(i) == classToBeMatched.charAt(i); ++i) {
494             if (patternClass.charAt(i) == PACKAGE_SEPARATOR) {
495                 result = i;
496             }
497         }
498         return result;
499     }
500 
501     /**
502      * Get the qualified name of type declaration by combining {@code packageName},
503      * {@code outerClassQualifiedName} and {@code className}.
504      *
505      * @param packageName packageName
506      * @param outerClassQualifiedName outerClassQualifiedName
507      * @param className className
508      * @return the qualified name of type declaration by combining {@code packageName},
509      *         {@code outerClassQualifiedName} and {@code className}
510      */
511     public static String getQualifiedTypeDeclarationName(String packageName,
512                                                          String outerClassQualifiedName,
513                                                          String className) {
514         final String qualifiedClassName;
515 
516         if (outerClassQualifiedName == null) {
517             if (packageName == null) {
518                 qualifiedClassName = className;
519             }
520             else {
521                 qualifiedClassName = packageName + PACKAGE_SEPARATOR + className;
522             }
523         }
524         else {
525             qualifiedClassName = outerClassQualifiedName + PACKAGE_SEPARATOR + className;
526         }
527         return qualifiedClassName;
528     }
529 
530     /**
531      * Get name of package and super class of anon inner class by concatenating
532      * the identifier values under {@link TokenTypes#DOT}.
533      *
534      * @param ast ast to extract superclass or package name from
535      * @return qualified name
536      */
537     public static String extractQualifiedName(DetailAST ast) {
538         return FullIdent.createFullIdent(ast).getText();
539     }
540 
541     /**
542      * Get the short name of super class of anonymous inner class.
543      * Example:
544      * <pre>
545      * TestClass.NestedClass obj = new Test().new NestedClass() {};
546      * // Short name will be Test.NestedClass
547      * </pre>
548      *
549      * @param literalNewAst ast node of type {@link TokenTypes#LITERAL_NEW}
550      * @return short name of base class of anonymous inner class
551      */
552     public static String getShortNameOfAnonInnerClass(DetailAST literalNewAst) {
553         DetailAST parentAst = literalNewAst;
554         while (TokenUtil.isOfType(parentAst, TokenTypes.LITERAL_NEW, TokenTypes.DOT)) {
555             parentAst = parentAst.getParent();
556         }
557         final DetailAST firstChild = parentAst.getFirstChild();
558         return extractQualifiedName(firstChild);
559     }
560 
561     /**
562      * Checks if the given file path is a package-info.java file.
563      *
564      * @param filePath path to the file.
565      * @return true if the package file.
566      */
567     public static boolean isPackageInfo(String filePath) {
568         final Path filename = Path.of(filePath).getFileName();
569         return filename != null && "package-info.java".equals(filename.toString());
570     }
571 
572     /**
573      * Checks if a given subtree is terminated by return, throw, break, continue, or yield.
574      *
575      * @param ast root of given subtree
576      * @return true if the subtree is terminated.
577      */
578     public static boolean isTerminated(final DetailAST ast) {
579         return isTerminated(ast, true, true, new HashSet<>());
580     }
581 
582     /**
583      * Checks if a given subtree terminated by return, throw, yield or,
584      * if allowed break, continue.
585      * When analyzing fall-through cases in switch statements, a Set of String labels
586      * is used to keep track of the labels encountered in the enclosing switch statements.
587      *
588      * @param ast root of given subtree
589      * @param useBreak should we consider break as terminator
590      * @param useContinue should we consider continue as terminator
591      * @param labelsForCurrentSwitchScope the Set labels for the current scope of the switch
592      * @return true if the subtree is terminated.
593      */
594     private static boolean isTerminated(final DetailAST ast, boolean useBreak, boolean useContinue,
595                                         Set<String> labelsForCurrentSwitchScope) {
596 
597         return switch (ast.getType()) {
598             case TokenTypes.LITERAL_RETURN, TokenTypes.LITERAL_YIELD,
599                     TokenTypes.LITERAL_THROW -> true;
600             case TokenTypes.LITERAL_BREAK -> useBreak
601                     || hasLabel(ast, labelsForCurrentSwitchScope);
602             case TokenTypes.LITERAL_CONTINUE -> useContinue
603                     || hasLabel(ast, labelsForCurrentSwitchScope);
604             case TokenTypes.SLIST -> checkSlist(ast, useBreak, useContinue,
605                     labelsForCurrentSwitchScope);
606             case TokenTypes.LITERAL_IF -> checkIf(ast, useBreak, useContinue,
607                     labelsForCurrentSwitchScope);
608             case TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_WHILE, TokenTypes.LITERAL_DO ->
609                 checkLoop(ast, labelsForCurrentSwitchScope);
610             case TokenTypes.LITERAL_TRY -> checkTry(ast, useBreak, useContinue,
611                     labelsForCurrentSwitchScope);
612             case TokenTypes.LITERAL_SWITCH -> checkSwitch(ast, useContinue,
613                     labelsForCurrentSwitchScope);
614             case TokenTypes.LITERAL_SYNCHRONIZED ->
615                 checkSynchronized(ast, useBreak, useContinue,
616                     labelsForCurrentSwitchScope);
617             case TokenTypes.LABELED_STAT -> {
618                 labelsForCurrentSwitchScope.add(ast.getFirstChild().getText());
619                 yield isTerminated(ast.getLastChild(), useBreak, useContinue,
620                         labelsForCurrentSwitchScope);
621             }
622             default -> false;
623         };
624     }
625 
626     /**
627      * Checks if given break or continue ast has outer label.
628      *
629      * @param statement break or continue node
630      * @param labelsForCurrentSwitchScope the Set labels for the current scope of the switch
631      * @return true if local label used
632      */
633     private static boolean hasLabel(DetailAST statement, Set<String> labelsForCurrentSwitchScope) {
634         return Optional.ofNullable(statement)
635                 .map(DetailAST::getFirstChild)
636                 .filter(child -> child.getType() == TokenTypes.IDENT)
637                 .map(DetailAST::getText)
638                 .filter(label -> !labelsForCurrentSwitchScope.contains(label))
639                 .isPresent();
640     }
641 
642     /**
643      * Checks if a given SLIST terminated by return, throw or,
644      * if allowed break, continue.
645      *
646      * @param slistAst SLIST to check
647      * @param useBreak should we consider break as terminator
648      * @param useContinue should we consider continue as terminator
649      * @param labels label names
650      * @return true if SLIST is terminated.
651      */
652     private static boolean checkSlist(final DetailAST slistAst, boolean useBreak,
653                                       boolean useContinue, Set<String> labels) {
654         DetailAST lastStmt = slistAst.getLastChild();
655 
656         if (lastStmt.getType() == TokenTypes.RCURLY) {
657             lastStmt = lastStmt.getPreviousSibling();
658         }
659 
660         while (TokenUtil.isOfType(lastStmt, TokenTypes.SINGLE_LINE_COMMENT,
661                 TokenTypes.BLOCK_COMMENT_BEGIN)) {
662             lastStmt = lastStmt.getPreviousSibling();
663         }
664 
665         return lastStmt != null
666             && isTerminated(lastStmt, useBreak, useContinue, labels);
667     }
668 
669     /**
670      * Checks if a given IF terminated by return, throw or,
671      * if allowed break, continue.
672      *
673      * @param ast IF to check
674      * @param useBreak should we consider break as terminator
675      * @param useContinue should we consider continue as terminator
676      * @param labels label names
677      * @return true if IF is terminated.
678      */
679     private static boolean checkIf(final DetailAST ast, boolean useBreak,
680                                    boolean useContinue, Set<String> labels) {
681         final DetailAST thenStmt = getNextNonCommentAst(ast.findFirstToken(TokenTypes.RPAREN));
682 
683         final DetailAST elseStmt = getNextNonCommentAst(thenStmt);
684 
685         return elseStmt != null
686                 && isTerminated(thenStmt, useBreak, useContinue, labels)
687                 && isTerminated(elseStmt.getLastChild(), useBreak, useContinue, labels);
688     }
689 
690     /**
691      * This method will skip the comment content while finding the next ast of current ast.
692      *
693      * @param ast current ast
694      * @return next ast after skipping comment
695      */
696     public static DetailAST getNextNonCommentAst(DetailAST ast) {
697         DetailAST nextSibling = ast.getNextSibling();
698         while (TokenUtil.isOfType(nextSibling, TokenTypes.SINGLE_LINE_COMMENT,
699                 TokenTypes.BLOCK_COMMENT_BEGIN)) {
700             nextSibling = nextSibling.getNextSibling();
701         }
702         return nextSibling;
703     }
704 
705     /**
706      * Checks if a given loop terminated by return, throw or,
707      * if allowed break, continue.
708      *
709      * @param ast loop to check
710      * @param labels label names
711      * @return true if loop is terminated.
712      */
713     private static boolean checkLoop(final DetailAST ast, Set<String> labels) {
714         final DetailAST loopBody;
715         if (ast.getType() == TokenTypes.LITERAL_DO) {
716             final DetailAST lparen = ast.findFirstToken(TokenTypes.DO_WHILE);
717             loopBody = lparen.getPreviousSibling();
718         }
719         else {
720             final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN);
721             loopBody = rparen.getNextSibling();
722         }
723         return isTerminated(loopBody, false, false, labels);
724     }
725 
726     /**
727      * Checks if a given try/catch/finally block terminated by return, throw or,
728      * if allowed break, continue.
729      *
730      * @param ast loop to check
731      * @param useBreak should we consider break as terminator
732      * @param useContinue should we consider continue as terminator
733      * @param labels label names
734      * @return true if try/catch/finally block is terminated
735      */
736     private static boolean checkTry(final DetailAST ast, boolean useBreak,
737                                     boolean useContinue, Set<String> labels) {
738         final DetailAST finalStmt = ast.getLastChild();
739         boolean isTerminated = finalStmt.getType() == TokenTypes.LITERAL_FINALLY
740                 && isTerminated(finalStmt.findFirstToken(TokenTypes.SLIST),
741                 useBreak, useContinue, labels);
742 
743         if (!isTerminated) {
744             DetailAST firstChild = ast.getFirstChild();
745 
746             if (firstChild.getType() == TokenTypes.RESOURCE_SPECIFICATION) {
747                 firstChild = firstChild.getNextSibling();
748             }
749 
750             isTerminated = isTerminated(firstChild,
751                     useBreak, useContinue, labels);
752 
753             DetailAST catchStmt = ast.findFirstToken(TokenTypes.LITERAL_CATCH);
754             while (catchStmt != null
755                     && isTerminated
756                     && catchStmt.getType() == TokenTypes.LITERAL_CATCH) {
757                 final DetailAST catchBody =
758                         catchStmt.findFirstToken(TokenTypes.SLIST);
759                 isTerminated = isTerminated(catchBody, useBreak, useContinue, labels);
760                 catchStmt = catchStmt.getNextSibling();
761             }
762         }
763         return isTerminated;
764     }
765 
766     /**
767      * Checks if a given switch terminated by return, throw or,
768      * if allowed break, continue.
769      *
770      * @param literalSwitchAst loop to check
771      * @param useContinue should we consider continue as terminator
772      * @param labels label names
773      * @return true if switch is terminated
774      */
775     private static boolean checkSwitch(DetailAST literalSwitchAst,
776                                        boolean useContinue, Set<String> labels) {
777         DetailAST caseGroup = literalSwitchAst.findFirstToken(TokenTypes.CASE_GROUP);
778         boolean isTerminated = caseGroup != null;
779         while (isTerminated && caseGroup.getType() != TokenTypes.RCURLY) {
780             final DetailAST caseBody =
781                 caseGroup.findFirstToken(TokenTypes.SLIST);
782             isTerminated = caseBody != null
783                     && isTerminated(caseBody, false, useContinue, labels);
784             caseGroup = caseGroup.getNextSibling();
785         }
786         return isTerminated;
787     }
788 
789     /**
790      * Checks if a given synchronized block terminated by return, throw or,
791      * if allowed break, continue.
792      *
793      * @param synchronizedAst synchronized block to check.
794      * @param useBreak should we consider break as terminator
795      * @param useContinue should we consider continue as terminator
796      * @param labels label names
797      * @return true if synchronized block is terminated
798      */
799     private static boolean checkSynchronized(final DetailAST synchronizedAst, boolean useBreak,
800                                       boolean useContinue, Set<String> labels) {
801         return isTerminated(
802             synchronizedAst.findFirstToken(TokenTypes.SLIST), useBreak, useContinue, labels);
803     }
804 }