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.Locale;
027import java.util.Map;
028import java.util.regex.PatternSyntaxException;
029
030import javax.xml.parsers.ParserConfigurationException;
031
032import org.xml.sax.Attributes;
033import org.xml.sax.InputSource;
034import org.xml.sax.SAXException;
035
036import com.puppycrawl.tools.checkstyle.api.AbstractLoader;
037import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
038import com.puppycrawl.tools.checkstyle.api.FilterSet;
039import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
040
041/**
042 * Loads a filter chain of suppressions.
043 * @author Rick Giles
044 */
045public final class SuppressionsLoader
046    extends AbstractLoader {
047    /** The public ID for the configuration dtd. */
048    private static final String DTD_PUBLIC_ID_1_0 =
049        "-//Puppy Crawl//DTD Suppressions 1.0//EN";
050    /** The resource for the configuration dtd. */
051    private static final String DTD_RESOURCE_NAME_1_0 =
052        "com/puppycrawl/tools/checkstyle/suppressions_1_0.dtd";
053    /** The public ID for the configuration dtd. */
054    private static final String DTD_PUBLIC_ID_1_1 =
055        "-//Puppy Crawl//DTD Suppressions 1.1//EN";
056    /** The resource for the configuration dtd. */
057    private static final String DTD_RESOURCE_NAME_1_1 =
058        "com/puppycrawl/tools/checkstyle/suppressions_1_1.dtd";
059    /** File search error message. **/
060    private static final String UNABLE_TO_FIND_ERROR_MESSAGE = "Unable to find: ";
061
062    /**
063     * The filter chain to return in getAFilterChain(),
064     * configured during parsing.
065     */
066    private final FilterSet filterChain = new FilterSet();
067
068    /**
069     * Creates a new {@code SuppressionsLoader} instance.
070     * @throws ParserConfigurationException if an error occurs
071     * @throws SAXException if an error occurs
072     */
073    private SuppressionsLoader()
074            throws ParserConfigurationException, SAXException {
075        super(createIdToResourceNameMap());
076    }
077
078    @Override
079    public void startElement(String namespaceUri,
080                             String localName,
081                             String qName,
082                             Attributes attributes)
083            throws SAXException {
084        if ("suppress".equals(qName)) {
085            //add SuppressElement filter to the filter chain
086            final String checks = attributes.getValue("checks");
087            final String modId = attributes.getValue("id");
088            if (checks == null && modId == null) {
089                // -@cs[IllegalInstantiation] SAXException is in the overridden method signature
090                throw new SAXException("missing checks and id attribute");
091            }
092            final SuppressElement suppress;
093            try {
094                final String files = attributes.getValue("files");
095                suppress = new SuppressElement(files);
096                if (modId != null) {
097                    suppress.setModuleId(modId);
098                }
099                if (checks != null) {
100                    suppress.setChecks(checks);
101                }
102            }
103            catch (final PatternSyntaxException ex) {
104                // -@cs[IllegalInstantiation] SAXException is in the overridden method signature
105                throw new SAXException("invalid files or checks format", ex);
106            }
107            final String lines = attributes.getValue("lines");
108            if (lines != null) {
109                suppress.setLines(lines);
110            }
111            final String columns = attributes.getValue("columns");
112            if (columns != null) {
113                suppress.setColumns(columns);
114            }
115            filterChain.addFilter(suppress);
116        }
117    }
118
119    /**
120     * Returns the suppression filters in a specified file.
121     * @param filename name of the suppressions file.
122     * @return the filter chain of suppression elements specified in the file.
123     * @throws CheckstyleException if an error occurs.
124     */
125    public static FilterSet loadSuppressions(String filename)
126            throws CheckstyleException {
127        // figure out if this is a File or a URL
128        final URI uri = CommonUtils.getUriByFilename(filename);
129        final InputSource source = new InputSource(uri.toString());
130        return loadSuppressions(source, filename);
131    }
132
133    /**
134     * Returns the suppression filters in a specified source.
135     * @param source the source for the suppressions.
136     * @param sourceName the name of the source.
137     * @return the filter chain of suppression elements in source.
138     * @throws CheckstyleException if an error occurs.
139     */
140    private static FilterSet loadSuppressions(
141            InputSource source, String sourceName)
142            throws CheckstyleException {
143        try {
144            final SuppressionsLoader suppressionsLoader =
145                new SuppressionsLoader();
146            suppressionsLoader.parseInputSource(source);
147            return suppressionsLoader.filterChain;
148        }
149        catch (final FileNotFoundException ex) {
150            throw new CheckstyleException(UNABLE_TO_FIND_ERROR_MESSAGE + sourceName, ex);
151        }
152        catch (final ParserConfigurationException | SAXException ex) {
153            final String message = String.format(Locale.ROOT, "Unable to parse %s - %s",
154                    sourceName, ex.getMessage());
155            throw new CheckstyleException(message, ex);
156        }
157        catch (final IOException ex) {
158            throw new CheckstyleException("Unable to read " + sourceName, ex);
159        }
160        catch (final NumberFormatException ex) {
161            final String message = String.format(Locale.ROOT, "Number format exception %s - %s",
162                    sourceName, ex.getMessage());
163            throw new CheckstyleException(message, ex);
164        }
165    }
166
167    /**
168     * Creates mapping between local resources and dtd ids.
169     * @return map between local resources and dtd ids.
170     */
171    private static Map<String, String> createIdToResourceNameMap() {
172        final Map<String, String> map = new HashMap<>();
173        map.put(DTD_PUBLIC_ID_1_0, DTD_RESOURCE_NAME_1_0);
174        map.put(DTD_PUBLIC_ID_1_1, DTD_RESOURCE_NAME_1_1);
175        return map;
176    }
177}