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