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