View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 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.net.URI;
25  import java.util.HashMap;
26  import java.util.HashSet;
27  import java.util.Locale;
28  import java.util.Map;
29  import java.util.Set;
30  import java.util.regex.PatternSyntaxException;
31  
32  import javax.xml.parsers.ParserConfigurationException;
33  
34  import org.xml.sax.Attributes;
35  import org.xml.sax.InputSource;
36  import org.xml.sax.SAXException;
37  
38  import com.puppycrawl.tools.checkstyle.TreeWalkerFilter;
39  import com.puppycrawl.tools.checkstyle.XmlLoader;
40  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
41  import com.puppycrawl.tools.checkstyle.api.FilterSet;
42  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
43  
44  /**
45   * Loads a filter chain of suppressions.
46   */
47  public final class SuppressionsLoader
48      extends XmlLoader {
49  
50      /** The public ID for the configuration dtd. */
51      private static final String DTD_PUBLIC_ID_1_0 =
52          "-//Puppy Crawl//DTD Suppressions 1.0//EN";
53      /** The new public ID for version 1_0 configuration dtd. */
54      private static final String DTD_PUBLIC_CS_ID_1_0 =
55          "-//Checkstyle//DTD SuppressionFilter Configuration 1.0//EN";
56      /** The resource for the configuration dtd. */
57      private static final String DTD_SUPPRESSIONS_NAME_1_0 =
58          "com/puppycrawl/tools/checkstyle/suppressions_1_0.dtd";
59      /** The public ID for the configuration dtd. */
60      private static final String DTD_PUBLIC_ID_1_1 =
61          "-//Puppy Crawl//DTD Suppressions 1.1//EN";
62      /** The new public ID for version 1_1 configuration dtd. */
63      private static final String DTD_PUBLIC_CS_ID_1_1 =
64          "-//Checkstyle//DTD SuppressionFilter Configuration 1.1//EN";
65      /** The resource for the configuration dtd. */
66      private static final String DTD_SUPPRESSIONS_NAME_1_1 =
67          "com/puppycrawl/tools/checkstyle/suppressions_1_1.dtd";
68      /** The public ID for the configuration dtd. */
69      private static final String DTD_PUBLIC_ID_1_2 =
70          "-//Puppy Crawl//DTD Suppressions 1.2//EN";
71      /** The new public ID for version 1_2 configuration dtd. */
72      private static final String DTD_PUBLIC_CS_ID_1_2 =
73          "-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN";
74      /** The resource for the configuration dtd. */
75      private static final String DTD_SUPPRESSIONS_NAME_1_2 =
76          "com/puppycrawl/tools/checkstyle/suppressions_1_2.dtd";
77      /** The public ID for the configuration dtd. */
78      private static final String DTD_PUBLIC_ID_1_1_XPATH =
79          "-//Puppy Crawl//DTD Suppressions Xpath Experimental 1.1//EN";
80      /** The new public ID for version 1_1 configuration dtd. */
81      private static final String DTD_PUBLIC_CS_ID_1_1_XPATH =
82          "-//Checkstyle//DTD SuppressionXpathFilter Experimental Configuration 1.1//EN";
83      /** The resource for the configuration dtd. */
84      private static final String DTD_SUPPRESSIONS_NAME_1_1_XPATH =
85          "com/puppycrawl/tools/checkstyle/suppressions_1_1_xpath_experimental.dtd";
86      /** The public ID for the configuration dtd. */
87      private static final String DTD_PUBLIC_ID_1_2_XPATH =
88          "-//Puppy Crawl//DTD Suppressions Xpath Experimental 1.2//EN";
89      /** The new public ID for version 1_2 configuration dtd. */
90      private static final String DTD_PUBLIC_CS_ID_1_2_XPATH =
91          "-//Checkstyle//DTD SuppressionXpathFilter Experimental Configuration 1.2//EN";
92      /** The resource for the configuration dtd. */
93      private static final String DTD_SUPPRESSIONS_NAME_1_2_XPATH =
94          "com/puppycrawl/tools/checkstyle/suppressions_1_2_xpath_experimental.dtd";
95      /** File search error message. **/
96      private static final String UNABLE_TO_FIND_ERROR_MESSAGE = "Unable to find: ";
97      /** String literal for attribute name. **/
98      private static final String ATTRIBUTE_NAME_FILES = "files";
99      /** String literal for attribute name. **/
100     private static final String ATTRIBUTE_NAME_CHECKS = "checks";
101     /** String literal for attribute name. **/
102     private static final String ATTRIBUTE_NAME_MESSAGE = "message";
103     /** String literal for attribute name. **/
104     private static final String ATTRIBUTE_NAME_ID = "id";
105     /** String literal for attribute name. **/
106     private static final String ATTRIBUTE_NAME_QUERY = "query";
107     /** String literal for attribute name. **/
108     private static final String ATTRIBUTE_NAME_LINES = "lines";
109     /** String literal for attribute name. **/
110     private static final String ATTRIBUTE_NAME_COLUMNS = "columns";
111 
112     /**
113      * The filter chain to return in getAFilterChain(),
114      * configured during parsing.
115      */
116     private final FilterSet filterChain = new FilterSet();
117 
118     /**
119      * The set of the {@code TreeWalkerFilter} filters. Being filled during parsing.
120      */
121     private final Set<TreeWalkerFilter> treeWalkerFilters = new HashSet<>();
122 
123     /**
124      * Creates a new {@code SuppressionsLoader} instance.
125      *
126      * @throws ParserConfigurationException if an error occurs
127      * @throws SAXException if an error occurs
128      */
129     private SuppressionsLoader()
130             throws ParserConfigurationException, SAXException {
131         super(createIdToResourceNameMap());
132     }
133 
134     @Override
135     public void startElement(String namespaceUri,
136                              String localName,
137                              String qName,
138                              Attributes attributes)
139             throws SAXException {
140         if ("suppress".equals(qName)) {
141             // add SuppressFilterElement filter to the filter chain
142             final SuppressFilterElement suppress = getSuppressElement(attributes);
143             filterChain.addFilter(suppress);
144         }
145         else if ("suppress-xpath".equals(qName)) {
146             final XpathFilterElement filter = getXpathFilter(attributes);
147             treeWalkerFilters.add(filter);
148         }
149     }
150 
151     /**
152      * Returns the suppress element, initialized from given attributes.
153      *
154      * @param attributes the attributes of xml-tag "&lt;suppress&gt;&lt;/suppress&gt;",
155      *                   specified inside suppression file.
156      * @return the suppress element
157      * @throws SAXException if an error occurs.
158      */
159     private static SuppressFilterElement getSuppressElement(Attributes attributes)
160             throws SAXException {
161         final String checks = attributes.getValue(ATTRIBUTE_NAME_CHECKS);
162         final String modId = attributes.getValue(ATTRIBUTE_NAME_ID);
163         final String message = attributes.getValue(ATTRIBUTE_NAME_MESSAGE);
164         if (checks == null && modId == null && message == null) {
165             // -@cs[IllegalInstantiation] SAXException is in the overridden method signature
166             throw new SAXException("missing checks or id or message attribute");
167         }
168         final SuppressFilterElement suppress;
169         try {
170             final String files = attributes.getValue(ATTRIBUTE_NAME_FILES);
171             final String lines = attributes.getValue(ATTRIBUTE_NAME_LINES);
172             final String columns = attributes.getValue(ATTRIBUTE_NAME_COLUMNS);
173             suppress = new SuppressFilterElement(files, checks, message, modId, lines, columns);
174         }
175         catch (final PatternSyntaxException ex) {
176             // -@cs[IllegalInstantiation] SAXException is in the overridden method signature
177             throw new SAXException("invalid files or checks or message format", ex);
178         }
179         return suppress;
180     }
181 
182     /**
183      * Returns the xpath filter, initialized from given attributes.
184      *
185      * @param attributes the attributes of xml-tag "&lt;suppress-xpath&gt;&lt;/suppress-xpath&gt;",
186      *                   specified inside suppression file.
187      * @return the xpath filter
188      * @throws SAXException if an error occurs.
189      */
190     private static XpathFilterElement getXpathFilter(Attributes attributes) throws SAXException {
191         final String checks = attributes.getValue(ATTRIBUTE_NAME_CHECKS);
192         final String modId = attributes.getValue(ATTRIBUTE_NAME_ID);
193         final String message = attributes.getValue(ATTRIBUTE_NAME_MESSAGE);
194         if (checks == null && modId == null && message == null) {
195             // -@cs[IllegalInstantiation] SAXException is in the overridden method signature
196             throw new SAXException("missing checks or id or message attribute for suppress-xpath");
197         }
198         final XpathFilterElement filter;
199         try {
200             final String files = attributes.getValue(ATTRIBUTE_NAME_FILES);
201             final String xpathQuery = attributes.getValue(ATTRIBUTE_NAME_QUERY);
202             filter = new XpathFilterElement(files, checks, message, modId, xpathQuery);
203         }
204         catch (final PatternSyntaxException ex) {
205             // -@cs[IllegalInstantiation] SAXException is in the overridden method signature
206             throw new SAXException("invalid files or checks or message format for suppress-xpath",
207                     ex);
208         }
209         return filter;
210     }
211 
212     /**
213      * Returns the suppression filters in a specified file.
214      *
215      * @param filename name of the suppressions file.
216      * @return the filter chain of suppression elements specified in the file.
217      * @throws CheckstyleException if an error occurs.
218      */
219     public static FilterSet loadSuppressions(String filename)
220             throws CheckstyleException {
221         // figure out if this is a File or a URL
222         final URI uri = CommonUtil.getUriByFilename(filename);
223         final InputSource source = new InputSource(uri.toString());
224         return loadSuppressions(source, filename);
225     }
226 
227     /**
228      * Returns the suppression filters in a specified source.
229      *
230      * @param source the source for the suppressions.
231      * @param sourceName the name of the source.
232      * @return the filter chain of suppression elements in source.
233      * @throws CheckstyleException if an error occurs.
234      */
235     private static FilterSet loadSuppressions(
236             InputSource source, String sourceName)
237             throws CheckstyleException {
238         return getSuppressionLoader(source, sourceName).filterChain;
239     }
240 
241     /**
242      * Returns the suppression {@code TreeWalker} filters in a specified file.
243      *
244      * @param filename name of the suppressions file.
245      * @return the set of xpath suppression elements specified in the file.
246      * @throws CheckstyleException if an error occurs.
247      */
248     public static Set<TreeWalkerFilter> loadXpathSuppressions(String filename)
249             throws CheckstyleException {
250         // figure out if this is a File or a URL
251         final URI uri = CommonUtil.getUriByFilename(filename);
252         final InputSource source = new InputSource(uri.toString());
253         return loadXpathSuppressions(source, filename);
254     }
255 
256     /**
257      * Returns the suppression {@code TreeWalker} filters in a specified source.
258      *
259      * @param source the source for the suppressions.
260      * @param sourceName the name of the source.
261      * @return the set of xpath suppression elements specified in source.
262      * @throws CheckstyleException if an error occurs.
263      */
264     private static Set<TreeWalkerFilter> loadXpathSuppressions(
265             InputSource source, String sourceName)
266             throws CheckstyleException {
267         return getSuppressionLoader(source, sourceName).treeWalkerFilters;
268     }
269 
270     /**
271      * Parses specified source and returns the suppression loader.
272      *
273      * @param source the source for the suppressions.
274      * @param sourceName the name of the source.
275      * @return the suppression loader
276      * @throws CheckstyleException if an error occurs.
277      */
278     private static SuppressionsLoader getSuppressionLoader(InputSource source, String sourceName)
279             throws CheckstyleException {
280         try {
281             final SuppressionsLoader suppressionsLoader =
282                 new SuppressionsLoader();
283             suppressionsLoader.parseInputSource(source);
284             return suppressionsLoader;
285         }
286         catch (final FileNotFoundException ex) {
287             throw new CheckstyleException(UNABLE_TO_FIND_ERROR_MESSAGE + sourceName, ex);
288         }
289         catch (final ParserConfigurationException | SAXException ex) {
290             final String message = String.format(Locale.ROOT, "Unable to parse %s - %s",
291                     sourceName, ex.getMessage());
292             throw new CheckstyleException(message, ex);
293         }
294         catch (final IOException ex) {
295             throw new CheckstyleException("Unable to read " + sourceName, ex);
296         }
297         catch (final NumberFormatException ex) {
298             final String message = String.format(Locale.ROOT, "Number format exception %s - %s",
299                     sourceName, ex.getMessage());
300             throw new CheckstyleException(message, ex);
301         }
302     }
303 
304     /**
305      * Creates mapping between local resources and dtd ids.
306      *
307      * @return map between local resources and dtd ids.
308      */
309     private static Map<String, String> createIdToResourceNameMap() {
310         final Map<String, String> map = new HashMap<>();
311         map.put(DTD_PUBLIC_ID_1_0, DTD_SUPPRESSIONS_NAME_1_0);
312         map.put(DTD_PUBLIC_ID_1_1, DTD_SUPPRESSIONS_NAME_1_1);
313         map.put(DTD_PUBLIC_ID_1_2, DTD_SUPPRESSIONS_NAME_1_2);
314         map.put(DTD_PUBLIC_ID_1_1_XPATH, DTD_SUPPRESSIONS_NAME_1_1_XPATH);
315         map.put(DTD_PUBLIC_ID_1_2_XPATH, DTD_SUPPRESSIONS_NAME_1_2_XPATH);
316         map.put(DTD_PUBLIC_CS_ID_1_0, DTD_SUPPRESSIONS_NAME_1_0);
317         map.put(DTD_PUBLIC_CS_ID_1_1, DTD_SUPPRESSIONS_NAME_1_1);
318         map.put(DTD_PUBLIC_CS_ID_1_2, DTD_SUPPRESSIONS_NAME_1_2);
319         map.put(DTD_PUBLIC_CS_ID_1_1_XPATH, DTD_SUPPRESSIONS_NAME_1_1_XPATH);
320         map.put(DTD_PUBLIC_CS_ID_1_2_XPATH, DTD_SUPPRESSIONS_NAME_1_2_XPATH);
321         return map;
322     }
323 
324 }