View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 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.utils;
21  
22  import java.util.ArrayList;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.regex.Pattern;
26  
27  import com.puppycrawl.tools.checkstyle.api.DetailAST;
28  import com.puppycrawl.tools.checkstyle.api.DetailNode;
29  import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes;
30  import com.puppycrawl.tools.checkstyle.api.TextBlock;
31  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
32  import com.puppycrawl.tools.checkstyle.checks.javadoc.InvalidJavadocTag;
33  import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTag;
34  import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTagInfo;
35  import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTags;
36  import com.puppycrawl.tools.checkstyle.checks.javadoc.utils.BlockTagUtil;
37  import com.puppycrawl.tools.checkstyle.checks.javadoc.utils.InlineTagUtil;
38  import com.puppycrawl.tools.checkstyle.checks.javadoc.utils.TagInfo;
39  
40  /**
41   * Contains utility methods for working with Javadoc.
42   */
43  public final class JavadocUtil {
44  
45      /**
46       * The type of Javadoc tag we want returned.
47       */
48      public enum JavadocTagType {
49  
50          /** Block type. */
51          BLOCK,
52          /** Inline type. */
53          INLINE,
54          /** All validTags. */
55          ALL,
56  
57      }
58  
59      /** Maps from a token name to value. */
60      private static final Map<String, Integer> TOKEN_NAME_TO_VALUE;
61      /** Maps from a token value to name. */
62      private static final Map<Integer, String> TOKEN_VALUE_TO_NAME;
63  
64      /** Exception message for unknown JavaDoc token id. */
65      private static final String UNKNOWN_JAVADOC_TOKEN_ID_EXCEPTION_MESSAGE = "Unknown javadoc"
66              + " token id. Given id: ";
67  
68      /** Newline pattern. */
69      private static final Pattern NEWLINE = Pattern.compile("\n");
70  
71      /** Return pattern. */
72      private static final Pattern RETURN = Pattern.compile("\r");
73  
74      /** Tab pattern. */
75      private static final Pattern TAB = Pattern.compile("\t");
76  
77      // initialise the constants
78      static {
79          TOKEN_NAME_TO_VALUE = TokenUtil.nameToValueMapFromPublicIntFields(JavadocTokenTypes.class);
80          TOKEN_VALUE_TO_NAME = TokenUtil.invertMap(TOKEN_NAME_TO_VALUE);
81      }
82  
83      /** Prevent instantiation. */
84      private JavadocUtil() {
85      }
86  
87      /**
88       * Gets validTags from a given piece of Javadoc.
89       *
90       * @param textBlock
91       *        the Javadoc comment to process.
92       * @param tagType
93       *        the type of validTags we're interested in
94       * @return all standalone validTags from the given javadoc.
95       */
96      public static JavadocTags getJavadocTags(TextBlock textBlock,
97              JavadocTagType tagType) {
98          final List<TagInfo> tags = new ArrayList<>();
99          final boolean isBlockTags = tagType == JavadocTagType.ALL
100                                         || tagType == JavadocTagType.BLOCK;
101         if (isBlockTags) {
102             tags.addAll(BlockTagUtil.extractBlockTags(textBlock.getText()));
103         }
104         final boolean isInlineTags = tagType == JavadocTagType.ALL
105                                         || tagType == JavadocTagType.INLINE;
106         if (isInlineTags) {
107             tags.addAll(InlineTagUtil.extractInlineTags(textBlock.getText()));
108         }
109 
110         final List<JavadocTag> validTags = new ArrayList<>();
111         final List<InvalidJavadocTag> invalidTags = new ArrayList<>();
112 
113         for (TagInfo tag : tags) {
114             final int col = tag.getPosition().getColumn();
115 
116             // Add the starting line of the comment to the line number to get the actual line number
117             // in the source.
118             // Lines are one-indexed, so need an off-by-one correction.
119             final int line = textBlock.getStartLineNo() + tag.getPosition().getLine() - 1;
120 
121             if (JavadocTagInfo.isValidName(tag.getName())) {
122                 validTags.add(
123                     new JavadocTag(line, col, tag.getName(), tag.getValue()));
124             }
125             else {
126                 invalidTags.add(new InvalidJavadocTag(line, col, tag.getName()));
127             }
128         }
129 
130         return new JavadocTags(validTags, invalidTags);
131     }
132 
133     /**
134      * Checks that commentContent starts with '*' javadoc comment identifier.
135      *
136      * @param commentContent
137      *        content of block comment
138      * @return true if commentContent starts with '*' javadoc comment
139      *         identifier.
140      */
141     public static boolean isJavadocComment(String commentContent) {
142         boolean result = false;
143 
144         if (!commentContent.isEmpty()) {
145             final char docCommentIdentifier = commentContent.charAt(0);
146             result = docCommentIdentifier == '*';
147         }
148 
149         return result;
150     }
151 
152     /**
153      * Checks block comment content starts with '*' javadoc comment identifier.
154      *
155      * @param blockCommentBegin
156      *        block comment AST
157      * @return true if block comment content starts with '*' javadoc comment
158      *         identifier.
159      */
160     public static boolean isJavadocComment(DetailAST blockCommentBegin) {
161         final String commentContent = getBlockCommentContent(blockCommentBegin);
162         return isJavadocComment(commentContent) && isCorrectJavadocPosition(blockCommentBegin);
163     }
164 
165     /**
166      * Gets content of block comment.
167      *
168      * @param blockCommentBegin
169      *        block comment AST.
170      * @return content of block comment.
171      */
172     public static String getBlockCommentContent(DetailAST blockCommentBegin) {
173         final DetailAST commentContent = blockCommentBegin.getFirstChild();
174         return commentContent.getText();
175     }
176 
177     /**
178      * Get content of Javadoc comment.
179      *
180      * @param javadocCommentBegin
181      *        Javadoc comment AST
182      * @return content of Javadoc comment.
183      */
184     public static String getJavadocCommentContent(DetailAST javadocCommentBegin) {
185         final DetailAST commentContent = javadocCommentBegin.getFirstChild();
186         return commentContent.getText().substring(1);
187     }
188 
189     /**
190      * Returns the first child token that has a specified type.
191      *
192      * @param detailNode
193      *        Javadoc AST node
194      * @param type
195      *        the token type to match
196      * @return the matching token, or null if no match
197      */
198     public static DetailNode findFirstToken(DetailNode detailNode, int type) {
199         DetailNode returnValue = null;
200         DetailNode node = getFirstChild(detailNode);
201         while (node != null) {
202             if (node.getType() == type) {
203                 returnValue = node;
204                 break;
205             }
206             node = getNextSibling(node);
207         }
208         return returnValue;
209     }
210 
211     /**
212      * Gets first child node of specified node.
213      *
214      * @param node DetailNode
215      * @return first child
216      */
217     public static DetailNode getFirstChild(DetailNode node) {
218         DetailNode resultNode = null;
219 
220         if (node.getChildren().length > 0) {
221             resultNode = node.getChildren()[0];
222         }
223         return resultNode;
224     }
225 
226     /**
227      * Gets next sibling of specified node.
228      *
229      * @param node DetailNode
230      * @return next sibling.
231      */
232     public static DetailNode getNextSibling(DetailNode node) {
233         DetailNode nextSibling = null;
234         final DetailNode parent = node.getParent();
235         if (parent != null) {
236             final int nextSiblingIndex = node.getIndex() + 1;
237             final DetailNode[] children = parent.getChildren();
238             if (nextSiblingIndex <= children.length - 1) {
239                 nextSibling = children[nextSiblingIndex];
240             }
241         }
242         return nextSibling;
243     }
244 
245     /**
246      * Gets next sibling of specified node with the specified type.
247      *
248      * @param node DetailNode
249      * @param tokenType javadoc token type
250      * @return next sibling.
251      */
252     public static DetailNode getNextSibling(DetailNode node, int tokenType) {
253         DetailNode nextSibling = getNextSibling(node);
254         while (nextSibling != null && nextSibling.getType() != tokenType) {
255             nextSibling = getNextSibling(nextSibling);
256         }
257         return nextSibling;
258     }
259 
260     /**
261      * Gets previous sibling of specified node.
262      *
263      * @param node DetailNode
264      * @return previous sibling
265      */
266     public static DetailNode getPreviousSibling(DetailNode node) {
267         DetailNode previousSibling = null;
268         final int previousSiblingIndex = node.getIndex() - 1;
269         if (previousSiblingIndex >= 0) {
270             final DetailNode parent = node.getParent();
271             final DetailNode[] children = parent.getChildren();
272             previousSibling = children[previousSiblingIndex];
273         }
274         return previousSibling;
275     }
276 
277     /**
278      * Returns the name of a token for a given ID.
279      *
280      * @param id
281      *        the ID of the token name to get
282      * @return a token name
283      * @throws IllegalArgumentException if an unknown token ID was specified.
284      */
285     public static String getTokenName(int id) {
286         final String name = TOKEN_VALUE_TO_NAME.get(id);
287         if (name == null) {
288             throw new IllegalArgumentException(UNKNOWN_JAVADOC_TOKEN_ID_EXCEPTION_MESSAGE + id);
289         }
290         return name;
291     }
292 
293     /**
294      * Returns the ID of a token for a given name.
295      *
296      * @param name
297      *        the name of the token ID to get
298      * @return a token ID
299      * @throws IllegalArgumentException if an unknown token name was specified.
300      */
301     public static int getTokenId(String name) {
302         final Integer id = TOKEN_NAME_TO_VALUE.get(name);
303         if (id == null) {
304             throw new IllegalArgumentException("Unknown javadoc token name. Given name " + name);
305         }
306         return id;
307     }
308 
309     /**
310      * Gets tag name from javadocTagSection.
311      *
312      * @param javadocTagSection to get tag name from.
313      * @return name, of the javadocTagSection's tag.
314      */
315     public static String getTagName(DetailNode javadocTagSection) {
316         final String javadocTagName;
317         if (javadocTagSection.getType() == JavadocTokenTypes.JAVADOC_INLINE_TAG) {
318             javadocTagName = getNextSibling(
319                     getFirstChild(javadocTagSection)).getText();
320         }
321         else {
322             javadocTagName = getFirstChild(javadocTagSection).getText();
323         }
324         return javadocTagName;
325     }
326 
327     /**
328      * Replace all control chars with escaped symbols.
329      *
330      * @param text the String to process.
331      * @return the processed String with all control chars escaped.
332      */
333     public static String escapeAllControlChars(String text) {
334         final String textWithoutNewlines = NEWLINE.matcher(text).replaceAll("\\\\n");
335         final String textWithoutReturns = RETURN.matcher(textWithoutNewlines).replaceAll("\\\\r");
336         return TAB.matcher(textWithoutReturns).replaceAll("\\\\t");
337     }
338 
339     /**
340      * Checks Javadoc comment it's in right place.
341      * <p>From Javadoc util documentation:
342      * "Placement of comments - Documentation comments are recognized only when placed
343      * immediately before class, interface, constructor, method, field or annotation field
344      * declarations -- see the class example, method example, and field example.
345      * Documentation comments placed in the body of a method are ignored."</p>
346      * <p>If there are many documentation comments per declaration statement,
347      * only the last one will be recognized.</p>
348      *
349      * @param blockComment Block comment AST
350      * @return true if Javadoc is in right place
351      * @see <a href="https://docs.oracle.com/javase/8/docs/technotes/tools/unix/javadoc.html">
352      *     Javadoc util documentation</a>
353      */
354     public static boolean isCorrectJavadocPosition(DetailAST blockComment) {
355         // We must be sure that after this one there are no other documentation comments.
356         DetailAST sibling = blockComment.getNextSibling();
357         while (sibling != null) {
358             if (sibling.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) {
359                 if (isJavadocComment(getBlockCommentContent(sibling))) {
360                     // Found another javadoc comment, so this one should be ignored.
361                     break;
362                 }
363                 sibling = sibling.getNextSibling();
364             }
365             else if (sibling.getType() == TokenTypes.SINGLE_LINE_COMMENT) {
366                 sibling = sibling.getNextSibling();
367             }
368             else {
369                 // Annotation, declaration or modifier is here. Do not check further.
370                 sibling = null;
371             }
372         }
373         return sibling == null
374             && (BlockCommentPosition.isOnType(blockComment)
375                 || BlockCommentPosition.isOnMember(blockComment)
376                 || BlockCommentPosition.isOnPackage(blockComment));
377     }
378 
379 }