001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2016 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018//////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.utils; 021 022import java.lang.reflect.Field; 023import java.lang.reflect.Modifier; 024import java.util.List; 025import java.util.regex.Matcher; 026import java.util.regex.Pattern; 027 028import com.google.common.collect.ImmutableMap; 029import com.google.common.collect.Lists; 030import com.puppycrawl.tools.checkstyle.api.DetailAST; 031import com.puppycrawl.tools.checkstyle.api.DetailNode; 032import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes; 033import com.puppycrawl.tools.checkstyle.api.TextBlock; 034import com.puppycrawl.tools.checkstyle.checks.javadoc.InvalidJavadocTag; 035import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTag; 036import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTagInfo; 037import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTags; 038 039/** 040 * Contains utility methods for working with Javadoc. 041 * @author Lyle Hanson 042 */ 043public final class JavadocUtils { 044 045 /** 046 * The type of Javadoc tag we want returned. 047 */ 048 public enum JavadocTagType { 049 /** Block type. */ 050 BLOCK, 051 /** Inline type. */ 052 INLINE, 053 /** All validTags. */ 054 ALL 055 } 056 057 /** Maps from a token name to value. */ 058 private static final ImmutableMap<String, Integer> TOKEN_NAME_TO_VALUE; 059 /** Maps from a token value to name. */ 060 private static final String[] TOKEN_VALUE_TO_NAME; 061 062 /** Exception message for unknown JavaDoc token id. */ 063 private static final String UNKNOWN_JAVADOC_TOKEN_ID_EXCEPTION_MESSAGE = "Unknown javadoc" 064 + " token id. Given id: "; 065 066 /** Comment pattern. */ 067 private static final Pattern COMMENT_PATTERN = Pattern.compile( 068 "^\\s*(?:/\\*{2,}|\\*+)\\s*(.*)"); 069 070 /** Block tag pattern for a first line. */ 071 private static final Pattern BLOCK_TAG_PATTERN_FIRST_LINE = Pattern.compile( 072 "/\\*{2,}\\s*@(\\p{Alpha}+)\\s"); 073 074 /** Block tag pattern. */ 075 private static final Pattern BLOCK_TAG_PATTERN = Pattern.compile( 076 "^\\s*\\**\\s*@(\\p{Alpha}+)\\s"); 077 078 /** Inline tag pattern. */ 079 private static final Pattern INLINE_TAG_PATTERN = Pattern.compile( 080 ".*?\\{@(\\p{Alpha}+)\\s+(.*?)\\}"); 081 082 /** Newline pattern. */ 083 private static final Pattern NEWLINE = Pattern.compile("\n"); 084 085 /** Return pattern. */ 086 private static final Pattern RETURN = Pattern.compile("\r"); 087 088 /** Tab pattern. */ 089 private static final Pattern TAB = Pattern.compile("\t"); 090 091 // Using reflection gets all token names and values from JavadocTokenTypes class 092 // and saves to TOKEN_NAME_TO_VALUE and TOKEN_VALUE_TO_NAME collections. 093 static { 094 final ImmutableMap.Builder<String, Integer> builder = ImmutableMap.builder(); 095 096 final Field[] fields = JavadocTokenTypes.class.getDeclaredFields(); 097 098 String[] tempTokenValueToName = CommonUtils.EMPTY_STRING_ARRAY; 099 100 for (final Field field : fields) { 101 102 // Only process public int fields. 103 if (!Modifier.isPublic(field.getModifiers()) 104 || field.getType() != Integer.TYPE) { 105 continue; 106 } 107 108 final String name = field.getName(); 109 110 final int tokenValue = TokenUtils.getIntFromField(field, name); 111 builder.put(name, tokenValue); 112 if (tokenValue > tempTokenValueToName.length - 1) { 113 final String[] temp = new String[tokenValue + 1]; 114 System.arraycopy(tempTokenValueToName, 0, temp, 0, tempTokenValueToName.length); 115 tempTokenValueToName = temp; 116 } 117 if (tokenValue == -1) { 118 tempTokenValueToName[0] = name; 119 } 120 else { 121 tempTokenValueToName[tokenValue] = name; 122 } 123 } 124 125 TOKEN_NAME_TO_VALUE = builder.build(); 126 TOKEN_VALUE_TO_NAME = tempTokenValueToName; 127 } 128 129 /** Prevent instantiation. */ 130 private JavadocUtils() { 131 } 132 133 /** 134 * Gets validTags from a given piece of Javadoc. 135 * @param textBlock 136 * the Javadoc comment to process. 137 * @param tagType 138 * the type of validTags we're interested in 139 * @return all standalone validTags from the given javadoc. 140 */ 141 public static JavadocTags getJavadocTags(TextBlock textBlock, 142 JavadocTagType tagType) { 143 final String[] text = textBlock.getText(); 144 final List<JavadocTag> tags = Lists.newArrayList(); 145 final List<InvalidJavadocTag> invalidTags = Lists.newArrayList(); 146 for (int i = 0; i < text.length; i++) { 147 final String textValue = text[i]; 148 final Matcher blockTagMatcher = getBlockTagPattern(i).matcher(textValue); 149 if ((tagType == JavadocTagType.ALL || tagType == JavadocTagType.BLOCK) 150 && blockTagMatcher.find()) { 151 final String tagName = blockTagMatcher.group(1); 152 String content = textValue.substring(blockTagMatcher.end(1)); 153 if (content.endsWith("*/")) { 154 content = content.substring(0, content.length() - 2); 155 } 156 final int line = textBlock.getStartLineNo() + i; 157 int col = blockTagMatcher.start(1) - 1; 158 if (i == 0) { 159 col += textBlock.getStartColNo(); 160 } 161 if (JavadocTagInfo.isValidName(tagName)) { 162 tags.add( 163 new JavadocTag(line, col, tagName, content.trim())); 164 } 165 else { 166 invalidTags.add(new InvalidJavadocTag(line, col, tagName)); 167 } 168 } 169 // No block tag, so look for inline validTags 170 else if (tagType == JavadocTagType.ALL || tagType == JavadocTagType.INLINE) { 171 lookForInlineTags(textBlock, i, tags, invalidTags); 172 } 173 } 174 return new JavadocTags(tags, invalidTags); 175 } 176 177 /** 178 * Get a block tag pattern depending on a line number of a javadoc. 179 * @param lineNumber the line number. 180 * @return a block tag pattern. 181 */ 182 private static Pattern getBlockTagPattern(int lineNumber) { 183 final Pattern blockTagPattern; 184 if (lineNumber == 0) { 185 blockTagPattern = BLOCK_TAG_PATTERN_FIRST_LINE; 186 } 187 else { 188 blockTagPattern = BLOCK_TAG_PATTERN; 189 } 190 return blockTagPattern; 191 } 192 193 /** 194 * Looks for inline tags in comment and adds them to the proper tags collection. 195 * @param comment comment text block 196 * @param lineNumber line number in the comment 197 * @param validTags collection of valid tags 198 * @param invalidTags collection of invalid tags 199 */ 200 private static void lookForInlineTags(TextBlock comment, int lineNumber, 201 final List<JavadocTag> validTags, final List<InvalidJavadocTag> invalidTags) { 202 final String text = comment.getText()[lineNumber]; 203 // Match Javadoc text after comment characters 204 final Matcher commentMatcher = COMMENT_PATTERN.matcher(text); 205 final String commentContents; 206 207 // offset including comment characters 208 final int commentOffset; 209 210 if (commentMatcher.find()) { 211 commentContents = commentMatcher.group(1); 212 commentOffset = commentMatcher.start(1) - 1; 213 } 214 else { 215 // No leading asterisks, still valid 216 commentContents = text; 217 commentOffset = 0; 218 } 219 final Matcher tagMatcher = INLINE_TAG_PATTERN.matcher(commentContents); 220 while (tagMatcher.find()) { 221 final String tagName = tagMatcher.group(1); 222 final String tagValue = tagMatcher.group(2).trim(); 223 final int line = comment.getStartLineNo() + lineNumber; 224 int col = commentOffset + tagMatcher.start(1) - 1; 225 if (lineNumber == 0) { 226 col += comment.getStartColNo(); 227 } 228 if (JavadocTagInfo.isValidName(tagName)) { 229 validTags.add(new JavadocTag(line, col, tagName, 230 tagValue)); 231 } 232 else { 233 invalidTags.add(new InvalidJavadocTag(line, col, 234 tagName)); 235 } 236 } 237 } 238 239 /** 240 * Checks that commentContent starts with '*' javadoc comment identifier. 241 * @param commentContent 242 * content of block comment 243 * @return true if commentContent starts with '*' javadoc comment 244 * identifier. 245 */ 246 public static boolean isJavadocComment(String commentContent) { 247 boolean result = false; 248 249 if (!commentContent.isEmpty()) { 250 final char docCommentIdentificator = commentContent.charAt(0); 251 result = docCommentIdentificator == '*'; 252 } 253 254 return result; 255 } 256 257 /** 258 * Checks block comment content starts with '*' javadoc comment identifier. 259 * @param blockCommentBegin 260 * block comment AST 261 * @return true if block comment content starts with '*' javadoc comment 262 * identifier. 263 */ 264 public static boolean isJavadocComment(DetailAST blockCommentBegin) { 265 final String commentContent = getBlockCommentContent(blockCommentBegin); 266 return isJavadocComment(commentContent); 267 } 268 269 /** 270 * Gets content of block comment. 271 * @param blockCommentBegin 272 * block comment AST. 273 * @return content of block comment. 274 */ 275 private static String getBlockCommentContent(DetailAST blockCommentBegin) { 276 final DetailAST commentContent = blockCommentBegin.getFirstChild(); 277 return commentContent.getText(); 278 } 279 280 /** 281 * Get content of Javadoc comment. 282 * @param javadocCommentBegin 283 * Javadoc comment AST 284 * @return content of Javadoc comment. 285 */ 286 public static String getJavadocCommentContent(DetailAST javadocCommentBegin) { 287 final DetailAST commentContent = javadocCommentBegin.getFirstChild(); 288 return commentContent.getText().substring(1); 289 } 290 291 /** 292 * Returns the first child token that has a specified type. 293 * @param detailNode 294 * Javadoc AST node 295 * @param type 296 * the token type to match 297 * @return the matching token, or null if no match 298 */ 299 public static DetailNode findFirstToken(DetailNode detailNode, int type) { 300 DetailNode returnValue = null; 301 DetailNode node = getFirstChild(detailNode); 302 while (node != null) { 303 if (node.getType() == type) { 304 returnValue = node; 305 break; 306 } 307 node = getNextSibling(node); 308 } 309 return returnValue; 310 } 311 312 /** 313 * Gets first child node of specified node. 314 * 315 * @param node DetailNode 316 * @return first child 317 */ 318 public static DetailNode getFirstChild(DetailNode node) { 319 DetailNode resultNode = null; 320 321 if (node.getChildren().length > 0) { 322 resultNode = node.getChildren()[0]; 323 } 324 return resultNode; 325 } 326 327 /** 328 * Checks whether node contains any node of specified type among children on any deep level. 329 * 330 * @param node DetailNode 331 * @param type token type 332 * @return true if node contains any node of type type among children on any deep level. 333 */ 334 public static boolean containsInBranch(DetailNode node, int type) { 335 DetailNode curNode = node; 336 while (true) { 337 338 if (type == curNode.getType()) { 339 return true; 340 } 341 342 DetailNode toVisit = getFirstChild(curNode); 343 while (curNode != null && toVisit == null) { 344 toVisit = getNextSibling(curNode); 345 if (toVisit == null) { 346 curNode = curNode.getParent(); 347 } 348 } 349 350 if (curNode == toVisit) { 351 break; 352 } 353 354 curNode = toVisit; 355 } 356 357 return false; 358 } 359 360 /** 361 * Gets next sibling of specified node. 362 * 363 * @param node DetailNode 364 * @return next sibling. 365 */ 366 public static DetailNode getNextSibling(DetailNode node) { 367 final DetailNode parent = node.getParent(); 368 if (parent != null) { 369 final int nextSiblingIndex = node.getIndex() + 1; 370 final DetailNode[] children = parent.getChildren(); 371 if (nextSiblingIndex <= children.length - 1) { 372 return children[nextSiblingIndex]; 373 } 374 } 375 return null; 376 } 377 378 /** 379 * Gets next sibling of specified node with the specified type. 380 * 381 * @param node DetailNode 382 * @param tokenType javadoc token type 383 * @return next sibling. 384 */ 385 public static DetailNode getNextSibling(DetailNode node, int tokenType) { 386 DetailNode nextSibling = getNextSibling(node); 387 while (nextSibling != null && nextSibling.getType() != tokenType) { 388 nextSibling = getNextSibling(nextSibling); 389 } 390 return nextSibling; 391 } 392 393 /** 394 * Gets previous sibling of specified node. 395 * @param node DetailNode 396 * @return previous sibling 397 */ 398 public static DetailNode getPreviousSibling(DetailNode node) { 399 final DetailNode parent = node.getParent(); 400 final int previousSiblingIndex = node.getIndex() - 1; 401 final DetailNode[] children = parent.getChildren(); 402 if (previousSiblingIndex >= 0) { 403 return children[previousSiblingIndex]; 404 } 405 return null; 406 } 407 408 /** 409 * Returns the name of a token for a given ID. 410 * @param id 411 * the ID of the token name to get 412 * @return a token name 413 */ 414 public static String getTokenName(int id) { 415 if (id == JavadocTokenTypes.EOF) { 416 return "EOF"; 417 } 418 if (id > TOKEN_VALUE_TO_NAME.length - 1) { 419 throw new IllegalArgumentException(UNKNOWN_JAVADOC_TOKEN_ID_EXCEPTION_MESSAGE + id); 420 } 421 final String name = TOKEN_VALUE_TO_NAME[id]; 422 if (name == null) { 423 throw new IllegalArgumentException(UNKNOWN_JAVADOC_TOKEN_ID_EXCEPTION_MESSAGE + id); 424 } 425 return name; 426 } 427 428 /** 429 * Returns the ID of a token for a given name. 430 * @param name 431 * the name of the token ID to get 432 * @return a token ID 433 */ 434 public static int getTokenId(String name) { 435 final Integer id = TOKEN_NAME_TO_VALUE.get(name); 436 if (id == null) { 437 throw new IllegalArgumentException("Unknown javadoc token name. Given name " + name); 438 } 439 return id; 440 } 441 442 /** 443 * Gets tag name from javadocTagSection. 444 * 445 * @param javadocTagSection to get tag name from. 446 * @return name, of the javadocTagSection's tag. 447 */ 448 public static String getTagName(DetailNode javadocTagSection) { 449 final String javadocTagName; 450 if (javadocTagSection.getType() == JavadocTokenTypes.JAVADOC_INLINE_TAG) { 451 javadocTagName = getNextSibling( 452 getFirstChild(javadocTagSection)).getText(); 453 } 454 else { 455 javadocTagName = getFirstChild(javadocTagSection).getText(); 456 } 457 return javadocTagName; 458 } 459 460 /** 461 * Replace all control chars with excaped symbols. 462 * @param text the String to process. 463 * @return the processed String with all control chars excaped. 464 */ 465 public static String excapeAllControlChars(String text) { 466 final String textWithoutNewlines = NEWLINE.matcher(text).replaceAll("\\\\n"); 467 final String textWithoutReturns = RETURN.matcher(textWithoutNewlines).replaceAll("\\\\r"); 468 return TAB.matcher(textWithoutReturns).replaceAll("\\\\t"); 469 } 470}