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