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