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