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; 027 028import org.apache.maven.doxia.macro.AbstractMacro; 029import org.apache.maven.doxia.macro.Macro; 030import org.apache.maven.doxia.macro.MacroExecutionException; 031import org.apache.maven.doxia.macro.MacroRequest; 032import org.apache.maven.doxia.sink.Sink; 033import org.codehaus.plexus.component.annotations.Component; 034 035import com.puppycrawl.tools.checkstyle.api.DetailNode; 036import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes; 037import com.puppycrawl.tools.checkstyle.meta.JavadocMetadataScraper; 038import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 039import com.puppycrawl.tools.checkstyle.utils.JavadocUtil; 040 041/** 042 * A macro that inserts a description of module from its Javadoc. 043 */ 044@Component(role = Macro.class, hint = "description") 045public class DescriptionMacro extends AbstractMacro { 046 047 /** New line escape character. */ 048 private static final String NEWLINE = "\n"; 049 /** A newline with 8 spaces of indentation. */ 050 private static final String INDENT_LEVEL_8 = SiteUtil.getNewlineAndIndentSpaces(8); 051 /** A newline with 10 spaces of indentation. */ 052 private static final String INDENT_LEVEL_10 = SiteUtil.getNewlineAndIndentSpaces(10); 053 /** A set of all html tags that need to be considered as text formatting for this macro. */ 054 private static final Set<String> HTML_TEXT_FORMAT_TAGS = Set.of("<code>", "<a", "</a>", "<b>", 055 "</b>", "<strong>", "</strong>", "<i>", "</i>", "<em>", "</em>", "<small>", "</small>", 056 "<ins>", "<sub>", "<sup>"); 057 058 @Override 059 public void execute(Sink sink, MacroRequest request) throws MacroExecutionException { 060 final Path modulePath = Paths.get((String) request.getParameter("modulePath")); 061 final String moduleName = CommonUtil.getFileNameWithoutExtension(modulePath.toString()); 062 063 final Set<String> propertyNames = getPropertyNames(moduleName); 064 final Map<String, DetailNode> propertiesJavadocs = SiteUtil.getPropertiesJavadocs( 065 propertyNames, moduleName, modulePath); 066 067 final DetailNode moduleJavadoc = propertiesJavadocs.get(moduleName); 068 069 final int descriptionEndIndex = getDescriptionEndIndex(moduleJavadoc, propertyNames); 070 final String moduleDescription = JavadocMetadataScraper.constructSubTreeText( 071 moduleJavadoc, 0, descriptionEndIndex); 072 073 writeOutDescription(moduleDescription, sink); 074 075 } 076 077 /** 078 * Assigns values to each instance variable. 079 * 080 * @param moduleName name of module. 081 * @return set of property names. 082 * @throws MacroExecutionException if the module could not be retrieved. 083 */ 084 private static Set<String> getPropertyNames(String moduleName) 085 throws MacroExecutionException { 086 final Object instance = SiteUtil.getModuleInstance(moduleName); 087 final Class<?> clss = instance.getClass(); 088 089 return SiteUtil.getPropertiesForDocumentation(clss, instance); 090 } 091 092 /** 093 * Gets the end index of the description. 094 * 095 * @param moduleJavadoc javadoc of module. 096 * @param propertyNamesSet Set with property names. 097 * @return the end index. 098 */ 099 private static int getDescriptionEndIndex(DetailNode moduleJavadoc, 100 Set<String> propertyNamesSet) { 101 int descriptionEndIndex = -1; 102 103 if (propertyNamesSet.isEmpty()) { 104 descriptionEndIndex += getParentSectionStartIndex(moduleJavadoc); 105 } 106 else { 107 final String somePropertyName = propertyNamesSet.iterator().next(); 108 109 final Optional<DetailNode> somePropertyModuleNode = 110 SiteUtil.getPropertyJavadocNodeInModule( 111 somePropertyName, moduleJavadoc); 112 113 if (somePropertyModuleNode.isPresent()) { 114 descriptionEndIndex += JavadocMetadataScraper 115 .getParentIndexOf(somePropertyModuleNode.get()); 116 } 117 } 118 119 return descriptionEndIndex; 120 } 121 122 /** 123 * Gets the starting index of the "Parent is" paragraph in module's javadoc. 124 * 125 * @param moduleJavadoc javadoc of module. 126 * @return start index of parent subsection. 127 */ 128 private static int getParentSectionStartIndex(DetailNode moduleJavadoc) { 129 int parentStartIndex = 0; 130 131 for (DetailNode node : moduleJavadoc.getChildren()) { 132 if (node.getType() == JavadocTokenTypes.HTML_ELEMENT) { 133 final DetailNode paragraphNode = JavadocUtil.findFirstToken( 134 node, JavadocTokenTypes.PARAGRAPH); 135 if (paragraphNode != null && JavadocMetadataScraper.isParentText(paragraphNode)) { 136 parentStartIndex = node.getIndex(); 137 break; 138 } 139 } 140 } 141 142 return parentStartIndex; 143 } 144 145 /** 146 * Writes the description into xdoc. 147 * 148 * @param description description of the module. 149 * @param sink sink of the macro. 150 */ 151 private static void writeOutDescription(String description, Sink sink) { 152 final String[] moduleDescriptionLinesSplit = description.split(NEWLINE); 153 154 sink.rawText(moduleDescriptionLinesSplit[0]); 155 String previousProcessedLine = moduleDescriptionLinesSplit[0]; 156 for (int index = 1; index < moduleDescriptionLinesSplit.length; index++) { 157 final String currentLine = moduleDescriptionLinesSplit[index].trim(); 158 final String processedLine; 159 160 if (currentLine.isEmpty()) { 161 processedLine = NEWLINE; 162 } 163 else if (currentLine.startsWith("<") 164 && !startsWithTextFormattingHtmlTag(currentLine)) { 165 166 processedLine = INDENT_LEVEL_8 + currentLine; 167 } 168 else if (index > 1 169 && (previousProcessedLine.contains("<pre") 170 || !previousProcessedLine.startsWith(INDENT_LEVEL_8))) { 171 172 final String currentLineWithPreservedIndent = moduleDescriptionLinesSplit[index] 173 .substring(1); 174 processedLine = NEWLINE + currentLineWithPreservedIndent; 175 } 176 else { 177 processedLine = INDENT_LEVEL_10 + currentLine; 178 } 179 180 sink.rawText(processedLine); 181 182 previousProcessedLine = processedLine; 183 } 184 185 } 186 187 /** 188 * Checks if given line starts with HTML text-formatting tag. 189 * 190 * @param line line to check on. 191 * @return whether given line starts with HTML text-formatting tag. 192 */ 193 private static boolean startsWithTextFormattingHtmlTag(String line) { 194 boolean result = false; 195 196 for (String tag : HTML_TEXT_FORMAT_TAGS) { 197 if (line.startsWith(tag)) { 198 result = true; 199 break; 200 } 201 } 202 203 return result; 204 } 205 206}