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