001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2026 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018///////////////////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.utils;
021
022import java.nio.file.Path;
023import java.util.ArrayList;
024import java.util.Arrays;
025import java.util.Collection;
026import java.util.HashSet;
027import java.util.List;
028import java.util.Optional;
029import java.util.Set;
030import java.util.function.Predicate;
031import java.util.regex.Pattern;
032import java.util.stream.Collectors;
033import java.util.stream.Stream;
034
035import com.puppycrawl.tools.checkstyle.api.DetailAST;
036import com.puppycrawl.tools.checkstyle.api.FullIdent;
037import com.puppycrawl.tools.checkstyle.api.TokenTypes;
038import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption;
039
040/**
041 * Contains utility methods for the checks.
042 *
043 */
044public final class CheckUtil {
045
046    // constants for parseDouble()
047    /** Binary radix. */
048    private static final int BASE_2 = 2;
049
050    /** Octal radix. */
051    private static final int BASE_8 = 8;
052
053    /** Decimal radix. */
054    private static final int BASE_10 = 10;
055
056    /** Hex radix. */
057    private static final int BASE_16 = 16;
058
059    /** Pattern matching underscore characters ('_'). */
060    private static final Pattern UNDERSCORE_PATTERN = Pattern.compile("_");
061
062    /** Compiled pattern for all system newlines. */
063    private static final Pattern ALL_NEW_LINES = Pattern.compile("\\R");
064
065    /** Package separator. */
066    private static final char PACKAGE_SEPARATOR = '.';
067
068    /** Prevent instances. */
069    private CheckUtil() {
070    }
071
072    /**
073     * Tests whether a method definition AST defines an equals covariant.
074     *
075     * @param ast the method definition AST to test.
076     *     Precondition: ast is a TokenTypes.METHOD_DEF node.
077     * @return true if ast defines an equals covariant.
078     */
079    public static boolean isEqualsMethod(DetailAST ast) {
080        boolean equalsMethod = false;
081
082        if (ast.getType() == TokenTypes.METHOD_DEF) {
083            final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
084            final boolean staticOrAbstract =
085                    modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null
086                    || modifiers.findFirstToken(TokenTypes.ABSTRACT) != null;
087
088            if (!staticOrAbstract) {
089                final DetailAST nameNode = ast.findFirstToken(TokenTypes.IDENT);
090                final String name = nameNode.getText();
091
092                if ("equals".equals(name)) {
093                    // one parameter?
094                    final DetailAST paramsNode = ast.findFirstToken(TokenTypes.PARAMETERS);
095                    equalsMethod = paramsNode.getChildCount() == 1;
096                }
097            }
098        }
099        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}