1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.github.sevntu.checkstyle.checks.coding;
21
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 import java.util.HashSet;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Set;
28 import java.util.TreeMap;
29
30 import com.github.sevntu.checkstyle.Utils;
31 import com.puppycrawl.tools.checkstyle.api.Check;
32 import com.puppycrawl.tools.checkstyle.api.DetailAST;
33 import com.puppycrawl.tools.checkstyle.api.FullIdent;
34 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125 public class MapIterationInForEachLoopCheck extends Check
126 {
127
128
129
130 private boolean proposeValuesUsage = true;
131
132
133
134
135 private boolean proposeKeySetUsage = false;
136
137
138
139
140 private boolean proposeEntrySetUsage = false;
141
142
143
144
145
146 public static final String MSG_KEY_KEYSET = "map.iteration.keySet";
147
148
149
150
151
152 public static final String MSG_KEY_ENTRYSET = "map.iteration.entrySet";
153
154
155
156
157
158 public static final String MSG_KEY_VALUES = "map.iteration.values";
159
160
161
162
163 private static final String KEY_SET_METHOD_NAME = "keySet";
164
165
166
167
168 private static final String ENTRY_SET_METHOD_NAME = "entrySet";
169
170
171
172
173 private static final String GET_NODE_NAME = "get";
174
175
176
177
178 private static final String GET_VALUE_NODE_NAME = "getValue";
179
180
181
182
183 private static final String GET_KEY_NODE_NAME = "getKey";
184
185
186
187
188 private List<String> mapNamesList = new ArrayList<String>();
189
190
191
192
193 private List<String> qualifiedImportList = new ArrayList<String>();
194
195
196
197
198
199 private final Set<String> supportedMapImplQualifiedNames = new HashSet<String>();
200
201
202
203
204 public MapIterationInForEachLoopCheck()
205 {
206 setSupportedMapImplQualifiedNames(new String[] {
207 Map.class.getName(), TreeMap.class.getName(),
208 HashMap.class.getName(), });
209 }
210
211
212
213
214
215
216
217
218 public void setSupportedMapImplQualifiedNames(
219 final String[] setSupportedMapImplQualifiedNames)
220 {
221 supportedMapImplQualifiedNames.clear();
222 if (setSupportedMapImplQualifiedNames != null) {
223 for (String name : setSupportedMapImplQualifiedNames) {
224 supportedMapImplQualifiedNames.add(name);
225 final String importPathWithoutClassName = name.substring(0,
226 name.lastIndexOf(".") + 1) + "*";
227 supportedMapImplQualifiedNames.add(importPathWithoutClassName);
228 }
229 }
230 }
231
232
233
234
235
236
237
238 public void setProposeValuesUsage(
239 final boolean proposeValuesUsage)
240 {
241 this.proposeValuesUsage = proposeValuesUsage;
242 }
243
244
245
246
247
248
249
250 public void setProposeKeySetUsage(
251 final boolean proposeKeySetUsage)
252 {
253 this.proposeKeySetUsage = proposeKeySetUsage;
254 }
255
256
257
258
259
260
261
262 public void setProposeEntrySetUsage(
263 final boolean proposeEntrySetUsage)
264 {
265 this.proposeEntrySetUsage = proposeEntrySetUsage;
266 }
267
268 @Override
269 public int[] getDefaultTokens()
270 {
271 return new int[] {TokenTypes.LITERAL_FOR, TokenTypes.IMPORT, TokenTypes.VARIABLE_DEF, };
272 }
273
274 @Override
275 public void beginTree(DetailAST ast)
276 {
277 qualifiedImportList.clear();
278 mapNamesList.clear();
279 }
280
281 @Override
282 public void visitToken(DetailAST ast)
283 {
284 switch (ast.getType()) {
285
286 case TokenTypes.IMPORT:
287 String qualifiedMapImportText = getMapImportQualifiedName(ast);
288 if (qualifiedMapImportText != null) {
289 qualifiedImportList.add(qualifiedMapImportText);
290 }
291 break;
292
293 case TokenTypes.VARIABLE_DEF:
294 if (!qualifiedImportList.isEmpty() && isMapVariable(ast)) {
295 DetailAST mapIdentNode = ast.findFirstToken(TokenTypes.TYPE).getNextSibling();
296 String mapName = mapIdentNode.getText();
297
298 if (!mapNamesList.contains(mapName)) {
299 mapNamesList.add(mapIdentNode.getText());
300 }
301 }
302 break;
303
304 case TokenTypes.LITERAL_FOR:
305 if (!qualifiedImportList.isEmpty() && isForEach(ast)) {
306 final String warningMessageKey = validate(ast);
307 if (warningMessageKey != null) {
308 log(ast, warningMessageKey);
309 }
310 }
311 break;
312
313 default:
314 Utils.reportInvalidToken(ast.getType());
315 break;
316 }
317 }
318
319
320
321
322
323
324
325
326
327 private String validate(DetailAST forLiteralNode)
328 {
329 String warningMessageKey = null;
330 final DetailAST forEachNode = forLiteralNode.findFirstToken(TokenTypes.FOR_EACH_CLAUSE);
331 final DetailAST keySetOrEntrySetNode =
332 getKeySetOrEntrySetNode(forEachNode);
333 boolean isMapClassField = false;
334
335 if (keySetOrEntrySetNode != null) {
336 if (keySetOrEntrySetNode.getPreviousSibling().getChildCount() != 0) {
337 isMapClassField = true;
338 }
339 final DetailAST variableDefNode = forEachNode.getFirstChild();
340 final String keyOrEntryVariableName = variableDefNode.getLastChild().getText();
341
342 final String currentMapVariableName = isMapClassField ?
343 keySetOrEntrySetNode.getPreviousSibling().getLastChild().getText()
344 :keySetOrEntrySetNode.getPreviousSibling().getText();
345 final DetailAST forEachOpeningBrace = forLiteralNode.getLastChild();
346
347 if (!isMapPassedIntoAnyMethod(forEachOpeningBrace)) {
348
349 if (proposeKeySetUsage
350 && KEY_SET_METHOD_NAME.equals(
351 keySetOrEntrySetNode.getText()))
352 {
353 warningMessageKey =
354 checkForWrongKeySetUsage(forEachOpeningBrace,
355 keyOrEntryVariableName, currentMapVariableName, isMapClassField);
356 }
357 else if (proposeEntrySetUsage) {
358 warningMessageKey = checkForWrongEntrySetUsage(forEachOpeningBrace,
359 keyOrEntryVariableName);
360 }
361 }
362 }
363 return warningMessageKey;
364 }
365
366
367
368
369
370
371 private static boolean isForEach(DetailAST forNode)
372 {
373 return forNode.findFirstToken(TokenTypes.FOR_EACH_CLAUSE) != null;
374 }
375
376
377
378
379
380
381
382
383 private DetailAST getKeySetOrEntrySetNode(DetailAST forEachNode)
384 {
385 final List<DetailAST> identAndThisNodesList = getSubTreeNodesOfType(forEachNode,
386 TokenTypes.IDENT, TokenTypes.LITERAL_THIS);
387 boolean isMapClassField = false;
388 for (DetailAST thisNode : identAndThisNodesList) {
389 if(thisNode.getType() == TokenTypes.LITERAL_THIS) {
390 isMapClassField = true;
391 break;
392 }
393 }
394 DetailAST keySetOrEntrySetNode = null;
395 for (DetailAST identNode : identAndThisNodesList) {
396 if (KEY_SET_METHOD_NAME.equals(identNode.getText())
397 || ENTRY_SET_METHOD_NAME.equals(identNode.getText()))
398 {
399 String mapClassName = isMapClassField
400 ? identNode.getPreviousSibling().getLastChild().getText()
401 : identNode.getPreviousSibling().getText();
402 if (mapNamesList.contains(mapClassName)) {
403 keySetOrEntrySetNode = identNode;
404 break;
405 }
406 }
407 }
408 return keySetOrEntrySetNode;
409 }
410
411
412
413
414
415
416
417
418 private boolean isMapPassedIntoAnyMethod(DetailAST forEachOpeningBraceNode)
419 {
420 final List<DetailAST> methodCallNodeList = getSubTreeNodesOfType(
421 forEachOpeningBraceNode, TokenTypes.METHOD_CALL);
422 for (DetailAST methodCallNode : methodCallNodeList) {
423 if (hasMapAsParameter(methodCallNode)) {
424 return true;
425 }
426 }
427 return false;
428 }
429
430
431
432
433
434
435
436 private boolean hasMapAsParameter(DetailAST methodCallNode)
437 {
438 boolean result = false;
439 final List<DetailAST> identNodesList = getSubTreeNodesOfType(methodCallNode,
440 TokenTypes.IDENT);
441 for (String mapName : mapNamesList) {
442 for (DetailAST identNode : identNodesList) {
443 if (mapName.equals(identNode.getText())
444 && identNode.getParent().getType() == TokenTypes.EXPR)
445 {
446 result = true;
447 }
448 }
449 }
450 return result;
451 }
452
453
454
455
456
457
458
459
460
461
462
463 private String
464 checkForWrongKeySetUsage(DetailAST forEachOpeningBraceNode,
465 String keyName, String mapName, boolean isMapClassField)
466 {
467 String result = null;
468
469 final List<DetailAST> identAndLiteralIfNodesList =
470 getSubTreeNodesOfType(forEachOpeningBraceNode,
471 TokenTypes.IDENT, TokenTypes.LITERAL_IF);
472 int methodGetCallCount = 0;
473 int keyIdentCount = 0;
474 for (DetailAST identOrLiteralIfNode : identAndLiteralIfNodesList) {
475 DetailAST mapIdentNode = identOrLiteralIfNode.getPreviousSibling();
476 if (isMapClassField && mapIdentNode != null) {
477 mapIdentNode = mapIdentNode.getLastChild();
478 }
479 if (mapIdentNode != null && GET_NODE_NAME.equals(identOrLiteralIfNode.getText())
480 && mapName.equals(mapIdentNode.getText()))
481 {
482
483 methodGetCallCount++;
484 }
485
486 if (keyName.equals(identOrLiteralIfNode.getText())) {
487 keyIdentCount++;
488 }
489 }
490
491 final DetailAST literalIfNode =
492 getFirstNodeOfType(identAndLiteralIfNodesList,
493 TokenTypes.LITERAL_IF);
494 int methodGetCallInsideIfCount = 0;
495 if (literalIfNode != null) {
496 for (DetailAST node : getSubTreeNodesOfType(literalIfNode, TokenTypes.IDENT))
497 {
498 DetailAST mapIdentNode = node.getPreviousSibling();
499 if (isMapClassField && mapIdentNode != null) {
500 mapIdentNode = mapIdentNode.getLastChild();
501 }
502
503 if (mapIdentNode != null && GET_NODE_NAME.equals(node.getText())
504 && mapName.equals(mapIdentNode.getText()))
505 {
506 methodGetCallInsideIfCount++;
507 }
508 }
509 }
510
511 if (methodGetCallCount != 0 && keyIdentCount != 0) {
512
513 if (proposeValuesUsage && methodGetCallCount == keyIdentCount) {
514 result = MSG_KEY_VALUES;
515 }
516
517 else if (methodGetCallCount < keyIdentCount && methodGetCallCount > 0
518 && methodGetCallInsideIfCount != methodGetCallCount)
519 {
520 result = MSG_KEY_ENTRYSET;
521 }
522 }
523 return result;
524 }
525
526
527
528
529
530
531
532
533
534 private String
535 checkForWrongEntrySetUsage(DetailAST forEachOpeningBraceNode, String entryName)
536 {
537 String result = null;
538
539 final List<DetailAST> identNodesList = getSubTreeNodesOfType(
540 forEachOpeningBraceNode, TokenTypes.IDENT);
541 int methodGetKeyCallCount = 0;
542 int methodGetValueCallCount = 0;
543 for (DetailAST identNode : identNodesList) {
544
545 final DetailAST entryNode = identNode.getPreviousSibling();
546
547 if (entryNode != null && GET_KEY_NODE_NAME.equals(identNode.getText())
548 && entryName.equals(entryNode.getText()))
549 {
550 methodGetKeyCallCount++;
551 }
552
553 if (entryNode != null && GET_VALUE_NODE_NAME.equals(identNode.getText())
554 && entryName.equals(entryNode.getText()))
555 {
556 methodGetValueCallCount++;
557 }
558 }
559
560 if (proposeValuesUsage
561 && methodGetKeyCallCount == 0 && methodGetValueCallCount > 0)
562 {
563 result = MSG_KEY_VALUES;
564 }
565 else if (methodGetKeyCallCount > 0 && methodGetValueCallCount == 0) {
566 result = MSG_KEY_KEYSET;
567
568 }
569 return result;
570 }
571
572
573
574
575
576
577
578 private boolean isMapVariable(DetailAST variableDefNode)
579 {
580 boolean result = false;
581 final List<DetailAST> literaNewNodeslList =
582 getSubTreeNodesOfType(variableDefNode,
583 TokenTypes.LITERAL_NEW, TokenTypes.ASSIGN);
584 final String className = getClassName(literaNewNodeslList);
585 if (getFirstNodeOfType(literaNewNodeslList, TokenTypes.ASSIGN)
586 != null && className != null) {
587 result = isMapImplementation(className);
588 }
589 return result;
590 }
591
592
593
594
595
596
597
598 private boolean isMapImplementation(String className)
599 {
600 return isClassContainsInsideQualifiedImportList(className)
601 || containsInSupportedMapImplQualifiedNames(className);
602 }
603
604
605
606
607
608
609
610
611 private boolean containsInSupportedMapImplQualifiedNames(String className)
612 {
613 boolean result = false;
614 for (String supportedMapName : supportedMapImplQualifiedNames) {
615 if (supportedMapName.endsWith(className)) {
616 final int lastDotIndex = supportedMapName.lastIndexOf(".") + 1;
617 final String packageName = supportedMapName.substring(0, lastDotIndex) + "*";
618 if (qualifiedImportList.contains(packageName)) {
619 result = true;
620 break;
621 }
622 }
623 }
624 return result;
625 }
626
627
628
629
630
631
632
633
634 private boolean isClassContainsInsideQualifiedImportList(String className)
635 {
636 boolean result = false;
637 for (String mapImplementationQualifiedName : qualifiedImportList) {
638 if (mapImplementationQualifiedName.endsWith(className)) {
639 result = true;
640 break;
641 }
642 }
643 return result;
644 }
645
646
647
648
649
650
651
652
653 private static String getClassName(final List<DetailAST> literaNewNodesList)
654 {
655 for (DetailAST literalNewNode : literaNewNodesList) {
656 DetailAST exprNode = literalNewNode.getParent();
657 if (exprNode.getParent().getType() == TokenTypes.ASSIGN) {
658 return literalNewNode.getFirstChild().getText();
659 }
660 }
661 return null;
662 }
663
664
665
666
667
668
669
670
671
672
673 private static DetailAST getFirstNodeOfType(List<DetailAST> nodesList,
674 int aSpecificType)
675 {
676 for (DetailAST node : nodesList) {
677 if (node.getType() == aSpecificType) {
678 return node;
679 }
680 }
681 return null;
682 }
683
684
685
686
687
688
689
690
691 private String getMapImportQualifiedName(DetailAST importNode)
692 {
693 final String mapClassQualifiedName = FullIdent.createFullIdent(
694 importNode.getFirstChild()).getText();
695 for (String qualifiedName : supportedMapImplQualifiedNames) {
696 if (mapClassQualifiedName.equals(qualifiedName)) {
697 return mapClassQualifiedName;
698 }
699 }
700 return null;
701 }
702
703
704
705
706
707
708
709
710
711 private static List<DetailAST> getSubTreeNodesOfType(DetailAST rootNode,
712 int... tokenTypes)
713 {
714 final List<DetailAST> result = new ArrayList<DetailAST>();
715 final DetailAST finishNode;
716 if (rootNode.getNextSibling() == null) {
717 finishNode = rootNode.getLastChild();
718 }
719 else {
720 finishNode = rootNode.getNextSibling();
721 }
722 DetailAST curNode = rootNode;
723 while (curNode != null && curNode != finishNode) {
724 for (int tokenType : tokenTypes) {
725 if (curNode.getType() == tokenType) {
726 result.add(curNode);
727 }
728 }
729 DetailAST toVisit = curNode.getFirstChild();
730 while ((curNode != null) && (toVisit == null)) {
731 toVisit = curNode.getNextSibling();
732 if (toVisit == null) {
733 curNode = curNode.getParent();
734 }
735 }
736 curNode = toVisit;
737 }
738 return result;
739 }
740 }