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