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