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