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   * <p>
43   * By default, this Check skip final validation on
44   *  <a href = "http://docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.14.2">
45   * Enhanced For-Loop</a>
46   * </p>
47   * <p>
48   * Option 'validateEnhancedForLoopVariable' could be used to make Check to validate even variable
49   *  from Enhanced For Loop.
50   * </p>
51   * <p>
52   * An example of how to configure the check so that it also validates enhanced For Loop Variable is:
53   * </p>
54   * <pre>
55   * &lt;module name="FinalLocalVariable"&gt;
56   *     &lt;property name="token" value="VARIABLE_DEF"/&gt;
57   *     &lt;property name="validateEnhancedForLoopVariable" value="true"/&gt;
58   * &lt;/module&gt;
59   * </pre>
60   * <p>Example:</p>
61   * <p>
62   * <code>
63   * for (int number : myNumbers) { // violation
64   *    System.out.println(number);
65   * }
66   * </code>
67   * </p>
68   * @author k_gibbs, r_auckenthaler
69   */
70  public class FinalLocalVariableCheck extends Check
71  {
72  
73      /**
74       * A key is pointing to the warning message text in "messages.properties"
75       * file.
76       */
77      public static final String MSG_KEY = "final.variable";
78  
79      /** Scope Stack */
80      private final FastStack<Map<String, DetailAST>> scopeStack =
81          FastStack.newInstance();
82  
83      /** Controls whether to check enhanced for-loop variable. */
84      private boolean validateEnhancedForLoopVariable;
85  
86      /**
87       * Whether to check enhanced for-loop variable or not.
88       * @param validateEnhancedForLoopVariable whether to check for-loop variable
89       */
90      public final void setValidateEnhancedForLoopVariable(boolean validateEnhancedForLoopVariable)
91      {
92          this.validateEnhancedForLoopVariable = validateEnhancedForLoopVariable;
93      }
94  
95      @Override
96      public int[] getDefaultTokens()
97      {
98          return new int[] {
99              TokenTypes.IDENT,
100             TokenTypes.CTOR_DEF,
101             TokenTypes.METHOD_DEF,
102             TokenTypes.VARIABLE_DEF,
103             TokenTypes.INSTANCE_INIT,
104             TokenTypes.STATIC_INIT,
105             TokenTypes.LITERAL_FOR,
106             TokenTypes.SLIST,
107             TokenTypes.OBJBLOCK,
108         };
109     }
110 
111     @Override
112     public int[] getAcceptableTokens()
113     {
114         return new int[] {
115             TokenTypes.IDENT,
116             TokenTypes.CTOR_DEF,
117             TokenTypes.METHOD_DEF,
118             TokenTypes.VARIABLE_DEF,
119             TokenTypes.INSTANCE_INIT,
120             TokenTypes.STATIC_INIT,
121             TokenTypes.LITERAL_FOR,
122             TokenTypes.SLIST,
123             TokenTypes.OBJBLOCK,
124             TokenTypes.PARAMETER_DEF,
125         };
126     }
127 
128     @Override
129     public int[] getRequiredTokens()
130     {
131         return new int[] {
132             TokenTypes.IDENT,
133             TokenTypes.CTOR_DEF,
134             TokenTypes.METHOD_DEF,
135             TokenTypes.INSTANCE_INIT,
136             TokenTypes.STATIC_INIT,
137             TokenTypes.LITERAL_FOR,
138             TokenTypes.SLIST,
139             TokenTypes.OBJBLOCK,
140         };
141     }
142 
143     @Override
144     public void visitToken(DetailAST ast)
145     {
146         switch (ast.getType()) {
147             case TokenTypes.OBJBLOCK:
148             case TokenTypes.SLIST:
149             case TokenTypes.LITERAL_FOR:
150             case TokenTypes.METHOD_DEF:
151             case TokenTypes.CTOR_DEF:
152             case TokenTypes.STATIC_INIT:
153             case TokenTypes.INSTANCE_INIT:
154                 scopeStack.push(new HashMap<String, DetailAST>());
155                 break;
156 
157             case TokenTypes.PARAMETER_DEF:
158                 if (ScopeUtils.inInterfaceBlock(ast)
159                     || inAbstractOrNativeMethod(ast))
160                 {
161                     break;
162                 }
163             case TokenTypes.VARIABLE_DEF:
164                 if (ast.getParent().getType() != TokenTypes.OBJBLOCK
165                     && shouldCheckEnhancedForLoopVariable(ast)
166                     && isVariableInForInit(ast))
167                 {
168                     insertVariable(ast);
169                 }
170                 break;
171 
172             case TokenTypes.IDENT:
173                 final int parentType = ast.getParent().getType();
174                 if (TokenTypes.POST_DEC        == parentType
175                     || TokenTypes.DEC          == parentType
176                     || TokenTypes.POST_INC     == parentType
177                     || TokenTypes.INC          == parentType
178                     || TokenTypes.ASSIGN       == parentType
179                     || TokenTypes.PLUS_ASSIGN  == parentType
180                     || TokenTypes.MINUS_ASSIGN == parentType
181                     || TokenTypes.DIV_ASSIGN   == parentType
182                     || TokenTypes.STAR_ASSIGN  == parentType
183                     || TokenTypes.MOD_ASSIGN   == parentType
184                     || TokenTypes.SR_ASSIGN    == parentType
185                     || TokenTypes.BSR_ASSIGN   == parentType
186                     || TokenTypes.SL_ASSIGN    == parentType
187                     || TokenTypes.BXOR_ASSIGN  == parentType
188                     || TokenTypes.BOR_ASSIGN   == parentType
189                     || TokenTypes.BAND_ASSIGN  == parentType)
190                 {
191                     // TODO: is there better way to check is ast
192                     // in left part of assignment?
193                     if (ast.getParent().getFirstChild() == ast) {
194                         removeVariable(ast);
195                     }
196                 }
197                 break;
198 
199             default:
200         }
201     }
202 
203     /**
204      * Determines whether enhanced for-loop variable should be checked or not.
205      * @param ast The ast to compare.
206      * @return true if enhanced for-loop variable should be checked.
207      */
208     private boolean shouldCheckEnhancedForLoopVariable(DetailAST ast)
209     {
210         return validateEnhancedForLoopVariable
211                 || ast.getParent().getType() != TokenTypes.FOR_EACH_CLAUSE;
212     }
213 
214     /**
215      * Checks if current variable is defined in
216      *  {@link TokenTypes#FOR_INIT for-loop init}, e.g.:
217      * <p>
218      * <code>
219      * for (int i = 0, j = 0; i < j; i++) { . . . }
220      * </code>
221      * </p>
222      * <code>i, j</code> are defined in {@link TokenTypes#FOR_INIT for-loop init}
223      * @param variableDef variable definition node.
224      * @return true if variable is defined in {@link TokenTypes#FOR_INIT for-loop init}
225      */
226     private static boolean isVariableInForInit(DetailAST variableDef)
227     {
228         return variableDef.getParent().getType() != TokenTypes.FOR_INIT;
229     }
230 
231     /**
232      * Determines whether an AST is a descendant of an abstract or native method.
233      * @param ast the AST to check.
234      * @return true if ast is a descendant of an abstract or native method.
235      */
236     private static boolean inAbstractOrNativeMethod(DetailAST ast)
237     {
238         DetailAST parent = ast.getParent();
239         while (parent != null) {
240             if (parent.getType() == TokenTypes.METHOD_DEF) {
241                 final DetailAST modifiers =
242                     parent.findFirstToken(TokenTypes.MODIFIERS);
243                 return modifiers.branchContains(TokenTypes.ABSTRACT)
244                         || modifiers.branchContains(TokenTypes.LITERAL_NATIVE);
245             }
246             parent = parent.getParent();
247         }
248         return false;
249     }
250 
251     /**
252      * Inserts a variable at the topmost scope stack
253      * @param ast the variable to insert
254      */
255     private void insertVariable(DetailAST ast)
256     {
257         if (!ast.branchContains(TokenTypes.FINAL)) {
258             final Map<String, DetailAST> state = scopeStack.peek();
259             final DetailAST astNode = ast.findFirstToken(TokenTypes.IDENT);
260             state.put(astNode.getText(), astNode);
261         }
262     }
263 
264     /**
265      * Removes the variable from the Stacks
266      * @param ast Variable to remove
267      */
268     private void removeVariable(DetailAST ast)
269     {
270         for (int i = scopeStack.size() - 1; i >= 0; i--) {
271             final Map<String, DetailAST> state = scopeStack.peek(i);
272             final Object obj = state.remove(ast.getText());
273             if (obj != null) {
274                 break;
275             }
276         }
277     }
278 
279     @Override
280     public void leaveToken(DetailAST ast)
281     {
282         super.leaveToken(ast);
283 
284         switch (ast.getType()) {
285             case TokenTypes.OBJBLOCK:
286             case TokenTypes.SLIST:
287             case TokenTypes.LITERAL_FOR:
288             case TokenTypes.CTOR_DEF:
289             case TokenTypes.STATIC_INIT:
290             case TokenTypes.INSTANCE_INIT:
291             case TokenTypes.METHOD_DEF:
292                 final Map<String, DetailAST> state = scopeStack.pop();
293                 for (DetailAST var : state.values()) {
294                     log(var.getLineNo(), var.getColumnNo(), MSG_KEY, var
295                         .getText());
296                 }
297                 break;
298 
299             default:
300         }
301     }
302 }