001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2017 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.ArrayList; 025import java.util.List; 026import java.util.regex.Pattern; 027 028import com.google.common.collect.ImmutableMap; 029import com.puppycrawl.tools.checkstyle.api.DetailAST; 030import com.puppycrawl.tools.checkstyle.api.DetailNode; 031import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes; 032import com.puppycrawl.tools.checkstyle.api.TextBlock; 033import com.puppycrawl.tools.checkstyle.checks.javadoc.InvalidJavadocTag; 034import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTag; 035import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTagInfo; 036import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTags; 037import com.puppycrawl.tools.checkstyle.checks.javadoc.utils.BlockTagUtils; 038import com.puppycrawl.tools.checkstyle.checks.javadoc.utils.InlineTagUtils; 039import com.puppycrawl.tools.checkstyle.checks.javadoc.utils.TagInfo; 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 /** Newline pattern. */ 069 private static final Pattern NEWLINE = Pattern.compile("\n"); 070 071 /** Return pattern. */ 072 private static final Pattern RETURN = Pattern.compile("\r"); 073 074 /** Tab pattern. */ 075 private static final Pattern TAB = Pattern.compile("\t"); 076 077 // Using reflection gets all token names and values from JavadocTokenTypes class 078 // and saves to TOKEN_NAME_TO_VALUE and TOKEN_VALUE_TO_NAME collections. 079 static { 080 final ImmutableMap.Builder<String, Integer> builder = ImmutableMap.builder(); 081 082 final Field[] fields = JavadocTokenTypes.class.getDeclaredFields(); 083 084 String[] tempTokenValueToName = CommonUtils.EMPTY_STRING_ARRAY; 085 086 for (final Field field : fields) { 087 088 // Only process public int fields. 089 if (!Modifier.isPublic(field.getModifiers()) 090 || field.getType() != Integer.TYPE) { 091 continue; 092 } 093 094 final String name = field.getName(); 095 096 final int tokenValue = TokenUtils.getIntFromField(field, name); 097 builder.put(name, tokenValue); 098 if (tokenValue > tempTokenValueToName.length - 1) { 099 final String[] temp = new String[tokenValue + 1]; 100 System.arraycopy(tempTokenValueToName, 0, temp, 0, tempTokenValueToName.length); 101 tempTokenValueToName = temp; 102 } 103 if (tokenValue == -1) { 104 tempTokenValueToName[0] = name; 105 } 106 else { 107 tempTokenValueToName[tokenValue] = name; 108 } 109 } 110 111 TOKEN_NAME_TO_VALUE = builder.build(); 112 TOKEN_VALUE_TO_NAME = tempTokenValueToName; 113 } 114 115 /** Prevent instantiation. */ 116 private JavadocUtils() { 117 } 118 119 /** 120 * Gets validTags from a given piece of Javadoc. 121 * @param textBlock 122 * the Javadoc comment to process. 123 * @param tagType 124 * the type of validTags we're interested in 125 * @return all standalone validTags from the given javadoc. 126 */ 127 public static JavadocTags getJavadocTags(TextBlock textBlock, 128 JavadocTagType tagType) { 129 130 final boolean getBlockTags = tagType == JavadocTagType.ALL 131 || tagType == JavadocTagType.BLOCK; 132 final boolean getInlineTags = tagType == JavadocTagType.ALL 133 || tagType == JavadocTagType.INLINE; 134 135 final List<TagInfo> tags = new ArrayList<>(); 136 137 if (getBlockTags) { 138 tags.addAll(BlockTagUtils.extractBlockTags(textBlock.getText())); 139 } 140 141 if (getInlineTags) { 142 tags.addAll(InlineTagUtils.extractInlineTags(textBlock.getText())); 143 } 144 145 final List<JavadocTag> validTags = new ArrayList<>(); 146 final List<InvalidJavadocTag> invalidTags = new ArrayList<>(); 147 148 for (TagInfo tag : tags) { 149 final int col = tag.getPosition().getColumn(); 150 151 // Add the starting line of the comment to the line number to get the actual line number 152 // in the source. 153 // Lines are one-indexed, so need a off-by-one correction. 154 final int line = textBlock.getStartLineNo() + tag.getPosition().getLine() - 1; 155 156 if (JavadocTagInfo.isValidName(tag.getName())) { 157 validTags.add( 158 new JavadocTag(line, col, tag.getName(), tag.getValue())); 159 } 160 else { 161 invalidTags.add(new InvalidJavadocTag(line, col, tag.getName())); 162 } 163 } 164 165 return new JavadocTags(validTags, invalidTags); 166 } 167 168 /** 169 * Checks that commentContent starts with '*' javadoc comment identifier. 170 * @param commentContent 171 * content of block comment 172 * @return true if commentContent starts with '*' javadoc comment 173 * identifier. 174 */ 175 public static boolean isJavadocComment(String commentContent) { 176 boolean result = false; 177 178 if (!commentContent.isEmpty()) { 179 final char docCommentIdentificator = commentContent.charAt(0); 180 result = docCommentIdentificator == '*'; 181 } 182 183 return result; 184 } 185 186 /** 187 * Checks block comment content starts with '*' javadoc comment identifier. 188 * @param blockCommentBegin 189 * block comment AST 190 * @return true if block comment content starts with '*' javadoc comment 191 * identifier. 192 */ 193 public static boolean isJavadocComment(DetailAST blockCommentBegin) { 194 final String commentContent = getBlockCommentContent(blockCommentBegin); 195 return isJavadocComment(commentContent) && isCorrectJavadocPosition(blockCommentBegin); 196 } 197 198 /** 199 * Gets content of block comment. 200 * @param blockCommentBegin 201 * block comment AST. 202 * @return content of block comment. 203 */ 204 private static String getBlockCommentContent(DetailAST blockCommentBegin) { 205 final DetailAST commentContent = blockCommentBegin.getFirstChild(); 206 return commentContent.getText(); 207 } 208 209 /** 210 * Get content of Javadoc comment. 211 * @param javadocCommentBegin 212 * Javadoc comment AST 213 * @return content of Javadoc comment. 214 */ 215 public static String getJavadocCommentContent(DetailAST javadocCommentBegin) { 216 final DetailAST commentContent = javadocCommentBegin.getFirstChild(); 217 return commentContent.getText().substring(1); 218 } 219 220 /** 221 * Returns the first child token that has a specified type. 222 * @param detailNode 223 * Javadoc AST node 224 * @param type 225 * the token type to match 226 * @return the matching token, or null if no match 227 */ 228 public static DetailNode findFirstToken(DetailNode detailNode, int type) { 229 DetailNode returnValue = null; 230 DetailNode node = getFirstChild(detailNode); 231 while (node != null) { 232 if (node.getType() == type) { 233 returnValue = node; 234 break; 235 } 236 node = getNextSibling(node); 237 } 238 return returnValue; 239 } 240 241 /** 242 * Gets first child node of specified node. 243 * 244 * @param node DetailNode 245 * @return first child 246 */ 247 public static DetailNode getFirstChild(DetailNode node) { 248 DetailNode resultNode = null; 249 250 if (node.getChildren().length > 0) { 251 resultNode = node.getChildren()[0]; 252 } 253 return resultNode; 254 } 255 256 /** 257 * Checks whether node contains any node of specified type among children on any deep level. 258 * 259 * @param node DetailNode 260 * @param type token type 261 * @return true if node contains any node of type type among children on any deep level. 262 */ 263 public static boolean containsInBranch(DetailNode node, int type) { 264 boolean result = true; 265 DetailNode curNode = node; 266 while (type != curNode.getType()) { 267 DetailNode toVisit = getFirstChild(curNode); 268 while (curNode != null && toVisit == null) { 269 toVisit = getNextSibling(curNode); 270 if (toVisit == null) { 271 curNode = curNode.getParent(); 272 } 273 } 274 275 if (curNode == toVisit) { 276 result = false; 277 break; 278 } 279 280 curNode = toVisit; 281 } 282 return result; 283 } 284 285 /** 286 * Gets next sibling of specified node. 287 * 288 * @param node DetailNode 289 * @return next sibling. 290 */ 291 public static DetailNode getNextSibling(DetailNode node) { 292 DetailNode nextSibling = null; 293 final DetailNode parent = node.getParent(); 294 if (parent != null) { 295 final int nextSiblingIndex = node.getIndex() + 1; 296 final DetailNode[] children = parent.getChildren(); 297 if (nextSiblingIndex <= children.length - 1) { 298 nextSibling = children[nextSiblingIndex]; 299 } 300 } 301 return nextSibling; 302 } 303 304 /** 305 * Gets next sibling of specified node with the specified type. 306 * 307 * @param node DetailNode 308 * @param tokenType javadoc token type 309 * @return next sibling. 310 */ 311 public static DetailNode getNextSibling(DetailNode node, int tokenType) { 312 DetailNode nextSibling = getNextSibling(node); 313 while (nextSibling != null && nextSibling.getType() != tokenType) { 314 nextSibling = getNextSibling(nextSibling); 315 } 316 return nextSibling; 317 } 318 319 /** 320 * Gets previous sibling of specified node. 321 * @param node DetailNode 322 * @return previous sibling 323 */ 324 public static DetailNode getPreviousSibling(DetailNode node) { 325 DetailNode previousSibling = null; 326 final int previousSiblingIndex = node.getIndex() - 1; 327 if (previousSiblingIndex >= 0) { 328 final DetailNode parent = node.getParent(); 329 final DetailNode[] children = parent.getChildren(); 330 previousSibling = children[previousSiblingIndex]; 331 } 332 return previousSibling; 333 } 334 335 /** 336 * Returns the name of a token for a given ID. 337 * @param id 338 * the ID of the token name to get 339 * @return a token name 340 */ 341 public static String getTokenName(int id) { 342 final String name; 343 if (id == JavadocTokenTypes.EOF) { 344 name = "EOF"; 345 } 346 else if (id >= TOKEN_VALUE_TO_NAME.length) { 347 throw new IllegalArgumentException(UNKNOWN_JAVADOC_TOKEN_ID_EXCEPTION_MESSAGE + id); 348 } 349 else { 350 name = TOKEN_VALUE_TO_NAME[id]; 351 if (name == null) { 352 throw new IllegalArgumentException(UNKNOWN_JAVADOC_TOKEN_ID_EXCEPTION_MESSAGE + id); 353 } 354 } 355 return name; 356 } 357 358 /** 359 * Returns the ID of a token for a given name. 360 * @param name 361 * the name of the token ID to get 362 * @return a token ID 363 */ 364 public static int getTokenId(String name) { 365 final Integer id = TOKEN_NAME_TO_VALUE.get(name); 366 if (id == null) { 367 throw new IllegalArgumentException("Unknown javadoc token name. Given name " + name); 368 } 369 return id; 370 } 371 372 /** 373 * Gets tag name from javadocTagSection. 374 * 375 * @param javadocTagSection to get tag name from. 376 * @return name, of the javadocTagSection's tag. 377 */ 378 public static String getTagName(DetailNode javadocTagSection) { 379 final String javadocTagName; 380 if (javadocTagSection.getType() == JavadocTokenTypes.JAVADOC_INLINE_TAG) { 381 javadocTagName = getNextSibling( 382 getFirstChild(javadocTagSection)).getText(); 383 } 384 else { 385 javadocTagName = getFirstChild(javadocTagSection).getText(); 386 } 387 return javadocTagName; 388 } 389 390 /** 391 * Replace all control chars with escaped symbols. 392 * @param text the String to process. 393 * @return the processed String with all control chars escaped. 394 */ 395 public static String escapeAllControlChars(String text) { 396 final String textWithoutNewlines = NEWLINE.matcher(text).replaceAll("\\\\n"); 397 final String textWithoutReturns = RETURN.matcher(textWithoutNewlines).replaceAll("\\\\r"); 398 return TAB.matcher(textWithoutReturns).replaceAll("\\\\t"); 399 } 400 401 /** 402 * Checks Javadoc comment it's in right place. 403 * From Javadoc util documentation: 404 * "Placement of comments - Documentation comments are recognized only when placed 405 * immediately before class, interface, constructor, method, or field 406 * declarations -- see the class example, method example, and field example. 407 * Documentation comments placed in the body of a method are ignored. Only one 408 * documentation comment per declaration statement is recognized by the Javadoc tool." 409 * 410 * @param blockComment Block comment AST 411 * @return true if Javadoc is in right place 412 */ 413 private static boolean isCorrectJavadocPosition(DetailAST blockComment) { 414 return BlockCommentPosition.isOnClass(blockComment) 415 || BlockCommentPosition.isOnInterface(blockComment) 416 || BlockCommentPosition.isOnEnum(blockComment) 417 || BlockCommentPosition.isOnMethod(blockComment) 418 || BlockCommentPosition.isOnField(blockComment) 419 || BlockCommentPosition.isOnConstructor(blockComment) 420 || BlockCommentPosition.isOnEnumConstant(blockComment) 421 || BlockCommentPosition.isOnAnnotationDef(blockComment); 422 } 423}