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 com.puppycrawl.tools.checkstyle.FileStatefulCheck;
23 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
24 import com.puppycrawl.tools.checkstyle.api.DetailAST;
25 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
26 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
27
28
29
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 @FileStatefulCheck
71 public class GenericWhitespaceCheck extends AbstractCheck {
72
73
74
75
76
77 public static final String MSG_WS_PRECEDED = "ws.preceded";
78
79
80
81
82
83 public static final String MSG_WS_FOLLOWED = "ws.followed";
84
85
86
87
88
89 public static final String MSG_WS_NOT_PRECEDED = "ws.notPreceded";
90
91
92
93
94
95 public static final String MSG_WS_ILLEGAL_FOLLOW = "ws.illegalFollow";
96
97
98 private static final String OPEN_ANGLE_BRACKET = "<";
99
100
101 private static final String CLOSE_ANGLE_BRACKET = ">";
102
103
104 private int depth;
105
106 @Override
107 public int[] getDefaultTokens() {
108 return getRequiredTokens();
109 }
110
111 @Override
112 public int[] getAcceptableTokens() {
113 return getRequiredTokens();
114 }
115
116 @Override
117 public int[] getRequiredTokens() {
118 return new int[] {TokenTypes.GENERIC_START, TokenTypes.GENERIC_END};
119 }
120
121 @Override
122 public void beginTree(DetailAST rootAST) {
123
124
125 depth = 0;
126 }
127
128 @Override
129 public void visitToken(DetailAST ast) {
130 switch (ast.getType()) {
131 case TokenTypes.GENERIC_START:
132 processStart(ast);
133 depth++;
134 break;
135 case TokenTypes.GENERIC_END:
136 processEnd(ast);
137 depth--;
138 break;
139 default:
140 throw new IllegalArgumentException("Unknown type " + ast);
141 }
142 }
143
144
145
146
147
148 private void processEnd(DetailAST ast) {
149 final String line = getLine(ast.getLineNo() - 1);
150 final int before = ast.getColumnNo() - 1;
151 final int after = ast.getColumnNo() + 1;
152
153 if (before >= 0 && Character.isWhitespace(line.charAt(before))
154 && !containsWhitespaceBefore(before, line)) {
155 log(ast, MSG_WS_PRECEDED, CLOSE_ANGLE_BRACKET);
156 }
157
158 if (after < line.length()) {
159
160
161 if (depth == 1) {
162 processSingleGeneric(ast, line, after);
163 }
164 else {
165 processNestedGenerics(ast, line, after);
166 }
167 }
168 }
169
170
171
172
173
174
175
176 private void processNestedGenerics(DetailAST ast, String line, int after) {
177
178
179
180
181
182
183
184
185 final int indexOfAmp = line.indexOf('&', after);
186 if (indexOfAmp >= 1
187 && containsWhitespaceBetween(after, indexOfAmp, line)) {
188 if (indexOfAmp - after == 0) {
189 log(ast, MSG_WS_NOT_PRECEDED, "&");
190 }
191 else if (indexOfAmp - after != 1) {
192 log(ast, MSG_WS_FOLLOWED, CLOSE_ANGLE_BRACKET);
193 }
194 }
195 else if (line.charAt(after) == ' ') {
196 log(ast, MSG_WS_FOLLOWED, CLOSE_ANGLE_BRACKET);
197 }
198 }
199
200
201
202
203
204
205
206 private void processSingleGeneric(DetailAST ast, String line, int after) {
207 final char charAfter = line.charAt(after);
208
209
210
211
212
213 if (isGenericBeforeMethod(ast)) {
214 if (Character.isWhitespace(charAfter)) {
215 log(ast, MSG_WS_FOLLOWED, CLOSE_ANGLE_BRACKET);
216 }
217 }
218 else if (!isCharacterValidAfterGenericEnd(charAfter)) {
219 log(ast, MSG_WS_ILLEGAL_FOLLOW, CLOSE_ANGLE_BRACKET);
220 }
221 }
222
223
224
225
226
227
228 private static boolean isGenericBeforeMethod(DetailAST ast) {
229 return ast.getParent().getParent().getType() == TokenTypes.DOT
230 && ast.getParent().getParent().getParent().getType() == TokenTypes.METHOD_CALL
231 || isAfterMethodReference(ast);
232 }
233
234
235
236
237
238
239
240 private static boolean isAfterMethodReference(DetailAST genericEnd) {
241 return genericEnd.getParent().getParent().getType() == TokenTypes.METHOD_REF;
242 }
243
244
245
246
247
248 private void processStart(DetailAST ast) {
249 final String line = getLine(ast.getLineNo() - 1);
250 final int before = ast.getColumnNo() - 1;
251 final int after = ast.getColumnNo() + 1;
252
253
254
255
256
257
258
259 if (before >= 0) {
260
261 final DetailAST parent = ast.getParent();
262 final DetailAST grandparent = parent.getParent();
263 if (grandparent.getType() == TokenTypes.CTOR_DEF
264 || grandparent.getType() == TokenTypes.METHOD_DEF) {
265
266 if (!Character.isWhitespace(line.charAt(before))) {
267 log(ast, MSG_WS_NOT_PRECEDED, OPEN_ANGLE_BRACKET);
268 }
269 }
270
271 else if (Character.isWhitespace(line.charAt(before))
272 && !containsWhitespaceBefore(before, line)) {
273 log(ast, MSG_WS_PRECEDED, OPEN_ANGLE_BRACKET);
274 }
275 }
276
277 if (after < line.length()
278 && Character.isWhitespace(line.charAt(after))) {
279 log(ast, MSG_WS_FOLLOWED, OPEN_ANGLE_BRACKET);
280 }
281 }
282
283
284
285
286
287
288
289
290
291
292 private static boolean containsWhitespaceBetween(int fromIndex, int toIndex, String line) {
293 boolean result = true;
294 for (int i = fromIndex; i < toIndex; i++) {
295 if (!Character.isWhitespace(line.charAt(i))) {
296 result = false;
297 break;
298 }
299 }
300 return result;
301 }
302
303
304
305
306
307
308
309
310
311 private static boolean containsWhitespaceBefore(int before, String line) {
312 return before != 0 && CommonUtil.hasWhitespaceBefore(before, line);
313 }
314
315
316
317
318
319
320 private static boolean isCharacterValidAfterGenericEnd(char charAfter) {
321 return charAfter == '(' || charAfter == ')'
322 || charAfter == ',' || charAfter == '['
323 || charAfter == '.' || charAfter == ':'
324 || charAfter == ';'
325 || Character.isWhitespace(charAfter);
326 }
327
328 }