View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2025 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
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.PropertyType;
35  import com.puppycrawl.tools.checkstyle.XdocsPropertyType;
36  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
37  import com.puppycrawl.tools.checkstyle.api.DetailAST;
38  import com.puppycrawl.tools.checkstyle.api.DetailNode;
39  import com.puppycrawl.tools.checkstyle.api.JavadocCommentsTokenTypes;
40  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
41  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
42  import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
43  
44  /**
45   * Base class for Checks that process Javadoc comments.
46   *
47   * @noinspection NoopMethodInAbstractClass
48   * @noinspectionreason NoopMethodInAbstractClass - we allow each
49   *      check to define these methods, as needed. They
50   *      should be overridden only by demand in subclasses
51   */
52  public abstract class AbstractJavadocCheck extends AbstractCheck {
53  
54      /**
55       * Parse error while rule recognition.
56       */
57      public static final String MSG_JAVADOC_PARSE_RULE_ERROR =
58              JavadocDetailNodeParser.MSG_JAVADOC_PARSE_RULE_ERROR;
59  
60      /**
61       * Message key of error message.
62       */
63      public static final String MSG_KEY_UNCLOSED_HTML_TAG =
64              JavadocDetailNodeParser.MSG_UNCLOSED_HTML_TAG;
65  
66      /**
67       * Key is the block comment node "lineNo". Value is {@link DetailNode} tree.
68       * Map is stored in {@link ThreadLocal}
69       * to guarantee basic thread safety and avoid shared, mutable state when not necessary.
70       */
71      private static final ThreadLocal<Map<Integer, ParseStatus>> TREE_CACHE =
72              ThreadLocal.withInitial(HashMap::new);
73  
74      /**
75       * The file context.
76       *
77       * @noinspection ThreadLocalNotStaticFinal
78       * @noinspectionreason ThreadLocalNotStaticFinal - static context is
79       *       problematic for multithreading
80       */
81      private final ThreadLocal<FileContext> context = ThreadLocal.withInitial(FileContext::new);
82  
83      /** The javadoc tokens the check is interested in. */
84      @XdocsPropertyType(PropertyType.TOKEN_ARRAY)
85      private final Set<Integer> javadocTokens = new HashSet<>();
86  
87      /**
88       * This property determines if a check should log a violation upon encountering javadoc with
89       * non-tight html. The default return value for this method is set to false since checks
90       * generally tend to be fine with non-tight html. It can be set through config file if a check
91       * is to log violation upon encountering non-tight HTML in javadoc.
92       *
93       * @see ParseStatus#isNonTight()
94       * @see <a href="https://checkstyle.org/writingjavadocchecks.html#Tight-HTML_rules">
95       *     Tight HTML rules</a>
96       */
97      private boolean violateExecutionOnNonTightHtml;
98  
99      /**
100      * Returns the default javadoc token types a check is interested in.
101      *
102      * @return the default javadoc token types
103      * @see JavadocCommentsTokenTypes
104      */
105     public abstract int[] getDefaultJavadocTokens();
106 
107     /**
108      * Called to process a Javadoc token.
109      *
110      * @param ast
111      *        the token to process
112      */
113     public abstract void visitJavadocToken(DetailNode ast);
114 
115     /**
116      * The configurable javadoc token set.
117      * Used to protect Checks against malicious users who specify an
118      * unacceptable javadoc token set in the configuration file.
119      * The default implementation returns the check's default javadoc tokens.
120      *
121      * @return the javadoc token set this check is designed for.
122      * @see JavadocCommentsTokenTypes
123      */
124     public int[] getAcceptableJavadocTokens() {
125         final int[] defaultJavadocTokens = getDefaultJavadocTokens();
126         final int[] copy = new int[defaultJavadocTokens.length];
127         System.arraycopy(defaultJavadocTokens, 0, copy, 0, defaultJavadocTokens.length);
128         return copy;
129     }
130 
131     /**
132      * The javadoc tokens that this check must be registered for.
133      *
134      * @return the javadoc token set this must be registered for.
135      * @see JavadocCommentsTokenTypes
136      */
137     public int[] getRequiredJavadocTokens() {
138         return CommonUtil.EMPTY_INT_ARRAY;
139     }
140 
141     /**
142      * This method determines if a check should process javadoc containing non-tight html tags.
143      * This method must be overridden in checks extending {@code AbstractJavadocCheck} which
144      * are not supposed to process javadoc containing non-tight html tags.
145      *
146      * @return true if the check should or can process javadoc containing non-tight html tags;
147      *     false otherwise
148      * @see ParseStatus#isNonTight()
149      * @see <a href="https://checkstyle.org/writingjavadocchecks.html#Tight-HTML_rules">
150      *     Tight HTML rules</a>
151      */
152     public boolean acceptJavadocWithNonTightHtml() {
153         return true;
154     }
155 
156     /**
157      * Setter to control when to print violations if the Javadoc being examined by this check
158      * violates the tight html rules defined at
159      * <a href="https://checkstyle.org/writingjavadocchecks.html#Tight-HTML_rules">
160      *     Tight-HTML Rules</a>.
161      *
162      * @param shouldReportViolation value to which the field shall be set to
163      * @since 8.3
164      */
165     public void setViolateExecutionOnNonTightHtml(boolean shouldReportViolation) {
166         violateExecutionOnNonTightHtml = shouldReportViolation;
167     }
168 
169     /**
170      * Adds a set of tokens the check is interested in.
171      *
172      * @param strRep the string representation of the tokens interested in
173      */
174     public void setJavadocTokens(String... strRep) {
175         for (String str : strRep) {
176             javadocTokens.add(JavadocUtil.getTokenId(str));
177         }
178     }
179 
180     @Override
181     public void init() {
182         validateDefaultJavadocTokens();
183         if (javadocTokens.isEmpty()) {
184             javadocTokens.addAll(
185                     Arrays.stream(getDefaultJavadocTokens()).boxed()
186                         .toList());
187         }
188         else {
189             final int[] acceptableJavadocTokens = getAcceptableJavadocTokens();
190             Arrays.sort(acceptableJavadocTokens);
191             for (Integer javadocTokenId : javadocTokens) {
192                 if (Arrays.binarySearch(acceptableJavadocTokens, javadocTokenId) < 0) {
193                     final String message = String.format(Locale.ROOT, "Javadoc Token \"%s\" was "
194                             + "not found in Acceptable javadoc tokens list in check %s",
195                             JavadocUtil.getTokenName(javadocTokenId), getClass().getName());
196                     throw new IllegalStateException(message);
197                 }
198             }
199         }
200     }
201 
202     /**
203      * Validates that check's required javadoc tokens are subset of default javadoc tokens.
204      *
205      * @throws IllegalStateException when validation of default javadoc tokens fails
206      */
207     private void validateDefaultJavadocTokens() {
208         final Set<Integer> defaultTokens = Arrays.stream(getDefaultJavadocTokens())
209                 .boxed()
210                 .collect(Collectors.toUnmodifiableSet());
211 
212         final List<Integer> missingRequiredTokenNames = Arrays.stream(getRequiredJavadocTokens())
213                 .boxed()
214                 .filter(token -> !defaultTokens.contains(token))
215                 .toList();
216 
217         if (!missingRequiredTokenNames.isEmpty()) {
218             final String message = String.format(Locale.ROOT,
219                         "Javadoc Token \"%s\" from required javadoc "
220                             + "tokens was not found in default "
221                             + "javadoc tokens list in check %s",
222                         missingRequiredTokenNames.stream()
223                         .map(String::valueOf)
224                         .collect(Collectors.joining(", ")),
225                         getClass().getName());
226             throw new IllegalStateException(message);
227         }
228     }
229 
230     /**
231      * Called before the starting to process a tree.
232      *
233      * @param rootAst
234      *        the root of the tree
235      * @noinspection WeakerAccess
236      * @noinspectionreason WeakerAccess - we avoid 'protected' when possible
237      */
238     public void beginJavadocTree(DetailNode rootAst) {
239         // No code by default, should be overridden only by demand at subclasses
240     }
241 
242     /**
243      * Called after finished processing a tree.
244      *
245      * @param rootAst
246      *        the root of the tree
247      * @noinspection WeakerAccess
248      * @noinspectionreason WeakerAccess - we avoid 'protected' when possible
249      */
250     public void finishJavadocTree(DetailNode rootAst) {
251         // No code by default, should be overridden only by demand at subclasses
252     }
253 
254     /**
255      * Called after all the child nodes have been process.
256      *
257      * @param ast
258      *        the token leaving
259      */
260     public void leaveJavadocToken(DetailNode ast) {
261         // No code by default, should be overridden only by demand at subclasses
262     }
263 
264     /**
265      * Defined final to not allow JavadocChecks to change default tokens.
266      *
267      * @return default tokens
268      */
269     @Override
270     public final int[] getDefaultTokens() {
271         return getRequiredTokens();
272     }
273 
274     @Override
275     public final int[] getAcceptableTokens() {
276         return getRequiredTokens();
277     }
278 
279     @Override
280     public final int[] getRequiredTokens() {
281         return new int[] {TokenTypes.BLOCK_COMMENT_BEGIN };
282     }
283 
284     /**
285      * Defined final because all JavadocChecks require comment nodes.
286      *
287      * @return true
288      */
289     @Override
290     public final boolean isCommentNodesRequired() {
291         return true;
292     }
293 
294     @Override
295     public final void beginTree(DetailAST rootAST) {
296         TREE_CACHE.get().clear();
297     }
298 
299     @Override
300     public final void finishTree(DetailAST rootAST) {
301         // No code, prevent override in subclasses
302     }
303 
304     @Override
305     public final void visitToken(DetailAST blockCommentNode) {
306         if (JavadocUtil.isJavadocComment(blockCommentNode)) {
307             // store as field, to share with child Checks
308             context.get().blockCommentAst = blockCommentNode;
309 
310             final int treeCacheKey = blockCommentNode.getLineNo();
311 
312             final ParseStatus result = TREE_CACHE.get()
313                     .computeIfAbsent(treeCacheKey, lineNumber -> {
314                         return context.get().parser.parseJavadocComment(blockCommentNode);
315                     });
316 
317             if (result.getParseErrorMessage() == null) {
318                 if (acceptJavadocWithNonTightHtml() || !result.isNonTight()) {
319                     processTree(result.getTree());
320                 }
321 
322                 if (violateExecutionOnNonTightHtml && result.isNonTight()) {
323                     log(result.getFirstNonTightHtmlTag().getLineNumber(),
324                             MSG_KEY_UNCLOSED_HTML_TAG,
325                             result.getFirstNonTightHtmlTag().getText());
326                 }
327             }
328             else {
329                 final ParseErrorMessage parseErrorMessage = result.getParseErrorMessage();
330                 log(parseErrorMessage.getLineNumber(),
331                         parseErrorMessage.getMessageKey(),
332                         parseErrorMessage.getMessageArguments());
333             }
334         }
335     }
336 
337     /**
338      * Getter for block comment in Java language syntax tree.
339      *
340      * @return A block comment in the syntax tree.
341      */
342     protected DetailAST getBlockCommentAst() {
343         return context.get().blockCommentAst;
344     }
345 
346     /**
347      * Processes JavadocAST tree notifying Check.
348      *
349      * @param root
350      *        root of JavadocAST tree.
351      */
352     private void processTree(DetailNode root) {
353         beginJavadocTree(root);
354         walk(root);
355         finishJavadocTree(root);
356     }
357 
358     /**
359      * Processes a node calling Check at interested nodes.
360      *
361      * @param root
362      *        the root of tree for process
363      */
364     private void walk(DetailNode root) {
365         DetailNode curNode = root;
366         while (curNode != null) {
367             boolean waitsForProcessing = shouldBeProcessed(curNode);
368 
369             if (waitsForProcessing) {
370                 visitJavadocToken(curNode);
371             }
372             DetailNode toVisit = curNode.getFirstChild();
373             while (curNode != null && toVisit == null) {
374                 if (waitsForProcessing) {
375                     leaveJavadocToken(curNode);
376                 }
377 
378                 toVisit = curNode.getNextSibling();
379                 curNode = curNode.getParent();
380                 if (curNode != null) {
381                     waitsForProcessing = shouldBeProcessed(curNode);
382                 }
383             }
384             curNode = toVisit;
385         }
386     }
387 
388     /**
389      * Checks whether the current node should be processed by the check.
390      *
391      * @param curNode current node.
392      * @return true if the current node should be processed by the check.
393      */
394     private boolean shouldBeProcessed(DetailNode curNode) {
395         return javadocTokens.contains(curNode.getType());
396     }
397 
398     @Override
399     public void destroy() {
400         super.destroy();
401         context.remove();
402         TREE_CACHE.remove();
403     }
404 
405     /**
406      * The file context holder.
407      */
408     private static final class FileContext {
409 
410         /**
411          * Parses content of Javadoc comment as DetailNode tree.
412          */
413         private final JavadocDetailNodeParser parser = new JavadocDetailNodeParser();
414 
415         /**
416          * DetailAST node of considered Javadoc comment that is just a block comment
417          * in Java language syntax tree.
418          */
419         private DetailAST blockCommentAst;
420 
421     }
422 
423 }