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.beans.PropertyDescriptor;
23 import java.io.File;
24 import java.io.IOException;
25 import java.lang.reflect.Array;
26 import java.lang.reflect.Field;
27 import java.lang.reflect.InvocationTargetException;
28 import java.lang.reflect.ParameterizedType;
29 import java.net.URI;
30 import java.nio.charset.StandardCharsets;
31 import java.nio.file.Files;
32 import java.nio.file.Path;
33 import java.util.ArrayDeque;
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.BitSet;
37 import java.util.Collection;
38 import java.util.Deque;
39 import java.util.HashMap;
40 import java.util.HashSet;
41 import java.util.LinkedHashMap;
42 import java.util.List;
43 import java.util.Locale;
44 import java.util.Map;
45 import java.util.Optional;
46 import java.util.Set;
47 import java.util.TreeSet;
48 import java.util.regex.Pattern;
49 import java.util.stream.Collectors;
50 import java.util.stream.IntStream;
51 import java.util.stream.Stream;
52
53 import javax.annotation.Nullable;
54
55 import org.apache.commons.beanutils.PropertyUtils;
56 import org.apache.maven.doxia.macro.MacroExecutionException;
57
58 import com.google.common.collect.Lists;
59 import com.puppycrawl.tools.checkstyle.Checker;
60 import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
61 import com.puppycrawl.tools.checkstyle.ModuleFactory;
62 import com.puppycrawl.tools.checkstyle.PackageNamesLoader;
63 import com.puppycrawl.tools.checkstyle.PackageObjectFactory;
64 import com.puppycrawl.tools.checkstyle.PropertyCacheFile;
65 import com.puppycrawl.tools.checkstyle.TreeWalker;
66 import com.puppycrawl.tools.checkstyle.TreeWalkerFilter;
67 import com.puppycrawl.tools.checkstyle.XdocsPropertyType;
68 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
69 import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck;
70 import com.puppycrawl.tools.checkstyle.api.BeforeExecutionFileFilter;
71 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
72 import com.puppycrawl.tools.checkstyle.api.DetailNode;
73 import com.puppycrawl.tools.checkstyle.api.Filter;
74 import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes;
75 import com.puppycrawl.tools.checkstyle.checks.javadoc.AbstractJavadocCheck;
76 import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption;
77 import com.puppycrawl.tools.checkstyle.checks.regexp.RegexpMultilineCheck;
78 import com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineCheck;
79 import com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineJavaCheck;
80 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
81 import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
82 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
83
84
85
86
87 public final class SiteUtil {
88
89
90 public static final String TOKENS = "tokens";
91
92 public static final String JAVADOC_TOKENS = "javadocTokens";
93
94 public static final String DOT = ".";
95
96 public static final String COMMA_SPACE = ", ";
97
98 public static final String TOKEN_TYPES = "TokenTypes";
99
100 public static final String PATH_TO_TOKEN_TYPES =
101 "apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html";
102
103 public static final String PATH_TO_JAVADOC_TOKEN_TYPES =
104 "apidocs/com/puppycrawl/tools/checkstyle/api/JavadocTokenTypes.html";
105
106 private static final String CHECKSTYLE_ORG_URL = "https://checkstyle.org/";
107
108 private static final String CHARSET = "charset";
109
110 private static final String CURLY_BRACKETS = "{}";
111
112 private static final String FILE_EXTENSIONS = "fileExtensions";
113
114 private static final String CHECKS = "checks";
115
116 private static final String NAMING = "naming";
117
118 private static final String SRC = "src";
119
120
121 private static final Pattern SETTER_PATTERN = Pattern.compile("^Setter to ");
122
123
124 private static final Map<Class<?>, String> CLASS_TO_PARENT_MODULE = Map.ofEntries(
125 Map.entry(AbstractCheck.class, TreeWalker.class.getSimpleName()),
126 Map.entry(TreeWalkerFilter.class, TreeWalker.class.getSimpleName()),
127 Map.entry(AbstractFileSetCheck.class, Checker.class.getSimpleName()),
128 Map.entry(Filter.class, Checker.class.getSimpleName()),
129 Map.entry(BeforeExecutionFileFilter.class, Checker.class.getSimpleName())
130 );
131
132
133 private static final Set<String> CHECK_PROPERTIES =
134 getProperties(AbstractCheck.class);
135
136
137 private static final Set<String> JAVADOC_CHECK_PROPERTIES =
138 getProperties(AbstractJavadocCheck.class);
139
140
141 private static final Set<String> FILESET_PROPERTIES =
142 getProperties(AbstractFileSetCheck.class);
143
144
145
146
147 private static final String HEADER_CHECK_HEADER = "HeaderCheck.header";
148
149
150
151
152 private static final String REGEXP_HEADER_CHECK_HEADER = "RegexpHeaderCheck.header";
153
154
155
156
157 private static final String MULTI_FILE_REGEXP_HEADER_CHECK_HEADER =
158 "MultiFileRegexpHeaderCheck.header";
159
160
161 private static final Set<String> UNDOCUMENTED_PROPERTIES = Set.of(
162 "SuppressWithNearbyCommentFilter.fileContents",
163 "SuppressionCommentFilter.fileContents"
164 );
165
166
167 private static final Set<String> PROPERTIES_ALLOWED_GET_TYPES_FROM_METHOD = Set.of(
168
169 "SuppressWarningsHolder.aliasList",
170
171 HEADER_CHECK_HEADER,
172 REGEXP_HEADER_CHECK_HEADER,
173
174 "RedundantModifierCheck.jdkVersion",
175
176 "CustomImportOrderCheck.customImportOrderRules"
177 );
178
179
180
181
182 private static final String VERSION_6_9 = "6.9";
183
184
185
186
187 private static final String VERSION_5_0 = "5.0";
188
189
190
191
192 private static final String VERSION_3_2 = "3.2";
193
194
195
196
197 private static final String VERSION_8_24 = "8.24";
198
199
200
201
202 private static final String VERSION_8_36 = "8.36";
203
204
205
206
207 private static final String VERSION_10_24 = "10.24";
208
209
210
211
212 private static final String VERSION_3_0 = "3.0";
213
214
215
216
217 private static final String VERSION_7_7 = "7.7";
218
219
220
221
222 private static final String VERSION_5_7 = "5.7";
223
224
225
226
227 private static final String VERSION_5_1 = "5.1";
228
229
230
231
232 private static final String VERSION_3_4 = "3.4";
233
234
235
236
237
238
239
240
241
242 private static final Map<String, String> SINCE_VERSION_FOR_INHERITED_PROPERTY = Map.ofEntries(
243 Map.entry("MissingDeprecatedCheck.violateExecutionOnNonTightHtml", VERSION_8_24),
244 Map.entry("NonEmptyAtclauseDescriptionCheck.violateExecutionOnNonTightHtml", "8.3"),
245 Map.entry("HeaderCheck.charset", VERSION_5_0),
246 Map.entry("HeaderCheck.fileExtensions", VERSION_6_9),
247 Map.entry("HeaderCheck.headerFile", VERSION_3_2),
248 Map.entry(HEADER_CHECK_HEADER, VERSION_5_0),
249 Map.entry("RegexpHeaderCheck.charset", VERSION_5_0),
250 Map.entry("RegexpHeaderCheck.fileExtensions", VERSION_6_9),
251 Map.entry("RegexpHeaderCheck.headerFile", VERSION_3_2),
252 Map.entry(REGEXP_HEADER_CHECK_HEADER, VERSION_5_0),
253 Map.entry("MultiFileRegexpHeaderCheck.fileExtensions", VERSION_10_24),
254 Map.entry("MultiFileRegexpHeaderCheck.headerFiles", VERSION_10_24),
255 Map.entry(MULTI_FILE_REGEXP_HEADER_CHECK_HEADER, VERSION_10_24),
256 Map.entry("ClassDataAbstractionCouplingCheck.excludeClassesRegexps", VERSION_7_7),
257 Map.entry("ClassDataAbstractionCouplingCheck.excludedClasses", VERSION_5_7),
258 Map.entry("ClassDataAbstractionCouplingCheck.excludedPackages", VERSION_7_7),
259 Map.entry("ClassDataAbstractionCouplingCheck.max", VERSION_3_4),
260 Map.entry("ClassFanOutComplexityCheck.excludeClassesRegexps", VERSION_7_7),
261 Map.entry("ClassFanOutComplexityCheck.excludedClasses", VERSION_5_7),
262 Map.entry("ClassFanOutComplexityCheck.excludedPackages", VERSION_7_7),
263 Map.entry("ClassFanOutComplexityCheck.max", VERSION_3_4),
264 Map.entry("NonEmptyAtclauseDescriptionCheck.javadocTokens", "7.3"),
265 Map.entry("FileTabCharacterCheck.fileExtensions", VERSION_5_0),
266 Map.entry("NewlineAtEndOfFileCheck.fileExtensions", "3.1"),
267 Map.entry("JavadocPackageCheck.fileExtensions", VERSION_5_0),
268 Map.entry("OrderedPropertiesCheck.fileExtensions", "8.22"),
269 Map.entry("UniquePropertiesCheck.fileExtensions", VERSION_5_7),
270 Map.entry("TranslationCheck.fileExtensions", VERSION_3_0),
271 Map.entry("LineLengthCheck.fileExtensions", VERSION_8_24),
272
273 Map.entry("JavadocBlockTagLocationCheck.violateExecutionOnNonTightHtml", VERSION_8_24),
274 Map.entry("JavadocLeadingAsteriskAlignCheck.violateExecutionOnNonTightHtml", "10.18"),
275 Map.entry("JavadocMissingLeadingAsteriskCheck.violateExecutionOnNonTightHtml", "8.38"),
276 Map.entry(
277 "RequireEmptyLineBeforeBlockTagGroupCheck.violateExecutionOnNonTightHtml",
278 VERSION_8_36),
279 Map.entry("ParenPadCheck.option", VERSION_3_0),
280 Map.entry("TypecastParenPadCheck.option", VERSION_3_2),
281 Map.entry("FileLengthCheck.fileExtensions", VERSION_5_0),
282 Map.entry("StaticVariableNameCheck.applyToPackage", VERSION_5_0),
283 Map.entry("StaticVariableNameCheck.applyToPrivate", VERSION_5_0),
284 Map.entry("StaticVariableNameCheck.applyToProtected", VERSION_5_0),
285 Map.entry("StaticVariableNameCheck.applyToPublic", VERSION_5_0),
286 Map.entry("StaticVariableNameCheck.format", VERSION_3_0),
287 Map.entry("TypeNameCheck.applyToPackage", VERSION_5_0),
288 Map.entry("TypeNameCheck.applyToPrivate", VERSION_5_0),
289 Map.entry("TypeNameCheck.applyToProtected", VERSION_5_0),
290 Map.entry("TypeNameCheck.applyToPublic", VERSION_5_0),
291 Map.entry("RegexpMultilineCheck.fileExtensions", VERSION_5_0),
292 Map.entry("RegexpOnFilenameCheck.fileExtensions", "6.15"),
293 Map.entry("RegexpSinglelineCheck.fileExtensions", VERSION_5_0),
294 Map.entry("ClassTypeParameterNameCheck.format", VERSION_5_0),
295 Map.entry("CatchParameterNameCheck.format", "6.14"),
296 Map.entry("LambdaParameterNameCheck.format", "8.11"),
297 Map.entry("IllegalIdentifierNameCheck.format", VERSION_8_36),
298 Map.entry("ConstantNameCheck.format", VERSION_3_0),
299 Map.entry("ConstantNameCheck.applyToPackage", VERSION_5_0),
300 Map.entry("ConstantNameCheck.applyToPrivate", VERSION_5_0),
301 Map.entry("ConstantNameCheck.applyToProtected", VERSION_5_0),
302 Map.entry("ConstantNameCheck.applyToPublic", VERSION_5_0),
303 Map.entry("InterfaceTypeParameterNameCheck.format", "5.8"),
304 Map.entry("LocalFinalVariableNameCheck.format", VERSION_3_0),
305 Map.entry("LocalVariableNameCheck.format", VERSION_3_0),
306 Map.entry("MemberNameCheck.format", VERSION_3_0),
307 Map.entry("MemberNameCheck.applyToPackage", VERSION_3_4),
308 Map.entry("MemberNameCheck.applyToPrivate", VERSION_3_4),
309 Map.entry("MemberNameCheck.applyToProtected", VERSION_3_4),
310 Map.entry("MemberNameCheck.applyToPublic", VERSION_3_4),
311 Map.entry("MethodNameCheck.format", VERSION_3_0),
312 Map.entry("MethodNameCheck.applyToPackage", VERSION_5_1),
313 Map.entry("MethodNameCheck.applyToPrivate", VERSION_5_1),
314 Map.entry("MethodNameCheck.applyToProtected", VERSION_5_1),
315 Map.entry("MethodNameCheck.applyToPublic", VERSION_5_1),
316 Map.entry("MethodTypeParameterNameCheck.format", VERSION_5_0),
317 Map.entry("ParameterNameCheck.format", VERSION_3_0),
318 Map.entry("PatternVariableNameCheck.format", VERSION_8_36),
319 Map.entry("RecordTypeParameterNameCheck.format", VERSION_8_36),
320 Map.entry("RecordComponentNameCheck.format", "8.40"),
321 Map.entry("TypeNameCheck.format", VERSION_3_0)
322 );
323
324
325 private static final Map<String, DetailNode> SUPER_CLASS_PROPERTIES_JAVADOCS =
326 new HashMap<>();
327
328
329 private static final String MAIN_FOLDER_PATH = Path.of(
330 SRC, "main", "java", "com", "puppycrawl", "tools", "checkstyle").toString();
331
332
333 private static final List<Path> MODULE_SUPER_CLASS_PATHS = List.of(
334 Path.of(MAIN_FOLDER_PATH, CHECKS, NAMING, "AbstractAccessControlNameCheck.java"),
335 Path.of(MAIN_FOLDER_PATH, CHECKS, NAMING, "AbstractNameCheck.java"),
336 Path.of(MAIN_FOLDER_PATH, CHECKS, "javadoc", "AbstractJavadocCheck.java"),
337 Path.of(MAIN_FOLDER_PATH, "api", "AbstractFileSetCheck.java"),
338 Path.of(MAIN_FOLDER_PATH, CHECKS, "header", "AbstractHeaderCheck.java"),
339 Path.of(MAIN_FOLDER_PATH, CHECKS, "metrics", "AbstractClassCouplingCheck.java"),
340 Path.of(MAIN_FOLDER_PATH, CHECKS, "whitespace", "AbstractParenPadCheck.java")
341 );
342
343
344
345
346 private SiteUtil() {
347 }
348
349
350
351
352
353
354
355
356 public static Set<String> getMessageKeys(Class<?> module)
357 throws MacroExecutionException {
358 final Set<Field> messageKeyFields = getCheckMessageKeys(module);
359
360 final Set<String> messageKeys = new TreeSet<>();
361 for (Field field : messageKeyFields) {
362 messageKeys.add(getFieldValue(field, module).toString());
363 }
364 return messageKeys;
365 }
366
367
368
369
370
371
372
373
374
375
376
377
378 private static Set<Field> getCheckMessageKeys(Class<?> module)
379 throws MacroExecutionException {
380 try {
381 final Set<Field> checkstyleMessages = new HashSet<>();
382
383
384 final Field[] fields = module.getDeclaredFields();
385
386 for (Field field : fields) {
387 if (field.getName().startsWith("MSG_")) {
388 checkstyleMessages.add(field);
389 }
390 }
391
392
393 final Class<?> superModule = module.getSuperclass();
394
395 if (superModule != null) {
396 checkstyleMessages.addAll(getCheckMessageKeys(superModule));
397 }
398
399
400 if (module == RegexpMultilineCheck.class) {
401 checkstyleMessages.addAll(getCheckMessageKeys(Class
402 .forName("com.puppycrawl.tools.checkstyle.checks.regexp.MultilineDetector")));
403 }
404 else if (module == RegexpSinglelineCheck.class
405 || module == RegexpSinglelineJavaCheck.class) {
406 checkstyleMessages.addAll(getCheckMessageKeys(Class
407 .forName("com.puppycrawl.tools.checkstyle.checks.regexp.SinglelineDetector")));
408 }
409
410 return checkstyleMessages;
411 }
412 catch (ClassNotFoundException exc) {
413 final String message = String.format(Locale.ROOT, "Couldn't find class: %s",
414 module.getName());
415 throw new MacroExecutionException(message, exc);
416 }
417 }
418
419
420
421
422
423
424
425
426
427 public static Object getFieldValue(Field field, Object instance)
428 throws MacroExecutionException {
429 try {
430
431 field.trySetAccessible();
432 return field.get(instance);
433 }
434 catch (IllegalAccessException exc) {
435 throw new MacroExecutionException("Couldn't get field value", exc);
436 }
437 }
438
439
440
441
442
443
444
445
446 public static Object getModuleInstance(String moduleName) throws MacroExecutionException {
447 final ModuleFactory factory = getPackageObjectFactory();
448 try {
449 return factory.createModule(moduleName);
450 }
451 catch (CheckstyleException exc) {
452 throw new MacroExecutionException("Couldn't find class: " + moduleName, exc);
453 }
454 }
455
456
457
458
459
460
461
462 private static PackageObjectFactory getPackageObjectFactory() throws MacroExecutionException {
463 try {
464 final ClassLoader cl = ViolationMessagesMacro.class.getClassLoader();
465 final Set<String> packageNames = PackageNamesLoader.getPackageNames(cl);
466 return new PackageObjectFactory(packageNames, cl);
467 }
468 catch (CheckstyleException exc) {
469 throw new MacroExecutionException("Couldn't load checkstyle modules", exc);
470 }
471 }
472
473
474
475
476
477
478
479
480
481
482
483 public static String getNewlineAndIndentSpaces(int amountOfSpaces) {
484 return System.lineSeparator() + " ".repeat(amountOfSpaces);
485 }
486
487
488
489
490
491
492
493
494
495 public static Path getTemplatePath(String moduleName) throws MacroExecutionException {
496 final String fileNamePattern = ".*[\\\\/]"
497 + moduleName.toLowerCase(Locale.ROOT) + "\\..*";
498 return getXdocsTemplatesFilePaths()
499 .stream()
500 .filter(path -> path.toString().matches(fileNamePattern))
501 .findFirst()
502 .orElse(null);
503 }
504
505
506
507
508
509
510
511
512
513 public static Set<Path> getXdocsTemplatesFilePaths() throws MacroExecutionException {
514 final Path directory = Path.of("src/site/xdoc");
515 try (Stream<Path> stream = Files.find(directory, Integer.MAX_VALUE,
516 (path, attr) -> {
517 return attr.isRegularFile()
518 && path.toString().endsWith(".xml.template");
519 })) {
520 return stream.collect(Collectors.toUnmodifiableSet());
521 }
522 catch (IOException ioException) {
523 throw new MacroExecutionException("Failed to find xdocs templates", ioException);
524 }
525 }
526
527
528
529
530
531
532
533
534
535 public static String getParentModule(Class<?> moduleClass)
536 throws MacroExecutionException {
537 String parentModuleName = "";
538 Class<?> parentClass = moduleClass.getSuperclass();
539
540 while (parentClass != null) {
541 parentModuleName = CLASS_TO_PARENT_MODULE.get(parentClass);
542 if (parentModuleName != null) {
543 break;
544 }
545 parentClass = parentClass.getSuperclass();
546 }
547
548
549 if (parentModuleName == null || parentModuleName.isEmpty()) {
550 final Class<?>[] interfaces = moduleClass.getInterfaces();
551 for (Class<?> interfaceClass : interfaces) {
552 parentModuleName = CLASS_TO_PARENT_MODULE.get(interfaceClass);
553 if (parentModuleName != null) {
554 break;
555 }
556 }
557 }
558
559 if (parentModuleName == null || parentModuleName.isEmpty()) {
560 final String message = String.format(Locale.ROOT,
561 "Failed to find parent module for %s", moduleClass.getSimpleName());
562 throw new MacroExecutionException(message);
563 }
564
565 return parentModuleName;
566 }
567
568
569
570
571
572
573
574
575 public static Set<String> getPropertiesForDocumentation(Class<?> clss, Object instance) {
576 final Set<String> properties =
577 getProperties(clss).stream()
578 .filter(prop -> {
579 return !isGlobalProperty(clss, prop) && !isUndocumentedProperty(clss, prop);
580 })
581 .collect(Collectors.toCollection(HashSet::new));
582 properties.addAll(getNonExplicitProperties(instance, clss));
583 return new TreeSet<>(properties);
584 }
585
586
587
588
589
590
591
592
593
594
595
596 public static Map<String, DetailNode> getPropertiesJavadocs(Set<String> properties,
597 String moduleName, Path modulePath)
598 throws MacroExecutionException {
599
600 if (SUPER_CLASS_PROPERTIES_JAVADOCS.isEmpty()) {
601 processSuperclasses();
602 }
603
604 processModule(moduleName, modulePath);
605
606 final Map<String, DetailNode> unmodifiableJavadocs =
607 ClassAndPropertiesSettersJavadocScraper.getJavadocsForModuleOrProperty();
608 final Map<String, DetailNode> javadocs = new LinkedHashMap<>(unmodifiableJavadocs);
609
610 properties.forEach(property -> {
611 final DetailNode superClassPropertyJavadoc =
612 SUPER_CLASS_PROPERTIES_JAVADOCS.get(property);
613 if (superClassPropertyJavadoc != null) {
614 javadocs.putIfAbsent(property, superClassPropertyJavadoc);
615 }
616 });
617
618 assertAllPropertySetterJavadocsAreFound(properties, moduleName, javadocs);
619
620 return javadocs;
621 }
622
623
624
625
626
627
628
629
630
631
632
633 private static void assertAllPropertySetterJavadocsAreFound(
634 Set<String> properties, String moduleName, Map<String, DetailNode> javadocs)
635 throws MacroExecutionException {
636 for (String property : properties) {
637 final boolean isDocumented = javadocs.containsKey(property)
638 || SUPER_CLASS_PROPERTIES_JAVADOCS.containsKey(property)
639 || TOKENS.equals(property) || JAVADOC_TOKENS.equals(property);
640 if (!isDocumented) {
641 throw new MacroExecutionException(String.format(Locale.ROOT,
642 "%s: Missing documentation for property '%s'. Check superclasses.",
643 moduleName, property));
644 }
645 }
646 }
647
648
649
650
651
652
653 private static void processSuperclasses() throws MacroExecutionException {
654 for (Path superclassPath : MODULE_SUPER_CLASS_PATHS) {
655 final Path fileNamePath = superclassPath.getFileName();
656 if (fileNamePath == null) {
657 throw new MacroExecutionException("Invalid superclass path: " + superclassPath);
658 }
659 final String superclassName = CommonUtil.getFileNameWithoutExtension(
660 fileNamePath.toString());
661 processModule(superclassName, superclassPath);
662 final Map<String, DetailNode> superclassJavadocs =
663 ClassAndPropertiesSettersJavadocScraper.getJavadocsForModuleOrProperty();
664 SUPER_CLASS_PROPERTIES_JAVADOCS.putAll(superclassJavadocs);
665 }
666 }
667
668
669
670
671
672
673
674
675
676 private static void processModule(String moduleName, Path modulePath)
677 throws MacroExecutionException {
678 if (!Files.isRegularFile(modulePath)) {
679 final String message = String.format(Locale.ROOT,
680 "File %s is not a file. Please check the 'modulePath' property.", modulePath);
681 throw new MacroExecutionException(message);
682 }
683 ClassAndPropertiesSettersJavadocScraper.initialize(moduleName);
684 final Checker checker = new Checker();
685 checker.setModuleClassLoader(Checker.class.getClassLoader());
686 final DefaultConfiguration scraperCheckConfig =
687 new DefaultConfiguration(
688 ClassAndPropertiesSettersJavadocScraper.class.getName());
689 final DefaultConfiguration defaultConfiguration =
690 new DefaultConfiguration("configuration");
691 final DefaultConfiguration treeWalkerConfig =
692 new DefaultConfiguration(TreeWalker.class.getName());
693 defaultConfiguration.addProperty(CHARSET, StandardCharsets.UTF_8.name());
694 defaultConfiguration.addChild(treeWalkerConfig);
695 treeWalkerConfig.addChild(scraperCheckConfig);
696 try {
697 checker.configure(defaultConfiguration);
698 final List<File> filesToProcess = List.of(modulePath.toFile());
699 checker.process(filesToProcess);
700 checker.destroy();
701 }
702 catch (CheckstyleException checkstyleException) {
703 final String message = String.format(Locale.ROOT, "Failed processing %s", moduleName);
704 throw new MacroExecutionException(message, checkstyleException);
705 }
706 }
707
708
709
710
711
712
713
714 public static Set<String> getProperties(Class<?> clss) {
715 final Set<String> result = new TreeSet<>();
716 final PropertyDescriptor[] propertyDescriptors = PropertyUtils.getPropertyDescriptors(clss);
717
718 for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
719 if (propertyDescriptor.getWriteMethod() != null) {
720 result.add(propertyDescriptor.getName());
721 }
722 }
723
724 return result;
725 }
726
727
728
729
730
731
732
733
734
735 private static boolean isGlobalProperty(Class<?> clss, String propertyName) {
736 return AbstractCheck.class.isAssignableFrom(clss)
737 && CHECK_PROPERTIES.contains(propertyName)
738 || AbstractJavadocCheck.class.isAssignableFrom(clss)
739 && JAVADOC_CHECK_PROPERTIES.contains(propertyName)
740 || AbstractFileSetCheck.class.isAssignableFrom(clss)
741 && FILESET_PROPERTIES.contains(propertyName);
742 }
743
744
745
746
747
748
749
750
751 private static boolean isUndocumentedProperty(Class<?> clss, String propertyName) {
752 return UNDOCUMENTED_PROPERTIES.contains(clss.getSimpleName() + DOT + propertyName);
753 }
754
755
756
757
758
759
760
761
762
763 private static Set<String> getNonExplicitProperties(
764 Object instance, Class<?> clss) {
765 final Set<String> result = new TreeSet<>();
766 if (AbstractCheck.class.isAssignableFrom(clss)) {
767 final AbstractCheck check = (AbstractCheck) instance;
768
769 final int[] acceptableTokens = check.getAcceptableTokens();
770 Arrays.sort(acceptableTokens);
771 final int[] defaultTokens = check.getDefaultTokens();
772 Arrays.sort(defaultTokens);
773 final int[] requiredTokens = check.getRequiredTokens();
774 Arrays.sort(requiredTokens);
775
776 if (!Arrays.equals(acceptableTokens, defaultTokens)
777 || !Arrays.equals(acceptableTokens, requiredTokens)) {
778 result.add(TOKENS);
779 }
780 }
781
782 if (AbstractJavadocCheck.class.isAssignableFrom(clss)) {
783 final AbstractJavadocCheck check = (AbstractJavadocCheck) instance;
784 result.add("violateExecutionOnNonTightHtml");
785
786 final int[] acceptableJavadocTokens = check.getAcceptableJavadocTokens();
787 Arrays.sort(acceptableJavadocTokens);
788 final int[] defaultJavadocTokens = check.getDefaultJavadocTokens();
789 Arrays.sort(defaultJavadocTokens);
790 final int[] requiredJavadocTokens = check.getRequiredJavadocTokens();
791 Arrays.sort(requiredJavadocTokens);
792
793 if (!Arrays.equals(acceptableJavadocTokens, defaultJavadocTokens)
794 || !Arrays.equals(acceptableJavadocTokens, requiredJavadocTokens)) {
795 result.add(JAVADOC_TOKENS);
796 }
797 }
798
799 if (AbstractFileSetCheck.class.isAssignableFrom(clss)) {
800 result.add(FILE_EXTENSIONS);
801 }
802 return result;
803 }
804
805
806
807
808
809
810
811
812
813
814 public static String getPropertyDescription(
815 String propertyName, DetailNode javadoc, String moduleName)
816 throws MacroExecutionException {
817 final String description;
818 if (TOKENS.equals(propertyName)) {
819 description = "tokens to check";
820 }
821 else if (JAVADOC_TOKENS.equals(propertyName)) {
822 description = "javadoc tokens to check";
823 }
824 else {
825 final String descriptionString = SETTER_PATTERN.matcher(
826 DescriptionExtractor.getDescriptionFromJavadoc(javadoc, moduleName))
827 .replaceFirst("");
828
829 final String firstLetterCapitalized = descriptionString.substring(0, 1)
830 .toUpperCase(Locale.ROOT);
831 description = firstLetterCapitalized + descriptionString.substring(1);
832 }
833 return description;
834 }
835
836
837
838
839
840
841
842
843
844
845
846 public static String getSinceVersion(String moduleName, DetailNode moduleJavadoc,
847 String propertyName, DetailNode propertyJavadoc)
848 throws MacroExecutionException {
849 final String sinceVersion;
850 final String superClassSinceVersion = SINCE_VERSION_FOR_INHERITED_PROPERTY
851 .get(moduleName + DOT + propertyName);
852 if (superClassSinceVersion != null) {
853 sinceVersion = superClassSinceVersion;
854 }
855 else if (TOKENS.equals(propertyName)
856 || JAVADOC_TOKENS.equals(propertyName)) {
857
858 sinceVersion = getSinceVersionFromJavadoc(moduleJavadoc);
859 }
860 else {
861 sinceVersion = getSinceVersionFromJavadoc(propertyJavadoc);
862 }
863
864 if (sinceVersion == null) {
865 final String message = String.format(Locale.ROOT,
866 "Failed to find '@since' version for '%s' property"
867 + " in '%s' and all parent classes.", propertyName, moduleName);
868 throw new MacroExecutionException(message);
869 }
870
871 return sinceVersion;
872 }
873
874
875
876
877
878
879
880 @Nullable
881 private static String getSinceVersionFromJavadoc(DetailNode javadoc) {
882 final DetailNode sinceJavadocTag = getSinceJavadocTag(javadoc);
883 return Optional.ofNullable(sinceJavadocTag)
884 .map(tag -> JavadocUtil.findFirstToken(tag, JavadocTokenTypes.DESCRIPTION))
885 .map(description -> JavadocUtil.findFirstToken(description, JavadocTokenTypes.TEXT))
886 .map(DetailNode::getText)
887 .orElse(null);
888 }
889
890
891
892
893
894
895
896 private static DetailNode getSinceJavadocTag(DetailNode javadoc) {
897 final DetailNode[] children = javadoc.getChildren();
898 DetailNode javadocTagWithSince = null;
899 for (final DetailNode child : children) {
900 if (child.getType() == JavadocTokenTypes.JAVADOC_TAG) {
901 final DetailNode sinceNode = JavadocUtil.findFirstToken(
902 child, JavadocTokenTypes.SINCE_LITERAL);
903 if (sinceNode != null) {
904 javadocTagWithSince = child;
905 break;
906 }
907 }
908 }
909 return javadocTagWithSince;
910 }
911
912
913
914
915
916
917
918
919
920
921
922 public static String getType(Field field, String propertyName,
923 String moduleName, Object instance)
924 throws MacroExecutionException {
925 final Class<?> fieldClass = getFieldClass(field, propertyName, moduleName, instance);
926 return Optional.ofNullable(field)
927 .map(nonNullField -> nonNullField.getAnnotation(XdocsPropertyType.class))
928 .map(propertyType -> propertyType.value().getDescription())
929 .orElseGet(fieldClass::getSimpleName);
930 }
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946 public static String getDefaultValue(String propertyName, Field field,
947 Object classInstance, String moduleName)
948 throws MacroExecutionException {
949 final Object value = getFieldValue(field, classInstance);
950 final Class<?> fieldClass = getFieldClass(field, propertyName, moduleName, classInstance);
951 String result = null;
952 if (CHARSET.equals(propertyName)) {
953 result = "the charset property of the parent"
954 + " <a href=\"https://checkstyle.org/config.html#Checker\">Checker</a> module";
955 }
956 else if (classInstance instanceof PropertyCacheFile) {
957 result = "null (no cache file)";
958 }
959 else if (fieldClass == boolean.class) {
960 result = value.toString();
961 }
962 else if (fieldClass == int.class) {
963 result = value.toString();
964 }
965 else if (fieldClass == int[].class) {
966 result = getIntArrayPropertyValue(value);
967 }
968 else if (fieldClass == double[].class) {
969 result = removeSquareBrackets(Arrays.toString((double[]) value).replace(".0", ""));
970 if (result.isEmpty()) {
971 result = CURLY_BRACKETS;
972 }
973 }
974 else if (fieldClass == String[].class) {
975 result = getStringArrayPropertyValue(propertyName, value);
976 }
977 else if (fieldClass == URI.class || fieldClass == String.class) {
978 if (value != null) {
979 result = '"' + value.toString() + '"';
980 }
981 }
982 else if (fieldClass == Pattern.class) {
983 if (value != null) {
984 result = '"' + value.toString().replace("\n", "\\n").replace("\t", "\\t")
985 .replace("\r", "\\r").replace("\f", "\\f") + '"';
986 }
987 }
988 else if (fieldClass == Pattern[].class) {
989 result = getPatternArrayPropertyValue(value);
990 }
991 else if (fieldClass.isEnum()) {
992 if (value != null) {
993 result = value.toString().toLowerCase(Locale.ENGLISH);
994 }
995 }
996 else if (fieldClass == AccessModifierOption[].class) {
997 result = removeSquareBrackets(Arrays.toString((Object[]) value));
998 }
999 else {
1000 final String message = String.format(Locale.ROOT,
1001 "Unknown property type: %s", fieldClass.getSimpleName());
1002 throw new MacroExecutionException(message);
1003 }
1004
1005 if (result == null) {
1006 result = "null";
1007 }
1008
1009 return result;
1010 }
1011
1012
1013
1014
1015
1016
1017
1018 private static String getPatternArrayPropertyValue(Object fieldValue) {
1019 Object value = fieldValue;
1020 if (value instanceof Collection) {
1021 final Collection<?> collection = (Collection<?>) value;
1022
1023 value = collection.stream()
1024 .map(Pattern.class::cast)
1025 .toArray(Pattern[]::new);
1026 }
1027
1028 String result = "";
1029 if (value != null && Array.getLength(value) > 0) {
1030 result = removeSquareBrackets(
1031 Arrays.stream((Pattern[]) value)
1032 .map(Pattern::pattern)
1033 .collect(Collectors.joining(COMMA_SPACE)));
1034 }
1035
1036 if (result.isEmpty()) {
1037 result = CURLY_BRACKETS;
1038 }
1039 return result;
1040 }
1041
1042
1043
1044
1045
1046
1047
1048 private static String removeSquareBrackets(String value) {
1049 return value
1050 .replace("[", "")
1051 .replace("]", "");
1052 }
1053
1054
1055
1056
1057
1058
1059
1060
1061 private static String getStringArrayPropertyValue(String propertyName, Object value) {
1062 String result;
1063 if (value == null) {
1064 result = "";
1065 }
1066 else {
1067 try (Stream<?> valuesStream = getValuesStream(value)) {
1068 result = valuesStream
1069 .map(String.class::cast)
1070 .sorted()
1071 .collect(Collectors.joining(COMMA_SPACE));
1072 }
1073 }
1074
1075 if (result.isEmpty()) {
1076 if (FILE_EXTENSIONS.equals(propertyName)) {
1077 result = "all files";
1078 }
1079 else {
1080 result = CURLY_BRACKETS;
1081 }
1082 }
1083 return result;
1084 }
1085
1086
1087
1088
1089
1090
1091
1092 private static Stream<?> getValuesStream(Object value) {
1093 final Stream<?> valuesStream;
1094 if (value instanceof Collection) {
1095 final Collection<?> collection = (Collection<?>) value;
1096 valuesStream = collection.stream();
1097 }
1098 else {
1099 final Object[] array = (Object[]) value;
1100 valuesStream = Arrays.stream(array);
1101 }
1102 return valuesStream;
1103 }
1104
1105
1106
1107
1108
1109
1110
1111 private static String getIntArrayPropertyValue(Object value) {
1112 try (IntStream stream = getIntStream(value)) {
1113 String result = stream
1114 .mapToObj(TokenUtil::getTokenName)
1115 .sorted()
1116 .collect(Collectors.joining(COMMA_SPACE));
1117 if (result.isEmpty()) {
1118 result = CURLY_BRACKETS;
1119 }
1120 return result;
1121 }
1122 }
1123
1124
1125
1126
1127
1128
1129
1130 private static IntStream getIntStream(Object value) {
1131 final IntStream stream;
1132 if (value instanceof Collection) {
1133 final Collection<?> collection = (Collection<?>) value;
1134 stream = collection.stream()
1135 .mapToInt(int.class::cast);
1136 }
1137 else if (value instanceof BitSet) {
1138 stream = ((BitSet) value).stream();
1139 }
1140 else {
1141 stream = Arrays.stream((int[]) value);
1142 }
1143 return stream;
1144 }
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157 private static Class<?> getFieldClass(Field field, String propertyName,
1158 String moduleName, Object instance)
1159 throws MacroExecutionException {
1160 Class<?> result = null;
1161
1162 if (PROPERTIES_ALLOWED_GET_TYPES_FROM_METHOD
1163 .contains(moduleName + DOT + propertyName)) {
1164 result = getPropertyClass(propertyName, instance);
1165 }
1166 if (field != null && result == null) {
1167 result = field.getType();
1168 }
1169 if (result == null) {
1170 throw new MacroExecutionException(
1171 "Could not find field " + propertyName + " in class " + moduleName);
1172 }
1173 if (field != null && (result == List.class || result == Set.class)) {
1174 final ParameterizedType type = (ParameterizedType) field.getGenericType();
1175 final Class<?> parameterClass = (Class<?>) type.getActualTypeArguments()[0];
1176
1177 if (parameterClass == Integer.class) {
1178 result = int[].class;
1179 }
1180 else if (parameterClass == String.class) {
1181 result = String[].class;
1182 }
1183 else if (parameterClass == Pattern.class) {
1184 result = Pattern[].class;
1185 }
1186 else {
1187 final String message = "Unknown parameterized type: "
1188 + parameterClass.getSimpleName();
1189 throw new MacroExecutionException(message);
1190 }
1191 }
1192 else if (result == BitSet.class) {
1193 result = int[].class;
1194 }
1195
1196 return result;
1197 }
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208 public static Class<?> getPropertyClass(String propertyName, Object instance)
1209 throws MacroExecutionException {
1210 final Class<?> result;
1211 try {
1212 final PropertyDescriptor descriptor = PropertyUtils.getPropertyDescriptor(instance,
1213 propertyName);
1214 result = descriptor.getPropertyType();
1215 }
1216 catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException exc) {
1217 throw new MacroExecutionException(exc.getMessage(), exc);
1218 }
1219 return result;
1220 }
1221
1222
1223
1224
1225
1226
1227
1228
1229 public static List<Integer> getDifference(int[] tokens, int... subtractions) {
1230 final Set<Integer> subtractionsSet = Arrays.stream(subtractions)
1231 .boxed()
1232 .collect(Collectors.toUnmodifiableSet());
1233 return Arrays.stream(tokens)
1234 .boxed()
1235 .filter(token -> !subtractionsSet.contains(token))
1236 .collect(Collectors.toUnmodifiableList());
1237 }
1238
1239
1240
1241
1242
1243
1244
1245
1246 public static Field getField(Class<?> fieldClass, String propertyName) {
1247 Field result = null;
1248 Class<?> currentClass = fieldClass;
1249
1250 while (!Object.class.equals(currentClass)) {
1251 try {
1252 result = currentClass.getDeclaredField(propertyName);
1253 result.trySetAccessible();
1254 break;
1255 }
1256 catch (NoSuchFieldException ignored) {
1257 currentClass = currentClass.getSuperclass();
1258 }
1259 }
1260
1261 return result;
1262 }
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272 public static String getLinkToDocument(String moduleName, String document)
1273 throws MacroExecutionException {
1274 final Path templatePath = getTemplatePath(moduleName.replace("Check", ""));
1275 if (templatePath == null) {
1276 throw new MacroExecutionException(
1277 String.format(Locale.ROOT,
1278 "Could not find template for %s", moduleName));
1279 }
1280 final Path templatePathParent = templatePath.getParent();
1281 if (templatePathParent == null) {
1282 throw new MacroExecutionException("Failed to get parent path for " + templatePath);
1283 }
1284 return templatePathParent
1285 .relativize(Path.of(SRC, "site/xdoc", document))
1286 .toString()
1287 .replace(".xml", ".html")
1288 .replace('\\', '/');
1289 }
1290
1291
1292 private static final class DescriptionExtractor {
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307 private static String getDescriptionFromJavadoc(DetailNode javadoc, String moduleName)
1308 throws MacroExecutionException {
1309 boolean isInCodeLiteral = false;
1310 boolean isInHtmlElement = false;
1311 boolean isInHrefAttribute = false;
1312 final StringBuilder description = new StringBuilder(128);
1313 final Deque<DetailNode> queue = new ArrayDeque<>();
1314 final List<DetailNode> descriptionNodes = getDescriptionNodes(javadoc);
1315 Lists.reverse(descriptionNodes).forEach(queue::push);
1316
1317
1318 while (!queue.isEmpty()) {
1319 final DetailNode node = queue.pop();
1320 Lists.reverse(Arrays.asList(node.getChildren())).forEach(queue::push);
1321
1322 if (node.getType() == JavadocTokenTypes.HTML_TAG_NAME
1323 && "href".equals(node.getText())) {
1324 isInHrefAttribute = true;
1325 }
1326 if (isInHrefAttribute && node.getType() == JavadocTokenTypes.ATTR_VALUE) {
1327 final String href = node.getText();
1328 if (href.contains(CHECKSTYLE_ORG_URL)) {
1329 handleInternalLink(description, moduleName, href);
1330 }
1331 else {
1332 description.append(href);
1333 }
1334
1335 isInHrefAttribute = false;
1336 continue;
1337 }
1338 if (node.getType() == JavadocTokenTypes.HTML_ELEMENT) {
1339 isInHtmlElement = true;
1340 }
1341 if (node.getType() == JavadocTokenTypes.END
1342 && node.getParent().getType() == JavadocTokenTypes.HTML_ELEMENT_END) {
1343 description.append(node.getText());
1344 isInHtmlElement = false;
1345 }
1346 if (node.getType() == JavadocTokenTypes.TEXT
1347
1348 || isInHtmlElement && node.getChildren().length == 0
1349
1350 && node.getType() != JavadocTokenTypes.LEADING_ASTERISK) {
1351 description.append(node.getText());
1352 }
1353 if (node.getType() == JavadocTokenTypes.CODE_LITERAL) {
1354 isInCodeLiteral = true;
1355 description.append("<code>");
1356 }
1357 if (isInCodeLiteral
1358 && node.getType() == JavadocTokenTypes.JAVADOC_INLINE_TAG_END) {
1359 isInCodeLiteral = false;
1360 description.append("</code>");
1361 }
1362 }
1363 return description.toString().trim();
1364 }
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375 private static void handleInternalLink(StringBuilder description,
1376 String moduleName, String value)
1377 throws MacroExecutionException {
1378 String href = value;
1379 href = href.replace(CHECKSTYLE_ORG_URL, "");
1380
1381 href = href.substring(1, href.length() - 1);
1382
1383 final String relativeHref = getLinkToDocument(moduleName, href);
1384 final char doubleQuote = '\"';
1385 description.append(doubleQuote).append(relativeHref).append(doubleQuote);
1386 }
1387
1388
1389
1390
1391
1392
1393
1394 private static List<DetailNode> getDescriptionNodes(DetailNode javadoc) {
1395 final DetailNode[] children = javadoc.getChildren();
1396 final List<DetailNode> descriptionNodes = new ArrayList<>();
1397 for (final DetailNode child : children) {
1398 if (isEndOfDescription(child)) {
1399 break;
1400 }
1401 descriptionNodes.add(child);
1402 }
1403 return descriptionNodes;
1404 }
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415 private static boolean isEndOfDescription(DetailNode child) {
1416 final DetailNode nextSibling = JavadocUtil.getNextSibling(child);
1417 final DetailNode secondNextSibling = JavadocUtil.getNextSibling(nextSibling);
1418 final DetailNode thirdNextSibling = JavadocUtil.getNextSibling(secondNextSibling);
1419
1420 return child.getType() == JavadocTokenTypes.NEWLINE
1421 && nextSibling.getType() == JavadocTokenTypes.LEADING_ASTERISK
1422 && secondNextSibling.getType() == JavadocTokenTypes.NEWLINE
1423 && thirdNextSibling.getType() == JavadocTokenTypes.LEADING_ASTERISK;
1424 }
1425 }
1426 }