1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.puppycrawl.tools.checkstyle.checks.coding;
21
22 import java.util.Arrays;
23 import java.util.BitSet;
24
25 import com.puppycrawl.tools.checkstyle.PropertyType;
26 import com.puppycrawl.tools.checkstyle.StatelessCheck;
27 import com.puppycrawl.tools.checkstyle.XdocsPropertyType;
28 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
29 import com.puppycrawl.tools.checkstyle.api.DetailAST;
30 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
31 import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
32 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
33 import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
34 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59 @StatelessCheck
60 public class MagicNumberCheck extends AbstractCheck {
61
62
63
64
65
66 public static final String MSG_KEY = "magic.number";
67
68
69
70
71
72 @XdocsPropertyType(PropertyType.TOKEN_ARRAY)
73 private BitSet constantWaiverParentToken = TokenUtil.asBitSet(
74 TokenTypes.ASSIGN,
75 TokenTypes.ARRAY_INIT,
76 TokenTypes.EXPR,
77 TokenTypes.UNARY_PLUS,
78 TokenTypes.UNARY_MINUS,
79 TokenTypes.TYPECAST,
80 TokenTypes.ELIST,
81 TokenTypes.LITERAL_NEW,
82 TokenTypes.METHOD_CALL,
83 TokenTypes.STAR,
84 TokenTypes.DIV,
85 TokenTypes.PLUS,
86 TokenTypes.MINUS,
87 TokenTypes.QUESTION,
88 TokenTypes.COLON,
89 TokenTypes.EQUAL,
90 TokenTypes.NOT_EQUAL,
91 TokenTypes.MOD,
92 TokenTypes.SR,
93 TokenTypes.BSR,
94 TokenTypes.GE,
95 TokenTypes.GT,
96 TokenTypes.SL,
97 TokenTypes.LE,
98 TokenTypes.LT,
99 TokenTypes.BXOR,
100 TokenTypes.BOR,
101 TokenTypes.BNOT,
102 TokenTypes.BAND
103 );
104
105
106 private double[] ignoreNumbers = {-1, 0, 1, 2};
107
108
109 private boolean ignoreHashCodeMethod;
110
111
112 private boolean ignoreAnnotation;
113
114
115 private boolean ignoreFieldDeclaration;
116
117
118 private boolean ignoreAnnotationElementDefaults = true;
119
120 @Override
121 public int[] getDefaultTokens() {
122 return getAcceptableTokens();
123 }
124
125 @Override
126 public int[] getAcceptableTokens() {
127 return new int[] {
128 TokenTypes.NUM_DOUBLE,
129 TokenTypes.NUM_FLOAT,
130 TokenTypes.NUM_INT,
131 TokenTypes.NUM_LONG,
132 };
133 }
134
135 @Override
136 public int[] getRequiredTokens() {
137 return CommonUtil.EMPTY_INT_ARRAY;
138 }
139
140 @Override
141 public void visitToken(DetailAST ast) {
142 if (shouldTestAnnotationArgs(ast)
143 && shouldTestAnnotationDefaults(ast)
144 && !isInIgnoreList(ast)
145 && shouldCheckHashCodeMethod(ast)
146 && shouldCheckFieldDeclaration(ast)) {
147 final DetailAST constantDefAST = findContainingConstantDef(ast);
148 if (isMagicNumberExists(ast, constantDefAST)) {
149 reportMagicNumber(ast);
150 }
151 }
152 }
153
154
155
156
157
158
159
160 private boolean shouldTestAnnotationArgs(DetailAST ast) {
161 return !ignoreAnnotation || !isChildOf(ast, TokenTypes.ANNOTATION);
162 }
163
164
165
166
167
168
169
170 private boolean shouldTestAnnotationDefaults(DetailAST ast) {
171 return !ignoreAnnotationElementDefaults || !isChildOf(ast, TokenTypes.LITERAL_DEFAULT);
172 }
173
174
175
176
177
178
179
180 private boolean shouldCheckHashCodeMethod(DetailAST ast) {
181 return !ignoreHashCodeMethod || !isInHashCodeMethod(ast);
182 }
183
184
185
186
187
188
189
190 private boolean shouldCheckFieldDeclaration(DetailAST ast) {
191 return !ignoreFieldDeclaration || !isFieldDeclaration(ast);
192 }
193
194
195
196
197
198
199
200
201 private boolean isMagicNumberExists(DetailAST ast, DetailAST constantDefAST) {
202 boolean found = false;
203 DetailAST astNode = ast.getParent();
204 while (astNode != constantDefAST) {
205 final int type = astNode.getType();
206
207 if (!constantWaiverParentToken.get(type)) {
208 found = true;
209 break;
210 }
211
212 astNode = astNode.getParent();
213 }
214 return found;
215 }
216
217
218
219
220
221
222
223 private static DetailAST findContainingConstantDef(DetailAST ast) {
224 DetailAST varDefAST = ast;
225 while (varDefAST != null
226 && varDefAST.getType() != TokenTypes.VARIABLE_DEF
227 && varDefAST.getType() != TokenTypes.ENUM_CONSTANT_DEF) {
228 varDefAST = varDefAST.getParent();
229 }
230 DetailAST constantDef = null;
231
232
233 if (varDefAST != null) {
234
235 if (ScopeUtil.isInInterfaceOrAnnotationBlock(varDefAST)
236 || varDefAST.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
237 constantDef = varDefAST;
238 }
239 else {
240
241 final DetailAST modifiersAST = varDefAST.findFirstToken(TokenTypes.MODIFIERS);
242
243 if (modifiersAST.findFirstToken(TokenTypes.FINAL) != null) {
244 constantDef = varDefAST;
245 }
246 }
247 }
248 return constantDef;
249 }
250
251
252
253
254
255
256 private void reportMagicNumber(DetailAST ast) {
257 String text = ast.getText();
258 final DetailAST parent = ast.getParent();
259 DetailAST reportAST = ast;
260 if (parent.getType() == TokenTypes.UNARY_MINUS) {
261 reportAST = parent;
262 text = "-" + text;
263 }
264 else if (parent.getType() == TokenTypes.UNARY_PLUS) {
265 reportAST = parent;
266 text = "+" + text;
267 }
268 log(reportAST,
269 MSG_KEY,
270 text);
271 }
272
273
274
275
276
277
278
279
280
281
282
283 private static boolean isInHashCodeMethod(DetailAST ast) {
284
285 DetailAST currentAST = ast;
286 while (currentAST != null
287 && currentAST.getType() != TokenTypes.METHOD_DEF) {
288 currentAST = currentAST.getParent();
289 }
290 final DetailAST methodDefAST = currentAST;
291 boolean inHashCodeMethod = false;
292
293 if (methodDefAST != null) {
294
295 final DetailAST identAST = methodDefAST.findFirstToken(TokenTypes.IDENT);
296
297 if ("hashCode".equals(identAST.getText())) {
298
299 final DetailAST paramAST = methodDefAST.findFirstToken(TokenTypes.PARAMETERS);
300
301
302 inHashCodeMethod = !paramAST.hasChildren();
303 }
304 }
305 return inHashCodeMethod;
306 }
307
308
309
310
311
312
313
314
315 private boolean isInIgnoreList(DetailAST ast) {
316 double value = CheckUtil.parseDouble(ast.getText(), ast.getType());
317 final DetailAST parent = ast.getParent();
318 if (parent.getType() == TokenTypes.UNARY_MINUS) {
319 value = -1 * value;
320 }
321 return Arrays.binarySearch(ignoreNumbers, value) >= 0;
322 }
323
324
325
326
327
328
329
330
331 private static boolean isFieldDeclaration(DetailAST ast) {
332 DetailAST varDefAST = null;
333 DetailAST node = ast;
334 while (node != null && node.getType() != TokenTypes.OBJBLOCK) {
335 if (node.getType() == TokenTypes.VARIABLE_DEF) {
336 varDefAST = node;
337 break;
338 }
339 node = node.getParent();
340 }
341
342
343
344 return varDefAST != null
345 && (varDefAST.getParent().getParent().getType() == TokenTypes.CLASS_DEF
346 || varDefAST.getParent().getParent().getType() == TokenTypes.RECORD_DEF
347 || varDefAST.getParent().getParent().getType() == TokenTypes.LITERAL_NEW);
348
349 }
350
351
352
353
354
355
356
357
358 public void setConstantWaiverParentToken(String... tokens) {
359 constantWaiverParentToken = TokenUtil.asBitSet(tokens);
360 }
361
362
363
364
365
366
367
368 public void setIgnoreNumbers(double... list) {
369 ignoreNumbers = new double[list.length];
370 System.arraycopy(list, 0, ignoreNumbers, 0, list.length);
371 Arrays.sort(ignoreNumbers);
372 }
373
374
375
376
377
378
379
380
381 public void setIgnoreHashCodeMethod(boolean ignoreHashCodeMethod) {
382 this.ignoreHashCodeMethod = ignoreHashCodeMethod;
383 }
384
385
386
387
388
389
390
391 public void setIgnoreAnnotation(boolean ignoreAnnotation) {
392 this.ignoreAnnotation = ignoreAnnotation;
393 }
394
395
396
397
398
399
400
401
402 public void setIgnoreFieldDeclaration(boolean ignoreFieldDeclaration) {
403 this.ignoreFieldDeclaration = ignoreFieldDeclaration;
404 }
405
406
407
408
409
410
411
412 public void setIgnoreAnnotationElementDefaults(boolean ignoreAnnotationElementDefaults) {
413 this.ignoreAnnotationElementDefaults = ignoreAnnotationElementDefaults;
414 }
415
416
417
418
419
420
421
422
423
424 private static boolean isChildOf(DetailAST ast, int type) {
425 boolean result = false;
426 DetailAST node = ast;
427 do {
428 if (node.getType() == type) {
429 result = true;
430 break;
431 }
432 node = node.getParent();
433 } while (node != null);
434
435 return result;
436 }
437
438 }