001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2014  Oliver Burn
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////////////////////////////////////////////////////////////////////////////////
019package com.puppycrawl.tools.checkstyle.filters;
020
021import java.io.File;
022import java.io.FileNotFoundException;
023import java.io.IOException;
024import java.net.MalformedURLException;
025import java.net.URI;
026import java.net.URISyntaxException;
027import java.net.URL;
028import java.util.Map;
029import java.util.regex.PatternSyntaxException;
030
031import javax.xml.parsers.ParserConfigurationException;
032
033import org.xml.sax.Attributes;
034import org.xml.sax.InputSource;
035import org.xml.sax.SAXException;
036
037import com.google.common.collect.Maps;
038import com.puppycrawl.tools.checkstyle.api.AbstractLoader;
039import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
040import com.puppycrawl.tools.checkstyle.api.FilterSet;
041
042/**
043 * Loads a filter chain of suppressions.
044 * @author Rick Giles
045 */
046public final class SuppressionsLoader
047    extends AbstractLoader
048{
049    /** the public ID for the configuration dtd */
050    private static final String DTD_PUBLIC_ID_1_0 =
051        "-//Puppy Crawl//DTD Suppressions 1.0//EN";
052    /** the resource for the configuration dtd */
053    private static final String DTD_RESOURCE_NAME_1_0 =
054        "com/puppycrawl/tools/checkstyle/suppressions_1_0.dtd";
055    /** the public ID for the configuration dtd */
056    private static final String DTD_PUBLIC_ID_1_1 =
057        "-//Puppy Crawl//DTD Suppressions 1.1//EN";
058    /** the resource for the configuration dtd */
059    private static final String DTD_RESOURCE_NAME_1_1 =
060        "com/puppycrawl/tools/checkstyle/suppressions_1_1.dtd";
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</code> instance.
070     * @throws ParserConfigurationException if an error occurs
071     * @throws SAXException if an error occurs
072     */
073    private SuppressionsLoader()
074        throws ParserConfigurationException, SAXException
075    {
076        super(createIdToResourceNameMap());
077    }
078
079    /**
080     * Returns the loaded filter chain.
081     * @return the loaded filter chain.
082     */
083    public FilterSet getFilterChain()
084    {
085        return filterChain;
086    }
087
088    @Override
089    public void startElement(String namespaceURI,
090                             String localName,
091                             String qName,
092                             Attributes atts)
093        throws SAXException
094    {
095        if ("suppress".equals(qName)) {
096            //add SuppressElement filter to the filter chain
097            final String files = atts.getValue("files");
098            if (files == null) {
099                throw new SAXException("missing files attribute");
100            }
101            final String checks = atts.getValue("checks");
102            final String modId = atts.getValue("id");
103            if ((checks == null) && (modId == null)) {
104                throw new SAXException("missing checks and id attribute");
105            }
106            final SuppressElement suppress;
107            try {
108                suppress = new SuppressElement(files);
109                if (modId != null) {
110                    suppress.setModuleId(modId);
111                }
112                if (checks != null) {
113                    suppress.setChecks(checks);
114                }
115            }
116            catch (final PatternSyntaxException e) {
117                throw new SAXException("invalid files or checks format");
118            }
119            final String lines = atts.getValue("lines");
120            if (lines != null) {
121                suppress.setLines(lines);
122            }
123            final String columns = atts.getValue("columns");
124            if (columns != null) {
125                suppress.setColumns(columns);
126            }
127            filterChain.addFilter(suppress);
128        }
129    }
130
131    /**
132     * Returns the suppression filters in a specified file.
133     * @param filename name of the suppresssions file.
134     * @return the filter chain of suppression elements specified in the file.
135     * @throws CheckstyleException if an error occurs.
136     */
137    public static FilterSet loadSuppressions(String filename)
138        throws CheckstyleException
139    {
140        try {
141            // figure out if this is a File or a URL
142            URI uri;
143            try {
144                final URL url = new URL(filename);
145                uri = url.toURI();
146            }
147            catch (final MalformedURLException ex) {
148                uri = null;
149            }
150            catch (final URISyntaxException ex) {
151                // URL violating RFC 2396
152                uri = null;
153            }
154            if (uri == null) {
155                final File file = new File(filename);
156                if (file.exists()) {
157                    uri = file.toURI();
158                }
159                else {
160                    // check to see if the file is in the classpath
161                    try {
162                        final URL configUrl = SuppressionsLoader.class
163                                .getResource(filename);
164                        if (configUrl == null) {
165                            throw new FileNotFoundException(filename);
166                        }
167                        uri = configUrl.toURI();
168                    }
169                    catch (final URISyntaxException e) {
170                        throw new FileNotFoundException(filename);
171                    }
172                }
173            }
174            final InputSource source = new InputSource(uri.toString());
175            return loadSuppressions(source, filename);
176        }
177        catch (final FileNotFoundException e) {
178            throw new CheckstyleException("unable to find " + filename, e);
179        }
180    }
181
182    /**
183     * Returns the suppression filters in a specified source.
184     * @param source the source for the suppressions.
185     * @param sourceName the name of the source.
186     * @return the filter chain of suppression elements in source.
187     * @throws CheckstyleException if an error occurs.
188     */
189    private static FilterSet loadSuppressions(
190            InputSource source, String sourceName)
191        throws CheckstyleException
192    {
193        try {
194            final SuppressionsLoader suppressionsLoader =
195                new SuppressionsLoader();
196            suppressionsLoader.parseInputSource(source);
197            return suppressionsLoader.getFilterChain();
198        }
199        catch (final FileNotFoundException e) {
200            throw new CheckstyleException("unable to find " + sourceName, e);
201        }
202        catch (final ParserConfigurationException e) {
203            throw new CheckstyleException("unable to parse " + sourceName, e);
204        }
205        catch (final SAXException e) {
206            throw new CheckstyleException("unable to parse "
207                    + sourceName + " - " + e.getMessage(), e);
208        }
209        catch (final IOException e) {
210            throw new CheckstyleException("unable to read " + sourceName, e);
211        }
212        catch (final NumberFormatException e) {
213            throw new CheckstyleException("number format exception "
214                + sourceName + " - " + e.getMessage(), e);
215        }
216    }
217
218    /**
219     * Creates mapping between local resources and dtd ids.
220     * @return map between local resources and dtd ids.
221     */
222    private static Map<String, String> createIdToResourceNameMap()
223    {
224        final Map<String, String> map = Maps.newHashMap();
225        map.put(DTD_PUBLIC_ID_1_0, DTD_RESOURCE_NAME_1_0);
226        map.put(DTD_PUBLIC_ID_1_1, DTD_RESOURCE_NAME_1_1);
227        return map;
228    }
229}