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