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 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 end node of the description.
180      *
181      * @param moduleJavadoc javadoc of module.
182      * @return the end index.
183      */
184     public static DetailNode getDescriptionEndNode(DetailNode moduleJavadoc) {
185         final DetailNode descriptionEndNode;
186 
187         final DetailNode notesStartingNode =
188             getNotesSectionStartNode(moduleJavadoc);
189 
190         if (notesStartingNode != null) {
191             descriptionEndNode = notesStartingNode.getPreviousSibling();
192         }
193         else {
194             descriptionEndNode = getModuleSinceVersionTagStartNode(
195                                         moduleJavadoc).getPreviousSibling();
196         }
197 
198         return descriptionEndNode;
199     }
200 
201     /**
202      * Gets the start node of the Notes section.
203      *
204      * @param moduleJavadoc javadoc of module.
205      * @return start node.
206      */
207     public static DetailNode getNotesSectionStartNode(DetailNode moduleJavadoc) {
208         DetailNode notesStartNode = null;
209         DetailNode node = moduleJavadoc.getFirstChild();
210 
211         while (node != null) {
212             if (node.getType() == JavadocCommentsTokenTypes.HTML_ELEMENT) {
213                 boolean found = false;
214                 if (JavadocUtil.isTag(node, "ul")) {
215                     final DetailNode htmlContentNode = JavadocUtil.findFirstToken(
216                         node, JavadocCommentsTokenTypes.HTML_CONTENT);
217                     if (isStartOfNotesSection(htmlContentNode.getFirstChild())) {
218                         notesStartNode = node;
219                         found = true;
220                     }
221                 }
222                 else if ((JavadocUtil.isTag(node, "p")
223                             || JavadocUtil.isTag(node, "li"))
224                             && isStartOfNotesSection(node)) {
225                     notesStartNode = node;
226                     found = true;
227                 }
228                 if (found) {
229                     break;
230                 }
231             }
232             node = node.getNextSibling();
233         }
234 
235         return notesStartNode;
236     }
237 
238     /**
239      * Gets the node representing the start of the {@code @since} version tag
240      * in the module's Javadoc.
241      *
242      * @param moduleJavadoc the root Javadoc node of the module
243      * @return the {@code @since} tag start node, or {@code null} if not found
244      */
245     public static DetailNode getModuleSinceVersionTagStartNode(DetailNode moduleJavadoc) {
246         return JavadocUtil.getAllNodesOfType(
247                 moduleJavadoc, JavadocCommentsTokenTypes.JAVADOC_BLOCK_TAG).stream()
248             .filter(javadocTag -> {
249                 return javadocTag.getFirstChild().getType()
250                         == JavadocCommentsTokenTypes.SINCE_BLOCK_TAG;
251             })
252             .findFirst()
253             .orElse(null);
254     }
255 
256     /**
257      * Gets the Notes section of module from module javadoc.
258      *
259      * @param moduleJavadoc module javadoc.
260      * @return Notes section of module.
261      */
262     public static String getModuleNotes(DetailNode moduleJavadoc) {
263         final String result;
264 
265         final DetailNode notesStartNode = getNotesSectionStartNode(moduleJavadoc);
266 
267         if (notesStartNode == null) {
268             result = "";
269         }
270         else {
271             final DetailNode notesEndNode = getNotesEndNode(moduleJavadoc);
272 
273             final String unprocessedNotes =
274                     JavadocMetadataScraperUtil.constructSubTreeText(
275                         notesStartNode, notesEndNode);
276             result = NOTES_LINE_WITH_NEWLINE.matcher(unprocessedNotes).replaceAll("");
277         }
278 
279         return result;
280     }
281 
282     /**
283      * Gets the end node of the Notes.
284      *
285      * @param moduleJavadoc javadoc of module.
286      * @return the end index.
287      */
288     public static DetailNode getNotesEndNode(DetailNode moduleJavadoc) {
289         return getModuleSinceVersionTagStartNode(
290                 moduleJavadoc).getPreviousSibling();
291     }
292 
293     /**
294      * Checks whether property is to contain tokens.
295      *
296      * @param propertyField property field.
297      * @return true if property is to contain tokens, false otherwise.
298      */
299     public static boolean isPropertySpecialTokenProp(Field propertyField) {
300         boolean result = false;
301 
302         if (propertyField != null) {
303             final XdocsPropertyType fieldXdocAnnotation =
304                 propertyField.getAnnotation(XdocsPropertyType.class);
305 
306             result = fieldXdocAnnotation != null
307                 && fieldXdocAnnotation.value() == PropertyType.TOKEN_ARRAY;
308         }
309 
310         return result;
311     }
312 
313 }