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