1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
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   * Loads a configuration from a standard configuration XML file.
50   *
51   */
52  public final class ConfigurationLoader {
53  
54      /**
55       * Enum to specify behaviour regarding ignored modules.
56       */
57      public enum IgnoredModulesOptions {
58  
59          /**
60           * Omit ignored modules.
61           */
62          OMIT,
63  
64          /**
65           * Execute ignored modules.
66           */
67          EXECUTE,
68  
69      }
70  
71      /** Format of message for sax parse exception. */
72      private static final String SAX_PARSE_EXCEPTION_FORMAT = "%s - %s:%s:%s";
73  
74      /** The public ID for version 1_0 of the configuration dtd. */
75      private static final String DTD_PUBLIC_ID_1_0 =
76          "-//Puppy Crawl//DTD Check Configuration 1.0//EN";
77  
78      /** The new public ID for version 1_0 of the configuration dtd. */
79      private static final String DTD_PUBLIC_CS_ID_1_0 =
80          "-//Checkstyle//DTD Checkstyle Configuration 1.0//EN";
81  
82      /** The resource for version 1_0 of the configuration dtd. */
83      private static final String DTD_CONFIGURATION_NAME_1_0 =
84          "com/puppycrawl/tools/checkstyle/configuration_1_0.dtd";
85  
86      /** The public ID for version 1_1 of the configuration dtd. */
87      private static final String DTD_PUBLIC_ID_1_1 =
88          "-//Puppy Crawl//DTD Check Configuration 1.1//EN";
89  
90      /** The new public ID for version 1_1 of the configuration dtd. */
91      private static final String DTD_PUBLIC_CS_ID_1_1 =
92          "-//Checkstyle//DTD Checkstyle Configuration 1.1//EN";
93  
94      /** The resource for version 1_1 of the configuration dtd. */
95      private static final String DTD_CONFIGURATION_NAME_1_1 =
96          "com/puppycrawl/tools/checkstyle/configuration_1_1.dtd";
97  
98      /** The public ID for version 1_2 of the configuration dtd. */
99      private static final String DTD_PUBLIC_ID_1_2 =
100         "-//Puppy Crawl//DTD Check Configuration 1.2//EN";
101 
102     /** The new public ID for version 1_2 of the configuration dtd. */
103     private static final String DTD_PUBLIC_CS_ID_1_2 =
104         "-//Checkstyle//DTD Checkstyle Configuration 1.2//EN";
105 
106     /** The resource for version 1_2 of the configuration dtd. */
107     private static final String DTD_CONFIGURATION_NAME_1_2 =
108         "com/puppycrawl/tools/checkstyle/configuration_1_2.dtd";
109 
110     /** The public ID for version 1_3 of the configuration dtd. */
111     private static final String DTD_PUBLIC_ID_1_3 =
112         "-//Puppy Crawl//DTD Check Configuration 1.3//EN";
113 
114     /** The new public ID for version 1_3 of the configuration dtd. */
115     private static final String DTD_PUBLIC_CS_ID_1_3 =
116         "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN";
117 
118     /** The resource for version 1_3 of the configuration dtd. */
119     private static final String DTD_CONFIGURATION_NAME_1_3 =
120         "com/puppycrawl/tools/checkstyle/configuration_1_3.dtd";
121 
122     /** Prefix for the exception when unable to parse resource. */
123     private static final String UNABLE_TO_PARSE_EXCEPTION_PREFIX = "unable to parse"
124             + " configuration stream";
125 
126     /** Dollar sign literal. */
127     private static final char DOLLAR_SIGN = '$';
128 
129     /** The SAX document handler. */
130     private final InternalLoader saxHandler;
131 
132     /** Property resolver. **/
133     private final PropertyResolver overridePropsResolver;
134     /** The loaded configurations. **/
135     private final Deque<DefaultConfiguration> configStack = new ArrayDeque<>();
136 
137     /** Flags if modules with the severity 'ignore' should be omitted. */
138     private final boolean omitIgnoredModules;
139 
140     /** The thread mode configuration. */
141     private final ThreadModeSettings threadModeSettings;
142 
143     /** The Configuration that is being built. */
144     private Configuration configuration;
145 
146     /**
147      * Creates a new {@code ConfigurationLoader} instance.
148      * @param overrideProps resolver for overriding properties
149      * @param omitIgnoredModules {@code true} if ignored modules should be
150      *         omitted
151      * @param threadModeSettings the thread mode configuration
152      * @throws ParserConfigurationException if an error occurs
153      * @throws SAXException if an error occurs
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      * Creates mapping between local resources and dtd ids. This method can't be
167      * moved to inner class because it must stay static because it is called
168      * from constructor and inner class isn't static.
169      * @return map between local resources and dtd ids.
170      * @noinspection MethodOnlyUsedFromInnerClass
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      * Parses the specified input source loading the configuration information.
187      * The stream wrapped inside the source, if any, is NOT
188      * explicitly closed after parsing, it is the responsibility of
189      * the caller to close the stream.
190      *
191      * @param source the source that contains the configuration data
192      * @throws IOException if an error occurs
193      * @throws SAXException if an error occurs
194      */
195     private void parseInputSource(InputSource source)
196             throws IOException, SAXException {
197         saxHandler.parseInputSource(source);
198     }
199 
200     /**
201      * Returns the module configurations in a specified file.
202      * @param config location of config file, can be either a URL or a filename
203      * @param overridePropsResolver overriding properties
204      * @return the check configurations
205      * @throws CheckstyleException if an error occurs
206      */
207     public static Configuration loadConfiguration(String config,
208             PropertyResolver overridePropsResolver) throws CheckstyleException {
209         return loadConfiguration(config, overridePropsResolver, IgnoredModulesOptions.EXECUTE);
210     }
211 
212     /**
213      * Returns the module configurations in a specified file.
214      * @param config location of config file, can be either a URL or a filename
215      * @param overridePropsResolver overriding properties
216      * @param threadModeSettings the thread mode configuration
217      * @return the check configurations
218      * @throws CheckstyleException if an error occurs
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      * Returns the module configurations in a specified file.
229      *
230      * @param config location of config file, can be either a URL or a filename
231      * @param overridePropsResolver overriding properties
232      * @param omitIgnoredModules {@code true} if modules with severity
233      *            'ignore' should be omitted, {@code false} otherwise
234      * @return the check configurations
235      * @throws CheckstyleException if an error occurs
236      * @deprecated in order to fulfill demands of BooleanParameter IDEA check.
237      * @noinspection BooleanParameter
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      * Returns the module configurations in a specified file.
249      *
250      * @param config location of config file, can be either a URL or a filename
251      * @param overridePropsResolver overriding properties
252      * @param omitIgnoredModules {@code true} if modules with severity
253      *            'ignore' should be omitted, {@code false} otherwise
254      * @param threadModeSettings the thread mode configuration
255      * @return the check configurations
256      * @throws CheckstyleException if an error occurs
257      * @deprecated in order to fulfill demands of BooleanParameter IDEA check.
258      * @noinspection BooleanParameter, WeakerAccess
259      */
260     @Deprecated
261     public static Configuration loadConfiguration(String config,
262             PropertyResolver overridePropsResolver,
263             boolean omitIgnoredModules, ThreadModeSettings threadModeSettings)
264             throws CheckstyleException {
265         // figure out if this is a File or a URL
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      * Returns the module configurations from a specified input stream.
274      * Note that clients are required to close the given stream by themselves
275      *
276      * @param configStream the input stream to the Checkstyle configuration
277      * @param overridePropsResolver overriding properties
278      * @param omitIgnoredModules {@code true} if modules with severity
279      *            'ignore' should be omitted, {@code false} otherwise
280      * @return the check configurations
281      * @throws CheckstyleException if an error occurs
282      *
283      * @deprecated As this method does not provide a valid system ID,
284      *     preventing resolution of external entities, a
285      *     {@link #loadConfiguration(InputSource,PropertyResolver,boolean)
286      *          version using an InputSource}
287      *     should be used instead
288      * @noinspection BooleanParameter
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      * Returns the module configurations from a specified input source.
300      * Note that if the source does wrap an open byte or character
301      * stream, clients are required to close that stream by themselves
302      *
303      * @param configSource the input stream to the Checkstyle configuration
304      * @param overridePropsResolver overriding properties
305      * @param omitIgnoredModules {@code true} if modules with severity
306      *            'ignore' should be omitted, {@code false} otherwise
307      * @return the check configurations
308      * @throws CheckstyleException if an error occurs
309      * @deprecated in order to fulfill demands of BooleanParameter IDEA check.
310      * @noinspection BooleanParameter
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      * Returns the module configurations from a specified input source.
322      * Note that if the source does wrap an open byte or character
323      * stream, clients are required to close that stream by themselves
324      *
325      * @param configSource the input stream to the Checkstyle configuration
326      * @param overridePropsResolver overriding properties
327      * @param omitIgnoredModules {@code true} if modules with severity
328      *            'ignore' should be omitted, {@code false} otherwise
329      * @param threadModeSettings the thread mode configuration
330      * @return the check configurations
331      * @throws CheckstyleException if an error occurs
332      * @deprecated in order to fulfill demands of BooleanParameter IDEA check.
333      * @noinspection BooleanParameter, WeakerAccess
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      * Returns the module configurations in a specified file.
360      *
361      * @param config location of config file, can be either a URL or a filename
362      * @param overridePropsResolver overriding properties
363      * @param ignoredModulesOptions {@code OMIT} if modules with severity
364      *            'ignore' should be omitted, {@code EXECUTE} otherwise
365      * @return the check configurations
366      * @throws CheckstyleException if an error occurs
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      * Returns the module configurations in a specified file.
378      *
379      * @param config location of config file, can be either a URL or a filename
380      * @param overridePropsResolver overriding properties
381      * @param ignoredModulesOptions {@code OMIT} if modules with severity
382      *            'ignore' should be omitted, {@code EXECUTE} otherwise
383      * @param threadModeSettings the thread mode configuration
384      * @return the check configurations
385      * @throws CheckstyleException if an error occurs
386      */
387     public static Configuration loadConfiguration(String config,
388                                                   PropertyResolver overridePropsResolver,
389                                                   IgnoredModulesOptions ignoredModulesOptions,
390                                                   ThreadModeSettings threadModeSettings)
391             throws CheckstyleException {
392         // figure out if this is a File or a URL
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      * Returns the module configurations from a specified input source.
401      * Note that if the source does wrap an open byte or character
402      * stream, clients are required to close that stream by themselves
403      *
404      * @param configSource the input stream to the Checkstyle configuration
405      * @param overridePropsResolver overriding properties
406      * @param ignoredModulesOptions {@code OMIT} if modules with severity
407      *            'ignore' should be omitted, {@code EXECUTE} otherwise
408      * @return the check configurations
409      * @throws CheckstyleException if an error occurs
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      * Returns the module configurations from a specified input source.
421      * Note that if the source does wrap an open byte or character
422      * stream, clients are required to close that stream by themselves
423      *
424      * @param configSource the input stream to the Checkstyle configuration
425      * @param overridePropsResolver overriding properties
426      * @param ignoredModulesOptions {@code OMIT} if modules with severity
427      *            'ignore' should be omitted, {@code EXECUTE} otherwise
428      * @param threadModeSettings the thread mode configuration
429      * @return the check configurations
430      * @throws CheckstyleException if an error occurs
431      * @noinspection WeakerAccess
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      * Replaces {@code ${xxx}} style constructions in the given value
459      * with the string value of the corresponding data types. This method must remain
460      * outside inner class for easier testing since inner class requires an instance.
461      *
462      * <p>Code copied from ant -
463      * http://cvs.apache.org/viewcvs/jakarta-ant/src/main/org/apache/tools/ant/ProjectHelper.java
464      *
465      * @param value The string to be scanned for property references.
466      *              May be {@code null}, in which case this
467      *              method returns immediately with no effect.
468      * @param props Mapping (String to String) of property names to their
469      *              values. Must not be {@code null}.
470      * @param defaultValue default to use if one of the properties in value
471      *              cannot be resolved from props.
472      *
473      * @return the original string with the properties replaced, or
474      *         {@code null} if the original string is {@code null}.
475      * @throws CheckstyleException if the string contains an opening
476      *                           {@code ${} without a closing
477      *                           {@code }}
478      * @noinspection MethodWithMultipleReturnPoints, MethodOnlyUsedFromInnerClass
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      * Parses a string containing {@code ${xxx}} style property
516      * references into two lists. The first list is a collection
517      * of text fragments, while the other is a set of string property names.
518      * {@code null} entries in the first list indicate a property
519      * reference from the second list.
520      *
521      * <p>Code copied from ant -
522      * http://cvs.apache.org/viewcvs/jakarta-ant/src/main/org/apache/tools/ant/ProjectHelper.java
523      *
524      * @param value     Text to parse. Must not be {@code null}.
525      * @param fragments List to add text fragments to.
526      *                  Must not be {@code null}.
527      * @param propertyRefs List to add property names to.
528      *                     Must not be {@code null}.
529      *
530      * @throws CheckstyleException if the string contains an opening
531      *                           {@code ${} without a closing
532      *                           {@code }}
533      */
534     private static void parsePropertyString(String value,
535                                            List<String> fragments,
536                                            List<String> propertyRefs)
537             throws CheckstyleException {
538         int prev = 0;
539         //search for the next instance of $ from the 'prev' position
540         int pos = value.indexOf(DOLLAR_SIGN, prev);
541         while (pos >= 0) {
542             //if there was any text before this, add it as a fragment
543             if (pos > 0) {
544                 fragments.add(value.substring(prev, pos));
545             }
546             //if we are at the end of the string, we tack on a $
547             //then move past it
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                 //property found, extract its name or bail on a typo
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                     //backwards compatibility two $ map to one mode
567                     fragments.add(String.valueOf(DOLLAR_SIGN));
568                 }
569                 else {
570                     //new behaviour: $X maps to $X for all values of X!='$'
571                     fragments.add(value.substring(pos, pos + 2));
572                 }
573                 prev = pos + 2;
574             }
575 
576             //search for the next instance of $ from the 'prev' position
577             pos = value.indexOf(DOLLAR_SIGN, prev);
578         }
579         //no more $ signs found
580         //if there is any tail to the file, append it
581         if (prev < value.length()) {
582             fragments.add(value.substring(prev));
583         }
584     }
585 
586     /**
587      * Implements the SAX document handler interfaces, so they do not
588      * appear in the public API of the ConfigurationLoader.
589      */
590     private final class InternalLoader
591         extends XmlLoader {
592 
593         /** Module elements. */
594         private static final String MODULE = "module";
595         /** Name attribute. */
596         private static final String NAME = "name";
597         /** Property element. */
598         private static final String PROPERTY = "property";
599         /** Value attribute. */
600         private static final String VALUE = "value";
601         /** Default attribute. */
602         private static final String DEFAULT = "default";
603         /** Name of the severity property. */
604         private static final String SEVERITY = "severity";
605         /** Name of the message element. */
606         private static final String MESSAGE = "message";
607         /** Name of the message element. */
608         private static final String METADATA = "metadata";
609         /** Name of the key attribute. */
610         private static final String KEY = "key";
611 
612         /**
613          * Creates a new InternalLoader.
614          * @throws SAXException if an error occurs
615          * @throws ParserConfigurationException if an error occurs
616          */
617         /* package */ 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                 //create configuration
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                 //add configuration to it's parent
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                 //extract value and name
650                 final String value;
651                 try {
652                     value = replaceProperties(attributes.getValue(VALUE),
653                         overridePropsResolver, attributes.getValue(DEFAULT));
654                 }
655                 catch (final CheckstyleException ex) {
656                     // -@cs[IllegalInstantiation] SAXException is in the overridden method signature
657                     throw new SAXException(ex);
658                 }
659                 final String name = attributes.getValue(NAME);
660 
661                 //add to attributes of configuration
662                 final DefaultConfiguration top =
663                     configStack.peek();
664                 top.addAttribute(name, value);
665             }
666             else if (qName.equals(MESSAGE)) {
667                 //extract key and value
668                 final String key = attributes.getValue(KEY);
669                 final String value = attributes.getValue(VALUE);
670 
671                 //add to messages of configuration
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                 // get severity attribute if it exists
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                         // -@cs[IllegalInstantiation] SAXException is in the overridden
699                         // method signature
700                         throw new SAXException(
701                                 "Problem during accessing '" + SEVERITY + "' attribute for "
702                                         + recentModule.getName(), ex);
703                     }
704                 }
705 
706                 // omit this module if these should be omitted and the module
707                 // has the severity 'ignore'
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          * Util method to recheck attribute in module.
721          * @param module module to check
722          * @param attributeName name of attribute in module to find
723          * @return true if attribute is present in module
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 }