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