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 (NotesMacro.getNotesStartIndex(moduleJavadoc) > -1) { 109 descriptionEndIndex += NotesMacro.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 final DetailNode paragraphNode = JavadocUtil.findFirstToken( 142 node, JavadocTokenTypes.PARAGRAPH); 143 if (paragraphNode != null && JavadocMetadataScraper.isChildNodeTextMatches( 144 paragraphNode, NOTES_LINE)) { 145 146 notesStartIndex = node.getIndex() - 1; 147 break; 148 } 149 } 150 } 151 152 return notesStartIndex; 153 } 154 155 /** 156 * Gets the starting index of the "Parent is" paragraph in module's javadoc. 157 * 158 * @param moduleJavadoc javadoc of module. 159 * @return start index of parent subsection. 160 */ 161 private static int getParentSectionStartIndex(DetailNode moduleJavadoc) { 162 int parentStartIndex = 0; 163 164 for (DetailNode node : moduleJavadoc.getChildren()) { 165 if (node.getType() == JavadocTokenTypes.HTML_ELEMENT) { 166 final DetailNode paragraphNode = JavadocUtil.findFirstToken( 167 node, JavadocTokenTypes.PARAGRAPH); 168 if (paragraphNode != null && JavadocMetadataScraper.isParentText(paragraphNode)) { 169 parentStartIndex = node.getIndex(); 170 break; 171 } 172 } 173 } 174 175 return parentStartIndex; 176 } 177 178 /** 179 * Writes the description into xdoc. 180 * 181 * @param description description of the module. 182 * @param sink sink of the macro. 183 */ 184 private static void writeOutDescription(String description, Sink sink) { 185 final String[] moduleDescriptionLinesSplit = description.split(NEWLINE); 186 187 sink.rawText(moduleDescriptionLinesSplit[0]); 188 String lastHtmlTag = moduleDescriptionLinesSplit[0]; 189 190 for (int index = 1; index < moduleDescriptionLinesSplit.length; index++) { 191 final String currentLine = moduleDescriptionLinesSplit[index].trim(); 192 final String processedLine; 193 194 if (currentLine.isEmpty()) { 195 processedLine = NEWLINE; 196 } 197 else if (currentLine.startsWith("<") 198 && !startsWithTextFormattingHtmlTag(currentLine)) { 199 200 processedLine = INDENT_LEVEL_8 + currentLine; 201 lastHtmlTag = currentLine; 202 } 203 else if (lastHtmlTag.contains("<pre")) { 204 final String currentLineWithPreservedIndent = moduleDescriptionLinesSplit[index] 205 .substring(1); 206 207 processedLine = NEWLINE + currentLineWithPreservedIndent; 208 } 209 else { 210 processedLine = INDENT_LEVEL_10 + currentLine; 211 } 212 213 sink.rawText(processedLine); 214 } 215 216 } 217 218 /** 219 * Checks if given line starts with HTML text-formatting tag. 220 * 221 * @param line line to check on. 222 * @return whether given line starts with HTML text-formatting tag. 223 */ 224 private static boolean startsWithTextFormattingHtmlTag(String line) { 225 boolean result = false; 226 227 for (String tag : HTML_TEXT_FORMAT_TAGS) { 228 if (line.startsWith(tag)) { 229 result = true; 230 break; 231 } 232 } 233 234 return result; 235 } 236 237}