001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2024 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.checks.coding;
021
022import java.util.ArrayDeque;
023import java.util.Collections;
024import java.util.Deque;
025import java.util.HashMap;
026import java.util.HashSet;
027import java.util.LinkedHashMap;
028import java.util.List;
029import java.util.Map;
030import java.util.Optional;
031import java.util.Set;
032import java.util.stream.Collectors;
033
034import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
035import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
036import com.puppycrawl.tools.checkstyle.api.DetailAST;
037import com.puppycrawl.tools.checkstyle.api.TokenTypes;
038import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption;
039import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
040import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
041
042/**
043 * <div>
044 * Checks that a local variable is declared and/or assigned, but not used.
045 * Doesn't support
046 * <a href="https://docs.oracle.com/javase/specs/jls/se17/html/jls-14.html#jls-14.30">
047 * pattern variables yet</a>.
048 * Doesn't check
049 * <a href="https://docs.oracle.com/javase/specs/jls/se17/html/jls-4.html#jls-4.12.3">
050 * array components</a> as array
051 * components are classified as different kind of variables by
052 * <a href="https://docs.oracle.com/javase/specs/jls/se17/html/index.html">JLS</a>.
053 * </div>
054 * <ul>
055 * <li>
056 * Property {@code allowUnnamedVariables} - Allow variables named with a single underscore
057 * (known as <a href="https://docs.oracle.com/en/java/javase/21/docs/specs/unnamed-jls.html">
058 * unnamed variables</a> in Java 21+).
059 * Type is {@code boolean}.
060 * Default value is {@code true}.
061 * </li>
062 * </ul>
063 *
064 * <p>
065 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
066 * </p>
067 *
068 * <p>
069 * Violation Message Keys:
070 * </p>
071 * <ul>
072 * <li>
073 * {@code unused.local.var}
074 * </li>
075 * <li>
076 * {@code unused.named.local.var}
077 * </li>
078 * </ul>
079 *
080 * @since 9.3
081 */
082@FileStatefulCheck
083public class UnusedLocalVariableCheck extends AbstractCheck {
084
085    /**
086     * A key is pointing to the warning message text in "messages.properties"
087     * file.
088     */
089    public static final String MSG_UNUSED_LOCAL_VARIABLE = "unused.local.var";
090
091    /**
092     * A key is pointing to the warning message text in "messages.properties"
093     * file.
094     */
095    public static final String MSG_UNUSED_NAMED_LOCAL_VARIABLE = "unused.named.local.var";
096
097    /**
098     * An array of increment and decrement tokens.
099     */
100    private static final int[] INCREMENT_AND_DECREMENT_TOKENS = {
101        TokenTypes.POST_INC,
102        TokenTypes.POST_DEC,
103        TokenTypes.INC,
104        TokenTypes.DEC,
105    };
106
107    /**
108     * An array of scope tokens.
109     */
110    private static final int[] SCOPES = {
111        TokenTypes.SLIST,
112        TokenTypes.LITERAL_FOR,
113        TokenTypes.OBJBLOCK,
114    };
115
116    /**
117     * An array of unacceptable children of ast of type {@link TokenTypes#DOT}.
118     */
119    private static final int[] UNACCEPTABLE_CHILD_OF_DOT = {
120        TokenTypes.DOT,
121        TokenTypes.METHOD_CALL,
122        TokenTypes.LITERAL_NEW,
123        TokenTypes.LITERAL_SUPER,
124        TokenTypes.LITERAL_CLASS,
125        TokenTypes.LITERAL_THIS,
126    };
127
128    /**
129     * An array of unacceptable parent of ast of type {@link TokenTypes#IDENT}.
130     */
131    private static final int[] UNACCEPTABLE_PARENT_OF_IDENT = {
132        TokenTypes.VARIABLE_DEF,
133        TokenTypes.DOT,
134        TokenTypes.LITERAL_NEW,
135        TokenTypes.PATTERN_VARIABLE_DEF,
136        TokenTypes.METHOD_CALL,
137        TokenTypes.TYPE,
138    };
139
140    /**
141     * An array of blocks in which local anon inner classes can exist.
142     */
143    private static final int[] ANONYMOUS_CLASS_PARENT_TOKENS = {
144        TokenTypes.METHOD_DEF,
145        TokenTypes.CTOR_DEF,
146        TokenTypes.STATIC_INIT,
147        TokenTypes.INSTANCE_INIT,
148        TokenTypes.COMPACT_CTOR_DEF,
149    };
150
151    /**
152     * An array of token types that indicate a variable is being used within
153     * an expression involving increment or decrement operators, or within a switch statement.
154     * When a token of one of these types is the parent of an expression, it indicates that the
155     * variable associated with the increment or decrement operation is being used.
156     * Ex:- TokenTypes.LITERAL_SWITCH: Indicates a switch statement. Variables used within the
157     * switch expression are considered to be used
158     */
159    private static final int[] INCREMENT_DECREMENT_VARIABLE_USAGE_TYPES = {
160        TokenTypes.ELIST,
161        TokenTypes.INDEX_OP,
162        TokenTypes.ASSIGN,
163        TokenTypes.LITERAL_SWITCH,
164    };
165
166    /** Package separator. */
167    private static final String PACKAGE_SEPARATOR = ".";
168
169    /**
170     * Keeps tracks of the variables declared in file.
171     */
172    private final Deque<VariableDesc> variables = new ArrayDeque<>();
173
174    /**
175     * Keeps track of all the type declarations present in the file.
176     * Pops the type out of the stack while leaving the type
177     * in visitor pattern.
178     */
179    private final Deque<TypeDeclDesc> typeDeclarations = new ArrayDeque<>();
180
181    /**
182     * Maps type declaration ast to their respective TypeDeclDesc objects.
183     */
184    private final Map<DetailAST, TypeDeclDesc> typeDeclAstToTypeDeclDesc = new LinkedHashMap<>();
185
186    /**
187     * Maps local anonymous inner class to the TypeDeclDesc object
188     * containing it.
189     */
190    private final Map<DetailAST, TypeDeclDesc> anonInnerAstToTypeDeclDesc = new HashMap<>();
191
192    /**
193     * Set of tokens of type {@link UnusedLocalVariableCheck#ANONYMOUS_CLASS_PARENT_TOKENS}
194     * and {@link TokenTypes#LAMBDA} in some cases.
195     */
196    private final Set<DetailAST> anonInnerClassHolders = new HashSet<>();
197
198    /**
199     * Allow variables named with a single underscore
200     * (known as  <a href="https://docs.oracle.com/en/java/javase/21/docs/specs/unnamed-jls.html">
201     *  unnamed variables</a> in Java 21+).
202     */
203    private boolean allowUnnamedVariables = true;
204
205    /**
206     * Name of the package.
207     */
208    private String packageName;
209
210    /**
211     * Depth at which a type declaration is nested, 0 for top level type declarations.
212     */
213    private int depth;
214
215    /**
216     * Setter to allow variables named with a single underscore
217     * (known as <a href="https://docs.oracle.com/en/java/javase/21/docs/specs/unnamed-jls.html">
218     * unnamed variables</a> in Java 21+).
219     *
220     * @param allowUnnamedVariables true or false.
221     * @since 10.18.0
222     */
223    public void setAllowUnnamedVariables(boolean allowUnnamedVariables) {
224        this.allowUnnamedVariables = allowUnnamedVariables;
225    }
226
227    @Override
228    public int[] getDefaultTokens() {
229        return new int[] {
230            TokenTypes.DOT,
231            TokenTypes.VARIABLE_DEF,
232            TokenTypes.IDENT,
233            TokenTypes.SLIST,
234            TokenTypes.LITERAL_FOR,
235            TokenTypes.OBJBLOCK,
236            TokenTypes.CLASS_DEF,
237            TokenTypes.INTERFACE_DEF,
238            TokenTypes.ANNOTATION_DEF,
239            TokenTypes.PACKAGE_DEF,
240            TokenTypes.LITERAL_NEW,
241            TokenTypes.METHOD_DEF,
242            TokenTypes.CTOR_DEF,
243            TokenTypes.STATIC_INIT,
244            TokenTypes.INSTANCE_INIT,
245            TokenTypes.COMPILATION_UNIT,
246            TokenTypes.LAMBDA,
247            TokenTypes.ENUM_DEF,
248            TokenTypes.RECORD_DEF,
249            TokenTypes.COMPACT_CTOR_DEF,
250        };
251    }
252
253    @Override
254    public int[] getAcceptableTokens() {
255        return getDefaultTokens();
256    }
257
258    @Override
259    public int[] getRequiredTokens() {
260        return getDefaultTokens();
261    }
262
263    @Override
264    public void beginTree(DetailAST root) {
265        variables.clear();
266        typeDeclarations.clear();
267        typeDeclAstToTypeDeclDesc.clear();
268        anonInnerAstToTypeDeclDesc.clear();
269        anonInnerClassHolders.clear();
270        packageName = null;
271        depth = 0;
272    }
273
274    @Override
275    public void visitToken(DetailAST ast) {
276        final int type = ast.getType();
277        if (type == TokenTypes.DOT) {
278            visitDotToken(ast, variables);
279        }
280        else if (type == TokenTypes.VARIABLE_DEF && !skipUnnamedVariables(ast)) {
281            visitVariableDefToken(ast);
282        }
283        else if (type == TokenTypes.IDENT) {
284            visitIdentToken(ast, variables);
285        }
286        else if (isInsideLocalAnonInnerClass(ast)) {
287            visitLocalAnonInnerClass(ast);
288        }
289        else if (isNonLocalTypeDeclaration(ast)) {
290            visitNonLocalTypeDeclarationToken(ast);
291        }
292        else if (type == TokenTypes.PACKAGE_DEF) {
293            packageName = CheckUtil.extractQualifiedName(ast.getFirstChild().getNextSibling());
294        }
295    }
296
297    @Override
298    public void leaveToken(DetailAST ast) {
299        if (TokenUtil.isOfType(ast, SCOPES)) {
300            logViolations(ast, variables);
301        }
302        else if (ast.getType() == TokenTypes.COMPILATION_UNIT) {
303            leaveCompilationUnit();
304        }
305        else if (isNonLocalTypeDeclaration(ast)) {
306            depth--;
307            typeDeclarations.pop();
308        }
309    }
310
311    /**
312     * Visit ast of type {@link TokenTypes#DOT}.
313     *
314     * @param dotAst dotAst
315     * @param variablesStack stack of all the relevant variables in the scope
316     */
317    private static void visitDotToken(DetailAST dotAst, Deque<VariableDesc> variablesStack) {
318        if (dotAst.getParent().getType() != TokenTypes.LITERAL_NEW
319                && shouldCheckIdentTokenNestedUnderDot(dotAst)) {
320            final DetailAST identifier = dotAst.findFirstToken(TokenTypes.IDENT);
321            if (identifier != null) {
322                checkIdentifierAst(identifier, variablesStack);
323            }
324        }
325    }
326
327    /**
328     * Visit ast of type {@link TokenTypes#VARIABLE_DEF}.
329     *
330     * @param varDefAst varDefAst
331     */
332    private void visitVariableDefToken(DetailAST varDefAst) {
333        addLocalVariables(varDefAst, variables);
334        addInstanceOrClassVar(varDefAst);
335    }
336
337    /**
338     * Visit ast of type {@link TokenTypes#IDENT}.
339     *
340     * @param identAst identAst
341     * @param variablesStack stack of all the relevant variables in the scope
342     */
343    private static void visitIdentToken(DetailAST identAst, Deque<VariableDesc> variablesStack) {
344        final DetailAST parent = identAst.getParent();
345        final boolean isMethodReferenceMethodName = parent.getType() == TokenTypes.METHOD_REF
346                && parent.getFirstChild() != identAst;
347        final boolean isConstructorReference = parent.getType() == TokenTypes.METHOD_REF
348                && parent.getLastChild().getType() == TokenTypes.LITERAL_NEW;
349        final boolean isNestedClassInitialization =
350                TokenUtil.isOfType(identAst.getNextSibling(), TokenTypes.LITERAL_NEW)
351                && parent.getType() == TokenTypes.DOT;
352
353        if (isNestedClassInitialization || !isMethodReferenceMethodName
354                && !isConstructorReference
355                && !TokenUtil.isOfType(parent, UNACCEPTABLE_PARENT_OF_IDENT)) {
356            checkIdentifierAst(identAst, variablesStack);
357        }
358    }
359
360    /**
361     * Visit the non-local type declaration token.
362     *
363     * @param typeDeclAst type declaration ast
364     */
365    private void visitNonLocalTypeDeclarationToken(DetailAST typeDeclAst) {
366        final String qualifiedName = getQualifiedTypeDeclarationName(typeDeclAst);
367        final TypeDeclDesc currTypeDecl = new TypeDeclDesc(qualifiedName, depth, typeDeclAst);
368        depth++;
369        typeDeclarations.push(currTypeDecl);
370        typeDeclAstToTypeDeclDesc.put(typeDeclAst, currTypeDecl);
371    }
372
373    /**
374     * Visit the local anon inner class.
375     *
376     * @param literalNewAst literalNewAst
377     */
378    private void visitLocalAnonInnerClass(DetailAST literalNewAst) {
379        anonInnerAstToTypeDeclDesc.put(literalNewAst, typeDeclarations.peek());
380        anonInnerClassHolders.add(getBlockContainingLocalAnonInnerClass(literalNewAst));
381    }
382
383    /**
384     * Check for skip current {@link TokenTypes#VARIABLE_DEF}
385     * due to <b>allowUnnamedVariable</b> option.
386     *
387     * @param varDefAst varDefAst variable to check
388     * @return true if the current variable should be skipped.
389     */
390    private boolean skipUnnamedVariables(DetailAST varDefAst) {
391        final DetailAST ident = varDefAst.findFirstToken(TokenTypes.IDENT);
392        return allowUnnamedVariables && "_".equals(ident.getText());
393    }
394
395    /**
396     * Whether ast node of type {@link TokenTypes#LITERAL_NEW} is a part of a local
397     * anonymous inner class.
398     *
399     * @param literalNewAst ast node of type {@link TokenTypes#LITERAL_NEW}
400     * @return true if variableDefAst is an instance variable in local anonymous inner class
401     */
402    private static boolean isInsideLocalAnonInnerClass(DetailAST literalNewAst) {
403        boolean result = false;
404        final DetailAST lastChild = literalNewAst.getLastChild();
405        if (lastChild != null && lastChild.getType() == TokenTypes.OBJBLOCK) {
406            DetailAST currentAst = literalNewAst;
407            while (!TokenUtil.isTypeDeclaration(currentAst.getType())) {
408                if (currentAst.getType() == TokenTypes.SLIST) {
409                    result = true;
410                    break;
411                }
412                currentAst = currentAst.getParent();
413            }
414        }
415        return result;
416    }
417
418    /**
419     * Traverse {@code variablesStack} stack and log the violations.
420     *
421     * @param scopeAst ast node of type {@link UnusedLocalVariableCheck#SCOPES}
422     * @param variablesStack stack of all the relevant variables in the scope
423     */
424    private void logViolations(DetailAST scopeAst, Deque<VariableDesc> variablesStack) {
425        while (!variablesStack.isEmpty() && variablesStack.peek().getScope() == scopeAst) {
426            final VariableDesc variableDesc = variablesStack.pop();
427            if (!variableDesc.isUsed()
428                    && !variableDesc.isInstVarOrClassVar()) {
429                final DetailAST typeAst = variableDesc.getTypeAst();
430                if (allowUnnamedVariables) {
431                    log(typeAst, MSG_UNUSED_NAMED_LOCAL_VARIABLE, variableDesc.getName());
432                }
433                else {
434                    log(typeAst, MSG_UNUSED_LOCAL_VARIABLE, variableDesc.getName());
435                }
436            }
437        }
438    }
439
440    /**
441     * We process all the blocks containing local anonymous inner classes
442     * separately after processing all the other nodes. This is being done
443     * due to the fact the instance variables of local anon inner classes can
444     * cast a shadow on local variables.
445     */
446    private void leaveCompilationUnit() {
447        anonInnerClassHolders.forEach(holder -> {
448            iterateOverBlockContainingLocalAnonInnerClass(holder, new ArrayDeque<>());
449        });
450    }
451
452    /**
453     * Whether a type declaration is non-local. Annotated interfaces are always non-local.
454     *
455     * @param typeDeclAst type declaration ast
456     * @return true if type declaration is non-local
457     */
458    private static boolean isNonLocalTypeDeclaration(DetailAST typeDeclAst) {
459        return TokenUtil.isTypeDeclaration(typeDeclAst.getType())
460                && typeDeclAst.getParent().getType() != TokenTypes.SLIST;
461    }
462
463    /**
464     * Get the block containing local anon inner class.
465     *
466     * @param literalNewAst ast node of type {@link TokenTypes#LITERAL_NEW}
467     * @return the block containing local anon inner class
468     */
469    private static DetailAST getBlockContainingLocalAnonInnerClass(DetailAST literalNewAst) {
470        DetailAST currentAst = literalNewAst;
471        DetailAST result = null;
472        DetailAST topMostLambdaAst = null;
473        while (currentAst != null && !TokenUtil.isOfType(currentAst,
474                ANONYMOUS_CLASS_PARENT_TOKENS)) {
475            if (currentAst.getType() == TokenTypes.LAMBDA) {
476                topMostLambdaAst = currentAst;
477            }
478            currentAst = currentAst.getParent();
479            result = currentAst;
480        }
481
482        if (currentAst == null) {
483            result = topMostLambdaAst;
484        }
485        return result;
486    }
487
488    /**
489     * Add local variables to the {@code variablesStack} stack.
490     * Also adds the instance variables defined in a local anonymous inner class.
491     *
492     * @param varDefAst ast node of type {@link TokenTypes#VARIABLE_DEF}
493     * @param variablesStack stack of all the relevant variables in the scope
494     */
495    private static void addLocalVariables(DetailAST varDefAst, Deque<VariableDesc> variablesStack) {
496        final DetailAST parentAst = varDefAst.getParent();
497        final DetailAST grandParent = parentAst.getParent();
498        final boolean isInstanceVarInInnerClass =
499                grandParent.getType() == TokenTypes.LITERAL_NEW
500                || grandParent.getType() == TokenTypes.CLASS_DEF;
501        if (isInstanceVarInInnerClass
502                || parentAst.getType() != TokenTypes.OBJBLOCK) {
503            final DetailAST ident = varDefAst.findFirstToken(TokenTypes.IDENT);
504            final VariableDesc desc = new VariableDesc(ident.getText(),
505                    varDefAst.findFirstToken(TokenTypes.TYPE), findScopeOfVariable(varDefAst));
506            if (isInstanceVarInInnerClass) {
507                desc.registerAsInstOrClassVar();
508            }
509            variablesStack.push(desc);
510        }
511    }
512
513    /**
514     * Add instance variables and class variables to the
515     * {@link TypeDeclDesc#instanceAndClassVarStack}.
516     *
517     * @param varDefAst ast node of type {@link TokenTypes#VARIABLE_DEF}
518     */
519    private void addInstanceOrClassVar(DetailAST varDefAst) {
520        final DetailAST parentAst = varDefAst.getParent();
521        if (isNonLocalTypeDeclaration(parentAst.getParent())
522                && !isPrivateInstanceVariable(varDefAst)) {
523            final DetailAST ident = varDefAst.findFirstToken(TokenTypes.IDENT);
524            final VariableDesc desc = new VariableDesc(ident.getText());
525            typeDeclAstToTypeDeclDesc.get(parentAst.getParent()).addInstOrClassVar(desc);
526        }
527    }
528
529    /**
530     * Whether instance variable or class variable have private access modifier.
531     *
532     * @param varDefAst ast node of type {@link TokenTypes#VARIABLE_DEF}
533     * @return true if instance variable or class variable have private access modifier
534     */
535    private static boolean isPrivateInstanceVariable(DetailAST varDefAst) {
536        final AccessModifierOption varAccessModifier =
537                CheckUtil.getAccessModifierFromModifiersToken(varDefAst);
538        return varAccessModifier == AccessModifierOption.PRIVATE;
539    }
540
541    /**
542     * Get the {@link TypeDeclDesc} of the super class of anonymous inner class.
543     *
544     * @param literalNewAst ast node of type {@link TokenTypes#LITERAL_NEW}
545     * @return {@link TypeDeclDesc} of the super class of anonymous inner class
546     */
547    private TypeDeclDesc getSuperClassOfAnonInnerClass(DetailAST literalNewAst) {
548        TypeDeclDesc obtainedClass = null;
549        final String shortNameOfClass = CheckUtil.getShortNameOfAnonInnerClass(literalNewAst);
550        if (packageName != null && shortNameOfClass.startsWith(packageName)) {
551            final Optional<TypeDeclDesc> classWithCompletePackageName =
552                    typeDeclAstToTypeDeclDesc.values()
553                    .stream()
554                    .filter(typeDeclDesc -> {
555                        return typeDeclDesc.getQualifiedName().equals(shortNameOfClass);
556                    })
557                    .findFirst();
558            if (classWithCompletePackageName.isPresent()) {
559                obtainedClass = classWithCompletePackageName.orElseThrow();
560            }
561        }
562        else {
563            final List<TypeDeclDesc> typeDeclWithSameName = typeDeclWithSameName(shortNameOfClass);
564            if (!typeDeclWithSameName.isEmpty()) {
565                obtainedClass = getClosestMatchingTypeDeclaration(
566                        anonInnerAstToTypeDeclDesc.get(literalNewAst).getQualifiedName(),
567                        typeDeclWithSameName);
568            }
569        }
570        return obtainedClass;
571    }
572
573    /**
574     * Add non-private instance and class variables of the super class of the anonymous class
575     * to the variables stack.
576     *
577     * @param obtainedClass super class of the anon inner class
578     * @param variablesStack stack of all the relevant variables in the scope
579     * @param literalNewAst ast node of type {@link TokenTypes#LITERAL_NEW}
580     */
581    private void modifyVariablesStack(TypeDeclDesc obtainedClass,
582            Deque<VariableDesc> variablesStack,
583            DetailAST literalNewAst) {
584        if (obtainedClass != null) {
585            final Deque<VariableDesc> instAndClassVarDeque = typeDeclAstToTypeDeclDesc
586                    .get(obtainedClass.getTypeDeclAst())
587                    .getUpdatedCopyOfVarStack(literalNewAst);
588            instAndClassVarDeque.forEach(variablesStack::push);
589        }
590    }
591
592    /**
593     * Checks if there is a type declaration with same name as the super class.
594     *
595     * @param superClassName name of the super class
596     * @return list if there is another type declaration with same name.
597     */
598    private List<TypeDeclDesc> typeDeclWithSameName(String superClassName) {
599        return typeDeclAstToTypeDeclDesc.values().stream()
600                .filter(typeDeclDesc -> {
601                    return hasSameNameAsSuperClass(superClassName, typeDeclDesc);
602                })
603                .collect(Collectors.toUnmodifiableList());
604    }
605
606    /**
607     * Whether the qualified name of {@code typeDeclDesc} matches the super class name.
608     *
609     * @param superClassName name of the super class
610     * @param typeDeclDesc type declaration description
611     * @return {@code true} if the qualified name of {@code typeDeclDesc}
612     *         matches the super class name
613     */
614    private boolean hasSameNameAsSuperClass(String superClassName, TypeDeclDesc typeDeclDesc) {
615        final boolean result;
616        if (packageName == null && typeDeclDesc.getDepth() == 0) {
617            result = typeDeclDesc.getQualifiedName().equals(superClassName);
618        }
619        else {
620            result = typeDeclDesc.getQualifiedName()
621                    .endsWith(PACKAGE_SEPARATOR + superClassName);
622        }
623        return result;
624    }
625
626    /**
627     * For all type declarations with the same name as the superclass, gets the nearest type
628     * declaration.
629     *
630     * @param outerTypeDeclName outer type declaration of anonymous inner class
631     * @param typeDeclWithSameName typeDeclarations which have the same name as the super class
632     * @return the nearest class
633     */
634    private static TypeDeclDesc getClosestMatchingTypeDeclaration(String outerTypeDeclName,
635            List<TypeDeclDesc> typeDeclWithSameName) {
636        return Collections.min(typeDeclWithSameName, (first, second) -> {
637            return calculateTypeDeclarationDistance(outerTypeDeclName, first, second);
638        });
639    }
640
641    /**
642     * Get the difference between type declaration name matching count. If the
643     * difference between them is zero, then their depth is compared to obtain the result.
644     *
645     * @param outerTypeName outer type declaration of anonymous inner class
646     * @param firstType first input type declaration
647     * @param secondType second input type declaration
648     * @return difference between type declaration name matching count
649     */
650    private static int calculateTypeDeclarationDistance(String outerTypeName,
651                                                        TypeDeclDesc firstType,
652                                                        TypeDeclDesc secondType) {
653        final int firstMatchCount =
654                countMatchingQualifierChars(outerTypeName, firstType.getQualifiedName());
655        final int secondMatchCount =
656                countMatchingQualifierChars(outerTypeName, secondType.getQualifiedName());
657        final int matchDistance = Integer.compare(secondMatchCount, firstMatchCount);
658
659        final int distance;
660        if (matchDistance == 0) {
661            distance = Integer.compare(firstType.getDepth(), secondType.getDepth());
662        }
663        else {
664            distance = matchDistance;
665        }
666
667        return distance;
668    }
669
670    /**
671     * Calculates the type declaration matching count for the superclass of an anonymous inner
672     * class.
673     *
674     * <p>
675     * For example, if the pattern class is {@code Main.ClassOne} and the class to be matched is
676     * {@code Main.ClassOne.ClassTwo.ClassThree}, then the matching count would be calculated by
677     * comparing the characters at each position, and updating the count whenever a '.'
678     * is encountered.
679     * This is necessary because pattern class can include anonymous inner classes, unlike regular
680     * inheritance where nested classes cannot be extended.
681     * </p>
682     *
683     * @param pattern type declaration to match against
684     * @param candidate type declaration to be matched
685     * @return the type declaration matching count
686     */
687    private static int countMatchingQualifierChars(String pattern,
688                                                   String candidate) {
689        final int typeDeclarationToBeMatchedLength = candidate.length();
690        final int minLength = Math
691                .min(typeDeclarationToBeMatchedLength, pattern.length());
692        final boolean shouldCountBeUpdatedAtLastCharacter =
693                typeDeclarationToBeMatchedLength > minLength
694                && candidate.charAt(minLength) == PACKAGE_SEPARATOR.charAt(0);
695
696        int result = 0;
697        for (int idx = 0;
698             idx < minLength
699                && pattern.charAt(idx) == candidate.charAt(idx);
700             idx++) {
701
702            if (shouldCountBeUpdatedAtLastCharacter
703                    || pattern.charAt(idx) == PACKAGE_SEPARATOR.charAt(0)) {
704                result = idx;
705            }
706        }
707        return result;
708    }
709
710    /**
711     * Get qualified type declaration name from type ast.
712     *
713     * @param typeDeclAst type declaration ast
714     * @return qualified name of type declaration
715     */
716    private String getQualifiedTypeDeclarationName(DetailAST typeDeclAst) {
717        final String className = typeDeclAst.findFirstToken(TokenTypes.IDENT).getText();
718        String outerClassQualifiedName = null;
719        if (!typeDeclarations.isEmpty()) {
720            outerClassQualifiedName = typeDeclarations.peek().getQualifiedName();
721        }
722        return CheckUtil
723            .getQualifiedTypeDeclarationName(packageName, outerClassQualifiedName, className);
724    }
725
726    /**
727     * Iterate over all the ast nodes present under {@code ast}.
728     *
729     * @param ast ast
730     * @param variablesStack stack of all the relevant variables in the scope
731     */
732    private void iterateOverBlockContainingLocalAnonInnerClass(
733            DetailAST ast, Deque<VariableDesc> variablesStack) {
734        DetailAST currNode = ast;
735        while (currNode != null) {
736            customVisitToken(currNode, variablesStack);
737            DetailAST toVisit = currNode.getFirstChild();
738            while (currNode != ast && toVisit == null) {
739                customLeaveToken(currNode, variablesStack);
740                toVisit = currNode.getNextSibling();
741                currNode = currNode.getParent();
742            }
743            currNode = toVisit;
744        }
745    }
746
747    /**
748     * Visit all ast nodes under {@link UnusedLocalVariableCheck#anonInnerClassHolders} once
749     * again.
750     *
751     * @param ast ast
752     * @param variablesStack stack of all the relevant variables in the scope
753     */
754    private void customVisitToken(DetailAST ast, Deque<VariableDesc> variablesStack) {
755        final int type = ast.getType();
756        if (type == TokenTypes.DOT) {
757            visitDotToken(ast, variablesStack);
758        }
759        else if (type == TokenTypes.VARIABLE_DEF) {
760            addLocalVariables(ast, variablesStack);
761        }
762        else if (type == TokenTypes.IDENT) {
763            visitIdentToken(ast, variablesStack);
764        }
765        else if (isInsideLocalAnonInnerClass(ast)) {
766            final TypeDeclDesc obtainedClass = getSuperClassOfAnonInnerClass(ast);
767            modifyVariablesStack(obtainedClass, variablesStack, ast);
768        }
769    }
770
771    /**
772     * Leave all ast nodes under {@link UnusedLocalVariableCheck#anonInnerClassHolders} once
773     * again.
774     *
775     * @param ast ast
776     * @param variablesStack stack of all the relevant variables in the scope
777     */
778    private void customLeaveToken(DetailAST ast, Deque<VariableDesc> variablesStack) {
779        logViolations(ast, variablesStack);
780    }
781
782    /**
783     * Whether to check identifier token nested under dotAst.
784     *
785     * @param dotAst dotAst
786     * @return true if ident nested under dotAst should be checked
787     */
788    private static boolean shouldCheckIdentTokenNestedUnderDot(DetailAST dotAst) {
789
790        return TokenUtil.findFirstTokenByPredicate(dotAst,
791                        childAst -> {
792                            return TokenUtil.isOfType(childAst,
793                                    UNACCEPTABLE_CHILD_OF_DOT);
794                        })
795                .isEmpty();
796    }
797
798    /**
799     * Checks the identifier ast.
800     *
801     * @param identAst ast of type {@link TokenTypes#IDENT}
802     * @param variablesStack stack of all the relevant variables in the scope
803     */
804    private static void checkIdentifierAst(DetailAST identAst, Deque<VariableDesc> variablesStack) {
805        for (VariableDesc variableDesc : variablesStack) {
806            if (identAst.getText().equals(variableDesc.getName())
807                    && !isLeftHandSideValue(identAst)) {
808                variableDesc.registerAsUsed();
809                break;
810            }
811        }
812    }
813
814    /**
815     * Find the scope of variable.
816     *
817     * @param variableDef ast of type {@link TokenTypes#VARIABLE_DEF}
818     * @return scope of variableDef
819     */
820    private static DetailAST findScopeOfVariable(DetailAST variableDef) {
821        final DetailAST result;
822        final DetailAST parentAst = variableDef.getParent();
823        if (TokenUtil.isOfType(parentAst, TokenTypes.SLIST, TokenTypes.OBJBLOCK)) {
824            result = parentAst;
825        }
826        else {
827            result = parentAst.getParent();
828        }
829        return result;
830    }
831
832    /**
833     * Checks whether the ast of type {@link TokenTypes#IDENT} is
834     * used as left-hand side value. An identifier is being used as a left-hand side
835     * value if it is used as the left operand of an assignment or as an
836     * operand of a stand-alone increment or decrement.
837     *
838     * @param identAst ast of type {@link TokenTypes#IDENT}
839     * @return true if identAst is used as a left-hand side value
840     */
841    private static boolean isLeftHandSideValue(DetailAST identAst) {
842        final DetailAST parent = identAst.getParent();
843        return isStandAloneIncrementOrDecrement(identAst)
844                || parent.getType() == TokenTypes.ASSIGN
845                && identAst != parent.getLastChild();
846    }
847
848    /**
849     * Checks whether the ast of type {@link TokenTypes#IDENT} is used as
850     * an operand of a stand-alone increment or decrement.
851     *
852     * @param identAst ast of type {@link TokenTypes#IDENT}
853     * @return true if identAst is used as an operand of stand-alone
854     *         increment or decrement
855     */
856    private static boolean isStandAloneIncrementOrDecrement(DetailAST identAst) {
857        final DetailAST parent = identAst.getParent();
858        final DetailAST grandParent = parent.getParent();
859        return TokenUtil.isOfType(parent, INCREMENT_AND_DECREMENT_TOKENS)
860                && TokenUtil.isOfType(grandParent, TokenTypes.EXPR)
861                && !isIncrementOrDecrementVariableUsed(grandParent);
862    }
863
864    /**
865     * A variable with increment or decrement operator is considered used if it
866     * is used as an argument or as an array index or for assigning value
867     * to a variable.
868     *
869     * @param exprAst ast of type {@link TokenTypes#EXPR}
870     * @return true if variable nested in exprAst is used
871     */
872    private static boolean isIncrementOrDecrementVariableUsed(DetailAST exprAst) {
873        return TokenUtil.isOfType(exprAst.getParent(), INCREMENT_DECREMENT_VARIABLE_USAGE_TYPES)
874                && exprAst.getParent().getParent().getType() != TokenTypes.FOR_ITERATOR;
875    }
876
877    /**
878     * Maintains information about the variable.
879     */
880    private static final class VariableDesc {
881
882        /**
883         * The name of the variable.
884         */
885        private final String name;
886
887        /**
888         * Ast of type {@link TokenTypes#TYPE}.
889         */
890        private final DetailAST typeAst;
891
892        /**
893         * The scope of variable is determined by the ast of type
894         * {@link TokenTypes#SLIST} or {@link TokenTypes#LITERAL_FOR}
895         * or {@link TokenTypes#OBJBLOCK} which is enclosing the variable.
896         */
897        private final DetailAST scope;
898
899        /**
900         * Is an instance variable or a class variable.
901         */
902        private boolean instVarOrClassVar;
903
904        /**
905         * Is the variable used.
906         */
907        private boolean used;
908
909        /**
910         * Create a new VariableDesc instance.
911         *
912         * @param name name of the variable
913         * @param typeAst ast of type {@link TokenTypes#TYPE}
914         * @param scope ast of type {@link TokenTypes#SLIST} or
915         *              {@link TokenTypes#LITERAL_FOR} or {@link TokenTypes#OBJBLOCK}
916         *              which is enclosing the variable
917         */
918        private VariableDesc(String name, DetailAST typeAst, DetailAST scope) {
919            this.name = name;
920            this.typeAst = typeAst;
921            this.scope = scope;
922        }
923
924        /**
925         * Create a new VariableDesc instance.
926         *
927         * @param name name of the variable
928         */
929        private VariableDesc(String name) {
930            this(name, null, null);
931        }
932
933        /**
934         * Create a new VariableDesc instance.
935         *
936         * @param name name of the variable
937         * @param scope ast of type {@link TokenTypes#SLIST} or
938         *              {@link TokenTypes#LITERAL_FOR} or {@link TokenTypes#OBJBLOCK}
939         *              which is enclosing the variable
940         */
941        private VariableDesc(String name, DetailAST scope) {
942            this(name, null, scope);
943        }
944
945        /**
946         * Get the name of variable.
947         *
948         * @return name of variable
949         */
950        public String getName() {
951            return name;
952        }
953
954        /**
955         * Get the associated ast node of type {@link TokenTypes#TYPE}.
956         *
957         * @return the associated ast node of type {@link TokenTypes#TYPE}
958         */
959        public DetailAST getTypeAst() {
960            return typeAst;
961        }
962
963        /**
964         * Get ast of type {@link TokenTypes#SLIST}
965         * or {@link TokenTypes#LITERAL_FOR} or {@link TokenTypes#OBJBLOCK}
966         * which is enclosing the variable i.e. its scope.
967         *
968         * @return the scope associated with the variable
969         */
970        public DetailAST getScope() {
971            return scope;
972        }
973
974        /**
975         * Register the variable as used.
976         */
977        public void registerAsUsed() {
978            used = true;
979        }
980
981        /**
982         * Register the variable as an instance variable or
983         * class variable.
984         */
985        public void registerAsInstOrClassVar() {
986            instVarOrClassVar = true;
987        }
988
989        /**
990         * Is the variable used or not.
991         *
992         * @return true if variable is used
993         */
994        public boolean isUsed() {
995            return used;
996        }
997
998        /**
999         * Is an instance variable or a class variable.
1000         *
1001         * @return true if is an instance variable or a class variable
1002         */
1003        public boolean isInstVarOrClassVar() {
1004            return instVarOrClassVar;
1005        }
1006    }
1007
1008    /**
1009     * Maintains information about the type declaration.
1010     * Any ast node of type {@link TokenTypes#CLASS_DEF} or {@link TokenTypes#INTERFACE_DEF}
1011     * or {@link TokenTypes#ENUM_DEF} or {@link TokenTypes#ANNOTATION_DEF}
1012     * or {@link TokenTypes#RECORD_DEF} is considered as a type declaration.
1013     */
1014    private static final class TypeDeclDesc {
1015
1016        /**
1017         * Complete type declaration name with package name and outer type declaration name.
1018         */
1019        private final String qualifiedName;
1020
1021        /**
1022         * Depth of nesting of type declaration.
1023         */
1024        private final int depth;
1025
1026        /**
1027         * Type declaration ast node.
1028         */
1029        private final DetailAST typeDeclAst;
1030
1031        /**
1032         * A stack of type declaration's instance and static variables.
1033         */
1034        private final Deque<VariableDesc> instanceAndClassVarStack;
1035
1036        /**
1037         * Create a new TypeDeclDesc instance.
1038         *
1039         * @param qualifiedName qualified name
1040         * @param depth depth of nesting
1041         * @param typeDeclAst type declaration ast node
1042         */
1043        private TypeDeclDesc(String qualifiedName, int depth,
1044                DetailAST typeDeclAst) {
1045            this.qualifiedName = qualifiedName;
1046            this.depth = depth;
1047            this.typeDeclAst = typeDeclAst;
1048            instanceAndClassVarStack = new ArrayDeque<>();
1049        }
1050
1051        /**
1052         * Get the complete type declaration name i.e. type declaration name with package name
1053         * and outer type declaration name.
1054         *
1055         * @return qualified class name
1056         */
1057        public String getQualifiedName() {
1058            return qualifiedName;
1059        }
1060
1061        /**
1062         * Get the depth of type declaration.
1063         *
1064         * @return the depth of nesting of type declaration
1065         */
1066        public int getDepth() {
1067            return depth;
1068        }
1069
1070        /**
1071         * Get the type declaration ast node.
1072         *
1073         * @return ast node of the type declaration
1074         */
1075        public DetailAST getTypeDeclAst() {
1076            return typeDeclAst;
1077        }
1078
1079        /**
1080         * Get the copy of variables in instanceAndClassVar stack with updated scope.
1081         *
1082         * @param literalNewAst ast node of type {@link TokenTypes#LITERAL_NEW}
1083         * @return copy of variables in instanceAndClassVar stack with updated scope.
1084         */
1085        public Deque<VariableDesc> getUpdatedCopyOfVarStack(DetailAST literalNewAst) {
1086            final DetailAST updatedScope = literalNewAst;
1087            final Deque<VariableDesc> instAndClassVarDeque = new ArrayDeque<>();
1088            instanceAndClassVarStack.forEach(instVar -> {
1089                final VariableDesc variableDesc = new VariableDesc(instVar.getName(),
1090                        updatedScope);
1091                variableDesc.registerAsInstOrClassVar();
1092                instAndClassVarDeque.push(variableDesc);
1093            });
1094            return instAndClassVarDeque;
1095        }
1096
1097        /**
1098         * Add an instance variable or class variable to the stack.
1099         *
1100         * @param variableDesc variable to be added
1101         */
1102        public void addInstOrClassVar(VariableDesc variableDesc) {
1103            instanceAndClassVarStack.push(variableDesc);
1104        }
1105    }
1106}