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.util.Optional;
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.api.DetailNode;
30  import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes;
31  import com.puppycrawl.tools.checkstyle.meta.JavadocMetadataScraper;
32  import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
33  
34  /**
35   * Utility class for parsing javadocs of modules.
36   */
37  public final class ModuleJavadocParsingUtil {
38      /** New line escape character. */
39      public static final String NEWLINE = System.lineSeparator();
40      /** A newline with 8 spaces of indentation. */
41      public static final String INDENT_LEVEL_8 = SiteUtil.getNewlineAndIndentSpaces(8);
42      /** A newline with 10 spaces of indentation. */
43      public static final String INDENT_LEVEL_10 = SiteUtil.getNewlineAndIndentSpaces(10);
44      /** A newline with 12 spaces of indentation. */
45      public static final String INDENT_LEVEL_12 = SiteUtil.getNewlineAndIndentSpaces(12);
46      /** A newline with 14 spaces of indentation. */
47      public static final String INDENT_LEVEL_14 = SiteUtil.getNewlineAndIndentSpaces(14);
48      /** A newline with 16 spaces of indentation. */
49      public static final String INDENT_LEVEL_16 = SiteUtil.getNewlineAndIndentSpaces(16);
50      /** A newline with 18 spaces of indentation. */
51      public static final String INDENT_LEVEL_18 = SiteUtil.getNewlineAndIndentSpaces(18);
52      /** A newline with 20 spaces of indentation. */
53      public static final String INDENT_LEVEL_20 = SiteUtil.getNewlineAndIndentSpaces(20);
54      /** A set of all html tags that need to be considered as text formatting for this macro. */
55      public static final Set<String> HTML_TEXT_FORMAT_TAGS = Set.of("<code>", "<a", "</a>", "<b>",
56          "</b>", "<strong>", "</strong>", "<i>", "</i>", "<em>", "</em>", "<small>", "</small>",
57          "<ins>", "<sub>", "<sup>");
58      /** "Notes:" javadoc marking. */
59      public static final String NOTES = "Notes:";
60      /** "Notes:" line. */
61      public static final Pattern NOTES_LINE = Pattern.compile("\\s*" + NOTES + "$");
62  
63      /**
64       * Private utility constructor.
65       */
66      private ModuleJavadocParsingUtil() {
67      }
68  
69      /**
70       * Gets properties of the specified module.
71       *
72       * @param moduleName name of module.
73       * @return set of properties name if present, otherwise null.
74       * @throws MacroExecutionException if the module could not be retrieved.
75       */
76      public static Set<String> getPropertyNames(String moduleName)
77              throws MacroExecutionException {
78          final Object instance = SiteUtil.getModuleInstance(moduleName);
79          final Class<?> clss = instance.getClass();
80  
81          return SiteUtil.getPropertiesForDocumentation(clss, instance);
82      }
83  
84      /**
85       * Gets the starting index of the "Parent is" paragraph in module's javadoc.
86       *
87       * @param moduleJavadoc javadoc of module.
88       * @return start index of parent subsection.
89       */
90      public static int getParentSectionStartIndex(DetailNode moduleJavadoc) {
91          int parentStartIndex = -1;
92  
93          for (DetailNode node : moduleJavadoc.getChildren()) {
94              if (node.getType() == JavadocTokenTypes.HTML_ELEMENT) {
95                  final DetailNode paragraphNode = JavadocUtil.findFirstToken(
96                      node, JavadocTokenTypes.PARAGRAPH);
97                  if (paragraphNode != null && JavadocMetadataScraper.isParentText(paragraphNode)) {
98                      parentStartIndex = node.getIndex();
99                      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 }