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) throws CheckstyleException {
201 return loadConfiguration(config, overridePropsResolver, IgnoredModulesOptions.EXECUTE);
202 }
203
204
205
206
207
208
209
210
211
212
213 public static Configuration loadConfiguration(String config,
214 PropertyResolver overridePropsResolver, ThreadModeSettings threadModeSettings)
215 throws CheckstyleException {
216 return loadConfiguration(config, overridePropsResolver,
217 IgnoredModulesOptions.EXECUTE, threadModeSettings);
218 }
219
220
221
222
223
224
225
226
227
228
229
230 public static Configuration loadConfiguration(String config,
231 PropertyResolver overridePropsResolver,
232 IgnoredModulesOptions ignoredModulesOptions)
233 throws CheckstyleException {
234 return loadConfiguration(config, overridePropsResolver, ignoredModulesOptions,
235 ThreadModeSettings.SINGLE_THREAD_MODE_INSTANCE);
236 }
237
238
239
240
241
242
243
244
245
246
247
248
249 public static Configuration loadConfiguration(String config,
250 PropertyResolver overridePropsResolver,
251 IgnoredModulesOptions ignoredModulesOptions,
252 ThreadModeSettings threadModeSettings)
253 throws CheckstyleException {
254 return loadConfiguration(CommonUtil.sourceFromFilename(config), overridePropsResolver,
255 ignoredModulesOptions, threadModeSettings);
256 }
257
258
259
260
261
262
263
264
265
266
267
268
269
270 public static Configuration loadConfiguration(InputSource configSource,
271 PropertyResolver overridePropsResolver,
272 IgnoredModulesOptions ignoredModulesOptions)
273 throws CheckstyleException {
274 return loadConfiguration(configSource, overridePropsResolver,
275 ignoredModulesOptions, ThreadModeSettings.SINGLE_THREAD_MODE_INSTANCE);
276 }
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293 public static Configuration loadConfiguration(InputSource configSource,
294 PropertyResolver overridePropsResolver,
295 IgnoredModulesOptions ignoredModulesOptions,
296 ThreadModeSettings threadModeSettings)
297 throws CheckstyleException {
298 try {
299 final boolean omitIgnoreModules = ignoredModulesOptions == IgnoredModulesOptions.OMIT;
300 final ConfigurationLoader loader =
301 new ConfigurationLoader(overridePropsResolver,
302 omitIgnoreModules, threadModeSettings);
303 return loader.parseInputSource(configSource);
304 }
305 catch (final SAXParseException exc) {
306 final String message = String.format(Locale.ROOT, SAX_PARSE_EXCEPTION_FORMAT,
307 UNABLE_TO_PARSE_EXCEPTION_PREFIX,
308 exc.getMessage(), exc.getLineNumber(), exc.getColumnNumber());
309 throw new CheckstyleException(message, exc);
310 }
311 catch (final ParserConfigurationException | IOException | SAXException exc) {
312 throw new CheckstyleException(UNABLE_TO_PARSE_EXCEPTION_PREFIX, exc);
313 }
314 }
315
316
317
318
319
320 private final class InternalLoader
321 extends XmlLoader {
322
323
324 private static final String MODULE = "module";
325
326 private static final String NAME = "name";
327
328 private static final String PROPERTY = "property";
329
330 private static final String VALUE = "value";
331
332 private static final String DEFAULT = "default";
333
334 private static final String SEVERITY = "severity";
335
336 private static final String MESSAGE = "message";
337
338 private static final String METADATA = "metadata";
339
340 private static final String KEY = "key";
341
342
343 private final Deque<DefaultConfiguration> configStack = new ArrayDeque<>();
344
345
346 private Configuration configuration;
347
348
349
350
351
352
353
354 private InternalLoader()
355 throws SAXException, ParserConfigurationException {
356 super(ID_TO_RESOURCE_NAME_MAP);
357 }
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378 private String replaceProperties(
379 String value, String defaultValue)
380 throws CheckstyleException {
381
382 final List<String> fragments = new ArrayList<>();
383 final List<String> propertyRefs = new ArrayList<>();
384 parsePropertyString(value, fragments, propertyRefs);
385
386 final StringBuilder sb = new StringBuilder(256);
387 final Iterator<String> fragmentsIterator = fragments.iterator();
388 final Iterator<String> propertyRefsIterator = propertyRefs.iterator();
389 while (fragmentsIterator.hasNext()) {
390 String fragment = fragmentsIterator.next();
391 if (fragment == null) {
392 final String propertyName = propertyRefsIterator.next();
393 fragment = overridePropsResolver.resolve(propertyName);
394 if (fragment == null) {
395 if (defaultValue != null) {
396 sb.replace(0, sb.length(), defaultValue);
397 break;
398 }
399 throw new CheckstyleException(
400 "Property ${" + propertyName + "} has not been set");
401 }
402 }
403 sb.append(fragment);
404 }
405
406 return sb.toString();
407 }
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431 private static void parsePropertyString(String value,
432 Collection<String> fragments,
433 Collection<String> propertyRefs)
434 throws CheckstyleException {
435 int prev = 0;
436
437 int pos = value.indexOf(DOLLAR_SIGN, prev);
438 while (pos >= 0) {
439
440 if (pos > 0) {
441 fragments.add(value.substring(prev, pos));
442 }
443
444
445 if (pos == value.length() - 1) {
446 fragments.add(DOLLAR_SIGN_STRING);
447 prev = pos + 1;
448 }
449 else if (value.charAt(pos + 1) == '{') {
450
451 final int endName = value.indexOf('}', pos);
452 if (endName == -1) {
453 throw new CheckstyleException("Syntax error in property: "
454 + value);
455 }
456 final String propertyName = value.substring(pos + 2, endName);
457 fragments.add(null);
458 propertyRefs.add(propertyName);
459 prev = endName + 1;
460 }
461 else {
462 if (value.charAt(pos + 1) == DOLLAR_SIGN) {
463
464 fragments.add(DOLLAR_SIGN_STRING);
465 }
466 else {
467
468 fragments.add(value.substring(pos, pos + 2));
469 }
470 prev = pos + 2;
471 }
472
473
474 pos = value.indexOf(DOLLAR_SIGN, prev);
475 }
476
477
478 if (prev < value.length()) {
479 fragments.add(value.substring(prev));
480 }
481 }
482
483 @Override
484 public void startElement(String uri,
485 String localName,
486 String qName,
487 Attributes attributes)
488 throws SAXException {
489 if (MODULE.equals(qName)) {
490
491 final String originalName = attributes.getValue(NAME);
492 final String name = threadModeSettings.resolveName(originalName);
493 final DefaultConfiguration conf =
494 new DefaultConfiguration(name, threadModeSettings);
495
496 if (configStack.isEmpty()) {
497
498 configuration = conf;
499 }
500 else {
501
502 final DefaultConfiguration top =
503 configStack.peek();
504 top.addChild(conf);
505 }
506
507 configStack.push(conf);
508 }
509 else if (PROPERTY.equals(qName)) {
510
511 final String attributesValue = attributes.getValue(VALUE);
512
513 final String value;
514 try {
515 value = replaceProperties(attributesValue, attributes.getValue(DEFAULT));
516 }
517 catch (final CheckstyleException exc) {
518
519
520 throw new SAXException(exc);
521 }
522
523 final String name = attributes.getValue(NAME);
524
525
526 final DefaultConfiguration top =
527 configStack.peek();
528 top.addProperty(name, value);
529 }
530 else if (MESSAGE.equals(qName)) {
531
532 final String key = attributes.getValue(KEY);
533 final String value = attributes.getValue(VALUE);
534
535
536 final DefaultConfiguration top = configStack.peek();
537 top.addMessage(key, value);
538 }
539 else {
540 if (!METADATA.equals(qName)) {
541 throw new IllegalStateException("Unknown name:" + qName + ".");
542 }
543 }
544 }
545
546 @Override
547 public void endElement(String uri,
548 String localName,
549 String qName) throws SAXException {
550 if (MODULE.equals(qName)) {
551 final Configuration recentModule =
552 configStack.pop();
553
554
555 Optional<SeverityLevel> level = Optional.empty();
556 if (containsAttribute(recentModule, SEVERITY)) {
557 try {
558 final String severity = recentModule.getProperty(SEVERITY);
559 level = Optional.of(SeverityLevel.getInstance(severity));
560 }
561 catch (final CheckstyleException exc) {
562
563
564 throw new SAXException(
565 "Problem during accessing '" + SEVERITY + "' attribute for "
566 + recentModule.getName(), exc);
567 }
568 }
569
570
571
572 final boolean omitModule = omitIgnoredModules
573 && level.isPresent() && level.get() == SeverityLevel.IGNORE;
574
575 if (omitModule && !configStack.isEmpty()) {
576 final DefaultConfiguration parentModule = 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 }