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.site; 021 022import java.lang.reflect.Field; 023import java.util.Set; 024import java.util.regex.Pattern; 025 026import org.apache.maven.doxia.macro.MacroExecutionException; 027import org.apache.maven.doxia.sink.Sink; 028 029import com.puppycrawl.tools.checkstyle.PropertyType; 030import com.puppycrawl.tools.checkstyle.XdocsPropertyType; 031import com.puppycrawl.tools.checkstyle.api.DetailNode; 032import com.puppycrawl.tools.checkstyle.api.JavadocCommentsTokenTypes; 033import com.puppycrawl.tools.checkstyle.meta.JavadocMetadataScraperUtil; 034import com.puppycrawl.tools.checkstyle.utils.JavadocUtil; 035 036/** 037 * Utility class for parsing javadocs of modules. 038 */ 039public final class ModuleJavadocParsingUtil { 040 /** New line escape character. */ 041 public static final String NEWLINE = System.lineSeparator(); 042 /** A newline with 4 spaces of indentation. */ 043 public static final String INDENT_LEVEL_4 = SiteUtil.getNewlineAndIndentSpaces(4); 044 /** A newline with 6 spaces of indentation. */ 045 public static final String INDENT_LEVEL_6 = SiteUtil.getNewlineAndIndentSpaces(6); 046 /** A newline with 8 spaces of indentation. */ 047 public static final String INDENT_LEVEL_8 = SiteUtil.getNewlineAndIndentSpaces(8); 048 /** A newline with 10 spaces of indentation. */ 049 public static final String INDENT_LEVEL_10 = SiteUtil.getNewlineAndIndentSpaces(10); 050 /** A newline with 12 spaces of indentation. */ 051 public static final String INDENT_LEVEL_12 = SiteUtil.getNewlineAndIndentSpaces(12); 052 /** A newline with 14 spaces of indentation. */ 053 public static final String INDENT_LEVEL_14 = SiteUtil.getNewlineAndIndentSpaces(14); 054 /** A newline with 16 spaces of indentation. */ 055 public static final String INDENT_LEVEL_16 = SiteUtil.getNewlineAndIndentSpaces(16); 056 /** A newline with 18 spaces of indentation. */ 057 public static final String INDENT_LEVEL_18 = SiteUtil.getNewlineAndIndentSpaces(18); 058 /** A newline with 20 spaces of indentation. */ 059 public static final String INDENT_LEVEL_20 = SiteUtil.getNewlineAndIndentSpaces(20); 060 /** A set of all html tags that need to be considered as text formatting for this macro. */ 061 public static final Set<String> HTML_TEXT_FORMAT_TAGS = Set.of("<code>", "<a", "</a>", "<b>", 062 "</b>", "<strong>", "</strong>", "<i>", "</i>", "<em>", "</em>", "<small>", "</small>", 063 "<ins>", "<sub>", "<sup>"); 064 /** "Notes:" javadoc marking. */ 065 public static final String NOTES = "Notes:"; 066 /** "Notes:" line. */ 067 public static final Pattern NOTES_LINE = Pattern.compile("\\s*" + NOTES + "$"); 068 /** "Notes:" line with new line accounted. */ 069 public static final Pattern NOTES_LINE_WITH_NEWLINE = Pattern.compile("\r?\n\\s?" + NOTES); 070 071 /** 072 * Private utility constructor. 073 */ 074 private ModuleJavadocParsingUtil() { 075 } 076 077 /** 078 * Gets properties of the specified module. 079 * 080 * @param moduleName name of module. 081 * @return set of properties name if present, otherwise null. 082 * @throws MacroExecutionException if the module could not be retrieved. 083 */ 084 public static Set<String> getPropertyNames(String moduleName) 085 throws MacroExecutionException { 086 final Object instance = SiteUtil.getModuleInstance(moduleName); 087 final Class<?> clss = instance.getClass(); 088 089 return SiteUtil.getPropertiesForDocumentation(clss, instance); 090 } 091 092 /** 093 * Determines whether the given HTML node marks the start of the "Notes" section. 094 * 095 * @param htmlElement html element to check. 096 * @return true if the element starts the "Notes" section, false otherwise. 097 */ 098 private static boolean isStartOfNotesSection(DetailNode htmlElement) { 099 boolean result = false; 100 if (htmlElement != null) { 101 final DetailNode htmlContentNode = JavadocUtil.findFirstToken( 102 htmlElement, JavadocCommentsTokenTypes.HTML_CONTENT); 103 104 result = htmlContentNode != null && JavadocMetadataScraperUtil.isChildNodeTextMatches( 105 htmlContentNode, NOTES_LINE); 106 } 107 return result; 108 } 109 110 /** 111 * Writes the given javadoc chunk into xdoc. 112 * 113 * @param javadocPortion javadoc text. 114 * @param sink sink of the macro. 115 */ 116 public static void writeOutJavadocPortion(String javadocPortion, Sink sink) { 117 final String[] javadocPortionLinesSplit = javadocPortion.split(NEWLINE 118 .replace("\r", ""), -1); 119 120 sink.rawText(javadocPortionLinesSplit[0]); 121 String lastHtmlTag = javadocPortionLinesSplit[0]; 122 123 for (int index = 1; index < javadocPortionLinesSplit.length; index++) { 124 final String currentLine = javadocPortionLinesSplit[index].trim(); 125 final String processedLine; 126 127 if (currentLine.isEmpty()) { 128 processedLine = NEWLINE; 129 } 130 else if (currentLine.startsWith("<") 131 && !startsWithTextFormattingHtmlTag(currentLine)) { 132 133 processedLine = INDENT_LEVEL_8 + currentLine; 134 lastHtmlTag = currentLine; 135 } 136 else if (lastHtmlTag.contains("<pre")) { 137 final String currentLineWithPreservedIndent = javadocPortionLinesSplit[index] 138 .substring(1); 139 140 processedLine = NEWLINE + currentLineWithPreservedIndent; 141 } 142 else { 143 processedLine = INDENT_LEVEL_10 + currentLine; 144 } 145 146 sink.rawText(processedLine); 147 } 148 149 } 150 151 /** 152 * Checks if given line starts with HTML text-formatting tag. 153 * 154 * @param line line to check on. 155 * @return whether given line starts with HTML text-formatting tag. 156 */ 157 public static boolean startsWithTextFormattingHtmlTag(String line) { 158 boolean result = false; 159 160 for (String tag : HTML_TEXT_FORMAT_TAGS) { 161 if (line.startsWith(tag)) { 162 result = true; 163 break; 164 } 165 } 166 167 return result; 168 } 169 170 /** 171 * Gets the description of module from module javadoc. 172 * 173 * @param moduleJavadoc module javadoc. 174 * @return module description. 175 */ 176 public static String getModuleDescription(DetailNode moduleJavadoc) { 177 final DetailNode descriptionEndNode = getDescriptionEndNode(moduleJavadoc); 178 String result = ""; 179 if (descriptionEndNode != null) { 180 result = JavadocMetadataScraperUtil.constructSubTreeText(moduleJavadoc, 181 descriptionEndNode); 182 } 183 return result; 184 } 185 186 /** 187 * Gets the {@code @since} version of module from module javadoc. 188 * 189 * @param moduleJavadoc module javadoc 190 * @return module {@code @since} version. For instance, {@code 8.0} 191 */ 192 public static String getModuleSinceVersion(DetailNode moduleJavadoc) { 193 final DetailNode sinceTagNode = getModuleSinceVersionTagStartNode(moduleJavadoc); 194 String result = ""; 195 196 if (sinceTagNode == null) { 197 result = ""; 198 } 199 else if (sinceTagNode.getFirstChild() != null) { 200 result = JavadocMetadataScraperUtil.constructSubTreeText(sinceTagNode, 201 sinceTagNode.getFirstChild()).replace("@since ", ""); 202 } 203 return result; 204 } 205 206 /** 207 * Gets the end node of the description. 208 * 209 * @param moduleJavadoc javadoc of module. 210 * @return the end index. 211 */ 212 public static DetailNode getDescriptionEndNode(DetailNode moduleJavadoc) { 213 final DetailNode descriptionEndNode; 214 215 final DetailNode notesStartingNode = 216 getNotesSectionStartNode(moduleJavadoc); 217 218 if (notesStartingNode != null) { 219 descriptionEndNode = notesStartingNode.getPreviousSibling(); 220 } 221 else { 222 descriptionEndNode = getNodeBeforeJavadocTags(moduleJavadoc); 223 } 224 225 return descriptionEndNode; 226 } 227 228 /** 229 * Gets the start node of the Notes section. 230 * 231 * @param moduleJavadoc javadoc of module. 232 * @return start node. 233 */ 234 public static DetailNode getNotesSectionStartNode(DetailNode moduleJavadoc) { 235 DetailNode notesStartNode = null; 236 if (moduleJavadoc != null) { 237 DetailNode node = moduleJavadoc.getFirstChild(); 238 239 while (node != null) { 240 if (isNotesSectionNode(node)) { 241 notesStartNode = node; 242 break; 243 } 244 node = node.getNextSibling(); 245 } 246 } 247 248 return notesStartNode; 249 } 250 251 /** 252 * Checks if the given node is the start of the Notes section. 253 * 254 * @param node the node to check. 255 * @return true if the node is the start of the Notes section, false otherwise. 256 */ 257 private static boolean isNotesSectionNode(DetailNode node) { 258 boolean result = false; 259 if (node.getType() == JavadocCommentsTokenTypes.HTML_ELEMENT) { 260 if (JavadocUtil.isTag(node, "ul")) { 261 final DetailNode htmlContentNode = JavadocUtil.findFirstToken( 262 node, JavadocCommentsTokenTypes.HTML_CONTENT); 263 result = htmlContentNode != null 264 && isStartOfNotesSection(htmlContentNode.getFirstChild()); 265 } 266 else { 267 result = (JavadocUtil.isTag(node, "p") 268 || JavadocUtil.isTag(node, "li")) 269 && isStartOfNotesSection(node); 270 } 271 } 272 return result; 273 } 274 275 /** 276 * Gets the node representing the start of the {@code @since} version tag 277 * in the module's Javadoc. 278 * 279 * @param moduleJavadoc the root Javadoc node of the module 280 * @return the {@code @since} tag start node, or {@code null} if not found 281 */ 282 public static DetailNode getModuleSinceVersionTagStartNode(DetailNode moduleJavadoc) { 283 DetailNode result = null; 284 285 if (moduleJavadoc != null) { 286 result = JavadocUtil.getAllNodesOfType( 287 moduleJavadoc, JavadocCommentsTokenTypes.JAVADOC_BLOCK_TAG).stream() 288 .filter(javadocTag -> { 289 final DetailNode firstChild = javadocTag.getFirstChild(); 290 return firstChild != null 291 && firstChild.getType() 292 == JavadocCommentsTokenTypes.SINCE_BLOCK_TAG; 293 }) 294 .findFirst() 295 .orElse(null); 296 } 297 return result; 298 } 299 300 /** 301 * Gets the node of module's javadoc whose next sibling is a node that defines a javadoc tag. 302 * 303 * @param moduleJavadoc the root Javadoc node of the module 304 * @return the node that precedes node defining javadoc tag if present, 305 * otherwise just the last node of module's javadoc. 306 */ 307 public static DetailNode getNodeBeforeJavadocTags(DetailNode moduleJavadoc) { 308 DetailNode nodeBeforeJavadocTags = null; 309 310 if (moduleJavadoc != null) { 311 nodeBeforeJavadocTags = moduleJavadoc.getFirstChild(); 312 313 if (nodeBeforeJavadocTags != null) { 314 while (nodeBeforeJavadocTags.getNextSibling() != null 315 && nodeBeforeJavadocTags.getNextSibling().getType() 316 != JavadocCommentsTokenTypes.JAVADOC_BLOCK_TAG) { 317 318 nodeBeforeJavadocTags = nodeBeforeJavadocTags.getNextSibling(); 319 } 320 } 321 } 322 323 return nodeBeforeJavadocTags; 324 } 325 326 /** 327 * Gets the Notes section of module from module javadoc. 328 * 329 * @param moduleJavadoc module javadoc. 330 * @return Notes section of module. 331 */ 332 public static String getModuleNotes(DetailNode moduleJavadoc) { 333 final String result; 334 335 final DetailNode notesStartNode = getNotesSectionStartNode(moduleJavadoc); 336 337 if (notesStartNode == null) { 338 result = ""; 339 } 340 else { 341 final DetailNode notesEndNode = getNodeBeforeJavadocTags(moduleJavadoc); 342 343 if (notesEndNode == null) { 344 result = ""; 345 } 346 else { 347 final String unprocessedNotes = JavadocMetadataScraperUtil.constructSubTreeText( 348 notesStartNode, notesEndNode); 349 result = NOTES_LINE_WITH_NEWLINE.matcher(unprocessedNotes).replaceAll(""); 350 } 351 } 352 353 return result; 354 } 355 356 /** 357 * Checks whether property is to contain tokens. 358 * 359 * @param propertyField property field. 360 * @return true if property is to contain tokens, false otherwise. 361 */ 362 public static boolean isPropertySpecialTokenProp(Field propertyField) { 363 boolean result = false; 364 365 if (propertyField != null) { 366 final XdocsPropertyType fieldXdocAnnotation = 367 propertyField.getAnnotation(XdocsPropertyType.class); 368 369 result = fieldXdocAnnotation != null 370 && fieldXdocAnnotation.value() == PropertyType.TOKEN_ARRAY; 371 } 372 373 return result; 374 } 375 376}