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