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