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.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      protected 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         final String allConfiguredPaths = checkInstance.getConfiguredHeaderPaths();
147 
148         final String[] expected = {
149             "3: " + getCheckMessage(MSG_HEADER_MISMATCH, "^$", allConfiguredPaths),
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         final String allConfiguredPaths = checkInstance.getConfiguredHeaderPaths();
183 
184         final String[] expected = {
185             "1: " + getCheckMessage(MSG_HEADER_MISMATCH, "^/*$", allConfiguredPaths),
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         final String allConfiguredPaths = checkInstance.getConfiguredHeaderPaths();
200 
201         final String[] expected = {
202             "1: " + getCheckMessage(MSG_HEADER_MISMATCH, "^/*$", allConfiguredPaths),
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         final String allConfiguredPaths = checkInstance.getConfiguredHeaderPaths();
217 
218         final String[] expected = {
219             "3: " + getCheckMessage(MSG_HEADER_MISMATCH, "^$", allConfiguredPaths),
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         final String allPaths = checkInstance.getConfiguredHeaderPaths();
275 
276         final String[] expected = {
277             "2: " + getCheckMessage(MSG_HEADER_MISMATCH, "// .*", allPaths),
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         final String allPaths2 = checkInstance.getConfiguredHeaderPaths();
291 
292         final String[] expected = {
293             "2: " + getCheckMessage(MSG_HEADER_MISMATCH, "// .*", allPaths2),
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         final String allPaths = checkInstance.getConfiguredHeaderPaths();
429 
430         final String[] expected = {
431             "2: " + getCheckMessage(MSG_HEADER_MISMATCH, "^$", allPaths),
432         };
433         verify(checkConfig, fileNoBlank.getPath(), expected);
434     }
435 
436     @Test
437     public void testMultipleHeaderFilesAllInvalid() throws Exception {
438         final File testFile = new File(temporaryFolder, "test.java");
439         Files.write(testFile.toPath(), List.of(
440             "// Different content",
441             "// Not matching any headers"), StandardCharsets.UTF_8);
442 
443         final File header1File = new File(temporaryFolder, "header1.header");
444         Files.write(header1File.toPath(), List.of("// Header 1"),
445             StandardCharsets.UTF_8);
446 
447         final File header2File = new File(temporaryFolder, "header2.header");
448         Files.write(header2File.toPath(), List.of("// Header 2"),
449             StandardCharsets.UTF_8);
450 
451         final DefaultConfiguration checkConfig =
452                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
453         checkConfig.addProperty("headerFiles",
454             header1File.getPath() + "," + header2File.getPath());
455 
456         final MultiFileRegexpHeaderCheck checkInstance = new MultiFileRegexpHeaderCheck();
457         checkInstance.configure(checkConfig);
458         final String allPaths = checkInstance.getConfiguredHeaderPaths();
459 
460         final String[] expected = {
461             "1: " + getCheckMessage(MSG_HEADER_MISMATCH, "// Header 1", allPaths),
462         };
463         verify(checkConfig, testFile.getPath(), expected);
464     }
465 
466     @Test
467     public void testMultipleHeaderFiles() throws Exception {
468         final File headerFile1 = new File(temporaryFolder, "header1.header");
469         Files.write(headerFile1.toPath(), List.of("Copyright", "Author: .*", ""),
470             StandardCharsets.UTF_8);
471 
472         final File headerFile2 = new File(temporaryFolder, "header2.header");
473         Files.write(headerFile2.toPath(), List.of("License", "Year: \\d{4}", ""),
474             StandardCharsets.UTF_8);
475 
476         final File testFile = new File(temporaryFolder, "TestFile.java");
477         Files.write(testFile.toPath(), List.of("Copyright", "Author: John Doe", ""),
478             StandardCharsets.UTF_8);
479 
480         final DefaultConfiguration checkConfig =
481                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
482         checkConfig.addProperty("headerFiles",
483                 headerFile1.getPath() + "," + headerFile2.getPath());
484 
485         verify(checkConfig, testFile.getPath(), EMPTY_STRING_ARRAY);
486     }
487 
488     @Test
489     public void testInvalidRegex() throws Exception {
490         final File corruptedHeaderFile = new File(temporaryFolder, "corrupted.header");
491         Files.write(corruptedHeaderFile.toPath(), List.of("Invalid regex [a-z"),
492             StandardCharsets.UTF_8);
493 
494         final MultiFileRegexpHeaderCheck check = new MultiFileRegexpHeaderCheck();
495 
496         try {
497             check.setHeaderFiles(corruptedHeaderFile.getPath());
498             assertWithMessage("Expected exception for corrupted header file").fail();
499         }
500         catch (IllegalArgumentException exc) {
501             assertWithMessage("Invalid exception message")
502                 .that(exc.getMessage())
503                 .isEqualTo("Invalid regex pattern: Invalid regex [a-z");
504         }
505     }
506 
507     @Test
508     public void testInvalidFileName() {
509         final MultiFileRegexpHeaderCheck check = new MultiFileRegexpHeaderCheck();
510         final String headerFile = "UnExisted file";
511         final IllegalArgumentException thrown =
512                 getExpectedThrowable(IllegalArgumentException.class, () -> {
513                     check.setHeaderFiles(headerFile);
514                 });
515         assertWithMessage("Exception message did not match for invalid file name.")
516                 .that(thrown.getMessage())
517                 .isEqualTo("Error reading or corrupted header file: " + headerFile);
518     }
519 
520     @Test
521     public void testHeaderMoreLinesThanFile() throws Exception {
522         final File testFile = new File(temporaryFolder, "shortFile.java");
523         Files.write(testFile.toPath(), List.of(
524             "// Different content",
525             "// Not matching header"), StandardCharsets.UTF_8);
526 
527         final File headerFile = new File(temporaryFolder, "longHeader.header");
528         Files.write(headerFile.toPath(), List.of(
529             "// First line",
530             "// Second line",
531             "// Third line",
532             "// Fourth line",
533             "// Fifth line"), StandardCharsets.UTF_8);
534 
535         final DefaultConfiguration checkConfig =
536                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
537         checkConfig.addProperty("headerFiles", headerFile.getPath());
538 
539         final MultiFileRegexpHeaderCheck checkInstance = new MultiFileRegexpHeaderCheck();
540         checkInstance.configure(checkConfig);
541         final String allPaths = checkInstance.getConfiguredHeaderPaths();
542 
543         final String[] expected = {
544             "1: " + getCheckMessage(MSG_HEADER_MISSING, headerFile.getPath(), allPaths),
545         };
546         verify(checkConfig, testFile.getPath(), expected);
547     }
548 
549     @Test
550     public void testMismatchLineCondition() throws Exception {
551         final File testFile = new File(temporaryFolder, "mismatchFile.java");
552         Files.write(testFile.toPath(), List.of(
553             "// First line matches",
554             "// This line doesn't match the header pattern",
555             "// Third line matches"), StandardCharsets.UTF_8);
556 
557         final File headerFile = new File(temporaryFolder, "mismatchHeader.header");
558         Files.write(headerFile.toPath(), List.of(
559             "// First line matches",
560             "// Second line matches",
561             "// Third line matches"), StandardCharsets.UTF_8);
562 
563         final DefaultConfiguration checkConfig =
564                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
565         checkConfig.addProperty("headerFiles", headerFile.getPath());
566 
567         final MultiFileRegexpHeaderCheck checkInstance = new MultiFileRegexpHeaderCheck();
568         checkInstance.configure(checkConfig);
569         final String allPaths = checkInstance.getConfiguredHeaderPaths();
570 
571         final String[] expected = {
572             "2: " + getCheckMessage(MSG_HEADER_MISMATCH,
573                 "// Second line matches", allPaths),
574         };
575         verify(checkConfig, testFile.getPath(), expected);
576     }
577 
578     @Test
579     public void testFindFirstMismatch() throws Exception {
580         final File testFile = new File(temporaryFolder, "test.java");
581         Files.write(testFile.toPath(), List.of(
582             "// First line",
583             "// Second line",
584             "// Third line"), StandardCharsets.UTF_8);
585 
586         final File headerFile = new File(temporaryFolder, "header.header");
587         Files.write(headerFile.toPath(), List.of(
588             "// First line",
589             "// Different line",
590             "// Third line"), StandardCharsets.UTF_8);
591 
592         final DefaultConfiguration checkConfig =
593                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
594         checkConfig.addProperty("headerFiles", headerFile.getPath());
595 
596         final MultiFileRegexpHeaderCheck checkInstance = new MultiFileRegexpHeaderCheck();
597         checkInstance.configure(checkConfig);
598         final String allPaths = checkInstance.getConfiguredHeaderPaths();
599 
600         final String[] expected = {
601             "2: " + getCheckMessage(MSG_HEADER_MISMATCH, "// Different line", allPaths),
602         };
603         verify(checkConfig, testFile.getPath(), expected);
604     }
605 
606     @Test
607     public void testNoMatchingHeaderFile() throws Exception {
608         final File testFile = new File(temporaryFolder, "test.java");
609         Files.write(testFile.toPath(), List.of(
610             "// Different content",
611             "// Not matching any headers"), StandardCharsets.UTF_8);
612 
613         final File headerFile = new File(temporaryFolder, "header.header");
614         Files.write(headerFile.toPath(), List.of(
615             "// Header content",
616             "// Another line"), StandardCharsets.UTF_8);
617 
618         final DefaultConfiguration checkConfig =
619                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
620         checkConfig.addProperty("headerFiles", headerFile.getPath());
621 
622         final MultiFileRegexpHeaderCheck checkInstance = new MultiFileRegexpHeaderCheck();
623         checkInstance.configure(checkConfig);
624         final String allPaths = checkInstance.getConfiguredHeaderPaths();
625 
626         final String[] expected = {
627             "1: " + getCheckMessage(MSG_HEADER_MISMATCH, "// Header content", allPaths),
628         };
629         verify(checkConfig, testFile.getPath(), expected);
630     }
631 
632     @Test
633     public void testNonEmptyPatternsWithMismatchedCardinality() throws Exception {
634         final File headerFile = new File(temporaryFolder, "header.header");
635         Files.write(headerFile.toPath(), List.of(
636             "// First line",
637             "// Second line",
638             "// Third line with different pattern"
639         ), StandardCharsets.UTF_8);
640 
641         final File testFile = new File(temporaryFolder, "testFile.java");
642         Files.write(testFile.toPath(), List.of(
643             "// First line",
644             "// Second line",
645             "// Third line that does not match"
646         ), StandardCharsets.UTF_8);
647 
648         final DefaultConfiguration checkConfig =
649             createModuleConfig(MultiFileRegexpHeaderCheck.class);
650         checkConfig.addProperty("headerFiles", headerFile.getPath());
651 
652         final MultiFileRegexpHeaderCheck checkInstance = new MultiFileRegexpHeaderCheck();
653         checkInstance.configure(checkConfig);
654         final String allPaths = checkInstance.getConfiguredHeaderPaths();
655 
656         final String[] expected = {
657             "3: " + getCheckMessage(MSG_HEADER_MISMATCH,
658                     "// Third line with different pattern", allPaths),
659         };
660         verify(checkConfig, testFile.getPath(), expected);
661     }
662 
663     @Test
664     public void testFileFailsWhenShorterThanHeader() throws Exception {
665         final File headerFile = new File(temporaryFolder, "longerHeader.header");
666         Files.write(headerFile.toPath(), List.of(
667             "// Line 1",
668             "// Line 2",
669             "// Line 3"
670         ), StandardCharsets.UTF_8);
671 
672         final File testFile = new File(temporaryFolder, "shorterFile.java");
673         Files.write(testFile.toPath(), List.of(
674             "// Line 1"
675         ), StandardCharsets.UTF_8);
676 
677         final DefaultConfiguration checkConfig =
678             createModuleConfig(MultiFileRegexpHeaderCheck.class);
679         checkConfig.addProperty("headerFiles", headerFile.getPath());
680 
681         final MultiFileRegexpHeaderCheck checkInstance = new MultiFileRegexpHeaderCheck();
682         checkInstance.configure(checkConfig);
683         final String allPaths = checkInstance.getConfiguredHeaderPaths();
684 
685         final String[] expected = {
686             "1: " + getCheckMessage(MSG_HEADER_MISSING, headerFile.getPath(), allPaths),
687         };
688         verify(checkConfig, testFile.getPath(), expected);
689     }
690 
691     @Test
692     public void testHeaderWithTrailingBlanksFailsWhenFileIsShorter() throws Exception {
693         final File headerFile = new File(temporaryFolder, "headerWithBlanks.header");
694         Files.write(headerFile.toPath(), List.of(
695             "// Line 1",
696             "",
697             "// Line 3"
698         ), StandardCharsets.UTF_8);
699 
700         final File testFile = new File(temporaryFolder, "shorterFile.java");
701         Files.write(testFile.toPath(), List.of(
702             "// Line 1"
703         ), StandardCharsets.UTF_8);
704 
705         final DefaultConfiguration checkConfig =
706             createModuleConfig(MultiFileRegexpHeaderCheck.class);
707         checkConfig.addProperty("headerFiles", headerFile.getPath());
708 
709         final MultiFileRegexpHeaderCheck checkInstance = new MultiFileRegexpHeaderCheck();
710         checkInstance.configure(checkConfig);
711         final String allPaths = checkInstance.getConfiguredHeaderPaths();
712 
713         final String[] expected = {
714             "1: " + getCheckMessage(MSG_HEADER_MISSING, headerFile.getPath(), allPaths),
715         };
716         verify(checkConfig, testFile.getPath(), expected);
717     }
718 
719     @Test
720     public void testFileSizeGreaterThanHeaderPatternSize() throws Exception {
721         final File headerFile = new File(temporaryFolder, "shorterHeader.header");
722         Files.write(headerFile.toPath(), List.of(
723             "// First line",
724             "// Second line",
725             "// Third line"), StandardCharsets.UTF_8);
726 
727         // Create a test file with more lines than the header
728         final File testFile = new File(temporaryFolder, "longerFile.java");
729         Files.write(testFile.toPath(), List.of(
730             "// First line",
731             "// Second line",
732             "// Third line",
733             "// Fourth line",
734             "// Fifth line"), StandardCharsets.UTF_8);
735 
736         final DefaultConfiguration checkConfig =
737                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
738         checkConfig.addProperty("headerFiles", headerFile.getPath());
739 
740         verify(checkConfig, testFile.getPath(), EMPTY_STRING_ARRAY);
741     }
742 
743     @Test
744     public void testFileSizeEqualHeaderPatternSize() throws Exception {
745         final File headerFile = new File(temporaryFolder, "shorterHeader.header");
746         Files.write(headerFile.toPath(), List.of(
747                 "// First line",
748                 "// Second line",
749                 "// Third line"), StandardCharsets.UTF_8);
750 
751         final File testFile = new File(temporaryFolder, "longerFile.java");
752         Files.write(testFile.toPath(), List.of(
753                 "// First line",
754                 "// Second line",
755                 "// Third line"), StandardCharsets.UTF_8);
756 
757         final DefaultConfiguration checkConfig =
758                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
759         checkConfig.addProperty("headerFiles", headerFile.getPath());
760 
761         verify(checkConfig, testFile.getPath(), EMPTY_STRING_ARRAY);
762     }
763 
764     @Test
765     public void testSpecificLineMismatch() throws Exception {
766         final File headerFile = new File(temporaryFolder, "multiline.header");
767         Files.write(headerFile.toPath(), List.of(
768             "// First line",
769             "// Second line",
770             "// Third line with pattern .*",
771             "// Fourth line",
772             "// Fifth line"), StandardCharsets.UTF_8);
773 
774         final File testFile = new File(temporaryFolder, "mismatchThirdLine.java");
775         Files.write(testFile.toPath(), List.of(
776             "// First line",
777             "// Second line",
778             "This line doesn't match the pattern",
779             "// Fourth line",
780             "// Fifth line"), StandardCharsets.UTF_8);
781 
782         final DefaultConfiguration checkConfig =
783                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
784         checkConfig.addProperty("headerFiles", headerFile.getPath());
785 
786         final MultiFileRegexpHeaderCheck checkInstance = new MultiFileRegexpHeaderCheck();
787         checkInstance.configure(checkConfig);
788         final String allPaths = checkInstance.getConfiguredHeaderPaths();
789 
790         final String[] expected = {
791             "3: " + getCheckMessage(MSG_HEADER_MISMATCH, "// Third line with pattern .*", allPaths),
792         };
793         verify(checkConfig, testFile.getPath(), expected);
794     }
795 
796     @Test
797     public void testMultipleHeaderFilesFirstMatchesSecondDoesnt() throws Exception {
798         final File matchingHeaderFile = new File(temporaryFolder, "matching.header");
799         Files.write(matchingHeaderFile.toPath(), List.of(
800             "// This header matches",
801             "// The file content"), StandardCharsets.UTF_8);
802 
803         final File nonMatchingHeaderFile = new File(temporaryFolder, "nonMatching.header");
804         Files.write(nonMatchingHeaderFile.toPath(), List.of(
805             "// This header doesn't match",
806             "// Different content"), StandardCharsets.UTF_8);
807 
808         final File testFile = new File(temporaryFolder, "TestFile.java");
809         Files.write(testFile.toPath(), List.of(
810             "// This header matches",
811             "// The file content"), StandardCharsets.UTF_8);
812 
813         final DefaultConfiguration checkConfig =
814                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
815         checkConfig.addProperty("headerFiles",
816             matchingHeaderFile.getPath() + "," + nonMatchingHeaderFile.getPath());
817         verify(checkConfig, testFile.getPath(), EMPTY_STRING_ARRAY);
818     }
819 
820     @Test
821     public void testMultipleHeaderFilesNoneMatch() throws Exception {
822         final File matchingHeader = new File(temporaryFolder, "nonMatching1.header");
823         Files.write(matchingHeader.toPath(), List.of(
824             "// First matching header",
825             "// Second matching header"), StandardCharsets.UTF_8);
826 
827         final File nonMatchingHeader = new File(temporaryFolder, "nonMatching2.header");
828         Files.write(nonMatchingHeader.toPath(), List.of(
829             "// First matching header",
830             "// Second no-matching header"), StandardCharsets.UTF_8);
831 
832         final File testFile = new File(temporaryFolder, "TestFile.java");
833         Files.write(testFile.toPath(), List.of(
834             "// First matching header",
835             "// Second matching header"), StandardCharsets.UTF_8);
836 
837         final DefaultConfiguration checkConfig =
838                 createModuleConfig(MultiFileRegexpHeaderCheck.class);
839         checkConfig.addProperty("headerFiles",
840             matchingHeader.getPath() + "," + nonMatchingHeader.getPath());
841 
842         verify(checkConfig, testFile.getPath(), EMPTY_STRING_ARRAY);
843     }
844 
845     @Test
846     public void testSetHeaderFilesWithNullPathThrowsException() {
847         final MultiFileRegexpHeaderCheck check = new MultiFileRegexpHeaderCheck();
848 
849         final IllegalArgumentException thrown =
850                 getExpectedThrowable(IllegalArgumentException.class, () -> {
851                     check.setHeaderFiles((String) null);
852                 });
853         assertWithMessage("Exception message mismatch for null header path")
854                 .that(thrown.getMessage()).isEqualTo("Header file is not set");
855     }
856 
857     @Test
858     public void testSetHeaderFilesWithNullVarargsArray() {
859         final MultiFileRegexpHeaderCheck check = new MultiFileRegexpHeaderCheck();
860 
861         assertDoesNotThrow(() -> {
862             check.setHeaderFiles((String[]) null);
863         });
864 
865         assertWithMessage(
866             "External locations should be empty when setHeaderFiles is called with null array")
867             .that(check.getExternalResourceLocations())
868             .isEmpty();
869     }
870 
871     @Test
872     public void testSetHeaderFilesClearsPreviousConfiguration() throws IOException {
873         final MultiFileRegexpHeaderCheck check = new MultiFileRegexpHeaderCheck();
874 
875         final File headerFileA = new File(temporaryFolder, "testHeaderA_clear_check.header");
876         Files.write(headerFileA.toPath(), List.of("// Header A for clear check"),
877                 StandardCharsets.UTF_8);
878         final String pathA = headerFileA.getAbsolutePath();
879         final URI uriA = headerFileA.toURI();
880 
881         check.setHeaderFiles(pathA);
882         assertWithMessage("After first set, only header A should be present")
883                 .that(check.getExternalResourceLocations())
884                 .containsExactly(uriA.toASCIIString());
885 
886         final File headerFileB = new File(temporaryFolder, "testHeaderB_clear_check.header");
887         Files.write(headerFileB.toPath(), List.of("// Header B for clear check"),
888                 StandardCharsets.UTF_8);
889         final String pathB = headerFileB.getAbsolutePath();
890         final URI uriB = headerFileB.toURI();
891 
892         check.setHeaderFiles(pathB);
893         assertWithMessage("After second set, only header B should be present")
894                 .that(check.getExternalResourceLocations())
895                 .containsExactly(uriB.toASCIIString());
896     }
897 
898     @Test
899     public void testHeaderFileMetadataMethods() throws Exception {
900         final File headerFile = new File(temporaryFolder, "header.header");
901         Files.write(headerFile.toPath(), List.of(
902             "// First line",
903             "",
904             "// Third line"), StandardCharsets.UTF_8);
905 
906         final MultiFileRegexpHeaderCheck check = new MultiFileRegexpHeaderCheck();
907         check.setHeaderFiles(headerFile.getPath());
908 
909         final Set<String> locations = check.getExternalResourceLocations();
910         assertWithMessage("Should have one external resource location")
911             .that(locations.size())
912             .isEqualTo(1);
913         assertWithMessage("Location should include header.header")
914             .that(locations.stream()
915                 .anyMatch(loc -> loc.contains("header.header")))
916             .isTrue();
917     }
918 
919     @Test
920     public void testIoExceptionInGetLines() {
921         final File nonExistentFile = new File(temporaryFolder, "nonexistent.header");
922         final URI fileUri = nonExistentFile.toURI();
923 
924         try {
925             MultiFileRegexpHeaderCheck.getLines("nonexistent.header", fileUri);
926             assertWithMessage(
927                     "Expected IllegalArgumentException when reading from a non-existent file")
928                 .fail();
929         }
930         catch (IllegalArgumentException exc) {
931             assertWithMessage("Unexpected exception message")
932                     .that(exc.getMessage())
933                     .contains("unable to load header file nonexistent.header");
934         }
935     }
936 
937     @Test
938     public void testConfiguredHeaderPathsNoFiles() {
939         final MultiFileRegexpHeaderCheck check = new MultiFileRegexpHeaderCheck();
940         assertWithMessage("Expected empty string when no header files are configured")
941             .that(check.getConfiguredHeaderPaths())
942             .isEmpty();
943     }
944 
945     @Test
946     public void testConfiguredHeaderPathsSingleFile() throws IOException {
947         final MultiFileRegexpHeaderCheck check = new MultiFileRegexpHeaderCheck();
948         final File headerFile = new File(temporaryFolder, "singleTest.header");
949         Files.writeString(headerFile.toPath(), "// Single test header", StandardCharsets.UTF_8);
950         final String path = headerFile.getAbsolutePath();
951 
952         check.setHeaderFiles(path);
953 
954         assertWithMessage("Expected path of the single configured file")
955             .that(check.getConfiguredHeaderPaths())
956             .isEqualTo(path);
957     }
958 
959     @Test
960     public void testConfiguredHeaderPathsMultipleFiles() throws IOException {
961         final MultiFileRegexpHeaderCheck check = new MultiFileRegexpHeaderCheck();
962         final File headerFile1 = new File(temporaryFolder, "multiTest1.header");
963         Files.writeString(headerFile1.toPath(), "// Multi test header 1", StandardCharsets.UTF_8);
964         final String path1 = headerFile1.getAbsolutePath();
965 
966         final File headerFile2 = new File(temporaryFolder, "multiTest2.header");
967         Files.writeString(headerFile2.toPath(), "// Multi test header 2", StandardCharsets.UTF_8);
968         final String path2 = headerFile2.getAbsolutePath();
969 
970         check.setHeaderFiles(path1, path2);
971 
972         final String expectedPaths = path1 + ", " + path2;
973         assertWithMessage("Expected comma-separated paths of multiple configured files")
974             .that(check.getConfiguredHeaderPaths())
975             .isEqualTo(expectedPaths);
976 
977         check.setHeaderFiles(path1, path2);
978         assertWithMessage("Expected comma-separated paths when set with distinct args")
979             .that(check.getConfiguredHeaderPaths())
980             .isEqualTo(expectedPaths);
981     }
982 
983 }