001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2022 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.net.URI;
029import java.nio.charset.Charset;
030import java.nio.charset.StandardCharsets;
031import java.nio.charset.UnsupportedCharsetException;
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.PropertyType;
039import com.puppycrawl.tools.checkstyle.XdocsPropertyType;
040import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck;
041import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
042import com.puppycrawl.tools.checkstyle.api.ExternalResourceHolder;
043import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
044
045/**
046 * Abstract super class for header checks.
047 * Provides support for header and headerFile properties.
048 */
049public abstract class AbstractHeaderCheck extends AbstractFileSetCheck
050    implements ExternalResourceHolder {
051
052    /** Pattern to detect occurrences of '\n' in text. */
053    private static final Pattern ESCAPED_LINE_FEED_PATTERN = Pattern.compile("\\\\n");
054
055    /** The lines of the header file. */
056    private final List<String> readerLines = new ArrayList<>();
057
058    /** Specify the name of the file containing the required header. */
059    private URI headerFile;
060
061    /** Specify the character encoding to use when reading the headerFile. */
062    @XdocsPropertyType(PropertyType.STRING)
063    private Charset charset = createCharset(System.getProperty("file.encoding",
064        StandardCharsets.UTF_8.name()));
065
066    /**
067     * Hook method for post-processing header lines.
068     * This implementation does nothing.
069     */
070    protected abstract void postProcessHeaderLines();
071
072    /**
073     * Return the header lines to check against.
074     *
075     * @return the header lines to check against.
076     */
077    protected List<String> getHeaderLines() {
078        return List.copyOf(readerLines);
079    }
080
081    /**
082     * Setter to specify the charset to use when reading the headerFile.
083     *
084     * @param charset the charset name to use for loading the header from a file
085     */
086    public void setCharset(String charset) {
087        this.charset = createCharset(charset);
088    }
089
090    /**
091     * Setter to specify the name of the file containing the required header..
092     *
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     *
109     * @throws CheckstyleException if the file cannot be loaded
110     */
111    private void loadHeaderFile() throws CheckstyleException {
112        checkHeaderNotInitialized();
113        try (Reader headerReader = new InputStreamReader(new BufferedInputStream(
114                    headerFile.toURL().openStream()), charset)) {
115            loadHeader(headerReader);
116        }
117        catch (final IOException ex) {
118            throw new CheckstyleException(
119                    "unable to load header file " + headerFile, ex);
120        }
121    }
122
123    /**
124     * Called before initializing the header.
125     *
126     * @throws IllegalArgumentException if header has already been set
127     */
128    private void checkHeaderNotInitialized() {
129        if (!readerLines.isEmpty()) {
130            throw new IllegalArgumentException(
131                    "header has already been set - "
132                    + "set either header or headerFile, not both");
133        }
134    }
135
136    /**
137     * Creates charset by name.
138     *
139     * @param name charset name
140     * @return created charset
141     * @throws UnsupportedCharsetException if charset is unsupported
142     */
143    private static Charset createCharset(String name) {
144        if (!Charset.isSupported(name)) {
145            final String message = "unsupported charset: '" + name + "'";
146            throw new UnsupportedCharsetException(message);
147        }
148        return Charset.forName(name);
149    }
150
151    /**
152     * Set the header to check against. Individual lines in the header
153     * must be separated by '\n' characters.
154     *
155     * @param header header content to check against.
156     * @throws IllegalArgumentException if the header cannot be interpreted
157     */
158    public void setHeader(String header) {
159        if (!CommonUtil.isBlank(header)) {
160            checkHeaderNotInitialized();
161
162            final String headerExpandedNewLines = ESCAPED_LINE_FEED_PATTERN
163                    .matcher(header).replaceAll("\n");
164
165            try (Reader headerReader = new StringReader(headerExpandedNewLines)) {
166                loadHeader(headerReader);
167            }
168            catch (final IOException ex) {
169                throw new IllegalArgumentException("unable to load header", ex);
170            }
171        }
172    }
173
174    /**
175     * Load header to check against from a Reader into readerLines.
176     *
177     * @param headerReader delivers the header to check against
178     * @throws IOException if
179     */
180    private void loadHeader(final Reader headerReader) throws IOException {
181        try (LineNumberReader lnr = new LineNumberReader(headerReader)) {
182            String line;
183            do {
184                line = lnr.readLine();
185                if (line != null) {
186                    readerLines.add(line);
187                }
188            } while (line != null);
189            postProcessHeaderLines();
190        }
191    }
192
193    @Override
194    protected final void finishLocalSetup() throws CheckstyleException {
195        if (headerFile != null) {
196            loadHeaderFile();
197        }
198    }
199
200    @Override
201    public Set<String> getExternalResourceLocations() {
202        final Set<String> result;
203
204        if (headerFile == null) {
205            result = Collections.emptySet();
206        }
207        else {
208            result = Collections.singleton(headerFile.toString());
209        }
210
211        return result;
212    }
213
214}