001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2018 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.XmlLoader;
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 */
047public final class SuppressionsLoader
048    extends XmlLoader {
049
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_SUPPRESSIONS_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_SUPPRESSIONS_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_2 =
064        "-//Puppy Crawl//DTD Suppressions 1.2//EN";
065    /** The resource for the configuration dtd. */
066    private static final String DTD_SUPPRESSIONS_NAME_1_2 =
067        "com/puppycrawl/tools/checkstyle/suppressions_1_2.dtd";
068    /** The public ID for the configuration dtd. */
069    private static final String DTD_PUBLIC_ID_1_1_XPATH =
070            "-//Puppy Crawl//DTD Suppressions Xpath Experimental 1.1//EN";
071    /** The resource for the configuration dtd. */
072    private static final String DTD_SUPPRESSIONS_NAME_1_1_XPATH =
073            "com/puppycrawl/tools/checkstyle/suppressions_1_1_xpath_experimental.dtd";
074    /** The public ID for the configuration dtd. */
075    private static final String DTD_PUBLIC_ID_1_2_XPATH =
076            "-//Puppy Crawl//DTD Suppressions Xpath Experimental 1.2//EN";
077    /** The resource for the configuration dtd. */
078    private static final String DTD_SUPPRESSIONS_NAME_1_2_XPATH =
079            "com/puppycrawl/tools/checkstyle/suppressions_1_2_xpath_experimental.dtd";
080    /** File search error message. **/
081    private static final String UNABLE_TO_FIND_ERROR_MESSAGE = "Unable to find: ";
082    /** String literal for attribute name. **/
083    private static final String ATTRIBUTE_NAME_FILES = "files";
084    /** String literal for attribute name. **/
085    private static final String ATTRIBUTE_NAME_CHECKS = "checks";
086    /** String literal for attribute name. **/
087    private static final String ATTRIBUTE_NAME_MESSAGE = "message";
088    /** String literal for attribute name. **/
089    private static final String ATTRIBUTE_NAME_ID = "id";
090    /** String literal for attribute name. **/
091    private static final String ATTRIBUTE_NAME_QUERY = "query";
092    /** String literal for attribute name. **/
093    private static final String ATTRIBUTE_NAME_LINES = "lines";
094    /** String literal for attribute name. **/
095    private static final String ATTRIBUTE_NAME_COLUMNS = "columns";
096
097    /**
098     * The filter chain to return in getAFilterChain(),
099     * 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
294}