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