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 }