001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2018 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.util.ArrayList; 023import java.util.List; 024import java.util.Map; 025import java.util.regex.Pattern; 026 027import com.puppycrawl.tools.checkstyle.api.DetailAST; 028import com.puppycrawl.tools.checkstyle.api.DetailNode; 029import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes; 030import com.puppycrawl.tools.checkstyle.api.TextBlock; 031import com.puppycrawl.tools.checkstyle.api.TokenTypes; 032import com.puppycrawl.tools.checkstyle.checks.javadoc.InvalidJavadocTag; 033import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTag; 034import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTagInfo; 035import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTags; 036import com.puppycrawl.tools.checkstyle.checks.javadoc.utils.BlockTagUtil; 037import com.puppycrawl.tools.checkstyle.checks.javadoc.utils.InlineTagUtil; 038import com.puppycrawl.tools.checkstyle.checks.javadoc.utils.TagInfo; 039 040/** 041 * Contains utility methods for working with Javadoc. 042 */ 043public final class JavadocUtil { 044 045 /** 046 * The type of Javadoc tag we want returned. 047 */ 048 public enum JavadocTagType { 049 050 /** Block type. */ 051 BLOCK, 052 /** Inline type. */ 053 INLINE, 054 /** All validTags. */ 055 ALL 056 057 } 058 059 /** Maps from a token name to value. */ 060 private static final Map<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 // initialise the constants 078 static { 079 TOKEN_NAME_TO_VALUE = TokenUtil.nameToValueMapFromPublicIntFields(JavadocTokenTypes.class); 080 TOKEN_VALUE_TO_NAME = TokenUtil.valueToNameArrayFromNameToValueMap(TOKEN_NAME_TO_VALUE); 081 } 082 083 /** Prevent instantiation. */ 084 private JavadocUtil() { 085 } 086 087 /** 088 * Gets validTags from a given piece of Javadoc. 089 * @param textBlock 090 * the Javadoc comment to process. 091 * @param tagType 092 * the type of validTags we're interested in 093 * @return all standalone validTags from the given javadoc. 094 */ 095 public static JavadocTags getJavadocTags(TextBlock textBlock, 096 JavadocTagType tagType) { 097 final boolean getBlockTags = tagType == JavadocTagType.ALL 098 || tagType == JavadocTagType.BLOCK; 099 final boolean getInlineTags = tagType == JavadocTagType.ALL 100 || tagType == JavadocTagType.INLINE; 101 102 final List<TagInfo> tags = new ArrayList<>(); 103 104 if (getBlockTags) { 105 tags.addAll(BlockTagUtil.extractBlockTags(textBlock.getText())); 106 } 107 108 if (getInlineTags) { 109 tags.addAll(InlineTagUtil.extractInlineTags(textBlock.getText())); 110 } 111 112 final List<JavadocTag> validTags = new ArrayList<>(); 113 final List<InvalidJavadocTag> invalidTags = new ArrayList<>(); 114 115 for (TagInfo tag : tags) { 116 final int col = tag.getPosition().getColumn(); 117 118 // Add the starting line of the comment to the line number to get the actual line number 119 // in the source. 120 // Lines are one-indexed, so need a off-by-one correction. 121 final int line = textBlock.getStartLineNo() + tag.getPosition().getLine() - 1; 122 123 if (JavadocTagInfo.isValidName(tag.getName())) { 124 validTags.add( 125 new JavadocTag(line, col, tag.getName(), tag.getValue())); 126 } 127 else { 128 invalidTags.add(new InvalidJavadocTag(line, col, tag.getName())); 129 } 130 } 131 132 return new JavadocTags(validTags, invalidTags); 133 } 134 135 /** 136 * Checks that commentContent starts with '*' javadoc comment identifier. 137 * @param commentContent 138 * content of block comment 139 * @return true if commentContent starts with '*' javadoc comment 140 * identifier. 141 */ 142 public static boolean isJavadocComment(String commentContent) { 143 boolean result = false; 144 145 if (!commentContent.isEmpty()) { 146 final char docCommentIdentifier = commentContent.charAt(0); 147 result = docCommentIdentifier == '*'; 148 } 149 150 return result; 151 } 152 153 /** 154 * Checks block comment content starts with '*' javadoc comment identifier. 155 * @param blockCommentBegin 156 * block comment AST 157 * @return true if block comment content starts with '*' javadoc comment 158 * identifier. 159 */ 160 public static boolean isJavadocComment(DetailAST blockCommentBegin) { 161 final String commentContent = getBlockCommentContent(blockCommentBegin); 162 return isJavadocComment(commentContent) && isCorrectJavadocPosition(blockCommentBegin); 163 } 164 165 /** 166 * Gets content of block comment. 167 * @param blockCommentBegin 168 * block comment AST. 169 * @return content of block comment. 170 */ 171 private static String getBlockCommentContent(DetailAST blockCommentBegin) { 172 final DetailAST commentContent = blockCommentBegin.getFirstChild(); 173 return commentContent.getText(); 174 } 175 176 /** 177 * Get content of Javadoc comment. 178 * @param javadocCommentBegin 179 * Javadoc comment AST 180 * @return content of Javadoc comment. 181 */ 182 public static String getJavadocCommentContent(DetailAST javadocCommentBegin) { 183 final DetailAST commentContent = javadocCommentBegin.getFirstChild(); 184 return commentContent.getText().substring(1); 185 } 186 187 /** 188 * Returns the first child token that has a specified type. 189 * @param detailNode 190 * Javadoc AST node 191 * @param type 192 * the token type to match 193 * @return the matching token, or null if no match 194 */ 195 public static DetailNode findFirstToken(DetailNode detailNode, int type) { 196 DetailNode returnValue = null; 197 DetailNode node = getFirstChild(detailNode); 198 while (node != null) { 199 if (node.getType() == type) { 200 returnValue = node; 201 break; 202 } 203 node = getNextSibling(node); 204 } 205 return returnValue; 206 } 207 208 /** 209 * Gets first child node of specified node. 210 * 211 * @param node DetailNode 212 * @return first child 213 */ 214 public static DetailNode getFirstChild(DetailNode node) { 215 DetailNode resultNode = null; 216 217 if (node.getChildren().length > 0) { 218 resultNode = node.getChildren()[0]; 219 } 220 return resultNode; 221 } 222 223 /** 224 * Checks whether node contains any node of specified type among children on any deep level. 225 * 226 * @param node DetailNode 227 * @param type token type 228 * @return true if node contains any node of type type among children on any deep level. 229 */ 230 public static boolean containsInBranch(DetailNode node, int type) { 231 boolean result = true; 232 DetailNode curNode = node; 233 while (type != curNode.getType()) { 234 DetailNode toVisit = getFirstChild(curNode); 235 while (curNode != null && toVisit == null) { 236 toVisit = getNextSibling(curNode); 237 if (toVisit == null) { 238 curNode = curNode.getParent(); 239 } 240 } 241 242 if (curNode == toVisit) { 243 result = false; 244 break; 245 } 246 247 curNode = toVisit; 248 } 249 return result; 250 } 251 252 /** 253 * Gets next sibling of specified node. 254 * 255 * @param node DetailNode 256 * @return next sibling. 257 */ 258 public static DetailNode getNextSibling(DetailNode node) { 259 DetailNode nextSibling = null; 260 final DetailNode parent = node.getParent(); 261 if (parent != null) { 262 final int nextSiblingIndex = node.getIndex() + 1; 263 final DetailNode[] children = parent.getChildren(); 264 if (nextSiblingIndex <= children.length - 1) { 265 nextSibling = children[nextSiblingIndex]; 266 } 267 } 268 return nextSibling; 269 } 270 271 /** 272 * Gets next sibling of specified node with the specified type. 273 * 274 * @param node DetailNode 275 * @param tokenType javadoc token type 276 * @return next sibling. 277 */ 278 public static DetailNode getNextSibling(DetailNode node, int tokenType) { 279 DetailNode nextSibling = getNextSibling(node); 280 while (nextSibling != null && nextSibling.getType() != tokenType) { 281 nextSibling = getNextSibling(nextSibling); 282 } 283 return nextSibling; 284 } 285 286 /** 287 * Gets previous sibling of specified node. 288 * @param node DetailNode 289 * @return previous sibling 290 */ 291 public static DetailNode getPreviousSibling(DetailNode node) { 292 DetailNode previousSibling = null; 293 final int previousSiblingIndex = node.getIndex() - 1; 294 if (previousSiblingIndex >= 0) { 295 final DetailNode parent = node.getParent(); 296 final DetailNode[] children = parent.getChildren(); 297 previousSibling = children[previousSiblingIndex]; 298 } 299 return previousSibling; 300 } 301 302 /** 303 * Returns the name of a token for a given ID. 304 * @param id 305 * the ID of the token name to get 306 * @return a token name 307 */ 308 public static String getTokenName(int id) { 309 final String name; 310 if (id == JavadocTokenTypes.EOF) { 311 name = "EOF"; 312 } 313 else if (id > TOKEN_VALUE_TO_NAME.length - 1) { 314 throw new IllegalArgumentException(UNKNOWN_JAVADOC_TOKEN_ID_EXCEPTION_MESSAGE + id); 315 } 316 else { 317 name = TOKEN_VALUE_TO_NAME[id]; 318 if (name == null) { 319 throw new IllegalArgumentException(UNKNOWN_JAVADOC_TOKEN_ID_EXCEPTION_MESSAGE + id); 320 } 321 } 322 return name; 323 } 324 325 /** 326 * Returns the ID of a token for a given name. 327 * @param name 328 * the name of the token ID to get 329 * @return a token ID 330 */ 331 public static int getTokenId(String name) { 332 final Integer id = TOKEN_NAME_TO_VALUE.get(name); 333 if (id == null) { 334 throw new IllegalArgumentException("Unknown javadoc token name. Given name " + name); 335 } 336 return id; 337 } 338 339 /** 340 * Gets tag name from javadocTagSection. 341 * 342 * @param javadocTagSection to get tag name from. 343 * @return name, of the javadocTagSection's tag. 344 */ 345 public static String getTagName(DetailNode javadocTagSection) { 346 final String javadocTagName; 347 if (javadocTagSection.getType() == JavadocTokenTypes.JAVADOC_INLINE_TAG) { 348 javadocTagName = getNextSibling( 349 getFirstChild(javadocTagSection)).getText(); 350 } 351 else { 352 javadocTagName = getFirstChild(javadocTagSection).getText(); 353 } 354 return javadocTagName; 355 } 356 357 /** 358 * Replace all control chars with escaped symbols. 359 * @param text the String to process. 360 * @return the processed String with all control chars escaped. 361 */ 362 public static String escapeAllControlChars(String text) { 363 final String textWithoutNewlines = NEWLINE.matcher(text).replaceAll("\\\\n"); 364 final String textWithoutReturns = RETURN.matcher(textWithoutNewlines).replaceAll("\\\\r"); 365 return TAB.matcher(textWithoutReturns).replaceAll("\\\\t"); 366 } 367 368 /** 369 * Checks Javadoc comment it's in right place. 370 * <p>From Javadoc util documentation: 371 * "Placement of comments - Documentation comments are recognized only when placed 372 * immediately before class, interface, constructor, method, field or annotation field 373 * declarations -- see the class example, method example, and field example. 374 * Documentation comments placed in the body of a method are ignored."</p> 375 * <p>If there are many documentation comments per declaration statement, 376 * only the last one will be recognized.</p> 377 * 378 * @param blockComment Block comment AST 379 * @return true if Javadoc is in right place 380 * @see <a href="https://docs.oracle.com/javase/8/docs/technotes/tools/unix/javadoc.html"> 381 * Javadoc util documentation</a> 382 */ 383 private static boolean isCorrectJavadocPosition(DetailAST blockComment) { 384 // We must be sure that after this one there are no other documentation comments. 385 DetailAST sibling = blockComment.getNextSibling(); 386 while (sibling != null) { 387 if (sibling.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) { 388 if (isJavadocComment(getBlockCommentContent(sibling))) { 389 // Found another javadoc comment, so this one should be ignored. 390 break; 391 } 392 sibling = sibling.getNextSibling(); 393 } 394 else if (sibling.getType() == TokenTypes.SINGLE_LINE_COMMENT) { 395 sibling = sibling.getNextSibling(); 396 } 397 else { 398 // Annotation, declaration or modifier is here. Do not check further. 399 sibling = null; 400 } 401 } 402 return sibling == null 403 && (BlockCommentPosition.isOnType(blockComment) 404 || BlockCommentPosition.isOnMember(blockComment)); 405 } 406 407}