1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  package com.puppycrawl.tools.checkstyle.site;
21  
22  import java.lang.reflect.Field;
23  import java.nio.file.Path;
24  import java.util.Arrays;
25  import java.util.LinkedList;
26  import java.util.List;
27  import java.util.Locale;
28  import java.util.Map;
29  import java.util.Set;
30  import java.util.regex.Pattern;
31  
32  import org.apache.maven.doxia.macro.AbstractMacro;
33  import org.apache.maven.doxia.macro.Macro;
34  import org.apache.maven.doxia.macro.MacroExecutionException;
35  import org.apache.maven.doxia.macro.MacroRequest;
36  import org.apache.maven.doxia.module.xdoc.XdocSink;
37  import org.apache.maven.doxia.sink.Sink;
38  import org.codehaus.plexus.component.annotations.Component;
39  
40  import com.puppycrawl.tools.checkstyle.PropertyType;
41  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
42  import com.puppycrawl.tools.checkstyle.api.DetailNode;
43  import com.puppycrawl.tools.checkstyle.checks.javadoc.AbstractJavadocCheck;
44  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
45  import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
46  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
47  
48  
49  
50  
51  @Component(role = Macro.class, hint = "properties")
52  public class PropertiesMacro extends AbstractMacro {
53  
54      
55  
56  
57      public static final String EMPTY = "empty";
58  
59      
60      private static final Pattern COMMA_SPACE_PATTERN = Pattern.compile(", ");
61  
62      
63      private static final String CURLY_BRACKET = "{}";
64  
65      
66      private static final String PROPERTY_TYPES_XML = "property_types.xml";
67  
68      
69      private static final String HASHTAG = "#";
70  
71      
72      private static final String URL_F = "%s#%s";
73  
74      
75      private static final String CODE_START = "<code>";
76  
77      
78      private static final String CODE_END = "</code>";
79  
80      
81  
82  
83  
84      private static final String TOKENS_PROPERTY = SiteUtil.TOKENS;
85  
86      
87      private static String currentModuleName = "";
88  
89      
90      private static Path currentModulePath = Path.of("");
91  
92      @Override
93      public void execute(Sink sink, MacroRequest request) throws MacroExecutionException {
94          
95          if (!(sink instanceof XdocSink)) {
96              throw new MacroExecutionException("Expected Sink to be an XdocSink.");
97          }
98  
99          final String modulePath = (String) request.getParameter("modulePath");
100 
101         configureGlobalProperties(modulePath);
102 
103         writePropertiesTable((XdocSink) sink);
104     }
105 
106     
107 
108 
109 
110 
111 
112     private static void configureGlobalProperties(String modulePath)
113             throws MacroExecutionException {
114         final Path modulePathObj = Path.of(modulePath);
115         currentModulePath = modulePathObj;
116         final Path fileNamePath = modulePathObj.getFileName();
117 
118         if (fileNamePath == null) {
119             throw new MacroExecutionException(
120                 "Invalid modulePath '" + modulePath + "': No file name present.");
121         }
122 
123         currentModuleName = CommonUtil.getFileNameWithoutExtension(
124             fileNamePath.toString());
125     }
126 
127     
128 
129 
130 
131 
132 
133 
134     private static void writePropertiesTable(XdocSink sink)
135             throws MacroExecutionException {
136         sink.table();
137         sink.setInsertNewline(false);
138         sink.tableRows(null, false);
139         sink.rawText(ModuleJavadocParsingUtil.INDENT_LEVEL_12);
140         writeTableHeaderRow(sink);
141         writeTablePropertiesRows(sink);
142         sink.rawText(ModuleJavadocParsingUtil.INDENT_LEVEL_10);
143         sink.tableRows_();
144         sink.table_();
145         sink.setInsertNewline(true);
146     }
147 
148     
149 
150 
151 
152 
153     private static void writeTableHeaderRow(Sink sink) {
154         sink.tableRow();
155         writeTableHeaderCell(sink, "name");
156         writeTableHeaderCell(sink, "description");
157         writeTableHeaderCell(sink, "type");
158         writeTableHeaderCell(sink, "default value");
159         writeTableHeaderCell(sink, "since");
160         sink.rawText(ModuleJavadocParsingUtil.INDENT_LEVEL_12);
161         sink.tableRow_();
162     }
163 
164     
165 
166 
167 
168 
169 
170     private static void writeTableHeaderCell(Sink sink, String text) {
171         sink.rawText(ModuleJavadocParsingUtil.INDENT_LEVEL_14);
172         sink.tableHeaderCell();
173         sink.text(text);
174         sink.tableHeaderCell_();
175     }
176 
177     
178 
179 
180 
181 
182 
183 
184     private static void writeTablePropertiesRows(Sink sink)
185             throws MacroExecutionException {
186         final Object instance = SiteUtil.getModuleInstance(currentModuleName);
187         final Class<?> clss = instance.getClass();
188 
189         final Set<String> properties = SiteUtil.getPropertiesForDocumentation(clss, instance);
190         final Map<String, DetailNode> propertiesJavadocs = SiteUtil
191                 .getPropertiesJavadocs(properties, currentModuleName, currentModulePath);
192 
193         final List<String> orderedProperties = orderProperties(properties);
194 
195         final DetailNode currentModuleJavadoc = SiteUtil.getModuleJavadoc(
196             currentModuleName, currentModulePath);
197 
198         for (String property : orderedProperties) {
199             try {
200                 final DetailNode propertyJavadoc = propertiesJavadocs.get(property);
201                 writePropertyRow(sink, property, propertyJavadoc, instance, currentModuleJavadoc);
202             }
203             
204             catch (Exception exc) {
205                 final String message = String.format(Locale.ROOT,
206                         "Exception while handling moduleName: %s propertyName: %s",
207                         currentModuleName, property);
208                 throw new MacroExecutionException(message, exc);
209             }
210         }
211     }
212 
213     
214 
215 
216 
217 
218 
219 
220     private static List<String> orderProperties(Set<String> properties) {
221 
222         final List<String> orderProperties = new LinkedList<>(properties);
223 
224         if (orderProperties.remove(TOKENS_PROPERTY)) {
225             orderProperties.add(TOKENS_PROPERTY);
226         }
227         if (orderProperties.remove(SiteUtil.JAVADOC_TOKENS)) {
228             orderProperties.add(SiteUtil.JAVADOC_TOKENS);
229         }
230         return List.copyOf(orderProperties);
231 
232     }
233 
234     
235 
236 
237 
238 
239 
240 
241 
242 
243 
244 
245     private static void writePropertyRow(Sink sink, String propertyName,
246                                          DetailNode propertyJavadoc, Object instance,
247                                             DetailNode moduleJavadoc)
248             throws MacroExecutionException {
249         final Field field = SiteUtil.getField(instance.getClass(), propertyName);
250 
251         sink.rawText(ModuleJavadocParsingUtil.INDENT_LEVEL_12);
252         sink.tableRow();
253 
254         writePropertyNameCell(sink, propertyName);
255         writePropertyDescriptionCell(sink, propertyName, propertyJavadoc);
256         writePropertyTypeCell(sink, propertyName, field, instance);
257         writePropertyDefaultValueCell(sink, propertyName, field, instance);
258         writePropertySinceVersionCell(
259                 sink, moduleJavadoc, propertyJavadoc);
260 
261         sink.rawText(ModuleJavadocParsingUtil.INDENT_LEVEL_12);
262         sink.tableRow_();
263     }
264 
265     
266 
267 
268 
269 
270 
271     private static void writePropertyNameCell(Sink sink, String propertyName) {
272         sink.rawText(ModuleJavadocParsingUtil.INDENT_LEVEL_14);
273         sink.tableCell();
274         sink.rawText("<a id=\"" + propertyName + "\"/>");
275         sink.link(HASHTAG + propertyName);
276         sink.text(propertyName);
277         sink.link_();
278         sink.tableCell_();
279     }
280 
281     
282 
283 
284 
285 
286 
287 
288 
289     private static void writePropertyDescriptionCell(Sink sink, String propertyName,
290                                                      DetailNode propertyJavadoc)
291             throws MacroExecutionException {
292         sink.rawText(ModuleJavadocParsingUtil.INDENT_LEVEL_14);
293         sink.tableCell();
294         final String description = SiteUtil
295                 .getPropertyDescriptionForXdoc(propertyName, propertyJavadoc, currentModuleName);
296 
297         sink.rawText(description);
298         sink.tableCell_();
299     }
300 
301     
302 
303 
304 
305 
306 
307 
308 
309 
310 
311     private static void writePropertyTypeCell(Sink sink, String propertyName,
312                                               Field field, Object instance)
313             throws MacroExecutionException {
314         sink.rawText(ModuleJavadocParsingUtil.INDENT_LEVEL_14);
315         sink.tableCell();
316 
317         if (SiteUtil.TOKENS.equals(propertyName)) {
318             final AbstractCheck check = (AbstractCheck) instance;
319             if (check.getRequiredTokens().length == 0
320                     && Arrays.equals(check.getAcceptableTokens(), TokenUtil.getAllTokenIds())) {
321                 sink.text("set of any supported");
322                 writeLink(sink);
323             }
324             else {
325                 final List<String> configurableTokens = SiteUtil
326                         .getDifference(check.getAcceptableTokens(),
327                                 check.getRequiredTokens())
328                         .stream()
329                         .map(TokenUtil::getTokenName)
330                         .toList();
331                 sink.text("subset of tokens");
332 
333                 writeTokensList(sink, configurableTokens, SiteUtil.PATH_TO_TOKEN_TYPES, true);
334             }
335         }
336         else if (SiteUtil.JAVADOC_TOKENS.equals(propertyName)) {
337             final AbstractJavadocCheck check = (AbstractJavadocCheck) instance;
338             final List<String> configurableTokens = SiteUtil
339                     .getDifference(check.getAcceptableJavadocTokens(),
340                             check.getRequiredJavadocTokens())
341                     .stream()
342                     .map(JavadocUtil::getTokenName)
343                     .toList();
344             sink.text("subset of javadoc tokens");
345             writeTokensList(sink, configurableTokens, SiteUtil.PATH_TO_JAVADOC_TOKEN_TYPES, true);
346         }
347         else {
348             final String type;
349 
350             if (ModuleJavadocParsingUtil.isPropertySpecialTokenProp(field)) {
351                 type = "subset of tokens TokenTypes";
352             }
353             else {
354                 final String fullTypeName =
355                     SiteUtil.getType(field, propertyName, currentModuleName, instance);
356                 type = SiteUtil.simplifyTypeName(fullTypeName);
357             }
358 
359             if (PropertyType.TOKEN_ARRAY.getDescription().equals(type)) {
360                 processLinkForTokenTypes(sink);
361             }
362             else {
363                 final String relativePathToPropertyTypes =
364                         SiteUtil.getLinkToDocument(currentModuleName, PROPERTY_TYPES_XML);
365                 final String escapedType = type
366                         .replace("[", ".5B")
367                         .replace("]", ".5D");
368 
369                 final String url =
370                         String.format(Locale.ROOT, URL_F, relativePathToPropertyTypes, escapedType);
371 
372                 sink.link(url);
373                 sink.text(type);
374                 sink.link_();
375             }
376         }
377         sink.tableCell_();
378     }
379 
380     
381 
382 
383 
384 
385 
386     private static void processLinkForTokenTypes(Sink sink)
387             throws MacroExecutionException {
388         final String link =
389                 SiteUtil.getLinkToDocument(currentModuleName, SiteUtil.PATH_TO_TOKEN_TYPES);
390 
391         sink.text("subset of tokens ");
392         sink.link(link);
393         sink.text("TokenTypes");
394         sink.link_();
395     }
396 
397     
398 
399 
400 
401 
402 
403     private static void writeLink(Sink sink)
404             throws MacroExecutionException {
405         sink.rawText(ModuleJavadocParsingUtil.INDENT_LEVEL_16);
406         final String link =
407                 SiteUtil.getLinkToDocument(currentModuleName, SiteUtil.PATH_TO_TOKEN_TYPES);
408         sink.link(link);
409         sink.rawText(ModuleJavadocParsingUtil.INDENT_LEVEL_20);
410         sink.text(SiteUtil.TOKENS);
411         sink.link_();
412         sink.rawText(ModuleJavadocParsingUtil.INDENT_LEVEL_14);
413     }
414 
415     
416 
417 
418 
419 
420 
421 
422 
423 
424     private static void writeTokensList(Sink sink, List<String> tokens, String tokenTypesLink,
425                                         boolean printDotAtTheEnd)
426             throws MacroExecutionException {
427         for (int index = 0; index < tokens.size(); index++) {
428             final String token = tokens.get(index);
429             sink.rawText(ModuleJavadocParsingUtil.INDENT_LEVEL_16);
430             if (index != 0) {
431                 sink.text(SiteUtil.COMMA_SPACE);
432             }
433             writeLinkToToken(sink, tokenTypesLink, token);
434         }
435         if (tokens.isEmpty()) {
436             sink.rawText(CODE_START);
437             sink.text(EMPTY);
438             sink.rawText(CODE_END);
439         }
440         else if (printDotAtTheEnd) {
441             sink.rawText(ModuleJavadocParsingUtil.INDENT_LEVEL_18);
442             sink.text(SiteUtil.DOT);
443             sink.rawText(ModuleJavadocParsingUtil.INDENT_LEVEL_14);
444         }
445         else {
446             sink.rawText(ModuleJavadocParsingUtil.INDENT_LEVEL_14);
447         }
448     }
449 
450     
451 
452 
453 
454 
455 
456 
457 
458     private static void writeLinkToToken(Sink sink, String document, String tokenName)
459             throws MacroExecutionException {
460         final String link = SiteUtil.getLinkToDocument(currentModuleName, document)
461                         + HASHTAG + tokenName;
462         sink.link(link);
463         sink.rawText(ModuleJavadocParsingUtil.INDENT_LEVEL_20);
464         sink.text(tokenName);
465         sink.link_();
466     }
467 
468     
469 
470 
471 
472 
473 
474 
475 
476 
477     private static void writePropertyDefaultValueCell(Sink sink, String propertyName,
478                                                       Field field, Object instance)
479             throws MacroExecutionException {
480         sink.rawText(ModuleJavadocParsingUtil.INDENT_LEVEL_14);
481         sink.tableCell();
482 
483         if (SiteUtil.TOKENS.equals(propertyName)) {
484             final AbstractCheck check = (AbstractCheck) instance;
485             if (check.getRequiredTokens().length == 0
486                     && Arrays.equals(check.getDefaultTokens(), TokenUtil.getAllTokenIds())) {
487                 sink.text(SiteUtil.TOKEN_TYPES);
488             }
489             else {
490                 final List<String> configurableTokens = SiteUtil
491                         .getDifference(check.getDefaultTokens(),
492                                 check.getRequiredTokens())
493                         .stream()
494                         .map(TokenUtil::getTokenName)
495                         .toList();
496                 writeTokensList(sink, configurableTokens, SiteUtil.PATH_TO_TOKEN_TYPES, true);
497             }
498         }
499         else if (SiteUtil.JAVADOC_TOKENS.equals(propertyName)) {
500             final AbstractJavadocCheck check = (AbstractJavadocCheck) instance;
501             final List<String> configurableTokens = SiteUtil
502                     .getDifference(check.getDefaultJavadocTokens(),
503                             check.getRequiredJavadocTokens())
504                     .stream()
505                     .map(JavadocUtil::getTokenName)
506                     .toList();
507             writeTokensList(sink, configurableTokens, SiteUtil.PATH_TO_JAVADOC_TOKEN_TYPES, true);
508         }
509         else {
510             final String defaultValue = getDefaultValue(propertyName, field, instance);
511 
512             if (ModuleJavadocParsingUtil.isPropertySpecialTokenProp(field)
513                 && !CURLY_BRACKET.equals(defaultValue)) {
514 
515                 final List<String> defaultValuesList =
516                         Arrays.asList(COMMA_SPACE_PATTERN.split(defaultValue));
517                 writeTokensList(sink, defaultValuesList, SiteUtil.PATH_TO_TOKEN_TYPES, false);
518             }
519             else {
520                 sink.rawText(CODE_START);
521                 sink.text(defaultValue);
522                 sink.rawText(CODE_END);
523             }
524         }
525 
526         sink.tableCell_();
527     }
528 
529     
530 
531 
532 
533 
534 
535 
536 
537 
538     private static String getDefaultValue(String propertyName, Field field, Object instance)
539             throws MacroExecutionException {
540         String result;
541 
542         if (field != null) {
543             result = SiteUtil.getDefaultValue(
544                     propertyName, field, instance, currentModuleName);
545         }
546         else {
547             final Class<?> fieldClass = SiteUtil.getPropertyClass(propertyName, instance);
548 
549             if (fieldClass.isArray()) {
550                 result = CURLY_BRACKET;
551             }
552             else {
553                 result = "null";
554             }
555         }
556 
557         final Class<?> fieldClass =
558             SiteUtil.getFieldClass(field, propertyName, currentModuleName, instance);
559         if (result.isEmpty() && fieldClass.isArray()) {
560             result = CURLY_BRACKET;
561 
562             if (fieldClass == String[].class && SiteUtil.FILE_EXTENSIONS.equals(propertyName)) {
563                 result = "all files";
564             }
565         }
566         else if (SiteUtil.CHARSET.equals(propertyName)) {
567             result = "the charset property of the parent"
568                 + " <a href=\"https://checkstyle.org/config.html#Checker\">"
569                 + "Checker</a> module";
570         }
571 
572         return result;
573     }
574 
575     
576 
577 
578 
579 
580 
581 
582 
583     private static void writePropertySinceVersionCell(Sink sink, DetailNode moduleJavadoc,
584                                                       DetailNode propertyJavadoc)
585             throws MacroExecutionException {
586         sink.rawText(ModuleJavadocParsingUtil.INDENT_LEVEL_14);
587         sink.tableCell();
588         final String sinceVersion = SiteUtil.getPropertySinceVersion(
589                 currentModuleName, moduleJavadoc, propertyJavadoc);
590         sink.text(sinceVersion);
591         sink.tableCell_();
592     }
593 }