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.checks.header;
020
021import java.io.BufferedInputStream;
022import java.io.File;
023import java.io.FileNotFoundException;
024import java.io.IOException;
025import java.io.InputStreamReader;
026import java.io.LineNumberReader;
027import java.io.Reader;
028import java.io.StringReader;
029import java.io.UnsupportedEncodingException;
030import java.net.MalformedURLException;
031import java.net.URI;
032import java.net.URISyntaxException;
033import java.net.URL;
034import java.nio.charset.Charset;
035import java.util.List;
036
037import org.apache.commons.beanutils.ConversionException;
038
039import com.google.common.collect.ImmutableList;
040import com.google.common.collect.Lists;
041import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck;
042import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
043import com.puppycrawl.tools.checkstyle.api.Utils;
044
045/**
046 * Abstract super class for header checks.
047 * Provides support for header and headerFile properties.
048 * @author o_sukhosolsky
049 */
050public abstract class AbstractHeaderCheck extends AbstractFileSetCheck
051{
052    /** The file that contains the header to check against. */
053    private String filename;
054
055    /** Name of a charset to use for loading the header from a file. */
056    private String charset = System.getProperty("file.encoding", "UTF-8");
057
058    /** the lines of the header file. */
059    private final List<String> readerLines = Lists.newArrayList();
060
061
062    /**
063     * Return the header lines to check against.
064     * @return the header lines to check against.
065     */
066    protected ImmutableList<String> getHeaderLines()
067    {
068        return ImmutableList.copyOf(readerLines);
069    }
070
071    /**
072     * Set the charset to use for loading the header from a file.
073     * @param charset the charset to use for loading the header from a file
074     * @throws UnsupportedEncodingException if charset is unsupported
075     */
076    public void setCharset(String charset) throws UnsupportedEncodingException
077    {
078        if (!Charset.isSupported(charset)) {
079            final String message = "unsupported charset: '" + charset + "'";
080            throw new UnsupportedEncodingException(message);
081        }
082        this.charset = charset;
083    }
084
085    /**
086     * Set the header file to check against.
087     * @param fileName the file that contains the header to check against.
088     */
089    public void setHeaderFile(String fileName)
090    {
091        // Handle empty param
092        if ((fileName == null) || (fileName.trim().length() == 0)) {
093            return;
094        }
095
096        filename = fileName;
097    }
098
099    /**
100     * Load the header from a file.
101     * @throws CheckstyleException if the file cannot be loaded
102     */
103    private void loadHeaderFile() throws CheckstyleException
104    {
105        checkHeaderNotInitialized();
106        Reader headerReader = null;
107        try {
108            final URI uri = resolveHeaderFile();
109            headerReader = new InputStreamReader(new BufferedInputStream(
110                    uri.toURL().openStream()), charset);
111            loadHeader(headerReader);
112        }
113        catch (final IOException ex) {
114            throw new CheckstyleException(
115                    "unable to load header file " + filename, ex);
116        }
117        finally {
118            Utils.closeQuietly(headerReader);
119        }
120    }
121
122    /**
123     * Resolve the specified filename param to a URI.
124     * @return resolved header file URI
125     * @throws IOException on failure
126     */
127    private URI resolveHeaderFile() throws IOException
128    {
129        // figure out if this is a File or a URL
130        URI uri;
131        try {
132            final URL url = new URL(filename);
133            uri = url.toURI();
134        }
135        catch (final MalformedURLException ex) {
136            uri = null;
137        }
138        catch (final URISyntaxException ex) {
139            // URL violating RFC 2396
140            uri = null;
141        }
142        if (uri == null) {
143            final File file = new File(filename);
144            if (file.exists()) {
145                uri = file.toURI();
146            }
147            else {
148                // check to see if the file is in the classpath
149                try {
150                    final URL configUrl = AbstractHeaderCheck.class
151                            .getResource(filename);
152                    if (configUrl == null) {
153                        throw new FileNotFoundException(filename);
154                    }
155                    uri = configUrl.toURI();
156                }
157                catch (final URISyntaxException e) {
158                    throw new FileNotFoundException(filename);
159                }
160            }
161        }
162        return uri;
163    }
164
165    /**
166     * Called before initializing the header.
167     * @throws ConversionException if header has already been set
168     */
169    private void checkHeaderNotInitialized()
170    {
171        if (!readerLines.isEmpty()) {
172            throw new ConversionException(
173                    "header has already been set - "
174                    + "set either header or headerFile, not both");
175        }
176    }
177
178    /**
179     * Set the header to check against. Individual lines in the header
180     * must be separated by '\n' characters.
181     * @param header header content to check against.
182     * @throws ConversionException if the header cannot be interpreted
183     */
184    public void setHeader(String header)
185    {
186        if ((header == null) || (header.trim().length() == 0)) {
187            return;
188        }
189
190        checkHeaderNotInitialized();
191
192        final String headerExpandedNewLines = header.replaceAll("\\\\n", "\n");
193
194        final Reader headerReader = new StringReader(headerExpandedNewLines);
195        try {
196            loadHeader(headerReader);
197        }
198        catch (final IOException ex) {
199            throw new ConversionException("unable to load header", ex);
200        }
201        finally {
202            Utils.closeQuietly(headerReader);
203        }
204    }
205
206    /**
207     * Load header to check against from a Reader into readerLines.
208     * @param headerReader delivers the header to check against
209     * @throws IOException if
210     */
211    private void loadHeader(final Reader headerReader) throws IOException
212    {
213        final LineNumberReader lnr = new LineNumberReader(headerReader);
214        readerLines.clear();
215        while (true) {
216            final String l = lnr.readLine();
217            if (l == null) {
218                break;
219            }
220            readerLines.add(l);
221        }
222        postprocessHeaderLines();
223    }
224
225    /**
226     * Hook method for post processing header lines.
227     * This implementation does nothing.
228     */
229    protected void postprocessHeaderLines()
230    {
231    }
232
233    @Override
234    protected final void finishLocalSetup() throws CheckstyleException
235    {
236        if (filename != null) {
237            loadHeaderFile();
238        }
239        if (readerLines.isEmpty()) {
240            throw new CheckstyleException(
241                    "property 'headerFile' is missing or invalid in module "
242                    + getConfiguration().getName());
243        }
244    }
245}