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