All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.puppycrawl.tools.checkstyle.checks.coding.FinalLocalVariableCheck Maven / Gradle / Ivy

////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2022 the original author or authors.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
////////////////////////////////////////////////////////////////////////////////

package com.puppycrawl.tools.checkstyle.checks.coding;

import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Optional;

import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
import com.puppycrawl.tools.checkstyle.utils.TokenUtil;

/**
 * 

* Checks that local variables that never have their values changed are declared final. * The check can be configured to also check that unchanged parameters are declared final. *

*

* When configured to check parameters, the check ignores parameters of interface * methods and abstract methods. *

*
    *
  • * Property {@code validateEnhancedForLoopVariable} - Control whether to check * * enhanced for-loop variable. * Type is {@code boolean}. * Default value is {@code false}. *
  • *
  • * Property {@code tokens} - tokens to check * Type is {@code java.lang.String[]}. * Validation type is {@code tokenSet}. * Default value is: * * VARIABLE_DEF. *
  • *
*

* To configure the check: *

*
 * <module name="FinalLocalVariable"/>
 * 
*

* To configure the check so that it checks local variables and parameters: *

*
 * <module name="FinalLocalVariable">
 *   <property name="tokens" value="VARIABLE_DEF,PARAMETER_DEF"/>
 * </module>
 * 
*

* By default, this Check skip final validation on * * Enhanced For-Loop. *

*

* Option 'validateEnhancedForLoopVariable' could be used to make Check to validate even variable * from Enhanced For Loop. *

*

* An example of how to configure the check so that it also validates enhanced For Loop Variable is: *

*
 * <module name="FinalLocalVariable">
 *   <property name="tokens" value="VARIABLE_DEF"/>
 *   <property name="validateEnhancedForLoopVariable" value="true"/>
 * </module>
 * 
*

Example:

*
 * for (int number : myNumbers) { // violation
 *   System.out.println(number);
 * }
 * 
*

* An example of how to configure check on local variables and parameters * but do not validate loop variables: *

*
 * <module name="FinalLocalVariable">
 *    <property name="tokens" value="VARIABLE_DEF,PARAMETER_DEF"/>
 *    <property name="validateEnhancedForLoopVariable" value="false"/>
 *  </module>
 * 
*

* Example: *

*
 * public class MyClass {
 *   static int foo(int x, int y) { //violations, parameters should be final
 *     return x+y;
 *   }
 *   public static void main (String []args) { //violation, parameters should be final
 *     for (String i : args) {
 *       System.out.println(i);
 *     }
 *     int result=foo(1,2); // violation
 *   }
 * }
 * 
*

* Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} *

*

* Violation Message Keys: *

*
    *
  • * {@code final.variable} *
  • *
* * @since 3.2 */ @FileStatefulCheck public class FinalLocalVariableCheck extends AbstractCheck { /** * A key is pointing to the warning message text in "messages.properties" * file. */ public static final String MSG_KEY = "final.variable"; /** * Assign operator types. */ private static final int[] ASSIGN_OPERATOR_TYPES = { TokenTypes.POST_INC, TokenTypes.POST_DEC, TokenTypes.ASSIGN, TokenTypes.PLUS_ASSIGN, TokenTypes.MINUS_ASSIGN, TokenTypes.STAR_ASSIGN, TokenTypes.DIV_ASSIGN, TokenTypes.MOD_ASSIGN, TokenTypes.SR_ASSIGN, TokenTypes.BSR_ASSIGN, TokenTypes.SL_ASSIGN, TokenTypes.BAND_ASSIGN, TokenTypes.BXOR_ASSIGN, TokenTypes.BOR_ASSIGN, TokenTypes.INC, TokenTypes.DEC, }; /** * Loop types. */ private static final int[] LOOP_TYPES = { TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_WHILE, TokenTypes.LITERAL_DO, }; /** Scope Deque. */ private final Deque scopeStack = new ArrayDeque<>(); /** Uninitialized variables of previous scope. */ private final Deque> prevScopeUninitializedVariables = new ArrayDeque<>(); /** Assigned variables of current scope. */ private final Deque> currentScopeAssignedVariables = new ArrayDeque<>(); /** * Control whether to check * * enhanced for-loop variable. */ private boolean validateEnhancedForLoopVariable; static { // Array sorting for binary search Arrays.sort(ASSIGN_OPERATOR_TYPES); Arrays.sort(LOOP_TYPES); } /** * Setter to control whether to check * * enhanced for-loop variable. * * @param validateEnhancedForLoopVariable whether to check for-loop variable */ public final void setValidateEnhancedForLoopVariable(boolean validateEnhancedForLoopVariable) { this.validateEnhancedForLoopVariable = validateEnhancedForLoopVariable; } @Override public int[] getRequiredTokens() { return new int[] { TokenTypes.IDENT, TokenTypes.CTOR_DEF, TokenTypes.METHOD_DEF, TokenTypes.SLIST, TokenTypes.OBJBLOCK, TokenTypes.LITERAL_BREAK, TokenTypes.LITERAL_FOR, TokenTypes.EXPR, }; } @Override public int[] getDefaultTokens() { return new int[] { TokenTypes.IDENT, TokenTypes.CTOR_DEF, TokenTypes.METHOD_DEF, TokenTypes.SLIST, TokenTypes.OBJBLOCK, TokenTypes.LITERAL_BREAK, TokenTypes.LITERAL_FOR, TokenTypes.VARIABLE_DEF, TokenTypes.EXPR, }; } @Override public int[] getAcceptableTokens() { return new int[] { TokenTypes.IDENT, TokenTypes.CTOR_DEF, TokenTypes.METHOD_DEF, TokenTypes.SLIST, TokenTypes.OBJBLOCK, TokenTypes.LITERAL_BREAK, TokenTypes.LITERAL_FOR, TokenTypes.VARIABLE_DEF, TokenTypes.PARAMETER_DEF, TokenTypes.EXPR, }; } // -@cs[CyclomaticComplexity] The only optimization which can be done here is moving CASE-block // expressions to separate methods, but that will not increase readability. @Override public void visitToken(DetailAST ast) { switch (ast.getType()) { case TokenTypes.OBJBLOCK: case TokenTypes.METHOD_DEF: case TokenTypes.CTOR_DEF: case TokenTypes.LITERAL_FOR: scopeStack.push(new ScopeData()); break; case TokenTypes.SLIST: currentScopeAssignedVariables.push(new ArrayDeque<>()); if (ast.getParent().getType() != TokenTypes.CASE_GROUP || ast.getParent().getParent().findFirstToken(TokenTypes.CASE_GROUP) == ast.getParent()) { storePrevScopeUninitializedVariableData(); scopeStack.push(new ScopeData()); } break; case TokenTypes.PARAMETER_DEF: if (!isInLambda(ast) && ast.findFirstToken(TokenTypes.MODIFIERS) .findFirstToken(TokenTypes.FINAL) == null && !isInAbstractOrNativeMethod(ast) && !ScopeUtil.isInInterfaceBlock(ast) && !isMultipleTypeCatch(ast) && !CheckUtil.isReceiverParameter(ast)) { insertParameter(ast); } break; case TokenTypes.VARIABLE_DEF: if (ast.getParent().getType() != TokenTypes.OBJBLOCK && ast.findFirstToken(TokenTypes.MODIFIERS) .findFirstToken(TokenTypes.FINAL) == null && !isVariableInForInit(ast) && shouldCheckEnhancedForLoopVariable(ast)) { insertVariable(ast); } break; case TokenTypes.IDENT: final int parentType = ast.getParent().getType(); if (isAssignOperator(parentType) && isFirstChild(ast)) { final Optional candidate = getFinalCandidate(ast); if (candidate.isPresent()) { determineAssignmentConditions(ast, candidate.get()); currentScopeAssignedVariables.peek().add(ast); } removeFinalVariableCandidateFromStack(ast); } break; case TokenTypes.LITERAL_BREAK: scopeStack.peek().containsBreak = true; break; case TokenTypes.EXPR: // Switch labeled expression has no slist if (ast.getParent().getType() == TokenTypes.SWITCH_RULE && ast.getParent().getParent().findFirstToken(TokenTypes.SWITCH_RULE) == ast.getParent()) { storePrevScopeUninitializedVariableData(); } break; default: throw new IllegalStateException("Incorrect token type"); } } @Override public void leaveToken(DetailAST ast) { Map scope = null; final Deque prevScopeUninitializedVariableData; switch (ast.getType()) { case TokenTypes.OBJBLOCK: case TokenTypes.CTOR_DEF: case TokenTypes.METHOD_DEF: case TokenTypes.LITERAL_FOR: scope = scopeStack.pop().scope; break; case TokenTypes.EXPR: // Switch labeled expression has no slist if (ast.getParent().getType() == TokenTypes.SWITCH_RULE) { prevScopeUninitializedVariableData = prevScopeUninitializedVariables.peek(); if (shouldUpdateUninitializedVariables(ast.getParent())) { updateAllUninitializedVariables(prevScopeUninitializedVariableData); } } break; case TokenTypes.SLIST: prevScopeUninitializedVariableData = prevScopeUninitializedVariables.peek(); boolean containsBreak = false; if (ast.getParent().getType() != TokenTypes.CASE_GROUP || findLastChildWhichContainsSpecifiedToken(ast.getParent().getParent(), TokenTypes.CASE_GROUP, TokenTypes.SLIST) == ast.getParent()) { containsBreak = scopeStack.peek().containsBreak; scope = scopeStack.pop().scope; prevScopeUninitializedVariables.pop(); } final DetailAST parent = ast.getParent(); if (containsBreak || shouldUpdateUninitializedVariables(parent)) { updateAllUninitializedVariables(prevScopeUninitializedVariableData); } updateCurrentScopeAssignedVariables(); break; default: // do nothing } if (scope != null) { for (FinalVariableCandidate candidate : scope.values()) { final DetailAST ident = candidate.variableIdent; log(ident, MSG_KEY, ident.getText()); } } } /** * Update assigned variables in a temporary stack. */ private void updateCurrentScopeAssignedVariables() { // -@cs[MoveVariableInsideIf] assignment value is a modification call so it can't be moved final Deque poppedScopeAssignedVariableData = currentScopeAssignedVariables.pop(); final Deque currentScopeAssignedVariableData = currentScopeAssignedVariables.peek(); if (currentScopeAssignedVariableData != null) { currentScopeAssignedVariableData.addAll(poppedScopeAssignedVariableData); } } /** * Determines identifier assignment conditions (assigned or already assigned). * * @param ident identifier. * @param candidate final local variable candidate. */ private static void determineAssignmentConditions(DetailAST ident, FinalVariableCandidate candidate) { if (candidate.assigned) { final int[] blockTypes = { TokenTypes.LITERAL_ELSE, TokenTypes.CASE_GROUP, TokenTypes.SWITCH_RULE, }; if (!isInSpecificCodeBlocks(ident, blockTypes)) { candidate.alreadyAssigned = true; } } else { candidate.assigned = true; } } /** * Checks whether the scope of a node is restricted to a specific code blocks. * * @param node node. * @param blockTypes int array of all block types to check. * @return true if the scope of a node is restricted to specific code block types. */ private static boolean isInSpecificCodeBlocks(DetailAST node, int... blockTypes) { boolean returnValue = false; for (int blockType : blockTypes) { for (DetailAST token = node.getParent(); token != null; token = token.getParent()) { final int type = token.getType(); if (type == blockType) { returnValue = true; break; } } } return returnValue; } /** * Gets final variable candidate for ast. * * @param ast ast. * @return Optional of {@link FinalVariableCandidate} for ast from scopeStack. */ private Optional getFinalCandidate(DetailAST ast) { Optional result = Optional.empty(); final Iterator iterator = scopeStack.descendingIterator(); while (iterator.hasNext() && result.isEmpty()) { final ScopeData scopeData = iterator.next(); result = scopeData.findFinalVariableCandidateForAst(ast); } return result; } /** * Store un-initialized variables in a temporary stack for future use. */ private void storePrevScopeUninitializedVariableData() { final ScopeData scopeData = scopeStack.peek(); final Deque prevScopeUninitializedVariableData = new ArrayDeque<>(); scopeData.uninitializedVariables.forEach(prevScopeUninitializedVariableData::push); prevScopeUninitializedVariables.push(prevScopeUninitializedVariableData); } /** * Update current scope data uninitialized variable according to the whole scope data. * * @param prevScopeUninitializedVariableData variable for previous stack of uninitialized * variables * @noinspection MethodParameterNamingConvention */ private void updateAllUninitializedVariables( Deque prevScopeUninitializedVariableData) { final boolean hasSomeScopes = !currentScopeAssignedVariables.isEmpty(); if (hasSomeScopes) { // Check for only previous scope updateUninitializedVariables(prevScopeUninitializedVariableData); // Check for rest of the scope prevScopeUninitializedVariables.forEach(this::updateUninitializedVariables); } } /** * Update current scope data uninitialized variable according to the specific scope data. * * @param scopeUninitializedVariableData variable for specific stack of uninitialized variables */ private void updateUninitializedVariables(Deque scopeUninitializedVariableData) { final Iterator iterator = currentScopeAssignedVariables.peek().iterator(); while (iterator.hasNext()) { final DetailAST assignedVariable = iterator.next(); boolean shouldRemove = false; for (DetailAST variable : scopeUninitializedVariableData) { for (ScopeData scopeData : scopeStack) { final FinalVariableCandidate candidate = scopeData.scope.get(variable.getText()); DetailAST storedVariable = null; if (candidate != null) { storedVariable = candidate.variableIdent; } if (storedVariable != null && isSameVariables(storedVariable, variable) && isSameVariables(assignedVariable, variable)) { scopeData.uninitializedVariables.push(variable); shouldRemove = true; } } } if (shouldRemove) { iterator.remove(); } } } /** * If token is LITERAL_IF and there is an {@code else} following or token is CASE_GROUP or * SWITCH_RULE and there is another {@code case} following, then update the * uninitialized variables. * * @param ast token to be checked * @return true if should be updated, else false */ private static boolean shouldUpdateUninitializedVariables(DetailAST ast) { return isIfTokenWithAnElseFollowing(ast) || isCaseTokenWithAnotherCaseFollowing(ast); } /** * If token is LITERAL_IF and there is an {@code else} following. * * @param ast token to be checked * @return true if token is LITERAL_IF and there is an {@code else} following, else false */ private static boolean isIfTokenWithAnElseFollowing(DetailAST ast) { return ast.getType() == TokenTypes.LITERAL_IF && ast.getLastChild().getType() == TokenTypes.LITERAL_ELSE; } /** * If token is CASE_GROUP or SWITCH_RULE and there is another {@code case} following. * * @param ast token to be checked * @return true if token is CASE_GROUP or SWITCH_RULE and there is another {@code case} * following, else false */ private static boolean isCaseTokenWithAnotherCaseFollowing(DetailAST ast) { boolean result = false; if (ast.getType() == TokenTypes.CASE_GROUP) { result = findLastChildWhichContainsSpecifiedToken( ast.getParent(), TokenTypes.CASE_GROUP, TokenTypes.SLIST) != ast; } else if (ast.getType() == TokenTypes.SWITCH_RULE) { result = ast.getNextSibling().getType() == TokenTypes.SWITCH_RULE; } return result; } /** * Returns the last child token that makes a specified type and contains containType in * its branch. * * @param ast token to be tested * @param childType the token type to match * @param containType the token type which has to be present in the branch * @return the matching token, or null if no match */ private static DetailAST findLastChildWhichContainsSpecifiedToken(DetailAST ast, int childType, int containType) { DetailAST returnValue = null; for (DetailAST astIterator = ast.getFirstChild(); astIterator != null; astIterator = astIterator.getNextSibling()) { if (astIterator.getType() == childType && astIterator.findFirstToken(containType) != null) { returnValue = astIterator; } } return returnValue; } /** * Determines whether enhanced for-loop variable should be checked or not. * * @param ast The ast to compare. * @return true if enhanced for-loop variable should be checked. */ private boolean shouldCheckEnhancedForLoopVariable(DetailAST ast) { return validateEnhancedForLoopVariable || ast.getParent().getType() != TokenTypes.FOR_EACH_CLAUSE; } /** * Insert a parameter at the topmost scope stack. * * @param ast the variable to insert. */ private void insertParameter(DetailAST ast) { final Map scope = scopeStack.peek().scope; final DetailAST astNode = ast.findFirstToken(TokenTypes.IDENT); scope.put(astNode.getText(), new FinalVariableCandidate(astNode)); } /** * Insert a variable at the topmost scope stack. * * @param ast the variable to insert. */ private void insertVariable(DetailAST ast) { final Map scope = scopeStack.peek().scope; final DetailAST astNode = ast.findFirstToken(TokenTypes.IDENT); final FinalVariableCandidate candidate = new FinalVariableCandidate(astNode); // for-each variables are implicitly assigned candidate.assigned = ast.getParent().getType() == TokenTypes.FOR_EACH_CLAUSE; scope.put(astNode.getText(), candidate); if (!isInitialized(astNode)) { scopeStack.peek().uninitializedVariables.add(astNode); } } /** * Check if VARIABLE_DEF is initialized or not. * * @param ast VARIABLE_DEF to be checked * @return true if initialized */ private static boolean isInitialized(DetailAST ast) { return ast.getParent().getLastChild().getType() == TokenTypes.ASSIGN; } /** * Whether the ast is the first child of its parent. * * @param ast the ast to check. * @return true if the ast is the first child of its parent. */ private static boolean isFirstChild(DetailAST ast) { return ast.getPreviousSibling() == null; } /** * Removes the final variable candidate from the Stack. * * @param ast variable to remove. */ private void removeFinalVariableCandidateFromStack(DetailAST ast) { final Iterator iterator = scopeStack.descendingIterator(); while (iterator.hasNext()) { final ScopeData scopeData = iterator.next(); final Map scope = scopeData.scope; final FinalVariableCandidate candidate = scope.get(ast.getText()); DetailAST storedVariable = null; if (candidate != null) { storedVariable = candidate.variableIdent; } if (storedVariable != null && isSameVariables(storedVariable, ast)) { if (shouldRemoveFinalVariableCandidate(scopeData, ast)) { scope.remove(ast.getText()); } break; } } } /** * Check if given parameter definition is a multiple type catch. * * @param parameterDefAst parameter definition * @return true if it is a multiple type catch, false otherwise */ private static boolean isMultipleTypeCatch(DetailAST parameterDefAst) { final DetailAST typeAst = parameterDefAst.findFirstToken(TokenTypes.TYPE); return typeAst.findFirstToken(TokenTypes.BOR) != null; } /** * Whether the final variable candidate should be removed from the list of final local variable * candidates. * * @param scopeData the scope data of the variable. * @param ast the variable ast. * @return true, if the variable should be removed. */ private static boolean shouldRemoveFinalVariableCandidate(ScopeData scopeData, DetailAST ast) { boolean shouldRemove = true; for (DetailAST variable : scopeData.uninitializedVariables) { if (variable.getText().equals(ast.getText())) { // if the variable is declared outside the loop and initialized inside // the loop, then it cannot be declared final, as it can be initialized // more than once in this case if (isInTheSameLoop(variable, ast) || !isUseOfExternalVariableInsideLoop(ast)) { final FinalVariableCandidate candidate = scopeData.scope.get(ast.getText()); shouldRemove = candidate.alreadyAssigned; } scopeData.uninitializedVariables.remove(variable); break; } } return shouldRemove; } /** * Checks whether a variable which is declared outside loop is used inside loop. * For example: *

* {@code * int x; * for (int i = 0, j = 0; i < j; i++) { * x = 5; * } * } *

* * @param variable variable. * @return true if a variable which is declared outside loop is used inside loop. */ private static boolean isUseOfExternalVariableInsideLoop(DetailAST variable) { DetailAST loop2 = variable.getParent(); while (loop2 != null && !isLoopAst(loop2.getType())) { loop2 = loop2.getParent(); } return loop2 != null; } /** * Is Arithmetic operator. * * @param parentType token AST * @return true is token type is in arithmetic operator */ private static boolean isAssignOperator(int parentType) { return Arrays.binarySearch(ASSIGN_OPERATOR_TYPES, parentType) >= 0; } /** * Checks if current variable is defined in * {@link TokenTypes#FOR_INIT for-loop init}, e.g.: *

* {@code * for (int i = 0, j = 0; i < j; i++) { . . . } * } *

* {@code i, j} are defined in {@link TokenTypes#FOR_INIT for-loop init} * * @param variableDef variable definition node. * @return true if variable is defined in {@link TokenTypes#FOR_INIT for-loop init} */ private static boolean isVariableInForInit(DetailAST variableDef) { return variableDef.getParent().getType() == TokenTypes.FOR_INIT; } /** * Determines whether an AST is a descendant of an abstract or native method. * * @param ast the AST to check. * @return true if ast is a descendant of an abstract or native method. */ private static boolean isInAbstractOrNativeMethod(DetailAST ast) { boolean abstractOrNative = false; DetailAST parent = ast.getParent(); while (parent != null && !abstractOrNative) { if (parent.getType() == TokenTypes.METHOD_DEF) { final DetailAST modifiers = parent.findFirstToken(TokenTypes.MODIFIERS); abstractOrNative = modifiers.findFirstToken(TokenTypes.ABSTRACT) != null || modifiers.findFirstToken(TokenTypes.LITERAL_NATIVE) != null; } parent = parent.getParent(); } return abstractOrNative; } /** * Check if current param is lambda's param. * * @param paramDef {@link TokenTypes#PARAMETER_DEF parameter def}. * @return true if current param is lambda's param. */ private static boolean isInLambda(DetailAST paramDef) { return paramDef.getParent().getParent().getType() == TokenTypes.LAMBDA; } /** * Find the Class, Constructor, Enum, Method, or Field in which it is defined. * * @param ast Variable for which we want to find the scope in which it is defined * @return ast The Class or Constructor or Method in which it is defined. */ private static DetailAST findFirstUpperNamedBlock(DetailAST ast) { DetailAST astTraverse = ast; while (!TokenUtil.isOfType(astTraverse, TokenTypes.METHOD_DEF, TokenTypes.CLASS_DEF, TokenTypes.ENUM_DEF, TokenTypes.CTOR_DEF, TokenTypes.COMPACT_CTOR_DEF) && !ScopeUtil.isClassFieldDef(astTraverse)) { astTraverse = astTraverse.getParent(); } return astTraverse; } /** * Check if both the Variables are same. * * @param ast1 Variable to compare * @param ast2 Variable to compare * @return true if both the variables are same, otherwise false */ private static boolean isSameVariables(DetailAST ast1, DetailAST ast2) { final DetailAST classOrMethodOfAst1 = findFirstUpperNamedBlock(ast1); final DetailAST classOrMethodOfAst2 = findFirstUpperNamedBlock(ast2); return classOrMethodOfAst1 == classOrMethodOfAst2 && ast1.getText().equals(ast2.getText()); } /** * Check if both the variables are in the same loop. * * @param ast1 variable to compare. * @param ast2 variable to compare. * @return true if both the variables are in the same loop. */ private static boolean isInTheSameLoop(DetailAST ast1, DetailAST ast2) { DetailAST loop1 = ast1.getParent(); while (loop1 != null && !isLoopAst(loop1.getType())) { loop1 = loop1.getParent(); } DetailAST loop2 = ast2.getParent(); while (loop2 != null && !isLoopAst(loop2.getType())) { loop2 = loop2.getParent(); } return loop1 != null && loop1 == loop2; } /** * Checks whether the ast is a loop. * * @param ast the ast to check. * @return true if the ast is a loop. */ private static boolean isLoopAst(int ast) { return Arrays.binarySearch(LOOP_TYPES, ast) >= 0; } /** * Holder for the scope data. */ private static class ScopeData { /** Contains variable definitions. */ private final Map scope = new HashMap<>(); /** Contains definitions of uninitialized variables. */ private final Deque uninitializedVariables = new ArrayDeque<>(); /** Whether there is a {@code break} in the scope. */ private boolean containsBreak; /** * Searches for final local variable candidate for ast in the scope. * * @param ast ast. * @return Optional of {@link FinalVariableCandidate}. */ public Optional findFinalVariableCandidateForAst(DetailAST ast) { Optional result = Optional.empty(); DetailAST storedVariable = null; final Optional candidate = Optional.ofNullable(scope.get(ast.getText())); if (candidate.isPresent()) { storedVariable = candidate.get().variableIdent; } if (storedVariable != null && isSameVariables(storedVariable, ast)) { result = candidate; } return result; } } /** Represents information about final local variable candidate. */ private static class FinalVariableCandidate { /** Identifier token. */ private final DetailAST variableIdent; /** Whether the variable is assigned. */ private boolean assigned; /** Whether the variable is already assigned. */ private boolean alreadyAssigned; /** * Creates new instance. * * @param variableIdent variable identifier. */ /* package */ FinalVariableCandidate(DetailAST variableIdent) { this.variableIdent = variableIdent; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy