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.module.ModuleDescriptor.Version;
26 import java.lang.reflect.Array;
27 import java.lang.reflect.Field;
28 import java.lang.reflect.InvocationTargetException;
29 import java.lang.reflect.ParameterizedType;
30 import java.net.URI;
31 import java.nio.file.Files;
32 import java.nio.file.Path;
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.BitSet;
36 import java.util.Collection;
37 import java.util.Collections;
38 import java.util.HashSet;
39 import java.util.List;
40 import java.util.Locale;
41 import java.util.Map;
42 import java.util.Optional;
43 import java.util.Set;
44 import java.util.TreeMap;
45 import java.util.TreeSet;
46 import java.util.regex.Pattern;
47 import java.util.stream.Collectors;
48 import java.util.stream.IntStream;
49 import java.util.stream.Stream;
50
51 import org.apache.commons.beanutils.PropertyUtils;
52 import org.apache.maven.doxia.macro.MacroExecutionException;
53
54 import com.puppycrawl.tools.checkstyle.Checker;
55 import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
56 import com.puppycrawl.tools.checkstyle.ModuleFactory;
57 import com.puppycrawl.tools.checkstyle.PackageNamesLoader;
58 import com.puppycrawl.tools.checkstyle.PackageObjectFactory;
59 import com.puppycrawl.tools.checkstyle.PropertyCacheFile;
60 import com.puppycrawl.tools.checkstyle.PropertyType;
61 import com.puppycrawl.tools.checkstyle.TreeWalker;
62 import com.puppycrawl.tools.checkstyle.TreeWalkerFilter;
63 import com.puppycrawl.tools.checkstyle.XdocsPropertyType;
64 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
65 import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck;
66 import com.puppycrawl.tools.checkstyle.api.BeforeExecutionFileFilter;
67 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
68 import com.puppycrawl.tools.checkstyle.api.DetailNode;
69 import com.puppycrawl.tools.checkstyle.api.Filter;
70 import com.puppycrawl.tools.checkstyle.api.JavadocCommentsTokenTypes;
71 import com.puppycrawl.tools.checkstyle.checks.javadoc.AbstractJavadocCheck;
72 import com.puppycrawl.tools.checkstyle.checks.regexp.RegexpMultilineCheck;
73 import com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineCheck;
74 import com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineJavaCheck;
75 import com.puppycrawl.tools.checkstyle.internal.annotation.PreserveOrder;
76 import com.puppycrawl.tools.checkstyle.meta.JavadocMetadataScraperUtil;
77 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
78 import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
79 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
80
81
82
83
84 public final class SiteUtil {
85
86
87 public static final String TOKENS = "tokens";
88
89 public static final String JAVADOC_TOKENS = "javadocTokens";
90
91 public static final String VIOLATE_EXECUTION_ON_NON_TIGHT_HTML =
92 "violateExecutionOnNonTightHtml";
93
94 public static final String DOT = ".";
95
96 public static final String COMMA = ",";
97
98 public static final String WHITESPACE = " ";
99
100 public static final String COMMA_SPACE = COMMA + WHITESPACE;
101
102 public static final String TOKEN_TYPES = "TokenTypes";
103
104 public static final String PATH_TO_TOKEN_TYPES =
105 "apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html";
106
107 public static final String PATH_TO_JAVADOC_TOKEN_TYPES =
108 "apidocs/com/puppycrawl/tools/checkstyle/api/JavadocTokenTypes.html";
109
110 public static final String SINCE_VERSION = "Since version";
111
112 public static final Pattern FINAL_CHECK = Pattern.compile("Check$");
113
114 public static final String FILE_EXTENSIONS = "fileExtensions";
115
116 public static final String CHARSET = "charset";
117
118
119 private static final Pattern SETTER_PATTERN = Pattern.compile("^Setter to ");
120
121
122 private static final String CHECKSTYLE_ORG_URL = "https://checkstyle.org/";
123
124 private static final String CHECKS = "checks";
125
126 private static final String NAMING = "naming";
127
128 private static final String SRC = "src";
129
130 private static final String TEMPLATE_FILE_EXTENSION = ".xml.template";
131
132
133 private static final Pattern COMMA_SPACE_PATTERN = Pattern.compile(", ");
134
135
136 private static final String EMPTY_CURLY_BRACES = "{}";
137
138
139 private static final String NULL_STR = "null";
140
141
142 private static final Map<Class<?>, String> CLASS_TO_PARENT_MODULE = Map.ofEntries(
143 Map.entry(AbstractCheck.class, TreeWalker.class.getSimpleName()),
144 Map.entry(TreeWalkerFilter.class, TreeWalker.class.getSimpleName()),
145 Map.entry(AbstractFileSetCheck.class, Checker.class.getSimpleName()),
146 Map.entry(Filter.class, Checker.class.getSimpleName()),
147 Map.entry(BeforeExecutionFileFilter.class, Checker.class.getSimpleName())
148 );
149
150
151 private static final Set<String> CHECK_PROPERTIES =
152 getProperties(AbstractCheck.class);
153
154
155 private static final Set<String> JAVADOC_CHECK_PROPERTIES =
156 getProperties(AbstractJavadocCheck.class);
157
158
159 private static final Set<String> FILESET_PROPERTIES =
160 getProperties(AbstractFileSetCheck.class);
161
162
163
164
165 private static final String HEADER_CHECK_HEADER = "HeaderCheck.header";
166
167
168
169
170 private static final String REGEXP_HEADER_CHECK_HEADER = "RegexpHeaderCheck.header";
171
172
173
174
175 private static final String API = "api";
176
177
178 private static final Set<String> UNDOCUMENTED_PROPERTIES = Set.of(
179 "SuppressWithNearbyCommentFilter.fileContents",
180 "SuppressionCommentFilter.fileContents"
181 );
182
183
184 private static final Set<String> PROPERTIES_ALLOWED_GET_TYPES_FROM_METHOD = Set.of(
185
186 "SuppressWarningsHolder.aliasList",
187
188 HEADER_CHECK_HEADER,
189 REGEXP_HEADER_CHECK_HEADER,
190
191 "RedundantModifierCheck.jdkVersion",
192
193 "CustomImportOrderCheck.customImportOrderRules"
194 );
195
196
197 private static final String MAIN_FOLDER_PATH = Path.of(
198 SRC, "main", "java", "com", "puppycrawl", "tools", "checkstyle").toString();
199
200
201 private static final List<Path> MODULE_SUPER_CLASS_PATHS = List.of(
202 Path.of(MAIN_FOLDER_PATH, CHECKS, NAMING, "AbstractAccessControlNameCheck.java"),
203 Path.of(MAIN_FOLDER_PATH, CHECKS, NAMING, "AbstractNameCheck.java"),
204 Path.of(MAIN_FOLDER_PATH, CHECKS, "javadoc", "AbstractJavadocCheck.java"),
205 Path.of(MAIN_FOLDER_PATH, API, "AbstractFileSetCheck.java"),
206 Path.of(MAIN_FOLDER_PATH, API, "AbstractCheck.java"),
207 Path.of(MAIN_FOLDER_PATH, CHECKS, "header", "AbstractHeaderCheck.java"),
208 Path.of(MAIN_FOLDER_PATH, CHECKS, "metrics", "AbstractClassCouplingCheck.java"),
209 Path.of(MAIN_FOLDER_PATH, CHECKS, "whitespace", "AbstractParenPadCheck.java")
210 );
211
212
213
214
215 private SiteUtil() {
216 }
217
218
219
220
221
222
223
224
225 public static Set<String> getMessageKeys(Class<?> module)
226 throws MacroExecutionException {
227 final Set<Field> messageKeyFields = getCheckMessageKeysFields(module);
228 final Set<String> messageKeys = new TreeSet<>();
229 for (Field field : messageKeyFields) {
230 messageKeys.add(getFieldValue(field, module).toString());
231 }
232 return messageKeys;
233 }
234
235
236
237
238
239
240
241
242
243
244
245
246 private static Set<Field> getCheckMessageKeysFields(Class<?> module)
247 throws MacroExecutionException {
248 try {
249 final Set<Field> checkstyleMessages = new HashSet<>();
250
251
252 final Field[] fields = module.getDeclaredFields();
253
254 for (Field field : fields) {
255 if (field.getName().startsWith("MSG_")) {
256 checkstyleMessages.add(field);
257 }
258 }
259
260 final Class<?> superModule = module.getSuperclass();
261 if (superModule != null) {
262 checkstyleMessages.addAll(getCheckMessageKeysFields(superModule));
263 }
264
265 if (module == RegexpMultilineCheck.class) {
266 checkstyleMessages.addAll(getCheckMessageKeysFields(Class.forName(
267 "com.puppycrawl.tools.checkstyle.checks.regexp.MultilineDetector")));
268 }
269 else if (module == RegexpSinglelineCheck.class
270 || module == RegexpSinglelineJavaCheck.class) {
271 checkstyleMessages.addAll(getCheckMessageKeysFields(Class
272 .forName("com.puppycrawl.tools.checkstyle.checks.regexp.SinglelineDetector")));
273 }
274 return checkstyleMessages;
275 }
276 catch (ClassNotFoundException exc) {
277 final String message = String.format(Locale.ROOT, "Couldn't find class: %s",
278 module.getName());
279 throw new MacroExecutionException(message, exc);
280 }
281 }
282
283
284
285
286
287
288
289
290
291 public static Object getFieldValue(Field field, Object instance)
292 throws MacroExecutionException {
293 try {
294 Object fieldValue = null;
295
296 if (field != null) {
297
298 field.trySetAccessible();
299 fieldValue = field.get(instance);
300 }
301
302 return fieldValue;
303 }
304 catch (IllegalAccessException exc) {
305 throw new MacroExecutionException("Couldn't get field value", exc);
306 }
307 }
308
309
310
311
312
313
314
315
316 public static Object getModuleInstance(String moduleName) throws MacroExecutionException {
317 final ModuleFactory factory = getPackageObjectFactory();
318 try {
319 return factory.createModule(moduleName);
320 }
321 catch (CheckstyleException exc) {
322 throw new MacroExecutionException("Couldn't find class: " + moduleName, exc);
323 }
324 }
325
326
327
328
329
330
331
332 private static PackageObjectFactory getPackageObjectFactory() throws MacroExecutionException {
333 try {
334 final ClassLoader cl = ViolationMessagesMacro.class.getClassLoader();
335 final Set<String> packageNames = PackageNamesLoader.getPackageNames(cl);
336 return new PackageObjectFactory(packageNames, cl);
337 }
338 catch (CheckstyleException exc) {
339 throw new MacroExecutionException("Couldn't load checkstyle modules", exc);
340 }
341 }
342
343
344
345
346
347
348
349
350
351
352
353 public static String getNewlineAndIndentSpaces(int amountOfSpaces) {
354 return System.lineSeparator() + WHITESPACE.repeat(amountOfSpaces);
355 }
356
357
358
359
360
361
362
363
364
365 public static Path getTemplatePath(String moduleName) throws MacroExecutionException {
366 final String fileNamePattern = ".*[\\\\/]"
367 + moduleName.toLowerCase(Locale.ROOT) + "\\..*";
368 return getXdocsTemplatesFilePaths()
369 .stream()
370 .filter(path -> path.toString().matches(fileNamePattern))
371 .findFirst()
372 .orElse(null);
373 }
374
375
376
377
378
379
380
381
382
383 public static Set<Path> getXdocsTemplatesFilePaths() throws MacroExecutionException {
384 final Path directory = Path.of("src/site/xdoc");
385 try (Stream<Path> stream = Files.find(directory, Integer.MAX_VALUE,
386 (path, attr) -> {
387 return attr.isRegularFile()
388 && path.toString().endsWith(TEMPLATE_FILE_EXTENSION);
389 })) {
390 return stream.collect(Collectors.toUnmodifiableSet());
391 }
392 catch (IOException ioException) {
393 throw new MacroExecutionException("Failed to find xdocs templates", ioException);
394 }
395 }
396
397
398
399
400
401
402
403
404
405 public static String getParentModule(Class<?> moduleClass)
406 throws MacroExecutionException {
407 String parentModuleName = "";
408 Class<?> parentClass = moduleClass.getSuperclass();
409
410 while (parentClass != null) {
411 parentModuleName = CLASS_TO_PARENT_MODULE.get(parentClass);
412 if (parentModuleName != null) {
413 break;
414 }
415 parentClass = parentClass.getSuperclass();
416 }
417
418
419 if (parentModuleName == null || parentModuleName.isEmpty()) {
420 final Class<?>[] interfaces = moduleClass.getInterfaces();
421 for (Class<?> interfaceClass : interfaces) {
422 parentModuleName = CLASS_TO_PARENT_MODULE.get(interfaceClass);
423 if (parentModuleName != null) {
424 break;
425 }
426 }
427 }
428 if (parentModuleName == null || parentModuleName.isEmpty()) {
429 final String message = String.format(Locale.ROOT,
430 "Failed to find parent module for %s", moduleClass.getSimpleName());
431 throw new MacroExecutionException(message);
432 }
433 return parentModuleName;
434 }
435
436
437
438
439
440
441
442
443 public static Set<String> getPropertiesForDocumentation(Class<?> clss, Object instance) {
444 final Set<String> properties =
445 getProperties(clss).stream()
446 .filter(prop -> {
447 return !isGlobalProperty(clss, prop)
448 && !isUndocumentedProperty(clss, prop);
449 })
450 .collect(Collectors.toCollection(HashSet::new));
451 properties.addAll(getNonExplicitProperties(instance, clss));
452 return new TreeSet<>(properties);
453 }
454
455
456
457
458
459
460
461
462
463 public static String getModuleSinceVersion(String moduleClassName, Path modulePath)
464 throws MacroExecutionException {
465 processModule(moduleClassName, modulePath);
466 return JavadocScraperResultUtil.getModuleSinceVersion();
467 }
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484 public static Map<String, PropertyDetails> buildPropertyDetails(Set<String> properties,
485 String moduleName, Path modulePath,
486 Object instance)
487 throws MacroExecutionException {
488 final Map<String, PropertyDetails> superClassPropertyData = buildSuperClassPropertyData();
489 processModule(moduleName, modulePath, instance, properties);
490
491 final Map<String, PropertyDetails> currentPropertiesDetails =
492 new TreeMap<>(JavadocScraperResultUtil.getPropertiesDetails());
493
494 for (String property : properties) {
495 if (!currentPropertiesDetails.containsKey(property)) {
496 processInheritedProperty(currentPropertiesDetails, property,
497 instance, moduleName, superClassPropertyData);
498 }
499 }
500 assertAllPropertiesAreFound(properties, moduleName, currentPropertiesDetails);
501 return Collections.unmodifiableMap(currentPropertiesDetails);
502 }
503
504
505
506
507
508
509
510
511
512
513
514 private static void processInheritedProperty(
515 Map<String, PropertyDetails> detailsMap,
516 String property, Object instance,
517 String moduleName,
518 Map<String, PropertyDetails> superClassPropertyData)
519 throws MacroExecutionException {
520 final String moduleSince = JavadocScraperResultUtil.getModuleSinceVersion();
521 final PropertyDetails inherited = superClassPropertyData.get(property);
522 if (inherited != null) {
523 final String description = inherited.getDescription();
524 final String inheritedSince = inherited.getSinceVersion();
525
526 final String since;
527 if (inheritedSince.isEmpty()
528 || !moduleSince.isEmpty()
529 && isVersionAtLeast(moduleSince, inheritedSince)) {
530 if (moduleSince.isEmpty()) {
531 since = inheritedSince;
532 }
533 else {
534 since = moduleSince;
535 }
536 }
537 else {
538 since = inheritedSince;
539 }
540 final Field field = getField(instance.getClass(), property);
541 final PropertyDetails.Builder builder = new PropertyDetails.Builder()
542 .name(property)
543 .description(description)
544 .sinceVersion(since);
545 detailsMap.put(property, constructPropertyDetails(builder,
546 instance, field, property, moduleName));
547 }
548 else if (TOKENS.equals(property)
549 || JAVADOC_TOKENS.equals(property)
550 || VIOLATE_EXECUTION_ON_NON_TIGHT_HTML.equals(property)) {
551 final String description = getPropertyDescriptionForXdoc(property, null,
552 moduleName);
553 final String since = getPropertySinceVersion(moduleSince, null);
554 final Field field = getField(instance.getClass(), property);
555 final PropertyDetails.Builder builder = new PropertyDetails.Builder()
556 .name(property)
557 .description(description)
558 .sinceVersion(since);
559 detailsMap.put(property, constructPropertyDetails(builder,
560 instance, field, property, moduleName));
561 }
562 }
563
564
565
566
567
568
569
570
571
572 private static void assertAllPropertiesAreFound(
573 Set<String> properties, String moduleName, Map<String, PropertyDetails> details)
574 throws MacroExecutionException {
575 for (String property : properties) {
576 if (!details.containsKey(property)) {
577 throw new MacroExecutionException(String.format(Locale.ROOT,
578 "%s: Missing documentation for property '%s'.", moduleName, property));
579 }
580 }
581 }
582
583
584
585
586
587
588
589
590
591 private static Map<String, PropertyDetails> buildSuperClassPropertyData()
592 throws MacroExecutionException {
593 final Map<String, PropertyDetails> result = new TreeMap<>();
594 for (Path superclassPath : MODULE_SUPER_CLASS_PATHS) {
595 final Path fileNamePath = superclassPath.getFileName();
596 if (fileNamePath == null) {
597 throw new MacroExecutionException("Invalid superclass path: " + superclassPath);
598 }
599 final String superclassName = CommonUtil.getFileNameWithoutExtension(
600 fileNamePath.toString());
601
602 final String pathString = superclassPath.toString().replace('\\', '/');
603 final String marker = "com/puppycrawl/tools/checkstyle/";
604 final String classPath = pathString.substring(pathString.indexOf(marker));
605 final String classFullName = classPath
606 .substring(0, classPath.lastIndexOf(".java"))
607 .replace('/', '.');
608 final Set<String> properties;
609 try {
610 final Class<?> superClass = Class.forName(classFullName);
611 final Set<String> setterProperties = new TreeSet<>(getProperties(superClass));
612 if (AbstractFileSetCheck.class.isAssignableFrom(superClass)) {
613 setterProperties.add(FILE_EXTENSIONS);
614 }
615 if (AbstractJavadocCheck.class.isAssignableFrom(superClass)) {
616 setterProperties.add(VIOLATE_EXECUTION_ON_NON_TIGHT_HTML);
617 }
618 properties = setterProperties;
619 }
620 catch (ClassNotFoundException exc) {
621 throw new MacroExecutionException("Failed to find class: " + classFullName, exc);
622 }
623
624 processModule(superclassName, superclassPath, null, properties);
625 result.putAll(JavadocScraperResultUtil.getPropertiesDetails());
626 }
627 return result;
628 }
629
630
631
632
633
634
635
636
637 public static void processModule(String moduleName, Path modulePath)
638 throws MacroExecutionException {
639 final Object instance = getModuleInstance(moduleName);
640 final Set<String> properties = getPropertiesForDocumentation(instance.getClass(),
641 instance);
642 processModule(moduleName, modulePath, instance, properties);
643 }
644
645
646
647
648
649
650
651
652
653
654
655 private static void processModule(String moduleName, Path modulePath, Object instance,
656 Set<String> properties)
657 throws MacroExecutionException {
658 final Path resolvedPath = Path.of("").toAbsolutePath()
659 .resolve(modulePath.toString().replace('\\', '/'))
660 .normalize();
661 if (!Files.isRegularFile(resolvedPath)) {
662 final String message = String.format(Locale.ROOT,
663 "File %s is not a file. Please check the 'modulePath' property.", modulePath);
664 throw new MacroExecutionException(message);
665 }
666 ClassAndPropertiesSettersJavadocScraper.initialize(moduleName, instance, properties);
667 final Checker checker = new Checker();
668 checker.setModuleClassLoader(Checker.class.getClassLoader());
669 final DefaultConfiguration scraperCheckConfig =
670 new DefaultConfiguration(
671 ClassAndPropertiesSettersJavadocScraper.class.getName());
672 final DefaultConfiguration defaultConfiguration =
673 new DefaultConfiguration("configuration");
674 final DefaultConfiguration treeWalkerConfig =
675 new DefaultConfiguration(TreeWalker.class.getName());
676 defaultConfiguration.addProperty(CHARSET, "UTF-8");
677 defaultConfiguration.addChild(treeWalkerConfig);
678 treeWalkerConfig.addChild(scraperCheckConfig);
679 try {
680 checker.configure(defaultConfiguration);
681 final List<File> filesToProcess = List.of(resolvedPath.toFile());
682 checker.process(filesToProcess);
683 checker.destroy();
684 }
685 catch (CheckstyleException checkstyleException) {
686 final String message = String.format(Locale.ROOT, "Failed processing %s", moduleName);
687 throw new MacroExecutionException(message, checkstyleException);
688 }
689 }
690
691
692
693
694
695
696
697
698
699
700
701
702 public static PropertyDetails constructPropertyDetails(PropertyDetails.Builder builder,
703 Object instance, Field field,
704 String propertyName, String moduleName)
705 throws MacroExecutionException {
706 if (TOKENS.equals(propertyName)) {
707 configureTokensDetails(builder, (AbstractCheck) instance);
708 }
709 else if (JAVADOC_TOKENS.equals(propertyName)) {
710 configureJavadocTokensDetails(builder, (AbstractJavadocCheck) instance);
711 }
712 else {
713 configureOtherPropertyDetails(builder, instance, field, propertyName, moduleName);
714 }
715 return builder.build();
716 }
717
718
719
720
721
722
723
724 private static void configureTokensDetails(PropertyDetails.Builder builder,
725 AbstractCheck check) {
726 final int[] requiredTokens = check.getRequiredTokens();
727 final int[] acceptableTokens = check.getAcceptableTokens();
728 final int[] defaultTokens = check.getDefaultTokens();
729 final int[] allTokenIds = TokenUtil.getAllTokenIds();
730 if (requiredTokens.length == 0
731 && Arrays.equals(acceptableTokens, allTokenIds)) {
732 builder.tokenPropertyType(PropertyDetails.TokenPropertyType.TOKEN_SET);
733 }
734 else {
735 builder.tokenPropertyType(PropertyDetails.TokenPropertyType.TOKEN_SUBSET);
736 builder.configurableTokens(getDifference(acceptableTokens,
737 requiredTokens).stream().map(TokenUtil::getTokenName).toList());
738 }
739 if (Arrays.equals(defaultTokens, allTokenIds)) {
740 builder.defaultValueTokens(List.of(TOKEN_TYPES));
741 }
742 else {
743 builder.defaultValueTokens(getDifference(defaultTokens,
744 requiredTokens).stream().map(TokenUtil::getTokenName).toList());
745 }
746 }
747
748
749
750
751
752
753
754 private static void configureJavadocTokensDetails(PropertyDetails.Builder builder,
755 AbstractJavadocCheck check) {
756 builder.tokenPropertyType(PropertyDetails.TokenPropertyType.JAVADOC_TOKEN_SUBSET);
757 builder.configurableTokens(getDifference(check.getAcceptableJavadocTokens(),
758 check.getRequiredJavadocTokens()).stream()
759 .map(JavadocUtil::getTokenName).toList());
760 builder.defaultValueTokens(getDifference(check.getDefaultJavadocTokens(),
761 check.getRequiredJavadocTokens()).stream()
762 .map(JavadocUtil::getTokenName).toList());
763 }
764
765
766
767
768
769
770
771
772
773
774
775 private static void configureOtherPropertyDetails(PropertyDetails.Builder builder,
776 Object instance, Field field,
777 String propertyName, String moduleName)
778 throws MacroExecutionException {
779 final Class<?> fieldClass = getFieldClass(field, propertyName, moduleName, instance);
780 final String type;
781 if (ModuleJavadocParsingUtil.isPropertySpecialTokenProp(field)) {
782 type = "subset of tokens TokenTypes";
783 }
784 else {
785 final String rawType = getType(field, propertyName, moduleName, instance);
786 type = simplifyTypeName(rawType);
787 }
788 builder.type(type);
789
790 String defaultValue;
791 if (field != null) {
792 defaultValue = getDefaultValue(propertyName, field, instance, moduleName);
793 }
794 else {
795 final Class<?> propertyClass = getPropertyClass(propertyName, instance);
796 if (propertyClass.isArray()) {
797 defaultValue = EMPTY_CURLY_BRACES;
798 }
799 else {
800 defaultValue = NULL_STR;
801 }
802 }
803
804 if (defaultValue.isEmpty() && fieldClass.isArray()) {
805 defaultValue = EMPTY_CURLY_BRACES;
806 }
807
808 if (ModuleJavadocParsingUtil.isPropertySpecialTokenProp(field)
809 && !EMPTY_CURLY_BRACES.equals(defaultValue)) {
810 builder.defaultValueTokens(Arrays.asList(COMMA_SPACE_PATTERN.split(defaultValue)));
811 }
812 else {
813 builder.defaultValue(defaultValue);
814 }
815 }
816
817
818
819
820
821
822
823 public static Set<String> getProperties(Class<?> clss) {
824 final Set<String> result = new TreeSet<>();
825 final PropertyDescriptor[] propertyDescriptors = PropertyUtils.getPropertyDescriptors(clss);
826
827 for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
828 if (propertyDescriptor.getWriteMethod() != null) {
829 result.add(propertyDescriptor.getName());
830 }
831 }
832
833 return result;
834 }
835
836
837
838
839
840
841
842
843
844 private static boolean isGlobalProperty(Class<?> clss, String propertyName) {
845 return AbstractCheck.class.isAssignableFrom(clss)
846 && CHECK_PROPERTIES.contains(propertyName)
847 || AbstractJavadocCheck.class.isAssignableFrom(clss)
848 && JAVADOC_CHECK_PROPERTIES.contains(propertyName)
849 || AbstractFileSetCheck.class.isAssignableFrom(clss)
850 && FILESET_PROPERTIES.contains(propertyName);
851 }
852
853
854
855
856
857
858
859
860 private static boolean isUndocumentedProperty(Class<?> clss, String propertyName) {
861 return UNDOCUMENTED_PROPERTIES.contains(clss.getSimpleName() + DOT + propertyName);
862 }
863
864
865
866
867
868
869
870
871
872 private static Set<String> getNonExplicitProperties(
873 Object instance, Class<?> clss) {
874 final Set<String> result = new TreeSet<>();
875 if (AbstractCheck.class.isAssignableFrom(clss)) {
876 final AbstractCheck check = (AbstractCheck) instance;
877
878 final int[] acceptableTokens = check.getAcceptableTokens();
879 Arrays.sort(acceptableTokens);
880 final int[] defaultTokens = check.getDefaultTokens();
881 Arrays.sort(defaultTokens);
882 final int[] requiredTokens = check.getRequiredTokens();
883 Arrays.sort(requiredTokens);
884
885 if (!Arrays.equals(acceptableTokens, defaultTokens)
886 || !Arrays.equals(acceptableTokens, requiredTokens)) {
887 result.add(TOKENS);
888 }
889 }
890
891 if (AbstractJavadocCheck.class.isAssignableFrom(clss)) {
892 final AbstractJavadocCheck check = (AbstractJavadocCheck) instance;
893 result.add(VIOLATE_EXECUTION_ON_NON_TIGHT_HTML);
894
895 final int[] acceptableJavadocTokens = check.getAcceptableJavadocTokens();
896 Arrays.sort(acceptableJavadocTokens);
897 final int[] defaultJavadocTokens = check.getDefaultJavadocTokens();
898 Arrays.sort(defaultJavadocTokens);
899 final int[] requiredJavadocTokens = check.getRequiredJavadocTokens();
900 Arrays.sort(requiredJavadocTokens);
901
902 if (!Arrays.equals(acceptableJavadocTokens, defaultJavadocTokens)
903 || !Arrays.equals(acceptableJavadocTokens, requiredJavadocTokens)) {
904 result.add(JAVADOC_TOKENS);
905 }
906 }
907
908 if (AbstractFileSetCheck.class.isAssignableFrom(clss)) {
909 result.add(FILE_EXTENSIONS);
910 }
911 return result;
912 }
913
914
915
916
917
918
919
920
921
922
923 public static String getPropertyDescriptionForXdoc(
924 String propertyName, DetailNode javadoc, String moduleName)
925 throws MacroExecutionException {
926 final String description;
927 if (TOKENS.equals(propertyName)) {
928 description = "tokens to check";
929 }
930 else if (JAVADOC_TOKENS.equals(propertyName)) {
931 description = "javadoc tokens to check";
932 }
933 else if (VIOLATE_EXECUTION_ON_NON_TIGHT_HTML.equals(propertyName)) {
934 description = "Control when to print violations if the Javadoc being"
935 + " examined by this check violates the tight html rules defined at"
936 + " <a href=\"" + CHECKSTYLE_ORG_URL
937 + "writingjavadocchecks.html#Tight-HTML_rules\">"
938 + "Tight-HTML Rules</a>.";
939 }
940 else if (FILE_EXTENSIONS.equals(propertyName)) {
941 description = "Specify the file extensions of the files to process.";
942 }
943 else {
944 final String javadocDescription =
945 getDescriptionFromJavadocForXdoc(javadoc, moduleName);
946 final String descriptionString = SETTER_PATTERN.matcher(javadocDescription)
947 .replaceFirst("");
948
949 if (descriptionString.isEmpty()) {
950 description = "";
951 }
952 else {
953 final String firstLetterCapitalized = descriptionString.substring(0, 1)
954 .toUpperCase(Locale.ROOT);
955 description = firstLetterCapitalized + descriptionString.substring(1);
956 }
957 }
958 return description;
959 }
960
961
962
963
964
965
966
967
968
969
970
971 public static String getPropertySinceVersion(String moduleSince,
972 DetailNode propertyJavadoc) {
973 final String sinceVersion;
974
975 final Optional<String> specifiedPropertyVersionInPropertyJavadoc =
976 getPropertyVersionFromItsJavadoc(propertyJavadoc);
977
978 if (specifiedPropertyVersionInPropertyJavadoc.isPresent()) {
979 sinceVersion = specifiedPropertyVersionInPropertyJavadoc.get();
980 }
981 else {
982 String propertySetterSince = null;
983 if (propertyJavadoc != null) {
984 propertySetterSince = getSinceVersionFromJavadoc(propertyJavadoc);
985 }
986
987 if (propertySetterSince != null
988 && (moduleSince == null || moduleSince.isEmpty()
989 || isVersionAtLeast(propertySetterSince, moduleSince))) {
990 sinceVersion = propertySetterSince;
991 }
992 else {
993 sinceVersion = Optional.ofNullable(moduleSince).orElse("");
994 }
995 }
996
997 return sinceVersion;
998 }
999
1000
1001
1002
1003
1004
1005
1006 private static Optional<String> getPropertyVersionFromItsJavadoc(DetailNode propertyJavadoc) {
1007 Optional<String> result = Optional.empty();
1008
1009 if (propertyJavadoc != null) {
1010 final Optional<DetailNode> propertyJavadocTag =
1011 getPropertySinceJavadocTag(propertyJavadoc);
1012
1013 result = propertyJavadocTag
1014 .map(tag -> {
1015 return JavadocUtil.findFirstToken(
1016 tag, JavadocCommentsTokenTypes.DESCRIPTION);
1017 })
1018 .map(description -> {
1019 return JavadocUtil.findFirstToken(
1020 description, JavadocCommentsTokenTypes.TEXT);
1021 })
1022 .map(DetailNode::getText)
1023 .map(String::trim);
1024 }
1025 return result;
1026 }
1027
1028
1029
1030
1031
1032
1033
1034 private static Optional<DetailNode> getPropertySinceJavadocTag(DetailNode javadoc) {
1035 Optional<DetailNode> propertySinceJavadocTag = Optional.empty();
1036 if (javadoc != null) {
1037 DetailNode child = javadoc.getFirstChild();
1038
1039 while (child != null) {
1040 if (child.getType() == JavadocCommentsTokenTypes.JAVADOC_BLOCK_TAG) {
1041 final DetailNode customBlockTag = JavadocUtil.findFirstToken(
1042 child, JavadocCommentsTokenTypes.CUSTOM_BLOCK_TAG);
1043
1044 if (customBlockTag != null
1045 && "propertySince".equals(JavadocUtil.findFirstToken(
1046 customBlockTag,
1047 JavadocCommentsTokenTypes.TAG_NAME).getText())) {
1048 propertySinceJavadocTag = Optional.of(customBlockTag);
1049 break;
1050 }
1051 }
1052 child = child.getNextSibling();
1053 }
1054 }
1055 return propertySinceJavadocTag;
1056 }
1057
1058
1059
1060
1061
1062
1063
1064
1065 public static List<DetailNode> getNodesOfSpecificType(DetailNode[] allNodes, int neededType) {
1066 return Arrays.stream(allNodes)
1067 .filter(child -> child.getType() == neededType)
1068 .toList();
1069 }
1070
1071
1072
1073
1074
1075
1076
1077 private static String getSinceVersionFromJavadoc(DetailNode javadoc) {
1078 String result = null;
1079
1080 if (javadoc != null) {
1081 final DetailNode sinceJavadocTag = getSinceJavadocTag(javadoc);
1082 result = Optional.ofNullable(sinceJavadocTag)
1083 .map(tag -> {
1084 return JavadocUtil.findFirstToken(
1085 tag, JavadocCommentsTokenTypes.DESCRIPTION);
1086 })
1087 .map(description -> {
1088 return JavadocUtil.findFirstToken(
1089 description, JavadocCommentsTokenTypes.TEXT);
1090 })
1091 .map(DetailNode::getText)
1092 .map(String::trim)
1093 .orElse(null);
1094 }
1095 return result;
1096 }
1097
1098
1099
1100
1101
1102
1103
1104 private static DetailNode getSinceJavadocTag(DetailNode javadoc) {
1105 DetailNode javadocTagWithSince = null;
1106
1107 if (javadoc != null) {
1108 DetailNode child = javadoc.getFirstChild();
1109
1110 while (child != null) {
1111 if (child.getType() == JavadocCommentsTokenTypes.JAVADOC_BLOCK_TAG) {
1112 final DetailNode sinceNode = JavadocUtil.findFirstToken(
1113 child, JavadocCommentsTokenTypes.SINCE_BLOCK_TAG);
1114
1115 if (sinceNode != null) {
1116 javadocTagWithSince = sinceNode;
1117 break;
1118 }
1119 }
1120 child = child.getNextSibling();
1121 }
1122 }
1123
1124 return javadocTagWithSince;
1125 }
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135 private static boolean isVersionAtLeast(String actualVersion,
1136 String requiredVersion) {
1137 final Version actualVersionParsed = Version.parse(actualVersion);
1138 final Version requiredVersionParsed = Version.parse(requiredVersion);
1139
1140 return actualVersionParsed.compareTo(requiredVersionParsed) >= 0;
1141 }
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153 public static String getType(Field field, String propertyName,
1154 String moduleName, Object instance)
1155 throws MacroExecutionException {
1156 final Class<?> fieldClass = getFieldClass(field, propertyName, moduleName, instance);
1157 return Optional.ofNullable(field)
1158 .map(nonNullField -> nonNullField.getAnnotation(XdocsPropertyType.class))
1159 .filter(propertyType -> propertyType.value() != PropertyType.TOKEN_ARRAY)
1160 .map(propertyType -> propertyType.value().getDescription())
1161 .orElseGet(fieldClass::getTypeName);
1162 }
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174 public static String getDefaultValue(String propertyName, Field field,
1175 Object classInstance, String moduleName)
1176 throws MacroExecutionException {
1177
1178 final String result;
1179 if (classInstance instanceof PropertyCacheFile) {
1180 result = "null (no cache file)";
1181 }
1182 else {
1183 final Object value = getFieldValue(field, classInstance);
1184 final Class<?> fieldClass = getFieldClass(field, propertyName, moduleName,
1185 classInstance);
1186
1187 final String fieldValue = getFieldDefaultValue(field, fieldClass, value);
1188 result = Optional.ofNullable(fieldValue).orElse(NULL_STR);
1189 }
1190
1191 return result;
1192 }
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203 private static String getFieldDefaultValue(Field field, Class<?> fieldClass, Object value) {
1204 String result = getScalarFieldDefaultValue(fieldClass, value);
1205 if (result == null) {
1206 result = getArrayFieldDefaultValue(field, fieldClass, value);
1207 }
1208 return result;
1209 }
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219 private static String getScalarFieldDefaultValue(Class<?> fieldClass, Object value) {
1220 final String result;
1221 if (fieldClass == boolean.class
1222 || fieldClass == int.class
1223 || fieldClass == URI.class
1224 || fieldClass == String.class) {
1225 result = Optional.ofNullable(value).map(Object::toString).orElse(null);
1226 }
1227 else if (fieldClass == Pattern.class) {
1228 result = getPatternDefaultValue(value);
1229 }
1230 else if (fieldClass.isEnum()) {
1231 result = Optional.ofNullable(value)
1232 .map(object -> object.toString().toLowerCase(Locale.ENGLISH))
1233 .orElse(null);
1234 }
1235 else {
1236 result = null;
1237 }
1238 return result;
1239 }
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250 private static String getArrayFieldDefaultValue(Field field, Class<?> fieldClass,
1251 Object value) {
1252 final String result;
1253
1254 if (fieldClass == int[].class
1255 || ModuleJavadocParsingUtil.isPropertySpecialTokenProp(field)) {
1256 result = getIntArrayPropertyValue(value);
1257 }
1258 else {
1259 result = switch (fieldClass.getSimpleName()) {
1260 case "double[]" -> removeSquareBrackets(
1261 Arrays.toString((double[]) value).replace(".0", ""));
1262 case "String[]" -> getStringArrayPropertyValue(value,
1263 hasPreserveOrderAnnotation(field));
1264 case "Pattern[]" -> getPatternArrayPropertyValue(value);
1265 case "AccessModifierOption[]" -> getAccessModifierDefaultValue(value);
1266 case null, default -> null;
1267 };
1268 }
1269
1270 return result;
1271 }
1272
1273
1274
1275
1276
1277
1278
1279 private static String getPatternDefaultValue(Object value) {
1280 final String result;
1281 if (value == null) {
1282 result = null;
1283 }
1284 else {
1285 result = value.toString()
1286 .replace("\n", "\\n")
1287 .replace("\t", "\\t")
1288 .replace("\r", "\\r")
1289 .replace("\f", "\\f");
1290 }
1291 return result;
1292 }
1293
1294
1295
1296
1297
1298
1299
1300 private static String getAccessModifierDefaultValue(Object value) {
1301 final String result;
1302 if (value != null && Array.getLength(value) > 0) {
1303 result = removeSquareBrackets(Arrays.toString((Object[]) value));
1304 }
1305 else {
1306 result = "";
1307 }
1308 return result;
1309 }
1310
1311
1312
1313
1314
1315
1316
1317 private static boolean hasPreserveOrderAnnotation(Field field) {
1318 return field != null && field.isAnnotationPresent(PreserveOrder.class);
1319 }
1320
1321
1322
1323
1324
1325
1326
1327 private static String getPatternArrayPropertyValue(Object fieldValue) {
1328 Object value = fieldValue;
1329 if (value instanceof Collection<?> collection) {
1330 value = collection.stream()
1331 .map(Pattern.class::cast)
1332 .toArray(Pattern[]::new);
1333 }
1334
1335 String result = "";
1336 if (value != null && Array.getLength(value) > 0) {
1337 result = removeSquareBrackets(
1338 Arrays.stream((Pattern[]) value)
1339 .map(Pattern::pattern)
1340 .collect(Collectors.joining(COMMA_SPACE)));
1341 }
1342
1343 return result;
1344 }
1345
1346
1347
1348
1349
1350
1351
1352 private static String removeSquareBrackets(String value) {
1353 return value
1354 .replace("[", "")
1355 .replace("]", "");
1356 }
1357
1358
1359
1360
1361
1362
1363
1364
1365 private static String getStringArrayPropertyValue(Object value, boolean preserveOrder) {
1366 final String result;
1367 if (value == null) {
1368 result = "";
1369 }
1370 else {
1371 try (Stream<?> valuesStream = getValuesStream(value)) {
1372 final List<String> stringList = valuesStream
1373 .map(String.class::cast)
1374 .collect(Collectors.toCollection(ArrayList<String>::new));
1375
1376 if (preserveOrder) {
1377 result = String.join(COMMA_SPACE, stringList);
1378 }
1379 else {
1380 result = stringList.stream()
1381 .sorted()
1382 .collect(Collectors.joining(COMMA_SPACE));
1383 }
1384 }
1385 }
1386 return result;
1387 }
1388
1389
1390
1391
1392
1393
1394
1395 private static Stream<?> getValuesStream(Object value) {
1396 final Stream<?> valuesStream;
1397 if (value instanceof Collection<?> collection) {
1398 valuesStream = collection.stream();
1399 }
1400 else {
1401 final Object[] array = (Object[]) value;
1402 valuesStream = Arrays.stream(array);
1403 }
1404 return valuesStream;
1405 }
1406
1407
1408
1409
1410
1411
1412
1413 private static String getIntArrayPropertyValue(Object value) {
1414 try (IntStream stream = getIntStream(value)) {
1415 return stream
1416 .mapToObj(TokenUtil::getTokenName)
1417 .sorted()
1418 .collect(Collectors.joining(COMMA_SPACE));
1419 }
1420 }
1421
1422
1423
1424
1425
1426
1427
1428
1429 private static IntStream getIntStream(Object value) {
1430 return switch (value) {
1431 case null -> throw new IllegalArgumentException("value is null");
1432 case Collection<?> collection -> collection.stream()
1433 .mapToInt(Integer.class::cast);
1434 case BitSet set -> set.stream();
1435 default -> Arrays.stream((int[]) value);
1436 };
1437 }
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451 public static Class<?> getFieldClass(Field field, String propertyName,
1452 String moduleName, Object instance)
1453 throws MacroExecutionException {
1454 Class<?> result = null;
1455
1456 if (PROPERTIES_ALLOWED_GET_TYPES_FROM_METHOD
1457 .contains(moduleName + DOT + propertyName)) {
1458 result = getPropertyClass(propertyName, instance);
1459 }
1460 if (ModuleJavadocParsingUtil.isPropertySpecialTokenProp(field)) {
1461 result = String[].class;
1462 }
1463 if (field != null && result == null) {
1464 result = field.getType();
1465 }
1466
1467 if (result == null) {
1468 throw new MacroExecutionException(
1469 "Could not find field " + propertyName + " in class " + moduleName);
1470 }
1471
1472 if (field != null && (result == List.class || result == Set.class)) {
1473 result = getParameterizedTypeClass(field);
1474 }
1475 else if (result == BitSet.class) {
1476 result = int[].class;
1477 }
1478
1479 return result;
1480 }
1481
1482
1483
1484
1485
1486
1487
1488
1489 private static Class<?> getParameterizedTypeClass(Field field) throws MacroExecutionException {
1490 final ParameterizedType type = (ParameterizedType) field.getGenericType();
1491 final Class<?> parameterClass = (Class<?>) type.getActualTypeArguments()[0];
1492 final Class<?> result;
1493
1494 if (parameterClass == Integer.class) {
1495 result = int[].class;
1496 }
1497 else if (parameterClass == String.class) {
1498 result = String[].class;
1499 }
1500 else if (parameterClass == Pattern.class) {
1501 result = Pattern[].class;
1502 }
1503 else {
1504 final String message = "Unknown parameterized type: "
1505 + parameterClass.getSimpleName();
1506 throw new MacroExecutionException(message);
1507 }
1508 return result;
1509 }
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520 public static Class<?> getPropertyClass(String propertyName, Object instance)
1521 throws MacroExecutionException {
1522 final Class<?> result;
1523 try {
1524 final PropertyDescriptor descriptor = PropertyUtils.getPropertyDescriptor(instance,
1525 propertyName);
1526 result = descriptor.getPropertyType();
1527 }
1528 catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException exc) {
1529 throw new MacroExecutionException("Failed to retrieve property type", exc);
1530 }
1531 return result;
1532 }
1533
1534
1535
1536
1537
1538
1539
1540
1541 public static List<Integer> getDifference(int[] tokens, int... subtractions) {
1542 final Set<Integer> subtractionsSet = Arrays.stream(subtractions)
1543 .boxed()
1544 .collect(Collectors.toUnmodifiableSet());
1545 return Arrays.stream(tokens)
1546 .boxed()
1547 .filter(token -> !subtractionsSet.contains(token))
1548 .toList();
1549 }
1550
1551
1552
1553
1554
1555
1556
1557
1558 public static Field getField(Class<?> fieldClass, String propertyName) {
1559 Field result = null;
1560 Class<?> currentClass = fieldClass;
1561
1562 while (currentClass != Object.class) {
1563 try {
1564 result = currentClass.getDeclaredField(propertyName);
1565 result.trySetAccessible();
1566 break;
1567 }
1568 catch (NoSuchFieldException ignored) {
1569 currentClass = currentClass.getSuperclass();
1570 }
1571 }
1572
1573 return result;
1574 }
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584 public static String getLinkToDocument(String moduleName, String document)
1585 throws MacroExecutionException {
1586 final Path templatePath = getTemplatePath(FINAL_CHECK.matcher(moduleName).replaceAll(""));
1587 if (templatePath == null) {
1588 throw new MacroExecutionException(
1589 String.format(Locale.ROOT,
1590 "Could not find template for %s", moduleName));
1591 }
1592 final Path templatePathParent = templatePath.getParent();
1593 if (templatePathParent == null) {
1594 throw new MacroExecutionException("Failed to get parent path for " + templatePath);
1595 }
1596 return templatePathParent
1597 .relativize(Path.of(SRC, "site/xdoc", document))
1598 .toString()
1599 .replace(".xml", ".html")
1600 .replace('\\', '/');
1601 }
1602
1603
1604
1605
1606
1607
1608
1609
1610 public static List<Path> getTemplatesThatContainPropertiesMacro()
1611 throws CheckstyleException, MacroExecutionException {
1612 final List<Path> result = new ArrayList<>();
1613 final Set<Path> templatesPaths = getXdocsTemplatesFilePaths();
1614 for (Path templatePath: templatesPaths) {
1615 final String content = getFileContents(templatePath);
1616 final String propertiesMacroDefinition = "<macro name=\"properties\"";
1617 if (content.contains(propertiesMacroDefinition)) {
1618 result.add(templatePath);
1619 }
1620 }
1621 return result;
1622 }
1623
1624
1625
1626
1627
1628
1629
1630
1631 private static String getFileContents(Path pathToFile) throws CheckstyleException {
1632 final String content;
1633 try {
1634 content = Files.readString(pathToFile);
1635 }
1636 catch (IOException ioException) {
1637 final String message = String.format(Locale.ROOT, "Failed to read file: %s",
1638 pathToFile);
1639 throw new CheckstyleException(message, ioException);
1640 }
1641 return content;
1642 }
1643
1644
1645
1646
1647
1648
1649
1650 public static String getModuleName(File file) {
1651 final String fullFileName = file.getName();
1652 return CommonUtil.getFileNameWithoutExtension(fullFileName);
1653 }
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668 private static String getDescriptionFromJavadocForXdoc(DetailNode javadoc, String moduleName)
1669 throws MacroExecutionException {
1670 final List<DetailNode> descriptionNodes = getFirstJavadocParagraphNodes(javadoc);
1671 final StringBuilder description = new StringBuilder(128);
1672
1673 if (!descriptionNodes.isEmpty()) {
1674 DetailNode node = descriptionNodes.getFirst();
1675 final DetailNode endNode = descriptionNodes.getLast();
1676
1677 final DescriptionTraversalState state = new DescriptionTraversalState();
1678
1679 while (node != null) {
1680 processDescriptionNode(node, description, state, moduleName);
1681
1682 DetailNode toVisit = node.getFirstChild();
1683 while (node != endNode && toVisit == null) {
1684 toVisit = node.getNextSibling();
1685 node = node.getParent();
1686 }
1687
1688 node = toVisit;
1689 }
1690 }
1691
1692 return description.toString().trim();
1693 }
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706 private static void processDescriptionNode(DetailNode node,
1707 StringBuilder description,
1708 DescriptionTraversalState state,
1709 String moduleName)
1710 throws MacroExecutionException {
1711 if (node.getType() == JavadocCommentsTokenTypes.TAG_ATTR_NAME
1712 && "href".equals(node.getText())) {
1713 state.inHrefAttribute = true;
1714 }
1715 if (state.inHrefAttribute && node.getType()
1716 == JavadocCommentsTokenTypes.ATTRIBUTE_VALUE) {
1717 processHrefAttributeValue(node, description, state, moduleName);
1718 }
1719 else {
1720 processNonHrefNode(node, description, state);
1721 }
1722 }
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733 private static void processHrefAttributeValue(DetailNode node,
1734 StringBuilder description,
1735 DescriptionTraversalState state,
1736 String moduleName)
1737 throws MacroExecutionException {
1738 final String href = node.getText();
1739 if (href.contains(CHECKSTYLE_ORG_URL)) {
1740 final String internalHref = href.replace(CHECKSTYLE_ORG_URL, "");
1741 final String path = internalHref.substring(1, internalHref.length() - 1);
1742 final String relativeHref = getLinkToDocument(moduleName, path);
1743
1744 description.append('\"').append(relativeHref).append('\"');
1745 }
1746 else {
1747 description.append(href);
1748 }
1749 state.inHrefAttribute = false;
1750 }
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760 private static void processNonHrefNode(DetailNode node,
1761 StringBuilder description,
1762 DescriptionTraversalState state) {
1763 processHtmlElementTracking(node, description, state);
1764 processTextContent(node, description, state);
1765 processInlineTagTracking(node, description, state);
1766 }
1767
1768
1769
1770
1771
1772
1773
1774
1775 private static void processHtmlElementTracking(DetailNode node,
1776 StringBuilder description,
1777 DescriptionTraversalState state) {
1778 if (node.getType() == JavadocCommentsTokenTypes.HTML_ELEMENT) {
1779 state.inHtmlElement = true;
1780 }
1781 if (node.getType() == JavadocCommentsTokenTypes.TAG_CLOSE
1782 && node.getParent().getType()
1783 == JavadocCommentsTokenTypes.HTML_TAG_END) {
1784 description.append(node.getText());
1785 state.inHtmlElement = false;
1786 }
1787 }
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797 private static void processTextContent(DetailNode node,
1798 StringBuilder description,
1799 DescriptionTraversalState state) {
1800 if (isTextContent(node, state.inHtmlElement)) {
1801 if (state.inCodeLiteral || state.inLiteralTag) {
1802 description.append(node.getText().trim()
1803 .replace("&", "&")
1804 .replace("<", "<")
1805 .replace(">", ">"));
1806 }
1807 else {
1808 description.append(node.getText());
1809 }
1810 }
1811 }
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821 private static void processInlineTagTracking(DetailNode node,
1822 StringBuilder description,
1823 DescriptionTraversalState state) {
1824 if (node.getType() == JavadocCommentsTokenTypes.TAG_NAME
1825 && node.getParent().getType()
1826 == JavadocCommentsTokenTypes.CODE_INLINE_TAG) {
1827 state.inCodeLiteral = true;
1828 description.append("<code>");
1829 }
1830 if (state.inCodeLiteral
1831 && node.getType() == JavadocCommentsTokenTypes.JAVADOC_INLINE_TAG_END) {
1832 state.inCodeLiteral = false;
1833 description.append("</code>");
1834 }
1835 if (node.getType() == JavadocCommentsTokenTypes.TAG_NAME
1836 && node.getParent().getType()
1837 == JavadocCommentsTokenTypes.LITERAL_INLINE_TAG) {
1838 state.inLiteralTag = true;
1839 }
1840 if (state.inLiteralTag
1841 && node.getType() == JavadocCommentsTokenTypes.JAVADOC_INLINE_TAG_END) {
1842 state.inLiteralTag = false;
1843 }
1844 }
1845
1846
1847
1848
1849
1850
1851
1852
1853 private static boolean isTextContent(DetailNode node, boolean isInHtmlElement) {
1854 return node.getType() == JavadocCommentsTokenTypes.TEXT
1855 || isInHtmlElement && node.getFirstChild() == null
1856 && node.getType() != JavadocCommentsTokenTypes.LEADING_ASTERISK;
1857 }
1858
1859
1860
1861
1862
1863
1864
1865 public static String getFirstParagraphFromJavadoc(DetailNode javadoc) {
1866 final String result;
1867 final List<DetailNode> firstParagraphNodes = getFirstJavadocParagraphNodes(javadoc);
1868 if (firstParagraphNodes.isEmpty()) {
1869 result = "";
1870 }
1871 else {
1872 final DetailNode startNode = firstParagraphNodes.getFirst();
1873 final DetailNode endNode = firstParagraphNodes.getLast();
1874 result = JavadocMetadataScraperUtil.constructSubTreeText(startNode, endNode);
1875 }
1876 return result;
1877 }
1878
1879
1880
1881
1882
1883
1884
1885 public static List<DetailNode> getFirstJavadocParagraphNodes(DetailNode javadoc) {
1886 final List<DetailNode> firstParagraphNodes = new ArrayList<>();
1887
1888 if (javadoc != null) {
1889 for (DetailNode child = javadoc.getFirstChild();
1890 child != null; child = child.getNextSibling()) {
1891 if (isEndOfFirstJavadocParagraph(child)) {
1892 break;
1893 }
1894 firstParagraphNodes.add(child);
1895 }
1896 }
1897 return firstParagraphNodes;
1898 }
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909 public static boolean isEndOfFirstJavadocParagraph(DetailNode child) {
1910 final DetailNode nextSibling = child.getNextSibling();
1911 boolean result = false;
1912 if (nextSibling != null) {
1913 final DetailNode secondNextSibling = nextSibling.getNextSibling();
1914 if (secondNextSibling != null) {
1915 final DetailNode thirdNextSibling = secondNextSibling.getNextSibling();
1916 if (thirdNextSibling != null) {
1917 result = child.getType() == JavadocCommentsTokenTypes.NEWLINE
1918 && nextSibling.getType()
1919 == JavadocCommentsTokenTypes.LEADING_ASTERISK
1920 && secondNextSibling.getType()
1921 == JavadocCommentsTokenTypes.NEWLINE
1922 && thirdNextSibling.getType()
1923 == JavadocCommentsTokenTypes.LEADING_ASTERISK;
1924 }
1925 }
1926 }
1927 return result;
1928 }
1929
1930
1931
1932
1933
1934
1935
1936 public static String simplifyTypeName(String fullTypeName) {
1937 final int simplifiedStartIndex;
1938
1939 if (fullTypeName.contains("$")) {
1940 simplifiedStartIndex = fullTypeName.lastIndexOf('$') + 1;
1941 }
1942 else {
1943 simplifiedStartIndex = fullTypeName.lastIndexOf('.') + 1;
1944 }
1945
1946 return fullTypeName.substring(simplifiedStartIndex);
1947 }
1948
1949
1950
1951
1952
1953
1954
1955 private static final class DescriptionTraversalState {
1956
1957 private boolean inCodeLiteral;
1958
1959 private boolean inLiteralTag;
1960
1961 private boolean inHtmlElement;
1962
1963 private boolean inHrefAttribute;
1964 }
1965 }