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