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.DetailAST;
22  import com.puppycrawl.tools.checkstyle.api.FastStack;
23  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
24  import com.puppycrawl.tools.checkstyle.checks.AbstractFormatCheck;
25  
26  /**
27   * <p>
28   * Restricts return statements to a specified count (default = 2).
29   * Ignores specified methods (<code>equals()</code> by default).
30   * </p>
31   * <p>
32   * Rationale: Too many return points can be indication that code is
33   * attempting to do too much or may be difficult to understand.
34   * </p>
35   *
36   * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
37   * TODO: Test for inside a static block
38   */
39  public final class ReturnCountCheck extends AbstractFormatCheck
40  {
41  
42      /**
43       * A key is pointing to the warning message text in "messages.properties"
44       * file.
45       */
46      public static final String MSG_KEY = "return.count";
47  
48      /** Default allowed number of return statements. */
49      private static final int DEFAULT_MAX = 2;
50  
51      /** Stack of method contexts. */
52      private final FastStack<Context> contextStack = FastStack.newInstance();
53      /** Maximum allowed number of return stmts. */
54      private int max;
55      /** Current method context. */
56      private Context context;
57  
58      /** Creates new instance of the checks. */
59      public ReturnCountCheck()
60      {
61          super("^equals$");
62          setMax(DEFAULT_MAX);
63      }
64  
65      @Override
66      public int[] getDefaultTokens()
67      {
68          return new int[] {
69              TokenTypes.CTOR_DEF,
70              TokenTypes.METHOD_DEF,
71              TokenTypes.LITERAL_RETURN,
72          };
73      }
74  
75      @Override
76      public int[] getRequiredTokens()
77      {
78          return new int[]{
79              TokenTypes.CTOR_DEF,
80              TokenTypes.METHOD_DEF,
81          };
82      }
83  
84      @Override
85      public int[] getAcceptableTokens()
86      {
87          return new int[] {
88              TokenTypes.CTOR_DEF,
89              TokenTypes.METHOD_DEF,
90              TokenTypes.LITERAL_RETURN,
91          };
92      }
93  
94      /**
95       * Getter for max property.
96       * @return maximum allowed number of return statements.
97       */
98      public int getMax()
99      {
100         return max;
101     }
102 
103     /**
104      * Setter for max property.
105      * @param max maximum allowed number of return statements.
106      */
107     public void setMax(int max)
108     {
109         this.max = max;
110     }
111 
112     @Override
113     public void beginTree(DetailAST rootAST)
114     {
115         context = null;
116         contextStack.clear();
117     }
118 
119     @Override
120     public void visitToken(DetailAST ast)
121     {
122         switch (ast.getType()) {
123             case TokenTypes.CTOR_DEF:
124             case TokenTypes.METHOD_DEF:
125                 visitMethodDef(ast);
126                 break;
127             case TokenTypes.LITERAL_RETURN:
128                 context.visitLiteralReturn();
129                 break;
130             default:
131                 throw new IllegalStateException(ast.toString());
132         }
133     }
134 
135     @Override
136     public void leaveToken(DetailAST ast)
137     {
138         switch (ast.getType()) {
139             case TokenTypes.CTOR_DEF:
140             case TokenTypes.METHOD_DEF:
141                 leaveMethodDef(ast);
142                 break;
143             case TokenTypes.LITERAL_RETURN:
144                 // Do nothing
145                 break;
146             default:
147                 throw new IllegalStateException(ast.toString());
148         }
149     }
150 
151     /**
152      * Creates new method context and places old one on the stack.
153      * @param ast method definition for check.
154      */
155     private void visitMethodDef(DetailAST ast)
156     {
157         contextStack.push(context);
158         final DetailAST methodNameAST = ast.findFirstToken(TokenTypes.IDENT);
159         context =
160             new Context(!getRegexp().matcher(methodNameAST.getText()).find());
161     }
162 
163     /**
164      * Checks number of return statements and restore
165      * previous method context.
166      * @param ast method def node.
167      */
168     private void leaveMethodDef(DetailAST ast)
169     {
170         context.checkCount(ast);
171         context = contextStack.pop();
172     }
173 
174     /**
175      * Class to encapsulate information about one method.
176      * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a>
177      */
178     private class Context
179     {
180         /** Whether we should check this method or not. */
181         private final boolean checking;
182         /** Counter for return statements. */
183         private int count;
184 
185         /**
186          * Creates new method context.
187          * @param checking should we check this method or not.
188          */
189         public Context(boolean checking)
190         {
191             this.checking = checking;
192             count = 0;
193         }
194 
195         /** Increase number of return statements. */
196         public void visitLiteralReturn()
197         {
198             ++count;
199         }
200 
201         /**
202          * Checks if number of return statements in method more
203          * than allowed.
204          * @param ast method def associated with this context.
205          */
206         public void checkCount(DetailAST ast)
207         {
208             if (checking && (count > getMax())) {
209                 log(ast.getLineNo(), ast.getColumnNo(), MSG_KEY,
210                     count, getMax());
211             }
212         }
213     }
214 }