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