View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2026 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.filters;
21  
22  import java.io.FileNotFoundException;
23  import java.io.IOException;
24  import java.util.HashMap;
25  import java.util.HashSet;
26  import java.util.Locale;
27  import java.util.Map;
28  import java.util.Set;
29  import java.util.regex.PatternSyntaxException;
30  
31  import javax.xml.parsers.ParserConfigurationException;
32  
33  import org.xml.sax.Attributes;
34  import org.xml.sax.InputSource;
35  import org.xml.sax.SAXException;
36  
37  import com.puppycrawl.tools.checkstyle.TreeWalkerFilter;
38  import com.puppycrawl.tools.checkstyle.XmlLoader;
39  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
40  import com.puppycrawl.tools.checkstyle.api.FilterSet;
41  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
42  
43  /**
44   * Loads a filter chain of suppressions.
45   */
46  public final class SuppressionsLoader
47      extends XmlLoader {
48  
49      /** The public ID for the configuration dtd. */
50      private static final String DTD_PUBLIC_ID_1_0 =
51          "-//Puppy Crawl//DTD Suppressions 1.0//EN";
52      /** The new public ID for version 1_0 configuration dtd. */
53      private static final String DTD_PUBLIC_CS_ID_1_0 =
54          "-//Checkstyle//DTD SuppressionFilter Configuration 1.0//EN";
55      /** The resource for the configuration dtd. */
56      private static final String DTD_SUPPRESSIONS_NAME_1_0 =
57          "com/puppycrawl/tools/checkstyle/suppressions_1_0.dtd";
58      /** The public ID for the configuration dtd. */
59      private static final String DTD_PUBLIC_ID_1_1 =
60          "-//Puppy Crawl//DTD Suppressions 1.1//EN";
61      /** The new public ID for version 1_1 configuration dtd. */
62      private static final String DTD_PUBLIC_CS_ID_1_1 =
63          "-//Checkstyle//DTD SuppressionFilter Configuration 1.1//EN";
64      /** The resource for the configuration dtd. */
65      private static final String DTD_SUPPRESSIONS_NAME_1_1 =
66          "com/puppycrawl/tools/checkstyle/suppressions_1_1.dtd";
67      /** The public ID for the configuration dtd. */
68      private static final String DTD_PUBLIC_ID_1_2 =
69          "-//Puppy Crawl//DTD Suppressions 1.2//EN";
70      /** The new public ID for version 1_2 configuration dtd. */
71      private static final String DTD_PUBLIC_CS_ID_1_2 =
72          "-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN";
73      /** The resource for the configuration dtd. */
74      private static final String DTD_SUPPRESSIONS_NAME_1_2 =
75          "com/puppycrawl/tools/checkstyle/suppressions_1_2.dtd";
76  
77      /** The old public ID for the configuration dtd. */
78      private static final String DTD_PUBLIC_ID_1_1_XPATH_EXPERIMENTAL =
79          "-//Puppy Crawl//DTD Suppressions Xpath Experimental 1.1//EN";
80      /** The old public ID for version 1_1 configuration dtd. */
81      private static final String DTD_PUBLIC_CS_ID_1_1_XPATH_EXPERIMENTAL =
82          "-//Checkstyle//DTD SuppressionXpathFilter Experimental Configuration 1.1//EN";
83      /** The new public ID for the configuration dtd. */
84      private static final String DTD_PUBLIC_ID_1_1_XPATH =
85          "-//Puppy Crawl//DTD Suppressions Xpath 1.1//EN";
86      /** The new public ID for version 1_1 configuration dtd. */
87      private static final String DTD_PUBLIC_CS_ID_1_1_XPATH =
88          "-//Checkstyle//DTD SuppressionXpathFilter Configuration 1.1//EN";
89      /** The resource for the configuration dtd. */
90      private static final String DTD_SUPPRESSIONS_NAME_1_1_XPATH =
91          "com/puppycrawl/tools/checkstyle/suppressions_1_1_xpath.dtd";
92  
93      /** The old public ID for the configuration dtd. */
94      private static final String DTD_PUBLIC_ID_1_2_XPATH_EXPERIMENTAL =
95          "-//Puppy Crawl//DTD Suppressions Xpath Experimental 1.2//EN";
96      /** The old public ID for version 1_2 configuration dtd. */
97      private static final String DTD_PUBLIC_CS_ID_1_2_XPATH_EXPERIMENTAL =
98          "-//Checkstyle//DTD SuppressionXpathFilter Experimental Configuration 1.2//EN";
99      /** 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 }