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