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.Optional;
23
24 import com.puppycrawl.tools.checkstyle.StatelessCheck;
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
29
30
31
32
33
34
35
36
37
38
39
40
41 @StatelessCheck
42 public class UnnecessaryNullCheckWithInstanceOfCheck extends AbstractCheck {
43
44
45
46
47 public static final String MSG_UNNECESSARY_NULLCHECK = "unnecessary.nullcheck.with.instanceof";
48
49 @Override
50 public int[] getDefaultTokens() {
51 return getRequiredTokens();
52 }
53
54 @Override
55 public int[] getAcceptableTokens() {
56 return getRequiredTokens();
57 }
58
59 @Override
60 public int[] getRequiredTokens() {
61 return new int[] {TokenTypes.LITERAL_INSTANCEOF};
62 }
63
64 @Override
65 public void visitToken(DetailAST instanceofNode) {
66 findUnnecessaryNullCheck(instanceofNode)
67 .ifPresent(violationNode -> log(violationNode, MSG_UNNECESSARY_NULLCHECK));
68 }
69
70
71
72
73
74
75
76 private static Optional<DetailAST> findUnnecessaryNullCheck(DetailAST instanceOfNode) {
77 final DetailAST topLevelExpr = findTopLevelLogicalExpression(instanceOfNode);
78
79 Optional<DetailAST> result = Optional.empty();
80 if (topLevelExpr.getType() == TokenTypes.LAND) {
81 result = findRedundantNullCheck(topLevelExpr, instanceOfNode)
82 .map(DetailAST::getFirstChild);
83 }
84 return result;
85 }
86
87
88
89
90
91
92
93 private static DetailAST findTopLevelLogicalExpression(DetailAST node) {
94 DetailAST currentParent = node;
95 while (currentParent.getParent().getType() == TokenTypes.LAND
96 || currentParent.getParent().getType() == TokenTypes.LOR) {
97 currentParent = currentParent.getParent();
98 }
99 return currentParent;
100 }
101
102
103
104
105
106
107
108
109 private static Optional<DetailAST> findRedundantNullCheck(DetailAST logicalAndNode,
110 DetailAST instanceOfNode) {
111
112 Optional<DetailAST> nullCheckNode = Optional.empty();
113 final DetailAST instanceOfIdent = instanceOfNode.findFirstToken(TokenTypes.IDENT);
114
115 if (instanceOfIdent != null
116 && !containsVariableDereference(logicalAndNode, instanceOfIdent.getText())) {
117
118 nullCheckNode = searchForNullCheck(logicalAndNode, instanceOfNode, instanceOfIdent);
119 }
120 return nullCheckNode;
121 }
122
123
124
125
126
127
128
129
130
131 private static Optional<DetailAST> searchForNullCheck(DetailAST logicalAndNode,
132 DetailAST instanceOfNode, DetailAST instanceOfIdent) {
133
134 Optional<DetailAST> nullCheckNode = Optional.empty();
135
136 final Optional<DetailAST> instanceOfSubtree =
137 findDirectChildContaining(logicalAndNode, instanceOfNode);
138
139 final DetailAST instanceOfSubtreeNode =
140 instanceOfSubtree.orElse(null);
141
142 final boolean instanceOfInLor =
143 instanceOfSubtreeNode != null
144 && instanceOfSubtreeNode.getType() == TokenTypes.LOR;
145
146 DetailAST currentChild = logicalAndNode.getFirstChild();
147 while (currentChild != null) {
148 if (instanceOfInLor && currentChild.equals(instanceOfSubtreeNode)) {
149 break;
150 }
151
152 if (nullCheckNode.isEmpty()) {
153 nullCheckNode = checkChildForNullCheck(
154 currentChild, instanceOfNode, instanceOfIdent);
155 }
156
157 currentChild = currentChild.getNextSibling();
158 }
159
160 return nullCheckNode;
161 }
162
163
164
165
166
167
168
169
170
171 private static Optional<DetailAST> checkChildForNullCheck(DetailAST currentChild,
172 DetailAST instanceOfNode, DetailAST instanceOfIdent) {
173
174 Optional<DetailAST> result = Optional.empty();
175
176 if (isNotEqual(currentChild)
177 && isNullCheckRedundant(instanceOfIdent, currentChild)) {
178 result = Optional.of(currentChild);
179 }
180 else if (currentChild.getType() == TokenTypes.LAND) {
181 result = findRedundantNullCheck(currentChild, instanceOfNode);
182 }
183
184 return result;
185 }
186
187
188
189
190
191
192
193
194 private static Optional<DetailAST> findDirectChildContaining(DetailAST parent,
195 DetailAST target) {
196
197 DetailAST result = null;
198 DetailAST child = parent.getFirstChild();
199 while (child != null) {
200 if (isAncestorOf(child, target)) {
201 result = child;
202 break;
203 }
204 child = child.getNextSibling();
205 }
206 return Optional.ofNullable(result);
207 }
208
209
210
211
212
213
214
215
216 private static boolean isAncestorOf(DetailAST node, DetailAST target) {
217 boolean found = false;
218 DetailAST current = target;
219 while (current != null) {
220 if (current.equals(node)) {
221 found = true;
222 break;
223 }
224 current = current.getParent();
225 }
226 return found;
227 }
228
229
230
231
232
233
234
235
236
237 private static boolean containsVariableDereference(DetailAST node, String variableName) {
238
239 boolean found = false;
240
241 if (node.getType() == TokenTypes.DOT
242 || node.getType() == TokenTypes.METHOD_CALL
243 || node.getType() == TokenTypes.LAND
244 || node.getType() == TokenTypes.LOR) {
245
246 DetailAST firstChild = node.getFirstChild();
247
248 while (firstChild != null) {
249 if (variableName.equals(firstChild.getText())
250 && firstChild.getNextSibling().getType() != TokenTypes.ELIST
251 || containsVariableDereference(firstChild, variableName)) {
252 found = true;
253 break;
254 }
255 firstChild = firstChild.getNextSibling();
256 }
257 }
258 return found;
259 }
260
261
262
263
264
265
266
267 private static boolean isNotEqual(DetailAST node) {
268 return node.getType() == TokenTypes.NOT_EQUAL;
269 }
270
271
272
273
274
275
276
277 private static boolean isNullLiteral(DetailAST node) {
278 return node.getType() == TokenTypes.LITERAL_NULL;
279 }
280
281
282
283
284
285
286
287
288 private static boolean isNullCheckRedundant(DetailAST instanceOfIdent,
289 final DetailAST nullCheckNode) {
290
291 final DetailAST nullCheckIdent = nullCheckNode.findFirstToken(TokenTypes.IDENT);
292 return nullCheckIdent != null
293 && (isNullLiteral(nullCheckNode.getFirstChild().getNextSibling())
294 || isNullLiteral(nullCheckNode.getFirstChild()))
295 && instanceOfIdent.getText().equals(nullCheckIdent.getText());
296 }
297 }