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.whitespace;
21
22 import java.util.stream.IntStream;
23
24 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
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.CodePointUtil;
29 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
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
74
75
76
77
78
79
80
81
82
83 @FileStatefulCheck
84 public class GenericWhitespaceCheck extends AbstractCheck {
85
86
87
88
89
90 public static final String MSG_WS_PRECEDED = "ws.preceded";
91
92
93
94
95
96 public static final String MSG_WS_FOLLOWED = "ws.followed";
97
98
99
100
101
102 public static final String MSG_WS_NOT_PRECEDED = "ws.notPreceded";
103
104
105
106
107
108 public static final String MSG_WS_ILLEGAL_FOLLOW = "ws.illegalFollow";
109
110
111 private static final String OPEN_ANGLE_BRACKET = "<";
112
113
114 private static final String CLOSE_ANGLE_BRACKET = ">";
115
116
117 private int depth;
118
119 @Override
120 public int[] getDefaultTokens() {
121 return getRequiredTokens();
122 }
123
124 @Override
125 public int[] getAcceptableTokens() {
126 return getRequiredTokens();
127 }
128
129 @Override
130 public int[] getRequiredTokens() {
131 return new int[] {TokenTypes.GENERIC_START, TokenTypes.GENERIC_END};
132 }
133
134 @Override
135 public void beginTree(DetailAST rootAST) {
136
137
138 depth = 0;
139 }
140
141 @Override
142 public void visitToken(DetailAST ast) {
143 switch (ast.getType()) {
144 case TokenTypes.GENERIC_START:
145 processStart(ast);
146 depth++;
147 break;
148 case TokenTypes.GENERIC_END:
149 processEnd(ast);
150 depth--;
151 break;
152 default:
153 throw new IllegalArgumentException("Unknown type " + ast);
154 }
155 }
156
157
158
159
160
161
162 private void processEnd(DetailAST ast) {
163 final int[] line = getLineCodePoints(ast.getLineNo() - 1);
164 final int before = ast.getColumnNo() - 1;
165 final int after = ast.getColumnNo() + 1;
166
167 if (before >= 0 && CommonUtil.isCodePointWhitespace(line, before)
168 && !containsWhitespaceBefore(before, line)) {
169 log(ast, MSG_WS_PRECEDED, CLOSE_ANGLE_BRACKET);
170 }
171
172 if (after < line.length) {
173
174
175 if (depth == 1) {
176 processSingleGeneric(ast, line, after);
177 }
178 else {
179 processNestedGenerics(ast, line, after);
180 }
181 }
182 }
183
184
185
186
187
188
189
190
191 private void processNestedGenerics(DetailAST ast, int[] line, int after) {
192
193
194
195
196
197
198
199
200 final int indexOfAmp = IntStream.range(after, line.length)
201 .filter(index -> line[index] == '&')
202 .findFirst()
203 .orElse(-1);
204 if (indexOfAmp >= 1
205 && containsWhitespaceBetween(after, indexOfAmp, line)) {
206 if (indexOfAmp - after == 0) {
207 log(ast, MSG_WS_NOT_PRECEDED, "&");
208 }
209 else if (indexOfAmp - after != 1) {
210 log(ast, MSG_WS_FOLLOWED, CLOSE_ANGLE_BRACKET);
211 }
212 }
213 else if (line[after] == ' ') {
214 log(ast, MSG_WS_FOLLOWED, CLOSE_ANGLE_BRACKET);
215 }
216 }
217
218
219
220
221
222
223
224
225 private void processSingleGeneric(DetailAST ast, int[] line, int after) {
226 final char charAfter = Character.toChars(line[after])[0];
227 if (isGenericBeforeMethod(ast)
228 || isGenericBeforeCtorInvocation(ast)
229 || isGenericBeforeRecordHeader(ast)) {
230 if (Character.isWhitespace(charAfter)) {
231 log(ast, MSG_WS_FOLLOWED, CLOSE_ANGLE_BRACKET);
232 }
233 }
234 else if (!isCharacterValidAfterGenericEnd(charAfter)) {
235 log(ast, MSG_WS_ILLEGAL_FOLLOW, CLOSE_ANGLE_BRACKET);
236 }
237 }
238
239
240
241
242
243
244
245
246
247
248
249 private static boolean isGenericBeforeRecordHeader(DetailAST ast) {
250 final DetailAST grandParent = ast.getParent().getParent();
251 return grandParent.getType() == TokenTypes.RECORD_DEF
252 || grandParent.getParent().getType() == TokenTypes.RECORD_PATTERN_DEF;
253 }
254
255
256
257
258
259
260
261
262
263
264
265 private static boolean isGenericBeforeCtorInvocation(DetailAST ast) {
266 final DetailAST grandParent = ast.getParent().getParent();
267 return grandParent.getType() == TokenTypes.LITERAL_NEW
268 || grandParent.getParent().getType() == TokenTypes.LITERAL_NEW;
269 }
270
271
272
273
274
275
276
277
278
279
280
281
282 private static boolean isGenericAfterNew(DetailAST ast) {
283 final DetailAST parent = ast.getParent();
284 return parent.getParent().getType() == TokenTypes.LITERAL_NEW
285 && (parent.getNextSibling().getType() == TokenTypes.IDENT
286 || parent.getNextSibling().getType() == TokenTypes.DOT
287 || parent.getNextSibling().getType() == TokenTypes.ANNOTATIONS);
288 }
289
290
291
292
293
294
295
296 private static boolean isGenericBeforeMethod(DetailAST ast) {
297 return ast.getParent().getParent().getParent().getType() == TokenTypes.METHOD_CALL
298 || isAfterMethodReference(ast);
299 }
300
301
302
303
304
305
306
307
308 private static boolean isAfterMethodReference(DetailAST genericEnd) {
309 return genericEnd.getParent().getParent().getType() == TokenTypes.METHOD_REF;
310 }
311
312
313
314
315
316
317 private void processStart(DetailAST ast) {
318 final int[] line = getLineCodePoints(ast.getLineNo() - 1);
319 final int before = ast.getColumnNo() - 1;
320 final int after = ast.getColumnNo() + 1;
321
322
323
324
325
326
327
328
329
330
331
332 if (before >= 0) {
333 final DetailAST parent = ast.getParent();
334 final DetailAST grandparent = parent.getParent();
335
336 if (grandparent.getType() == TokenTypes.CTOR_DEF
337 || grandparent.getType() == TokenTypes.METHOD_DEF
338 || isGenericAfterNew(ast)) {
339
340 if (!CommonUtil.isCodePointWhitespace(line, before)) {
341 log(ast, MSG_WS_NOT_PRECEDED, OPEN_ANGLE_BRACKET);
342 }
343 }
344
345 else if (CommonUtil.isCodePointWhitespace(line, before)
346 && !containsWhitespaceBefore(before, line)) {
347 log(ast, MSG_WS_PRECEDED, OPEN_ANGLE_BRACKET);
348 }
349 }
350
351 if (after < line.length
352 && CommonUtil.isCodePointWhitespace(line, after)) {
353 log(ast, MSG_WS_FOLLOWED, OPEN_ANGLE_BRACKET);
354 }
355 }
356
357
358
359
360
361
362
363
364
365
366 private static boolean containsWhitespaceBetween(int fromIndex, int toIndex, int... line) {
367 boolean result = true;
368 for (int i = fromIndex; i < toIndex; i++) {
369 if (!CommonUtil.isCodePointWhitespace(line, i)) {
370 result = false;
371 break;
372 }
373 }
374 return result;
375 }
376
377
378
379
380
381
382
383
384
385 private static boolean containsWhitespaceBefore(int before, int... line) {
386 return before != 0 && CodePointUtil.hasWhitespaceBefore(before, line);
387 }
388
389
390
391
392
393
394
395 private static boolean isCharacterValidAfterGenericEnd(char charAfter) {
396 return charAfter == ')' || charAfter == ','
397 || charAfter == '[' || charAfter == '.'
398 || charAfter == ':' || charAfter == ';'
399 || Character.isWhitespace(charAfter);
400 }
401
402 }