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