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.Collections;
23 import java.util.HashSet;
24 import java.util.Set;
25
26 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
27 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
28 import com.puppycrawl.tools.checkstyle.api.DetailAST;
29 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
30
31
32
33
34
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73 @FileStatefulCheck
74 public class EqualsAvoidNullCheck extends AbstractCheck {
75
76
77
78
79
80 public static final String MSG_EQUALS_AVOID_NULL = "equals.avoid.null";
81
82
83
84
85
86 public static final String MSG_EQUALS_IGNORE_CASE_AVOID_NULL = "equalsIgnoreCase.avoid.null";
87
88
89 private static final String EQUALS = "equals";
90
91
92 private static final String STRING = "String";
93
94
95 private static final String LEFT_CURLY = "{";
96
97
98 private boolean ignoreEqualsIgnoreCase;
99
100
101 private FieldFrame currentFrame;
102
103 @Override
104 public int[] getDefaultTokens() {
105 return getRequiredTokens();
106 }
107
108 @Override
109 public int[] getAcceptableTokens() {
110 return getRequiredTokens();
111 }
112
113 @Override
114 public int[] getRequiredTokens() {
115 return new int[] {
116 TokenTypes.METHOD_CALL,
117 TokenTypes.CLASS_DEF,
118 TokenTypes.METHOD_DEF,
119 TokenTypes.LITERAL_FOR,
120 TokenTypes.LITERAL_CATCH,
121 TokenTypes.LITERAL_TRY,
122 TokenTypes.LITERAL_SWITCH,
123 TokenTypes.VARIABLE_DEF,
124 TokenTypes.PARAMETER_DEF,
125 TokenTypes.CTOR_DEF,
126 TokenTypes.SLIST,
127 TokenTypes.OBJBLOCK,
128 TokenTypes.ENUM_DEF,
129 TokenTypes.ENUM_CONSTANT_DEF,
130 TokenTypes.LITERAL_NEW,
131 TokenTypes.LAMBDA,
132 };
133 }
134
135
136
137
138
139
140 public void setIgnoreEqualsIgnoreCase(boolean newValue) {
141 ignoreEqualsIgnoreCase = newValue;
142 }
143
144 @Override
145 public void beginTree(DetailAST rootAST) {
146 currentFrame = new FieldFrame(null);
147 }
148
149 @Override
150 public void visitToken(final DetailAST ast) {
151 switch (ast.getType()) {
152 case TokenTypes.VARIABLE_DEF:
153 case TokenTypes.PARAMETER_DEF:
154 currentFrame.addField(ast);
155 break;
156 case TokenTypes.METHOD_CALL:
157 processMethodCall(ast);
158 break;
159 case TokenTypes.SLIST:
160 processSlist(ast);
161 break;
162 case TokenTypes.LITERAL_NEW:
163 processLiteralNew(ast);
164 break;
165 case TokenTypes.OBJBLOCK:
166 final int parentType = ast.getParent().getType();
167 if (parentType != TokenTypes.CLASS_DEF && parentType != TokenTypes.ENUM_DEF) {
168 processFrame(ast);
169 }
170 break;
171 default:
172 processFrame(ast);
173 }
174 }
175
176 @Override
177 public void leaveToken(DetailAST ast) {
178 final int astType = ast.getType();
179 if (astType == TokenTypes.SLIST) {
180 leaveSlist(ast);
181 }
182 else if (astType == TokenTypes.LITERAL_NEW) {
183 leaveLiteralNew(ast);
184 }
185 else if (astType == TokenTypes.OBJBLOCK) {
186 final int parentType = ast.getParent().getType();
187 if (parentType != TokenTypes.CLASS_DEF && parentType != TokenTypes.ENUM_DEF) {
188 currentFrame = currentFrame.getParent();
189 }
190 }
191 else if (astType != TokenTypes.VARIABLE_DEF
192 && astType != TokenTypes.PARAMETER_DEF
193 && astType != TokenTypes.METHOD_CALL) {
194 currentFrame = currentFrame.getParent();
195 }
196 }
197
198 @Override
199 public void finishTree(DetailAST ast) {
200 traverseFieldFrameTree(currentFrame);
201 }
202
203
204
205
206
207
208 private void processSlist(DetailAST ast) {
209 if (LEFT_CURLY.equals(ast.getText())) {
210 final FieldFrame frame = new FieldFrame(currentFrame);
211 currentFrame.addChild(frame);
212 currentFrame = frame;
213 }
214 }
215
216
217
218
219
220 private void leaveSlist(DetailAST ast) {
221 if (LEFT_CURLY.equals(ast.getText())) {
222 currentFrame = currentFrame.getParent();
223 }
224 }
225
226
227
228
229
230
231 private void processFrame(DetailAST ast) {
232 final FieldFrame frame = new FieldFrame(currentFrame);
233 final int astType = ast.getType();
234 if (astType == TokenTypes.CLASS_DEF
235 || astType == TokenTypes.ENUM_DEF
236 || astType == TokenTypes.ENUM_CONSTANT_DEF) {
237 frame.setClassOrEnumOrEnumConstDef(true);
238 frame.setFrameName(ast.findFirstToken(TokenTypes.IDENT).getText());
239 }
240 currentFrame.addChild(frame);
241 currentFrame = frame;
242 }
243
244
245
246
247
248 private void processMethodCall(DetailAST methodCall) {
249 final DetailAST dot = methodCall.getFirstChild();
250 if (dot.getType() == TokenTypes.DOT) {
251 final String methodName = dot.getLastChild().getText();
252 if (EQUALS.equals(methodName)
253 || !ignoreEqualsIgnoreCase && "equalsIgnoreCase".equals(methodName)) {
254 currentFrame.addMethodCall(methodCall);
255 }
256 }
257 }
258
259
260
261
262
263
264 private void processLiteralNew(DetailAST ast) {
265 if (ast.findFirstToken(TokenTypes.OBJBLOCK) != null) {
266 final FieldFrame frame = new FieldFrame(currentFrame);
267 currentFrame.addChild(frame);
268 currentFrame = frame;
269 }
270 }
271
272
273
274
275
276
277
278 private void leaveLiteralNew(DetailAST ast) {
279 if (ast.findFirstToken(TokenTypes.OBJBLOCK) != null) {
280 currentFrame = currentFrame.getParent();
281 }
282 }
283
284
285
286
287
288 private void traverseFieldFrameTree(FieldFrame frame) {
289 for (FieldFrame child: frame.getChildren()) {
290 if (!child.getChildren().isEmpty()) {
291 traverseFieldFrameTree(child);
292 }
293 currentFrame = child;
294 child.getMethodCalls().forEach(this::checkMethodCall);
295 }
296 }
297
298
299
300
301
302 private void checkMethodCall(DetailAST methodCall) {
303 DetailAST objCalledOn = methodCall.getFirstChild().getFirstChild();
304 if (objCalledOn.getType() == TokenTypes.DOT) {
305 objCalledOn = objCalledOn.getLastChild();
306 }
307 final DetailAST expr = methodCall.findFirstToken(TokenTypes.ELIST).getFirstChild();
308 if (containsOneArgument(methodCall)
309 && containsAllSafeTokens(expr)
310 && isCalledOnStringFieldOrVariable(objCalledOn)) {
311 final String methodName = methodCall.getFirstChild().getLastChild().getText();
312 if (EQUALS.equals(methodName)) {
313 log(methodCall, MSG_EQUALS_AVOID_NULL);
314 }
315 else {
316 log(methodCall, MSG_EQUALS_IGNORE_CASE_AVOID_NULL);
317 }
318 }
319 }
320
321
322
323
324
325
326
327 private static boolean containsOneArgument(DetailAST methodCall) {
328 final DetailAST elist = methodCall.findFirstToken(TokenTypes.ELIST);
329 return elist.getChildCount() == 1;
330 }
331
332
333
334
335
336
337
338 private static boolean containsAllSafeTokens(final DetailAST expr) {
339 DetailAST arg = expr.getFirstChild();
340 arg = skipVariableAssign(arg);
341
342 boolean argIsNotNull = false;
343 if (arg.getType() == TokenTypes.PLUS) {
344 DetailAST child = arg.getFirstChild();
345 while (child != null
346 && !argIsNotNull) {
347 argIsNotNull = child.getType() == TokenTypes.STRING_LITERAL
348 || child.getType() == TokenTypes.IDENT;
349 child = child.getNextSibling();
350 }
351 }
352
353 return argIsNotNull
354 || !arg.branchContains(TokenTypes.IDENT)
355 && !arg.branchContains(TokenTypes.LITERAL_NULL);
356 }
357
358
359
360
361
362
363 private static DetailAST skipVariableAssign(final DetailAST currentAST) {
364 DetailAST result = currentAST;
365 if (currentAST.getType() == TokenTypes.ASSIGN
366 && currentAST.getFirstChild().getType() == TokenTypes.IDENT) {
367 result = currentAST.getFirstChild().getNextSibling();
368 }
369 return result;
370 }
371
372
373
374
375
376
377 private boolean isCalledOnStringFieldOrVariable(DetailAST objCalledOn) {
378 final boolean result;
379 final DetailAST previousSiblingAst = objCalledOn.getPreviousSibling();
380 if (previousSiblingAst == null) {
381 result = isStringFieldOrVariable(objCalledOn);
382 }
383 else {
384 if (previousSiblingAst.getType() == TokenTypes.LITERAL_THIS) {
385 result = isStringFieldOrVariableFromThisInstance(objCalledOn);
386 }
387 else {
388 final String className = previousSiblingAst.getText();
389 result = isStringFieldOrVariableFromClass(objCalledOn, className);
390 }
391 }
392 return result;
393 }
394
395
396
397
398
399
400 private boolean isStringFieldOrVariable(DetailAST objCalledOn) {
401 boolean result = false;
402 final String name = objCalledOn.getText();
403 FieldFrame frame = currentFrame;
404 while (frame != null) {
405 final DetailAST field = frame.findField(name);
406 if (field != null
407 && (frame.isClassOrEnumOrEnumConstDef()
408 || checkLineNo(field, objCalledOn))) {
409 result = STRING.equals(getFieldType(field));
410 break;
411 }
412 frame = frame.getParent();
413 }
414 return result;
415 }
416
417
418
419
420
421
422 private boolean isStringFieldOrVariableFromThisInstance(DetailAST objCalledOn) {
423 boolean result = false;
424 final String name = objCalledOn.getText();
425 final DetailAST field = getObjectFrame(currentFrame).findField(name);
426 if (field != null) {
427 result = STRING.equals(getFieldType(field));
428 }
429 return result;
430 }
431
432
433
434
435
436
437
438 private boolean isStringFieldOrVariableFromClass(DetailAST objCalledOn,
439 final String className) {
440 boolean result = false;
441 final String name = objCalledOn.getText();
442 FieldFrame frame = getObjectFrame(currentFrame);
443 while (frame != null) {
444 if (className.equals(frame.getFrameName())) {
445 final DetailAST field = frame.findField(name);
446 if (field != null) {
447 result = STRING.equals(getFieldType(field));
448 }
449 break;
450 }
451 frame = getObjectFrame(frame.getParent());
452 }
453 return result;
454 }
455
456
457
458
459
460
461 private static FieldFrame getObjectFrame(FieldFrame frame) {
462 FieldFrame objectFrame = frame;
463 while (objectFrame != null && !objectFrame.isClassOrEnumOrEnumConstDef()) {
464 objectFrame = objectFrame.getParent();
465 }
466 return objectFrame;
467 }
468
469
470
471
472
473
474
475
476 private static boolean checkLineNo(DetailAST field, DetailAST objCalledOn) {
477 boolean result = false;
478
479
480
481
482
483
484
485
486 final int minimumSymbolsBetween = 11;
487 if (field.getLineNo() < objCalledOn.getLineNo()
488 || field.getLineNo() == objCalledOn.getLineNo()
489 && field.getColumnNo() + minimumSymbolsBetween <= objCalledOn.getColumnNo()) {
490 result = true;
491 }
492 return result;
493 }
494
495
496
497
498
499
500 private static String getFieldType(DetailAST field) {
501 String fieldType = null;
502 final DetailAST identAst = field.findFirstToken(TokenTypes.TYPE)
503 .findFirstToken(TokenTypes.IDENT);
504 if (identAst != null) {
505 fieldType = identAst.getText();
506 }
507 return fieldType;
508 }
509
510
511
512
513 private static class FieldFrame {
514
515
516 private final FieldFrame parent;
517
518
519 private final Set<FieldFrame> children = new HashSet<>();
520
521
522 private final Set<DetailAST> fields = new HashSet<>();
523
524
525 private final Set<DetailAST> methodCalls = new HashSet<>();
526
527
528 private String frameName;
529
530
531 private boolean classOrEnumOrEnumConstDef;
532
533
534
535
536
537 FieldFrame(FieldFrame parent) {
538 this.parent = parent;
539 }
540
541
542
543
544
545 public void setFrameName(String frameName) {
546 this.frameName = frameName;
547 }
548
549
550
551
552
553 public String getFrameName() {
554 return frameName;
555 }
556
557
558
559
560
561 public FieldFrame getParent() {
562 return parent;
563 }
564
565
566
567
568
569 public Set<FieldFrame> getChildren() {
570 return Collections.unmodifiableSet(children);
571 }
572
573
574
575
576
577 public void addChild(FieldFrame child) {
578 children.add(child);
579 }
580
581
582
583
584
585 public void addField(DetailAST field) {
586 if (field.findFirstToken(TokenTypes.IDENT) != null) {
587 fields.add(field);
588 }
589 }
590
591
592
593
594
595 public void setClassOrEnumOrEnumConstDef(boolean value) {
596 classOrEnumOrEnumConstDef = value;
597 }
598
599
600
601
602
603 public boolean isClassOrEnumOrEnumConstDef() {
604 return classOrEnumOrEnumConstDef;
605 }
606
607
608
609
610
611 public void addMethodCall(DetailAST methodCall) {
612 methodCalls.add(methodCall);
613 }
614
615
616
617
618
619
620 public DetailAST findField(String name) {
621 DetailAST resultField = null;
622 for (DetailAST field: fields) {
623 if (getFieldName(field).equals(name)) {
624 resultField = field;
625 break;
626 }
627 }
628 return resultField;
629 }
630
631
632
633
634
635 public Set<DetailAST> getMethodCalls() {
636 return Collections.unmodifiableSet(methodCalls);
637 }
638
639
640
641
642
643
644 private static String getFieldName(DetailAST field) {
645 return field.findFirstToken(TokenTypes.IDENT).getText();
646 }
647
648 }
649
650 }