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