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}