1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 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  
20  package com.puppycrawl.tools.checkstyle.checks.sizes;
21  
22  import java.util.ArrayDeque;
23  import java.util.Deque;
24  
25  import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
26  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
27  import com.puppycrawl.tools.checkstyle.api.DetailAST;
28  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
29  
30  /**
31   * Restricts the number of executable statements to a specified limit
32   * (default = 30).
33   */
34  @FileStatefulCheck
35  public final class ExecutableStatementCountCheck
36      extends AbstractCheck {
37  
38      /**
39       * A key is pointing to the warning message text in "messages.properties"
40       * file.
41       */
42      public static final String MSG_KEY = "executableStatementCount";
43  
44      /** Default threshold. */
45      private static final int DEFAULT_MAX = 30;
46  
47      /** Stack of method contexts. */
48      private final Deque<Context> contextStack = new ArrayDeque<>();
49  
50      /** Threshold to report error for. */
51      private int max;
52  
53      /** Current method context. */
54      private Context context;
55  
56      /** Constructs a {@code ExecutableStatementCountCheck}. */
57      public ExecutableStatementCountCheck() {
58          max = DEFAULT_MAX;
59      }
60  
61      @Override
62      public int[] getDefaultTokens() {
63          return new int[] {
64              TokenTypes.CTOR_DEF,
65              TokenTypes.METHOD_DEF,
66              TokenTypes.INSTANCE_INIT,
67              TokenTypes.STATIC_INIT,
68              TokenTypes.SLIST,
69          };
70      }
71  
72      @Override
73      public int[] getRequiredTokens() {
74          return new int[] {TokenTypes.SLIST};
75      }
76  
77      @Override
78      public int[] getAcceptableTokens() {
79          return new int[] {
80              TokenTypes.CTOR_DEF,
81              TokenTypes.METHOD_DEF,
82              TokenTypes.INSTANCE_INIT,
83              TokenTypes.STATIC_INIT,
84              TokenTypes.SLIST,
85          };
86      }
87  
88      /**
89       * Sets the maximum threshold.
90       * @param max the maximum threshold.
91       */
92      public void setMax(int max) {
93          this.max = max;
94      }
95  
96      @Override
97      public void beginTree(DetailAST rootAST) {
98          context = new Context(null);
99          contextStack.clear();
100     }
101 
102     @Override
103     public void visitToken(DetailAST ast) {
104         switch (ast.getType()) {
105             case TokenTypes.CTOR_DEF:
106             case TokenTypes.METHOD_DEF:
107             case TokenTypes.INSTANCE_INIT:
108             case TokenTypes.STATIC_INIT:
109                 visitMemberDef(ast);
110                 break;
111             case TokenTypes.SLIST:
112                 visitSlist(ast);
113                 break;
114             default:
115                 throw new IllegalStateException(ast.toString());
116         }
117     }
118 
119     @Override
120     public void leaveToken(DetailAST ast) {
121         switch (ast.getType()) {
122             case TokenTypes.CTOR_DEF:
123             case TokenTypes.METHOD_DEF:
124             case TokenTypes.INSTANCE_INIT:
125             case TokenTypes.STATIC_INIT:
126                 leaveMemberDef(ast);
127                 break;
128             case TokenTypes.SLIST:
129                 // Do nothing
130                 break;
131             default:
132                 throw new IllegalStateException(ast.toString());
133         }
134     }
135 
136     /**
137      * Process the start of the member definition.
138      * @param ast the token representing the member definition.
139      */
140     private void visitMemberDef(DetailAST ast) {
141         contextStack.push(context);
142         context = new Context(ast);
143     }
144 
145     /**
146      * Process the end of a member definition.
147      *
148      * @param ast the token representing the member definition.
149      */
150     private void leaveMemberDef(DetailAST ast) {
151         final int count = context.getCount();
152         if (count > max) {
153             log(ast, MSG_KEY, count, max);
154         }
155         context = contextStack.pop();
156     }
157 
158     /**
159      * Process the end of a statement list.
160      *
161      * @param ast the token representing the statement list.
162      */
163     private void visitSlist(DetailAST ast) {
164         if (context.getAST() != null) {
165             // find member AST for the statement list
166             final DetailAST contextAST = context.getAST();
167             DetailAST parent = ast.getParent();
168             int type = parent.getType();
169             while (type != TokenTypes.CTOR_DEF
170                 && type != TokenTypes.METHOD_DEF
171                 && type != TokenTypes.INSTANCE_INIT
172                 && type != TokenTypes.STATIC_INIT) {
173                 parent = parent.getParent();
174                 type = parent.getType();
175             }
176             if (parent == contextAST) {
177                 context.addCount(ast.getChildCount() / 2);
178             }
179         }
180     }
181 
182     /**
183      * Class to encapsulate counting information about one member.
184      */
185     private static class Context {
186 
187         /** Member AST node. */
188         private final DetailAST ast;
189 
190         /** Counter for context elements. */
191         private int count;
192 
193         /**
194          * Creates new member context.
195          * @param ast member AST node.
196          */
197         /* package */ Context(DetailAST ast) {
198             this.ast = ast;
199             count = 0;
200         }
201 
202         /**
203          * Increase count.
204          * @param addition the count increment.
205          */
206         public void addCount(int addition) {
207             count += addition;
208         }
209 
210         /**
211          * Gets the member AST node.
212          * @return the member AST node.
213          */
214         public DetailAST getAST() {
215             return ast;
216         }
217 
218         /**
219          * Gets the count.
220          * @return the count.
221          */
222         public int getCount() {
223             return count;
224         }
225 
226     }
227 
228 }