001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2025 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018/////////////////////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.site; 021 022import java.nio.file.Path; 023import java.nio.file.Paths; 024import java.util.Map; 025import java.util.Optional; 026import java.util.Set; 027import java.util.regex.Pattern; 028 029import org.apache.maven.doxia.macro.AbstractMacro; 030import org.apache.maven.doxia.macro.Macro; 031import org.apache.maven.doxia.macro.MacroExecutionException; 032import org.apache.maven.doxia.macro.MacroRequest; 033import org.apache.maven.doxia.sink.Sink; 034import org.codehaus.plexus.component.annotations.Component; 035 036import com.puppycrawl.tools.checkstyle.api.DetailNode; 037import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes; 038import com.puppycrawl.tools.checkstyle.meta.JavadocMetadataScraper; 039import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 040import com.puppycrawl.tools.checkstyle.utils.JavadocUtil; 041 042/** 043 * A macro that inserts a description of module from its Javadoc. 044 */ 045@Component(role = Macro.class, hint = "description") 046public class DescriptionMacro extends AbstractMacro { 047 048 /** New line escape character. */ 049 private static final String NEWLINE = "\n"; 050 /** A newline with 8 spaces of indentation. */ 051 private static final String INDENT_LEVEL_8 = SiteUtil.getNewlineAndIndentSpaces(8); 052 /** A newline with 10 spaces of indentation. */ 053 private static final String INDENT_LEVEL_10 = SiteUtil.getNewlineAndIndentSpaces(10); 054 /** A set of all html tags that need to be considered as text formatting for this macro. */ 055 private static final Set<String> HTML_TEXT_FORMAT_TAGS = Set.of("<code>", "<a", "</a>", "<b>", 056 "</b>", "<strong>", "</strong>", "<i>", "</i>", "<em>", "</em>", "<small>", "</small>", 057 "<ins>", "<sub>", "<sup>"); 058 /** "Notes:" javadoc marking. */ 059 private static final String NOTES = "Notes:"; 060 /** "Notes:" line. */ 061 private static final Pattern NOTES_LINE = Pattern.compile("\\s*" + NOTES + "$"); 062 063 @Override 064 public void execute(Sink sink, MacroRequest request) throws MacroExecutionException { 065 final Path modulePath = Paths.get((String) request.getParameter("modulePath")); 066 final String moduleName = CommonUtil.getFileNameWithoutExtension(modulePath.toString()); 067 068 final Set<String> propertyNames = getPropertyNames(moduleName); 069 final Map<String, DetailNode> propertiesJavadocs = SiteUtil.getPropertiesJavadocs( 070 propertyNames, moduleName, modulePath); 071 072 final DetailNode moduleJavadoc = propertiesJavadocs.get(moduleName); 073 074 final int descriptionEndIndex = getDescriptionEndIndex(moduleJavadoc, propertyNames); 075 final String moduleDescription = JavadocMetadataScraper.constructSubTreeText( 076 moduleJavadoc, 0, descriptionEndIndex); 077 078 writeOutDescription(moduleDescription, sink); 079 080 } 081 082 /** 083 * Assigns values to each instance variable. 084 * 085 * @param moduleName name of module. 086 * @return set of property names. 087 * @throws MacroExecutionException if the module could not be retrieved. 088 */ 089 private static Set<String> getPropertyNames(String moduleName) 090 throws MacroExecutionException { 091 final Object instance = SiteUtil.getModuleInstance(moduleName); 092 final Class<?> clss = instance.getClass(); 093 094 return SiteUtil.getPropertiesForDocumentation(clss, instance); 095 } 096 097 /** 098 * Gets the end index of the description. 099 * 100 * @param moduleJavadoc javadoc of module. 101 * @param propertyNamesSet Set with property names. 102 * @return the end index. 103 */ 104 private static int getDescriptionEndIndex(DetailNode moduleJavadoc, 105 Set<String> propertyNamesSet) { 106 int descriptionEndIndex = -1; 107 108 if (getNotesStartIndex(moduleJavadoc) > -1) { 109 descriptionEndIndex += getNotesStartIndex(moduleJavadoc); 110 } 111 else if (propertyNamesSet.isEmpty()) { 112 descriptionEndIndex += getParentSectionStartIndex(moduleJavadoc); 113 } 114 else { 115 final String somePropertyName = propertyNamesSet.iterator().next(); 116 117 final Optional<DetailNode> somePropertyModuleNode = 118 SiteUtil.getPropertyJavadocNodeInModule( 119 somePropertyName, moduleJavadoc); 120 121 if (somePropertyModuleNode.isPresent()) { 122 descriptionEndIndex += JavadocMetadataScraper 123 .getParentIndexOf(somePropertyModuleNode.get()); 124 } 125 } 126 127 return descriptionEndIndex; 128 } 129 130 /** 131 * Gets the start index of the Notes section. 132 * 133 * @param moduleJavadoc javadoc of module. 134 * @return start index. 135 */ 136 public static int getNotesStartIndex(DetailNode moduleJavadoc) { 137 int notesStartIndex = -1; 138 139 for (DetailNode node : moduleJavadoc.getChildren()) { 140 if (node.getType() == JavadocTokenTypes.HTML_ELEMENT 141 && hasHtmlTagToStoreNotesSection(node)) { 142 143 notesStartIndex += node.getIndex(); 144 break; 145 } 146 } 147 148 return notesStartIndex; 149 } 150 151 /** 152 * Checks whether html element has tag that stores notes section data. 153 * 154 * @param htmlElement html element to check. 155 * @return true if html element has tag storing notes section, otherwise false. 156 */ 157 private static boolean hasHtmlTagToStoreNotesSection(DetailNode htmlElement) { 158 final DetailNode paragraphNode = JavadocUtil.findFirstToken( 159 htmlElement, JavadocTokenTypes.PARAGRAPH); 160 final Optional<DetailNode> liNode = getLiTagNode(htmlElement); 161 162 return paragraphNode != null && JavadocMetadataScraper.isChildNodeTextMatches( 163 paragraphNode, NOTES_LINE) 164 || liNode.isPresent() && JavadocMetadataScraper.isChildNodeTextMatches( 165 liNode.get(), NOTES_LINE); 166 } 167 168 /** 169 * Gets the node of Li HTML tag. 170 * 171 * @param htmlElement html element to get li tag from. 172 * @return Optional of li tag node. 173 */ 174 private static Optional<DetailNode> getLiTagNode(DetailNode htmlElement) { 175 return Optional.of(htmlElement) 176 .map(element -> JavadocUtil.findFirstToken(element, JavadocTokenTypes.HTML_TAG)) 177 .map(element -> JavadocUtil.findFirstToken(element, JavadocTokenTypes.HTML_ELEMENT)) 178 .map(element -> JavadocUtil.findFirstToken(element, JavadocTokenTypes.LI)); 179 } 180 181 /** 182 * Gets the starting index of the "Parent is" paragraph in module's javadoc. 183 * 184 * @param moduleJavadoc javadoc of module. 185 * @return start index of parent subsection. 186 */ 187 private static int getParentSectionStartIndex(DetailNode moduleJavadoc) { 188 int parentStartIndex = 0; 189 190 for (DetailNode node : moduleJavadoc.getChildren()) { 191 if (node.getType() == JavadocTokenTypes.HTML_ELEMENT) { 192 final DetailNode paragraphNode = JavadocUtil.findFirstToken( 193 node, JavadocTokenTypes.PARAGRAPH); 194 if (paragraphNode != null && JavadocMetadataScraper.isParentText(paragraphNode)) { 195 parentStartIndex = node.getIndex(); 196 break; 197 } 198 } 199 } 200 201 return parentStartIndex; 202 } 203 204 /** 205 * Writes the description into xdoc. 206 * 207 * @param description description of the module. 208 * @param sink sink of the macro. 209 */ 210 private static void writeOutDescription(String description, Sink sink) { 211 final String[] moduleDescriptionLinesSplit = description.split(NEWLINE); 212 213 sink.rawText(moduleDescriptionLinesSplit[0]); 214 String lastHtmlTag = moduleDescriptionLinesSplit[0]; 215 216 for (int index = 1; index < moduleDescriptionLinesSplit.length; index++) { 217 final String currentLine = moduleDescriptionLinesSplit[index].trim(); 218 final String processedLine; 219 220 if (currentLine.isEmpty()) { 221 processedLine = NEWLINE; 222 } 223 else if (currentLine.startsWith("<") 224 && !startsWithTextFormattingHtmlTag(currentLine)) { 225 226 processedLine = INDENT_LEVEL_8 + currentLine; 227 lastHtmlTag = currentLine; 228 } 229 else if (lastHtmlTag.contains("<pre")) { 230 final String currentLineWithPreservedIndent = moduleDescriptionLinesSplit[index] 231 .substring(1); 232 233 processedLine = NEWLINE + currentLineWithPreservedIndent; 234 } 235 else { 236 processedLine = INDENT_LEVEL_10 + currentLine; 237 } 238 239 sink.rawText(processedLine); 240 } 241 242 } 243 244 /** 245 * Checks if given line starts with HTML text-formatting tag. 246 * 247 * @param line line to check on. 248 * @return whether given line starts with HTML text-formatting tag. 249 */ 250 private static boolean startsWithTextFormattingHtmlTag(String line) { 251 boolean result = false; 252 253 for (String tag : HTML_TEXT_FORMAT_TAGS) { 254 if (line.startsWith(tag)) { 255 result = true; 256 break; 257 } 258 } 259 260 return result; 261 } 262 263}