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