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