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          final DetailNode htmlContentNode = JavadocUtil.findFirstToken(
100             htmlElement, JavadocCommentsTokenTypes.HTML_CONTENT);
101 
102         return htmlContentNode != null && JavadocMetadataScraperUtil.isChildNodeTextMatches(
103             htmlContentNode, NOTES_LINE);
104     }
105 
106     /**
107      * Writes the given javadoc chunk into xdoc.
108      *
109      * @param javadocPortion javadoc text.
110      * @param sink sink of the macro.
111      */
112     public static void writeOutJavadocPortion(String javadocPortion, Sink sink) {
113         final String[] javadocPortionLinesSplit = javadocPortion.split(NEWLINE
114             .replace("\r", ""));
115 
116         sink.rawText(javadocPortionLinesSplit[0]);
117         String lastHtmlTag = javadocPortionLinesSplit[0];
118 
119         for (int index = 1; index < javadocPortionLinesSplit.length; index++) {
120             final String currentLine = javadocPortionLinesSplit[index].trim();
121             final String processedLine;
122 
123             if (currentLine.isEmpty()) {
124                 processedLine = NEWLINE;
125             }
126             else if (currentLine.startsWith("<")
127                 && !startsWithTextFormattingHtmlTag(currentLine)) {
128 
129                 processedLine = INDENT_LEVEL_8 + currentLine;
130                 lastHtmlTag = currentLine;
131             }
132             else if (lastHtmlTag.contains("<pre")) {
133                 final String currentLineWithPreservedIndent = javadocPortionLinesSplit[index]
134                     .substring(1);
135 
136                 processedLine = NEWLINE + currentLineWithPreservedIndent;
137             }
138             else {
139                 processedLine = INDENT_LEVEL_10 + currentLine;
140             }
141 
142             sink.rawText(processedLine);
143         }
144 
145     }
146 
147     /**
148      * Checks if given line starts with HTML text-formatting tag.
149      *
150      * @param line line to check on.
151      * @return whether given line starts with HTML text-formatting tag.
152      */
153     public static boolean startsWithTextFormattingHtmlTag(String line) {
154         boolean result = false;
155 
156         for (String tag : HTML_TEXT_FORMAT_TAGS) {
157             if (line.startsWith(tag)) {
158                 result = true;
159                 break;
160             }
161         }
162 
163         return result;
164     }
165 
166     /**
167      * Gets the description of module from module javadoc.
168      *
169      * @param moduleJavadoc module javadoc.
170      * @return module description.
171      */
172     public static String getModuleDescription(DetailNode moduleJavadoc) {
173         final DetailNode descriptionEndNode = getDescriptionEndNode(moduleJavadoc);
174 
175         return JavadocMetadataScraperUtil.constructSubTreeText(moduleJavadoc, descriptionEndNode);
176     }
177 
178     /**
179      * Gets the {@code @since} version of module from module javadoc.
180      *
181      * @param moduleJavadoc module javadoc
182      * @return module {@code @since} version. For instance, {@code 8.0}
183      */
184     public static String getModuleSinceVersion(DetailNode moduleJavadoc) {
185         final DetailNode sinceTagNode = getModuleSinceVersionTagStartNode(moduleJavadoc);
186         return JavadocMetadataScraperUtil
187                     .constructSubTreeText(sinceTagNode, sinceTagNode.getFirstChild())
188                     .replace("@since ", "");
189     }
190 
191     /**
192      * Gets the end node of the description.
193      *
194      * @param moduleJavadoc javadoc of module.
195      * @return the end index.
196      */
197     public static DetailNode getDescriptionEndNode(DetailNode moduleJavadoc) {
198         final DetailNode descriptionEndNode;
199 
200         final DetailNode notesStartingNode =
201             getNotesSectionStartNode(moduleJavadoc);
202 
203         if (notesStartingNode != null) {
204             descriptionEndNode = notesStartingNode.getPreviousSibling();
205         }
206         else {
207             descriptionEndNode = getNodeBeforeJavadocTags(moduleJavadoc);
208         }
209 
210         return descriptionEndNode;
211     }
212 
213     /**
214      * Gets the start node of the Notes section.
215      *
216      * @param moduleJavadoc javadoc of module.
217      * @return start node.
218      */
219     public static DetailNode getNotesSectionStartNode(DetailNode moduleJavadoc) {
220         DetailNode notesStartNode = null;
221         DetailNode node = moduleJavadoc.getFirstChild();
222 
223         while (node != null) {
224             if (node.getType() == JavadocCommentsTokenTypes.HTML_ELEMENT) {
225                 boolean found = false;
226                 if (JavadocUtil.isTag(node, "ul")) {
227                     final DetailNode htmlContentNode = JavadocUtil.findFirstToken(
228                         node, JavadocCommentsTokenTypes.HTML_CONTENT);
229                     if (isStartOfNotesSection(htmlContentNode.getFirstChild())) {
230                         notesStartNode = node;
231                         found = true;
232                     }
233                 }
234                 else if ((JavadocUtil.isTag(node, "p")
235                             || JavadocUtil.isTag(node, "li"))
236                             && isStartOfNotesSection(node)) {
237                     notesStartNode = node;
238                     found = true;
239                 }
240                 if (found) {
241                     break;
242                 }
243             }
244             node = node.getNextSibling();
245         }
246 
247         return notesStartNode;
248     }
249 
250     /**
251      * Gets the node representing the start of the {@code @since} version tag
252      * in the module's Javadoc.
253      *
254      * @param moduleJavadoc the root Javadoc node of the module
255      * @return the {@code @since} tag start node, or {@code null} if not found
256      */
257     public static DetailNode getModuleSinceVersionTagStartNode(DetailNode moduleJavadoc) {
258         return JavadocUtil.getAllNodesOfType(
259                 moduleJavadoc, JavadocCommentsTokenTypes.JAVADOC_BLOCK_TAG).stream()
260             .filter(javadocTag -> {
261                 return javadocTag.getFirstChild().getType()
262                         == JavadocCommentsTokenTypes.SINCE_BLOCK_TAG;
263             })
264             .findFirst()
265             .orElse(null);
266     }
267 
268     /**
269      * Gets the node of module's javadoc whose next sibling is a node that defines a javadoc tag.
270      *
271      * @param moduleJavadoc the root Javadoc node of the module
272      * @return the node that precedes node defining javadoc tag if present,
273      *     otherwise just the last node of module's javadoc.
274      */
275     public static DetailNode getNodeBeforeJavadocTags(DetailNode moduleJavadoc) {
276         DetailNode nodeBeforeJavadocTags = moduleJavadoc.getFirstChild();
277 
278         while (nodeBeforeJavadocTags.getNextSibling() != null
279                 && nodeBeforeJavadocTags.getNextSibling().getType()
280                     != JavadocCommentsTokenTypes.JAVADOC_BLOCK_TAG) {
281 
282             nodeBeforeJavadocTags = nodeBeforeJavadocTags.getNextSibling();
283         }
284 
285         return nodeBeforeJavadocTags;
286     }
287 
288     /**
289      * Gets the Notes section of module from module javadoc.
290      *
291      * @param moduleJavadoc module javadoc.
292      * @return Notes section of module.
293      */
294     public static String getModuleNotes(DetailNode moduleJavadoc) {
295         final String result;
296 
297         final DetailNode notesStartNode = getNotesSectionStartNode(moduleJavadoc);
298 
299         if (notesStartNode == null) {
300             result = "";
301         }
302         else {
303             final DetailNode notesEndNode = getNodeBeforeJavadocTags(moduleJavadoc);
304 
305             final String unprocessedNotes =
306                     JavadocMetadataScraperUtil.constructSubTreeText(
307                         notesStartNode, notesEndNode);
308             result = NOTES_LINE_WITH_NEWLINE.matcher(unprocessedNotes).replaceAll("");
309         }
310 
311         return result;
312     }
313 
314     /**
315      * Checks whether property is to contain tokens.
316      *
317      * @param propertyField property field.
318      * @return true if property is to contain tokens, false otherwise.
319      */
320     public static boolean isPropertySpecialTokenProp(Field propertyField) {
321         boolean result = false;
322 
323         if (propertyField != null) {
324             final XdocsPropertyType fieldXdocAnnotation =
325                 propertyField.getAnnotation(XdocsPropertyType.class);
326 
327             result = fieldXdocAnnotation != null
328                 && fieldXdocAnnotation.value() == PropertyType.TOKEN_ARRAY;
329         }
330 
331         return result;
332     }
333 
334 }