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