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.util.Optional; 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.api.DetailNode; 030import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes; 031import com.puppycrawl.tools.checkstyle.meta.JavadocMetadataScraper; 032import com.puppycrawl.tools.checkstyle.utils.JavadocUtil; 033 034/** 035 * Utility class for parsing javadocs of modules. 036 */ 037public final class ModuleJavadocParsingUtil { 038 /** New line escape character. */ 039 public static final String NEWLINE = System.lineSeparator(); 040 /** A newline with 8 spaces of indentation. */ 041 public static final String INDENT_LEVEL_8 = SiteUtil.getNewlineAndIndentSpaces(8); 042 /** A newline with 10 spaces of indentation. */ 043 public static final String INDENT_LEVEL_10 = SiteUtil.getNewlineAndIndentSpaces(10); 044 /** A newline with 12 spaces of indentation. */ 045 public static final String INDENT_LEVEL_12 = SiteUtil.getNewlineAndIndentSpaces(12); 046 /** A newline with 14 spaces of indentation. */ 047 public static final String INDENT_LEVEL_14 = SiteUtil.getNewlineAndIndentSpaces(14); 048 /** A newline with 16 spaces of indentation. */ 049 public static final String INDENT_LEVEL_16 = SiteUtil.getNewlineAndIndentSpaces(16); 050 /** A newline with 18 spaces of indentation. */ 051 public static final String INDENT_LEVEL_18 = SiteUtil.getNewlineAndIndentSpaces(18); 052 /** A newline with 20 spaces of indentation. */ 053 public static final String INDENT_LEVEL_20 = SiteUtil.getNewlineAndIndentSpaces(20); 054 /** A set of all html tags that need to be considered as text formatting for this macro. */ 055 public static final Set<String> HTML_TEXT_FORMAT_TAGS = Set.of("<code>", "<a", "</a>", "<b>", 056 "</b>", "<strong>", "</strong>", "<i>", "</i>", "<em>", "</em>", "<small>", "</small>", 057 "<ins>", "<sub>", "<sup>"); 058 /** "Notes:" javadoc marking. */ 059 public static final String NOTES = "Notes:"; 060 /** "Notes:" line. */ 061 public static final Pattern NOTES_LINE = Pattern.compile("\\s*" + NOTES + "$"); 062 063 /** 064 * Private utility constructor. 065 */ 066 private ModuleJavadocParsingUtil() { 067 } 068 069 /** 070 * Gets properties of the specified module. 071 * 072 * @param moduleName name of module. 073 * @return set of properties name if present, otherwise null. 074 * @throws MacroExecutionException if the module could not be retrieved. 075 */ 076 public static Set<String> getPropertyNames(String moduleName) 077 throws MacroExecutionException { 078 final Object instance = SiteUtil.getModuleInstance(moduleName); 079 final Class<?> clss = instance.getClass(); 080 081 return SiteUtil.getPropertiesForDocumentation(clss, instance); 082 } 083 084 /** 085 * Gets the starting index of the "Parent is" paragraph in module's javadoc. 086 * 087 * @param moduleJavadoc javadoc of module. 088 * @return start index of parent subsection. 089 */ 090 public static int getParentSectionStartIndex(DetailNode moduleJavadoc) { 091 int parentStartIndex = -1; 092 093 for (DetailNode node : moduleJavadoc.getChildren()) { 094 if (node.getType() == JavadocTokenTypes.HTML_ELEMENT) { 095 final DetailNode paragraphNode = JavadocUtil.findFirstToken( 096 node, JavadocTokenTypes.PARAGRAPH); 097 if (paragraphNode != null && JavadocMetadataScraper.isParentText(paragraphNode)) { 098 parentStartIndex = node.getIndex(); 099 break; 100 } 101 } 102 } 103 104 return parentStartIndex; 105 } 106 107 /** 108 * Gets the start index of the Notes section. 109 * 110 * @param moduleJavadoc javadoc of module. 111 * @return start index. 112 */ 113 public static int getNotesSectionStartIndex(DetailNode moduleJavadoc) { 114 int notesStartIndex = -1; 115 116 for (DetailNode node : moduleJavadoc.getChildren()) { 117 if (node.getType() == JavadocTokenTypes.HTML_ELEMENT 118 && isStartOfNotesSection(node)) { 119 120 notesStartIndex += node.getIndex(); 121 break; 122 } 123 } 124 125 return notesStartIndex; 126 } 127 128 /** 129 * Determines whether the given HTML node marks the start of the "Notes" section. 130 * 131 * @param htmlElement html element to check. 132 * @return true if the element starts the "Notes" section, false otherwise. 133 */ 134 private static boolean isStartOfNotesSection(DetailNode htmlElement) { 135 final DetailNode paragraphNode = JavadocUtil.findFirstToken( 136 htmlElement, JavadocTokenTypes.PARAGRAPH); 137 final Optional<DetailNode> liNode = getLiTagNode(htmlElement); 138 139 return paragraphNode != null && JavadocMetadataScraper.isChildNodeTextMatches( 140 paragraphNode, NOTES_LINE) 141 || liNode.isPresent() && JavadocMetadataScraper.isChildNodeTextMatches( 142 liNode.get(), NOTES_LINE); 143 } 144 145 /** 146 * Gets the node of Li HTML tag. 147 * 148 * @param htmlElement html element to get li tag from. 149 * @return Optional of li tag node. 150 */ 151 public static Optional<DetailNode> getLiTagNode(DetailNode htmlElement) { 152 return Optional.of(htmlElement) 153 .map(element -> JavadocUtil.findFirstToken(element, JavadocTokenTypes.HTML_TAG)) 154 .map(element -> JavadocUtil.findFirstToken(element, JavadocTokenTypes.HTML_ELEMENT)) 155 .map(element -> JavadocUtil.findFirstToken(element, JavadocTokenTypes.LI)); 156 } 157 158 /** 159 * Gets the start index of property section in module's javadoc. 160 * 161 * @param moduleJavadoc javadoc of module. 162 * @param propertyNames set with property names. 163 * @return index of property section. 164 */ 165 public static int getPropertySectionStartIndex(DetailNode moduleJavadoc, 166 Set<String> propertyNames) { 167 int propertySectionStartIndex = -1; 168 169 final String somePropertyName = propertyNames.iterator().next(); 170 final Optional<DetailNode> somePropertyModuleNode = 171 SiteUtil.getPropertyJavadocNodeInModule(somePropertyName, moduleJavadoc); 172 173 if (somePropertyModuleNode.isPresent()) { 174 propertySectionStartIndex = JavadocMetadataScraper.getParentIndexOf( 175 somePropertyModuleNode.get()); 176 } 177 178 return propertySectionStartIndex; 179 } 180 181 /** 182 * Writes the given javadoc chunk into xdoc. 183 * 184 * @param javadocPortion javadoc text. 185 * @param sink sink of the macro. 186 */ 187 public static void writeOutJavadocPortion(String javadocPortion, Sink sink) { 188 final String[] javadocPortionLinesSplit = javadocPortion.split(NEWLINE 189 .replace("\r", "")); 190 191 sink.rawText(javadocPortionLinesSplit[0]); 192 String lastHtmlTag = javadocPortionLinesSplit[0]; 193 194 for (int index = 1; index < javadocPortionLinesSplit.length; index++) { 195 final String currentLine = javadocPortionLinesSplit[index].trim(); 196 final String processedLine; 197 198 if (currentLine.isEmpty()) { 199 processedLine = NEWLINE; 200 } 201 else if (currentLine.startsWith("<") 202 && !startsWithTextFormattingHtmlTag(currentLine)) { 203 204 processedLine = INDENT_LEVEL_8 + currentLine; 205 lastHtmlTag = currentLine; 206 } 207 else if (lastHtmlTag.contains("<pre")) { 208 final String currentLineWithPreservedIndent = javadocPortionLinesSplit[index] 209 .substring(1); 210 211 processedLine = NEWLINE + currentLineWithPreservedIndent; 212 } 213 else { 214 processedLine = INDENT_LEVEL_10 + currentLine; 215 } 216 217 sink.rawText(processedLine); 218 } 219 220 } 221 222 /** 223 * Checks if given line starts with HTML text-formatting tag. 224 * 225 * @param line line to check on. 226 * @return whether given line starts with HTML text-formatting tag. 227 */ 228 public static boolean startsWithTextFormattingHtmlTag(String line) { 229 boolean result = false; 230 231 for (String tag : HTML_TEXT_FORMAT_TAGS) { 232 if (line.startsWith(tag)) { 233 result = true; 234 break; 235 } 236 } 237 238 return result; 239 } 240 241}