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.Locale;
29 import java.util.Map;
30 import java.util.Set;
31 import java.util.regex.Matcher;
32 import java.util.regex.Pattern;
33 import java.util.stream.Collectors;
34 import java.util.stream.Stream;
35
36 import javax.xml.parsers.DocumentBuilder;
37 import javax.xml.parsers.DocumentBuilderFactory;
38 import javax.xml.parsers.ParserConfigurationException;
39
40 import org.w3c.dom.Document;
41 import org.w3c.dom.Element;
42 import org.w3c.dom.Node;
43 import org.w3c.dom.NodeList;
44 import org.xml.sax.SAXException;
45
46
47
48
49
50
51
52 public final class XdocUtil {
53
54 public static final String DIRECTORY_PATH = "src/site/xdoc";
55
56 private XdocUtil() {
57 }
58
59
60
61
62
63
64
65 public static Set<Path> getXdocsFilePaths() throws IOException {
66 final Path directory = Path.of(DIRECTORY_PATH);
67 try (Stream<Path> stream = Files.find(directory, Integer.MAX_VALUE,
68 (path, attr) -> {
69 return attr.isRegularFile()
70 && (path.toString().endsWith(".xml")
71 || path.toString().endsWith(".xml.vm"));
72 })) {
73 return stream.collect(Collectors.toUnmodifiableSet());
74 }
75 }
76
77
78
79
80
81
82
83 public static Set<Path> getXdocsTemplatesFilePaths() throws IOException {
84 final Path directory = Path.of(DIRECTORY_PATH);
85 try (Stream<Path> stream = Files.find(directory, Integer.MAX_VALUE,
86 (path, attr) -> {
87 return attr.isRegularFile()
88 && path.toString().endsWith(".xml.template");
89 })) {
90 return stream.collect(Collectors.toUnmodifiableSet());
91 }
92 }
93
94
95
96
97
98
99
100 public static Set<String> getDocumentedProperties(String moduleName) {
101 String pageName = moduleName;
102 if (pageName.endsWith("Check")) {
103 pageName = pageName.substring(0, pageName.length() - "Check".length());
104 }
105 final String fileName = pageName.toLowerCase(Locale.ROOT) + ".xml";
106 final Set<String> result = new HashSet<>();
107
108 try {
109 Path xdocPath = null;
110 for (Path path : getXdocsFilePaths()) {
111 if (path.getFileName().toString().equals(fileName)) {
112 xdocPath = path;
113 break;
114 }
115 }
116 if (xdocPath == null) {
117 throw new IllegalStateException("Generated xdoc does not exist: " + fileName);
118 }
119 final String content = Files.readString(xdocPath);
120 final Document document = XmlUtil.getRawXml(fileName, content, content);
121 final NodeList subsections = document.getElementsByTagName("subsection");
122 for (int index = 0; index < subsections.getLength(); index++) {
123 final Element subsection = (Element) subsections.item(index);
124 if ("Properties".equals(subsection.getAttribute("name"))) {
125 final NodeList rows = subsection.getElementsByTagName("tr");
126 for (int rowIndex = 1; rowIndex < rows.getLength(); rowIndex++) {
127 final NodeList columns = ((Element) rows.item(rowIndex))
128 .getElementsByTagName("td");
129 if (columns.getLength() > 0) {
130 result.add(columns.item(0).getTextContent().trim());
131 }
132 }
133 break;
134 }
135 }
136 }
137 catch (IOException | ParserConfigurationException exception) {
138 throw new IllegalStateException("Failed to read generated xdoc: " + fileName,
139 exception);
140 }
141 return Set.copyOf(result);
142 }
143
144
145
146
147
148
149
150 public static Set<Path> getXdocsConfigFilePaths(Set<Path> files) {
151 final Set<Path> xdocs = new HashSet<>();
152 for (Path entry : files) {
153 final String fileName = entry.getFileName().toString();
154 if (!entry.getParent().toString().matches("src[\\\\/]site[\\\\/]xdocs")
155 && fileName.endsWith(".xml")) {
156 xdocs.add(entry);
157 }
158 }
159 return xdocs;
160 }
161
162
163
164
165
166
167
168 public static Set<Path> getXdocsStyleFilePaths(Set<Path> files) {
169 final Set<Path> xdocs = new HashSet<>();
170 for (Path entry : files) {
171 final String fileName = entry.getFileName().toString();
172 if (fileName.endsWith("_style.xml")) {
173 xdocs.add(entry);
174 }
175 }
176 return xdocs;
177 }
178
179
180
181
182
183
184
185
186
187
188 public static Set<String> getModulesNamesWhichHaveXdoc() throws Exception {
189 final DocumentBuilderFactory factory = DocumentBuilderFactory
190 .newInstance();
191
192
193
194 factory.setNamespaceAware(false);
195 factory.setValidating(false);
196 factory.setFeature("http://xml.org/sax/features/namespaces", false);
197 factory.setFeature("http://xml.org/sax/features/validation", false);
198 factory.setFeature(
199 "http://apache.org/xml/features/nonvalidating/load-dtd-grammar",
200 false);
201 factory.setFeature(
202 "http://apache.org/xml/features/nonvalidating/load-external-dtd",
203 false);
204
205 final Set<String> modulesNamesWhichHaveXdoc = new HashSet<>();
206
207 for (Path path : getXdocsConfigFilePaths(getXdocsFilePaths())) {
208 final DocumentBuilder builder = factory.newDocumentBuilder();
209 final Document document = builder.parse(path.toFile());
210
211
212
213
214
215 document.getDocumentElement().normalize();
216
217 final NodeList nodeList = document.getElementsByTagName("section");
218
219 for (int i = 0; i < nodeList.getLength(); i++) {
220 final Node currentNode = nodeList.item(i);
221 if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
222 final Element module = (Element) currentNode;
223 final String moduleName = module.getAttribute("name");
224 if (!"Content".equals(moduleName)
225 && !"Overview".equals(moduleName)) {
226 modulesNamesWhichHaveXdoc.add(moduleName);
227 }
228 }
229 }
230 }
231 return modulesNamesWhichHaveXdoc;
232 }
233
234
235
236
237
238
239
240 public static Map<String, Set<String>> extractUsedPropertiesFromXdocsExamples()
241 throws IOException {
242 final List<Path> roots = List.of(
243 Path.of("src/xdocs-examples/resources/com/puppycrawl/tools/checkstyle/checks"),
244 Path.of("src/xdocs-examples/resources-noncompilable/"
245 + "com/puppycrawl/tools/checkstyle/checks")
246 );
247
248 final Map<String, Set<String>> checkToProperties = new HashMap<>();
249
250 for (Path root : roots) {
251 if (Files.exists(root)) {
252 try (Stream<Path> paths = Files.walk(root)) {
253 paths.filter(path -> path.toString().endsWith(".java"))
254 .forEach(path -> processXdocExampleFile(path, checkToProperties));
255 }
256 }
257 }
258
259 return checkToProperties;
260 }
261
262 private static void processXdocExampleFile(
263 Path file, Map<String, Set<String>> checkToProperties) {
264 try {
265 final String content = Files.readString(file);
266 final Matcher xmlBlockMatcher =
267 Pattern.compile("/\\*xml(.*?)\\*/", Pattern.DOTALL).matcher(content);
268
269 if (xmlBlockMatcher.find()) {
270 final Map.Entry<String, Set<String>> entry =
271 parseConfigBlock(xmlBlockMatcher.group(1));
272
273 if (entry != null) {
274 final String checkClassKey = entry.getKey();
275 final Set<String> props = entry.getValue();
276 checkToProperties
277 .computeIfAbsent(checkClassKey, key -> new HashSet<>())
278 .addAll(props);
279
280
281 if (checkClassKey.endsWith("Check")) {
282 final String moduleNameKey = checkClassKey.substring(
283 0, checkClassKey.length() - "Check".length());
284 checkToProperties
285 .computeIfAbsent(moduleNameKey, key -> new HashSet<>())
286 .addAll(props);
287 }
288 }
289 }
290 }
291 catch (IOException ioe) {
292 throw new IllegalStateException("Error reading file: " + file, ioe);
293 }
294 }
295
296 private static Map.Entry<String, Set<String>> parseConfigBlock(String configBlock) {
297 final Matcher propMatcher =
298 Pattern.compile("<property name=\"([^\"]+)\"").matcher(configBlock);
299 final Set<String> props = new HashSet<>();
300 int firstPropStart = -1;
301 while (propMatcher.find()) {
302 if (firstPropStart == -1) {
303 firstPropStart = propMatcher.start();
304 }
305 props.add(propMatcher.group(1));
306 }
307
308 Map.Entry<String, Set<String>> result = null;
309 if (!props.isEmpty()) {
310
311
312
313 final Matcher moduleMatcher =
314 Pattern.compile("<module name=\"([^\"]+)\"")
315 .matcher(configBlock.substring(0, firstPropStart));
316 String lastModule = null;
317 while (moduleMatcher.find()) {
318 lastModule = moduleMatcher.group(1);
319 }
320
321 if (lastModule != null) {
322 final String checkClassName;
323 if (lastModule.endsWith("Check")) {
324 checkClassName = lastModule;
325 }
326 else {
327 checkClassName = lastModule + "Check";
328 }
329 result = Map.entry(checkClassName, props);
330 }
331 }
332
333 return result;
334 }
335
336 }