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