001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2018 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle;
021
022import java.io.IOException;
023import java.io.InputStream;
024import java.net.URI;
025import java.util.ArrayDeque;
026import java.util.ArrayList;
027import java.util.Arrays;
028import java.util.Deque;
029import java.util.HashMap;
030import java.util.Iterator;
031import java.util.List;
032import java.util.Locale;
033import java.util.Map;
034import java.util.Optional;
035
036import javax.xml.parsers.ParserConfigurationException;
037
038import org.xml.sax.Attributes;
039import org.xml.sax.InputSource;
040import org.xml.sax.SAXException;
041import org.xml.sax.SAXParseException;
042
043import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
044import com.puppycrawl.tools.checkstyle.api.Configuration;
045import com.puppycrawl.tools.checkstyle.api.SeverityLevel;
046import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
047
048/**
049 * Loads a configuration from a standard configuration XML file.
050 *
051 */
052public final class ConfigurationLoader {
053
054    /**
055     * Enum to specify behaviour regarding ignored modules.
056     */
057    public enum IgnoredModulesOptions {
058
059        /**
060         * Omit ignored modules.
061         */
062        OMIT,
063
064        /**
065         * Execute ignored modules.
066         */
067        EXECUTE
068
069    }
070
071    /** Format of message for sax parse exception. */
072    private static final String SAX_PARSE_EXCEPTION_FORMAT = "%s - %s:%s:%s";
073
074    /** The public ID for version 1_0 of the configuration dtd. */
075    private static final String DTD_PUBLIC_ID_1_0 =
076        "-//Puppy Crawl//DTD Check Configuration 1.0//EN";
077
078    /** The new public ID for version 1_0 of the configuration dtd. */
079    private static final String DTD_PUBLIC_CS_ID_1_0 =
080        "-//Checkstyle//DTD Checkstyle Configuration 1.0//EN";
081
082    /** The resource for version 1_0 of the configuration dtd. */
083    private static final String DTD_CONFIGURATION_NAME_1_0 =
084        "com/puppycrawl/tools/checkstyle/configuration_1_0.dtd";
085
086    /** The public ID for version 1_1 of the configuration dtd. */
087    private static final String DTD_PUBLIC_ID_1_1 =
088        "-//Puppy Crawl//DTD Check Configuration 1.1//EN";
089
090    /** The new public ID for version 1_1 of the configuration dtd. */
091    private static final String DTD_PUBLIC_CS_ID_1_1 =
092        "-//Checkstyle//DTD Checkstyle Configuration 1.1//EN";
093
094    /** The resource for version 1_1 of the configuration dtd. */
095    private static final String DTD_CONFIGURATION_NAME_1_1 =
096        "com/puppycrawl/tools/checkstyle/configuration_1_1.dtd";
097
098    /** The public ID for version 1_2 of the configuration dtd. */
099    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.
167     * @return map between local resources and dtd ids.
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     * Parses the specified input source loading the configuration information.
184     * The stream wrapped inside the source, if any, is NOT
185     * explicitly closed after parsing, it is the responsibility of
186     * the caller to close the stream.
187     *
188     * @param source the source that contains the configuration data
189     * @throws IOException if an error occurs
190     * @throws SAXException if an error occurs
191     */
192    private void parseInputSource(InputSource source)
193            throws IOException, SAXException {
194        saxHandler.parseInputSource(source);
195    }
196
197    /**
198     * Returns the module configurations in a specified file.
199     * @param config location of config file, can be either a URL or a filename
200     * @param overridePropsResolver overriding properties
201     * @return the check configurations
202     * @throws CheckstyleException if an error occurs
203     */
204    public static Configuration loadConfiguration(String config,
205            PropertyResolver overridePropsResolver) throws CheckstyleException {
206        return loadConfiguration(config, overridePropsResolver, IgnoredModulesOptions.EXECUTE);
207    }
208
209    /**
210     * Returns the module configurations in a specified file.
211     * @param config location of config file, can be either a URL or a filename
212     * @param overridePropsResolver overriding properties
213     * @param threadModeSettings the thread mode configuration
214     * @return the check configurations
215     * @throws CheckstyleException if an error occurs
216     */
217    public static Configuration loadConfiguration(String config,
218            PropertyResolver overridePropsResolver, ThreadModeSettings threadModeSettings)
219            throws CheckstyleException {
220        return loadConfiguration(config, overridePropsResolver,
221                IgnoredModulesOptions.EXECUTE, threadModeSettings);
222    }
223
224    /**
225     * Returns the module configurations in a specified file.
226     *
227     * @param config location of config file, can be either a URL or a filename
228     * @param overridePropsResolver overriding properties
229     * @param omitIgnoredModules {@code true} if modules with severity
230     *            'ignore' should be omitted, {@code false} otherwise
231     * @return the check configurations
232     * @throws CheckstyleException if an error occurs
233     * @deprecated in order to fulfill demands of BooleanParameter IDEA check.
234     * @noinspection BooleanParameter
235     */
236    @Deprecated
237    public static Configuration loadConfiguration(String config,
238        PropertyResolver overridePropsResolver, boolean omitIgnoredModules)
239            throws CheckstyleException {
240        return loadConfiguration(config, overridePropsResolver, omitIgnoredModules,
241                ThreadModeSettings.SINGLE_THREAD_MODE_INSTANCE);
242    }
243
244    /**
245     * Returns the module configurations in a specified file.
246     *
247     * @param config location of config file, can be either a URL or a filename
248     * @param overridePropsResolver overriding properties
249     * @param omitIgnoredModules {@code true} if modules with severity
250     *            'ignore' should be omitted, {@code false} otherwise
251     * @param threadModeSettings the thread mode configuration
252     * @return the check configurations
253     * @throws CheckstyleException if an error occurs
254     * @deprecated in order to fulfill demands of BooleanParameter IDEA check.
255     * @noinspection BooleanParameter, WeakerAccess
256     */
257    @Deprecated
258    public static Configuration loadConfiguration(String config,
259            PropertyResolver overridePropsResolver,
260            boolean omitIgnoredModules, ThreadModeSettings threadModeSettings)
261            throws CheckstyleException {
262        // figure out if this is a File or a URL
263        final URI uri = CommonUtil.getUriByFilename(config);
264        final InputSource source = new InputSource(uri.toString());
265        return loadConfiguration(source, overridePropsResolver,
266                omitIgnoredModules, threadModeSettings);
267    }
268
269    /**
270     * Returns the module configurations from a specified input stream.
271     * Note that clients are required to close the given stream by themselves
272     *
273     * @param configStream the input stream to the Checkstyle configuration
274     * @param overridePropsResolver overriding properties
275     * @param omitIgnoredModules {@code true} if modules with severity
276     *            'ignore' should be omitted, {@code false} otherwise
277     * @return the check configurations
278     * @throws CheckstyleException if an error occurs
279     *
280     * @deprecated As this method does not provide a valid system ID,
281     *     preventing resolution of external entities, a
282     *     {@link #loadConfiguration(InputSource,PropertyResolver,boolean)
283     *          version using an InputSource}
284     *     should be used instead
285     * @noinspection BooleanParameter
286     */
287    @Deprecated
288    public static Configuration loadConfiguration(InputStream configStream,
289        PropertyResolver overridePropsResolver, boolean omitIgnoredModules)
290            throws CheckstyleException {
291        return loadConfiguration(new InputSource(configStream),
292                                 overridePropsResolver, omitIgnoredModules);
293    }
294
295    /**
296     * Returns the module configurations from a specified input source.
297     * Note that if the source does wrap an open byte or character
298     * stream, clients are required to close that stream by themselves
299     *
300     * @param configSource the input stream to the Checkstyle configuration
301     * @param overridePropsResolver overriding properties
302     * @param omitIgnoredModules {@code true} if modules with severity
303     *            'ignore' should be omitted, {@code false} otherwise
304     * @return the check configurations
305     * @throws CheckstyleException if an error occurs
306     * @deprecated in order to fulfill demands of BooleanParameter IDEA check.
307     * @noinspection BooleanParameter
308     */
309    @Deprecated
310    public static Configuration loadConfiguration(InputSource configSource,
311            PropertyResolver overridePropsResolver, boolean omitIgnoredModules)
312            throws CheckstyleException {
313        return loadConfiguration(configSource, overridePropsResolver,
314                omitIgnoredModules, ThreadModeSettings.SINGLE_THREAD_MODE_INSTANCE);
315    }
316
317    /**
318     * Returns the module configurations from a specified input source.
319     * Note that if the source does wrap an open byte or character
320     * stream, clients are required to close that stream by themselves
321     *
322     * @param configSource the input stream to the Checkstyle configuration
323     * @param overridePropsResolver overriding properties
324     * @param omitIgnoredModules {@code true} if modules with severity
325     *            'ignore' should be omitted, {@code false} otherwise
326     * @param threadModeSettings the thread mode configuration
327     * @return the check configurations
328     * @throws CheckstyleException if an error occurs
329     * @deprecated in order to fulfill demands of BooleanParameter IDEA check.
330     * @noinspection BooleanParameter, WeakerAccess
331     */
332    @Deprecated
333    public static Configuration loadConfiguration(InputSource configSource,
334        PropertyResolver overridePropsResolver,
335        boolean omitIgnoredModules, ThreadModeSettings threadModeSettings)
336            throws CheckstyleException {
337        try {
338            final ConfigurationLoader loader =
339                new ConfigurationLoader(overridePropsResolver,
340                                        omitIgnoredModules, threadModeSettings);
341            loader.parseInputSource(configSource);
342            return loader.configuration;
343        }
344        catch (final SAXParseException ex) {
345            final String message = String.format(Locale.ROOT, SAX_PARSE_EXCEPTION_FORMAT,
346                    UNABLE_TO_PARSE_EXCEPTION_PREFIX,
347                    ex.getMessage(), ex.getLineNumber(), ex.getColumnNumber());
348            throw new CheckstyleException(message, ex);
349        }
350        catch (final ParserConfigurationException | IOException | SAXException ex) {
351            throw new CheckstyleException(UNABLE_TO_PARSE_EXCEPTION_PREFIX, ex);
352        }
353    }
354
355    /**
356     * Returns the module configurations in a specified file.
357     *
358     * @param config location of config file, can be either a URL or a filename
359     * @param overridePropsResolver overriding properties
360     * @param ignoredModulesOptions {@code OMIT} if modules with severity
361     *            'ignore' should be omitted, {@code EXECUTE} otherwise
362     * @return the check configurations
363     * @throws CheckstyleException if an error occurs
364     */
365    public static Configuration loadConfiguration(String config,
366                                                  PropertyResolver overridePropsResolver,
367                                                  IgnoredModulesOptions ignoredModulesOptions)
368            throws CheckstyleException {
369        return loadConfiguration(config, overridePropsResolver, ignoredModulesOptions,
370                ThreadModeSettings.SINGLE_THREAD_MODE_INSTANCE);
371    }
372
373    /**
374     * Returns the module configurations in a specified file.
375     *
376     * @param config location of config file, can be either a URL or a filename
377     * @param overridePropsResolver overriding properties
378     * @param ignoredModulesOptions {@code OMIT} if modules with severity
379     *            'ignore' should be omitted, {@code EXECUTE} otherwise
380     * @param threadModeSettings the thread mode configuration
381     * @return the check configurations
382     * @throws CheckstyleException if an error occurs
383     */
384    public static Configuration loadConfiguration(String config,
385                                                  PropertyResolver overridePropsResolver,
386                                                  IgnoredModulesOptions ignoredModulesOptions,
387                                                  ThreadModeSettings threadModeSettings)
388            throws CheckstyleException {
389        // figure out if this is a File or a URL
390        final URI uri = CommonUtil.getUriByFilename(config);
391        final InputSource source = new InputSource(uri.toString());
392        return loadConfiguration(source, overridePropsResolver,
393                ignoredModulesOptions, threadModeSettings);
394    }
395
396    /**
397     * Returns the module configurations from a specified input source.
398     * Note that if the source does wrap an open byte or character
399     * stream, clients are required to close that stream by themselves
400     *
401     * @param configSource the input stream to the Checkstyle configuration
402     * @param overridePropsResolver overriding properties
403     * @param ignoredModulesOptions {@code OMIT} if modules with severity
404     *            'ignore' should be omitted, {@code EXECUTE} otherwise
405     * @return the check configurations
406     * @throws CheckstyleException if an error occurs
407     */
408    public static Configuration loadConfiguration(InputSource configSource,
409                                                  PropertyResolver overridePropsResolver,
410                                                  IgnoredModulesOptions ignoredModulesOptions)
411            throws CheckstyleException {
412        return loadConfiguration(configSource, overridePropsResolver,
413                ignoredModulesOptions, ThreadModeSettings.SINGLE_THREAD_MODE_INSTANCE);
414    }
415
416    /**
417     * Returns the module configurations from a specified input source.
418     * Note that if the source does wrap an open byte or character
419     * stream, clients are required to close that stream by themselves
420     *
421     * @param configSource the input stream to the Checkstyle configuration
422     * @param overridePropsResolver overriding properties
423     * @param ignoredModulesOptions {@code OMIT} if modules with severity
424     *            'ignore' should be omitted, {@code EXECUTE} otherwise
425     * @param threadModeSettings the thread mode configuration
426     * @return the check configurations
427     * @throws CheckstyleException if an error occurs
428     * @noinspection WeakerAccess
429     */
430    public static Configuration loadConfiguration(InputSource configSource,
431                                                  PropertyResolver overridePropsResolver,
432                                                  IgnoredModulesOptions ignoredModulesOptions,
433                                                  ThreadModeSettings threadModeSettings)
434            throws CheckstyleException {
435        try {
436            final boolean omitIgnoreModules = ignoredModulesOptions == IgnoredModulesOptions.OMIT;
437            final ConfigurationLoader loader =
438                    new ConfigurationLoader(overridePropsResolver,
439                            omitIgnoreModules, threadModeSettings);
440            loader.parseInputSource(configSource);
441            return loader.configuration;
442        }
443        catch (final SAXParseException ex) {
444            final String message = String.format(Locale.ROOT, SAX_PARSE_EXCEPTION_FORMAT,
445                    UNABLE_TO_PARSE_EXCEPTION_PREFIX,
446                    ex.getMessage(), ex.getLineNumber(), ex.getColumnNumber());
447            throw new CheckstyleException(message, ex);
448        }
449        catch (final ParserConfigurationException | IOException | SAXException ex) {
450            throw new CheckstyleException(UNABLE_TO_PARSE_EXCEPTION_PREFIX, ex);
451        }
452    }
453
454    /**
455     * Replaces {@code ${xxx}} style constructions in the given value
456     * with the string value of the corresponding data types.
457     *
458     * <p>Code copied from ant -
459     * http://cvs.apache.org/viewcvs/jakarta-ant/src/main/org/apache/tools/ant/ProjectHelper.java
460     *
461     * @param value The string to be scanned for property references.
462     *              May be {@code null}, in which case this
463     *              method returns immediately with no effect.
464     * @param props Mapping (String to String) of property names to their
465     *              values. Must not be {@code null}.
466     * @param defaultValue default to use if one of the properties in value
467     *              cannot be resolved from props.
468     *
469     * @return the original string with the properties replaced, or
470     *         {@code null} if the original string is {@code null}.
471     * @throws CheckstyleException if the string contains an opening
472     *                           {@code ${} without a closing
473     *                           {@code }}
474     * @noinspection MethodWithMultipleReturnPoints
475     */
476    private static String replaceProperties(
477            String value, PropertyResolver props, String defaultValue)
478            throws CheckstyleException {
479        if (value == null) {
480            return null;
481        }
482
483        final List<String> fragments = new ArrayList<>();
484        final List<String> propertyRefs = new ArrayList<>();
485        parsePropertyString(value, fragments, propertyRefs);
486
487        final StringBuilder sb = new StringBuilder(256);
488        final Iterator<String> fragmentsIterator = fragments.iterator();
489        final Iterator<String> propertyRefsIterator = propertyRefs.iterator();
490        while (fragmentsIterator.hasNext()) {
491            String fragment = fragmentsIterator.next();
492            if (fragment == null) {
493                final String propertyName = propertyRefsIterator.next();
494                fragment = props.resolve(propertyName);
495                if (fragment == null) {
496                    if (defaultValue != null) {
497                        sb.replace(0, sb.length(), defaultValue);
498                        break;
499                    }
500                    throw new CheckstyleException(
501                        "Property ${" + propertyName + "} has not been set");
502                }
503            }
504            sb.append(fragment);
505        }
506
507        return sb.toString();
508    }
509
510    /**
511     * Parses a string containing {@code ${xxx}} style property
512     * references into two lists. The first list is a collection
513     * of text fragments, while the other is a set of string property names.
514     * {@code null} entries in the first list indicate a property
515     * reference from the second list.
516     *
517     * <p>Code copied from ant -
518     * http://cvs.apache.org/viewcvs/jakarta-ant/src/main/org/apache/tools/ant/ProjectHelper.java
519     *
520     * @param value     Text to parse. Must not be {@code null}.
521     * @param fragments List to add text fragments to.
522     *                  Must not be {@code null}.
523     * @param propertyRefs List to add property names to.
524     *                     Must not be {@code null}.
525     *
526     * @throws CheckstyleException if the string contains an opening
527     *                           {@code ${} without a closing
528     *                           {@code }}
529     */
530    private static void parsePropertyString(String value,
531                                           List<String> fragments,
532                                           List<String> propertyRefs)
533            throws CheckstyleException {
534        int prev = 0;
535        //search for the next instance of $ from the 'prev' position
536        int pos = value.indexOf(DOLLAR_SIGN, prev);
537        while (pos >= 0) {
538            //if there was any text before this, add it as a fragment
539            if (pos > 0) {
540                fragments.add(value.substring(prev, pos));
541            }
542            //if we are at the end of the string, we tack on a $
543            //then move past it
544            if (pos == value.length() - 1) {
545                fragments.add(String.valueOf(DOLLAR_SIGN));
546                prev = pos + 1;
547            }
548            else if (value.charAt(pos + 1) == '{') {
549                //property found, extract its name or bail on a typo
550                final int endName = value.indexOf('}', pos);
551                if (endName == -1) {
552                    throw new CheckstyleException("Syntax error in property: "
553                                                    + value);
554                }
555                final String propertyName = value.substring(pos + 2, endName);
556                fragments.add(null);
557                propertyRefs.add(propertyName);
558                prev = endName + 1;
559            }
560            else {
561                if (value.charAt(pos + 1) == DOLLAR_SIGN) {
562                    //backwards compatibility two $ map to one mode
563                    fragments.add(String.valueOf(DOLLAR_SIGN));
564                }
565                else {
566                    //new behaviour: $X maps to $X for all values of X!='$'
567                    fragments.add(value.substring(pos, pos + 2));
568                }
569                prev = pos + 2;
570            }
571
572            //search for the next instance of $ from the 'prev' position
573            pos = value.indexOf(DOLLAR_SIGN, prev);
574        }
575        //no more $ signs found
576        //if there is any tail to the file, append it
577        if (prev < value.length()) {
578            fragments.add(value.substring(prev));
579        }
580    }
581
582    /**
583     * Implements the SAX document handler interfaces, so they do not
584     * appear in the public API of the ConfigurationLoader.
585     */
586    private final class InternalLoader
587        extends XmlLoader {
588
589        /** Module elements. */
590        private static final String MODULE = "module";
591        /** Name attribute. */
592        private static final String NAME = "name";
593        /** Property element. */
594        private static final String PROPERTY = "property";
595        /** Value attribute. */
596        private static final String VALUE = "value";
597        /** Default attribute. */
598        private static final String DEFAULT = "default";
599        /** Name of the severity property. */
600        private static final String SEVERITY = "severity";
601        /** Name of the message element. */
602        private static final String MESSAGE = "message";
603        /** Name of the message element. */
604        private static final String METADATA = "metadata";
605        /** Name of the key attribute. */
606        private static final String KEY = "key";
607
608        /**
609         * Creates a new InternalLoader.
610         * @throws SAXException if an error occurs
611         * @throws ParserConfigurationException if an error occurs
612         */
613        InternalLoader()
614                throws SAXException, ParserConfigurationException {
615            super(createIdToResourceNameMap());
616        }
617
618        @Override
619        public void startElement(String uri,
620                                 String localName,
621                                 String qName,
622                                 Attributes attributes)
623                throws SAXException {
624            if (qName.equals(MODULE)) {
625                //create configuration
626                final String originalName = attributes.getValue(NAME);
627                final String name = threadModeSettings.resolveName(originalName);
628                final DefaultConfiguration conf =
629                    new DefaultConfiguration(name, threadModeSettings);
630
631                if (configuration == null) {
632                    configuration = conf;
633                }
634
635                //add configuration to it's parent
636                if (!configStack.isEmpty()) {
637                    final DefaultConfiguration top =
638                        configStack.peek();
639                    top.addChild(conf);
640                }
641
642                configStack.push(conf);
643            }
644            else if (qName.equals(PROPERTY)) {
645                //extract value and name
646                final String value;
647                try {
648                    value = replaceProperties(attributes.getValue(VALUE),
649                        overridePropsResolver, attributes.getValue(DEFAULT));
650                }
651                catch (final CheckstyleException ex) {
652                    // -@cs[IllegalInstantiation] SAXException is in the overridden method signature
653                    throw new SAXException(ex);
654                }
655                final String name = attributes.getValue(NAME);
656
657                //add to attributes of configuration
658                final DefaultConfiguration top =
659                    configStack.peek();
660                top.addAttribute(name, value);
661            }
662            else if (qName.equals(MESSAGE)) {
663                //extract key and value
664                final String key = attributes.getValue(KEY);
665                final String value = attributes.getValue(VALUE);
666
667                //add to messages of configuration
668                final DefaultConfiguration top = configStack.peek();
669                top.addMessage(key, value);
670            }
671            else {
672                if (!qName.equals(METADATA)) {
673                    throw new IllegalStateException("Unknown name:" + qName + ".");
674                }
675            }
676        }
677
678        @Override
679        public void endElement(String uri,
680                               String localName,
681                               String qName) throws SAXException {
682            if (qName.equals(MODULE)) {
683                final Configuration recentModule =
684                    configStack.pop();
685
686                // get severity attribute if it exists
687                SeverityLevel level = null;
688                if (containsAttribute(recentModule, SEVERITY)) {
689                    try {
690                        final String severity = recentModule.getAttribute(SEVERITY);
691                        level = SeverityLevel.getInstance(severity);
692                    }
693                    catch (final CheckstyleException ex) {
694                        // -@cs[IllegalInstantiation] SAXException is in the overridden
695                        // method signature
696                        throw new SAXException(
697                                "Problem during accessing '" + SEVERITY + "' attribute for "
698                                        + recentModule.getName(), ex);
699                    }
700                }
701
702                // omit this module if these should be omitted and the module
703                // has the severity 'ignore'
704                final boolean omitModule = omitIgnoredModules
705                    && level == SeverityLevel.IGNORE;
706
707                if (omitModule && !configStack.isEmpty()) {
708                    final DefaultConfiguration parentModule =
709                        configStack.peek();
710                    parentModule.removeChild(recentModule);
711                }
712            }
713        }
714
715        /**
716         * Util method to recheck attribute in module.
717         * @param module module to check
718         * @param attributeName name of attribute in module to find
719         * @return true if attribute is present in module
720         */
721        private boolean containsAttribute(Configuration module, String attributeName) {
722            final String[] names = module.getAttributeNames();
723            final Optional<String> result = Arrays.stream(names)
724                    .filter(name -> name.equals(attributeName)).findFirst();
725            return result.isPresent();
726        }
727
728    }
729
730}