001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2023 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.io.File; 023import java.lang.reflect.Field; 024import java.util.Arrays; 025import java.util.List; 026import java.util.Locale; 027import java.util.Map; 028import java.util.Set; 029import java.util.stream.Collectors; 030 031import org.apache.maven.doxia.macro.AbstractMacro; 032import org.apache.maven.doxia.macro.Macro; 033import org.apache.maven.doxia.macro.MacroExecutionException; 034import org.apache.maven.doxia.macro.MacroRequest; 035import org.apache.maven.doxia.module.xdoc.XdocSink; 036import org.apache.maven.doxia.sink.Sink; 037import org.codehaus.plexus.component.annotations.Component; 038 039import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 040import com.puppycrawl.tools.checkstyle.api.DetailNode; 041import com.puppycrawl.tools.checkstyle.checks.javadoc.AbstractJavadocCheck; 042import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 043import com.puppycrawl.tools.checkstyle.utils.JavadocUtil; 044import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 045 046/** 047 * A macro that inserts a table of properties for the given checkstyle module. 048 */ 049@Component(role = Macro.class, hint = "properties") 050public class PropertiesMacro extends AbstractMacro { 051 052 /** A newline with 10 spaces of indentation. */ 053 private static final String INDENT_LEVEL_10 = SiteUtil.getNewlineAndIndentSpaces(10); 054 /** A newline with 12 spaces of indentation. */ 055 private static final String INDENT_LEVEL_12 = SiteUtil.getNewlineAndIndentSpaces(12); 056 /** A newline with 14 spaces of indentation. */ 057 private static final String INDENT_LEVEL_14 = SiteUtil.getNewlineAndIndentSpaces(14); 058 /** A newline with 16 spaces of indentation. */ 059 private static final String INDENT_LEVEL_16 = SiteUtil.getNewlineAndIndentSpaces(16); 060 /** A newline with 18 spaces of indentation. */ 061 private static final String INDENT_LEVEL_18 = SiteUtil.getNewlineAndIndentSpaces(18); 062 /** A newline with 20 spaces of indentation. */ 063 private static final String INDENT_LEVEL_20 = SiteUtil.getNewlineAndIndentSpaces(20); 064 065 /** The name of the current module being processed. */ 066 private static String currentModuleName = ""; 067 068 /** The file of the current module being processed. */ 069 private static File currentModuleFile = new File(""); 070 071 @Override 072 public void execute(Sink sink, MacroRequest request) throws MacroExecutionException { 073 // until https://github.com/checkstyle/checkstyle/issues/13426 074 if (!(sink instanceof XdocSink)) { 075 throw new MacroExecutionException("Expected Sink to be an XdocSink."); 076 } 077 078 final String modulePath = (String) request.getParameter("modulePath"); 079 configureGlobalProperties(modulePath); 080 081 writePropertiesTable((XdocSink) sink); 082 } 083 084 /** 085 * Configures the global properties for the current module. 086 * 087 * @param modulePath the path of the current module processed. 088 */ 089 private static void configureGlobalProperties(String modulePath) { 090 final File moduleFile = new File(modulePath); 091 currentModuleFile = moduleFile; 092 currentModuleName = CommonUtil.getFileNameWithoutExtension(moduleFile.getName()); 093 } 094 095 /** 096 * Writes the properties table for the given module. Expects that the module has been processed 097 * with the ClassAndPropertiesSettersJavadocScraper before calling this method. 098 * 099 * @param sink the sink to write to. 100 * @throws MacroExecutionException if an error occurs during writing. 101 */ 102 private static void writePropertiesTable(XdocSink sink) 103 throws MacroExecutionException { 104 sink.table(); 105 sink.setInsertNewline(false); 106 sink.tableRows(null, false); 107 sink.rawText(INDENT_LEVEL_12); 108 writeTableHeaderRow(sink); 109 writeTablePropertiesRows(sink); 110 sink.rawText(INDENT_LEVEL_10); 111 sink.tableRows_(); 112 sink.table_(); 113 sink.setInsertNewline(true); 114 } 115 116 /** 117 * Writes the table header row with 5 columns - name, description, type, default value, since. 118 * 119 * @param sink sink to write to. 120 */ 121 private static void writeTableHeaderRow(Sink sink) { 122 sink.tableRow(); 123 writeTableHeaderCell(sink, "name"); 124 writeTableHeaderCell(sink, "description"); 125 writeTableHeaderCell(sink, "type"); 126 writeTableHeaderCell(sink, "default value"); 127 writeTableHeaderCell(sink, "since"); 128 sink.rawText(INDENT_LEVEL_12); 129 sink.tableRow_(); 130 } 131 132 /** 133 * Writes a table header cell with the given text. 134 * 135 * @param sink sink to write to. 136 * @param text the text to write. 137 */ 138 private static void writeTableHeaderCell(Sink sink, String text) { 139 sink.rawText(INDENT_LEVEL_14); 140 sink.tableHeaderCell(); 141 sink.text(text); 142 sink.tableHeaderCell_(); 143 } 144 145 /** 146 * Writes the rows of the table with the 5 columns - name, description, type, default value, 147 * since. Each row corresponds to a property of the module. 148 * 149 * @param sink sink to write to. 150 * @throws MacroExecutionException if an error occurs during writing. 151 */ 152 private static void writeTablePropertiesRows(Sink sink) 153 throws MacroExecutionException { 154 final Object instance = SiteUtil.getModuleInstance(currentModuleName); 155 final Class<?> clss = instance.getClass(); 156 157 final Set<String> properties = SiteUtil.getPropertiesForDocumentation(clss, instance); 158 final Map<String, DetailNode> propertiesJavadocs = SiteUtil 159 .getPropertiesJavadocs(properties, currentModuleName, currentModuleFile); 160 161 for (String property : properties) { 162 final DetailNode propertyJavadoc = propertiesJavadocs.get(property); 163 final DetailNode currentModuleJavadoc = propertiesJavadocs.get(currentModuleName); 164 writePropertyRow(sink, property, propertyJavadoc, instance, currentModuleJavadoc); 165 } 166 } 167 168 /** 169 * Writes a table row with 5 columns for the given property - name, description, type, 170 * default value, since. 171 * 172 * @param sink sink to write to. 173 * @param propertyName the name of the property. 174 * @param propertyJavadoc the Javadoc of the property. 175 * @param instance the instance of the module. 176 * @param moduleJavadoc the Javadoc of the module. 177 * @throws MacroExecutionException if an error occurs during writing. 178 */ 179 private static void writePropertyRow(Sink sink, String propertyName, 180 DetailNode propertyJavadoc, Object instance, 181 DetailNode moduleJavadoc) 182 throws MacroExecutionException { 183 final Field field = SiteUtil.getField(instance.getClass(), propertyName); 184 185 sink.rawText(INDENT_LEVEL_12); 186 sink.tableRow(); 187 188 writePropertyNameCell(sink, propertyName); 189 writePropertyDescriptionCell(sink, propertyName, propertyJavadoc); 190 writePropertyTypeCell(sink, propertyName, field, instance); 191 writePropertyDefaultValueCell(sink, propertyName, field, instance); 192 writePropertySinceVersionCell( 193 sink, propertyName, moduleJavadoc, propertyJavadoc); 194 195 sink.rawText(INDENT_LEVEL_12); 196 sink.tableRow_(); 197 } 198 199 /** 200 * Writes a table cell with the given property name. 201 * 202 * @param sink sink to write to. 203 * @param propertyName the name of the property. 204 */ 205 private static void writePropertyNameCell(Sink sink, String propertyName) { 206 sink.rawText(INDENT_LEVEL_14); 207 sink.tableCell(); 208 sink.text(propertyName); 209 sink.tableCell_(); 210 } 211 212 /** 213 * Writes a table cell with the property description. 214 * 215 * @param sink sink to write to. 216 * @param propertyName the name of the property. 217 * @param propertyJavadoc the Javadoc of the property containing the description. 218 * @throws MacroExecutionException if an error occurs during retrieval of the description. 219 */ 220 private static void writePropertyDescriptionCell(Sink sink, String propertyName, 221 DetailNode propertyJavadoc) 222 throws MacroExecutionException { 223 sink.rawText(INDENT_LEVEL_14); 224 sink.tableCell(); 225 final String description = SiteUtil 226 .getPropertyDescription(propertyName, propertyJavadoc, currentModuleName); 227 sink.rawText(description); 228 sink.tableCell_(); 229 } 230 231 /** 232 * Writes a table cell with the property type. 233 * 234 * @param sink sink to write to. 235 * @param propertyName the name of the property. 236 * @param field the field of the property. 237 * @param instance the instance of the module. 238 * @throws MacroExecutionException if link to the property_types.html file cannot be 239 * constructed. 240 */ 241 private static void writePropertyTypeCell(Sink sink, String propertyName, 242 Field field, Object instance) 243 throws MacroExecutionException { 244 sink.rawText(INDENT_LEVEL_14); 245 sink.tableCell(); 246 247 if (SiteUtil.TOKENS.equals(propertyName)) { 248 final AbstractCheck check = (AbstractCheck) instance; 249 if (check.getRequiredTokens().length == 0 250 && Arrays.equals(check.getAcceptableTokens(), TokenUtil.getAllTokenIds())) { 251 sink.text(SiteUtil.TOKEN_TYPES); 252 } 253 else { 254 final List<String> configurableTokens = SiteUtil 255 .getDifference(check.getAcceptableTokens(), 256 check.getRequiredTokens()) 257 .stream() 258 .map(TokenUtil::getTokenName) 259 .collect(Collectors.toList()); 260 sink.text("subset of tokens"); 261 writeTokensList(sink, configurableTokens, SiteUtil.PATH_TO_TOKEN_TYPES); 262 } 263 } 264 else if (SiteUtil.JAVADOC_TOKENS.equals(propertyName)) { 265 final AbstractJavadocCheck check = (AbstractJavadocCheck) instance; 266 final List<String> configurableTokens = SiteUtil 267 .getDifference(check.getAcceptableJavadocTokens(), 268 check.getRequiredJavadocTokens()) 269 .stream() 270 .map(JavadocUtil::getTokenName) 271 .collect(Collectors.toList()); 272 sink.text("subset of javadoc tokens"); 273 writeTokensList(sink, configurableTokens, SiteUtil.PATH_TO_JAVADOC_TOKEN_TYPES); 274 } 275 else { 276 final String type = SiteUtil.getType(field, propertyName, currentModuleName, instance); 277 final String relativePathToPropertyTypes = 278 SiteUtil.getLinkToDocument(currentModuleName, "property_types.xml"); 279 final String escapedType = type 280 .replace("[", ".5B") 281 .replace("]", ".5D"); 282 final String url = 283 String.format(Locale.ROOT, "%s#%s", relativePathToPropertyTypes, escapedType); 284 sink.link(url); 285 sink.text(type); 286 sink.link_(); 287 } 288 sink.tableCell_(); 289 } 290 291 /** 292 * Write a list of tokens with links to the tokenTypesLink file. 293 * 294 * @param sink sink to write to. 295 * @param tokens the list of tokens to write. 296 * @param tokenTypesLink the link to the token types file. 297 * @throws MacroExecutionException if link to the tokenTypesLink file cannot be constructed. 298 */ 299 private static void writeTokensList(Sink sink, List<String> tokens, String tokenTypesLink) 300 throws MacroExecutionException { 301 for (int index = 0; index < tokens.size(); index++) { 302 final String token = tokens.get(index); 303 sink.rawText(INDENT_LEVEL_16); 304 if (index != 0) { 305 sink.text(SiteUtil.COMMA_SPACE); 306 } 307 writeLinkToToken(sink, tokenTypesLink, token); 308 } 309 sink.rawText(INDENT_LEVEL_18); 310 sink.text(SiteUtil.DOT); 311 sink.rawText(INDENT_LEVEL_14); 312 } 313 314 /** 315 * Writes a link to the given token. 316 * 317 * @param sink sink to write to. 318 * @param document the document to link to. 319 * @param tokenName the name of the token. 320 * @throws MacroExecutionException if link to the document file cannot be constructed. 321 */ 322 private static void writeLinkToToken(Sink sink, String document, String tokenName) 323 throws MacroExecutionException { 324 final String link = SiteUtil.getLinkToDocument(currentModuleName, document) 325 + "#" + tokenName; 326 sink.link(link); 327 sink.rawText(INDENT_LEVEL_20); 328 sink.text(tokenName); 329 sink.link_(); 330 } 331 332 /** 333 * Writes a table cell with the property default value. 334 * 335 * @param sink sink to write to. 336 * @param propertyName the name of the property. 337 * @param field the field of the property. 338 * @param instance the instance of the module. 339 * @throws MacroExecutionException if an error occurs during retrieval of the default value. 340 */ 341 private static void writePropertyDefaultValueCell(Sink sink, String propertyName, 342 Field field, Object instance) 343 throws MacroExecutionException { 344 sink.rawText(INDENT_LEVEL_14); 345 sink.tableCell(); 346 347 if (SiteUtil.TOKENS.equals(propertyName)) { 348 final AbstractCheck check = (AbstractCheck) instance; 349 if (check.getRequiredTokens().length == 0 350 && Arrays.equals(check.getDefaultTokens(), TokenUtil.getAllTokenIds())) { 351 sink.text(SiteUtil.TOKEN_TYPES); 352 } 353 else { 354 final List<String> configurableTokens = SiteUtil 355 .getDifference(check.getDefaultTokens(), 356 check.getRequiredTokens()) 357 .stream() 358 .map(TokenUtil::getTokenName) 359 .collect(Collectors.toList()); 360 writeTokensList(sink, configurableTokens, SiteUtil.PATH_TO_TOKEN_TYPES); 361 } 362 } 363 else if (SiteUtil.JAVADOC_TOKENS.equals(propertyName)) { 364 final AbstractJavadocCheck check = (AbstractJavadocCheck) instance; 365 final List<String> configurableTokens = SiteUtil 366 .getDifference(check.getDefaultJavadocTokens(), 367 check.getRequiredJavadocTokens()) 368 .stream() 369 .map(JavadocUtil::getTokenName) 370 .collect(Collectors.toList()); 371 writeTokensList(sink, configurableTokens, SiteUtil.PATH_TO_JAVADOC_TOKEN_TYPES); 372 } 373 else { 374 final String defaultValue = SiteUtil.getDefaultValue( 375 propertyName, field, instance, currentModuleName); 376 sink.rawText("<code>"); 377 sink.text(defaultValue); 378 sink.rawText("</code>"); 379 } 380 381 sink.tableCell_(); 382 } 383 384 /** 385 * Writes a table cell with the property since version. 386 * 387 * @param sink sink to write to. 388 * @param propertyName the name of the property. 389 * @param moduleJavadoc the Javadoc of the module. 390 * @param propertyJavadoc the Javadoc of the property containing the since version. 391 * @throws MacroExecutionException if an error occurs during retrieval of the since version. 392 */ 393 private static void writePropertySinceVersionCell(Sink sink, String propertyName, 394 DetailNode moduleJavadoc, 395 DetailNode propertyJavadoc) 396 throws MacroExecutionException { 397 sink.rawText(INDENT_LEVEL_14); 398 sink.tableCell(); 399 final String sinceVersion = SiteUtil.getSinceVersion( 400 currentModuleName, moduleJavadoc, propertyName, propertyJavadoc); 401 sink.text(sinceVersion); 402 sink.tableCell_(); 403 } 404}