001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2016 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.checks.header;
021
022import java.io.BufferedInputStream;
023import java.io.IOException;
024import java.io.InputStreamReader;
025import java.io.LineNumberReader;
026import java.io.Reader;
027import java.io.StringReader;
028import java.io.UnsupportedEncodingException;
029import java.net.URI;
030import java.nio.charset.Charset;
031import java.util.List;
032import java.util.Set;
033import java.util.regex.Pattern;
034
035import org.apache.commons.beanutils.ConversionException;
036
037import com.google.common.collect.ImmutableList;
038import com.google.common.collect.ImmutableSet;
039import com.google.common.collect.Lists;
040import com.google.common.io.Closeables;
041import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck;
042import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
043import com.puppycrawl.tools.checkstyle.api.ExternalResourceHolder;
044import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
045
046/**
047 * Abstract super class for header checks.
048 * Provides support for header and headerFile properties.
049 * @author o_sukhosolsky
050 */
051public abstract class AbstractHeaderCheck extends AbstractFileSetCheck
052    implements ExternalResourceHolder {
053    /** Pattern to detect occurrences of '\n' in text. */
054    private static final Pattern ESCAPED_LINE_FEED_PATTERN = Pattern.compile("\\\\n");
055
056    /** The lines of the header file. */
057    private final List<String> readerLines = Lists.newArrayList();
058
059    /** The file that contains the header to check against. */
060    private String headerFile;
061
062    /** Name of a charset to use for loading the header from a file. */
063    private String charset = System.getProperty("file.encoding", "UTF-8");
064
065    /**
066     * Hook method for post processing header lines.
067     * This implementation does nothing.
068     */
069    protected abstract void postProcessHeaderLines();
070
071    /**
072     * Return the header lines to check against.
073     * @return the header lines to check against.
074     */
075    protected ImmutableList<String> getHeaderLines() {
076        return ImmutableList.copyOf(readerLines);
077    }
078
079    /**
080     * Set the charset to use for loading the header from a file.
081     * @param charset the charset to use for loading the header from a file
082     * @throws UnsupportedEncodingException if charset is unsupported
083     */
084    public void setCharset(String charset) throws UnsupportedEncodingException {
085        if (!Charset.isSupported(charset)) {
086            final String message = "unsupported charset: '" + charset + "'";
087            throw new UnsupportedEncodingException(message);
088        }
089        this.charset = charset;
090    }
091
092    /**
093     * Set the header file to check against.
094     * @param fileName the file that contains the header to check against.
095     * @throws CheckstyleException if fileName is empty.
096     */
097    public void setHeaderFile(String fileName) throws CheckstyleException {
098        if (CommonUtils.isBlank(fileName)) {
099            throw new CheckstyleException(
100                "property 'headerFile' is missing or invalid in module "
101                    + getConfiguration().getName());
102        }
103
104        headerFile = fileName;
105    }
106
107    /**
108     * Load the header from a file.
109     * @throws CheckstyleException if the file cannot be loaded
110     */
111    private void loadHeaderFile() throws CheckstyleException {
112        checkHeaderNotInitialized();
113        Reader headerReader = null;
114        try {
115            final URI uri = CommonUtils.getUriByFilename(headerFile);
116            headerReader = new InputStreamReader(new BufferedInputStream(
117                    uri.toURL().openStream()), charset);
118            loadHeader(headerReader);
119        }
120        catch (final IOException ex) {
121            throw new CheckstyleException(
122                    "unable to load header file " + headerFile, ex);
123        }
124        finally {
125            Closeables.closeQuietly(headerReader);
126        }
127    }
128
129    /**
130     * Called before initializing the header.
131     * @throws ConversionException if header has already been set
132     */
133    private void checkHeaderNotInitialized() {
134        if (!readerLines.isEmpty()) {
135            throw new ConversionException(
136                    "header has already been set - "
137                    + "set either header or headerFile, not both");
138        }
139    }
140
141    /**
142     * Set the header to check against. Individual lines in the header
143     * must be separated by '\n' characters.
144     * @param header header content to check against.
145     * @throws ConversionException if the header cannot be interpreted
146     */
147    public void setHeader(String header) {
148        if (!CommonUtils.isBlank(header)) {
149            checkHeaderNotInitialized();
150
151            final String headerExpandedNewLines = ESCAPED_LINE_FEED_PATTERN
152                    .matcher(header).replaceAll("\n");
153
154            final Reader headerReader = new StringReader(headerExpandedNewLines);
155            try {
156                loadHeader(headerReader);
157            }
158            catch (final IOException ex) {
159                throw new ConversionException("unable to load header", ex);
160            }
161            finally {
162                Closeables.closeQuietly(headerReader);
163            }
164        }
165    }
166
167    /**
168     * Load header to check against from a Reader into readerLines.
169     * @param headerReader delivers the header to check against
170     * @throws IOException if
171     */
172    private void loadHeader(final Reader headerReader) throws IOException {
173        readerLines.clear();
174        final LineNumberReader lnr = new LineNumberReader(headerReader);
175        while (true) {
176            final String line = lnr.readLine();
177            if (line == null) {
178                break;
179            }
180            readerLines.add(line);
181        }
182        postProcessHeaderLines();
183    }
184
185    @Override
186    protected final void finishLocalSetup() throws CheckstyleException {
187        if (headerFile != null) {
188            loadHeaderFile();
189        }
190        if (readerLines.isEmpty()) {
191            setHeader(null);
192        }
193    }
194
195    @Override
196    public Set<String> getExternalResourceLocations() {
197        return ImmutableSet.of(headerFile);
198    }
199}