View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2015 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  package com.puppycrawl.tools.checkstyle.checks.coding;
20  
21  import com.puppycrawl.tools.checkstyle.api.Check;
22  import com.puppycrawl.tools.checkstyle.api.DetailAST;
23  import com.puppycrawl.tools.checkstyle.api.FastStack;
24  import com.puppycrawl.tools.checkstyle.api.ScopeUtils;
25  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
26  import java.util.HashMap;
27  import java.util.Map;
28  
29  /**
30   * <p>
31   * Ensures that local variables that never get their values changed,
32   * must be declared final.
33   * </p>
34   * <p>
35   * An example of how to configure the check is:
36   * </p>
37   * <pre>
38   * &lt;module name="FinalLocalVariable"&gt;
39   *     &lt;property name="token" value="VARIABLE_DEF"/&gt;
40   * &lt;/module&gt;
41   * </pre>
42   * @author k_gibbs, r_auckenthaler
43   */
44  public class FinalLocalVariableCheck extends Check
45  {
46  
47      /**
48       * A key is pointing to the warning message text in "messages.properties"
49       * file.
50       */
51      public static final String MSG_KEY = "final.variable";
52  
53      /** Scope Stack */
54      private final FastStack<Map<String, DetailAST>> scopeStack =
55          FastStack.newInstance();
56  
57      @Override
58      public int[] getDefaultTokens()
59      {
60          return new int[] {
61              TokenTypes.IDENT,
62              TokenTypes.CTOR_DEF,
63              TokenTypes.METHOD_DEF,
64              TokenTypes.VARIABLE_DEF,
65              TokenTypes.INSTANCE_INIT,
66              TokenTypes.STATIC_INIT,
67              TokenTypes.LITERAL_FOR,
68              TokenTypes.SLIST,
69              TokenTypes.OBJBLOCK,
70          };
71      }
72  
73      @Override
74      public int[] getAcceptableTokens()
75      {
76          return new int[] {
77              TokenTypes.VARIABLE_DEF,
78              TokenTypes.PARAMETER_DEF,
79          };
80      }
81  
82      @Override
83      public int[] getRequiredTokens()
84      {
85          return new int[] {
86              TokenTypes.IDENT,
87              TokenTypes.CTOR_DEF,
88              TokenTypes.METHOD_DEF,
89              TokenTypes.INSTANCE_INIT,
90              TokenTypes.STATIC_INIT,
91              TokenTypes.LITERAL_FOR,
92              TokenTypes.SLIST,
93              TokenTypes.OBJBLOCK,
94          };
95      }
96  
97      @Override
98      public void visitToken(DetailAST ast)
99      {
100         switch (ast.getType()) {
101             case TokenTypes.OBJBLOCK:
102             case TokenTypes.SLIST:
103             case TokenTypes.LITERAL_FOR:
104             case TokenTypes.METHOD_DEF:
105             case TokenTypes.CTOR_DEF:
106             case TokenTypes.STATIC_INIT:
107             case TokenTypes.INSTANCE_INIT:
108                 scopeStack.push(new HashMap<String, DetailAST>());
109                 break;
110 
111             case TokenTypes.PARAMETER_DEF:
112                 if (ScopeUtils.inInterfaceBlock(ast)
113                     || inAbstractOrNativeMethod(ast))
114                 {
115                     break;
116                 }
117             case TokenTypes.VARIABLE_DEF:
118                 if ((ast.getParent().getType() != TokenTypes.OBJBLOCK)
119                     && (ast.getParent().getType() != TokenTypes.FOR_EACH_CLAUSE)
120                     && isVariableInForInit(ast))
121                 {
122                     insertVariable(ast);
123                 }
124                 break;
125 
126             case TokenTypes.IDENT:
127                 final int parentType = ast.getParent().getType();
128                 if ((TokenTypes.POST_DEC        == parentType)
129                     || (TokenTypes.DEC          == parentType)
130                     || (TokenTypes.POST_INC     == parentType)
131                     || (TokenTypes.INC          == parentType)
132                     || (TokenTypes.ASSIGN       == parentType)
133                     || (TokenTypes.PLUS_ASSIGN  == parentType)
134                     || (TokenTypes.MINUS_ASSIGN == parentType)
135                     || (TokenTypes.DIV_ASSIGN   == parentType)
136                     || (TokenTypes.STAR_ASSIGN  == parentType)
137                     || (TokenTypes.MOD_ASSIGN   == parentType)
138                     || (TokenTypes.SR_ASSIGN    == parentType)
139                     || (TokenTypes.BSR_ASSIGN   == parentType)
140                     || (TokenTypes.SL_ASSIGN    == parentType)
141                     || (TokenTypes.BXOR_ASSIGN  == parentType)
142                     || (TokenTypes.BOR_ASSIGN   == parentType)
143                     || (TokenTypes.BAND_ASSIGN  == parentType))
144                 {
145                     // TODO: is there better way to check is ast
146                     // in left part of assignment?
147                     if (ast.getParent().getFirstChild() == ast) {
148                         removeVariable(ast);
149                     }
150                 }
151                 break;
152 
153             default:
154         }
155     }
156 
157     /**
158      * Checks if current variable is defined in
159      *  {@link TokenTypes#FOR_INIT for-loop init}, e.g.:
160      * <p>
161      * <code>
162      * for (int i = 0, j = 0; i < j; i++) { . . . }
163      * </code>
164      * </p>
165      * <code>i, j</code> are defined in {@link TokenTypes#FOR_INIT for-loop init}
166      * @param variableDef variable definition node.
167      * @return true if variable is defined in {@link TokenTypes#FOR_INIT for-loop init}
168      */
169     private static boolean isVariableInForInit(DetailAST variableDef)
170     {
171         return variableDef.getParent().getType() != TokenTypes.FOR_INIT;
172     }
173 
174     /**
175      * Determines whether an AST is a descendant of an abstract or native method.
176      * @param ast the AST to check.
177      * @return true if ast is a descendant of an abstract or native method.
178      */
179     private static boolean inAbstractOrNativeMethod(DetailAST ast)
180     {
181         DetailAST parent = ast.getParent();
182         while (parent != null) {
183             if (parent.getType() == TokenTypes.METHOD_DEF) {
184                 final DetailAST modifiers =
185                     parent.findFirstToken(TokenTypes.MODIFIERS);
186                 return modifiers.branchContains(TokenTypes.ABSTRACT)
187                         || modifiers.branchContains(TokenTypes.LITERAL_NATIVE);
188             }
189             parent = parent.getParent();
190         }
191         return false;
192     }
193 
194     /**
195      * Inserts a variable at the topmost scope stack
196      * @param ast the variable to insert
197      */
198     private void insertVariable(DetailAST ast)
199     {
200         if (!ast.branchContains(TokenTypes.FINAL)) {
201             final Map<String, DetailAST> state = scopeStack.peek();
202             final DetailAST astNode = ast.findFirstToken(TokenTypes.IDENT);
203             state.put(astNode.getText(), astNode);
204         }
205     }
206 
207     /**
208      * Removes the variable from the Stacks
209      * @param ast Variable to remove
210      */
211     private void removeVariable(DetailAST ast)
212     {
213         for (int i = scopeStack.size() - 1; i >= 0; i--) {
214             final Map<String, DetailAST> state = scopeStack.peek(i);
215             final Object obj = state.remove(ast.getText());
216             if (obj != null) {
217                 break;
218             }
219         }
220     }
221 
222     @Override
223     public void leaveToken(DetailAST ast)
224     {
225         super.leaveToken(ast);
226 
227         switch (ast.getType()) {
228             case TokenTypes.OBJBLOCK:
229             case TokenTypes.SLIST:
230             case TokenTypes.LITERAL_FOR:
231             case TokenTypes.CTOR_DEF:
232             case TokenTypes.STATIC_INIT:
233             case TokenTypes.INSTANCE_INIT:
234             case TokenTypes.METHOD_DEF:
235                 final Map<String, DetailAST> state = scopeStack.pop();
236                 for (DetailAST var : state.values()) {
237                     log(var.getLineNo(), var.getColumnNo(), MSG_KEY, var
238                         .getText());
239                 }
240                 break;
241 
242             default:
243         }
244     }
245 }