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