001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2026 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.UnsupportedCharsetException;
031import java.util.ArrayList;
032import java.util.List;
033import java.util.Set;
034import java.util.regex.Pattern;
035
036import com.puppycrawl.tools.checkstyle.PropertyType;
037import com.puppycrawl.tools.checkstyle.XdocsPropertyType;
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    @XdocsPropertyType(PropertyType.STRING)
061    private Charset charset;
062
063    /**
064     * Creates a new {@code AbstractHeaderCheck} instance.
065     */
066    protected AbstractHeaderCheck() {
067        // no code by default
068    }
069
070    /**
071     * Hook method for post-processing header lines.
072     * This implementation does nothing.
073     */
074    protected abstract void postProcessHeaderLines();
075
076    /**
077     * Return the header lines to check against.
078     *
079     * @return the header lines to check against.
080     */
081    protected List<String> getHeaderLines() {
082        return List.copyOf(readerLines);
083    }
084
085    /**
086     * Setter to specify the character encoding to use when reading the headerFile.
087     *
088     * @param charset the charset name to use for loading the header from a file
089     */
090    public void setCharset(String charset) {
091        this.charset = createCharset(charset);
092    }
093
094    /**
095     * Setter to specify the name of the file containing the required header.
096     *
097     * @param uri the uri of the header to load.
098     * @throws CheckstyleException if fileName is empty.
099     */
100    public void setHeaderFile(URI uri) throws CheckstyleException {
101        if (uri == null) {
102            throw new CheckstyleException(
103                "property 'headerFile' is missing or invalid in module "
104                    + getConfiguration().getName());
105        }
106
107        headerFile = uri;
108    }
109
110    /**
111     * Load the header from a file.
112     *
113     * @throws CheckstyleException if the file cannot be loaded
114     */
115    private void loadHeaderFile() throws CheckstyleException {
116        checkHeaderNotInitialized();
117        try (Reader headerReader = new InputStreamReader(new BufferedInputStream(
118                    headerFile.toURL().openStream()), charset)) {
119            loadHeader(headerReader);
120        }
121        catch (final IOException exc) {
122            throw new CheckstyleException(
123                    "unable to load header file " + headerFile, exc);
124        }
125    }
126
127    /**
128     * Called before initializing the header.
129     *
130     * @throws IllegalArgumentException if header has already been set
131     */
132    private void checkHeaderNotInitialized() {
133        if (!readerLines.isEmpty()) {
134            throw new IllegalArgumentException(
135                    "header has already been set - "
136                    + "set either header or headerFile, not both");
137        }
138    }
139
140    /**
141     * Creates charset by name.
142     *
143     * @param name charset name
144     * @return created charset
145     * @throws UnsupportedCharsetException if charset is unsupported
146     */
147    private static Charset createCharset(String name) {
148        if (!Charset.isSupported(name)) {
149            final String message = "unsupported charset: '" + name + "'";
150            throw new UnsupportedCharsetException(message);
151        }
152        return Charset.forName(name);
153    }
154
155    /**
156     * Specify the required header specified inline.
157     * Individual header lines must be separated by the string
158     * {@code "\n"}(even on platforms with a different line separator).
159     *
160     * @param header header content to check against.
161     * @throws IllegalArgumentException if the header cannot be interpreted
162     */
163    public void setHeader(String header) {
164        if (!CommonUtil.isBlank(header)) {
165            checkHeaderNotInitialized();
166
167            final String headerExpandedNewLines = ESCAPED_LINE_FEED_PATTERN
168                    .matcher(header).replaceAll("\n");
169
170            try (Reader headerReader = new StringReader(headerExpandedNewLines)) {
171                loadHeader(headerReader);
172            }
173            catch (final IOException exc) {
174                throw new IllegalArgumentException("unable to load header", exc);
175            }
176        }
177    }
178
179    /**
180     * Load header to check against from a Reader into readerLines.
181     *
182     * @param headerReader delivers the header to check against
183     * @throws IOException if
184     */
185    private void loadHeader(final Reader headerReader) throws IOException {
186        try (LineNumberReader lnr = new LineNumberReader(headerReader)) {
187            String line;
188            do {
189                line = lnr.readLine();
190                if (line != null) {
191                    readerLines.add(line);
192                }
193            } while (line != null);
194            postProcessHeaderLines();
195        }
196    }
197
198    @Override
199    protected final void finishLocalSetup() throws CheckstyleException {
200        if (headerFile != null) {
201            loadHeaderFile();
202        }
203    }
204
205    @Override
206    public Set<String> getExternalResourceLocations() {
207        final Set<String> result;
208
209        if (headerFile == null) {
210            result = Set.of();
211        }
212        else {
213            result = Set.of(headerFile.toASCIIString());
214        }
215
216        return result;
217    }
218
219}