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