Coverage Report - com.puppycrawl.tools.checkstyle.utils.JavadocUtils
 
Classes in this File Line Coverage Branch Coverage Complexity
JavadocUtils
100%
131/131
100%
74/74
3.059
JavadocUtils$JavadocTagType
100%
4/4
N/A
3.059
 
 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.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  7
     public enum JavadocTagType {
 52  
 
 53  
         /** Block type. */
 54  1
         BLOCK,
 55  
         /** Inline type. */
 56  1
         INLINE,
 57  
         /** All validTags. */
 58  1
         ALL
 59  
 
 60  
     }
 61  
 
 62  
     /** Maps from a token name to value. */
 63  
     private static final ImmutableMap<String, Integer> TOKEN_NAME_TO_VALUE;
 64  
     /** Maps from a token value to name. */
 65  
     private static final String[] TOKEN_VALUE_TO_NAME;
 66  
 
 67  
     /** Exception message for unknown JavaDoc token id. */
 68  
     private static final String UNKNOWN_JAVADOC_TOKEN_ID_EXCEPTION_MESSAGE = "Unknown javadoc"
 69  
             + " token id. Given id: ";
 70  
 
 71  
     /** Newline pattern. */
 72  2
     private static final Pattern NEWLINE = Pattern.compile("\n");
 73  
 
 74  
     /** Return pattern. */
 75  2
     private static final Pattern RETURN = Pattern.compile("\r");
 76  
 
 77  
     /** Tab pattern. */
 78  2
     private static final Pattern TAB = Pattern.compile("\t");
 79  
 
 80  
     // Using reflection gets all token names and values from JavadocTokenTypes class
 81  
     // and saves to TOKEN_NAME_TO_VALUE and TOKEN_VALUE_TO_NAME collections.
 82  
     static {
 83  2
         final ImmutableMap.Builder<String, Integer> builder = ImmutableMap.builder();
 84  
 
 85  2
         final Field[] fields = JavadocTokenTypes.class.getDeclaredFields();
 86  
 
 87  2
         String[] tempTokenValueToName = CommonUtils.EMPTY_STRING_ARRAY;
 88  
 
 89  330
         for (final Field field : fields) {
 90  
             // Only process public int fields.
 91  328
             if (!Modifier.isPublic(field.getModifiers())
 92  326
                     || field.getType() != Integer.TYPE) {
 93  2
                 continue;
 94  
             }
 95  
 
 96  324
             final String name = field.getName();
 97  
 
 98  324
             final int tokenValue = TokenUtils.getIntFromField(field, name);
 99  324
             builder.put(name, tokenValue);
 100  324
             if (tokenValue > tempTokenValueToName.length - 1) {
 101  38
                 final String[] temp = new String[tokenValue + 1];
 102  38
                 System.arraycopy(tempTokenValueToName, 0, temp, 0, tempTokenValueToName.length);
 103  38
                 tempTokenValueToName = temp;
 104  
             }
 105  324
             if (tokenValue == -1) {
 106  2
                 tempTokenValueToName[0] = name;
 107  
             }
 108  
             else {
 109  322
                 tempTokenValueToName[tokenValue] = name;
 110  
             }
 111  
         }
 112  
 
 113  2
         TOKEN_NAME_TO_VALUE = builder.build();
 114  2
         TOKEN_VALUE_TO_NAME = tempTokenValueToName;
 115  2
     }
 116  
 
 117  
     /** Prevent instantiation. */
 118  1
     private JavadocUtils() {
 119  1
     }
 120  
 
 121  
     /**
 122  
      * Gets validTags from a given piece of Javadoc.
 123  
      * @param textBlock
 124  
      *        the Javadoc comment to process.
 125  
      * @param tagType
 126  
      *        the type of validTags we're interested in
 127  
      * @return all standalone validTags from the given javadoc.
 128  
      */
 129  
     public static JavadocTags getJavadocTags(TextBlock textBlock,
 130  
             JavadocTagType tagType) {
 131  120
         final boolean getBlockTags = tagType == JavadocTagType.ALL
 132  
                                          || tagType == JavadocTagType.BLOCK;
 133  120
         final boolean getInlineTags = tagType == JavadocTagType.ALL
 134  
                                           || tagType == JavadocTagType.INLINE;
 135  
 
 136  120
         final List<TagInfo> tags = new ArrayList<>();
 137  
 
 138  120
         if (getBlockTags) {
 139  100
             tags.addAll(BlockTagUtils.extractBlockTags(textBlock.getText()));
 140  
         }
 141  
 
 142  120
         if (getInlineTags) {
 143  26
             tags.addAll(InlineTagUtils.extractInlineTags(textBlock.getText()));
 144  
         }
 145  
 
 146  120
         final List<JavadocTag> validTags = new ArrayList<>();
 147  120
         final List<InvalidJavadocTag> invalidTags = new ArrayList<>();
 148  
 
 149  120
         for (TagInfo tag : tags) {
 150  146
             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  146
             final int line = textBlock.getStartLineNo() + tag.getPosition().getLine() - 1;
 156  
 
 157  146
             if (JavadocTagInfo.isValidName(tag.getName())) {
 158  284
                 validTags.add(
 159  142
                     new JavadocTag(line, col, tag.getName(), tag.getValue()));
 160  
             }
 161  
             else {
 162  4
                 invalidTags.add(new InvalidJavadocTag(line, col, tag.getName()));
 163  
             }
 164  146
         }
 165  
 
 166  120
         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  1948
         boolean result = false;
 178  
 
 179  1948
         if (!commentContent.isEmpty()) {
 180  1946
             final char docCommentIdentifier = commentContent.charAt(0);
 181  1946
             result = docCommentIdentifier == '*';
 182  
         }
 183  
 
 184  1948
         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  1909
         final String commentContent = getBlockCommentContent(blockCommentBegin);
 196  1909
         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  1944
         final DetailAST commentContent = blockCommentBegin.getFirstChild();
 207  1944
         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  674
         final DetailAST commentContent = javadocCommentBegin.getFirstChild();
 218  674
         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  205
         DetailNode returnValue = null;
 231  205
         DetailNode node = getFirstChild(detailNode);
 232  455
         while (node != null) {
 233  421
             if (node.getType() == type) {
 234  171
                 returnValue = node;
 235  171
                 break;
 236  
             }
 237  250
             node = getNextSibling(node);
 238  
         }
 239  205
         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  20563
         DetailNode resultNode = null;
 250  
 
 251  20563
         if (node.getChildren().length > 0) {
 252  5417
             resultNode = node.getChildren()[0];
 253  
         }
 254  20563
         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  3
         boolean result = true;
 266  3
         DetailNode curNode = node;
 267  8
         while (type != curNode.getType()) {
 268  7
             DetailNode toVisit = getFirstChild(curNode);
 269  11
             while (curNode != null && toVisit == null) {
 270  4
                 toVisit = getNextSibling(curNode);
 271  4
                 if (toVisit == null) {
 272  1
                     curNode = curNode.getParent();
 273  
                 }
 274  
             }
 275  
 
 276  7
             if (curNode == toVisit) {
 277  2
                 result = false;
 278  2
                 break;
 279  
             }
 280  
 
 281  5
             curNode = toVisit;
 282  5
         }
 283  3
         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  66525
         DetailNode nextSibling = null;
 294  66525
         final DetailNode parent = node.getParent();
 295  66525
         if (parent != null) {
 296  65230
             final int nextSiblingIndex = node.getIndex() + 1;
 297  65230
             final DetailNode[] children = parent.getChildren();
 298  65230
             if (nextSiblingIndex <= children.length - 1) {
 299  45850
                 nextSibling = children[nextSiblingIndex];
 300  
             }
 301  
         }
 302  66525
         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  4
         DetailNode nextSibling = getNextSibling(node);
 314  9
         while (nextSibling != null && nextSibling.getType() != tokenType) {
 315  5
             nextSibling = getNextSibling(nextSibling);
 316  
         }
 317  4
         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  1027
         DetailNode previousSibling = null;
 327  1027
         final int previousSiblingIndex = node.getIndex() - 1;
 328  1027
         if (previousSiblingIndex >= 0) {
 329  943
             final DetailNode parent = node.getParent();
 330  943
             final DetailNode[] children = parent.getChildren();
 331  943
             previousSibling = children[previousSiblingIndex];
 332  
         }
 333  1027
         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  8123
         if (id == JavadocTokenTypes.EOF) {
 345  58
             name = "EOF";
 346  
         }
 347  8065
         else if (id > TOKEN_VALUE_TO_NAME.length - 1) {
 348  2
             throw new IllegalArgumentException(UNKNOWN_JAVADOC_TOKEN_ID_EXCEPTION_MESSAGE + id);
 349  
         }
 350  
         else {
 351  8063
             name = TOKEN_VALUE_TO_NAME[id];
 352  8063
             if (name == null) {
 353  1
                 throw new IllegalArgumentException(UNKNOWN_JAVADOC_TOKEN_ID_EXCEPTION_MESSAGE + id);
 354  
             }
 355  
         }
 356  8120
         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  8245
         final Integer id = TOKEN_NAME_TO_VALUE.get(name);
 367  8245
         if (id == null) {
 368  1
             throw new IllegalArgumentException("Unknown javadoc token name. Given name " + name);
 369  
         }
 370  8244
         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  18
         if (javadocTagSection.getType() == JavadocTokenTypes.JAVADOC_INLINE_TAG) {
 382  16
             javadocTagName = getNextSibling(
 383  16
                     getFirstChild(javadocTagSection)).getText();
 384  
         }
 385  
         else {
 386  10
             javadocTagName = getFirstChild(javadocTagSection).getText();
 387  
         }
 388  18
         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  7723
         final String textWithoutNewlines = NEWLINE.matcher(text).replaceAll("\\\\n");
 398  7723
         final String textWithoutReturns = RETURN.matcher(textWithoutNewlines).replaceAll("\\\\r");
 399  7723
         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  1877
         DetailAST sibling = blockComment.getNextSibling();
 420  3820
         while (sibling != null) {
 421  1963
             if (sibling.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) {
 422  35
                 if (isJavadocComment(getBlockCommentContent(sibling))) {
 423  
                     // Found another javadoc comment, so this one should be ignored.
 424  20
                     break;
 425  
                 }
 426  15
                 sibling = sibling.getNextSibling();
 427  
             }
 428  1928
             else if (sibling.getType() == TokenTypes.SINGLE_LINE_COMMENT) {
 429  75
                 sibling = sibling.getNextSibling();
 430  
             }
 431  
             else {
 432  
                 // Annotation, declaration or modifier is here. Do not check further.
 433  1853
                 sibling = null;
 434  
             }
 435  
         }
 436  3754
         return sibling == null
 437  1857
             && (BlockCommentPosition.isOnType(blockComment)
 438  1721
                 || BlockCommentPosition.isOnMember(blockComment));
 439  
     }
 440  
 
 441  
 }