001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2017 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.net.URI;
025import java.util.HashMap;
026import java.util.HashSet;
027import java.util.Locale;
028import java.util.Map;
029import java.util.Set;
030import java.util.regex.PatternSyntaxException;
031
032import javax.xml.parsers.ParserConfigurationException;
033
034import org.xml.sax.Attributes;
035import org.xml.sax.InputSource;
036import org.xml.sax.SAXException;
037
038import com.puppycrawl.tools.checkstyle.TreeWalkerFilter;
039import com.puppycrawl.tools.checkstyle.api.AbstractLoader;
040import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
041import com.puppycrawl.tools.checkstyle.api.FilterSet;
042import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
043
044/**
045 * Loads a filter chain of suppressions.
046 * @author Rick Giles
047 */
048public final class SuppressionsLoader
049    extends AbstractLoader {
050    /** The public ID for the configuration dtd. */
051    private static final String DTD_PUBLIC_ID_1_0 =
052        "-//Puppy Crawl//DTD Suppressions 1.0//EN";
053    /** The resource for the configuration dtd. */
054    private static final String DTD_RESOURCE_NAME_1_0 =
055        "com/puppycrawl/tools/checkstyle/suppressions_1_0.dtd";
056    /** The public ID for the configuration dtd. */
057    private static final String DTD_PUBLIC_ID_1_1 =
058        "-//Puppy Crawl//DTD Suppressions 1.1//EN";
059    /** The resource for the configuration dtd. */
060    private static final String DTD_RESOURCE_NAME_1_1 =
061        "com/puppycrawl/tools/checkstyle/suppressions_1_1.dtd";
062    /** The public ID for the configuration dtd. */
063    private static final String DTD_PUBLIC_ID_1_1_XPATH =
064            "-//Puppy Crawl//DTD Suppressions Xpath Experimental 1.1//EN";
065    /** The resource for the configuration dtd. */
066    private static final String DTD_RESOURCE_NAME_1_1_XPATH =
067            "com/puppycrawl/tools/checkstyle/suppressions_1_1_xpath_experimental.dtd";
068    /** File search error message. **/
069    private static final String UNABLE_TO_FIND_ERROR_MESSAGE = "Unable to find: ";
070    /** String literal for attribute name. **/
071    private static final String ATTRIBUTE_NAME_FILES = "files";
072    /** String literal for attribute name. **/
073    private static final String ATTRIBUTE_NAME_CHECKS = "checks";
074    /** String literal for attribute name. **/
075    private static final String ATTRIBUTE_NAME_ID = "id";
076    /** String literal for attribute name. **/
077    private static final String ATTRIBUTE_NAME_QUERY = "query";
078    /** String literal for attribute name. **/
079    private static final String ATTRIBUTE_NAME_LINES = "lines";
080    /** String literal for attribute name. **/
081    private static final String ATTRIBUTE_NAME_COLUMNS = "columns";
082
083    /**
084     * The filter chain to return in getAFilterChain(),
085     * configured during parsing.
086     */
087    private final FilterSet filterChain = new FilterSet();
088
089    /**
090     * The set of the {@code TreeWalkerFilter} filters. Being filled during parsing.
091     */
092    private final Set<TreeWalkerFilter> treeWalkerFilters = new HashSet<>();
093
094    /**
095     * Creates a new {@code SuppressionsLoader} instance.
096     * @throws ParserConfigurationException if an error occurs
097     * @throws SAXException if an error occurs
098     */
099    private SuppressionsLoader()
100            throws ParserConfigurationException, SAXException {
101        super(createIdToResourceNameMap());
102    }
103
104    @Override
105    public void startElement(String namespaceUri,
106                             String localName,
107                             String qName,
108                             Attributes attributes)
109            throws SAXException {
110        if ("suppress".equals(qName)) {
111            //add SuppressElement filter to the filter chain
112            final SuppressElement suppress = getSuppressElement(attributes);
113            filterChain.addFilter(suppress);
114        }
115        else if ("suppress-xpath".equals(qName)) {
116            final XpathFilter filter = getXpathFilter(attributes);
117            treeWalkerFilters.add(filter);
118        }
119    }
120
121    /**
122     * Returns the suppress element, initialized from given attributes.
123     * @param attributes the attributes of xml-tag "<suppress></suppress>", specified inside
124     *                   suppression file.
125     * @return the suppress element
126     * @throws SAXException if an error occurs.
127     */
128    private static SuppressElement getSuppressElement(Attributes attributes) throws SAXException {
129        final String checks = attributes.getValue(ATTRIBUTE_NAME_CHECKS);
130        final String modId = attributes.getValue(ATTRIBUTE_NAME_ID);
131        if (checks == null && modId == null) {
132            // -@cs[IllegalInstantiation] SAXException is in the overridden method signature
133            throw new SAXException("missing checks and id attribute");
134        }
135        final SuppressElement suppress;
136        try {
137            final String files = attributes.getValue(ATTRIBUTE_NAME_FILES);
138            final String lines = attributes.getValue(ATTRIBUTE_NAME_LINES);
139            final String columns = attributes.getValue(ATTRIBUTE_NAME_COLUMNS);
140            suppress = new SuppressElement(files, checks, modId, lines, columns);
141        }
142        catch (final PatternSyntaxException ex) {
143            // -@cs[IllegalInstantiation] SAXException is in the overridden method signature
144            throw new SAXException("invalid files or checks format", ex);
145        }
146        return suppress;
147    }
148
149    /**
150     * Returns the xpath filter, initialized from given attributes.
151     * @param attributes the attributes of xml-tag "<suppress-xpath></suppress-xpath>",
152     *                   specified inside suppression file.
153     * @return the xpath filter
154     * @throws SAXException if an error occurs.
155     */
156    private static XpathFilter getXpathFilter(Attributes attributes) throws SAXException {
157        final String checks = attributes.getValue(ATTRIBUTE_NAME_CHECKS);
158        final String modId = attributes.getValue(ATTRIBUTE_NAME_ID);
159        if (checks == null && modId == null) {
160            // -@cs[IllegalInstantiation] SAXException is in the overridden method signature
161            throw new SAXException("missing checks and id attribute for suppress-xpath");
162        }
163        final XpathFilter filter;
164        try {
165            final String files = attributes.getValue(ATTRIBUTE_NAME_FILES);
166            final String xpathQuery = attributes.getValue(ATTRIBUTE_NAME_QUERY);
167            filter = new XpathFilter(files, checks, modId, xpathQuery);
168        }
169        catch (final PatternSyntaxException ex) {
170            // -@cs[IllegalInstantiation] SAXException is in the overridden method signature
171            throw new SAXException("invalid files or checks format for suppress-xpath", ex);
172        }
173        return filter;
174    }
175
176    /**
177     * Returns the suppression filters in a specified file.
178     * @param filename name of the suppressions file.
179     * @return the filter chain of suppression elements specified in the file.
180     * @throws CheckstyleException if an error occurs.
181     */
182    public static FilterSet loadSuppressions(String filename)
183            throws CheckstyleException {
184        // figure out if this is a File or a URL
185        final URI uri = CommonUtils.getUriByFilename(filename);
186        final InputSource source = new InputSource(uri.toString());
187        return loadSuppressions(source, filename);
188    }
189
190    /**
191     * Returns the suppression filters in a specified source.
192     * @param source the source for the suppressions.
193     * @param sourceName the name of the source.
194     * @return the filter chain of suppression elements in source.
195     * @throws CheckstyleException if an error occurs.
196     */
197    private static FilterSet loadSuppressions(
198            InputSource source, String sourceName)
199            throws CheckstyleException {
200        return getSuppressionLoader(source, sourceName).filterChain;
201    }
202
203    /**
204     * Returns the suppression {@code TreeWalker} filters in a specified file.
205     * @param filename name of the suppressions file.
206     * @return the set of xpath suppression elements specified in the file.
207     * @throws CheckstyleException if an error occurs.
208     */
209    public static Set<TreeWalkerFilter> loadXpathSuppressions(String filename)
210            throws CheckstyleException {
211        // figure out if this is a File or a URL
212        final URI uri = CommonUtils.getUriByFilename(filename);
213        final InputSource source = new InputSource(uri.toString());
214        return loadXpathSuppressions(source, filename);
215    }
216
217    /**
218     * Returns the suppression {@code TreeWalker} filters in a specified source.
219     * @param source the source for the suppressions.
220     * @param sourceName the name of the source.
221     * @return the set of xpath suppression elements specified in source.
222     * @throws CheckstyleException if an error occurs.
223     */
224    private static Set<TreeWalkerFilter> loadXpathSuppressions(
225            InputSource source, String sourceName)
226            throws CheckstyleException {
227        return getSuppressionLoader(source, sourceName).treeWalkerFilters;
228    }
229
230    /**
231     * Parses specified source and returns the suppression loader.
232     * @param source the source for the suppressions.
233     * @param sourceName the name of the source.
234     * @return the suppression loader
235     * @throws CheckstyleException if an error occurs.
236     */
237    private static SuppressionsLoader getSuppressionLoader(InputSource source, String sourceName)
238            throws CheckstyleException {
239        try {
240            final SuppressionsLoader suppressionsLoader =
241                new SuppressionsLoader();
242            suppressionsLoader.parseInputSource(source);
243            return suppressionsLoader;
244        }
245        catch (final FileNotFoundException ex) {
246            throw new CheckstyleException(UNABLE_TO_FIND_ERROR_MESSAGE + sourceName, ex);
247        }
248        catch (final ParserConfigurationException | SAXException ex) {
249            final String message = String.format(Locale.ROOT, "Unable to parse %s - %s",
250                    sourceName, ex.getMessage());
251            throw new CheckstyleException(message, ex);
252        }
253        catch (final IOException ex) {
254            throw new CheckstyleException("Unable to read " + sourceName, ex);
255        }
256        catch (final NumberFormatException ex) {
257            final String message = String.format(Locale.ROOT, "Number format exception %s - %s",
258                    sourceName, ex.getMessage());
259            throw new CheckstyleException(message, ex);
260        }
261    }
262
263    /**
264     * Creates mapping between local resources and dtd ids.
265     * @return map between local resources and dtd ids.
266     */
267    private static Map<String, String> createIdToResourceNameMap() {
268        final Map<String, String> map = new HashMap<>();
269        map.put(DTD_PUBLIC_ID_1_0, DTD_RESOURCE_NAME_1_0);
270        map.put(DTD_PUBLIC_ID_1_1, DTD_RESOURCE_NAME_1_1);
271        map.put(DTD_PUBLIC_ID_1_1_XPATH, DTD_RESOURCE_NAME_1_1_XPATH);
272        return map;
273    }
274}