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