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