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 static com.google.common.truth.Truth.assertWithMessage;
23  import static com.puppycrawl.tools.checkstyle.checks.header.MultiFileRegexpHeaderCheck.MSG_HEADER_MISMATCH;
24  import static com.puppycrawl.tools.checkstyle.checks.header.MultiFileRegexpHeaderCheck.MSG_HEADER_MISSING;
25  import static com.puppycrawl.tools.checkstyle.internal.utils.TestUtil.getExpectedThrowable;
26  import static com.puppycrawl.tools.checkstyle.utils.CommonUtil.EMPTY_STRING_ARRAY;
27  import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
28  
29  import java.io.File;
30  import java.io.IOException;
31  import java.net.URI;
32  import java.nio.charset.StandardCharsets;
33  import java.nio.file.Files;
34  import java.util.ArrayList;
35  import java.util.List;
36  import java.util.Set;
37  
38  import org.junit.jupiter.api.Test;
39  import org.junit.jupiter.api.io.TempDir;
40  
41  import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
42  import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
43  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
44  
45  /**
46   * Tests for {@link MultiFileRegexpHeaderCheck}.
47   *
48   * <p>
49   * IMPORTANT TEST CONFIGURATION NOTE:
50   * Test input files (like {@code Input...Config.java}) are loaded relative to
51   * {@link #getPackageLocation()}. The check itself uses paths to header template
52   * files. If a Java test input file's path is mistakenly used as a header
53   * template path for the check, it will cause errors.
54   * Always use distinct paths for test input files versus header template files.
55   * </p>
56   */
57  public class MultiFileRegexpHeaderCheckTest extends AbstractModuleTestSupport {
58  
59      @TempDir
60      public File temporaryFolder;
61  
62      @Override
63      public String getPackageLocation() {
64          return "com/puppycrawl/tools/checkstyle/checks/header/regexpheader";
65      }
66  
67      @Test
68      public void testSetHeaderUriNotSupport() {
69          final MultiFileRegexpHeaderCheck instance = new MultiFileRegexpHeaderCheck();
70          final IllegalArgumentException exc =
71                  getExpectedThrowable(IllegalArgumentException.class,
72                          () -> instance.setHeaderFiles(String.valueOf(URI.create("file://test"))));
73          assertWithMessage("Invalid exception message")
74                  .that(exc.getMessage())
75                  .isEqualTo("unable to load header file file://test");
76      }
77  
78      @Test
79      public void testSetHeaderFilesNull() throws CheckstyleException {
80          final DefaultConfiguration checkConfig =
81                  createModuleConfig(MultiFileRegexpHeaderCheck.class);
82  
83          final MultiFileRegexpHeaderCheck instance = new MultiFileRegexpHeaderCheck();
84  
85          instance.configure(checkConfig);
86  
87          assertWithMessage(
88                  "Expected no header files to be configured when"
89                  + " 'headerFiles' property is not set")
90              .that(instance.getExternalResourceLocations())
91              .isEmpty();
92      }
93  
94      @Test
95      public void testSetHeaderFilesIsBlank() throws CheckstyleException {
96          final DefaultConfiguration checkConfig =
97                  createModuleConfig(MultiFileRegexpHeaderCheck.class);
98          checkConfig.addProperty("headerFiles", " , ");
99  
100         final MultiFileRegexpHeaderCheck instance = new MultiFileRegexpHeaderCheck();
101 
102         instance.configure(checkConfig);
103 
104         assertWithMessage(
105                 "Expected no header files to be configured for a blank input string")
106             .that(instance.getExternalResourceLocations())
107             .isEmpty();
108     }
109 
110     @Test
111     public void testEmptyFilename() throws Exception {
112         final DefaultConfiguration checkConfig =
113                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
114         checkConfig.addProperty("headerFiles", "");
115 
116         final MultiFileRegexpHeaderCheck instance = new MultiFileRegexpHeaderCheck();
117         instance.configure(checkConfig);
118         assertWithMessage(
119                 "Expected no header files to be configured for an empty input string")
120             .that(instance.getExternalResourceLocations())
121             .isEmpty();
122 
123         verify(checkConfig, getPath("InputRegexpHeaderDefaultConfig.java"), EMPTY_STRING_ARRAY);
124     }
125 
126     @Test
127     public void testDefaultConfiguration() throws Exception {
128         final DefaultConfiguration checkConfig =
129                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
130         createChecker(checkConfig);
131         verify(checkConfig,
132                 getPath("InputRegexpHeaderDefaultConfig.java"), EMPTY_STRING_ARRAY);
133     }
134 
135     @Test
136     public void testAllHeaderLinesMatchedWithEmptyLine() throws Exception {
137         final DefaultConfiguration checkConfig =
138                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
139         checkConfig.addProperty("headerFiles", getPath("InputRegexpHeaderNewLines.header"));
140 
141         final MultiFileRegexpHeaderCheck checkInstance = new MultiFileRegexpHeaderCheck();
142         checkInstance.configure(checkConfig);
143 
144         final String[] expected = {
145             "3: " + getCheckMessage(MSG_HEADER_MISMATCH, "^$",
146                     getPath("InputRegexpHeaderNewLines.header")),
147         };
148         verify(checkConfig,
149                 getPath("InputRegexpHeaderConsecutiveNewLines.java"), expected);
150     }
151 
152     @Test
153     public void testEmptyHeaderFile() throws IOException {
154         final File emptyFile = new File(temporaryFolder, "empty.header");
155         Files.write(emptyFile.toPath(), new ArrayList<>(), StandardCharsets.UTF_8);
156         final URI fileUri = emptyFile.toURI();
157 
158         final IllegalArgumentException exc =
159                 getExpectedThrowable(IllegalArgumentException.class,
160                         () -> MultiFileRegexpHeaderCheck.getLines("empty.header", fileUri));
161         assertWithMessage("Unexpected exception message")
162                 .that(exc.getMessage())
163                 .contains("Header file is empty: empty.header");
164     }
165 
166     @Test
167     public void testRegexpHeader() throws Exception {
168         final DefaultConfiguration checkConfig =
169                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
170         checkConfig.addProperty("headerFiles", getPath("InputRegexpHeader.header"));
171 
172         final MultiFileRegexpHeaderCheck checkInstance = new MultiFileRegexpHeaderCheck();
173         checkInstance.configure(checkConfig);
174 
175         final String[] expected = {
176             "1: " + getCheckMessage(MSG_HEADER_MISMATCH, "^/*$",
177                     getPath("InputRegexpHeader.header")),
178         };
179         verify(checkConfig, getPath("InputRegexpHeaderNonMatching.java"),
180                 expected);
181     }
182 
183     @Test
184     public void testRegexpHeaderUrl() throws Exception {
185         final DefaultConfiguration checkConfig =
186                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
187         checkConfig.addProperty("headerFiles", getUriString("InputRegexpHeader.header"));
188 
189         final MultiFileRegexpHeaderCheck checkInstance = new MultiFileRegexpHeaderCheck();
190         checkInstance.configure(checkConfig);
191 
192         final String[] expected = {
193             "1: " + getCheckMessage(MSG_HEADER_MISMATCH, "^/*$",
194                     getUriString("InputRegexpHeader.header")),
195         };
196         verify(checkConfig, getPath("InputRegexpHeaderNonMatching.java"),
197                 expected);
198     }
199 
200     @Test
201     public void testInlineRegexpHeaderConsecutiveNewlinesThroughConfigFile() throws Exception {
202         final DefaultConfiguration checkConfig =
203                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
204         checkConfig.addProperty("headerFiles", getUriString("InputRegexpHeaderNewLines.header"));
205 
206         final MultiFileRegexpHeaderCheck checkInstance = new MultiFileRegexpHeaderCheck();
207         checkInstance.configure(checkConfig);
208 
209         final String[] expected = {
210             "3: " + getCheckMessage(MSG_HEADER_MISMATCH, "^$",
211                     getUriString("InputRegexpHeaderNewLines.header")),
212         };
213         verify(checkConfig,
214                 getPath("InputRegexpHeaderConsecutiveNewLines.java"), expected);
215     }
216 
217     @Test
218     public void testLogFirstMismatchNoMismatch() throws Exception {
219         final DefaultConfiguration checkConfig =
220                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
221         checkConfig.addProperty("headerFiles",
222                 getPath("InputRegexpHeader.header") + ","
223                         + getPath("InputRegexpHeader1.header")
224         );
225         verify(checkConfig, getPath("InputRegexpHeaderIgnore.java"),
226                 EMPTY_STRING_ARRAY);
227     }
228 
229     @Test
230     public void testAllHeaderLinesMatchedExactly() throws Exception {
231         final DefaultConfiguration checkConfig =
232                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
233         checkConfig.addProperty("headerFiles",
234                 getPath("InputRegexpHeader1.header"));
235         verify(checkConfig,
236                 getPath("InputRegexpHeaderIgnore.java"), EMPTY_STRING_ARRAY);
237     }
238 
239     @Test
240     public void testBlankPatternBranch() throws Exception {
241         final File headerFile = new File(temporaryFolder, "blankPattern.header");
242         Files.write(headerFile.toPath(),
243                 List.of("// First line", "// Second line", "   "),
244                 StandardCharsets.UTF_8);
245 
246         final File testFile = new File(temporaryFolder, "testFile.java");
247         Files.write(testFile.toPath(),
248                 List.of("// First line", "// Second line", "   "),
249                 StandardCharsets.UTF_8);
250 
251         final DefaultConfiguration checkConfig =
252                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
253         checkConfig.addProperty("headerFiles", headerFile.getPath());
254 
255         verify(checkConfig, testFile.getPath(), EMPTY_STRING_ARRAY);
256     }
257 
258     @Test
259     public void testMismatchInMiddleOfHeader() throws Exception {
260         final DefaultConfiguration checkConfig =
261                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
262         checkConfig.addProperty("headerFiles", getPath("InputRegexpHeader.header"));
263 
264         final MultiFileRegexpHeaderCheck checkInstance = new MultiFileRegexpHeaderCheck();
265         checkInstance.configure(checkConfig);
266 
267         final String[] expected = {
268             "2: " + getCheckMessage(MSG_HEADER_MISMATCH, "// .*",
269                     getPath("InputRegexpHeader.header")),
270         };
271         verify(checkConfig, getPath("InputRegexpHeaderMulti52.java"), expected);
272     }
273 
274     @Test
275     public void testMismatchInHeaderLine() throws Exception {
276         final DefaultConfiguration checkConfig =
277                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
278         checkConfig.addProperty("headerFiles", getPath("InputRegexpHeader.header"));
279 
280         final MultiFileRegexpHeaderCheck checkInstance = new MultiFileRegexpHeaderCheck();
281         checkInstance.configure(checkConfig);
282 
283         final String[] expected = {
284             "2: " + getCheckMessage(MSG_HEADER_MISMATCH, "// .*",
285                     getPath("InputRegexpHeader.header")),
286         };
287         verify(checkConfig, getPath("InputRegexpHeaderMulti52.java"), expected);
288     }
289 
290     @Test
291     public void testNoWarningIfSingleLinedLeft() throws Exception {
292         final DefaultConfiguration checkConfig =
293                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
294         checkConfig.addProperty("headerFiles", getPath("InputRegexpHeader4.header"));
295         verify(checkConfig, getPath("InputRegexpHeaderMulti5.java"), EMPTY_STRING_ARRAY);
296     }
297 
298     @Test
299     public void testEmptyPatternInHeader() throws Exception {
300         final File headerFile = new File(temporaryFolder, "headerFiles.header");
301         Files.write(headerFile.toPath(), List.of("", "valid"),
302             StandardCharsets.UTF_8);
303 
304         final DefaultConfiguration checkConfig =
305                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
306         checkConfig.addProperty("headerFiles", headerFile.getPath() + "," + headerFile.getPath());
307 
308         verify(checkConfig, headerFile.getPath(), EMPTY_STRING_ARRAY);
309     }
310 
311     @Test
312     public void testGetExternalResourceLocationsWithNoPropertySet() throws Exception {
313         final DefaultConfiguration configNoHeaderFiles =
314                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
315         final MultiFileRegexpHeaderCheck checkNoHeaders = new MultiFileRegexpHeaderCheck();
316         checkNoHeaders.configure(configNoHeaderFiles);
317 
318         final Set<String> locationsNoHeaders = checkNoHeaders.getExternalResourceLocations();
319         assertWithMessage(
320                 "External locations should be empty when headerFiles property is not set")
321             .that(locationsNoHeaders)
322             .isEmpty();
323     }
324 
325     @Test
326     public void testGetExternalResourceLocationsFromMultipleFilesSet() throws Exception {
327         final DefaultConfiguration configWithHeaderFiles =
328                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
329         final String header4Path = getPath("InputRegexpHeader4.header");
330         final String header1Path = getPath("InputRegexpHeader1.header");
331         configWithHeaderFiles.addProperty("headerFiles",
332                 header4Path + "," + header1Path);
333 
334         final MultiFileRegexpHeaderCheck checkWithHeaders = new MultiFileRegexpHeaderCheck();
335         checkWithHeaders.configure(configWithHeaderFiles);
336 
337         final Set<String> locationsWithHeaders = checkWithHeaders.getExternalResourceLocations();
338         assertWithMessage("Should have two external resource locations")
339             .that(locationsWithHeaders.size())
340             .isEqualTo(2);
341 
342         final String expectedUri4 = new File(header4Path).toURI().toASCIIString();
343         final String expectedUri1 = new File(header1Path).toURI().toASCIIString();
344 
345         assertWithMessage(
346                 "Locations should include URI for InputRegexpHeader4.header")
347             .that(locationsWithHeaders)
348             .contains(expectedUri4);
349         assertWithMessage(
350                 "Locations should include URI for InputRegexpHeader1.header")
351             .that(locationsWithHeaders)
352             .contains(expectedUri1);
353     }
354 
355     @Test
356     public void testAllLinesMatch() throws Exception {
357         final String[] fileLines = {
358             "// First line",
359             "// Second line",
360             "// Third line",
361         };
362 
363         final File testFile = new File(temporaryFolder, "test.java");
364         Files.write(testFile.toPath(), List.of(fileLines), StandardCharsets.UTF_8);
365 
366         final File headerFile = new File(temporaryFolder, "header.header");
367         Files.write(headerFile.toPath(), List.of(
368             "// First line",
369             "// Second line",
370             "// Third line"), StandardCharsets.UTF_8);
371 
372         final DefaultConfiguration checkConfig =
373                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
374         checkConfig.addProperty("headerFiles", headerFile.getPath());
375 
376         verify(checkConfig, testFile.getPath(), EMPTY_STRING_ARRAY);
377     }
378 
379     @Test
380     public void testEmptyPatternMatch() throws Exception {
381         final File fileWithBlank = new File(temporaryFolder, "blank.java");
382         Files.write(fileWithBlank.toPath(), List.of(
383             "// First line",
384             "",
385             "// Third line"), StandardCharsets.UTF_8);
386 
387         final File headerFile = new File(temporaryFolder, "header.header");
388         Files.write(headerFile.toPath(), List.of(
389             "// First line",
390             "^$",
391             "// Third line"), StandardCharsets.UTF_8);
392 
393         final DefaultConfiguration checkConfig =
394                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
395         checkConfig.addProperty("headerFiles", headerFile.getPath());
396 
397         verify(checkConfig, fileWithBlank.getPath(), EMPTY_STRING_ARRAY);
398     }
399 
400     @Test
401     public void testBlankLineMismatch() throws Exception {
402         final File fileNoBlank = new File(temporaryFolder, "noblank.java");
403         Files.write(fileNoBlank.toPath(), List.of(
404             "// First line",
405             "// Second line",
406             "// Third line"), StandardCharsets.UTF_8);
407 
408         final File headerFile = new File(temporaryFolder, "header.header");
409         Files.write(headerFile.toPath(), List.of(
410             "// First line",
411             "^$",
412             "// Third line"), StandardCharsets.UTF_8);
413 
414         final DefaultConfiguration checkConfig =
415                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
416         checkConfig.addProperty("headerFiles", headerFile.getPath());
417 
418         final MultiFileRegexpHeaderCheck checkInstance = new MultiFileRegexpHeaderCheck();
419         checkInstance.configure(checkConfig);
420 
421         final String[] expected = {
422             "2: " + getCheckMessage(MSG_HEADER_MISMATCH, "^$", headerFile.getPath()),
423         };
424         verify(checkConfig, fileNoBlank.getPath(), expected);
425     }
426 
427     @Test
428     public void testMultipleHeaderFilesAllInvalid() throws Exception {
429         final File testFile = new File(temporaryFolder, "test.java");
430         Files.write(testFile.toPath(), List.of(
431             "// Different content",
432             "// Not matching any headers"), StandardCharsets.UTF_8);
433 
434         final File header1File = new File(temporaryFolder, "header1.header");
435         Files.write(header1File.toPath(), List.of("// Header 1"),
436             StandardCharsets.UTF_8);
437 
438         final File header2File = new File(temporaryFolder, "header2.header");
439         Files.write(header2File.toPath(), List.of("// Header 2"),
440             StandardCharsets.UTF_8);
441 
442         final DefaultConfiguration checkConfig =
443                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
444         checkConfig.addProperty("headerFiles",
445             header1File.getPath() + "," + header2File.getPath());
446 
447         final MultiFileRegexpHeaderCheck checkInstance = new MultiFileRegexpHeaderCheck();
448         checkInstance.configure(checkConfig);
449 
450         final String[] expected = {
451             "1: " + getCheckMessage(MSG_HEADER_MISMATCH, "// Header 1",
452                     header1File.getPath() + ", " + header2File.getPath()),
453         };
454         verify(checkConfig, testFile.getPath(), expected);
455     }
456 
457     @Test
458     public void testMultipleHeaderFiles() throws Exception {
459         final File headerFile1 = new File(temporaryFolder, "header1.header");
460         Files.write(headerFile1.toPath(), List.of("Copyright", "Author: .*", ""),
461             StandardCharsets.UTF_8);
462 
463         final File headerFile2 = new File(temporaryFolder, "header2.header");
464         Files.write(headerFile2.toPath(), List.of("License", "Year: \\d{4}", ""),
465             StandardCharsets.UTF_8);
466 
467         final File testFile = new File(temporaryFolder, "TestFile.java");
468         Files.write(testFile.toPath(), List.of("Copyright", "Author: John Doe", ""),
469             StandardCharsets.UTF_8);
470 
471         final DefaultConfiguration checkConfig =
472                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
473         checkConfig.addProperty("headerFiles",
474                 headerFile1.getPath() + "," + headerFile2.getPath());
475 
476         verify(checkConfig, testFile.getPath(), EMPTY_STRING_ARRAY);
477     }
478 
479     @Test
480     public void testInvalidRegex() throws Exception {
481         final File corruptedHeaderFile = new File(temporaryFolder, "corrupted.header");
482         Files.write(corruptedHeaderFile.toPath(), List.of("Invalid regex [a-z"),
483             StandardCharsets.UTF_8);
484 
485         final MultiFileRegexpHeaderCheck check = new MultiFileRegexpHeaderCheck();
486 
487         final IllegalArgumentException exc =
488                 getExpectedThrowable(IllegalArgumentException.class,
489                         () -> check.setHeaderFiles(corruptedHeaderFile.getPath()));
490         assertWithMessage("Invalid exception message")
491                 .that(exc.getMessage())
492                 .isEqualTo("Invalid regex pattern: Invalid regex [a-z");
493     }
494 
495     @Test
496     public void testInvalidFileName() {
497         final MultiFileRegexpHeaderCheck check = new MultiFileRegexpHeaderCheck();
498         final String headerFile = "UnExisted file";
499         final IllegalArgumentException thrown =
500                 getExpectedThrowable(IllegalArgumentException.class, () -> {
501                     check.setHeaderFiles(headerFile);
502                 });
503         assertWithMessage("Exception message did not match for invalid file name.")
504                 .that(thrown.getMessage())
505                 .isEqualTo("Error reading or corrupted header file: " + headerFile);
506     }
507 
508     @Test
509     public void testHeaderMoreLinesThanFile() throws Exception {
510         final File testFile = new File(temporaryFolder, "shortFile.java");
511         Files.write(testFile.toPath(), List.of(
512             "// Different content",
513             "// Not matching header"), StandardCharsets.UTF_8);
514 
515         final File headerFile = new File(temporaryFolder, "longHeader.header");
516         Files.write(headerFile.toPath(), List.of(
517             "// First line",
518             "// Second line",
519             "// Third line",
520             "// Fourth line",
521             "// Fifth line"), StandardCharsets.UTF_8);
522 
523         final DefaultConfiguration checkConfig =
524                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
525         checkConfig.addProperty("headerFiles", headerFile.getPath());
526 
527         final MultiFileRegexpHeaderCheck checkInstance = new MultiFileRegexpHeaderCheck();
528         checkInstance.configure(checkConfig);
529 
530         final String[] expected = {
531             "1: " + getCheckMessage(MSG_HEADER_MISSING, headerFile.getPath(), headerFile.getPath()),
532         };
533         verify(checkConfig, testFile.getPath(), expected);
534     }
535 
536     @Test
537     public void testMismatchLineCondition() throws Exception {
538         final File testFile = new File(temporaryFolder, "mismatchFile.java");
539         Files.write(testFile.toPath(), List.of(
540             "// First line matches",
541             "// This line doesn't match the header pattern",
542             "// Third line matches"), StandardCharsets.UTF_8);
543 
544         final File headerFile = new File(temporaryFolder, "mismatchHeader.header");
545         Files.write(headerFile.toPath(), List.of(
546             "// First line matches",
547             "// Second line matches",
548             "// Third line matches"), StandardCharsets.UTF_8);
549 
550         final DefaultConfiguration checkConfig =
551                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
552         checkConfig.addProperty("headerFiles", headerFile.getPath());
553 
554         final MultiFileRegexpHeaderCheck checkInstance = new MultiFileRegexpHeaderCheck();
555         checkInstance.configure(checkConfig);
556 
557         final String[] expected = {
558             "2: " + getCheckMessage(MSG_HEADER_MISMATCH,
559                 "// Second line matches", headerFile.getPath()),
560         };
561         verify(checkConfig, testFile.getPath(), expected);
562     }
563 
564     @Test
565     public void testFindFirstMismatch() throws Exception {
566         final File testFile = new File(temporaryFolder, "test.java");
567         Files.write(testFile.toPath(), List.of(
568             "// First line",
569             "// Second line",
570             "// Third line"), StandardCharsets.UTF_8);
571 
572         final File headerFile = new File(temporaryFolder, "header.header");
573         Files.write(headerFile.toPath(), List.of(
574             "// First line",
575             "// Different line",
576             "// Third line"), StandardCharsets.UTF_8);
577 
578         final DefaultConfiguration checkConfig =
579                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
580         checkConfig.addProperty("headerFiles", headerFile.getPath());
581 
582         final MultiFileRegexpHeaderCheck checkInstance = new MultiFileRegexpHeaderCheck();
583         checkInstance.configure(checkConfig);
584 
585         final String[] expected = {
586             "2: " + getCheckMessage(MSG_HEADER_MISMATCH, "// Different line",
587                     headerFile.getPath()),
588         };
589         verify(checkConfig, testFile.getPath(), expected);
590     }
591 
592     @Test
593     public void testNoMatchingHeaderFile() throws Exception {
594         final File testFile = new File(temporaryFolder, "test.java");
595         Files.write(testFile.toPath(), List.of(
596             "// Different content",
597             "// Not matching any headers"), StandardCharsets.UTF_8);
598 
599         final File headerFile = new File(temporaryFolder, "header.header");
600         Files.write(headerFile.toPath(), List.of(
601             "// Header content",
602             "// Another line"), StandardCharsets.UTF_8);
603 
604         final DefaultConfiguration checkConfig =
605                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
606         checkConfig.addProperty("headerFiles", headerFile.getPath());
607 
608         final MultiFileRegexpHeaderCheck checkInstance = new MultiFileRegexpHeaderCheck();
609         checkInstance.configure(checkConfig);
610         final String[] expected = {
611             "1: " + getCheckMessage(MSG_HEADER_MISMATCH, "// Header content",
612                     headerFile.getPath()),
613         };
614         verify(checkConfig, testFile.getPath(), expected);
615     }
616 
617     @Test
618     public void testNonEmptyPatternsWithMismatchedCardinality() throws Exception {
619         final File headerFile = new File(temporaryFolder, "header.header");
620         Files.write(headerFile.toPath(), List.of(
621             "// First line",
622             "// Second line",
623             "// Third line with different pattern"
624         ), StandardCharsets.UTF_8);
625 
626         final File testFile = new File(temporaryFolder, "testFile.java");
627         Files.write(testFile.toPath(), List.of(
628             "// First line",
629             "// Second line",
630             "// Third line that does not match"
631         ), StandardCharsets.UTF_8);
632 
633         final DefaultConfiguration checkConfig =
634             createModuleConfig(MultiFileRegexpHeaderCheck.class);
635         checkConfig.addProperty("headerFiles", headerFile.getPath());
636 
637         final MultiFileRegexpHeaderCheck checkInstance = new MultiFileRegexpHeaderCheck();
638         checkInstance.configure(checkConfig);
639 
640         final String[] expected = {
641             "3: " + getCheckMessage(MSG_HEADER_MISMATCH,
642                     "// Third line with different pattern", headerFile.getPath()),
643         };
644         verify(checkConfig, testFile.getPath(), expected);
645     }
646 
647     @Test
648     public void testFileFailsWhenShorterThanHeader() throws Exception {
649         final File headerFile = new File(temporaryFolder, "longerHeader.header");
650         Files.write(headerFile.toPath(), List.of(
651             "// Line 1",
652             "// Line 2",
653             "// Line 3"
654         ), StandardCharsets.UTF_8);
655 
656         final File testFile = new File(temporaryFolder, "shorterFile.java");
657         Files.write(testFile.toPath(), List.of(
658             "// Line 1"
659         ), StandardCharsets.UTF_8);
660 
661         final DefaultConfiguration checkConfig =
662             createModuleConfig(MultiFileRegexpHeaderCheck.class);
663         checkConfig.addProperty("headerFiles", headerFile.getPath());
664 
665         final MultiFileRegexpHeaderCheck checkInstance = new MultiFileRegexpHeaderCheck();
666         checkInstance.configure(checkConfig);
667 
668         final String[] expected = {
669             "1: " + getCheckMessage(MSG_HEADER_MISSING, headerFile.getPath(), headerFile.getPath()),
670         };
671         verify(checkConfig, testFile.getPath(), expected);
672     }
673 
674     @Test
675     public void testHeaderWithTrailingBlanksFailsWhenFileIsShorter() throws Exception {
676         final File headerFile = new File(temporaryFolder, "headerWithBlanks.header");
677         Files.write(headerFile.toPath(), List.of(
678             "// Line 1",
679             "",
680             "// Line 3"
681         ), StandardCharsets.UTF_8);
682 
683         final File testFile = new File(temporaryFolder, "shorterFile.java");
684         Files.write(testFile.toPath(), List.of(
685             "// Line 1"
686         ), StandardCharsets.UTF_8);
687 
688         final DefaultConfiguration checkConfig =
689             createModuleConfig(MultiFileRegexpHeaderCheck.class);
690         checkConfig.addProperty("headerFiles", headerFile.getPath());
691 
692         final MultiFileRegexpHeaderCheck checkInstance = new MultiFileRegexpHeaderCheck();
693         checkInstance.configure(checkConfig);
694 
695         final String[] expected = {
696             "1: " + getCheckMessage(MSG_HEADER_MISSING, headerFile.getPath(), headerFile.getPath()),
697         };
698         verify(checkConfig, testFile.getPath(), expected);
699     }
700 
701     @Test
702     public void testFileSizeGreaterThanHeaderPatternSize() throws Exception {
703         final File headerFile = new File(temporaryFolder, "shorterHeader.header");
704         Files.write(headerFile.toPath(), List.of(
705             "// First line",
706             "// Second line",
707             "// Third line"), StandardCharsets.UTF_8);
708 
709         // Create a test file with more lines than the header
710         final File testFile = new File(temporaryFolder, "longerFile.java");
711         Files.write(testFile.toPath(), List.of(
712             "// First line",
713             "// Second line",
714             "// Third line",
715             "// Fourth line",
716             "// Fifth line"), StandardCharsets.UTF_8);
717 
718         final DefaultConfiguration checkConfig =
719                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
720         checkConfig.addProperty("headerFiles", headerFile.getPath());
721 
722         verify(checkConfig, testFile.getPath(), EMPTY_STRING_ARRAY);
723     }
724 
725     @Test
726     public void testFileSizeEqualHeaderPatternSize() throws Exception {
727         final File headerFile = new File(temporaryFolder, "shorterHeader.header");
728         Files.write(headerFile.toPath(), List.of(
729                 "// First line",
730                 "// Second line",
731                 "// Third line"), StandardCharsets.UTF_8);
732 
733         final File testFile = new File(temporaryFolder, "longerFile.java");
734         Files.write(testFile.toPath(), List.of(
735                 "// First line",
736                 "// Second line",
737                 "// Third line"), StandardCharsets.UTF_8);
738 
739         final DefaultConfiguration checkConfig =
740                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
741         checkConfig.addProperty("headerFiles", headerFile.getPath());
742 
743         verify(checkConfig, testFile.getPath(), EMPTY_STRING_ARRAY);
744     }
745 
746     @Test
747     public void testSpecificLineMismatch() throws Exception {
748         final File headerFile = new File(temporaryFolder, "multiline.header");
749         Files.write(headerFile.toPath(), List.of(
750             "// First line",
751             "// Second line",
752             "// Third line with pattern .*",
753             "// Fourth line",
754             "// Fifth line"), StandardCharsets.UTF_8);
755 
756         final File testFile = new File(temporaryFolder, "mismatchThirdLine.java");
757         Files.write(testFile.toPath(), List.of(
758             "// First line",
759             "// Second line",
760             "This line doesn't match the pattern",
761             "// Fourth line",
762             "// Fifth line"), StandardCharsets.UTF_8);
763 
764         final DefaultConfiguration checkConfig =
765                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
766         checkConfig.addProperty("headerFiles", headerFile.getPath());
767 
768         final MultiFileRegexpHeaderCheck checkInstance = new MultiFileRegexpHeaderCheck();
769         checkInstance.configure(checkConfig);
770 
771         final String[] expected = {
772             "3: " + getCheckMessage(MSG_HEADER_MISMATCH, "// Third line with pattern .*",
773                     headerFile.getPath()),
774         };
775         verify(checkConfig, testFile.getPath(), expected);
776     }
777 
778     @Test
779     public void testMultipleHeaderFilesFirstMatchesSecondDoesnt() throws Exception {
780         final File matchingHeaderFile = new File(temporaryFolder, "matching.header");
781         Files.write(matchingHeaderFile.toPath(), List.of(
782             "// This header matches",
783             "// The file content"), StandardCharsets.UTF_8);
784 
785         final File nonMatchingHeaderFile = new File(temporaryFolder, "nonMatching.header");
786         Files.write(nonMatchingHeaderFile.toPath(), List.of(
787             "// This header doesn't match",
788             "// Different content"), StandardCharsets.UTF_8);
789 
790         final File testFile = new File(temporaryFolder, "TestFile.java");
791         Files.write(testFile.toPath(), List.of(
792             "// This header matches",
793             "// The file content"), StandardCharsets.UTF_8);
794 
795         final DefaultConfiguration checkConfig =
796                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
797         checkConfig.addProperty("headerFiles",
798             matchingHeaderFile.getPath() + "," + nonMatchingHeaderFile.getPath());
799         verify(checkConfig, testFile.getPath(), EMPTY_STRING_ARRAY);
800     }
801 
802     @Test
803     public void testMultipleHeaderFilesNoneMatch() throws Exception {
804         final File matchingHeader = new File(temporaryFolder, "nonMatching1.header");
805         Files.write(matchingHeader.toPath(), List.of(
806             "// First matching header",
807             "// Second matching header"), StandardCharsets.UTF_8);
808 
809         final File nonMatchingHeader = new File(temporaryFolder, "nonMatching2.header");
810         Files.write(nonMatchingHeader.toPath(), List.of(
811             "// First matching header",
812             "// Second no-matching header"), StandardCharsets.UTF_8);
813 
814         final File testFile = new File(temporaryFolder, "TestFile.java");
815         Files.write(testFile.toPath(), List.of(
816             "// First matching header",
817             "// Second matching header"), StandardCharsets.UTF_8);
818 
819         final DefaultConfiguration checkConfig =
820                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
821         checkConfig.addProperty("headerFiles",
822             matchingHeader.getPath() + "," + nonMatchingHeader.getPath());
823 
824         verify(checkConfig, testFile.getPath(), EMPTY_STRING_ARRAY);
825     }
826 
827     @Test
828     public void testSetHeaderFilesWithNullPathThrowsException() {
829         final MultiFileRegexpHeaderCheck check = new MultiFileRegexpHeaderCheck();
830 
831         final IllegalArgumentException thrown =
832                 getExpectedThrowable(IllegalArgumentException.class, () -> {
833                     check.setHeaderFiles((String) null);
834                 });
835         assertWithMessage("Exception message mismatch for null header path")
836                 .that(thrown.getMessage()).isEqualTo("Header file is not set");
837     }
838 
839     @Test
840     public void testSetHeaderFilesWithNullVarargsArray() {
841         final MultiFileRegexpHeaderCheck check = new MultiFileRegexpHeaderCheck();
842 
843         assertDoesNotThrow(() -> {
844             check.setHeaderFiles((String[]) null);
845         });
846 
847         assertWithMessage(
848             "External locations should be empty when setHeaderFiles is called with null array")
849             .that(check.getExternalResourceLocations())
850             .isEmpty();
851     }
852 
853     @Test
854     public void testSetHeaderFilesClearsPreviousConfiguration() throws IOException {
855         final MultiFileRegexpHeaderCheck check = new MultiFileRegexpHeaderCheck();
856 
857         final File headerFileA = new File(temporaryFolder, "testHeaderA_clear_check.header");
858         Files.write(headerFileA.toPath(), List.of("// Header A for clear check"),
859                 StandardCharsets.UTF_8);
860         final String pathA = headerFileA.getAbsolutePath();
861         final URI uriA = headerFileA.toURI();
862 
863         check.setHeaderFiles(pathA);
864         assertWithMessage("After first set, only header A should be present")
865                 .that(check.getExternalResourceLocations())
866                 .containsExactly(uriA.toASCIIString());
867 
868         final File headerFileB = new File(temporaryFolder, "testHeaderB_clear_check.header");
869         Files.write(headerFileB.toPath(), List.of("// Header B for clear check"),
870                 StandardCharsets.UTF_8);
871         final String pathB = headerFileB.getAbsolutePath();
872         final URI uriB = headerFileB.toURI();
873 
874         check.setHeaderFiles(pathB);
875         assertWithMessage("After second set, only header B should be present")
876                 .that(check.getExternalResourceLocations())
877                 .containsExactly(uriB.toASCIIString());
878     }
879 
880     @Test
881     public void testHeaderFileMetadataMethods() throws Exception {
882         final File headerFile = new File(temporaryFolder, "header.header");
883         Files.write(headerFile.toPath(), List.of(
884             "// First line",
885             "",
886             "// Third line"), StandardCharsets.UTF_8);
887 
888         final MultiFileRegexpHeaderCheck check = new MultiFileRegexpHeaderCheck();
889         check.setHeaderFiles(headerFile.getPath());
890 
891         final Set<String> locations = check.getExternalResourceLocations();
892         assertWithMessage("Should have one external resource location")
893             .that(locations.size())
894             .isEqualTo(1);
895         assertWithMessage("Location should include header.header")
896             .that(locations.stream()
897                 .anyMatch(loc -> loc.contains("header.header")))
898             .isTrue();
899     }
900 
901     @Test
902     public void testIoExceptionInGetLines() {
903         final File nonExistentFile = new File(temporaryFolder, "nonexistent.header");
904         final URI fileUri = nonExistentFile.toURI();
905 
906         final IllegalArgumentException exc =
907                 getExpectedThrowable(IllegalArgumentException.class,
908                         () -> MultiFileRegexpHeaderCheck.getLines("nonexistent.header", fileUri));
909         assertWithMessage("Unexpected exception message")
910                 .that(exc.getMessage())
911                 .contains("unable to load header file nonexistent.header");
912     }
913 }