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.CommonUtils;
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 resource for version 1_0 of the configuration dtd. */
079    private static final String DTD_CONFIGURATION_NAME_1_0 =
080        "com/puppycrawl/tools/checkstyle/configuration_1_0.dtd";
081
082    /** The public ID for version 1_1 of the configuration dtd. */
083    private static final String DTD_PUBLIC_ID_1_1 =
084        "-//Puppy Crawl//DTD Check Configuration 1.1//EN";
085
086    /** The resource for version 1_1 of the configuration dtd. */
087    private static final String DTD_CONFIGURATION_NAME_1_1 =
088        "com/puppycrawl/tools/checkstyle/configuration_1_1.dtd";
089
090    /** The public ID for version 1_2 of the configuration dtd. */
091    private static final String DTD_PUBLIC_ID_1_2 =
092        "-//Puppy Crawl//DTD Check Configuration 1.2//EN";
093
094    /** The resource for version 1_2 of the configuration dtd. */
095    private static final String DTD_CONFIGURATION_NAME_1_2 =
096        "com/puppycrawl/tools/checkstyle/configuration_1_2.dtd";
097
098    /** The public ID for version 1_3 of the configuration dtd. */
099    private static final String DTD_PUBLIC_ID_1_3 =
100        "-//Puppy Crawl//DTD Check Configuration 1.3//EN";
101
102    /** The resource for version 1_3 of the configuration dtd. */
103    private static final String DTD_CONFIGURATION_NAME_1_3 =
104        "com/puppycrawl/tools/checkstyle/configuration_1_3.dtd";
105
106    /** Prefix for the exception when unable to parse resource. */
107    private static final String UNABLE_TO_PARSE_EXCEPTION_PREFIX = "unable to parse"
108            + " configuration stream";
109
110    /** Dollar sign literal. */
111    private static final char DOLLAR_SIGN = '$';
112
113    /** The SAX document handler. */
114    private final InternalLoader saxHandler;
115
116    /** Property resolver. **/
117    private final PropertyResolver overridePropsResolver;
118    /** The loaded configurations. **/
119    private final Deque<DefaultConfiguration> configStack = new ArrayDeque<>();
120
121    /** Flags if modules with the severity 'ignore' should be omitted. */
122    private final boolean omitIgnoredModules;
123
124    /** The thread mode configuration. */
125    private final ThreadModeSettings threadModeSettings;
126
127    /** The Configuration that is being built. */
128    private Configuration configuration;
129
130    /**
131     * Creates a new {@code ConfigurationLoader} instance.
132     * @param overrideProps resolver for overriding properties
133     * @param omitIgnoredModules {@code true} if ignored modules should be
134     *         omitted
135     * @param threadModeSettings the thread mode configuration
136     * @throws ParserConfigurationException if an error occurs
137     * @throws SAXException if an error occurs
138     */
139    private ConfigurationLoader(final PropertyResolver overrideProps,
140                                final boolean omitIgnoredModules,
141                                final ThreadModeSettings threadModeSettings)
142            throws ParserConfigurationException, SAXException {
143        saxHandler = new InternalLoader();
144        overridePropsResolver = overrideProps;
145        this.omitIgnoredModules = omitIgnoredModules;
146        this.threadModeSettings = threadModeSettings;
147    }
148
149    /**
150     * Creates mapping between local resources and dtd ids.
151     * @return map between local resources and dtd ids.
152     */
153    private static Map<String, String> createIdToResourceNameMap() {
154        final Map<String, String> map = new HashMap<>();
155        map.put(DTD_PUBLIC_ID_1_0, DTD_CONFIGURATION_NAME_1_0);
156        map.put(DTD_PUBLIC_ID_1_1, DTD_CONFIGURATION_NAME_1_1);
157        map.put(DTD_PUBLIC_ID_1_2, DTD_CONFIGURATION_NAME_1_2);
158        map.put(DTD_PUBLIC_ID_1_3, DTD_CONFIGURATION_NAME_1_3);
159        return map;
160    }
161
162    /**
163     * Parses the specified input source loading the configuration information.
164     * The stream wrapped inside the source, if any, is NOT
165     * explicitly closed after parsing, it is the responsibility of
166     * the caller to close the stream.
167     *
168     * @param source the source that contains the configuration data
169     * @throws IOException if an error occurs
170     * @throws SAXException if an error occurs
171     */
172    private void parseInputSource(InputSource source)
173            throws IOException, SAXException {
174        saxHandler.parseInputSource(source);
175    }
176
177    /**
178     * Returns the module configurations in a specified file.
179     * @param config location of config file, can be either a URL or a filename
180     * @param overridePropsResolver overriding properties
181     * @return the check configurations
182     * @throws CheckstyleException if an error occurs
183     */
184    public static Configuration loadConfiguration(String config,
185            PropertyResolver overridePropsResolver) throws CheckstyleException {
186        return loadConfiguration(config, overridePropsResolver, IgnoredModulesOptions.EXECUTE);
187    }
188
189    /**
190     * Returns the module configurations in a specified file.
191     * @param config location of config file, can be either a URL or a filename
192     * @param overridePropsResolver overriding properties
193     * @param threadModeSettings the thread mode configuration
194     * @return the check configurations
195     * @throws CheckstyleException if an error occurs
196     */
197    public static Configuration loadConfiguration(String config,
198            PropertyResolver overridePropsResolver, ThreadModeSettings threadModeSettings)
199            throws CheckstyleException {
200        return loadConfiguration(config, overridePropsResolver,
201                IgnoredModulesOptions.EXECUTE, threadModeSettings);
202    }
203
204    /**
205     * Returns the module configurations in a specified file.
206     *
207     * @param config location of config file, can be either a URL or a filename
208     * @param overridePropsResolver overriding properties
209     * @param omitIgnoredModules {@code true} if modules with severity
210     *            'ignore' should be omitted, {@code false} otherwise
211     * @return the check configurations
212     * @throws CheckstyleException if an error occurs
213     * @deprecated in order to fulfill demands of BooleanParameter IDEA check.
214     * @noinspection BooleanParameter
215     */
216    @Deprecated
217    public static Configuration loadConfiguration(String config,
218        PropertyResolver overridePropsResolver, boolean omitIgnoredModules)
219            throws CheckstyleException {
220        return loadConfiguration(config, overridePropsResolver, omitIgnoredModules,
221                ThreadModeSettings.SINGLE_THREAD_MODE_INSTANCE);
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     * @param threadModeSettings the thread mode configuration
232     * @return the check configurations
233     * @throws CheckstyleException if an error occurs
234     * @deprecated in order to fulfill demands of BooleanParameter IDEA check.
235     * @noinspection BooleanParameter, WeakerAccess
236     */
237    @Deprecated
238    public static Configuration loadConfiguration(String config,
239            PropertyResolver overridePropsResolver,
240            boolean omitIgnoredModules, ThreadModeSettings threadModeSettings)
241            throws CheckstyleException {
242        // figure out if this is a File or a URL
243        final URI uri = CommonUtils.getUriByFilename(config);
244        final InputSource source = new InputSource(uri.toString());
245        return loadConfiguration(source, overridePropsResolver,
246                omitIgnoredModules, threadModeSettings);
247    }
248
249    /**
250     * Returns the module configurations from a specified input stream.
251     * Note that clients are required to close the given stream by themselves
252     *
253     * @param configStream the input stream to the Checkstyle configuration
254     * @param overridePropsResolver overriding properties
255     * @param omitIgnoredModules {@code true} if modules with severity
256     *            'ignore' should be omitted, {@code false} otherwise
257     * @return the check configurations
258     * @throws CheckstyleException if an error occurs
259     *
260     * @deprecated As this method does not provide a valid system ID,
261     *     preventing resolution of external entities, a
262     *     {@link #loadConfiguration(InputSource,PropertyResolver,boolean)
263     *          version using an InputSource}
264     *     should be used instead
265     * @noinspection BooleanParameter
266     */
267    @Deprecated
268    public static Configuration loadConfiguration(InputStream configStream,
269        PropertyResolver overridePropsResolver, boolean omitIgnoredModules)
270            throws CheckstyleException {
271        return loadConfiguration(new InputSource(configStream),
272                                 overridePropsResolver, omitIgnoredModules);
273    }
274
275    /**
276     * Returns the module configurations from a specified input source.
277     * Note that if the source does wrap an open byte or character
278     * stream, clients are required to close that stream by themselves
279     *
280     * @param configSource the input stream to the Checkstyle configuration
281     * @param overridePropsResolver overriding properties
282     * @param omitIgnoredModules {@code true} if modules with severity
283     *            'ignore' should be omitted, {@code false} otherwise
284     * @return the check configurations
285     * @throws CheckstyleException if an error occurs
286     * @deprecated in order to fulfill demands of BooleanParameter IDEA check.
287     * @noinspection BooleanParameter
288     */
289    @Deprecated
290    public static Configuration loadConfiguration(InputSource configSource,
291            PropertyResolver overridePropsResolver, boolean omitIgnoredModules)
292            throws CheckstyleException {
293        return loadConfiguration(configSource, overridePropsResolver,
294                omitIgnoredModules, ThreadModeSettings.SINGLE_THREAD_MODE_INSTANCE);
295    }
296
297    /**
298     * Returns the module configurations from a specified input source.
299     * Note that if the source does wrap an open byte or character
300     * stream, clients are required to close that stream by themselves
301     *
302     * @param configSource the input stream to the Checkstyle configuration
303     * @param overridePropsResolver overriding properties
304     * @param omitIgnoredModules {@code true} if modules with severity
305     *            'ignore' should be omitted, {@code false} otherwise
306     * @param threadModeSettings the thread mode configuration
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, WeakerAccess
311     */
312    @Deprecated
313    public static Configuration loadConfiguration(InputSource configSource,
314        PropertyResolver overridePropsResolver,
315        boolean omitIgnoredModules, ThreadModeSettings threadModeSettings)
316            throws CheckstyleException {
317        try {
318            final ConfigurationLoader loader =
319                new ConfigurationLoader(overridePropsResolver,
320                                        omitIgnoredModules, threadModeSettings);
321            loader.parseInputSource(configSource);
322            return loader.configuration;
323        }
324        catch (final SAXParseException ex) {
325            final String message = String.format(Locale.ROOT, SAX_PARSE_EXCEPTION_FORMAT,
326                    UNABLE_TO_PARSE_EXCEPTION_PREFIX,
327                    ex.getMessage(), ex.getLineNumber(), ex.getColumnNumber());
328            throw new CheckstyleException(message, ex);
329        }
330        catch (final ParserConfigurationException | IOException | SAXException ex) {
331            throw new CheckstyleException(UNABLE_TO_PARSE_EXCEPTION_PREFIX, ex);
332        }
333    }
334
335    /**
336     * Returns the module configurations in a specified file.
337     *
338     * @param config location of config file, can be either a URL or a filename
339     * @param overridePropsResolver overriding properties
340     * @param ignoredModulesOptions {@code OMIT} if modules with severity
341     *            'ignore' should be omitted, {@code EXECUTE} otherwise
342     * @return the check configurations
343     * @throws CheckstyleException if an error occurs
344     */
345    public static Configuration loadConfiguration(String config,
346                                                  PropertyResolver overridePropsResolver,
347                                                  IgnoredModulesOptions ignoredModulesOptions)
348            throws CheckstyleException {
349        return loadConfiguration(config, overridePropsResolver, ignoredModulesOptions,
350                ThreadModeSettings.SINGLE_THREAD_MODE_INSTANCE);
351    }
352
353    /**
354     * Returns the module configurations in a specified file.
355     *
356     * @param config location of config file, can be either a URL or a filename
357     * @param overridePropsResolver overriding properties
358     * @param ignoredModulesOptions {@code OMIT} if modules with severity
359     *            'ignore' should be omitted, {@code EXECUTE} otherwise
360     * @param threadModeSettings the thread mode configuration
361     * @return the check configurations
362     * @throws CheckstyleException if an error occurs
363     */
364    public static Configuration loadConfiguration(String config,
365                                                  PropertyResolver overridePropsResolver,
366                                                  IgnoredModulesOptions ignoredModulesOptions,
367                                                  ThreadModeSettings threadModeSettings)
368            throws CheckstyleException {
369        // figure out if this is a File or a URL
370        final URI uri = CommonUtils.getUriByFilename(config);
371        final InputSource source = new InputSource(uri.toString());
372        return loadConfiguration(source, overridePropsResolver,
373                ignoredModulesOptions, threadModeSettings);
374    }
375
376    /**
377     * Returns the module configurations from a specified input source.
378     * Note that if the source does wrap an open byte or character
379     * stream, clients are required to close that stream by themselves
380     *
381     * @param configSource the input stream to the Checkstyle configuration
382     * @param overridePropsResolver overriding properties
383     * @param ignoredModulesOptions {@code OMIT} if modules with severity
384     *            'ignore' should be omitted, {@code EXECUTE} otherwise
385     * @return the check configurations
386     * @throws CheckstyleException if an error occurs
387     */
388    public static Configuration loadConfiguration(InputSource configSource,
389                                                  PropertyResolver overridePropsResolver,
390                                                  IgnoredModulesOptions ignoredModulesOptions)
391            throws CheckstyleException {
392        return loadConfiguration(configSource, overridePropsResolver,
393                ignoredModulesOptions, ThreadModeSettings.SINGLE_THREAD_MODE_INSTANCE);
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     * @param threadModeSettings the thread mode configuration
406     * @return the check configurations
407     * @throws CheckstyleException if an error occurs
408     * @noinspection WeakerAccess
409     */
410    public static Configuration loadConfiguration(InputSource configSource,
411                                                  PropertyResolver overridePropsResolver,
412                                                  IgnoredModulesOptions ignoredModulesOptions,
413                                                  ThreadModeSettings threadModeSettings)
414            throws CheckstyleException {
415        try {
416            final boolean omitIgnoreModules = ignoredModulesOptions == IgnoredModulesOptions.OMIT;
417            final ConfigurationLoader loader =
418                    new ConfigurationLoader(overridePropsResolver,
419                            omitIgnoreModules, threadModeSettings);
420            loader.parseInputSource(configSource);
421            return loader.configuration;
422        }
423        catch (final SAXParseException ex) {
424            final String message = String.format(Locale.ROOT, SAX_PARSE_EXCEPTION_FORMAT,
425                    UNABLE_TO_PARSE_EXCEPTION_PREFIX,
426                    ex.getMessage(), ex.getLineNumber(), ex.getColumnNumber());
427            throw new CheckstyleException(message, ex);
428        }
429        catch (final ParserConfigurationException | IOException | SAXException ex) {
430            throw new CheckstyleException(UNABLE_TO_PARSE_EXCEPTION_PREFIX, ex);
431        }
432    }
433
434    /**
435     * Replaces {@code ${xxx}} style constructions in the given value
436     * with the string value of the corresponding data types.
437     *
438     * <p>Code copied from ant -
439     * http://cvs.apache.org/viewcvs/jakarta-ant/src/main/org/apache/tools/ant/ProjectHelper.java
440     *
441     * @param value The string to be scanned for property references.
442     *              May be {@code null}, in which case this
443     *              method returns immediately with no effect.
444     * @param props Mapping (String to String) of property names to their
445     *              values. Must not be {@code null}.
446     * @param defaultValue default to use if one of the properties in value
447     *              cannot be resolved from props.
448     *
449     * @return the original string with the properties replaced, or
450     *         {@code null} if the original string is {@code null}.
451     * @throws CheckstyleException if the string contains an opening
452     *                           {@code ${} without a closing
453     *                           {@code }}
454     * @noinspection MethodWithMultipleReturnPoints
455     */
456    private static String replaceProperties(
457            String value, PropertyResolver props, String defaultValue)
458            throws CheckstyleException {
459        if (value == null) {
460            return null;
461        }
462
463        final List<String> fragments = new ArrayList<>();
464        final List<String> propertyRefs = new ArrayList<>();
465        parsePropertyString(value, fragments, propertyRefs);
466
467        final StringBuilder sb = new StringBuilder(256);
468        final Iterator<String> fragmentsIterator = fragments.iterator();
469        final Iterator<String> propertyRefsIterator = propertyRefs.iterator();
470        while (fragmentsIterator.hasNext()) {
471            String fragment = fragmentsIterator.next();
472            if (fragment == null) {
473                final String propertyName = propertyRefsIterator.next();
474                fragment = props.resolve(propertyName);
475                if (fragment == null) {
476                    if (defaultValue != null) {
477                        sb.replace(0, sb.length(), defaultValue);
478                        break;
479                    }
480                    throw new CheckstyleException(
481                        "Property ${" + propertyName + "} has not been set");
482                }
483            }
484            sb.append(fragment);
485        }
486
487        return sb.toString();
488    }
489
490    /**
491     * Parses a string containing {@code ${xxx}} style property
492     * references into two lists. The first list is a collection
493     * of text fragments, while the other is a set of string property names.
494     * {@code null} entries in the first list indicate a property
495     * reference from the second list.
496     *
497     * <p>Code copied from ant -
498     * http://cvs.apache.org/viewcvs/jakarta-ant/src/main/org/apache/tools/ant/ProjectHelper.java
499     *
500     * @param value     Text to parse. Must not be {@code null}.
501     * @param fragments List to add text fragments to.
502     *                  Must not be {@code null}.
503     * @param propertyRefs List to add property names to.
504     *                     Must not be {@code null}.
505     *
506     * @throws CheckstyleException if the string contains an opening
507     *                           {@code ${} without a closing
508     *                           {@code }}
509     */
510    private static void parsePropertyString(String value,
511                                           List<String> fragments,
512                                           List<String> propertyRefs)
513            throws CheckstyleException {
514        int prev = 0;
515        //search for the next instance of $ from the 'prev' position
516        int pos = value.indexOf(DOLLAR_SIGN, prev);
517        while (pos >= 0) {
518            //if there was any text before this, add it as a fragment
519            if (pos > 0) {
520                fragments.add(value.substring(prev, pos));
521            }
522            //if we are at the end of the string, we tack on a $
523            //then move past it
524            if (pos == value.length() - 1) {
525                fragments.add(String.valueOf(DOLLAR_SIGN));
526                prev = pos + 1;
527            }
528            else if (value.charAt(pos + 1) == '{') {
529                //property found, extract its name or bail on a typo
530                final int endName = value.indexOf('}', pos);
531                if (endName == -1) {
532                    throw new CheckstyleException("Syntax error in property: "
533                                                    + value);
534                }
535                final String propertyName = value.substring(pos + 2, endName);
536                fragments.add(null);
537                propertyRefs.add(propertyName);
538                prev = endName + 1;
539            }
540            else {
541                if (value.charAt(pos + 1) == DOLLAR_SIGN) {
542                    //backwards compatibility two $ map to one mode
543                    fragments.add(String.valueOf(DOLLAR_SIGN));
544                    prev = pos + 2;
545                }
546                else {
547                    //new behaviour: $X maps to $X for all values of X!='$'
548                    fragments.add(value.substring(pos, pos + 2));
549                    prev = pos + 2;
550                }
551            }
552
553            //search for the next instance of $ from the 'prev' position
554            pos = value.indexOf(DOLLAR_SIGN, prev);
555        }
556        //no more $ signs found
557        //if there is any tail to the file, append it
558        if (prev < value.length()) {
559            fragments.add(value.substring(prev));
560        }
561    }
562
563    /**
564     * Implements the SAX document handler interfaces, so they do not
565     * appear in the public API of the ConfigurationLoader.
566     */
567    private final class InternalLoader
568        extends XmlLoader {
569
570        /** Module elements. */
571        private static final String MODULE = "module";
572        /** Name attribute. */
573        private static final String NAME = "name";
574        /** Property element. */
575        private static final String PROPERTY = "property";
576        /** Value attribute. */
577        private static final String VALUE = "value";
578        /** Default attribute. */
579        private static final String DEFAULT = "default";
580        /** Name of the severity property. */
581        private static final String SEVERITY = "severity";
582        /** Name of the message element. */
583        private static final String MESSAGE = "message";
584        /** Name of the message element. */
585        private static final String METADATA = "metadata";
586        /** Name of the key attribute. */
587        private static final String KEY = "key";
588
589        /**
590         * Creates a new InternalLoader.
591         * @throws SAXException if an error occurs
592         * @throws ParserConfigurationException if an error occurs
593         */
594        InternalLoader()
595                throws SAXException, ParserConfigurationException {
596            super(createIdToResourceNameMap());
597        }
598
599        @Override
600        public void startElement(String uri,
601                                 String localName,
602                                 String qName,
603                                 Attributes attributes)
604                throws SAXException {
605            if (qName.equals(MODULE)) {
606                //create configuration
607                final String originalName = attributes.getValue(NAME);
608                final String name = threadModeSettings.resolveName(originalName);
609                final DefaultConfiguration conf =
610                    new DefaultConfiguration(name, threadModeSettings);
611
612                if (configuration == null) {
613                    configuration = conf;
614                }
615
616                //add configuration to it's parent
617                if (!configStack.isEmpty()) {
618                    final DefaultConfiguration top =
619                        configStack.peek();
620                    top.addChild(conf);
621                }
622
623                configStack.push(conf);
624            }
625            else if (qName.equals(PROPERTY)) {
626                //extract value and name
627                final String value;
628                try {
629                    value = replaceProperties(attributes.getValue(VALUE),
630                        overridePropsResolver, attributes.getValue(DEFAULT));
631                }
632                catch (final CheckstyleException ex) {
633                    // -@cs[IllegalInstantiation] SAXException is in the overridden method signature
634                    throw new SAXException(ex);
635                }
636                final String name = attributes.getValue(NAME);
637
638                //add to attributes of configuration
639                final DefaultConfiguration top =
640                    configStack.peek();
641                top.addAttribute(name, value);
642            }
643            else if (qName.equals(MESSAGE)) {
644                //extract key and value
645                final String key = attributes.getValue(KEY);
646                final String value = attributes.getValue(VALUE);
647
648                //add to messages of configuration
649                final DefaultConfiguration top = configStack.peek();
650                top.addMessage(key, value);
651            }
652            else {
653                if (!qName.equals(METADATA)) {
654                    throw new IllegalStateException("Unknown name:" + qName + ".");
655                }
656            }
657        }
658
659        @Override
660        public void endElement(String uri,
661                               String localName,
662                               String qName) throws SAXException {
663            if (qName.equals(MODULE)) {
664                final Configuration recentModule =
665                    configStack.pop();
666
667                // get severity attribute if it exists
668                SeverityLevel level = null;
669                if (containsAttribute(recentModule, SEVERITY)) {
670                    try {
671                        final String severity = recentModule.getAttribute(SEVERITY);
672                        level = SeverityLevel.getInstance(severity);
673                    }
674                    catch (final CheckstyleException ex) {
675                        // -@cs[IllegalInstantiation] SAXException is in the overridden
676                        // method signature
677                        throw new SAXException(
678                                "Problem during accessing '" + SEVERITY + "' attribute for "
679                                        + recentModule.getName(), ex);
680                    }
681                }
682
683                // omit this module if these should be omitted and the module
684                // has the severity 'ignore'
685                final boolean omitModule = omitIgnoredModules
686                    && level == SeverityLevel.IGNORE;
687
688                if (omitModule && !configStack.isEmpty()) {
689                    final DefaultConfiguration parentModule =
690                        configStack.peek();
691                    parentModule.removeChild(recentModule);
692                }
693            }
694        }
695
696        /**
697         * Util method to recheck attribute in module.
698         * @param module module to check
699         * @param attributeName name of attribute in module to find
700         * @return true if attribute is present in module
701         */
702        private boolean containsAttribute(Configuration module, String attributeName) {
703            final String[] names = module.getAttributeNames();
704            final Optional<String> result = Arrays.stream(names)
705                    .filter(name -> name.equals(attributeName)).findFirst();
706            return result.isPresent();
707        }
708
709    }
710
711}