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