View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2026 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.site;
21  
22  import java.lang.reflect.Field;
23  import java.util.Set;
24  import java.util.regex.Pattern;
25  
26  import org.apache.maven.doxia.macro.MacroExecutionException;
27  import org.apache.maven.doxia.sink.Sink;
28  
29  import com.puppycrawl.tools.checkstyle.PropertyType;
30  import com.puppycrawl.tools.checkstyle.XdocsPropertyType;
31  import com.puppycrawl.tools.checkstyle.api.DetailNode;
32  import com.puppycrawl.tools.checkstyle.api.JavadocCommentsTokenTypes;
33  import com.puppycrawl.tools.checkstyle.meta.JavadocMetadataScraperUtil;
34  import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
35  
36  /**
37   * Utility class for parsing javadocs of modules.
38   */
39  public final class ModuleJavadocParsingUtil {
40      /** New line escape character. */
41      public static final String NEWLINE = System.lineSeparator();
42      /** A newline with 4 spaces of indentation. */
43      public static final String INDENT_LEVEL_4 = SiteUtil.getNewlineAndIndentSpaces(4);
44      /** A newline with 6 spaces of indentation. */
45      public static final String INDENT_LEVEL_6 = SiteUtil.getNewlineAndIndentSpaces(6);
46      /** A newline with 8 spaces of indentation. */
47      public static final String INDENT_LEVEL_8 = SiteUtil.getNewlineAndIndentSpaces(8);
48      /** A newline with 10 spaces of indentation. */
49      public static final String INDENT_LEVEL_10 = SiteUtil.getNewlineAndIndentSpaces(10);
50      /** A newline with 12 spaces of indentation. */
51      public static final String INDENT_LEVEL_12 = SiteUtil.getNewlineAndIndentSpaces(12);
52      /** A newline with 14 spaces of indentation. */
53      public static final String INDENT_LEVEL_14 = SiteUtil.getNewlineAndIndentSpaces(14);
54      /** A newline with 16 spaces of indentation. */
55      public static final String INDENT_LEVEL_16 = SiteUtil.getNewlineAndIndentSpaces(16);
56      /** A newline with 18 spaces of indentation. */
57      public static final String INDENT_LEVEL_18 = SiteUtil.getNewlineAndIndentSpaces(18);
58      /** A newline with 20 spaces of indentation. */
59      public static final String INDENT_LEVEL_20 = SiteUtil.getNewlineAndIndentSpaces(20);
60      /** A set of all html tags that need to be considered as text formatting for this macro. */
61      public static final Set<String> HTML_TEXT_FORMAT_TAGS = Set.of("<code>", "<a", "</a>", "<b>",
62          "</b>", "<strong>", "</strong>", "<i>", "</i>", "<em>", "</em>", "<small>", "</small>",
63          "<ins>", "<sub>", "<sup>");
64      /** "Notes:" javadoc marking. */
65      public static final String NOTES = "Notes:";
66      /** "Notes:" line. */
67      public static final Pattern NOTES_LINE = Pattern.compile("\\s*" + NOTES + "$");
68      /** "Notes:" line with new line accounted. */
69      public static final Pattern NOTES_LINE_WITH_NEWLINE = Pattern.compile("\r?\n\\s?" + NOTES);
70  
71      /**
72       * Private utility constructor.
73       */
74      private ModuleJavadocParsingUtil() {
75      }
76  
77      /**
78       * Gets properties of the specified module.
79       *
80       * @param moduleName name of module.
81       * @return set of properties name if present, otherwise null.
82       * @throws MacroExecutionException if the module could not be retrieved.
83       */
84      public static Set<String> getPropertyNames(String moduleName)
85              throws MacroExecutionException {
86          final Object instance = SiteUtil.getModuleInstance(moduleName);
87          final Class<?> clss = instance.getClass();
88  
89          return SiteUtil.getPropertiesForDocumentation(clss, instance);
90      }
91  
92      /**
93       * Determines whether the given HTML node marks the start of the "Notes" section.
94       *
95       * @param htmlElement html element to check.
96       * @return true if the element starts the "Notes" section, false otherwise.
97       */
98      private static boolean isStartOfNotesSection(DetailNode htmlElement) {
99          boolean result = false;
100         if (htmlElement != null) {
101             final DetailNode htmlContentNode = JavadocUtil.findFirstToken(
102                 htmlElement, JavadocCommentsTokenTypes.HTML_CONTENT);
103 
104             result = htmlContentNode != null && JavadocMetadataScraperUtil.isChildNodeTextMatches(
105                 htmlContentNode, NOTES_LINE);
106         }
107         return result;
108     }
109 
110     /**
111      * Writes the given javadoc chunk into xdoc.
112      *
113      * @param javadocPortion javadoc text.
114      * @param sink sink of the macro.
115      */
116     public static void writeOutJavadocPortion(String javadocPortion, Sink sink) {
117         final String[] javadocPortionLinesSplit = javadocPortion.split(NEWLINE
118             .replace("\r", ""), -1);
119 
120         sink.rawText(javadocPortionLinesSplit[0]);
121         String lastHtmlTag = javadocPortionLinesSplit[0];
122 
123         for (int index = 1; index < javadocPortionLinesSplit.length; index++) {
124             final String currentLine = javadocPortionLinesSplit[index].trim();
125             final String processedLine;
126 
127             if (currentLine.isEmpty()) {
128                 processedLine = NEWLINE;
129             }
130             else if (currentLine.startsWith("<")
131                 && !startsWithTextFormattingHtmlTag(currentLine)) {
132 
133                 processedLine = INDENT_LEVEL_8 + currentLine;
134                 lastHtmlTag = currentLine;
135             }
136             else if (lastHtmlTag.contains("<pre")) {
137                 final String currentLineWithPreservedIndent = javadocPortionLinesSplit[index]
138                     .substring(1);
139 
140                 processedLine = NEWLINE + currentLineWithPreservedIndent;
141             }
142             else {
143                 processedLine = INDENT_LEVEL_10 + currentLine;
144             }
145 
146             sink.rawText(processedLine);
147         }
148 
149     }
150 
151     /**
152      * Checks if given line starts with HTML text-formatting tag.
153      *
154      * @param line line to check on.
155      * @return whether given line starts with HTML text-formatting tag.
156      */
157     public static boolean startsWithTextFormattingHtmlTag(String line) {
158         boolean result = false;
159 
160         for (String tag : HTML_TEXT_FORMAT_TAGS) {
161             if (line.startsWith(tag)) {
162                 result = true;
163                 break;
164             }
165         }
166 
167         return result;
168     }
169 
170     /**
171      * Gets the description of module from module javadoc.
172      *
173      * @param moduleJavadoc module javadoc.
174      * @return module description.
175      */
176     public static String getModuleDescription(DetailNode moduleJavadoc) {
177         final DetailNode descriptionEndNode = getDescriptionEndNode(moduleJavadoc);
178         String result = "";
179         if (descriptionEndNode != null) {
180             result = JavadocMetadataScraperUtil.constructSubTreeText(moduleJavadoc,
181                     descriptionEndNode);
182         }
183         return result;
184     }
185 
186     /**
187      * Gets the {@code @since} version of module from module javadoc.
188      *
189      * @param moduleJavadoc module javadoc
190      * @return module {@code @since} version. For instance, {@code 8.0}
191      */
192     public static String getModuleSinceVersion(DetailNode moduleJavadoc) {
193         final DetailNode sinceTagNode = getModuleSinceVersionTagStartNode(moduleJavadoc);
194         String result = "";
195 
196         if (sinceTagNode == null) {
197             result = "";
198         }
199         else if (sinceTagNode.getFirstChild() != null) {
200             result = JavadocMetadataScraperUtil.constructSubTreeText(sinceTagNode,
201                     sinceTagNode.getFirstChild()).replace("@since ", "");
202         }
203         return result;
204     }
205 
206     /**
207      * Gets the end node of the description.
208      *
209      * @param moduleJavadoc javadoc of module.
210      * @return the end index.
211      */
212     public static DetailNode getDescriptionEndNode(DetailNode moduleJavadoc) {
213         final DetailNode descriptionEndNode;
214 
215         final DetailNode notesStartingNode =
216             getNotesSectionStartNode(moduleJavadoc);
217 
218         if (notesStartingNode != null) {
219             descriptionEndNode = notesStartingNode.getPreviousSibling();
220         }
221         else {
222             descriptionEndNode = getNodeBeforeJavadocTags(moduleJavadoc);
223         }
224 
225         return descriptionEndNode;
226     }
227 
228     /**
229      * Gets the start node of the Notes section.
230      *
231      * @param moduleJavadoc javadoc of module.
232      * @return start node.
233      */
234     public static DetailNode getNotesSectionStartNode(DetailNode moduleJavadoc) {
235         DetailNode notesStartNode = null;
236         if (moduleJavadoc != null) {
237             DetailNode node = moduleJavadoc.getFirstChild();
238 
239             while (node != null) {
240                 if (isNotesSectionNode(node)) {
241                     notesStartNode = node;
242                     break;
243                 }
244                 node = node.getNextSibling();
245             }
246         }
247 
248         return notesStartNode;
249     }
250 
251     /**
252      * Checks if the given node is the start of the Notes section.
253      *
254      * @param node the node to check.
255      * @return true if the node is the start of the Notes section, false otherwise.
256      */
257     private static boolean isNotesSectionNode(DetailNode node) {
258         boolean result = false;
259         if (node.getType() == JavadocCommentsTokenTypes.HTML_ELEMENT) {
260             if (JavadocUtil.isTag(node, "ul")) {
261                 final DetailNode htmlContentNode = JavadocUtil.findFirstToken(
262                     node, JavadocCommentsTokenTypes.HTML_CONTENT);
263                 result = htmlContentNode != null
264                         && isStartOfNotesSection(htmlContentNode.getFirstChild());
265             }
266             else {
267                 result = (JavadocUtil.isTag(node, "p")
268                             || JavadocUtil.isTag(node, "li"))
269                             && isStartOfNotesSection(node);
270             }
271         }
272         return result;
273     }
274 
275     /**
276      * Gets the node representing the start of the {@code @since} version tag
277      * in the module's Javadoc.
278      *
279      * @param moduleJavadoc the root Javadoc node of the module
280      * @return the {@code @since} tag start node, or {@code null} if not found
281      */
282     public static DetailNode getModuleSinceVersionTagStartNode(DetailNode moduleJavadoc) {
283         DetailNode result = null;
284 
285         if (moduleJavadoc != null) {
286             result = JavadocUtil.getAllNodesOfType(
287                     moduleJavadoc, JavadocCommentsTokenTypes.JAVADOC_BLOCK_TAG).stream()
288                 .filter(javadocTag -> {
289                     final DetailNode firstChild = javadocTag.getFirstChild();
290                     return firstChild != null
291                             && firstChild.getType()
292                                 == JavadocCommentsTokenTypes.SINCE_BLOCK_TAG;
293                 })
294                 .findFirst()
295                 .orElse(null);
296         }
297         return result;
298     }
299 
300     /**
301      * Gets the node of module's javadoc whose next sibling is a node that defines a javadoc tag.
302      *
303      * @param moduleJavadoc the root Javadoc node of the module
304      * @return the node that precedes node defining javadoc tag if present,
305      *     otherwise just the last node of module's javadoc.
306      */
307     public static DetailNode getNodeBeforeJavadocTags(DetailNode moduleJavadoc) {
308         DetailNode nodeBeforeJavadocTags = null;
309 
310         if (moduleJavadoc != null) {
311             nodeBeforeJavadocTags = moduleJavadoc.getFirstChild();
312 
313             if (nodeBeforeJavadocTags != null) {
314                 while (nodeBeforeJavadocTags.getNextSibling() != null
315                         && nodeBeforeJavadocTags.getNextSibling().getType()
316                             != JavadocCommentsTokenTypes.JAVADOC_BLOCK_TAG) {
317 
318                     nodeBeforeJavadocTags = nodeBeforeJavadocTags.getNextSibling();
319                 }
320             }
321         }
322 
323         return nodeBeforeJavadocTags;
324     }
325 
326     /**
327      * Gets the Notes section of module from module javadoc.
328      *
329      * @param moduleJavadoc module javadoc.
330      * @return Notes section of module.
331      */
332     public static String getModuleNotes(DetailNode moduleJavadoc) {
333         final String result;
334 
335         final DetailNode notesStartNode = getNotesSectionStartNode(moduleJavadoc);
336 
337         if (notesStartNode == null) {
338             result = "";
339         }
340         else {
341             final DetailNode notesEndNode = getNodeBeforeJavadocTags(moduleJavadoc);
342 
343             if (notesEndNode == null) {
344                 result = "";
345             }
346             else {
347                 final String unprocessedNotes = JavadocMetadataScraperUtil.constructSubTreeText(
348                             notesStartNode, notesEndNode);
349                 result = NOTES_LINE_WITH_NEWLINE.matcher(unprocessedNotes).replaceAll("");
350             }
351         }
352 
353         return result;
354     }
355 
356     /**
357      * Checks whether property is to contain tokens.
358      *
359      * @param propertyField property field.
360      * @return true if property is to contain tokens, false otherwise.
361      */
362     public static boolean isPropertySpecialTokenProp(Field propertyField) {
363         boolean result = false;
364 
365         if (propertyField != null) {
366             final XdocsPropertyType fieldXdocAnnotation =
367                 propertyField.getAnnotation(XdocsPropertyType.class);
368 
369             result = fieldXdocAnnotation != null
370                 && fieldXdocAnnotation.value() == PropertyType.TOKEN_ARRAY;
371         }
372 
373         return result;
374     }
375 
376 }