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.internal.utils;
21
22 import java.io.IOException;
23 import java.nio.file.Files;
24 import java.nio.file.Path;
25 import java.util.HashMap;
26 import java.util.HashSet;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Set;
30 import java.util.regex.Matcher;
31 import java.util.regex.Pattern;
32 import java.util.stream.Collectors;
33 import java.util.stream.Stream;
34
35 import javax.xml.parsers.DocumentBuilder;
36 import javax.xml.parsers.DocumentBuilderFactory;
37 import javax.xml.parsers.ParserConfigurationException;
38
39 import org.w3c.dom.Document;
40 import org.w3c.dom.Element;
41 import org.w3c.dom.Node;
42 import org.w3c.dom.NodeList;
43 import org.xml.sax.SAXException;
44
45
46
47
48
49
50
51 public final class XdocUtil {
52
53 public static final String DIRECTORY_PATH = "src/site/xdoc";
54
55 private XdocUtil() {
56 }
57
58
59
60
61
62
63
64 public static Set<Path> getXdocsFilePaths() throws IOException {
65 final Path directory = Path.of(DIRECTORY_PATH);
66 try (Stream<Path> stream = Files.find(directory, Integer.MAX_VALUE,
67 (path, attr) -> {
68 return attr.isRegularFile()
69 && (path.toString().endsWith(".xml")
70 || path.toString().endsWith(".xml.vm"));
71 })) {
72 return stream.collect(Collectors.toUnmodifiableSet());
73 }
74 }
75
76
77
78
79
80
81
82
83
84 public static Set<Path> getXdocsTemplatesFilePaths() throws IOException {
85 final Path directory = Path.of(DIRECTORY_PATH);
86 try (Stream<Path> stream = Files.find(directory, Integer.MAX_VALUE,
87 (path, attr) -> {
88 return attr.isRegularFile()
89 && path.toString().endsWith(".xml.template");
90 })) {
91 return stream.collect(Collectors.toUnmodifiableSet());
92 }
93 }
94
95
96
97
98
99
100
101 public static Set<Path> getXdocsConfigFilePaths(Set<Path> files) {
102 final Set<Path> xdocs = new HashSet<>();
103 for (Path entry : files) {
104 final String fileName = entry.getFileName().toString();
105 if (!entry.getParent().toString().matches("src[\\\\/]site[\\\\/]xdocs")
106 && fileName.endsWith(".xml")) {
107 xdocs.add(entry);
108 }
109 }
110 return xdocs;
111 }
112
113
114
115
116
117
118
119 public static Set<Path> getXdocsStyleFilePaths(Set<Path> files) {
120 final Set<Path> xdocs = new HashSet<>();
121 for (Path entry : files) {
122 final String fileName = entry.getFileName().toString();
123 if (fileName.endsWith("_style.xml")) {
124 xdocs.add(entry);
125 }
126 }
127 return xdocs;
128 }
129
130
131
132
133
134
135
136
137
138
139 public static Set<String> getModulesNamesWhichHaveXdoc() throws Exception {
140 final DocumentBuilderFactory factory = DocumentBuilderFactory
141 .newInstance();
142
143
144
145 factory.setNamespaceAware(false);
146 factory.setValidating(false);
147 factory.setFeature("http://xml.org/sax/features/namespaces", false);
148 factory.setFeature("http://xml.org/sax/features/validation", false);
149 factory.setFeature(
150 "http://apache.org/xml/features/nonvalidating/load-dtd-grammar",
151 false);
152 factory.setFeature(
153 "http://apache.org/xml/features/nonvalidating/load-external-dtd",
154 false);
155
156 final Set<String> modulesNamesWhichHaveXdoc = new HashSet<>();
157
158 for (Path path : getXdocsConfigFilePaths(getXdocsFilePaths())) {
159 final DocumentBuilder builder = factory.newDocumentBuilder();
160 final Document document = builder.parse(path.toFile());
161
162
163
164
165
166 document.getDocumentElement().normalize();
167
168 final NodeList nodeList = document.getElementsByTagName("section");
169
170 for (int i = 0; i < nodeList.getLength(); i++) {
171 final Node currentNode = nodeList.item(i);
172 if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
173 final Element module = (Element) currentNode;
174 final String moduleName = module.getAttribute("name");
175 if (!"Content".equals(moduleName)
176 && !"Overview".equals(moduleName)) {
177 modulesNamesWhichHaveXdoc.add(moduleName);
178 }
179 }
180 }
181 }
182 return modulesNamesWhichHaveXdoc;
183 }
184
185
186
187
188
189
190
191 public static Map<String, Set<String>> extractUsedPropertiesFromXdocsExamples()
192 throws IOException {
193 final List<Path> roots = List.of(
194 Path.of("src/xdocs-examples/resources/com/puppycrawl/tools/checkstyle/checks"),
195 Path.of("src/xdocs-examples/resources-noncompilable/"
196 + "com/puppycrawl/tools/checkstyle/checks")
197 );
198
199 final Map<String, Set<String>> checkToProperties = new HashMap<>();
200
201 for (Path root : roots) {
202 if (Files.exists(root)) {
203 try (Stream<Path> paths = Files.walk(root)) {
204 paths.filter(path -> path.toString().endsWith(".java"))
205 .forEach(path -> processXdocExampleFile(path, checkToProperties));
206 }
207 }
208 }
209
210 return checkToProperties;
211 }
212
213 private static void processXdocExampleFile(
214 Path file, Map<String, Set<String>> checkToProperties) {
215 try {
216 final String content = Files.readString(file);
217 final Matcher xmlBlockMatcher =
218 Pattern.compile("/\\*xml(.*?)\\*/", Pattern.DOTALL).matcher(content);
219
220 if (xmlBlockMatcher.find()) {
221 final Map.Entry<String, Set<String>> entry =
222 parseConfigBlock(xmlBlockMatcher.group(1));
223
224 if (entry != null) {
225 checkToProperties
226 .computeIfAbsent(entry.getKey(), key -> new HashSet<>())
227 .addAll(entry.getValue());
228 }
229 }
230 }
231 catch (IOException ioe) {
232 throw new IllegalStateException("Error reading file: " + file, ioe);
233 }
234 }
235
236 private static Map.Entry<String, Set<String>> parseConfigBlock(String configBlock) {
237 final Matcher moduleMatcher =
238 Pattern.compile("<module name=\"([^\"]+)\"").matcher(configBlock);
239 String lastModule = null;
240 while (moduleMatcher.find()) {
241 lastModule = moduleMatcher.group(1);
242 }
243
244 final Matcher propMatcher =
245 Pattern.compile("<property name=\"([^\"]+)\"").matcher(configBlock);
246 final Set<String> props = new HashSet<>();
247 while (propMatcher.find()) {
248 props.add(propMatcher.group(1));
249 }
250
251 Map.Entry<String, Set<String>> result = null;
252 if (lastModule != null && !props.isEmpty()) {
253 final String checkClassName;
254 if (lastModule.endsWith("Check")) {
255 checkClassName = lastModule;
256 }
257 else {
258 checkClassName = lastModule + "Check";
259 }
260 result = Map.entry(checkClassName, props);
261 }
262
263 return result;
264 }
265 }