View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2014  Oliver Burn
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      /** Scope Stack */
47      private final FastStack<Map<String, DetailAST>> scopeStack =
48          FastStack.newInstance();
49  
50      @Override
51      public int[] getDefaultTokens()
52      {
53          return new int[] {
54              TokenTypes.IDENT,
55              TokenTypes.CTOR_DEF,
56              TokenTypes.METHOD_DEF,
57              TokenTypes.VARIABLE_DEF,
58              TokenTypes.INSTANCE_INIT,
59              TokenTypes.STATIC_INIT,
60              TokenTypes.LITERAL_FOR,
61              TokenTypes.SLIST,
62              TokenTypes.OBJBLOCK,
63          };
64      }
65  
66      @Override
67      public int[] getAcceptableTokens()
68      {
69          return new int[] {
70              TokenTypes.VARIABLE_DEF,
71              TokenTypes.PARAMETER_DEF,
72          };
73      }
74  
75      @Override
76      public int[] getRequiredTokens()
77      {
78          return new int[] {
79              TokenTypes.IDENT,
80              TokenTypes.CTOR_DEF,
81              TokenTypes.METHOD_DEF,
82              TokenTypes.INSTANCE_INIT,
83              TokenTypes.STATIC_INIT,
84              TokenTypes.LITERAL_FOR,
85              TokenTypes.SLIST,
86              TokenTypes.OBJBLOCK,
87          };
88      }
89  
90      @Override
91      public void visitToken(DetailAST ast)
92      {
93          switch (ast.getType()) {
94              case TokenTypes.OBJBLOCK:
95              case TokenTypes.SLIST:
96              case TokenTypes.LITERAL_FOR:
97              case TokenTypes.METHOD_DEF:
98              case TokenTypes.CTOR_DEF:
99              case TokenTypes.STATIC_INIT:
100             case TokenTypes.INSTANCE_INIT:
101                 scopeStack.push(new HashMap<String, DetailAST>());
102                 break;
103 
104             case TokenTypes.PARAMETER_DEF:
105                 if (ScopeUtils.inInterfaceBlock(ast)
106                     || inAbstractOrNativeMethod(ast))
107                 {
108                     break;
109                 }
110             case TokenTypes.VARIABLE_DEF:
111                 if ((ast.getParent().getType() != TokenTypes.OBJBLOCK)
112                     && (ast.getParent().getType() != TokenTypes.FOR_EACH_CLAUSE)
113                     && isFirstVariableInForInit(ast))
114                 {
115                     insertVariable(ast);
116                 }
117                 break;
118 
119             case TokenTypes.IDENT:
120                 final int parentType = ast.getParent().getType();
121                 if ((TokenTypes.POST_DEC        == parentType)
122                     || (TokenTypes.DEC          == parentType)
123                     || (TokenTypes.POST_INC     == parentType)
124                     || (TokenTypes.INC          == parentType)
125                     || (TokenTypes.ASSIGN       == parentType)
126                     || (TokenTypes.PLUS_ASSIGN  == parentType)
127                     || (TokenTypes.MINUS_ASSIGN == parentType)
128                     || (TokenTypes.DIV_ASSIGN   == parentType)
129                     || (TokenTypes.STAR_ASSIGN  == parentType)
130                     || (TokenTypes.MOD_ASSIGN   == parentType)
131                     || (TokenTypes.SR_ASSIGN    == parentType)
132                     || (TokenTypes.BSR_ASSIGN   == parentType)
133                     || (TokenTypes.SL_ASSIGN    == parentType)
134                     || (TokenTypes.BXOR_ASSIGN  == parentType)
135                     || (TokenTypes.BOR_ASSIGN   == parentType)
136                     || (TokenTypes.BAND_ASSIGN  == parentType))
137                 {
138                     // TODO: is there better way to check is ast
139                     // in left part of assignment?
140                     if (ast.getParent().getFirstChild() == ast) {
141                         removeVariable(ast);
142                     }
143                 }
144                 break;
145 
146             default:
147         }
148     }
149 
150     /**
151      * Checks if current variable is defined first in
152      *  {@link TokenTypes#FOR_INIT for-loop init}, e.g.:
153      * <p>
154      * <code>
155      * for (int i = 0, j = 0; i < j; i++) { . . . }
156      * </code>
157      * </p>
158      * <code>i</code> is first variable in {@link TokenTypes#FOR_INIT for-loop init}
159      * @param variableDef variable definition node.
160      * @return true if variableDef is first variable in {@link TokenTypes#FOR_INIT for-loop init}
161      */
162     private static boolean isFirstVariableInForInit(DetailAST variableDef)
163     {
164         return variableDef.getParent().getType() != TokenTypes.FOR_INIT
165                  || variableDef.getPreviousSibling() == null
166                  || variableDef.getPreviousSibling().getType() != TokenTypes.COMMA;
167     }
168 
169     /**
170      * Determines whether an AST is a descendant of an abstract or native method.
171      * @param ast the AST to check.
172      * @return true if ast is a descendant of an abstract or native method.
173      */
174     private static boolean inAbstractOrNativeMethod(DetailAST ast)
175     {
176         DetailAST parent = ast.getParent();
177         while (parent != null) {
178             if (parent.getType() == TokenTypes.METHOD_DEF) {
179                 final DetailAST modifiers =
180                     parent.findFirstToken(TokenTypes.MODIFIERS);
181                 return modifiers.branchContains(TokenTypes.ABSTRACT)
182                         || modifiers.branchContains(TokenTypes.LITERAL_NATIVE);
183             }
184             parent = parent.getParent();
185         }
186         return false;
187     }
188 
189     /**
190      * Inserts a variable at the topmost scope stack
191      * @param ast the variable to insert
192      */
193     private void insertVariable(DetailAST ast)
194     {
195         if (!ast.branchContains(TokenTypes.FINAL)) {
196             final Map<String, DetailAST> state = scopeStack.peek();
197             final DetailAST astNode = ast.findFirstToken(TokenTypes.IDENT);
198             state.put(astNode.getText(), astNode);
199         }
200     }
201 
202     /**
203      * Removes the variable from the Stacks
204      * @param ast Variable to remove
205      */
206     private void removeVariable(DetailAST ast)
207     {
208         for (int i = scopeStack.size() - 1; i >= 0; i--) {
209             final Map<String, DetailAST> state = scopeStack.peek(i);
210             final Object obj = state.remove(ast.getText());
211             if (obj != null) {
212                 break;
213             }
214         }
215     }
216 
217     @Override
218     public void leaveToken(DetailAST ast)
219     {
220         super.leaveToken(ast);
221 
222         switch (ast.getType()) {
223             case TokenTypes.OBJBLOCK:
224             case TokenTypes.SLIST:
225             case TokenTypes.LITERAL_FOR:
226             case TokenTypes.CTOR_DEF:
227             case TokenTypes.STATIC_INIT:
228             case TokenTypes.INSTANCE_INIT:
229             case TokenTypes.METHOD_DEF:
230                 final Map<String, DetailAST> state = scopeStack.pop();
231                 for (DetailAST var : state.values()) {
232                     log(var.getLineNo(), var.getColumnNo(), "final.variable", var
233                         .getText());
234                 }
235                 break;
236 
237             default:
238         }
239     }
240 }