View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2017 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.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.CommonUtils;
38  import com.puppycrawl.tools.checkstyle.utils.JavadocUtils;
39  
40  /**
41   * Base class for Checks that process Javadoc comments.
42   * @author Baratali Izmailov
43   * @noinspection NoopMethodInAbstractClass
44   */
45  public abstract class AbstractJavadocCheck extends AbstractCheck {
46      /**
47       * Message key of error message. Missed close HTML tag breaks structure
48       * of parse tree, so parser stops parsing and generates such error
49       * message. This case is special because parser prints error like
50       * {@code "no viable alternative at input 'b \n *\n'"} and it is not
51       * clear that error is about missed close HTML tag.
52       */
53      public static final String MSG_JAVADOC_MISSED_HTML_CLOSE =
54              JavadocDetailNodeParser.MSG_JAVADOC_MISSED_HTML_CLOSE;
55  
56      /**
57       * Message key of error message.
58       */
59      public static final String MSG_JAVADOC_WRONG_SINGLETON_TAG =
60              JavadocDetailNodeParser.MSG_JAVADOC_WRONG_SINGLETON_TAG;
61  
62      /**
63       * Parse error while rule recognition.
64       */
65      public static final String MSG_JAVADOC_PARSE_RULE_ERROR =
66              JavadocDetailNodeParser.MSG_JAVADOC_PARSE_RULE_ERROR;
67  
68      /**
69       * Key is "line:column". Value is {@link DetailNode} tree. Map is stored in {@link ThreadLocal}
70       * to guarantee basic thread safety and avoid shared, mutable state when not necessary.
71       */
72      private static final ThreadLocal<Map<String, ParseStatus>> TREE_CACHE =
73              ThreadLocal.withInitial(HashMap::new);
74  
75      /**
76       * The file context.
77       * @noinspection ThreadLocalNotStaticFinal
78       */
79      private final ThreadLocal<FileContext> context = ThreadLocal.withInitial(FileContext::new);
80  
81      /** The javadoc tokens the check is interested in. */
82      private final Set<Integer> javadocTokens = new HashSet<>();
83  
84      /**
85       * This property determines if a check should log a violation upon encountering javadoc with
86       * non-tight html. The default return value for this method is set to false since checks
87       * generally tend to be fine with non tight html. It can be set through config file if a check
88       * is to log violation upon encountering non-tight HTML in javadoc.
89       *
90       * @see ParseStatus#firstNonTightHtmlTag
91       * @see ParseStatus#isNonTight()
92       * @see <a href="http://checkstyle.sourceforge.net/writingjavadocchecks.html#Tight-HTML_rules">
93       *     Tight HTML rules</a>
94       */
95      private boolean violateExecutionOnNonTightHtml;
96  
97      /**
98       * Returns the default javadoc token types a check is interested in.
99       * @return the default javadoc token types
100      * @see JavadocTokenTypes
101      */
102     public abstract int[] getDefaultJavadocTokens();
103 
104     /**
105      * Called to process a Javadoc token.
106      * @param ast
107      *        the token to process
108      */
109     public abstract void visitJavadocToken(DetailNode ast);
110 
111     /**
112      * The configurable javadoc token set.
113      * Used to protect Checks against malicious users who specify an
114      * unacceptable javadoc token set in the configuration file.
115      * The default implementation returns the check's default javadoc tokens.
116      * @return the javadoc token set this check is designed for.
117      * @see JavadocTokenTypes
118      */
119     public int[] getAcceptableJavadocTokens() {
120         final int[] defaultJavadocTokens = getDefaultJavadocTokens();
121         final int[] copy = new int[defaultJavadocTokens.length];
122         System.arraycopy(defaultJavadocTokens, 0, copy, 0, defaultJavadocTokens.length);
123         return copy;
124     }
125 
126     /**
127      * The javadoc tokens that this check must be registered for.
128      * @return the javadoc token set this must be registered for.
129      * @see JavadocTokenTypes
130      */
131     public int[] getRequiredJavadocTokens() {
132         return CommonUtils.EMPTY_INT_ARRAY;
133     }
134 
135     /**
136      * This method determines if a check should process javadoc containing non-tight html tags.
137      * This method must be overridden in checks extending {@code AbstractJavadocCheck} which
138      * are not supposed to process javadoc containing non-tight html tags.
139      *
140      * @return true if the check should or can process javadoc containing non-tight html tags;
141      *     false otherwise
142      * @see ParseStatus#isNonTight()
143      * @see <a href="http://checkstyle.sourceforge.net/writingjavadocchecks.html#Tight-HTML_rules">
144      *     Tight HTML rules</a>
145      */
146     public boolean acceptJavadocWithNonTightHtml() {
147         return true;
148     }
149 
150     /**
151      * Setter for {@link #violateExecutionOnNonTightHtml}.
152      * @param shouldReportViolation value to which the field shall be set to
153      * @see <a href="http://checkstyle.sourceforge.net/writingjavadocchecks.html#Tight-HTML_rules">
154      *     Tight HTML rules</a>
155      */
156     public final void setViolateExecutionOnNonTightHtml(boolean shouldReportViolation) {
157         violateExecutionOnNonTightHtml = shouldReportViolation;
158     }
159 
160     /**
161      * Adds a set of tokens the check is interested in.
162      * @param strRep the string representation of the tokens interested in
163      */
164     public final void setJavadocTokens(String... strRep) {
165         javadocTokens.clear();
166         for (String str : strRep) {
167             javadocTokens.add(JavadocUtils.getTokenId(str));
168         }
169     }
170 
171     @Override
172     public void init() {
173         validateDefaultJavadocTokens();
174         if (javadocTokens.isEmpty()) {
175             for (int id : getDefaultJavadocTokens()) {
176                 javadocTokens.add(id);
177             }
178         }
179         else {
180             final int[] acceptableJavadocTokens = getAcceptableJavadocTokens();
181             Arrays.sort(acceptableJavadocTokens);
182             for (Integer javadocTokenId : javadocTokens) {
183                 if (Arrays.binarySearch(acceptableJavadocTokens, javadocTokenId) < 0) {
184                     final String message = String.format(Locale.ROOT, "Javadoc Token \"%s\" was "
185                             + "not found in Acceptable javadoc tokens list in check %s",
186                             JavadocUtils.getTokenName(javadocTokenId), getClass().getName());
187                     throw new IllegalStateException(message);
188                 }
189             }
190         }
191     }
192 
193     /**
194      * Validates that check's required javadoc tokens are subset of default javadoc tokens.
195      * @throws IllegalStateException when validation of default javadoc tokens fails
196      */
197     private void validateDefaultJavadocTokens() {
198         if (getRequiredJavadocTokens().length != 0) {
199             final int[] defaultJavadocTokens = getDefaultJavadocTokens();
200             Arrays.sort(defaultJavadocTokens);
201             for (final int javadocToken : getRequiredJavadocTokens()) {
202                 if (Arrays.binarySearch(defaultJavadocTokens, javadocToken) < 0) {
203                     final String message = String.format(Locale.ROOT,
204                             "Javadoc Token \"%s\" from required javadoc "
205                                 + "tokens was not found in default "
206                                 + "javadoc tokens list in check %s",
207                             javadocToken, getClass().getName());
208                     throw new IllegalStateException(message);
209                 }
210             }
211         }
212     }
213 
214     /**
215      * Called before the starting to process a tree.
216      * @param rootAst
217      *        the root of the tree
218      * @noinspection WeakerAccess
219      */
220     public void beginJavadocTree(DetailNode rootAst) {
221         // No code by default, should be overridden only by demand at subclasses
222     }
223 
224     /**
225      * Called after finished processing a tree.
226      * @param rootAst
227      *        the root of the tree
228      * @noinspection WeakerAccess
229      */
230     public void finishJavadocTree(DetailNode rootAst) {
231         // No code by default, should be overridden only by demand at subclasses
232     }
233 
234     /**
235      * Called after all the child nodes have been process.
236      * @param ast
237      *        the token leaving
238      */
239     public void leaveJavadocToken(DetailNode ast) {
240         // No code by default, should be overridden only by demand at subclasses
241     }
242 
243     /**
244      * Defined final to not allow JavadocChecks to change default tokens.
245      * @return default tokens
246      */
247     @Override
248     public final int[] getDefaultTokens() {
249         return getRequiredTokens();
250     }
251 
252     @Override
253     public final int[] getAcceptableTokens() {
254         return getRequiredTokens();
255     }
256 
257     @Override
258     public final int[] getRequiredTokens() {
259         return new int[] {TokenTypes.BLOCK_COMMENT_BEGIN };
260     }
261 
262     /**
263      * Defined final because all JavadocChecks require comment nodes.
264      * @return true
265      */
266     @Override
267     public final boolean isCommentNodesRequired() {
268         return true;
269     }
270 
271     @Override
272     public final void beginTree(DetailAST rootAST) {
273         TREE_CACHE.get().clear();
274     }
275 
276     @Override
277     public final void finishTree(DetailAST rootAST) {
278         TREE_CACHE.get().clear();
279     }
280 
281     @Override
282     public final void visitToken(DetailAST blockCommentNode) {
283         if (JavadocUtils.isJavadocComment(blockCommentNode)) {
284             // store as field, to share with child Checks
285             context.get().blockCommentAst = blockCommentNode;
286 
287             final String treeCacheKey = blockCommentNode.getLineNo() + ":"
288                     + blockCommentNode.getColumnNo();
289 
290             final ParseStatus result;
291 
292             if (TREE_CACHE.get().containsKey(treeCacheKey)) {
293                 result = TREE_CACHE.get().get(treeCacheKey);
294             }
295             else {
296                 result = context.get().parser
297                         .parseJavadocAsDetailNode(blockCommentNode);
298                 TREE_CACHE.get().put(treeCacheKey, result);
299             }
300 
301             if (result.getParseErrorMessage() == null) {
302                 if (acceptJavadocWithNonTightHtml() || !result.isNonTight()) {
303                     processTree(result.getTree());
304                 }
305 
306                 if (violateExecutionOnNonTightHtml && result.isNonTight()) {
307                     log(result.getFirstNonTightHtmlTag().getLine(),
308                             JavadocDetailNodeParser.MSG_UNCLOSED_HTML_TAG,
309                             result.getFirstNonTightHtmlTag().getText());
310                 }
311             }
312             else {
313                 final ParseErrorMessage parseErrorMessage = result.getParseErrorMessage();
314                 log(parseErrorMessage.getLineNumber(),
315                         parseErrorMessage.getMessageKey(),
316                         parseErrorMessage.getMessageArguments());
317             }
318         }
319 
320     }
321 
322     /**
323      * Getter for block comment in Java language syntax tree.
324      * @return A block comment in the syntax tree.
325      */
326     protected DetailAST getBlockCommentAst() {
327         return context.get().blockCommentAst;
328     }
329 
330     /**
331      * Processes JavadocAST tree notifying Check.
332      * @param root
333      *        root of JavadocAST tree.
334      */
335     private void processTree(DetailNode root) {
336         beginJavadocTree(root);
337         walk(root);
338         finishJavadocTree(root);
339     }
340 
341     /**
342      * Processes a node calling Check at interested nodes.
343      * @param root
344      *        the root of tree for process
345      */
346     private void walk(DetailNode root) {
347         DetailNode curNode = root;
348         while (curNode != null) {
349             boolean waitsForProcessing = shouldBeProcessed(curNode);
350 
351             if (waitsForProcessing) {
352                 visitJavadocToken(curNode);
353             }
354             DetailNode toVisit = JavadocUtils.getFirstChild(curNode);
355             while (curNode != null && toVisit == null) {
356 
357                 if (waitsForProcessing) {
358                     leaveJavadocToken(curNode);
359                 }
360 
361                 toVisit = JavadocUtils.getNextSibling(curNode);
362                 if (toVisit == null) {
363                     curNode = curNode.getParent();
364                     if (curNode != null) {
365                         waitsForProcessing = shouldBeProcessed(curNode);
366                     }
367                 }
368             }
369             curNode = toVisit;
370         }
371     }
372 
373     /**
374      * Checks whether the current node should be processed by the check.
375      * @param curNode current node.
376      * @return true if the current node should be processed by the check.
377      */
378     private boolean shouldBeProcessed(DetailNode curNode) {
379         return javadocTokens.contains(curNode.getType());
380     }
381 
382     /**
383      * The file context holder.
384      */
385     private static class FileContext {
386         /**
387          * Parses content of Javadoc comment as DetailNode tree.
388          */
389         private final JavadocDetailNodeParser parser = new JavadocDetailNodeParser();
390 
391         /**
392          * DetailAST node of considered Javadoc comment that is just a block comment
393          * in Java language syntax tree.
394          */
395         private DetailAST blockCommentAst;
396     }
397 }