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.utils;
21  
22  import static com.google.common.truth.Truth.assertThat;
23  import static com.google.common.truth.Truth.assertWithMessage;
24  import static com.puppycrawl.tools.checkstyle.internal.utils.TestUtil.getExpectedThrowable;
25  import static com.puppycrawl.tools.checkstyle.internal.utils.TestUtil.isUtilsClassHasPrivateConstructor;
26  import static org.mockito.Mockito.CALLS_REAL_METHODS;
27  import static org.mockito.Mockito.mock;
28  import static org.mockito.Mockito.mockStatic;
29  import static org.mockito.Mockito.when;
30  
31  import java.io.Closeable;
32  import java.io.File;
33  import java.io.IOException;
34  import java.lang.reflect.Constructor;
35  import java.net.URI;
36  import java.net.URISyntaxException;
37  import java.net.URL;
38  import java.nio.charset.StandardCharsets;
39  import java.util.Dictionary;
40  import java.util.Properties;
41  import java.util.regex.Pattern;
42  
43  import org.apache.commons.io.IOUtils;
44  import org.junit.jupiter.api.Test;
45  import org.mockito.MockedStatic;
46  
47  import com.puppycrawl.tools.checkstyle.AbstractPathTestSupport;
48  import com.puppycrawl.tools.checkstyle.ConfigurationLoader;
49  import com.puppycrawl.tools.checkstyle.PropertiesExpander;
50  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
51  import com.puppycrawl.tools.checkstyle.api.Configuration;
52  
53  public class CommonUtilTest extends AbstractPathTestSupport {
54  
55      /** After appending to path produces equivalent, but denormalized path. */
56      private static final String PATH_DENORMALIZER = "/levelDown/.././";
57  
58      @Override
59      public String getPackageLocation() {
60          return "com/puppycrawl/tools/checkstyle/utils/commonutil";
61      }
62  
63      @Test
64      public void testIsProperUtilsClass() throws ReflectiveOperationException {
65          assertWithMessage("Constructor is not private")
66                  .that(isUtilsClassHasPrivateConstructor(CommonUtil.class))
67                  .isTrue();
68      }
69  
70      /**
71       * Test CommonUtil.countCharInString.
72       */
73      @Test
74      public void testLengthExpandedTabs() {
75          final String s1 = "\t";
76          assertWithMessage("Invalid expanded tabs length")
77              .that(CommonUtil.lengthExpandedTabs(s1, s1.length(), 8))
78              .isEqualTo(8);
79  
80          final String s2 = "  \t";
81          assertWithMessage("Invalid expanded tabs length")
82              .that(CommonUtil.lengthExpandedTabs(s2, s2.length(), 8))
83              .isEqualTo(8);
84  
85          final String s3 = "\t\t";
86          assertWithMessage("Invalid expanded tabs length")
87              .that(CommonUtil.lengthExpandedTabs(s3, s3.length(), 8))
88              .isEqualTo(16);
89  
90          final String s4 = " \t ";
91          assertWithMessage("Invalid expanded tabs length")
92              .that(CommonUtil.lengthExpandedTabs(s4, s4.length(), 8))
93              .isEqualTo(9);
94  
95          assertWithMessage("Invalid expanded tabs length")
96              .that(CommonUtil.lengthMinusTrailingWhitespace(""))
97              .isEqualTo(0);
98          assertWithMessage("Invalid expanded tabs length")
99              .that(CommonUtil.lengthMinusTrailingWhitespace(" \t "))
100             .isEqualTo(0);
101         assertWithMessage("Invalid expanded tabs length")
102             .that(CommonUtil.lengthMinusTrailingWhitespace(" 23"))
103             .isEqualTo(3);
104         assertWithMessage("Invalid expanded tabs length")
105             .that(CommonUtil.lengthMinusTrailingWhitespace(" 23 \t "))
106             .isEqualTo(3);
107     }
108 
109     @Test
110     public void testCreatePattern() {
111         assertWithMessage("invalid pattern")
112             .that(CommonUtil.createPattern("Test").pattern())
113             .isEqualTo("Test");
114         assertWithMessage("invalid pattern")
115             .that(CommonUtil.createPattern(".*Pattern.*")
116                 .pattern())
117             .isEqualTo(".*Pattern.*");
118     }
119 
120     @Test
121     public void testBadRegex() {
122         final IllegalArgumentException ex =
123                 getExpectedThrowable(IllegalArgumentException.class, () -> {
124                     CommonUtil.createPattern("[");
125                 });
126         assertWithMessage("Invalid exception message")
127                 .that(ex)
128                 .hasMessageThat()
129                         .isEqualTo("Failed to initialise regular expression [");
130     }
131 
132     @Test
133     public void testBadRegex2() {
134         final IllegalArgumentException ex =
135                 getExpectedThrowable(IllegalArgumentException.class, () -> {
136                     CommonUtil.createPattern("[", Pattern.MULTILINE);
137                 });
138         assertWithMessage("Invalid exception message")
139                 .that(ex)
140                 .hasMessageThat()
141                         .isEqualTo("Failed to initialise regular expression [");
142     }
143 
144     @Test
145     public void testFileExtensions() {
146         final String[] fileExtensions = {"java"};
147         final File pdfFile = new File("file.pdf");
148         assertWithMessage("Invalid file extension")
149                 .that(CommonUtil.matchesFileExtension(pdfFile, fileExtensions))
150                 .isFalse();
151         assertWithMessage("Invalid file extension")
152                 .that(CommonUtil.matchesFileExtension(pdfFile))
153                 .isTrue();
154         assertWithMessage("Invalid file extension")
155                 .that(CommonUtil.matchesFileExtension(pdfFile, (String[]) null))
156                 .isTrue();
157         final File javaFile = new File("file.java");
158         assertWithMessage("Invalid file extension")
159                 .that(CommonUtil.matchesFileExtension(javaFile, fileExtensions))
160                 .isTrue();
161         final File invalidJavaFile = new File("file,java");
162         assertWithMessage("Invalid file extension")
163                 .that(CommonUtil.matchesFileExtension(invalidJavaFile, fileExtensions))
164                 .isFalse();
165         final File emptyExtensionFile = new File("file.");
166         assertWithMessage("Invalid file extension")
167                 .that(CommonUtil.matchesFileExtension(emptyExtensionFile, ""))
168                 .isTrue();
169         assertWithMessage("Invalid file extension")
170                 .that(CommonUtil.matchesFileExtension(pdfFile, ".noMatch"))
171                 .isFalse();
172         assertWithMessage("Invalid file extension")
173                 .that(CommonUtil.matchesFileExtension(pdfFile, ".pdf"))
174                 .isTrue();
175     }
176 
177     @Test
178     public void testHasWhitespaceBefore() {
179         assertWithMessage("Invalid result")
180                 .that(CommonUtil.hasWhitespaceBefore(0, "a"))
181                 .isTrue();
182         assertWithMessage("Invalid result")
183                 .that(CommonUtil.hasWhitespaceBefore(4, "    a"))
184                 .isTrue();
185         assertWithMessage("Invalid result")
186                 .that(CommonUtil.hasWhitespaceBefore(5, "    a"))
187                 .isFalse();
188     }
189 
190     @Test
191     public void testBaseClassNameForCanonicalName() {
192         assertWithMessage("Invalid base class name")
193             .that(CommonUtil.baseClassName("java.util.List"))
194             .isEqualTo("List");
195     }
196 
197     @Test
198     public void testBaseClassNameForSimpleName() {
199         assertWithMessage("Invalid base class name")
200             .that(CommonUtil.baseClassName("Set"))
201             .isEqualTo("Set");
202     }
203 
204     @Test
205     public void testRelativeNormalizedPath() {
206         final String relativePath = CommonUtil.relativizePath("/home", "/home/test");
207 
208         assertWithMessage("Invalid relative path")
209             .that(relativePath)
210             .isEqualTo("test");
211     }
212 
213     @Test
214     public void testRelativeNormalizedPathWithNullBaseDirectory() {
215         final String relativePath = CommonUtil.relativizePath(null, "/tmp");
216 
217         assertWithMessage("Invalid relative path")
218             .that(relativePath)
219             .isEqualTo("/tmp");
220     }
221 
222     @Test
223     public void testRelativeNormalizedPathWithDenormalizedBaseDirectory() throws IOException {
224         final String sampleAbsolutePath = new File("src/main/java").getCanonicalPath();
225         final String absoluteFilePath = sampleAbsolutePath + "/SampleFile.java";
226         final String basePath = sampleAbsolutePath + PATH_DENORMALIZER;
227 
228         final String relativePath = CommonUtil.relativizePath(basePath,
229             absoluteFilePath);
230 
231         assertWithMessage("Invalid relative path")
232             .that(relativePath)
233             .isEqualTo("SampleFile.java");
234     }
235 
236     @Test
237     public void testPattern() {
238         final boolean result = CommonUtil.isPatternValid("someValidPattern");
239         assertWithMessage("Should return true when pattern is valid")
240                 .that(result)
241                 .isTrue();
242     }
243 
244     @Test
245     public void testInvalidPattern() {
246         final boolean result = CommonUtil.isPatternValid("some[invalidPattern");
247         assertWithMessage("Should return false when pattern is invalid")
248                 .that(result)
249                 .isFalse();
250     }
251 
252     @Test
253     public void testGetExistingConstructor() throws NoSuchMethodException {
254         final Constructor<?> constructor = CommonUtil.getConstructor(String.class, String.class);
255 
256         assertWithMessage("Invalid constructor")
257             .that(constructor)
258             .isEqualTo(String.class.getConstructor(String.class));
259     }
260 
261     @Test
262     public void testGetNonExistentConstructor() {
263         final IllegalStateException ex = getExpectedThrowable(IllegalStateException.class, () -> {
264             CommonUtil.getConstructor(Math.class);
265         });
266         assertWithMessage("Invalid exception cause")
267                 .that(ex)
268                 .hasCauseThat()
269                         .isInstanceOf(NoSuchMethodException.class);
270     }
271 
272     @Test
273     public void testInvokeConstructor() throws NoSuchMethodException {
274         final Constructor<String> constructor = String.class.getConstructor(String.class);
275 
276         final String constructedString = CommonUtil.invokeConstructor(constructor, "string");
277 
278         assertWithMessage("Invalid construction result")
279             .that(constructedString)
280             .isEqualTo("string");
281     }
282 
283     @SuppressWarnings("rawtypes")
284     @Test
285     public void testInvokeConstructorThatFails() throws NoSuchMethodException {
286         final Constructor<Dictionary> constructor = Dictionary.class.getConstructor();
287         final IllegalStateException ex = getExpectedThrowable(IllegalStateException.class, () -> {
288             CommonUtil.invokeConstructor(constructor);
289         });
290         assertWithMessage("Invalid exception cause")
291                 .that(ex)
292                 .hasCauseThat()
293                         .isInstanceOf(InstantiationException.class);
294     }
295 
296     @Test
297     public void testClose() {
298         final TestCloseable closeable = new TestCloseable();
299 
300         CommonUtil.close(null);
301         CommonUtil.close(closeable);
302 
303         assertWithMessage("Should be closed")
304                 .that(closeable.closed)
305                 .isTrue();
306     }
307 
308     @Test
309     public void testCloseWithException() {
310         final IllegalStateException ex = getExpectedThrowable(IllegalStateException.class, () -> {
311             CommonUtil.close(() -> {
312                 throw new IOException("Test IOException");
313             });
314         });
315         assertWithMessage("Invalid exception message")
316                 .that(ex)
317                 .hasMessageThat()
318                         .isEqualTo("Cannot close the stream");
319     }
320 
321     @Test
322     public void testFillTemplateWithStringsByRegexp() {
323         assertWithMessage("invalid result")
324             .that(CommonUtil.fillTemplateWithStringsByRegexp("template",
325                 "lineToPlaceInTemplate", Pattern.compile("NO MATCH")))
326             .isEqualTo("template");
327         assertWithMessage("invalid result")
328             .that(CommonUtil.fillTemplateWithStringsByRegexp("before $0 after", "word",
329                         Pattern.compile("\\w+")))
330             .isEqualTo("before word after");
331         assertWithMessage("invalid result")
332             .that(CommonUtil.fillTemplateWithStringsByRegexp("before $0 after1 $1 after2 $2 after3",
333                         "word 123", Pattern.compile("(\\w+) (\\d+)")))
334             .isEqualTo("before word 123 after1 word after2 123 after3");
335         assertWithMessage("dollar sign in match must be treated literally")
336             .that(CommonUtil.fillTemplateWithStringsByRegexp("before $0 after",
337                         "a$b", Pattern.compile(".+")))
338             .isEqualTo("before a$b after");
339         assertWithMessage("backslash in match must be treated literally")
340             .that(CommonUtil.fillTemplateWithStringsByRegexp("before $0 after",
341                         "a\\b", Pattern.compile(".+")))
342             .isEqualTo("before a\\b after");
343         assertWithMessage("non-matching optional group must be left untouched")
344             .that(CommonUtil.fillTemplateWithStringsByRegexp("x $1 y $2 z",
345                         "a", Pattern.compile("(a)(b)?")))
346             .isEqualTo("x a y $2 z");
347     }
348 
349     @Test
350     public void testGetFileNameWithoutExtension() {
351         assertWithMessage("invalid result")
352             .that(CommonUtil.getFileNameWithoutExtension("filename"))
353             .isEqualTo("filename");
354         assertWithMessage("invalid result")
355             .that(CommonUtil.getFileNameWithoutExtension("filename.extension"))
356             .isEqualTo("filename");
357         assertWithMessage("invalid result")
358             .that(CommonUtil.getFileNameWithoutExtension("filename.subext.extension"))
359             .isEqualTo("filename.subext");
360     }
361 
362     @Test
363     public void testGetFileExtension() {
364         assertWithMessage("Invalid extension")
365             .that(CommonUtil.getFileExtension("filename"))
366             .isEqualTo("");
367         assertWithMessage("Invalid extension")
368             .that(CommonUtil.getFileExtension("filename.extension"))
369             .isEqualTo("extension");
370         assertWithMessage("Invalid extension")
371             .that(CommonUtil.getFileExtension("filename.subext.extension"))
372             .isEqualTo("extension");
373     }
374 
375     @Test
376     public void testIsIdentifier() {
377         assertWithMessage("Should return true when valid identifier is passed")
378                 .that(CommonUtil.isIdentifier("aValidIdentifier"))
379                 .isTrue();
380     }
381 
382     @Test
383     public void testIsIdentifierEmptyString() {
384         assertWithMessage("Should return false when empty string is passed")
385                 .that(CommonUtil.isIdentifier(""))
386                 .isFalse();
387     }
388 
389     @Test
390     public void testIsIdentifierInvalidFirstSymbol() {
391         assertWithMessage("Should return false when invalid identifier is passed")
392                 .that(CommonUtil.isIdentifier("1InvalidIdentifier"))
393                 .isFalse();
394     }
395 
396     @Test
397     public void testIsIdentifierInvalidSymbols() {
398         assertWithMessage("Should return false when invalid identifier is passed")
399                 .that(CommonUtil.isIdentifier("invalid#Identifier"))
400                 .isFalse();
401     }
402 
403     @Test
404     public void testIsName() {
405         assertWithMessage("Should return true when valid name is passed")
406                 .that(CommonUtil.isName("a.valid.Nam3"))
407                 .isTrue();
408     }
409 
410     @Test
411     public void testIsNameEmptyString() {
412         assertWithMessage("Should return false when empty string is passed")
413                 .that(CommonUtil.isName(""))
414                 .isFalse();
415     }
416 
417     @Test
418     public void testIsNameInvalidFirstSymbol() {
419         assertWithMessage("Should return false when invalid name is passed")
420                 .that(CommonUtil.isName("1.invalid.name"))
421                 .isFalse();
422     }
423 
424     @Test
425     public void testIsNameEmptyPart() {
426         assertWithMessage("Should return false when name has empty part")
427                 .that(CommonUtil.isName("invalid..name"))
428                 .isFalse();
429     }
430 
431     @Test
432     public void testIsNameEmptyLastPart() {
433         assertWithMessage("Should return false when name has empty part")
434                 .that(CommonUtil.isName("invalid.name."))
435                 .isFalse();
436     }
437 
438     @Test
439     public void testIsNameInvalidSymbol() {
440         assertWithMessage("Should return false when invalid name is passed")
441                 .that(CommonUtil.isName("invalid.name#42"))
442                 .isFalse();
443     }
444 
445     @Test
446     public void testIsBlank() {
447         assertWithMessage("Should return false when string is not empty")
448                 .that(CommonUtil.isBlank("string"))
449                 .isFalse();
450     }
451 
452     @Test
453     public void testIsBlankAheadWhitespace() {
454         assertWithMessage("Should return false when string is not empty")
455                 .that(CommonUtil.isBlank("  string"))
456                 .isFalse();
457     }
458 
459     @Test
460     public void testIsBlankBehindWhitespace() {
461         assertWithMessage("Should return false when string is not empty")
462                 .that(CommonUtil.isBlank("string    "))
463                 .isFalse();
464     }
465 
466     @Test
467     public void testIsBlankWithWhitespacesAround() {
468         assertWithMessage("Should return false when string is not empty")
469                 .that(CommonUtil.isBlank("    string    "))
470                 .isFalse();
471     }
472 
473     @Test
474     public void testIsBlankWhitespaceInside() {
475         assertWithMessage("Should return false when string is not empty")
476                 .that(CommonUtil.isBlank("str    ing"))
477                 .isFalse();
478     }
479 
480     @Test
481     public void testIsBlankNullString() {
482         assertWithMessage("Should return true when string is null")
483                 .that(CommonUtil.isBlank(null))
484                 .isTrue();
485     }
486 
487     @Test
488     public void testIsBlankWithEmptyString() {
489         assertWithMessage("Should return true when string is empty")
490                 .that(CommonUtil.isBlank(""))
491                 .isTrue();
492     }
493 
494     @Test
495     public void testIsBlankWithWhitespacesOnly() {
496         assertWithMessage("Should return true when string contains only spaces")
497                 .that(CommonUtil.isBlank("   "))
498                 .isTrue();
499     }
500 
501     @Test
502     public void testGetUriByFilenameFindsAbsoluteResourceOnClasspath() throws Exception {
503         final String filename =
504             "/" + getPackageLocation() + "/InputCommonUtilTest_empty_checks.xml";
505         final URI uri = CommonUtil.getUriByFilename(filename);
506 
507         final Properties properties = System.getProperties();
508         final Configuration config = ConfigurationLoader.loadConfiguration(uri.toASCIIString(),
509             new PropertiesExpander(properties));
510         assertWithMessage("Unexpected config name!")
511             .that(config.getName())
512             .isEqualTo("Checker");
513     }
514 
515     @Test
516     public void testGetUriByFilenameFindsRelativeResourceOnClasspath() throws Exception {
517         final String filename =
518             getPackageLocation() + "/InputCommonUtilTest_empty_checks.xml";
519         final URI uri = CommonUtil.getUriByFilename(filename);
520 
521         final Properties properties = System.getProperties();
522         final Configuration config = ConfigurationLoader.loadConfiguration(uri.toASCIIString(),
523             new PropertiesExpander(properties));
524         assertWithMessage("Unexpected config name!")
525             .that(config.getName())
526             .isEqualTo("Checker");
527     }
528 
529     /**
530      * This test illustrates #6232.
531      * Without fix, the assertion will fail because the URL under test
532      * "com/puppycrawl/tools/checkstyle/utils/commonutil/InputCommonUtilTest_resource.txt"
533      * will be interpreted relative to the current package
534      * "com/puppycrawl/tools/checkstyle/utils/"
535      */
536     @Test
537     public void testGetUriByFilenameFindsResourceRelativeToRootClasspath() throws Exception {
538         final String filename =
539                 getPackageLocation() + "/InputCommonUtilTest_resource.txt";
540         final URI uri = CommonUtil.getUriByFilename(filename);
541         assertWithMessage("URI is null for: %s", filename)
542             .that(uri)
543             .isNotNull();
544         final String uriRelativeToPackage =
545                 "com/puppycrawl/tools/checkstyle/utils/"
546                         + getPackageLocation() + "/InputCommonUtilTest_resource.txt";
547         assertWithMessage("URI is relative to package %s", uriRelativeToPackage)
548             .that(uri.toASCIIString())
549             .doesNotContain(uriRelativeToPackage);
550         final String content = IOUtils.toString(uri.toURL(), StandardCharsets.UTF_8);
551         assertWithMessage("Content mismatches for: %s", uri.toASCIIString())
552             .that(content)
553             .startsWith("good");
554     }
555 
556     @Test
557     public void testGetUriByFilenameClasspathPrefixLoadConfig() throws Exception {
558         final String filename = CommonUtil.CLASSPATH_URL_PROTOCOL
559             + getPackageLocation() + "/InputCommonUtilTestWithChecks.xml";
560         final URI uri = CommonUtil.getUriByFilename(filename);
561 
562         final Properties properties = System.getProperties();
563         final Configuration config = ConfigurationLoader.loadConfiguration(uri.toASCIIString(),
564             new PropertiesExpander(properties));
565         assertWithMessage("Unexpected config name!")
566             .that(config.getName())
567             .isEqualTo("Checker");
568     }
569 
570     @Test
571     public void testGetUriByFilenameFindsRelativeResourceOnClasspathPrefix() throws Exception {
572         final String filename = CommonUtil.CLASSPATH_URL_PROTOCOL
573             + getPackageLocation() + "/InputCommonUtilTest_empty_checks.xml";
574         final URI uri = CommonUtil.getUriByFilename(filename);
575 
576         final Properties properties = System.getProperties();
577         final Configuration config = ConfigurationLoader.loadConfiguration(uri.toASCIIString(),
578             new PropertiesExpander(properties));
579         assertWithMessage("Unexpected config name!")
580             .that(config.getName())
581             .isEqualTo("Checker");
582     }
583 
584     @Test
585     public void testIsCodePointWhitespace() {
586         final int[] codePoints = " 123".codePoints().toArray();
587         assertThat(CommonUtil.isCodePointWhitespace(codePoints, 0))
588                 .isTrue();
589         assertThat(CommonUtil.isCodePointWhitespace(codePoints, 1))
590                 .isFalse();
591     }
592 
593     @Test
594     public void testLoadSuppressionsUriSyntaxException() throws Exception {
595         final URL configUrl = mock();
596         when(configUrl.toURI()).thenThrow(URISyntaxException.class);
597         try (MockedStatic<CommonUtil> utilities =
598                      mockStatic(CommonUtil.class, CALLS_REAL_METHODS)) {
599             final String fileName = "/suppressions_none.xml";
600             utilities.when(() -> CommonUtil.getCheckstyleResource(fileName))
601                     .thenReturn(configUrl);
602 
603             final CheckstyleException ex = getExpectedThrowable(CheckstyleException.class, () -> {
604                 CommonUtil.getUriByFilename(fileName);
605             });
606             assertWithMessage("Invalid exception cause")
607                     .that(ex)
608                     .hasCauseThat()
609                             .isInstanceOf(URISyntaxException.class);
610             assertWithMessage("Invalid exception message")
611                     .that(ex)
612                     .hasMessageThat()
613                             .isEqualTo("Unable to find: " + fileName);
614         }
615     }
616 
617     private static final class TestCloseable implements Closeable {
618 
619         private boolean closed;
620 
621         @Override
622         public void close() {
623             closed = true;
624         }
625 
626     }
627 
628 }