001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2023 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.AbstractMap.SimpleEntry;
023import java.util.ArrayList;
024import java.util.List;
025import java.util.Map.Entry;
026import java.util.Objects;
027import java.util.Optional;
028import java.util.regex.Matcher;
029import java.util.regex.Pattern;
030
031import com.puppycrawl.tools.checkstyle.StatelessCheck;
032import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
033import com.puppycrawl.tools.checkstyle.api.DetailAST;
034import com.puppycrawl.tools.checkstyle.api.FullIdent;
035import com.puppycrawl.tools.checkstyle.api.TokenTypes;
036import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
037
038/**
039 * <p>
040 * Checks the distance between declaration of variable and its first usage.
041 * Note : Variable declaration/initialization statements are not counted while calculating length.
042 * </p>
043 * <ul>
044 * <li>
045 * Property {@code allowedDistance} - Specify distance between declaration
046 * of variable and its first usage. Values should be greater than 0.
047 * Type is {@code int}.
048 * Default value is {@code 3}.
049 * </li>
050 * <li>
051 * Property {@code ignoreVariablePattern} - Define RegExp to ignore distance calculation
052 * for variables listed in this pattern.
053 * Type is {@code java.util.regex.Pattern}.
054 * Default value is {@code ""}.
055 * </li>
056 * <li>
057 * Property {@code validateBetweenScopes} - Allow to calculate the distance between
058 * declaration of variable and its first usage in the different scopes.
059 * Type is {@code boolean}.
060 * Default value is {@code false}.
061 * </li>
062 * <li>
063 * Property {@code ignoreFinal} - Allow to ignore variables with a 'final' modifier.
064 * Type is {@code boolean}.
065 * Default value is {@code true}.
066 * </li>
067 * </ul>
068 * <p>
069 * To configure the check with default config:
070 * </p>
071 * <pre>
072 * &lt;module name=&quot;VariableDeclarationUsageDistance&quot;/&gt;
073 * </pre>
074 * <p>Example:</p>
075 * <pre>
076 * public class Test {
077 *
078 *   public void foo1() {
079 *     int num;        // violation, distance = 4
080 *     final int PI;   // OK, final variables not checked
081 *     System.out.println("Statement 1");
082 *     System.out.println("Statement 2");
083 *     System.out.println("Statement 3");
084 *     num = 1;
085 *     PI = 3.14;
086 *   }
087 *
088 *   public void foo2() {
089 *     int a;          // OK, used in different scope
090 *     int b;          // OK, used in different scope
091 *     int count = 0;  // OK, used in different scope
092 *
093 *     {
094 *       System.out.println("Inside inner scope");
095 *       a = 1;
096 *       b = 2;
097 *       count++;
098 *     }
099 *   }
100 * }
101 * </pre>
102 * <p>
103 * Check can detect a block of initialization methods. If a variable is used in
104 * such a block and there are no other statements after variable declaration, then distance = 1.
105 * </p>
106 * <p>Case #1:</p>
107 * <pre>
108 * int minutes = 5;
109 * Calendar cal = Calendar.getInstance();
110 * cal.setTimeInMillis(timeNow);
111 * cal.set(Calendar.SECOND, 0);
112 * cal.set(Calendar.MILLISECOND, 0);
113 * cal.set(Calendar.HOUR_OF_DAY, hh);
114 * cal.set(Calendar.MINUTE, minutes);
115 * </pre>
116 * <p>
117 * The distance for the variable "minutes" is 1 even
118 * though this variable is used in the fifth method's call.
119 * </p>
120 * <p>Case #2:</p>
121 * <pre>
122 * int minutes = 5;
123 * Calendar cal = Calendar.getInstance();
124 * cal.setTimeInMillis(timeNow);
125 * cal.set(Calendar.SECOND, 0);
126 * cal.set(Calendar.MILLISECOND, 0);
127 * <i>System.out.println(cal);</i>
128 * cal.set(Calendar.HOUR_OF_DAY, hh);
129 * cal.set(Calendar.MINUTE, minutes);
130 * </pre>
131 * <p>
132 * The distance for the variable "minutes" is 6 because there is one more expression
133 * (except the initialization block) between the declaration of this variable and its usage.
134 * </p>
135 * <p>
136 * To configure the check to set allowed distance:
137 * </p>
138 * <pre>
139 * &lt;module name=&quot;VariableDeclarationUsageDistance&quot;&gt;
140 *   &lt;property name=&quot;allowedDistance&quot; value=&quot;4&quot;/&gt;
141 * &lt;/module&gt;
142 * </pre>
143 * <p>Example:</p>
144 * <pre>
145 * public class Test {
146 *
147 *   public void foo1() {
148 *     int num;        // OK, distance = 4
149 *     final int PI;   // OK, final variables not checked
150 *     System.out.println("Statement 1");
151 *     System.out.println("Statement 2");
152 *     System.out.println("Statement 3");
153 *     num = 1;
154 *     PI = 3.14;
155 *   }
156 *
157 *   public void foo2() {
158 *     int a;          // OK, used in different scope
159 *     int b;          // OK, used in different scope
160 *     int count = 0;  // OK, used in different scope
161 *
162 *     {
163 *       System.out.println("Inside inner scope");
164 *       a = 1;
165 *       b = 2;
166 *       count++;
167 *     }
168 *   }
169 * }
170 * </pre>
171 * <p>
172 * To configure the check to ignore certain variables:
173 * </p>
174 * <pre>
175 * &lt;module name=&quot;VariableDeclarationUsageDistance&quot;&gt;
176 *   &lt;property name=&quot;ignoreVariablePattern&quot; value=&quot;^num$&quot;/&gt;
177 * &lt;/module&gt;
178 * </pre>
179 * <p>
180 * This configuration ignores variables named "num".
181 * </p>
182 * <p>Example:</p>
183 * <pre>
184 * public class Test {
185 *
186 *   public void foo1() {
187 *     int num;        // OK, variable ignored
188 *     final int PI;   // OK, final variables not checked
189 *     System.out.println("Statement 1");
190 *     System.out.println("Statement 2");
191 *     System.out.println("Statement 3");
192 *     num = 1;
193 *     PI = 3.14;
194 *   }
195 *
196 *   public void foo2() {
197 *     int a;          // OK, used in different scope
198 *     int b;          // OK, used in different scope
199 *     int count = 0;  // OK, used in different scope
200 *
201 *     {
202 *       System.out.println("Inside inner scope");
203 *       a = 1;
204 *       b = 2;
205 *       count++;
206 *     }
207 *   }
208 * }
209 * </pre>
210 * <p>
211 * To configure the check to force validation between scopes:
212 * </p>
213 * <pre>
214 * &lt;module name=&quot;VariableDeclarationUsageDistance&quot;&gt;
215 *   &lt;property name=&quot;validateBetweenScopes&quot; value=&quot;true&quot;/&gt;
216 * &lt;/module&gt;
217 * </pre>
218 * <p>Example:</p>
219 * <pre>
220 * public class Test {
221 *
222 *   public void foo1() {
223 *     int num;        // violation, distance = 4
224 *     final int PI;   // OK, final variables not checked
225 *     System.out.println("Statement 1");
226 *     System.out.println("Statement 2");
227 *     System.out.println("Statement 3");
228 *     num = 1;
229 *     PI = 3.14;
230 *   }
231 *
232 *   public void foo2() {
233 *     int a;          // OK, distance = 2
234 *     int b;          // OK, distance = 3
235 *     int count = 0;  // violation, distance = 4
236 *
237 *     {
238 *       System.out.println("Inside inner scope");
239 *       a = 1;
240 *       b = 2;
241 *       count++;
242 *     }
243 *   }
244 * }
245 * </pre>
246 * <p>
247 * To configure the check to check final variables:
248 * </p>
249 * <pre>
250 * &lt;module name=&quot;VariableDeclarationUsageDistance&quot;&gt;
251 *   &lt;property name=&quot;ignoreFinal&quot; value=&quot;false&quot;/&gt;
252 * &lt;/module&gt;
253 * </pre>
254 * <p>Example:</p>
255 * <pre>
256 * public class Test {
257 *
258 *   public void foo1() {
259 *     int num;        // violation, distance = 4
260 *     final int PI;   // violation, distance = 5
261 *     System.out.println("Statement 1");
262 *     System.out.println("Statement 2");
263 *     System.out.println("Statement 3");
264 *     num = 1;
265 *     PI = 3.14;
266 *   }
267 *
268 *   public void foo2() {
269 *     int a;          // OK, used in different scope
270 *     int b;          // OK, used in different scope
271 *     int count = 0;  // OK, used in different scope
272 *
273 *     {
274 *       System.out.println("Inside inner scope");
275 *       a = 1;
276 *       b = 2;
277 *       count++;
278 *     }
279 *   }
280 * }
281 * </pre>
282 * <p>
283 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
284 * </p>
285 * <p>
286 * Violation Message Keys:
287 * </p>
288 * <ul>
289 * <li>
290 * {@code variable.declaration.usage.distance}
291 * </li>
292 * <li>
293 * {@code variable.declaration.usage.distance.extend}
294 * </li>
295 * </ul>
296 *
297 * @since 5.8
298 */
299@StatelessCheck
300public class VariableDeclarationUsageDistanceCheck extends AbstractCheck {
301
302    /**
303     * Warning message key.
304     */
305    public static final String MSG_KEY = "variable.declaration.usage.distance";
306
307    /**
308     * Warning message key.
309     */
310    public static final String MSG_KEY_EXT = "variable.declaration.usage.distance.extend";
311
312    /**
313     * Default value of distance between declaration of variable and its first
314     * usage.
315     */
316    private static final int DEFAULT_DISTANCE = 3;
317
318    /**
319     * Specify distance between declaration of variable and its first usage.
320     * Values should be greater than 0.
321     */
322    private int allowedDistance = DEFAULT_DISTANCE;
323
324    /**
325     * Define RegExp to ignore distance calculation for variables listed in
326     * this pattern.
327     */
328    private Pattern ignoreVariablePattern = Pattern.compile("");
329
330    /**
331     * Allow to calculate the distance between declaration of variable and its
332     * first usage in the different scopes.
333     */
334    private boolean validateBetweenScopes;
335
336    /** Allow to ignore variables with a 'final' modifier. */
337    private boolean ignoreFinal = true;
338
339    /**
340     * Setter to specify distance between declaration of variable and its first usage.
341     * Values should be greater than 0.
342     *
343     * @param allowedDistance
344     *        Allowed distance between declaration of variable and its first
345     *        usage.
346     */
347    public void setAllowedDistance(int allowedDistance) {
348        this.allowedDistance = allowedDistance;
349    }
350
351    /**
352     * Setter to define RegExp to ignore distance calculation for variables listed in this pattern.
353     *
354     * @param pattern a pattern.
355     */
356    public void setIgnoreVariablePattern(Pattern pattern) {
357        ignoreVariablePattern = pattern;
358    }
359
360    /**
361     * Setter to allow to calculate the distance between declaration of
362     * variable and its first usage in the different scopes.
363     *
364     * @param validateBetweenScopes
365     *        Defines if allow to calculate distance between declaration of
366     *        variable and its first usage in different scopes or not.
367     */
368    public void setValidateBetweenScopes(boolean validateBetweenScopes) {
369        this.validateBetweenScopes = validateBetweenScopes;
370    }
371
372    /**
373     * Setter to allow to ignore variables with a 'final' modifier.
374     *
375     * @param ignoreFinal
376     *        Defines if ignore variables with 'final' modifier or not.
377     */
378    public void setIgnoreFinal(boolean ignoreFinal) {
379        this.ignoreFinal = ignoreFinal;
380    }
381
382    @Override
383    public int[] getDefaultTokens() {
384        return getRequiredTokens();
385    }
386
387    @Override
388    public int[] getAcceptableTokens() {
389        return getRequiredTokens();
390    }
391
392    @Override
393    public int[] getRequiredTokens() {
394        return new int[] {TokenTypes.VARIABLE_DEF};
395    }
396
397    @Override
398    public void visitToken(DetailAST ast) {
399        final int parentType = ast.getParent().getType();
400        final DetailAST modifiers = ast.getFirstChild();
401
402        if (parentType != TokenTypes.OBJBLOCK
403                && (!ignoreFinal || modifiers.findFirstToken(TokenTypes.FINAL) == null)) {
404            final DetailAST variable = ast.findFirstToken(TokenTypes.IDENT);
405
406            if (!isVariableMatchesIgnorePattern(variable.getText())) {
407                final DetailAST semicolonAst = ast.getNextSibling();
408                final Entry<DetailAST, Integer> entry;
409                if (validateBetweenScopes) {
410                    entry = calculateDistanceBetweenScopes(semicolonAst, variable);
411                }
412                else {
413                    entry = calculateDistanceInSingleScope(semicolonAst, variable);
414                }
415                final DetailAST variableUsageAst = entry.getKey();
416                final int dist = entry.getValue();
417                if (dist > allowedDistance
418                        && !isInitializationSequence(variableUsageAst, variable.getText())) {
419                    if (ignoreFinal) {
420                        log(ast, MSG_KEY_EXT, variable.getText(), dist, allowedDistance);
421                    }
422                    else {
423                        log(ast, MSG_KEY, variable.getText(), dist, allowedDistance);
424                    }
425                }
426            }
427        }
428    }
429
430    /**
431     * Get name of instance whose method is called.
432     *
433     * @param methodCallAst
434     *        DetailAST of METHOD_CALL.
435     * @return name of instance.
436     */
437    private static String getInstanceName(DetailAST methodCallAst) {
438        final String methodCallName =
439                FullIdent.createFullIdentBelow(methodCallAst).getText();
440        final int lastDotIndex = methodCallName.lastIndexOf('.');
441        String instanceName = "";
442        if (lastDotIndex != -1) {
443            instanceName = methodCallName.substring(0, lastDotIndex);
444        }
445        return instanceName;
446    }
447
448    /**
449     * Processes statements until usage of variable to detect sequence of
450     * initialization methods.
451     *
452     * @param variableUsageAst
453     *        DetailAST of expression that uses variable named variableName.
454     * @param variableName
455     *        name of considered variable.
456     * @return true if statements between declaration and usage of variable are
457     *         initialization methods.
458     */
459    private static boolean isInitializationSequence(
460            DetailAST variableUsageAst, String variableName) {
461        boolean result = true;
462        boolean isUsedVariableDeclarationFound = false;
463        DetailAST currentSiblingAst = variableUsageAst;
464        String initInstanceName = "";
465
466        while (result
467                && !isUsedVariableDeclarationFound
468                && currentSiblingAst != null) {
469            switch (currentSiblingAst.getType()) {
470                case TokenTypes.EXPR:
471                    final DetailAST methodCallAst = currentSiblingAst.getFirstChild();
472
473                    if (methodCallAst.getType() == TokenTypes.METHOD_CALL) {
474                        final String instanceName =
475                            getInstanceName(methodCallAst);
476                        // method is called without instance
477                        if (instanceName.isEmpty()) {
478                            result = false;
479                        }
480                        // differs from previous instance
481                        else if (!instanceName.equals(initInstanceName)) {
482                            if (initInstanceName.isEmpty()) {
483                                initInstanceName = instanceName;
484                            }
485                            else {
486                                result = false;
487                            }
488                        }
489                    }
490                    else {
491                        // is not method call
492                        result = false;
493                    }
494                    break;
495
496                case TokenTypes.VARIABLE_DEF:
497                    final String currentVariableName = currentSiblingAst
498                        .findFirstToken(TokenTypes.IDENT).getText();
499                    isUsedVariableDeclarationFound = variableName.equals(currentVariableName);
500                    break;
501
502                case TokenTypes.SEMI:
503                    break;
504
505                default:
506                    result = false;
507            }
508
509            currentSiblingAst = currentSiblingAst.getPreviousSibling();
510        }
511
512        return result;
513    }
514
515    /**
516     * Calculates distance between declaration of variable and its first usage
517     * in single scope.
518     *
519     * @param semicolonAst
520     *        Regular node of Ast which is checked for content of checking
521     *        variable.
522     * @param variableIdentAst
523     *        Variable which distance is calculated for.
524     * @return entry which contains expression with variable usage and distance.
525     *         If variable usage is not found, then the expression node is null,
526     *         although the distance can be greater than zero.
527     */
528    private static Entry<DetailAST, Integer> calculateDistanceInSingleScope(
529            DetailAST semicolonAst, DetailAST variableIdentAst) {
530        int dist = 0;
531        boolean firstUsageFound = false;
532        DetailAST currentAst = semicolonAst;
533        DetailAST variableUsageAst = null;
534
535        while (!firstUsageFound && currentAst != null) {
536            if (currentAst.getFirstChild() != null) {
537                if (isChild(currentAst, variableIdentAst)) {
538                    dist = getDistToVariableUsageInChildNode(currentAst, variableIdentAst, dist);
539                    variableUsageAst = currentAst;
540                    firstUsageFound = true;
541                }
542                else if (currentAst.getType() != TokenTypes.VARIABLE_DEF) {
543                    dist++;
544                }
545            }
546            currentAst = currentAst.getNextSibling();
547        }
548
549        return new SimpleEntry<>(variableUsageAst, dist);
550    }
551
552    /**
553     * Returns the distance to variable usage for in the child node.
554     *
555     * @param childNode child node.
556     * @param varIdent variable variable identifier.
557     * @param currentDistToVarUsage current distance to the variable usage.
558     * @return the distance to variable usage for in the child node.
559     */
560    private static int getDistToVariableUsageInChildNode(DetailAST childNode, DetailAST varIdent,
561                                                         int currentDistToVarUsage) {
562        DetailAST examineNode = childNode;
563        if (examineNode.getType() == TokenTypes.LABELED_STAT) {
564            examineNode = examineNode.getFirstChild().getNextSibling();
565        }
566
567        int resultDist = currentDistToVarUsage;
568        switch (examineNode.getType()) {
569            case TokenTypes.VARIABLE_DEF:
570                resultDist++;
571                break;
572            case TokenTypes.SLIST:
573                resultDist = 0;
574                break;
575            case TokenTypes.LITERAL_FOR:
576            case TokenTypes.LITERAL_WHILE:
577            case TokenTypes.LITERAL_DO:
578            case TokenTypes.LITERAL_IF:
579            case TokenTypes.LITERAL_SWITCH:
580                if (isVariableInOperatorExpr(examineNode, varIdent)) {
581                    resultDist++;
582                }
583                else {
584                    // variable usage is in inner scope
585                    // reset counters, because we can't determine distance
586                    resultDist = 0;
587                }
588                break;
589            default:
590                if (examineNode.findFirstToken(TokenTypes.SLIST) == null) {
591                    resultDist++;
592                }
593                else {
594                    resultDist = 0;
595                }
596        }
597        return resultDist;
598    }
599
600    /**
601     * Calculates distance between declaration of variable and its first usage
602     * in multiple scopes.
603     *
604     * @param ast
605     *        Regular node of Ast which is checked for content of checking
606     *        variable.
607     * @param variable
608     *        Variable which distance is calculated for.
609     * @return entry which contains expression with variable usage and distance.
610     */
611    private static Entry<DetailAST, Integer> calculateDistanceBetweenScopes(
612            DetailAST ast, DetailAST variable) {
613        int dist = 0;
614        DetailAST currentScopeAst = ast;
615        DetailAST variableUsageAst = null;
616        while (currentScopeAst != null) {
617            final Entry<List<DetailAST>, Integer> searchResult =
618                    searchVariableUsageExpressions(variable, currentScopeAst);
619
620            currentScopeAst = null;
621
622            final List<DetailAST> variableUsageExpressions = searchResult.getKey();
623            dist += searchResult.getValue();
624
625            // If variable usage exists in a single scope, then look into
626            // this scope and count distance until variable usage.
627            if (variableUsageExpressions.size() == 1) {
628                final DetailAST blockWithVariableUsage = variableUsageExpressions
629                        .get(0);
630                DetailAST exprWithVariableUsage = null;
631                switch (blockWithVariableUsage.getType()) {
632                    case TokenTypes.VARIABLE_DEF:
633                    case TokenTypes.EXPR:
634                        dist++;
635                        break;
636                    case TokenTypes.LITERAL_FOR:
637                    case TokenTypes.LITERAL_WHILE:
638                    case TokenTypes.LITERAL_DO:
639                        exprWithVariableUsage = getFirstNodeInsideForWhileDoWhileBlocks(
640                            blockWithVariableUsage, variable);
641                        break;
642                    case TokenTypes.LITERAL_IF:
643                        exprWithVariableUsage = getFirstNodeInsideIfBlock(
644                            blockWithVariableUsage, variable);
645                        break;
646                    case TokenTypes.LITERAL_SWITCH:
647                        exprWithVariableUsage = getFirstNodeInsideSwitchBlock(
648                            blockWithVariableUsage, variable);
649                        break;
650                    case TokenTypes.LITERAL_TRY:
651                        exprWithVariableUsage =
652                            getFirstNodeInsideTryCatchFinallyBlocks(blockWithVariableUsage,
653                                variable);
654                        break;
655                    default:
656                        exprWithVariableUsage = blockWithVariableUsage.getFirstChild();
657                }
658                currentScopeAst = exprWithVariableUsage;
659                variableUsageAst =
660                        Objects.requireNonNullElse(exprWithVariableUsage, blockWithVariableUsage);
661            }
662
663            // If there's no any variable usage, then distance = 0.
664            else if (variableUsageExpressions.isEmpty()) {
665                variableUsageAst = null;
666            }
667            // If variable usage exists in different scopes, then distance =
668            // distance until variable first usage.
669            else {
670                dist++;
671                variableUsageAst = variableUsageExpressions.get(0);
672            }
673        }
674        return new SimpleEntry<>(variableUsageAst, dist);
675    }
676
677    /**
678     * Searches variable usages starting from specified statement.
679     *
680     * @param variableAst Variable that is used.
681     * @param statementAst DetailAST to start searching from.
682     * @return entry which contains list with found expressions that use the variable
683     *     and distance from specified statement to first found expression.
684     */
685    private static Entry<List<DetailAST>, Integer>
686        searchVariableUsageExpressions(final DetailAST variableAst, final DetailAST statementAst) {
687        final List<DetailAST> variableUsageExpressions = new ArrayList<>();
688        int distance = 0;
689        DetailAST currentStatementAst = statementAst;
690        while (currentStatementAst != null) {
691            if (currentStatementAst.getFirstChild() != null) {
692                if (isChild(currentStatementAst, variableAst)) {
693                    variableUsageExpressions.add(currentStatementAst);
694                }
695                // If expression doesn't contain variable and this variable
696                // hasn't been met yet, then distance + 1.
697                else if (variableUsageExpressions.isEmpty()
698                        && currentStatementAst.getType() != TokenTypes.VARIABLE_DEF) {
699                    distance++;
700                }
701            }
702            currentStatementAst = currentStatementAst.getNextSibling();
703        }
704        return new SimpleEntry<>(variableUsageExpressions, distance);
705    }
706
707    /**
708     * Gets first Ast node inside FOR, WHILE or DO-WHILE blocks if variable
709     * usage is met only inside the block (not in its declaration!).
710     *
711     * @param block
712     *        Ast node represents FOR, WHILE or DO-WHILE block.
713     * @param variable
714     *        Variable which is checked for content in block.
715     * @return If variable usage is met only inside the block
716     *         (not in its declaration!) then return the first Ast node
717     *         of this block, otherwise - null.
718     */
719    private static DetailAST getFirstNodeInsideForWhileDoWhileBlocks(
720            DetailAST block, DetailAST variable) {
721        DetailAST firstNodeInsideBlock = null;
722
723        if (!isVariableInOperatorExpr(block, variable)) {
724            final DetailAST currentNode;
725
726            // Find currentNode for DO-WHILE block.
727            if (block.getType() == TokenTypes.LITERAL_DO) {
728                currentNode = block.getFirstChild();
729            }
730            // Find currentNode for FOR or WHILE block.
731            else {
732                // Looking for RPAREN ( ')' ) token to mark the end of operator
733                // expression.
734                currentNode = block.findFirstToken(TokenTypes.RPAREN).getNextSibling();
735            }
736
737            final int currentNodeType = currentNode.getType();
738
739            if (currentNodeType != TokenTypes.EXPR) {
740                firstNodeInsideBlock = currentNode;
741            }
742        }
743
744        return firstNodeInsideBlock;
745    }
746
747    /**
748     * Gets first Ast node inside IF block if variable usage is met
749     * only inside the block (not in its declaration!).
750     *
751     * @param block
752     *        Ast node represents IF block.
753     * @param variable
754     *        Variable which is checked for content in block.
755     * @return If variable usage is met only inside the block
756     *         (not in its declaration!) then return the first Ast node
757     *         of this block, otherwise - null.
758     */
759    private static DetailAST getFirstNodeInsideIfBlock(
760            DetailAST block, DetailAST variable) {
761        DetailAST firstNodeInsideBlock = null;
762
763        if (!isVariableInOperatorExpr(block, variable)) {
764            final Optional<DetailAST> slistToken = TokenUtil
765                .findFirstTokenByPredicate(block, token -> token.getType() == TokenTypes.SLIST);
766            final DetailAST lastNode = block.getLastChild();
767            DetailAST previousNode = lastNode.getPreviousSibling();
768
769            if (slistToken.isEmpty()
770                && lastNode.getType() == TokenTypes.LITERAL_ELSE) {
771
772                // Is if statement without '{}' and has a following else branch,
773                // then change previousNode to the if statement body.
774                previousNode = previousNode.getPreviousSibling();
775            }
776
777            final List<DetailAST> variableUsageExpressions = new ArrayList<>();
778            if (isChild(previousNode, variable)) {
779                variableUsageExpressions.add(previousNode);
780            }
781
782            if (isChild(lastNode, variable)) {
783                variableUsageExpressions.add(lastNode);
784            }
785
786            // If variable usage exists in several related blocks, then
787            // firstNodeInsideBlock = null, otherwise if variable usage exists
788            // only inside one block, then get node from
789            // variableUsageExpressions.
790            if (variableUsageExpressions.size() == 1) {
791                firstNodeInsideBlock = variableUsageExpressions.get(0);
792            }
793        }
794
795        return firstNodeInsideBlock;
796    }
797
798    /**
799     * Gets first Ast node inside SWITCH block if variable usage is met
800     * only inside the block (not in its declaration!).
801     *
802     * @param block
803     *        Ast node represents SWITCH block.
804     * @param variable
805     *        Variable which is checked for content in block.
806     * @return If variable usage is met only inside the block
807     *         (not in its declaration!) then return the first Ast node
808     *         of this block, otherwise - null.
809     */
810    private static DetailAST getFirstNodeInsideSwitchBlock(
811            DetailAST block, DetailAST variable) {
812        final List<DetailAST> variableUsageExpressions =
813                getVariableUsageExpressionsInsideSwitchBlock(block, variable);
814
815        // If variable usage exists in several related blocks, then
816        // firstNodeInsideBlock = null, otherwise if variable usage exists
817        // only inside one block, then get node from
818        // variableUsageExpressions.
819        DetailAST firstNodeInsideBlock = null;
820        if (variableUsageExpressions.size() == 1) {
821            firstNodeInsideBlock = variableUsageExpressions.get(0);
822        }
823
824        return firstNodeInsideBlock;
825    }
826
827    /**
828     * Helper method for getFirstNodeInsideSwitchBlock to return all variable
829     * usage expressions inside a given switch block.
830     *
831     * @param block the switch block to check.
832     * @param variable variable which is checked for in switch block.
833     * @return List of usages or empty list if none are found.
834     */
835    private static List<DetailAST> getVariableUsageExpressionsInsideSwitchBlock(DetailAST block,
836                                                                            DetailAST variable) {
837        final Optional<DetailAST> firstToken = TokenUtil.findFirstTokenByPredicate(block, child -> {
838            return child.getType() == TokenTypes.SWITCH_RULE
839                    || child.getType() == TokenTypes.CASE_GROUP;
840        });
841
842        final List<DetailAST> variableUsageExpressions = new ArrayList<>();
843
844        firstToken.ifPresent(token -> {
845            TokenUtil.forEachChild(block, token.getType(), child -> {
846                final DetailAST lastNodeInCaseGroup = child.getLastChild();
847                if (isChild(lastNodeInCaseGroup, variable)) {
848                    variableUsageExpressions.add(lastNodeInCaseGroup);
849                }
850            });
851        });
852
853        return variableUsageExpressions;
854    }
855
856    /**
857     * Gets first Ast node inside TRY-CATCH-FINALLY blocks if variable usage is
858     * met only inside the block (not in its declaration!).
859     *
860     * @param block
861     *        Ast node represents TRY-CATCH-FINALLY block.
862     * @param variable
863     *        Variable which is checked for content in block.
864     * @return If variable usage is met only inside the block
865     *         (not in its declaration!) then return the first Ast node
866     *         of this block, otherwise - null.
867     */
868    private static DetailAST getFirstNodeInsideTryCatchFinallyBlocks(
869            DetailAST block, DetailAST variable) {
870        DetailAST currentNode = block.getFirstChild();
871        final List<DetailAST> variableUsageExpressions =
872                new ArrayList<>();
873
874        // Checking variable usage inside TRY block.
875        if (isChild(currentNode, variable)) {
876            variableUsageExpressions.add(currentNode);
877        }
878
879        // Switch on CATCH block.
880        currentNode = currentNode.getNextSibling();
881
882        // Checking variable usage inside all CATCH blocks.
883        while (currentNode != null
884                && currentNode.getType() == TokenTypes.LITERAL_CATCH) {
885            final DetailAST catchBlock = currentNode.getLastChild();
886
887            if (isChild(catchBlock, variable)) {
888                variableUsageExpressions.add(catchBlock);
889            }
890            currentNode = currentNode.getNextSibling();
891        }
892
893        // Checking variable usage inside FINALLY block.
894        if (currentNode != null) {
895            final DetailAST finalBlock = currentNode.getLastChild();
896
897            if (isChild(finalBlock, variable)) {
898                variableUsageExpressions.add(finalBlock);
899            }
900        }
901
902        DetailAST variableUsageNode = null;
903
904        // If variable usage exists in several related blocks, then
905        // firstNodeInsideBlock = null, otherwise if variable usage exists
906        // only inside one block, then get node from
907        // variableUsageExpressions.
908        if (variableUsageExpressions.size() == 1) {
909            variableUsageNode = variableUsageExpressions.get(0).getFirstChild();
910        }
911
912        return variableUsageNode;
913    }
914
915    /**
916     * Checks if variable is in operator declaration. For instance:
917     * <pre>
918     * boolean b = true;
919     * if (b) {...}
920     * </pre>
921     * Variable 'b' is in declaration of operator IF.
922     *
923     * @param operator
924     *        Ast node which represents operator.
925     * @param variable
926     *        Variable which is checked for content in operator.
927     * @return true if operator contains variable in its declaration, otherwise
928     *         - false.
929     */
930    private static boolean isVariableInOperatorExpr(
931            DetailAST operator, DetailAST variable) {
932        boolean isVarInOperatorDeclaration = false;
933        final DetailAST openingBracket =
934                operator.findFirstToken(TokenTypes.LPAREN);
935
936        // Get EXPR between brackets
937        DetailAST exprBetweenBrackets = openingBracket.getNextSibling();
938
939        // Look if variable is in operator expression
940        while (exprBetweenBrackets.getType() != TokenTypes.RPAREN) {
941            if (isChild(exprBetweenBrackets, variable)) {
942                isVarInOperatorDeclaration = true;
943                break;
944            }
945            exprBetweenBrackets = exprBetweenBrackets.getNextSibling();
946        }
947
948        // Variable may be met in ELSE declaration
949        // So, check variable usage in these declarations.
950        if (!isVarInOperatorDeclaration) {
951            final DetailAST elseBlock = operator.getLastChild();
952
953            if (elseBlock.getType() == TokenTypes.LITERAL_ELSE) {
954                // Get IF followed by ELSE
955                final DetailAST firstNodeInsideElseBlock = elseBlock.getFirstChild();
956
957                if (firstNodeInsideElseBlock.getType() == TokenTypes.LITERAL_IF) {
958                    isVarInOperatorDeclaration =
959                        isVariableInOperatorExpr(firstNodeInsideElseBlock, variable);
960                }
961            }
962        }
963
964        return isVarInOperatorDeclaration;
965    }
966
967    /**
968     * Checks if Ast node contains given element.
969     *
970     * @param parent
971     *        Node of AST.
972     * @param ast
973     *        Ast element which is checked for content in Ast node.
974     * @return true if Ast element was found in Ast node, otherwise - false.
975     */
976    private static boolean isChild(DetailAST parent, DetailAST ast) {
977        boolean isChild = false;
978        DetailAST curNode = parent.getFirstChild();
979
980        while (curNode != null) {
981            if (curNode.getType() == ast.getType() && curNode.getText().equals(ast.getText())) {
982                isChild = true;
983                break;
984            }
985
986            DetailAST toVisit = curNode.getFirstChild();
987            while (toVisit == null) {
988                toVisit = curNode.getNextSibling();
989                curNode = curNode.getParent();
990
991                if (curNode == parent) {
992                    break;
993                }
994            }
995
996            curNode = toVisit;
997        }
998
999        return isChild;
1000    }
1001
1002    /**
1003     * Checks if entrance variable is contained in ignored pattern.
1004     *
1005     * @param variable
1006     *        Variable which is checked for content in ignored pattern.
1007     * @return true if variable was found, otherwise - false.
1008     */
1009    private boolean isVariableMatchesIgnorePattern(String variable) {
1010        final Matcher matcher = ignoreVariablePattern.matcher(variable);
1011        return matcher.matches();
1012    }
1013
1014}