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