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}