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 final DetailNode htmlContentNode = JavadocUtil.findFirstToken( 100 htmlElement, JavadocCommentsTokenTypes.HTML_CONTENT); 101 102 return htmlContentNode != null && JavadocMetadataScraperUtil.isChildNodeTextMatches( 103 htmlContentNode, NOTES_LINE); 104 } 105 106 /** 107 * Writes the given javadoc chunk into xdoc. 108 * 109 * @param javadocPortion javadoc text. 110 * @param sink sink of the macro. 111 */ 112 public static void writeOutJavadocPortion(String javadocPortion, Sink sink) { 113 final String[] javadocPortionLinesSplit = javadocPortion.split(NEWLINE 114 .replace("\r", "")); 115 116 sink.rawText(javadocPortionLinesSplit[0]); 117 String lastHtmlTag = javadocPortionLinesSplit[0]; 118 119 for (int index = 1; index < javadocPortionLinesSplit.length; index++) { 120 final String currentLine = javadocPortionLinesSplit[index].trim(); 121 final String processedLine; 122 123 if (currentLine.isEmpty()) { 124 processedLine = NEWLINE; 125 } 126 else if (currentLine.startsWith("<") 127 && !startsWithTextFormattingHtmlTag(currentLine)) { 128 129 processedLine = INDENT_LEVEL_8 + currentLine; 130 lastHtmlTag = currentLine; 131 } 132 else if (lastHtmlTag.contains("<pre")) { 133 final String currentLineWithPreservedIndent = javadocPortionLinesSplit[index] 134 .substring(1); 135 136 processedLine = NEWLINE + currentLineWithPreservedIndent; 137 } 138 else { 139 processedLine = INDENT_LEVEL_10 + currentLine; 140 } 141 142 sink.rawText(processedLine); 143 } 144 145 } 146 147 /** 148 * Checks if given line starts with HTML text-formatting tag. 149 * 150 * @param line line to check on. 151 * @return whether given line starts with HTML text-formatting tag. 152 */ 153 public static boolean startsWithTextFormattingHtmlTag(String line) { 154 boolean result = false; 155 156 for (String tag : HTML_TEXT_FORMAT_TAGS) { 157 if (line.startsWith(tag)) { 158 result = true; 159 break; 160 } 161 } 162 163 return result; 164 } 165 166 /** 167 * Gets the description of module from module javadoc. 168 * 169 * @param moduleJavadoc module javadoc. 170 * @return module description. 171 */ 172 public static String getModuleDescription(DetailNode moduleJavadoc) { 173 final DetailNode descriptionEndNode = getDescriptionEndNode(moduleJavadoc); 174 175 return JavadocMetadataScraperUtil.constructSubTreeText(moduleJavadoc, descriptionEndNode); 176 } 177 178 /** 179 * Gets the {@code @since} version of module from module javadoc. 180 * 181 * @param moduleJavadoc module javadoc 182 * @return module {@code @since} version. For instance, {@code 8.0} 183 */ 184 public static String getModuleSinceVersion(DetailNode moduleJavadoc) { 185 final DetailNode sinceTagNode = getModuleSinceVersionTagStartNode(moduleJavadoc); 186 return JavadocMetadataScraperUtil 187 .constructSubTreeText(sinceTagNode, sinceTagNode.getFirstChild()) 188 .replace("@since ", ""); 189 } 190 191 /** 192 * Gets the end node of the description. 193 * 194 * @param moduleJavadoc javadoc of module. 195 * @return the end index. 196 */ 197 public static DetailNode getDescriptionEndNode(DetailNode moduleJavadoc) { 198 final DetailNode descriptionEndNode; 199 200 final DetailNode notesStartingNode = 201 getNotesSectionStartNode(moduleJavadoc); 202 203 if (notesStartingNode != null) { 204 descriptionEndNode = notesStartingNode.getPreviousSibling(); 205 } 206 else { 207 descriptionEndNode = getNodeBeforeJavadocTags(moduleJavadoc); 208 } 209 210 return descriptionEndNode; 211 } 212 213 /** 214 * Gets the start node of the Notes section. 215 * 216 * @param moduleJavadoc javadoc of module. 217 * @return start node. 218 */ 219 public static DetailNode getNotesSectionStartNode(DetailNode moduleJavadoc) { 220 DetailNode notesStartNode = null; 221 DetailNode node = moduleJavadoc.getFirstChild(); 222 223 while (node != null) { 224 if (node.getType() == JavadocCommentsTokenTypes.HTML_ELEMENT) { 225 boolean found = false; 226 if (JavadocUtil.isTag(node, "ul")) { 227 final DetailNode htmlContentNode = JavadocUtil.findFirstToken( 228 node, JavadocCommentsTokenTypes.HTML_CONTENT); 229 if (isStartOfNotesSection(htmlContentNode.getFirstChild())) { 230 notesStartNode = node; 231 found = true; 232 } 233 } 234 else if ((JavadocUtil.isTag(node, "p") 235 || JavadocUtil.isTag(node, "li")) 236 && isStartOfNotesSection(node)) { 237 notesStartNode = node; 238 found = true; 239 } 240 if (found) { 241 break; 242 } 243 } 244 node = node.getNextSibling(); 245 } 246 247 return notesStartNode; 248 } 249 250 /** 251 * Gets the node representing the start of the {@code @since} version tag 252 * in the module's Javadoc. 253 * 254 * @param moduleJavadoc the root Javadoc node of the module 255 * @return the {@code @since} tag start node, or {@code null} if not found 256 */ 257 public static DetailNode getModuleSinceVersionTagStartNode(DetailNode moduleJavadoc) { 258 return JavadocUtil.getAllNodesOfType( 259 moduleJavadoc, JavadocCommentsTokenTypes.JAVADOC_BLOCK_TAG).stream() 260 .filter(javadocTag -> { 261 return javadocTag.getFirstChild().getType() 262 == JavadocCommentsTokenTypes.SINCE_BLOCK_TAG; 263 }) 264 .findFirst() 265 .orElse(null); 266 } 267 268 /** 269 * Gets the node of module's javadoc whose next sibling is a node that defines a javadoc tag. 270 * 271 * @param moduleJavadoc the root Javadoc node of the module 272 * @return the node that precedes node defining javadoc tag if present, 273 * otherwise just the last node of module's javadoc. 274 */ 275 public static DetailNode getNodeBeforeJavadocTags(DetailNode moduleJavadoc) { 276 DetailNode nodeBeforeJavadocTags = moduleJavadoc.getFirstChild(); 277 278 while (nodeBeforeJavadocTags.getNextSibling() != null 279 && nodeBeforeJavadocTags.getNextSibling().getType() 280 != JavadocCommentsTokenTypes.JAVADOC_BLOCK_TAG) { 281 282 nodeBeforeJavadocTags = nodeBeforeJavadocTags.getNextSibling(); 283 } 284 285 return nodeBeforeJavadocTags; 286 } 287 288 /** 289 * Gets the Notes section of module from module javadoc. 290 * 291 * @param moduleJavadoc module javadoc. 292 * @return Notes section of module. 293 */ 294 public static String getModuleNotes(DetailNode moduleJavadoc) { 295 final String result; 296 297 final DetailNode notesStartNode = getNotesSectionStartNode(moduleJavadoc); 298 299 if (notesStartNode == null) { 300 result = ""; 301 } 302 else { 303 final DetailNode notesEndNode = getNodeBeforeJavadocTags(moduleJavadoc); 304 305 final String unprocessedNotes = 306 JavadocMetadataScraperUtil.constructSubTreeText( 307 notesStartNode, notesEndNode); 308 result = NOTES_LINE_WITH_NEWLINE.matcher(unprocessedNotes).replaceAll(""); 309 } 310 311 return result; 312 } 313 314 /** 315 * Checks whether property is to contain tokens. 316 * 317 * @param propertyField property field. 318 * @return true if property is to contain tokens, false otherwise. 319 */ 320 public static boolean isPropertySpecialTokenProp(Field propertyField) { 321 boolean result = false; 322 323 if (propertyField != null) { 324 final XdocsPropertyType fieldXdocAnnotation = 325 propertyField.getAnnotation(XdocsPropertyType.class); 326 327 result = fieldXdocAnnotation != null 328 && fieldXdocAnnotation.value() == PropertyType.TOKEN_ARRAY; 329 } 330 331 return result; 332 } 333 334}