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