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