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