001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 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 javax.annotation.Nullable; 028 029import com.puppycrawl.tools.checkstyle.api.DetailAST; 030import com.puppycrawl.tools.checkstyle.api.DetailNode; 031import com.puppycrawl.tools.checkstyle.api.JavadocCommentsTokenTypes; 032import com.puppycrawl.tools.checkstyle.api.TokenTypes; 033 034/** 035 * Contains utility methods for working with Javadoc. 036 */ 037public final class JavadocUtil { 038 039 /** Maps from a token name to value. */ 040 private static final Map<String, Integer> TOKEN_NAME_TO_VALUE; 041 /** Maps from a token value to name. */ 042 private static final Map<Integer, String> TOKEN_VALUE_TO_NAME; 043 044 /** Exception message for unknown JavaDoc token id. */ 045 private static final String UNKNOWN_JAVADOC_TOKEN_ID_EXCEPTION_MESSAGE = "Unknown javadoc" 046 + " token id. Given id: "; 047 048 /** Newline pattern. */ 049 private static final Pattern NEWLINE = Pattern.compile("\n"); 050 051 /** Return pattern. */ 052 private static final Pattern RETURN = Pattern.compile("\r"); 053 054 /** Tab pattern. */ 055 private static final Pattern TAB = Pattern.compile("\t"); 056 057 // initialise the constants 058 static { 059 TOKEN_NAME_TO_VALUE = 060 TokenUtil.nameToValueMapFromPublicIntFields(JavadocCommentsTokenTypes.class); 061 TOKEN_VALUE_TO_NAME = TokenUtil.invertMap(TOKEN_NAME_TO_VALUE); 062 } 063 064 /** Prevent instantiation. */ 065 private JavadocUtil() { 066 } 067 068 /** 069 * Checks that commentContent starts with '*' javadoc comment identifier. 070 * 071 * @param commentContent 072 * content of block comment 073 * @return true if commentContent starts with '*' javadoc comment 074 * identifier. 075 */ 076 public static boolean isJavadocComment(String commentContent) { 077 boolean result = false; 078 079 if (!commentContent.isEmpty()) { 080 final char docCommentIdentifier = commentContent.charAt(0); 081 result = docCommentIdentifier == '*'; 082 } 083 084 return result; 085 } 086 087 /** 088 * Checks block comment content starts with '*' javadoc comment identifier. 089 * 090 * @param blockCommentBegin 091 * block comment AST 092 * @return true if block comment content starts with '*' javadoc comment 093 * identifier. 094 */ 095 public static boolean isJavadocComment(DetailAST blockCommentBegin) { 096 final String commentContent = getBlockCommentContent(blockCommentBegin); 097 return isJavadocComment(commentContent) && isCorrectJavadocPosition(blockCommentBegin); 098 } 099 100 /** 101 * Gets content of block comment. 102 * 103 * @param blockCommentBegin 104 * block comment AST. 105 * @return content of block comment. 106 */ 107 public static String getBlockCommentContent(DetailAST blockCommentBegin) { 108 final DetailAST commentContent = blockCommentBegin.getFirstChild(); 109 return commentContent.getText(); 110 } 111 112 /** 113 * Get content of Javadoc comment. 114 * 115 * @param javadocCommentBegin 116 * Javadoc comment AST 117 * @return content of Javadoc comment. 118 */ 119 public static String getJavadocCommentContent(DetailAST javadocCommentBegin) { 120 final DetailAST commentContent = javadocCommentBegin.getFirstChild(); 121 return commentContent.getText().substring(1); 122 } 123 124 /** 125 * Returns the Javadoc block comment attached to the given declaration AST node. 126 * 127 * @param ast the declaration AST node 128 * @return the attached Javadoc block comment, or {@code null} if none is found 129 */ 130 @Nullable 131 public static DetailAST getAttachedJavadocComment(final DetailAST ast) { 132 DetailAST result = null; 133 DetailAST child = ast.getFirstChild(); 134 while (result == null && child.getType() != TokenTypes.IDENT) { 135 result = findJavadocComment(child); 136 child = child.getNextSibling(); 137 } 138 return result; 139 } 140 141 /** 142 * Finds the first Javadoc block comment under the given AST node. 143 * 144 * @param ast the AST node to search 145 * @return the Javadoc block comment, or {@code null} if none is found 146 */ 147 @Nullable 148 private static DetailAST findJavadocComment(DetailAST ast) { 149 DetailAST result = null; 150 if (ast.getType() == TokenTypes.BLOCK_COMMENT_BEGIN && isJavadocComment(ast)) { 151 result = ast; 152 } 153 else { 154 DetailAST child = ast.getFirstChild(); 155 while (result == null && child != null) { 156 result = findJavadocComment(child); 157 child = child.getNextSibling(); 158 } 159 } 160 return result; 161 } 162 163 /** 164 * Returns the first child token that has a specified type. 165 * 166 * @param detailNode 167 * Javadoc AST node 168 * @param type 169 * the token type to match 170 * @return the matching token, or null if no match 171 */ 172 public static DetailNode findFirstToken(DetailNode detailNode, int type) { 173 DetailNode returnValue = null; 174 DetailNode node = detailNode.getFirstChild(); 175 while (node != null) { 176 if (node.getType() == type) { 177 returnValue = node; 178 break; 179 } 180 node = node.getNextSibling(); 181 } 182 return returnValue; 183 } 184 185 /** 186 * Returns all child tokens that have a specified type. 187 * 188 * @param detailNode Javadoc AST node 189 * @param type the token type to match 190 * @return the matching tokens, or an empty list if no match 191 */ 192 public static List<DetailNode> getAllNodesOfType(DetailNode detailNode, int type) { 193 final List<DetailNode> nodes = new ArrayList<>(); 194 DetailNode node = detailNode.getFirstChild(); 195 while (node != null) { 196 if (node.getType() == type) { 197 nodes.add(node); 198 } 199 node = node.getNextSibling(); 200 } 201 return nodes; 202 } 203 204 /** 205 * Checks whether the given AST node is an HTML element with the specified tag name. 206 * This method ignore void elements. 207 * 208 * @param ast the AST node to check 209 * (must be of type {@link JavadocCommentsTokenTypes#HTML_ELEMENT}) 210 * @param expectedTagName the tag name to match (case-insensitive) 211 * @return {@code true} if the node has the given tag name, {@code false} otherwise 212 */ 213 public static boolean isTag(DetailNode ast, String expectedTagName) { 214 final DetailNode htmlTagStart = findFirstToken(ast, 215 JavadocCommentsTokenTypes.HTML_TAG_START); 216 boolean isTag = false; 217 if (htmlTagStart != null) { 218 final String tagName = findFirstToken(htmlTagStart, 219 JavadocCommentsTokenTypes.TAG_NAME).getText(); 220 isTag = expectedTagName.equalsIgnoreCase(tagName); 221 } 222 return isTag; 223 } 224 225 /** 226 * Gets next sibling of specified node with the specified type. 227 * 228 * @param node DetailNode 229 * @param tokenType javadoc token type 230 * @return next sibling. 231 */ 232 public static DetailNode getNextSibling(DetailNode node, int tokenType) { 233 DetailNode nextSibling = node.getNextSibling(); 234 while (nextSibling != null && nextSibling.getType() != tokenType) { 235 nextSibling = nextSibling.getNextSibling(); 236 } 237 return nextSibling; 238 } 239 240 /** 241 * Returns the name of a token for a given ID. 242 * 243 * @param id 244 * the ID of the token name to get 245 * @return a token name 246 * @throws IllegalArgumentException if an unknown token ID was specified. 247 */ 248 public static String getTokenName(int id) { 249 final String name = TOKEN_VALUE_TO_NAME.get(id); 250 if (name == null) { 251 throw new IllegalArgumentException(UNKNOWN_JAVADOC_TOKEN_ID_EXCEPTION_MESSAGE + id); 252 } 253 return name; 254 } 255 256 /** 257 * Returns the ID of a token for a given name. 258 * 259 * @param name 260 * the name of the token ID to get 261 * @return a token ID 262 * @throws IllegalArgumentException if an unknown token name was specified. 263 */ 264 public static int getTokenId(String name) { 265 final Integer id = TOKEN_NAME_TO_VALUE.get(name); 266 if (id == null) { 267 throw new IllegalArgumentException("Unknown javadoc token name. Given name " + name); 268 } 269 return id; 270 } 271 272 /** 273 * Extracts the tag name from the given Javadoc tag section. 274 * 275 * @param javadocTagSection the node representing a Javadoc tag section. 276 * This node must be of type {@link JavadocCommentsTokenTypes#JAVADOC_BLOCK_TAG} 277 * or {@link JavadocCommentsTokenTypes#JAVADOC_INLINE_TAG}. 278 * @return the tag name (e.g., "param", "return", "link") 279 */ 280 public static String getTagName(DetailNode javadocTagSection) { 281 return findFirstToken(javadocTagSection.getFirstChild(), 282 JavadocCommentsTokenTypes.TAG_NAME).getText(); 283 } 284 285 /** 286 * Replace all control chars with escaped symbols. 287 * 288 * @param text the String to process. 289 * @return the processed String with all control chars escaped. 290 */ 291 public static String escapeAllControlChars(String text) { 292 final String textWithoutNewlines = NEWLINE.matcher(text).replaceAll("\\\\n"); 293 final String textWithoutReturns = RETURN.matcher(textWithoutNewlines).replaceAll("\\\\r"); 294 return TAB.matcher(textWithoutReturns).replaceAll("\\\\t"); 295 } 296 297 /** 298 * Checks Javadoc comment it's in right place. 299 * 300 * <p>From Javadoc util documentation: 301 * "Placement of comments - Documentation comments are recognized only when placed 302 * immediately before class, interface, constructor, method, field or annotation field 303 * declarations -- see the class example, method example, and field example. 304 * Documentation comments placed in the body of a method are ignored."</p> 305 * 306 * <p>If there are many documentation comments per declaration statement, 307 * only the last one will be recognized.</p> 308 * 309 * @param blockComment Block comment AST 310 * @return true if Javadoc is in right place 311 * @see <a href="https://docs.oracle.com/javase/8/docs/technotes/tools/unix/javadoc.html"> 312 * Javadoc util documentation</a> 313 */ 314 public static boolean isCorrectJavadocPosition(DetailAST blockComment) { 315 // We must be sure that after this one there are no other documentation comments. 316 DetailAST sibling = blockComment.getNextSibling(); 317 while (sibling != null) { 318 if (sibling.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) { 319 if (isJavadocComment(getBlockCommentContent(sibling))) { 320 // Found another javadoc comment, so this one should be ignored. 321 break; 322 } 323 sibling = sibling.getNextSibling(); 324 } 325 else if (sibling.getType() == TokenTypes.SINGLE_LINE_COMMENT) { 326 sibling = sibling.getNextSibling(); 327 } 328 else { 329 // Annotation, declaration or modifier is here. Do not check further. 330 sibling = null; 331 } 332 } 333 return sibling == null 334 && (BlockCommentPosition.isOnType(blockComment) 335 || BlockCommentPosition.isOnMember(blockComment) 336 || BlockCommentPosition.isOnPackage(blockComment) 337 || BlockCommentPosition.isOnModule(blockComment)); 338 } 339 340}