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