1 | //////////////////////////////////////////////////////////////////////////////// | |
2 | // checkstyle: Checks Java source code for adherence to a set of rules. | |
3 | // Copyright (C) 2001-2017 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.metrics; | |
21 | ||
22 | import java.util.ArrayDeque; | |
23 | import java.util.Deque; | |
24 | ||
25 | import com.puppycrawl.tools.checkstyle.api.AbstractCheck; | |
26 | import com.puppycrawl.tools.checkstyle.api.DetailAST; | |
27 | import com.puppycrawl.tools.checkstyle.api.TokenTypes; | |
28 | import com.puppycrawl.tools.checkstyle.utils.CheckUtils; | |
29 | ||
30 | /** | |
31 | * Restricts nested boolean operators (&&, ||, &, | and ^) to | |
32 | * a specified depth (default = 3). | |
33 | * Note: &, | and ^ are not checked if they are part of constructor or | |
34 | * method call because they can be applied to non boolean variables and | |
35 | * Checkstyle does not know types of methods from different classes. | |
36 | * | |
37 | * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a> | |
38 | * @author o_sukhodolsky | |
39 | */ | |
40 | public final class BooleanExpressionComplexityCheck extends AbstractCheck { | |
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 = "booleanExpressionComplexity"; | |
47 | ||
48 | /** Default allowed complexity. */ | |
49 | private static final int DEFAULT_MAX = 3; | |
50 | ||
51 | /** Stack of contexts. */ | |
52 | private final Deque<Context> contextStack = new ArrayDeque<>(); | |
53 | /** Maximum allowed complexity. */ | |
54 | private int max; | |
55 | /** Current context. */ | |
56 | private Context context = new Context(false); | |
57 | ||
58 | /** Creates new instance of the check. */ | |
59 | public BooleanExpressionComplexityCheck() { | |
60 | max = DEFAULT_MAX; | |
61 | } | |
62 | ||
63 | @Override | |
64 | public int[] getDefaultTokens() { | |
65 |
1
1. getDefaultTokens : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/metrics/BooleanExpressionComplexityCheck::getDefaultTokens to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return new int[] { |
66 | TokenTypes.CTOR_DEF, | |
67 | TokenTypes.METHOD_DEF, | |
68 | TokenTypes.EXPR, | |
69 | TokenTypes.LAND, | |
70 | TokenTypes.BAND, | |
71 | TokenTypes.LOR, | |
72 | TokenTypes.BOR, | |
73 | TokenTypes.BXOR, | |
74 | }; | |
75 | } | |
76 | ||
77 | @Override | |
78 | public int[] getRequiredTokens() { | |
79 |
1
1. getRequiredTokens : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/metrics/BooleanExpressionComplexityCheck::getRequiredTokens to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return new int[] { |
80 | TokenTypes.CTOR_DEF, | |
81 | TokenTypes.METHOD_DEF, | |
82 | TokenTypes.EXPR, | |
83 | }; | |
84 | } | |
85 | ||
86 | @Override | |
87 | public int[] getAcceptableTokens() { | |
88 |
1
1. getAcceptableTokens : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/metrics/BooleanExpressionComplexityCheck::getAcceptableTokens to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return new int[] { |
89 | TokenTypes.CTOR_DEF, | |
90 | TokenTypes.METHOD_DEF, | |
91 | TokenTypes.EXPR, | |
92 | TokenTypes.LAND, | |
93 | TokenTypes.BAND, | |
94 | TokenTypes.LOR, | |
95 | TokenTypes.BOR, | |
96 | TokenTypes.BXOR, | |
97 | }; | |
98 | } | |
99 | ||
100 | /** | |
101 | * Setter for maximum allowed complexity. | |
102 | * @param max new maximum allowed complexity. | |
103 | */ | |
104 | public void setMax(int max) { | |
105 | this.max = max; | |
106 | } | |
107 | ||
108 | @Override | |
109 | public void visitToken(DetailAST ast) { | |
110 | switch (ast.getType()) { | |
111 | case TokenTypes.CTOR_DEF: | |
112 | case TokenTypes.METHOD_DEF: | |
113 |
1
1. visitToken : removed call to com/puppycrawl/tools/checkstyle/checks/metrics/BooleanExpressionComplexityCheck::visitMethodDef → KILLED |
visitMethodDef(ast); |
114 | break; | |
115 | case TokenTypes.EXPR: | |
116 |
1
1. visitToken : removed call to com/puppycrawl/tools/checkstyle/checks/metrics/BooleanExpressionComplexityCheck::visitExpr → KILLED |
visitExpr(); |
117 | break; | |
118 | case TokenTypes.BOR: | |
119 |
2
1. visitToken : negated conditional → KILLED 2. visitToken : negated conditional → KILLED |
if (!isPipeOperator(ast) && !isPassedInParameter(ast)) { |
120 |
1
1. visitToken : removed call to com/puppycrawl/tools/checkstyle/checks/metrics/BooleanExpressionComplexityCheck$Context::visitBooleanOperator → KILLED |
context.visitBooleanOperator(); |
121 | } | |
122 | break; | |
123 | case TokenTypes.BAND: | |
124 | case TokenTypes.BXOR: | |
125 |
1
1. visitToken : negated conditional → KILLED |
if (!isPassedInParameter(ast)) { |
126 |
1
1. visitToken : removed call to com/puppycrawl/tools/checkstyle/checks/metrics/BooleanExpressionComplexityCheck$Context::visitBooleanOperator → KILLED |
context.visitBooleanOperator(); |
127 | } | |
128 | break; | |
129 | case TokenTypes.LAND: | |
130 | case TokenTypes.LOR: | |
131 |
1
1. visitToken : removed call to com/puppycrawl/tools/checkstyle/checks/metrics/BooleanExpressionComplexityCheck$Context::visitBooleanOperator → KILLED |
context.visitBooleanOperator(); |
132 | break; | |
133 | default: | |
134 | throw new IllegalArgumentException("Unknown type: " + ast); | |
135 | } | |
136 | } | |
137 | ||
138 | /** | |
139 | * Checks if logical operator is part of constructor or method call. | |
140 | * @param logicalOperator logical operator | |
141 | * @return true if logical operator is part of constructor or method call | |
142 | */ | |
143 | private static boolean isPassedInParameter(DetailAST logicalOperator) { | |
144 |
2
1. isPassedInParameter : negated conditional → KILLED 2. isPassedInParameter : replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED |
return logicalOperator.getParent().getType() == TokenTypes.EXPR |
145 |
1
1. isPassedInParameter : negated conditional → KILLED |
&& logicalOperator.getParent().getParent().getType() == TokenTypes.ELIST; |
146 | } | |
147 | ||
148 | /** | |
149 | * Checks if {@link TokenTypes#BOR binary OR} is applied to exceptions | |
150 | * in | |
151 | * <a href="http://docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.20"> | |
152 | * multi-catch</a> (pipe-syntax). | |
153 | * @param binaryOr {@link TokenTypes#BOR binary or} | |
154 | * @return true if binary or is applied to exceptions in multi-catch. | |
155 | */ | |
156 | private static boolean isPipeOperator(DetailAST binaryOr) { | |
157 |
2
1. isPipeOperator : negated conditional → KILLED 2. isPipeOperator : replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED |
return binaryOr.getParent().getType() == TokenTypes.TYPE; |
158 | } | |
159 | ||
160 | @Override | |
161 | public void leaveToken(DetailAST ast) { | |
162 | switch (ast.getType()) { | |
163 | case TokenTypes.CTOR_DEF: | |
164 | case TokenTypes.METHOD_DEF: | |
165 |
1
1. leaveToken : removed call to com/puppycrawl/tools/checkstyle/checks/metrics/BooleanExpressionComplexityCheck::leaveMethodDef → SURVIVED |
leaveMethodDef(); |
166 | break; | |
167 | case TokenTypes.EXPR: | |
168 |
1
1. leaveToken : removed call to com/puppycrawl/tools/checkstyle/checks/metrics/BooleanExpressionComplexityCheck::leaveExpr → KILLED |
leaveExpr(ast); |
169 | break; | |
170 | default: | |
171 | // Do nothing | |
172 | } | |
173 | } | |
174 | ||
175 | /** | |
176 | * Creates new context for a given method. | |
177 | * @param ast a method we start to check. | |
178 | */ | |
179 | private void visitMethodDef(DetailAST ast) { | |
180 |
1
1. visitMethodDef : removed call to java/util/Deque::push → KILLED |
contextStack.push(context); |
181 |
1
1. visitMethodDef : negated conditional → KILLED |
final boolean check = !CheckUtils.isEqualsMethod(ast); |
182 | context = new Context(check); | |
183 | } | |
184 | ||
185 | /** Removes old context. */ | |
186 | private void leaveMethodDef() { | |
187 | context = contextStack.pop(); | |
188 | } | |
189 | ||
190 | /** Creates and pushes new context. */ | |
191 | private void visitExpr() { | |
192 |
1
1. visitExpr : removed call to java/util/Deque::push → KILLED |
contextStack.push(context); |
193 | context = new Context(context.isChecking()); | |
194 | } | |
195 | ||
196 | /** | |
197 | * Restores previous context. | |
198 | * @param ast expression we leave. | |
199 | */ | |
200 | private void leaveExpr(DetailAST ast) { | |
201 |
1
1. leaveExpr : removed call to com/puppycrawl/tools/checkstyle/checks/metrics/BooleanExpressionComplexityCheck$Context::checkCount → KILLED |
context.checkCount(ast); |
202 | context = contextStack.pop(); | |
203 | } | |
204 | ||
205 | /** | |
206 | * Represents context (method/expression) in which we check complexity. | |
207 | * | |
208 | * @author <a href="mailto:simon@redhillconsulting.com.au">Simon Harris</a> | |
209 | * @author o_sukhodolsky | |
210 | */ | |
211 | private class Context { | |
212 | /** | |
213 | * Should we perform check in current context or not. | |
214 | * Usually false if we are inside equals() method. | |
215 | */ | |
216 | private final boolean checking; | |
217 | /** Count of boolean operators. */ | |
218 | private int count; | |
219 | ||
220 | /** | |
221 | * Creates new instance. | |
222 | * @param checking should we check in current context or not. | |
223 | */ | |
224 | Context(boolean checking) { | |
225 | this.checking = checking; | |
226 | count = 0; | |
227 | } | |
228 | ||
229 | /** | |
230 | * Getter for checking property. | |
231 | * @return should we check in current context or not. | |
232 | */ | |
233 | public boolean isChecking() { | |
234 |
1
1. isChecking : replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED |
return checking; |
235 | } | |
236 | ||
237 | /** Increases operator counter. */ | |
238 | public void visitBooleanOperator() { | |
239 |
1
1. visitBooleanOperator : Replaced integer addition with subtraction → KILLED |
++count; |
240 | } | |
241 | ||
242 | /** | |
243 | * Checks if we violates maximum allowed complexity. | |
244 | * @param ast a node we check now. | |
245 | */ | |
246 | public void checkCount(DetailAST ast) { | |
247 |
3
1. checkCount : changed conditional boundary → KILLED 2. checkCount : negated conditional → KILLED 3. checkCount : negated conditional → KILLED |
if (checking && count > max) { |
248 | final DetailAST parentAST = ast.getParent(); | |
249 | ||
250 |
1
1. checkCount : removed call to com/puppycrawl/tools/checkstyle/checks/metrics/BooleanExpressionComplexityCheck::log → KILLED |
log(parentAST.getLineNo(), parentAST.getColumnNo(), |
251 | MSG_KEY, count, max); | |
252 | } | |
253 | } | |
254 | } | |
255 | } | |
Mutations | ||
65 |
1.1 |
|
79 |
1.1 |
|
88 |
1.1 |
|
113 |
1.1 |
|
116 |
1.1 |
|
119 |
1.1 2.2 |
|
120 |
1.1 |
|
125 |
1.1 |
|
126 |
1.1 |
|
131 |
1.1 |
|
144 |
1.1 2.2 |
|
145 |
1.1 |
|
157 |
1.1 2.2 |
|
165 |
1.1 |
|
168 |
1.1 |
|
180 |
1.1 |
|
181 |
1.1 |
|
192 |
1.1 |
|
201 |
1.1 |
|
234 |
1.1 |
|
239 |
1.1 |
|
247 |
1.1 2.2 3.3 |
|
250 |
1.1 |