001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2025 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 end node of the description. 180 * 181 * @param moduleJavadoc javadoc of module. 182 * @return the end index. 183 */ 184 public static DetailNode getDescriptionEndNode(DetailNode moduleJavadoc) { 185 final DetailNode descriptionEndNode; 186 187 final DetailNode notesStartingNode = 188 getNotesSectionStartNode(moduleJavadoc); 189 190 if (notesStartingNode != null) { 191 descriptionEndNode = notesStartingNode.getPreviousSibling(); 192 } 193 else { 194 descriptionEndNode = getModuleSinceVersionTagStartNode( 195 moduleJavadoc).getPreviousSibling(); 196 } 197 198 return descriptionEndNode; 199 } 200 201 /** 202 * Gets the start node of the Notes section. 203 * 204 * @param moduleJavadoc javadoc of module. 205 * @return start node. 206 */ 207 public static DetailNode getNotesSectionStartNode(DetailNode moduleJavadoc) { 208 DetailNode notesStartNode = null; 209 DetailNode node = moduleJavadoc.getFirstChild(); 210 211 while (node != null) { 212 if (node.getType() == JavadocCommentsTokenTypes.HTML_ELEMENT) { 213 boolean found = false; 214 if (JavadocUtil.isTag(node, "ul")) { 215 final DetailNode htmlContentNode = JavadocUtil.findFirstToken( 216 node, JavadocCommentsTokenTypes.HTML_CONTENT); 217 if (isStartOfNotesSection(htmlContentNode.getFirstChild())) { 218 notesStartNode = node; 219 found = true; 220 } 221 } 222 else if ((JavadocUtil.isTag(node, "p") 223 || JavadocUtil.isTag(node, "li")) 224 && isStartOfNotesSection(node)) { 225 notesStartNode = node; 226 found = true; 227 } 228 if (found) { 229 break; 230 } 231 } 232 node = node.getNextSibling(); 233 } 234 235 return notesStartNode; 236 } 237 238 /** 239 * Gets the node representing the start of the {@code @since} version tag 240 * in the module's Javadoc. 241 * 242 * @param moduleJavadoc the root Javadoc node of the module 243 * @return the {@code @since} tag start node, or {@code null} if not found 244 */ 245 public static DetailNode getModuleSinceVersionTagStartNode(DetailNode moduleJavadoc) { 246 return JavadocUtil.getAllNodesOfType( 247 moduleJavadoc, JavadocCommentsTokenTypes.JAVADOC_BLOCK_TAG).stream() 248 .filter(javadocTag -> { 249 return javadocTag.getFirstChild().getType() 250 == JavadocCommentsTokenTypes.SINCE_BLOCK_TAG; 251 }) 252 .findFirst() 253 .orElse(null); 254 } 255 256 /** 257 * Gets the Notes section of module from module javadoc. 258 * 259 * @param moduleJavadoc module javadoc. 260 * @return Notes section of module. 261 */ 262 public static String getModuleNotes(DetailNode moduleJavadoc) { 263 final String result; 264 265 final DetailNode notesStartNode = getNotesSectionStartNode(moduleJavadoc); 266 267 if (notesStartNode == null) { 268 result = ""; 269 } 270 else { 271 final DetailNode notesEndNode = getNotesEndNode(moduleJavadoc); 272 273 final String unprocessedNotes = 274 JavadocMetadataScraperUtil.constructSubTreeText( 275 notesStartNode, notesEndNode); 276 result = NOTES_LINE_WITH_NEWLINE.matcher(unprocessedNotes).replaceAll(""); 277 } 278 279 return result; 280 } 281 282 /** 283 * Gets the end node of the Notes. 284 * 285 * @param moduleJavadoc javadoc of module. 286 * @return the end index. 287 */ 288 public static DetailNode getNotesEndNode(DetailNode moduleJavadoc) { 289 return getModuleSinceVersionTagStartNode( 290 moduleJavadoc).getPreviousSibling(); 291 } 292 293 /** 294 * Checks whether property is to contain tokens. 295 * 296 * @param propertyField property field. 297 * @return true if property is to contain tokens, false otherwise. 298 */ 299 public static boolean isPropertySpecialTokenProp(Field propertyField) { 300 boolean result = false; 301 302 if (propertyField != null) { 303 final XdocsPropertyType fieldXdocAnnotation = 304 propertyField.getAnnotation(XdocsPropertyType.class); 305 306 result = fieldXdocAnnotation != null 307 && fieldXdocAnnotation.value() == PropertyType.TOKEN_ARRAY; 308 } 309 310 return result; 311 } 312 313}