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