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