View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2025 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 8 spaces of indentation. */
43      public static final String INDENT_LEVEL_8 = SiteUtil.getNewlineAndIndentSpaces(8);
44      /** A newline with 10 spaces of indentation. */
45      public static final String INDENT_LEVEL_10 = SiteUtil.getNewlineAndIndentSpaces(10);
46      /** A newline with 12 spaces of indentation. */
47      public static final String INDENT_LEVEL_12 = SiteUtil.getNewlineAndIndentSpaces(12);
48      /** A newline with 14 spaces of indentation. */
49      public static final String INDENT_LEVEL_14 = SiteUtil.getNewlineAndIndentSpaces(14);
50      /** A newline with 16 spaces of indentation. */
51      public static final String INDENT_LEVEL_16 = SiteUtil.getNewlineAndIndentSpaces(16);
52      /** A newline with 18 spaces of indentation. */
53      public static final String INDENT_LEVEL_18 = SiteUtil.getNewlineAndIndentSpaces(18);
54      /** A newline with 20 spaces of indentation. */
55      public static final String INDENT_LEVEL_20 = SiteUtil.getNewlineAndIndentSpaces(20);
56      /** A set of all html tags that need to be considered as text formatting for this macro. */
57      public static final Set<String> HTML_TEXT_FORMAT_TAGS = Set.of("<code>", "<a", "</a>", "<b>",
58          "</b>", "<strong>", "</strong>", "<i>", "</i>", "<em>", "</em>", "<small>", "</small>",
59          "<ins>", "<sub>", "<sup>");
60      /** "Notes:" javadoc marking. */
61      public static final String NOTES = "Notes:";
62      /** "Notes:" line. */
63      public static final Pattern NOTES_LINE = Pattern.compile("\\s*" + NOTES + "$");
64      /** "Notes:" line with new line accounted. */
65      public static final Pattern NOTES_LINE_WITH_NEWLINE = Pattern.compile("\r?\n\\s?" + NOTES);
66  
67      /**
68       * Private utility constructor.
69       */
70      private ModuleJavadocParsingUtil() {
71      }
72  
73      /**
74       * Gets properties of the specified module.
75       *
76       * @param moduleName name of module.
77       * @return set of properties name if present, otherwise null.
78       * @throws MacroExecutionException if the module could not be retrieved.
79       */
80      public static Set<String> getPropertyNames(String moduleName)
81              throws MacroExecutionException {
82          final Object instance = SiteUtil.getModuleInstance(moduleName);
83          final Class<?> clss = instance.getClass();
84  
85          return SiteUtil.getPropertiesForDocumentation(clss, instance);
86      }
87  
88      /**
89       * Determines whether the given HTML node marks the start of the "Notes" section.
90       *
91       * @param htmlElement html element to check.
92       * @return true if the element starts the "Notes" section, false otherwise.
93       */
94      private static boolean isStartOfNotesSection(DetailNode htmlElement) {
95          final DetailNode htmlContentNode = JavadocUtil.findFirstToken(
96              htmlElement, JavadocCommentsTokenTypes.HTML_CONTENT);
97  
98          return htmlContentNode != null && JavadocMetadataScraperUtil.isChildNodeTextMatches(
99              htmlContentNode, NOTES_LINE);
100     }
101 
102     /**
103      * Writes the given javadoc chunk into xdoc.
104      *
105      * @param javadocPortion javadoc text.
106      * @param sink sink of the macro.
107      */
108     public static void writeOutJavadocPortion(String javadocPortion, Sink sink) {
109         final String[] javadocPortionLinesSplit = javadocPortion.split(NEWLINE
110             .replace("\r", ""));
111 
112         sink.rawText(javadocPortionLinesSplit[0]);
113         String lastHtmlTag = javadocPortionLinesSplit[0];
114 
115         for (int index = 1; index < javadocPortionLinesSplit.length; index++) {
116             final String currentLine = javadocPortionLinesSplit[index].trim();
117             final String processedLine;
118 
119             if (currentLine.isEmpty()) {
120                 processedLine = NEWLINE;
121             }
122             else if (currentLine.startsWith("<")
123                 && !startsWithTextFormattingHtmlTag(currentLine)) {
124 
125                 processedLine = INDENT_LEVEL_8 + currentLine;
126                 lastHtmlTag = currentLine;
127             }
128             else if (lastHtmlTag.contains("<pre")) {
129                 final String currentLineWithPreservedIndent = javadocPortionLinesSplit[index]
130                     .substring(1);
131 
132                 processedLine = NEWLINE + currentLineWithPreservedIndent;
133             }
134             else {
135                 processedLine = INDENT_LEVEL_10 + currentLine;
136             }
137 
138             sink.rawText(processedLine);
139         }
140 
141     }
142 
143     /**
144      * Checks if given line starts with HTML text-formatting tag.
145      *
146      * @param line line to check on.
147      * @return whether given line starts with HTML text-formatting tag.
148      */
149     public static boolean startsWithTextFormattingHtmlTag(String line) {
150         boolean result = false;
151 
152         for (String tag : HTML_TEXT_FORMAT_TAGS) {
153             if (line.startsWith(tag)) {
154                 result = true;
155                 break;
156             }
157         }
158 
159         return result;
160     }
161 
162     /**
163      * Gets the description of module from module javadoc.
164      *
165      * @param moduleJavadoc module javadoc.
166      * @return module description.
167      */
168     public static String getModuleDescription(DetailNode moduleJavadoc) {
169         final DetailNode descriptionEndNode = getDescriptionEndNode(moduleJavadoc);
170 
171         return JavadocMetadataScraperUtil.constructSubTreeText(moduleJavadoc, descriptionEndNode);
172     }
173 
174     /**
175      * Gets the end node of the description.
176      *
177      * @param moduleJavadoc javadoc of module.
178      * @return the end index.
179      */
180     public static DetailNode getDescriptionEndNode(DetailNode moduleJavadoc) {
181         final DetailNode descriptionEndNode;
182 
183         final DetailNode notesStartingNode =
184             getNotesSectionStartNode(moduleJavadoc);
185 
186         if (notesStartingNode != null) {
187             descriptionEndNode = notesStartingNode.getPreviousSibling();
188         }
189         else {
190             descriptionEndNode = getModuleSinceVersionTagStartNode(
191                                         moduleJavadoc).getPreviousSibling();
192         }
193 
194         return descriptionEndNode;
195     }
196 
197     /**
198      * Gets the start node of the Notes section.
199      *
200      * @param moduleJavadoc javadoc of module.
201      * @return start node.
202      */
203     public static DetailNode getNotesSectionStartNode(DetailNode moduleJavadoc) {
204         DetailNode notesStartNode = null;
205         DetailNode node = moduleJavadoc.getFirstChild();
206 
207         while (node != null) {
208             if (node.getType() == JavadocCommentsTokenTypes.HTML_ELEMENT) {
209                 boolean found = false;
210                 if (JavadocUtil.isTag(node, "ul")) {
211                     final DetailNode htmlContentNode = JavadocUtil.findFirstToken(
212                         node, JavadocCommentsTokenTypes.HTML_CONTENT);
213                     if (isStartOfNotesSection(htmlContentNode.getFirstChild())) {
214                         notesStartNode = node;
215                         found = true;
216                     }
217                 }
218                 else if ((JavadocUtil.isTag(node, "p")
219                             || JavadocUtil.isTag(node, "li"))
220                             && isStartOfNotesSection(node)) {
221                     notesStartNode = node;
222                     found = true;
223                 }
224                 if (found) {
225                     break;
226                 }
227             }
228             node = node.getNextSibling();
229         }
230 
231         return notesStartNode;
232     }
233 
234     /**
235      * Gets the node representing the start of the {@code @since} version tag
236      * in the module's Javadoc.
237      *
238      * @param moduleJavadoc the root Javadoc node of the module
239      * @return the {@code @since} tag start node, or {@code null} if not found
240      */
241     public static DetailNode getModuleSinceVersionTagStartNode(DetailNode moduleJavadoc) {
242         return JavadocUtil.getAllNodesOfType(
243                 moduleJavadoc, JavadocCommentsTokenTypes.JAVADOC_BLOCK_TAG).stream()
244             .filter(javadocTag -> {
245                 return javadocTag.getFirstChild().getType()
246                         == JavadocCommentsTokenTypes.SINCE_BLOCK_TAG;
247             })
248             .findFirst()
249             .orElse(null);
250     }
251 
252     /**
253      * Gets the Notes section of module from module javadoc.
254      *
255      * @param moduleJavadoc module javadoc.
256      * @return Notes section of module.
257      */
258     public static String getModuleNotes(DetailNode moduleJavadoc) {
259         final String result;
260 
261         final DetailNode notesStartNode = getNotesSectionStartNode(moduleJavadoc);
262 
263         if (notesStartNode == null) {
264             result = "";
265         }
266         else {
267             final DetailNode notesEndNode = getNotesEndNode(moduleJavadoc);
268 
269             final String unprocessedNotes =
270                     JavadocMetadataScraperUtil.constructSubTreeText(
271                         notesStartNode, notesEndNode);
272             result = NOTES_LINE_WITH_NEWLINE.matcher(unprocessedNotes).replaceAll("");
273         }
274 
275         return result;
276     }
277 
278     /**
279      * Gets the end node of the Notes.
280      *
281      * @param moduleJavadoc javadoc of module.
282      * @return the end index.
283      */
284     public static DetailNode getNotesEndNode(DetailNode moduleJavadoc) {
285         return getModuleSinceVersionTagStartNode(
286                 moduleJavadoc).getPreviousSibling();
287     }
288 
289     /**
290      * Checks whether property is to contain tokens.
291      *
292      * @param propertyField property field.
293      * @return true if property is to contain tokens, false otherwise.
294      */
295     public static boolean isPropertySpecialTokenProp(Field propertyField) {
296         boolean result = false;
297 
298         if (propertyField != null) {
299             final XdocsPropertyType fieldXdocAnnotation =
300                 propertyField.getAnnotation(XdocsPropertyType.class);
301 
302             result = fieldXdocAnnotation != null
303                 && fieldXdocAnnotation.value() == PropertyType.TOKEN_ARRAY;
304         }
305 
306         return result;
307     }
308 
309 }