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 }