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