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.javadoc;
21
22 import java.util.Arrays;
23 import java.util.HashMap;
24 import java.util.HashSet;
25 import java.util.List;
26 import java.util.Locale;
27 import java.util.Map;
28 import java.util.Set;
29 import java.util.stream.Collectors;
30
31 import com.puppycrawl.tools.checkstyle.JavadocDetailNodeParser;
32 import com.puppycrawl.tools.checkstyle.JavadocDetailNodeParser.ParseErrorMessage;
33 import com.puppycrawl.tools.checkstyle.JavadocDetailNodeParser.ParseStatus;
34 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
35 import com.puppycrawl.tools.checkstyle.api.DetailAST;
36 import com.puppycrawl.tools.checkstyle.api.DetailNode;
37 import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes;
38 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
39 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
40 import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
41
42
43
44
45
46
47
48
49
50 public abstract class AbstractJavadocCheck extends AbstractCheck {
51
52
53
54
55
56
57
58
59 public static final String MSG_JAVADOC_MISSED_HTML_CLOSE =
60 JavadocDetailNodeParser.MSG_JAVADOC_MISSED_HTML_CLOSE;
61
62
63
64
65 public static final String MSG_JAVADOC_WRONG_SINGLETON_TAG =
66 JavadocDetailNodeParser.MSG_JAVADOC_WRONG_SINGLETON_TAG;
67
68
69
70
71 public static final String MSG_JAVADOC_PARSE_RULE_ERROR =
72 JavadocDetailNodeParser.MSG_JAVADOC_PARSE_RULE_ERROR;
73
74
75
76
77 public static final String MSG_KEY_UNCLOSED_HTML_TAG =
78 JavadocDetailNodeParser.MSG_UNCLOSED_HTML_TAG;
79
80
81
82
83
84
85 private static final ThreadLocal<Map<Integer, ParseStatus>> TREE_CACHE =
86 ThreadLocal.withInitial(HashMap::new);
87
88
89
90
91
92
93
94
95 private final ThreadLocal<FileContext> context = ThreadLocal.withInitial(FileContext::new);
96
97
98 private final Set<Integer> javadocTokens = new HashSet<>();
99
100
101
102
103
104
105
106
107
108
109
110 private boolean violateExecutionOnNonTightHtml;
111
112
113
114
115
116
117
118 public abstract int[] getDefaultJavadocTokens();
119
120
121
122
123
124
125
126 public abstract void visitJavadocToken(DetailNode ast);
127
128
129
130
131
132
133
134
135
136
137 public int[] getAcceptableJavadocTokens() {
138 final int[] defaultJavadocTokens = getDefaultJavadocTokens();
139 final int[] copy = new int[defaultJavadocTokens.length];
140 System.arraycopy(defaultJavadocTokens, 0, copy, 0, defaultJavadocTokens.length);
141 return copy;
142 }
143
144
145
146
147
148
149
150 public int[] getRequiredJavadocTokens() {
151 return CommonUtil.EMPTY_INT_ARRAY;
152 }
153
154
155
156
157
158
159
160
161
162
163
164
165 public boolean acceptJavadocWithNonTightHtml() {
166 return true;
167 }
168
169
170
171
172
173
174
175
176
177
178 public final void setViolateExecutionOnNonTightHtml(boolean shouldReportViolation) {
179 violateExecutionOnNonTightHtml = shouldReportViolation;
180 }
181
182
183
184
185
186
187 public final void setJavadocTokens(String... strRep) {
188 for (String str : strRep) {
189 javadocTokens.add(JavadocUtil.getTokenId(str));
190 }
191 }
192
193 @Override
194 public void init() {
195 validateDefaultJavadocTokens();
196 if (javadocTokens.isEmpty()) {
197 javadocTokens.addAll(
198 Arrays.stream(getDefaultJavadocTokens()).boxed()
199 .collect(Collectors.toUnmodifiableList()));
200 }
201 else {
202 final int[] acceptableJavadocTokens = getAcceptableJavadocTokens();
203 Arrays.sort(acceptableJavadocTokens);
204 for (Integer javadocTokenId : javadocTokens) {
205 if (Arrays.binarySearch(acceptableJavadocTokens, javadocTokenId) < 0) {
206 final String message = String.format(Locale.ROOT, "Javadoc Token \"%s\" was "
207 + "not found in Acceptable javadoc tokens list in check %s",
208 JavadocUtil.getTokenName(javadocTokenId), getClass().getName());
209 throw new IllegalStateException(message);
210 }
211 }
212 }
213 }
214
215
216
217
218
219
220 private void validateDefaultJavadocTokens() {
221 final Set<Integer> defaultTokens = Arrays.stream(getDefaultJavadocTokens())
222 .boxed()
223 .collect(Collectors.toUnmodifiableSet());
224
225 final List<Integer> missingRequiredTokenNames = Arrays.stream(getRequiredJavadocTokens())
226 .boxed()
227 .filter(token -> !defaultTokens.contains(token))
228 .collect(Collectors.toUnmodifiableList());
229
230 if (!missingRequiredTokenNames.isEmpty()) {
231 final String message = String.format(Locale.ROOT,
232 "Javadoc Token \"%s\" from required javadoc "
233 + "tokens was not found in default "
234 + "javadoc tokens list in check %s",
235 missingRequiredTokenNames.stream()
236 .map(String::valueOf)
237 .collect(Collectors.joining(", ")),
238 getClass().getName());
239 throw new IllegalStateException(message);
240 }
241 }
242
243
244
245
246
247
248
249
250
251 public void beginJavadocTree(DetailNode rootAst) {
252
253 }
254
255
256
257
258
259
260
261
262
263 public void finishJavadocTree(DetailNode rootAst) {
264
265 }
266
267
268
269
270
271
272
273 public void leaveJavadocToken(DetailNode ast) {
274
275 }
276
277
278
279
280
281
282 @Override
283 public final int[] getDefaultTokens() {
284 return getRequiredTokens();
285 }
286
287 @Override
288 public final int[] getAcceptableTokens() {
289 return getRequiredTokens();
290 }
291
292 @Override
293 public final int[] getRequiredTokens() {
294 return new int[] {TokenTypes.BLOCK_COMMENT_BEGIN };
295 }
296
297
298
299
300
301
302 @Override
303 public final boolean isCommentNodesRequired() {
304 return true;
305 }
306
307 @Override
308 public final void beginTree(DetailAST rootAST) {
309 TREE_CACHE.get().clear();
310 }
311
312 @Override
313 public final void finishTree(DetailAST rootAST) {
314
315 }
316
317 @Override
318 public final void visitToken(DetailAST blockCommentNode) {
319 if (JavadocUtil.isJavadocComment(blockCommentNode)) {
320
321 context.get().blockCommentAst = blockCommentNode;
322
323 final int treeCacheKey = blockCommentNode.getLineNo();
324
325 final ParseStatus result = TREE_CACHE.get()
326 .computeIfAbsent(treeCacheKey, lineNumber -> {
327 return context.get().parser.parseJavadocAsDetailNode(blockCommentNode);
328 });
329
330 if (result.getParseErrorMessage() == null) {
331 if (acceptJavadocWithNonTightHtml() || !result.isNonTight()) {
332 processTree(result.getTree());
333 }
334
335 if (violateExecutionOnNonTightHtml && result.isNonTight()) {
336 log(result.getFirstNonTightHtmlTag().getLine(),
337 MSG_KEY_UNCLOSED_HTML_TAG,
338 result.getFirstNonTightHtmlTag().getText());
339 }
340 }
341 else {
342 final ParseErrorMessage parseErrorMessage = result.getParseErrorMessage();
343 log(parseErrorMessage.getLineNumber(),
344 parseErrorMessage.getMessageKey(),
345 parseErrorMessage.getMessageArguments());
346 }
347 }
348 }
349
350
351
352
353
354
355 protected DetailAST getBlockCommentAst() {
356 return context.get().blockCommentAst;
357 }
358
359
360
361
362
363
364
365 private void processTree(DetailNode root) {
366 beginJavadocTree(root);
367 walk(root);
368 finishJavadocTree(root);
369 }
370
371
372
373
374
375
376
377 private void walk(DetailNode root) {
378 DetailNode curNode = root;
379 while (curNode != null) {
380 boolean waitsForProcessing = shouldBeProcessed(curNode);
381
382 if (waitsForProcessing) {
383 visitJavadocToken(curNode);
384 }
385 DetailNode toVisit = JavadocUtil.getFirstChild(curNode);
386 while (curNode != null && toVisit == null) {
387 if (waitsForProcessing) {
388 leaveJavadocToken(curNode);
389 }
390
391 toVisit = JavadocUtil.getNextSibling(curNode);
392 curNode = curNode.getParent();
393 if (curNode != null) {
394 waitsForProcessing = shouldBeProcessed(curNode);
395 }
396 }
397 curNode = toVisit;
398 }
399 }
400
401
402
403
404
405
406
407 private boolean shouldBeProcessed(DetailNode curNode) {
408 return javadocTokens.contains(curNode.getType());
409 }
410
411 @Override
412 public void destroy() {
413 super.destroy();
414 context.remove();
415 TREE_CACHE.remove();
416 }
417
418
419
420
421 private static final class FileContext {
422
423
424
425
426 private final JavadocDetailNodeParser parser = new JavadocDetailNodeParser();
427
428
429
430
431
432 private DetailAST blockCommentAst;
433
434 }
435
436 }