001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2026 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.filters;
021
022import java.io.FileNotFoundException;
023import java.io.IOException;
024import java.util.HashMap;
025import java.util.HashSet;
026import java.util.Locale;
027import java.util.Map;
028import java.util.Set;
029import java.util.regex.PatternSyntaxException;
030
031import javax.xml.parsers.ParserConfigurationException;
032
033import org.xml.sax.Attributes;
034import org.xml.sax.InputSource;
035import org.xml.sax.SAXException;
036
037import com.puppycrawl.tools.checkstyle.TreeWalkerFilter;
038import com.puppycrawl.tools.checkstyle.XmlLoader;
039import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
040import com.puppycrawl.tools.checkstyle.api.FilterSet;
041import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
042
043/**
044 * Loads a filter chain of suppressions.
045 */
046public final class SuppressionsLoader
047    extends XmlLoader {
048
049    /** The public ID for the configuration dtd. */
050    private static final String DTD_PUBLIC_ID_1_0 =
051        "-//Puppy Crawl//DTD Suppressions 1.0//EN";
052    /** The new public ID for version 1_0 configuration dtd. */
053    private static final String DTD_PUBLIC_CS_ID_1_0 =
054        "-//Checkstyle//DTD SuppressionFilter Configuration 1.0//EN";
055    /** The resource for the configuration dtd. */
056    private static final String DTD_SUPPRESSIONS_NAME_1_0 =
057        "com/puppycrawl/tools/checkstyle/suppressions_1_0.dtd";
058    /** The public ID for the configuration dtd. */
059    private static final String DTD_PUBLIC_ID_1_1 =
060        "-//Puppy Crawl//DTD Suppressions 1.1//EN";
061    /** The new public ID for version 1_1 configuration dtd. */
062    private static final String DTD_PUBLIC_CS_ID_1_1 =
063        "-//Checkstyle//DTD SuppressionFilter Configuration 1.1//EN";
064    /** The resource for the configuration dtd. */
065    private static final String DTD_SUPPRESSIONS_NAME_1_1 =
066        "com/puppycrawl/tools/checkstyle/suppressions_1_1.dtd";
067    /** The public ID for the configuration dtd. */
068    private static final String DTD_PUBLIC_ID_1_2 =
069        "-//Puppy Crawl//DTD Suppressions 1.2//EN";
070    /** The new public ID for version 1_2 configuration dtd. */
071    private static final String DTD_PUBLIC_CS_ID_1_2 =
072        "-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN";
073    /** The resource for the configuration dtd. */
074    private static final String DTD_SUPPRESSIONS_NAME_1_2 =
075        "com/puppycrawl/tools/checkstyle/suppressions_1_2.dtd";
076
077    /** The old public ID for the configuration dtd. */
078    private static final String DTD_PUBLIC_ID_1_1_XPATH_EXPERIMENTAL =
079        "-//Puppy Crawl//DTD Suppressions Xpath Experimental 1.1//EN";
080    /** The old public ID for version 1_1 configuration dtd. */
081    private static final String DTD_PUBLIC_CS_ID_1_1_XPATH_EXPERIMENTAL =
082        "-//Checkstyle//DTD SuppressionXpathFilter Experimental Configuration 1.1//EN";
083    /** The new public ID for the configuration dtd. */
084    private static final String DTD_PUBLIC_ID_1_1_XPATH =
085        "-//Puppy Crawl//DTD Suppressions Xpath 1.1//EN";
086    /** The new public ID for version 1_1 configuration dtd. */
087    private static final String DTD_PUBLIC_CS_ID_1_1_XPATH =
088        "-//Checkstyle//DTD SuppressionXpathFilter Configuration 1.1//EN";
089    /** The resource for the configuration dtd. */
090    private static final String DTD_SUPPRESSIONS_NAME_1_1_XPATH =
091        "com/puppycrawl/tools/checkstyle/suppressions_1_1_xpath.dtd";
092
093    /** The old public ID for the configuration dtd. */
094    private static final String DTD_PUBLIC_ID_1_2_XPATH_EXPERIMENTAL =
095        "-//Puppy Crawl//DTD Suppressions Xpath Experimental 1.2//EN";
096    /** The old public ID for version 1_2 configuration dtd. */
097    private static final String DTD_PUBLIC_CS_ID_1_2_XPATH_EXPERIMENTAL =
098        "-//Checkstyle//DTD SuppressionXpathFilter Experimental Configuration 1.2//EN";
099    /** The new public ID for the configuration dtd. */
100    private static final String DTD_PUBLIC_ID_1_2_XPATH =
101        "-//Puppy Crawl//DTD Suppressions Xpath 1.2//EN";
102    /** The new public ID for version 1_2 configuration dtd. */
103    private static final String DTD_PUBLIC_CS_ID_1_2_XPATH =
104        "-//Checkstyle//DTD SuppressionXpathFilter Configuration 1.2//EN";
105    /** The resource for the configuration dtd. */
106    private static final String DTD_SUPPRESSIONS_NAME_1_2_XPATH =
107        "com/puppycrawl/tools/checkstyle/suppressions_1_2_xpath.dtd";
108
109    /** File search error message. **/
110    private static final String UNABLE_TO_FIND_ERROR_MESSAGE = "Unable to find: ";
111    /** String literal for attribute name. **/
112    private static final String ATTRIBUTE_NAME_FILES = "files";
113    /** String literal for attribute name. **/
114    private static final String ATTRIBUTE_NAME_CHECKS = "checks";
115    /** String literal for attribute name. **/
116    private static final String ATTRIBUTE_NAME_MESSAGE = "message";
117    /** String literal for attribute name. **/
118    private static final String ATTRIBUTE_NAME_ID = "id";
119    /** String literal for attribute name. **/
120    private static final String ATTRIBUTE_NAME_QUERY = "query";
121    /** String literal for attribute name. **/
122    private static final String ATTRIBUTE_NAME_LINES = "lines";
123    /** String literal for attribute name. **/
124    private static final String ATTRIBUTE_NAME_COLUMNS = "columns";
125
126    /**
127     * The filter chain to return in getAFilterChain(),
128     * configured during parsing.
129     */
130    private final FilterSet filterChain = new FilterSet();
131
132    /**
133     * The set of the {@code TreeWalkerFilter} filters. Being filled during parsing.
134     */
135    private final Set<TreeWalkerFilter> treeWalkerFilters = new HashSet<>();
136
137    /**
138     * Creates a new {@code SuppressionsLoader} instance.
139     *
140     * @throws ParserConfigurationException if an error occurs
141     * @throws SAXException if an error occurs
142     */
143    private SuppressionsLoader()
144            throws ParserConfigurationException, SAXException {
145        super(createIdToResourceNameMap());
146    }
147
148    @Override
149    public void startElement(String namespaceUri,
150                             String localName,
151                             String qName,
152                             Attributes attributes)
153            throws SAXException {
154        if ("suppress".equals(qName)) {
155            // add SuppressFilterElement filter to the filter chain
156            final SuppressFilterElement suppress = getSuppressElement(attributes);
157            filterChain.addFilter(suppress);
158        }
159        else if ("suppress-xpath".equals(qName)) {
160            final XpathFilterElement filter = getXpathFilter(attributes);
161            treeWalkerFilters.add(filter);
162        }
163    }
164
165    /**
166     * Returns the suppress element, initialized from given attributes.
167     *
168     * @param attributes the attributes of xml-tag "&lt;suppress&gt;&lt;/suppress&gt;",
169     *                   specified inside suppression file.
170     * @return the suppress element
171     * @throws SAXException if an error occurs.
172     */
173    private static SuppressFilterElement getSuppressElement(Attributes attributes)
174            throws SAXException {
175        final String checks = attributes.getValue(ATTRIBUTE_NAME_CHECKS);
176        final String modId = attributes.getValue(ATTRIBUTE_NAME_ID);
177        final String message = attributes.getValue(ATTRIBUTE_NAME_MESSAGE);
178        if (checks == null && modId == null && message == null) {
179            // -@cs[IllegalInstantiation] SAXException is in the overridden method signature
180            throw new SAXException("missing checks or id or message attribute");
181        }
182        final SuppressFilterElement suppress;
183        try {
184            final String files = attributes.getValue(ATTRIBUTE_NAME_FILES);
185            final String lines = attributes.getValue(ATTRIBUTE_NAME_LINES);
186            final String columns = attributes.getValue(ATTRIBUTE_NAME_COLUMNS);
187            suppress = new SuppressFilterElement(files, checks, message, modId, lines, columns);
188        }
189        catch (final PatternSyntaxException exc) {
190            // -@cs[IllegalInstantiation] SAXException is in the overridden method signature
191            throw new SAXException("invalid files or checks or message format", exc);
192        }
193        return suppress;
194    }
195
196    /**
197     * Returns the xpath filter, initialized from given attributes.
198     *
199     * @param attributes the attributes of xml-tag "&lt;suppress-xpath&gt;&lt;/suppress-xpath&gt;",
200     *                   specified inside suppression file.
201     * @return the xpath filter
202     * @throws SAXException if an error occurs.
203     */
204    private static XpathFilterElement getXpathFilter(Attributes attributes) throws SAXException {
205        final String checks = attributes.getValue(ATTRIBUTE_NAME_CHECKS);
206        final String modId = attributes.getValue(ATTRIBUTE_NAME_ID);
207        final String message = attributes.getValue(ATTRIBUTE_NAME_MESSAGE);
208        if (checks == null && modId == null && message == null) {
209            // -@cs[IllegalInstantiation] SAXException is in the overridden method signature
210            throw new SAXException("missing checks or id or message attribute for suppress-xpath");
211        }
212        final XpathFilterElement filter;
213        try {
214            final String files = attributes.getValue(ATTRIBUTE_NAME_FILES);
215            final String xpathQuery = attributes.getValue(ATTRIBUTE_NAME_QUERY);
216            filter = new XpathFilterElement(files, checks, message, modId, xpathQuery);
217        }
218        catch (final PatternSyntaxException exc) {
219            // -@cs[IllegalInstantiation] SAXException is in the overridden method signature
220            throw new SAXException("invalid files or checks or message format for suppress-xpath",
221                    exc);
222        }
223        return filter;
224    }
225
226    /**
227     * Returns the suppression filters in a specified file.
228     *
229     * @param filename name of the suppressions file.
230     * @return the filter chain of suppression elements specified in the file.
231     * @throws CheckstyleException if an error occurs.
232     */
233    public static FilterSet loadSuppressions(String filename)
234            throws CheckstyleException {
235        return loadSuppressions(CommonUtil.sourceFromFilename(filename), filename);
236    }
237
238    /**
239     * Returns the suppression filters in a specified source.
240     *
241     * @param source the source for the suppressions.
242     * @param sourceName the name of the source.
243     * @return the filter chain of suppression elements in source.
244     * @throws CheckstyleException if an error occurs.
245     */
246    private static FilterSet loadSuppressions(
247            InputSource source, String sourceName)
248            throws CheckstyleException {
249        return getSuppressionLoader(source, sourceName).filterChain;
250    }
251
252    /**
253     * Returns the suppression {@code TreeWalker} filters in a specified file.
254     *
255     * @param filename name of the suppressions file.
256     * @return the set of xpath suppression elements specified in the file.
257     * @throws CheckstyleException if an error occurs.
258     */
259    public static Set<TreeWalkerFilter> loadXpathSuppressions(String filename)
260            throws CheckstyleException {
261        return loadXpathSuppressions(CommonUtil.sourceFromFilename(filename), filename);
262    }
263
264    /**
265     * Returns the suppression {@code TreeWalker} filters in a specified source.
266     *
267     * @param source the source for the suppressions.
268     * @param sourceName the name of the source.
269     * @return the set of xpath suppression elements specified in source.
270     * @throws CheckstyleException if an error occurs.
271     */
272    private static Set<TreeWalkerFilter> loadXpathSuppressions(
273            InputSource source, String sourceName)
274            throws CheckstyleException {
275        return getSuppressionLoader(source, sourceName).treeWalkerFilters;
276    }
277
278    /**
279     * Parses specified source and returns the suppression loader.
280     *
281     * @param source the source for the suppressions.
282     * @param sourceName the name of the source.
283     * @return the suppression loader
284     * @throws CheckstyleException if an error occurs.
285     */
286    private static SuppressionsLoader getSuppressionLoader(InputSource source, String sourceName)
287            throws CheckstyleException {
288        try {
289            final SuppressionsLoader suppressionsLoader =
290                new SuppressionsLoader();
291            suppressionsLoader.parseInputSource(source);
292            return suppressionsLoader;
293        }
294        catch (final FileNotFoundException exc) {
295            throw new CheckstyleException(UNABLE_TO_FIND_ERROR_MESSAGE + sourceName, exc);
296        }
297        catch (final ParserConfigurationException | SAXException exc) {
298            final String message = String.format(Locale.ROOT, "Unable to parse %s - %s",
299                    sourceName, exc.getMessage());
300            throw new CheckstyleException(message, exc);
301        }
302        catch (final IOException exc) {
303            throw new CheckstyleException("Unable to read " + sourceName, exc);
304        }
305        catch (final NumberFormatException exc) {
306            final String message = String.format(Locale.ROOT, "Number format exception %s - %s",
307                    sourceName, exc.getMessage());
308            throw new CheckstyleException(message, exc);
309        }
310    }
311
312    /**
313     * Creates mapping between local resources and dtd ids.
314     *
315     * @return map between local resources and dtd ids.
316     */
317    private static Map<String, String> createIdToResourceNameMap() {
318        final Map<String, String> map = new HashMap<>();
319        map.put(DTD_PUBLIC_ID_1_0, DTD_SUPPRESSIONS_NAME_1_0);
320        map.put(DTD_PUBLIC_ID_1_1, DTD_SUPPRESSIONS_NAME_1_1);
321        map.put(DTD_PUBLIC_ID_1_2, DTD_SUPPRESSIONS_NAME_1_2);
322        map.put(DTD_PUBLIC_CS_ID_1_0, DTD_SUPPRESSIONS_NAME_1_0);
323        map.put(DTD_PUBLIC_CS_ID_1_1, DTD_SUPPRESSIONS_NAME_1_1);
324        map.put(DTD_PUBLIC_CS_ID_1_2, DTD_SUPPRESSIONS_NAME_1_2);
325
326        // 1.1 Xpath mappings
327        map.put(DTD_PUBLIC_ID_1_1_XPATH_EXPERIMENTAL, DTD_SUPPRESSIONS_NAME_1_1_XPATH);
328        map.put(DTD_PUBLIC_CS_ID_1_1_XPATH_EXPERIMENTAL, DTD_SUPPRESSIONS_NAME_1_1_XPATH);
329        map.put(DTD_PUBLIC_ID_1_1_XPATH, DTD_SUPPRESSIONS_NAME_1_1_XPATH);
330        map.put(DTD_PUBLIC_CS_ID_1_1_XPATH, DTD_SUPPRESSIONS_NAME_1_1_XPATH);
331
332        // 1.2 Xpath mappings
333        map.put(DTD_PUBLIC_ID_1_2_XPATH_EXPERIMENTAL, DTD_SUPPRESSIONS_NAME_1_2_XPATH);
334        map.put(DTD_PUBLIC_CS_ID_1_2_XPATH_EXPERIMENTAL, DTD_SUPPRESSIONS_NAME_1_2_XPATH);
335        map.put(DTD_PUBLIC_ID_1_2_XPATH, DTD_SUPPRESSIONS_NAME_1_2_XPATH);
336        map.put(DTD_PUBLIC_CS_ID_1_2_XPATH, DTD_SUPPRESSIONS_NAME_1_2_XPATH);
337        return map;
338    }
339
340}