001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2020 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.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck;
039import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
040import com.puppycrawl.tools.checkstyle.api.ExternalResourceHolder;
041import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
042
043/**
044 * Abstract super class for header checks.
045 * Provides support for header and headerFile properties.
046 */
047public abstract class AbstractHeaderCheck extends AbstractFileSetCheck
048    implements ExternalResourceHolder {
049
050    /** Pattern to detect occurrences of '\n' in text. */
051    private static final Pattern ESCAPED_LINE_FEED_PATTERN = Pattern.compile("\\\\n");
052
053    /** The lines of the header file. */
054    private final List<String> readerLines = new ArrayList<>();
055
056    /** Specify the name of the file containing the required header. */
057    private URI headerFile;
058
059    /** Specify the character encoding to use when reading the headerFile. */
060    private String charset = System.getProperty("file.encoding", StandardCharsets.UTF_8.name());
061
062    /**
063     * Hook method for post processing header lines.
064     * This implementation does nothing.
065     */
066    protected abstract void postProcessHeaderLines();
067
068    /**
069     * Return the header lines to check against.
070     *
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     * Setter to specify the character encoding to use when reading the headerFile.
080     *
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     * Setter to specify the name of the file containing the required header..
094     *
095     * @param uri the uri of the header to load.
096     * @throws CheckstyleException if fileName is empty.
097     */
098    public void setHeaderFile(URI uri) throws CheckstyleException {
099        if (uri == null) {
100            throw new CheckstyleException(
101                "property 'headerFile' is missing or invalid in module "
102                    + getConfiguration().getName());
103        }
104
105        headerFile = uri;
106    }
107
108    /**
109     * Load the header from a file.
110     *
111     * @throws CheckstyleException if the file cannot be loaded
112     */
113    private void loadHeaderFile() throws CheckstyleException {
114        checkHeaderNotInitialized();
115        try (Reader headerReader = new InputStreamReader(new BufferedInputStream(
116                    headerFile.toURL().openStream()), charset)) {
117            loadHeader(headerReader);
118        }
119        catch (final IOException ex) {
120            throw new CheckstyleException(
121                    "unable to load header file " + headerFile, ex);
122        }
123    }
124
125    /**
126     * Called before initializing the header.
127     *
128     * @throws IllegalArgumentException if header has already been set
129     */
130    private void checkHeaderNotInitialized() {
131        if (!readerLines.isEmpty()) {
132            throw new IllegalArgumentException(
133                    "header has already been set - "
134                    + "set either header or headerFile, not both");
135        }
136    }
137
138    /**
139     * Set the header to check against. Individual lines in the header
140     * must be separated by '\n' characters.
141     *
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 (!CommonUtil.isBlank(header)) {
147            checkHeaderNotInitialized();
148
149            final String headerExpandedNewLines = ESCAPED_LINE_FEED_PATTERN
150                    .matcher(header).replaceAll("\n");
151
152            try (Reader headerReader = new StringReader(headerExpandedNewLines)) {
153                loadHeader(headerReader);
154            }
155            catch (final IOException ex) {
156                throw new IllegalArgumentException("unable to load header", ex);
157            }
158        }
159    }
160
161    /**
162     * Load header to check against from a Reader into readerLines.
163     *
164     * @param headerReader delivers the header to check against
165     * @throws IOException if
166     */
167    private void loadHeader(final Reader headerReader) throws IOException {
168        try (LineNumberReader lnr = new LineNumberReader(headerReader)) {
169            String line;
170            do {
171                line = lnr.readLine();
172                if (line != null) {
173                    readerLines.add(line);
174                }
175            } while (line != null);
176            postProcessHeaderLines();
177        }
178    }
179
180    @Override
181    protected final void finishLocalSetup() throws CheckstyleException {
182        if (headerFile != null) {
183            loadHeaderFile();
184        }
185    }
186
187    @Override
188    public Set<String> getExternalResourceLocations() {
189        final Set<String> result;
190
191        if (headerFile == null) {
192            result = Collections.emptySet();
193        }
194        else {
195            result = Collections.singleton(headerFile.toString());
196        }
197
198        return result;
199    }
200
201}