View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2026 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.header;
21  
22  import java.io.BufferedInputStream;
23  import java.io.IOException;
24  import java.io.InputStreamReader;
25  import java.io.LineNumberReader;
26  import java.io.Reader;
27  import java.io.StringReader;
28  import java.net.URI;
29  import java.nio.charset.Charset;
30  import java.nio.charset.UnsupportedCharsetException;
31  import java.util.ArrayList;
32  import java.util.List;
33  import java.util.Set;
34  import java.util.regex.Pattern;
35  
36  import com.puppycrawl.tools.checkstyle.PropertyType;
37  import com.puppycrawl.tools.checkstyle.XdocsPropertyType;
38  import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck;
39  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
40  import com.puppycrawl.tools.checkstyle.api.ExternalResourceHolder;
41  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
42  
43  /**
44   * Abstract super class for header checks.
45   * Provides support for header and headerFile properties.
46   */
47  public abstract class AbstractHeaderCheck extends AbstractFileSetCheck
48      implements ExternalResourceHolder {
49  
50      /** Pattern to detect occurrences of '\n' in text. */
51      private static final Pattern ESCAPED_LINE_FEED_PATTERN = Pattern.compile("\\\\n");
52  
53      /** The lines of the header file. */
54      private final List<String> readerLines = new ArrayList<>();
55  
56      /** Specify the name of the file containing the required header. */
57      private URI headerFile;
58  
59      /** Specify the character encoding to use when reading the headerFile. */
60      @XdocsPropertyType(PropertyType.STRING)
61      private Charset charset;
62  
63      /**
64       * Creates a new {@code AbstractHeaderCheck} instance.
65       */
66      protected AbstractHeaderCheck() {
67          // no code by default
68      }
69  
70      /**
71       * Hook method for post-processing header lines.
72       * This implementation does nothing.
73       */
74      protected abstract void postProcessHeaderLines();
75  
76      /**
77       * Return the header lines to check against.
78       *
79       * @return the header lines to check against.
80       */
81      protected List<String> getHeaderLines() {
82          return List.copyOf(readerLines);
83      }
84  
85      /**
86       * Setter to specify the character encoding to use when reading the headerFile.
87       *
88       * @param charset the charset name to use for loading the header from a file
89       */
90      public void setCharset(String charset) {
91          this.charset = createCharset(charset);
92      }
93  
94      /**
95       * Setter to specify the name of the file containing the required header.
96       *
97       * @param uri the uri of the header to load.
98       * @throws CheckstyleException if fileName is empty.
99       */
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 }