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;
21
22 import java.io.IOException;
23 import java.util.ArrayDeque;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.Collection;
27 import java.util.Deque;
28 import java.util.Iterator;
29 import java.util.List;
30 import java.util.Locale;
31 import java.util.Map;
32 import java.util.Optional;
33
34 import javax.xml.parsers.ParserConfigurationException;
35
36 import org.xml.sax.Attributes;
37 import org.xml.sax.InputSource;
38 import org.xml.sax.SAXException;
39 import org.xml.sax.SAXParseException;
40
41 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
42 import com.puppycrawl.tools.checkstyle.api.Configuration;
43 import com.puppycrawl.tools.checkstyle.api.SeverityLevel;
44 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
45
46
47
48
49
50 @SuppressWarnings("UnrecognisedJavadocTag")
51 public final class ConfigurationLoader {
52
53
54
55
56 public enum IgnoredModulesOptions {
57
58
59
60
61 OMIT,
62
63
64
65
66 EXECUTE,
67
68 }
69
70
71 public static final String DTD_PUBLIC_CS_ID_1_3 =
72 "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN";
73
74
75 public static final String DTD_CONFIGURATION_NAME_1_3 =
76 "com/puppycrawl/tools/checkstyle/configuration_1_3.dtd";
77
78
79 private static final String SAX_PARSE_EXCEPTION_FORMAT = "%s - %s:%s:%s";
80
81
82 private static final String DTD_PUBLIC_ID_1_0 =
83 "-//Puppy Crawl//DTD Check Configuration 1.0//EN";
84
85
86 private static final String DTD_PUBLIC_CS_ID_1_0 =
87 "-//Checkstyle//DTD Checkstyle Configuration 1.0//EN";
88
89
90 private static final String DTD_CONFIGURATION_NAME_1_0 =
91 "com/puppycrawl/tools/checkstyle/configuration_1_0.dtd";
92
93
94 private static final String DTD_PUBLIC_ID_1_1 =
95 "-//Puppy Crawl//DTD Check Configuration 1.1//EN";
96
97
98 private static final String DTD_PUBLIC_CS_ID_1_1 =
99 "-//Checkstyle//DTD Checkstyle Configuration 1.1//EN";
100
101
102 private static final String DTD_CONFIGURATION_NAME_1_1 =
103 "com/puppycrawl/tools/checkstyle/configuration_1_1.dtd";
104
105
106 private static final String DTD_PUBLIC_ID_1_2 =
107 "-//Puppy Crawl//DTD Check Configuration 1.2//EN";
108
109
110 private static final String DTD_PUBLIC_CS_ID_1_2 =
111 "-//Checkstyle//DTD Checkstyle Configuration 1.2//EN";
112
113
114 private static final String DTD_CONFIGURATION_NAME_1_2 =
115 "com/puppycrawl/tools/checkstyle/configuration_1_2.dtd";
116
117
118 private static final String DTD_PUBLIC_ID_1_3 =
119 "-//Puppy Crawl//DTD Check Configuration 1.3//EN";
120
121
122 private static final String UNABLE_TO_PARSE_EXCEPTION_PREFIX = "unable to parse"
123 + " configuration stream";
124
125
126 private static final char DOLLAR_SIGN = '$';
127
128 private static final String DOLLAR_SIGN_STRING = String.valueOf(DOLLAR_SIGN);
129
130
131 private static final Map<String, String> ID_TO_RESOURCE_NAME_MAP = Map.ofEntries(
132 Map.entry(DTD_PUBLIC_ID_1_0, DTD_CONFIGURATION_NAME_1_0),
133 Map.entry(DTD_PUBLIC_ID_1_1, DTD_CONFIGURATION_NAME_1_1),
134 Map.entry(DTD_PUBLIC_ID_1_2, DTD_CONFIGURATION_NAME_1_2),
135 Map.entry(DTD_PUBLIC_ID_1_3, DTD_CONFIGURATION_NAME_1_3),
136 Map.entry(DTD_PUBLIC_CS_ID_1_0, DTD_CONFIGURATION_NAME_1_0),
137 Map.entry(DTD_PUBLIC_CS_ID_1_1, DTD_CONFIGURATION_NAME_1_1),
138 Map.entry(DTD_PUBLIC_CS_ID_1_2, DTD_CONFIGURATION_NAME_1_2),
139 Map.entry(DTD_PUBLIC_CS_ID_1_3, DTD_CONFIGURATION_NAME_1_3)
140 );
141
142
143 private final InternalLoader saxHandler;
144
145
146 private final PropertyResolver overridePropsResolver;
147
148
149 private final boolean omitIgnoredModules;
150
151
152 private final ThreadModeSettings threadModeSettings;
153
154
155
156
157
158
159
160
161
162
163
164 private ConfigurationLoader(final PropertyResolver overrideProps,
165 final boolean omitIgnoredModules,
166 final ThreadModeSettings threadModeSettings)
167 throws ParserConfigurationException, SAXException {
168 saxHandler = new InternalLoader();
169 overridePropsResolver = overrideProps;
170 this.omitIgnoredModules = omitIgnoredModules;
171 this.threadModeSettings = threadModeSettings;
172 }
173
174
175
176
177
178
179
180
181
182
183
184
185 private Configuration parseInputSource(InputSource source)
186 throws IOException, SAXException {
187 saxHandler.parseInputSource(source);
188 return saxHandler.configuration;
189 }
190
191
192
193
194
195
196
197
198
199 public static Configuration loadConfiguration(String config,
200 PropertyResolver overridePropsResolver)
201 throws CheckstyleException {
202 return loadConfiguration(config, overridePropsResolver, IgnoredModulesOptions.EXECUTE);
203 }
204
205
206
207
208
209
210
211
212
213
214 public static Configuration loadConfiguration(String config,
215 PropertyResolver overridePropsResolver, ThreadModeSettings threadModeSettings)
216 throws CheckstyleException {
217 return loadConfiguration(config, overridePropsResolver,
218 IgnoredModulesOptions.EXECUTE, threadModeSettings);
219 }
220
221
222
223
224
225
226
227
228
229
230
231 public static Configuration loadConfiguration(String config,
232 PropertyResolver overridePropsResolver,
233 IgnoredModulesOptions ignoredModulesOptions)
234 throws CheckstyleException {
235 return loadConfiguration(config, overridePropsResolver, ignoredModulesOptions,
236 ThreadModeSettings.SINGLE_THREAD_MODE_INSTANCE);
237 }
238
239
240
241
242
243
244
245
246
247
248
249
250
251 public static Configuration loadConfiguration(InputSource configSource,
252 PropertyResolver overridePropsResolver,
253 IgnoredModulesOptions ignoredModulesOptions)
254 throws CheckstyleException {
255 return loadConfiguration(configSource, overridePropsResolver,
256 ignoredModulesOptions, ThreadModeSettings.SINGLE_THREAD_MODE_INSTANCE);
257 }
258
259
260
261
262
263
264
265
266
267
268
269
270 public static Configuration loadConfiguration(String config,
271 PropertyResolver overridePropsResolver,
272 IgnoredModulesOptions ignoredModulesOptions,
273 ThreadModeSettings threadModeSettings)
274 throws CheckstyleException {
275 return loadConfiguration(CommonUtil.sourceFromFilename(config), overridePropsResolver,
276 ignoredModulesOptions, threadModeSettings);
277 }
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294 public static Configuration loadConfiguration(InputSource configSource,
295 PropertyResolver overridePropsResolver,
296 IgnoredModulesOptions ignoredModulesOptions,
297 ThreadModeSettings threadModeSettings)
298 throws CheckstyleException {
299 try {
300 final boolean omitIgnoreModules = ignoredModulesOptions == IgnoredModulesOptions.OMIT;
301 final ConfigurationLoader loader =
302 new ConfigurationLoader(overridePropsResolver,
303 omitIgnoreModules, threadModeSettings);
304 return loader.parseInputSource(configSource);
305 }
306 catch (final SAXParseException exc) {
307 final String message = String.format(Locale.ROOT, SAX_PARSE_EXCEPTION_FORMAT,
308 UNABLE_TO_PARSE_EXCEPTION_PREFIX,
309 exc.getMessage(), exc.getLineNumber(), exc.getColumnNumber());
310 throw new CheckstyleException(message, exc);
311 }
312 catch (final ParserConfigurationException | IOException | SAXException exc) {
313 throw new CheckstyleException(UNABLE_TO_PARSE_EXCEPTION_PREFIX, exc);
314 }
315 }
316
317
318
319
320
321 private final class InternalLoader
322 extends XmlLoader {
323
324
325 private static final String MODULE = "module";
326
327 private static final String NAME = "name";
328
329 private static final String PROPERTY = "property";
330
331 private static final String VALUE = "value";
332
333 private static final String DEFAULT = "default";
334
335 private static final String SEVERITY = "severity";
336
337 private static final String MESSAGE = "message";
338
339 private static final String METADATA = "metadata";
340
341 private static final String KEY = "key";
342
343
344 private final Deque<DefaultConfiguration> configStack = new ArrayDeque<>();
345
346
347 private Configuration configuration;
348
349
350
351
352
353
354
355 private InternalLoader()
356 throws SAXException, ParserConfigurationException {
357 super(ID_TO_RESOURCE_NAME_MAP);
358 }
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379 private String replaceProperties(
380 String value, String defaultValue)
381 throws CheckstyleException {
382
383 final List<String> fragments = new ArrayList<>();
384 final List<String> propertyRefs = new ArrayList<>();
385 parsePropertyString(value, fragments, propertyRefs);
386
387 final StringBuilder sb = new StringBuilder(256);
388 final Iterator<String> fragmentsIterator = fragments.iterator();
389 final Iterator<String> propertyRefsIterator = propertyRefs.iterator();
390 while (fragmentsIterator.hasNext()) {
391 String fragment = fragmentsIterator.next();
392 if (fragment == null) {
393 final String propertyName = propertyRefsIterator.next();
394 fragment = overridePropsResolver.resolve(propertyName);
395 if (fragment == null) {
396 if (defaultValue != null) {
397 sb.replace(0, sb.length(), defaultValue);
398 break;
399 }
400 throw new CheckstyleException(
401 "Property ${" + propertyName + "} has not been set");
402 }
403 }
404 sb.append(fragment);
405 }
406
407 return sb.toString();
408 }
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432 private static void parsePropertyString(String value,
433 Collection<String> fragments,
434 Collection<String> propertyRefs)
435 throws CheckstyleException {
436 int prev = 0;
437
438 int pos = value.indexOf(DOLLAR_SIGN, prev);
439 while (pos >= 0) {
440
441 if (pos > 0) {
442 fragments.add(value.substring(prev, pos));
443 }
444
445
446 if (pos == value.length() - 1) {
447 fragments.add(DOLLAR_SIGN_STRING);
448 prev = pos + 1;
449 }
450 else if (value.charAt(pos + 1) == '{') {
451
452 final int endName = value.indexOf('}', pos);
453 if (endName == -1) {
454 throw new CheckstyleException("Syntax error in property: "
455 + value);
456 }
457 final String propertyName = value.substring(pos + 2, endName);
458 fragments.add(null);
459 propertyRefs.add(propertyName);
460 prev = endName + 1;
461 }
462 else {
463 if (value.charAt(pos + 1) == DOLLAR_SIGN) {
464
465 fragments.add(DOLLAR_SIGN_STRING);
466 }
467 else {
468
469 fragments.add(value.substring(pos, pos + 2));
470 }
471 prev = pos + 2;
472 }
473
474
475 pos = value.indexOf(DOLLAR_SIGN, prev);
476 }
477
478
479 if (prev < value.length()) {
480 fragments.add(value.substring(prev));
481 }
482 }
483
484 @Override
485 public void startElement(String uri,
486 String localName,
487 String qName,
488 Attributes attributes)
489 throws SAXException {
490 if (MODULE.equals(qName)) {
491
492 final String originalName = attributes.getValue(NAME);
493 final String name = threadModeSettings.resolveName(originalName);
494 final DefaultConfiguration conf =
495 new DefaultConfiguration(name, threadModeSettings);
496
497 if (configStack.isEmpty()) {
498
499 configuration = conf;
500 }
501 else {
502
503 final DefaultConfiguration top =
504 configStack.peek();
505 top.addChild(conf);
506 }
507
508 configStack.push(conf);
509 }
510 else if (PROPERTY.equals(qName)) {
511
512 final String attributesValue = attributes.getValue(VALUE);
513
514 final String value;
515 try {
516 value = replaceProperties(attributesValue, attributes.getValue(DEFAULT));
517 }
518 catch (final CheckstyleException exc) {
519
520
521 throw new SAXException(exc);
522 }
523
524 final String name = attributes.getValue(NAME);
525
526
527 final DefaultConfiguration top =
528 configStack.peek();
529 top.addProperty(name, value);
530 }
531 else if (MESSAGE.equals(qName)) {
532
533 final String key = attributes.getValue(KEY);
534 final String value = attributes.getValue(VALUE);
535
536
537 final DefaultConfiguration top = configStack.peek();
538 top.addMessage(key, value);
539 }
540 else {
541 if (!METADATA.equals(qName)) {
542 throw new IllegalStateException("Unknown name:" + qName + ".");
543 }
544 }
545 }
546
547 @Override
548 public void endElement(String uri,
549 String localName,
550 String qName)
551 throws SAXException {
552 if (MODULE.equals(qName)) {
553 final Configuration recentModule =
554 configStack.pop();
555
556
557 Optional<SeverityLevel> level = Optional.empty();
558 if (containsAttribute(recentModule, SEVERITY)) {
559 try {
560 final String severity = recentModule.getProperty(SEVERITY);
561 level = Optional.of(SeverityLevel.getInstance(severity));
562 }
563 catch (final CheckstyleException exc) {
564
565
566 throw new SAXException(
567 "Problem during accessing '" + SEVERITY + "' attribute for "
568 + recentModule.getName(), exc);
569 }
570 }
571
572
573
574 final boolean omitModule = omitIgnoredModules
575 && level.isPresent() && level.get() == SeverityLevel.IGNORE;
576
577 if (omitModule && !configStack.isEmpty()) {
578 final DefaultConfiguration parentModule = configStack.peek();
579 parentModule.removeChild(recentModule);
580 }
581 }
582 }
583
584
585
586
587
588
589
590
591 private static boolean containsAttribute(Configuration module, String attributeName) {
592 final String[] names = module.getPropertyNames();
593 final Optional<String> result = Arrays.stream(names)
594 .filter(name -> name.equals(attributeName)).findFirst();
595 return result.isPresent();
596 }
597
598 }
599
600 }