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