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.Optional; 024import java.util.Set; 025import java.util.regex.Pattern; 026 027import org.apache.maven.doxia.macro.MacroExecutionException; 028import org.apache.maven.doxia.sink.Sink; 029 030import com.puppycrawl.tools.checkstyle.PropertyType; 031import com.puppycrawl.tools.checkstyle.XdocsPropertyType; 032import com.puppycrawl.tools.checkstyle.api.DetailNode; 033import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes; 034import com.puppycrawl.tools.checkstyle.meta.JavadocMetadataScraperUtil; 035import com.puppycrawl.tools.checkstyle.utils.JavadocUtil; 036 037/** 038 * Utility class for parsing javadocs of modules. 039 */ 040public final class ModuleJavadocParsingUtil { 041 /** New line escape character. */ 042 public static final String NEWLINE = System.lineSeparator(); 043 /** A newline with 8 spaces of indentation. */ 044 public static final String INDENT_LEVEL_8 = SiteUtil.getNewlineAndIndentSpaces(8); 045 /** A newline with 10 spaces of indentation. */ 046 public static final String INDENT_LEVEL_10 = SiteUtil.getNewlineAndIndentSpaces(10); 047 /** A newline with 12 spaces of indentation. */ 048 public static final String INDENT_LEVEL_12 = SiteUtil.getNewlineAndIndentSpaces(12); 049 /** A newline with 14 spaces of indentation. */ 050 public static final String INDENT_LEVEL_14 = SiteUtil.getNewlineAndIndentSpaces(14); 051 /** A newline with 16 spaces of indentation. */ 052 public static final String INDENT_LEVEL_16 = SiteUtil.getNewlineAndIndentSpaces(16); 053 /** A newline with 18 spaces of indentation. */ 054 public static final String INDENT_LEVEL_18 = SiteUtil.getNewlineAndIndentSpaces(18); 055 /** A newline with 20 spaces of indentation. */ 056 public static final String INDENT_LEVEL_20 = SiteUtil.getNewlineAndIndentSpaces(20); 057 /** A set of all html tags that need to be considered as text formatting for this macro. */ 058 public static final Set<String> HTML_TEXT_FORMAT_TAGS = Set.of("<code>", "<a", "</a>", "<b>", 059 "</b>", "<strong>", "</strong>", "<i>", "</i>", "<em>", "</em>", "<small>", "</small>", 060 "<ins>", "<sub>", "<sup>"); 061 /** "Notes:" javadoc marking. */ 062 public static final String NOTES = "Notes:"; 063 /** "Notes:" line. */ 064 public static final Pattern NOTES_LINE = Pattern.compile("\\s*" + NOTES + "$"); 065 /** "Notes:" line with new line accounted. */ 066 public static final Pattern NOTES_LINE_WITH_NEWLINE = Pattern.compile("\r?\n\\s?" + NOTES); 067 068 /** 069 * Private utility constructor. 070 */ 071 private ModuleJavadocParsingUtil() { 072 } 073 074 /** 075 * Gets properties of the specified module. 076 * 077 * @param moduleName name of module. 078 * @return set of properties name if present, otherwise null. 079 * @throws MacroExecutionException if the module could not be retrieved. 080 */ 081 public static Set<String> getPropertyNames(String moduleName) 082 throws MacroExecutionException { 083 final Object instance = SiteUtil.getModuleInstance(moduleName); 084 final Class<?> clss = instance.getClass(); 085 086 return SiteUtil.getPropertiesForDocumentation(clss, instance); 087 } 088 089 /** 090 * Determines whether the given HTML node marks the start of the "Notes" section. 091 * 092 * @param htmlElement html element to check. 093 * @return true if the element starts the "Notes" section, false otherwise. 094 */ 095 private static boolean isStartOfNotesSection(DetailNode htmlElement) { 096 final DetailNode paragraphNode = JavadocUtil.findFirstToken( 097 htmlElement, JavadocTokenTypes.PARAGRAPH); 098 final Optional<DetailNode> liNode = getLiTagNode(htmlElement); 099 100 return paragraphNode != null && JavadocMetadataScraperUtil.isChildNodeTextMatches( 101 paragraphNode, NOTES_LINE) 102 || liNode.isPresent() && JavadocMetadataScraperUtil.isChildNodeTextMatches( 103 liNode.get(), NOTES_LINE); 104 } 105 106 /** 107 * Gets the node of Li HTML tag. 108 * 109 * @param htmlElement html element to get li tag from. 110 * @return Optional of li tag node. 111 */ 112 public static Optional<DetailNode> getLiTagNode(DetailNode htmlElement) { 113 return Optional.of(htmlElement) 114 .map(element -> JavadocUtil.findFirstToken(element, JavadocTokenTypes.HTML_TAG)) 115 .map(element -> JavadocUtil.findFirstToken(element, JavadocTokenTypes.HTML_ELEMENT)) 116 .map(element -> JavadocUtil.findFirstToken(element, JavadocTokenTypes.LI)); 117 } 118 119 /** 120 * Writes the given javadoc chunk into xdoc. 121 * 122 * @param javadocPortion javadoc text. 123 * @param sink sink of the macro. 124 */ 125 public static void writeOutJavadocPortion(String javadocPortion, Sink sink) { 126 final String[] javadocPortionLinesSplit = javadocPortion.split(NEWLINE 127 .replace("\r", "")); 128 129 sink.rawText(javadocPortionLinesSplit[0]); 130 String lastHtmlTag = javadocPortionLinesSplit[0]; 131 132 for (int index = 1; index < javadocPortionLinesSplit.length; index++) { 133 final String currentLine = javadocPortionLinesSplit[index].trim(); 134 final String processedLine; 135 136 if (currentLine.isEmpty()) { 137 processedLine = NEWLINE; 138 } 139 else if (currentLine.startsWith("<") 140 && !startsWithTextFormattingHtmlTag(currentLine)) { 141 142 processedLine = INDENT_LEVEL_8 + currentLine; 143 lastHtmlTag = currentLine; 144 } 145 else if (lastHtmlTag.contains("<pre")) { 146 final String currentLineWithPreservedIndent = javadocPortionLinesSplit[index] 147 .substring(1); 148 149 processedLine = NEWLINE + currentLineWithPreservedIndent; 150 } 151 else { 152 processedLine = INDENT_LEVEL_10 + currentLine; 153 } 154 155 sink.rawText(processedLine); 156 } 157 158 } 159 160 /** 161 * Checks if given line starts with HTML text-formatting tag. 162 * 163 * @param line line to check on. 164 * @return whether given line starts with HTML text-formatting tag. 165 */ 166 public static boolean startsWithTextFormattingHtmlTag(String line) { 167 boolean result = false; 168 169 for (String tag : HTML_TEXT_FORMAT_TAGS) { 170 if (line.startsWith(tag)) { 171 result = true; 172 break; 173 } 174 } 175 176 return result; 177 } 178 179 /** 180 * Gets the description of module from module javadoc. 181 * 182 * @param moduleJavadoc module javadoc. 183 * @return module description. 184 */ 185 public static String getModuleDescription(DetailNode moduleJavadoc) { 186 final int descriptionEndIndex = getDescriptionEndIndex(moduleJavadoc); 187 188 return JavadocMetadataScraperUtil 189 .constructSubTreeText(moduleJavadoc, 0, descriptionEndIndex); 190 } 191 192 /** 193 * Gets the end index of the description. 194 * 195 * @param moduleJavadoc javadoc of module. 196 * @return the end index. 197 */ 198 public static int getDescriptionEndIndex(DetailNode moduleJavadoc) { 199 int descriptionEndIndex = -1; 200 201 final int notesStartingIndex = 202 getNotesSectionStartIndex(moduleJavadoc); 203 204 if (notesStartingIndex > -1) { 205 descriptionEndIndex += notesStartingIndex; 206 } 207 else { 208 descriptionEndIndex += getModuleSinceVersionTagStartIndex(moduleJavadoc); 209 } 210 211 return descriptionEndIndex; 212 } 213 214 /** 215 * Gets the start index of the Notes section. 216 * 217 * @param moduleJavadoc javadoc of module. 218 * @return start index. 219 */ 220 public static int getNotesSectionStartIndex(DetailNode moduleJavadoc) { 221 int notesStartIndex = -1; 222 223 for (DetailNode node : moduleJavadoc.getChildren()) { 224 if (node.getType() == JavadocTokenTypes.HTML_ELEMENT 225 && isStartOfNotesSection(node)) { 226 227 notesStartIndex += node.getIndex(); 228 break; 229 } 230 } 231 232 return notesStartIndex; 233 } 234 235 /** 236 * Gets the starting index of the "@since" version tag in module's javadoc. 237 * 238 * @param moduleJavadoc javadoc of module. 239 * @return start index of "@since". 240 */ 241 public static int getModuleSinceVersionTagStartIndex(DetailNode moduleJavadoc) { 242 return SiteUtil.getNodesOfSpecificType(moduleJavadoc.getChildren(), 243 JavadocTokenTypes.JAVADOC_TAG).stream() 244 .filter(javadocTag -> { 245 return JavadocUtil 246 .findFirstToken(javadocTag, JavadocTokenTypes.SINCE_LITERAL) != null; 247 }) 248 .findFirst() 249 .map(DetailNode::getIndex) 250 .orElse(-1); 251 } 252 253 /** 254 * Gets the Notes section of module from module javadoc. 255 * 256 * @param moduleJavadoc module javadoc. 257 * @return Notes section of module. 258 */ 259 public static String getModuleNotes(DetailNode moduleJavadoc) { 260 final String result; 261 262 final int notesStartIndex = getNotesSectionStartIndex(moduleJavadoc); 263 264 if (notesStartIndex < 0) { 265 result = ""; 266 } 267 else { 268 final int notesEndIndex = getNotesEndIndex(moduleJavadoc); 269 270 final String unprocessedNotes = JavadocMetadataScraperUtil.constructSubTreeText( 271 moduleJavadoc, notesStartIndex, notesEndIndex); 272 273 result = NOTES_LINE_WITH_NEWLINE.matcher(unprocessedNotes).replaceAll(""); 274 } 275 276 return result; 277 } 278 279 /** 280 * Gets the end index of the Notes. 281 * 282 * @param moduleJavadoc javadoc of module. 283 * @return the end index. 284 */ 285 public static int getNotesEndIndex(DetailNode moduleJavadoc) { 286 int notesEndIndex = -1; 287 288 notesEndIndex += getModuleSinceVersionTagStartIndex(moduleJavadoc); 289 290 return notesEndIndex; 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}