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.imports;
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.checks.imports.ImportControlCheck.MSG_DISALLOWED;
25  import static com.puppycrawl.tools.checkstyle.checks.imports.ImportControlCheck.MSG_MISSING_FILE;
26  import static com.puppycrawl.tools.checkstyle.checks.imports.ImportControlCheck.MSG_UNKNOWN_PKG;
27  import static com.puppycrawl.tools.checkstyle.internal.utils.TestUtil.getExpectedThrowable;
28  
29  import java.io.File;
30  import java.net.URI;
31  import java.nio.file.Files;
32  import java.util.Arrays;
33  import java.util.List;
34  import java.util.UUID;
35  
36  import org.junit.jupiter.api.Test;
37  import org.junit.jupiter.api.io.TempDir;
38  
39  import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
40  import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
41  import com.puppycrawl.tools.checkstyle.TreeWalker;
42  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
43  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
44  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
45  
46  public class ImportControlCheckTest extends AbstractModuleTestSupport {
47  
48      @TempDir
49      public File temporaryFolder;
50  
51      @Override
52      public String getPackageLocation() {
53          return "com/puppycrawl/tools/checkstyle/checks/imports/importcontrol";
54      }
55  
56      @Test
57      public void testGetRequiredTokens() {
58          final ImportControlCheck checkObj = new ImportControlCheck();
59          final int[] expected = {
60              TokenTypes.PACKAGE_DEF,
61              TokenTypes.IMPORT,
62              TokenTypes.STATIC_IMPORT,
63              TokenTypes.MODULE_IMPORT,
64          };
65          assertWithMessage("Default required tokens are invalid")
66              .that(checkObj.getRequiredTokens())
67              .isEqualTo(expected);
68      }
69  
70      @Test
71      public void testOne() throws Exception {
72          final String[] expected = {"13:1: " + getCheckMessage(MSG_DISALLOWED, "java.io.File")};
73  
74          verifyWithInlineConfigParser(
75                  getPath("InputImportControl.java"), expected);
76      }
77  
78      @Test
79      public void testTwo() throws Exception {
80          final String[] expected = {
81              "11:1: " + getCheckMessage(MSG_DISALLOWED, "java.awt.Image"),
82              "12:1: " + getCheckMessage(MSG_DISALLOWED, "javax.swing.border.*"),
83              "14:1: " + getCheckMessage(MSG_DISALLOWED, "java.awt.Button.ABORT"),
84          };
85  
86          verifyWithInlineConfigParser(
87                  getPath("InputImportControl2.java"), expected);
88      }
89  
90      @Test
91      public void testWrong() throws Exception {
92          final String[] expected = {"9:1: " + getCheckMessage(MSG_UNKNOWN_PKG)};
93          verifyWithInlineConfigParser(
94                  getPath("InputImportControl3.java"), expected);
95      }
96  
97      @Test
98      public void testMissing() throws Exception {
99          final String[] expected = {"9:1: " + getCheckMessage(MSG_MISSING_FILE)};
100         verifyWithInlineConfigParser(
101                 getPath("InputImportControl4.java"), expected);
102     }
103 
104     @Test
105     public void testEmpty() throws Exception {
106         final String[] expected = {"9:1: " + getCheckMessage(MSG_MISSING_FILE)};
107         verifyWithInlineConfigParser(
108                 getPath("InputImportControl5.java"), expected);
109     }
110 
111     @Test
112     public void testNull() throws Exception {
113         final String[] expected = {"9:1: " + getCheckMessage(MSG_MISSING_FILE)};
114         verifyWithInlineConfigParser(
115                 getPath("InputImportControl6.java"), expected);
116     }
117 
118     @Test
119     public void testUnknown() {
120         final CheckstyleException exc =
121                 getExpectedThrowable(CheckstyleException.class, () -> {
122                     final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
123                     verifyWithInlineConfigParser(
124                             getPath("InputImportControl7.java"), expected);
125                 }, "Test should fail if exception was not thrown");
126         final String message = getCheckstyleExceptionMessage(exc);
127         final String messageStart = "Unable to find: ";
128 
129         assertWithMessage("Invalid message, should start with: %s", messageStart)
130                 .that(message)
131                 .startsWith(messageStart);
132     }
133 
134     @Test
135     public void testBroken() {
136         final CheckstyleException exc =
137                 getExpectedThrowable(CheckstyleException.class, () -> {
138                     final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
139                     verifyWithInlineConfigParser(getPath("InputImportControl8.java"), expected);
140                 }, "Test should fail if exception was not thrown");
141         final String message = getCheckstyleExceptionMessage(exc);
142         final String messageStart = "Unable to load ";
143 
144         assertWithMessage("Invalid message, should start with: %s", messageStart)
145                 .that(message)
146                 .startsWith(messageStart);
147     }
148 
149     @Test
150     public void testSetFileContainsUriInMessage() {
151         final ImportControlCheck check = new ImportControlCheck();
152         final File missingFile = new File(temporaryFolder, "missing-import-control.xml");
153         final URI uri = missingFile.toURI();
154 
155         final IllegalArgumentException exception =
156                 getExpectedThrowable(IllegalArgumentException.class, () -> check.setFile(uri));
157 
158         assertWithMessage("Invalid exception message")
159                 .that(exception.getMessage())
160                 .isEqualTo("Unable to load " + uri);
161         assertWithMessage("Invalid exception cause")
162                 .that(exception.getCause())
163                 .isInstanceOf(CheckstyleException.class);
164         assertWithMessage("Cause message should contain uri")
165                 .that(exception.getCause().getMessage())
166                 .isEqualTo("unable to find " + uri);
167     }
168 
169     @Test
170     public void testOneRegExp() throws Exception {
171         final String[] expected = {"13:1: " + getCheckMessage(MSG_DISALLOWED, "java.io.File")};
172 
173         verifyWithInlineConfigParser(
174                 getPath("InputImportControl9.java"), expected);
175     }
176 
177     @Test
178     public void testTwoRegExp() throws Exception {
179         final String[] expected = {
180             "11:1: " + getCheckMessage(MSG_DISALLOWED, "java.awt.Image"),
181             "12:1: " + getCheckMessage(MSG_DISALLOWED, "javax.swing.border.*"),
182             "14:1: " + getCheckMessage(MSG_DISALLOWED, "java.awt.Button.ABORT"),
183         };
184 
185         verifyWithInlineConfigParser(
186                 getPath("InputImportControl10.java"), expected);
187     }
188 
189     @Test
190     public void testNotRegExpNoMatch() throws Exception {
191 
192         verifyWithInlineConfigParser(
193                 getPath("InputImportControl11.java"), CommonUtil.EMPTY_STRING_ARRAY);
194     }
195 
196     @Test
197     public void testBlacklist() throws Exception {
198         final String[] expected = {
199             "11:1: " + getCheckMessage(MSG_DISALLOWED, "java.util.stream.Stream"),
200             "12:1: " + getCheckMessage(MSG_DISALLOWED, "java.util.Date"),
201             "14:1: " + getCheckMessage(MSG_DISALLOWED, "java.util.stream.Collectors"),
202             "15:1: " + getCheckMessage(MSG_DISALLOWED, "java.util.stream.IntStream"),
203         };
204 
205         verifyWithInlineConfigParser(
206                 getPath("InputImportControl_Blacklist.java"), expected);
207     }
208 
209     @Test
210     public void testStrategyOnMismatchOne() throws Exception {
211         final String[] expected = {
212             "11:1: " + getCheckMessage(MSG_DISALLOWED, "java.awt.Image"),
213             "12:1: " + getCheckMessage(MSG_DISALLOWED, "javax.swing.border.*"),
214             "14:1: " + getCheckMessage(MSG_DISALLOWED, "java.awt.Button.ABORT"),
215         };
216 
217         verifyWithInlineConfigParser(
218                 getPath("InputImportControl12.java"), expected);
219     }
220 
221     @Test
222     public void testStrategyOnMismatchTwo() throws Exception {
223         final String[] expected = {
224             "11:1: " + getCheckMessage(MSG_DISALLOWED, "java.awt.Image"),
225             "14:1: " + getCheckMessage(MSG_DISALLOWED, "java.awt.Button.ABORT"),
226         };
227 
228         verifyWithInlineConfigParser(
229                 getPath("InputImportControl13.java"), expected);
230     }
231 
232     @Test
233     public void testStrategyOnMismatchThree() throws Exception {
234         final String[] expected = {
235             "11:1: " + getCheckMessage(MSG_DISALLOWED, "java.awt.Image"),
236         };
237 
238         verifyWithInlineConfigParser(
239                 getPath("InputImportControl14.java"), expected);
240     }
241 
242     @Test
243     public void testStrategyOnMismatchFour() throws Exception {
244         final String[] expected = {
245             "11:1: " + getCheckMessage(MSG_DISALLOWED, "java.awt.Image"),
246             "12:1: " + getCheckMessage(MSG_DISALLOWED, "javax.swing.border.*"),
247         };
248 
249         verifyWithInlineConfigParser(
250                 getPath("InputImportControl15.java"), expected);
251     }
252 
253     @Test
254     public void testWithoutRegexAndWithStrategyOnMismatch() throws Exception {
255         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
256 
257         verifyWithInlineConfigParser(
258             getPath("InputImportControlWithoutRegexAndWithStrategyOnMismatch.java"),
259             expected);
260     }
261 
262     @Test
263     public void testPkgRegExpInParent() throws Exception {
264         final String[] expected = {"13:1: " + getCheckMessage(MSG_DISALLOWED, "java.io.File")};
265 
266         verifyWithInlineConfigParser(
267                 getPath("InputImportControl16.java"), expected);
268     }
269 
270     @Test
271     public void testPkgRegExpInChild() throws Exception {
272         final String[] expected = {"13:1: " + getCheckMessage(MSG_DISALLOWED, "java.io.File")};
273 
274         verifyWithInlineConfigParser(
275                 getPath("InputImportControl162.java"), expected);
276     }
277 
278     @Test
279     public void testPkgRegExpInBoth() throws Exception {
280         final String[] expected = {"13:1: " + getCheckMessage(MSG_DISALLOWED, "java.io.File")};
281 
282         verifyWithInlineConfigParser(
283                 getPath("InputImportControl163.java"), expected);
284     }
285 
286     @Test
287     public void testGetAcceptableTokens() {
288         final ImportControlCheck testCheckObject =
289                 new ImportControlCheck();
290         final int[] actual = testCheckObject.getAcceptableTokens();
291         final int[] expected = {
292             TokenTypes.PACKAGE_DEF,
293             TokenTypes.IMPORT,
294             TokenTypes.STATIC_IMPORT,
295             TokenTypes.MODULE_IMPORT,
296         };
297 
298         assertWithMessage("Default acceptable tokens are invalid")
299             .that(actual)
300             .isEqualTo(expected);
301     }
302 
303     @Test
304     public void testResource() throws Exception {
305         final String[] expected = {"13:1: " + getCheckMessage(MSG_DISALLOWED, "java.io.File")};
306 
307         verifyWithInlineConfigParser(
308                 getPath("InputImportControl17.java"), expected);
309     }
310 
311     @Test
312     public void testResourceUnableToLoad() {
313         final CheckstyleException exc =
314                 getExpectedThrowable(CheckstyleException.class, () -> {
315                     final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
316                     verifyWithInlineConfigParser(getPath("InputImportControl18.java"), expected);
317                 }, "Test should fail if exception was not thrown");
318         final String message = getCheckstyleExceptionMessage(exc);
319         final String messageStart = "Unable to find: ";
320 
321         assertWithMessage("Invalid message, should start with: %s", messageStart)
322                 .that(message)
323                 .startsWith(messageStart);
324     }
325 
326     @Test
327     public void testUrlInFileProperty() throws Exception {
328         final String[] expected = {"13:1: " + getCheckMessage(MSG_DISALLOWED, "java.io.File")};
329 
330         verifyWithInlineConfigParser(
331                 getPath("InputImportControl19.java"), expected);
332     }
333 
334     @Test
335     public void testUrlInFilePropertyUnableToLoad() {
336 
337         final CheckstyleException exc =
338                 getExpectedThrowable(CheckstyleException.class, () -> {
339                     final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
340                     verifyWithInlineConfigParser(
341                             getPath("InputImportControl20.java"), expected);
342                 }, "Test should fail if exception was not thrown");
343         final String message = getCheckstyleExceptionMessage(exc);
344         final String messageStart = "Unable to load ";
345 
346         assertWithMessage("Invalid message, should start with: %s", messageStart)
347                 .that(message)
348                 .startsWith(messageStart);
349     }
350 
351     @Test
352     public void testCacheWhenFileExternalResourceContentDoesNotChange() throws Exception {
353         final DefaultConfiguration checkConfig = createModuleConfig(ImportControlCheck.class);
354         checkConfig.addProperty("file", getPath("InputImportControlOneRegExp.xml"));
355 
356         final DefaultConfiguration treeWalkerConfig = createModuleConfig(TreeWalker.class);
357         treeWalkerConfig.addChild(checkConfig);
358 
359         final DefaultConfiguration checkerConfig = createRootConfig(treeWalkerConfig);
360         final String uniqueFileName1 = "junit_" + UUID.randomUUID() + ".java";
361         final File cacheFile = new File(temporaryFolder, uniqueFileName1);
362         checkerConfig.addProperty("cacheFile", cacheFile.getPath());
363 
364         final String uniqueFileName2 = "empty_" + UUID.randomUUID() + ".java";
365         final File filePath = new File(temporaryFolder, uniqueFileName2);
366 
367         execute(checkerConfig, filePath.toString());
368         // One more time to use cache.
369         execute(checkerConfig, filePath.toString());
370 
371         final String contents = Files.readString(cacheFile.toPath());
372         assertWithMessage("External resource is not present in cache")
373                 .that(contents)
374                 .contains("InputImportControlOneRegExp.xml");
375     }
376 
377     @Test
378     public void testPathRegexMatches() throws Exception {
379         final String[] expected = {"13:1: " + getCheckMessage(MSG_DISALLOWED, "java.io.File")};
380 
381         verifyWithInlineConfigParser(
382                 getPath("InputImportControl21.java"), expected);
383     }
384 
385     @Test
386     public void testPathRegexMatchesPartially() throws Exception {
387         final String[] expected = {"13:1: " + getCheckMessage(MSG_DISALLOWED, "java.io.File")};
388 
389         verifyWithInlineConfigParser(
390                 getPath("InputImportControl22.java"), expected);
391     }
392 
393     @Test
394     public void testPathRegexDoesntMatch() throws Exception {
395         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
396 
397         verifyWithInlineConfigParser(
398                 getPath("InputImportControl23.java"), expected);
399     }
400 
401     @Test
402     public void testPathRegexDoesntMatchPartially() throws Exception {
403         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
404 
405         verifyWithInlineConfigParser(
406                 getPath("InputImportControl24.java"), expected);
407     }
408 
409     @Test
410     public void testDisallowClassOfAllowPackage() throws Exception {
411         final String[] expected = {
412             "12:1: " + getCheckMessage(MSG_DISALLOWED, "java.util.Date"),
413         };
414 
415         verifyWithInlineConfigParser(
416                 getPath("InputImportControlDisallowClassOfAllowPackage.java"),
417                 expected);
418     }
419 
420     @Test
421     public void testFileName() throws Exception {
422         final String[] expected = {
423             "11:1: " + getCheckMessage(MSG_DISALLOWED, "java.awt.Image"),
424         };
425 
426         verifyWithInlineConfigParser(
427                 getPath("InputImportControlFileName.java"), expected);
428     }
429 
430     @Test
431     public void testWithRegex() throws Exception {
432         final String[] expected = {
433             "11:1: " + getCheckMessage(MSG_DISALLOWED, "java.io.File"),
434         };
435 
436         verifyWithInlineConfigParser(
437             getPath("InputImportControlWithRegex.java"), expected);
438     }
439 
440     @Test
441     public void testFileNameNoExtension() throws Exception {
442         final String[] expected = {
443             "13:1: " + getCheckMessage(MSG_DISALLOWED, "java.awt.Image"),
444         };
445 
446         verifyWithInlineConfigParser(
447                 getPath("InputImportControlFileNameNoExtension"), expected);
448     }
449 
450     @Test
451     public void testBeginTreeCurrentImportControl() throws Exception {
452         final String file1 = getPath("InputImportControlBeginTree1.java");
453         final String file2 = getPath("InputImportControlBeginTree2.java");
454         final List<String> expectedFirstInput = Arrays.asList(
455             "11:1: " + getCheckMessage(MSG_DISALLOWED, "java.util.stream.Stream"),
456             "12:1: " + getCheckMessage(MSG_DISALLOWED, "java.util.stream.Collectors")
457         );
458         final List<String> expectedSecondInput = Arrays.asList(CommonUtil.EMPTY_STRING_ARRAY);
459         verifyWithInlineConfigParser(file1, file2, expectedFirstInput, expectedSecondInput);
460     }
461 
462     @Test
463     public void testImportControlFileName() throws Exception {
464         final String[] expected = {
465             "11:1: " + getCheckMessage(MSG_DISALLOWED, "java.awt.Image"),
466         };
467 
468         verifyWithInlineConfigParser(
469                 getPath("InputImportControlTestRegexpInFile.java"), expected);
470     }
471 
472     @Test
473     public void testImportControlFileName2() throws Exception {
474         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
475 
476         verifyWithInlineConfigParser(
477                 getPath("InputImportControlTestRegexpInFile2.java"), expected);
478     }
479 
480     @Test
481     public void testImportControlTestException() {
482         final CheckstyleException ex = getExpectedThrowable(CheckstyleException.class,
483                 () -> {
484                     verifyWithInlineConfigParser(getPath("InputImportControlTestException.java"));
485                 });
486 
487         assertThat(ex.getCause().getCause().getCause().getCause().getCause().getMessage())
488                 .startsWith("unable to parse file:");
489     }
490 
491     @Test
492     public void testModuleImportNoRegex() throws Exception {
493         final String[] expected = {
494             "12:1: " + getCheckMessage(MSG_DISALLOWED, "java.sql"),
495             "14:1: " + getCheckMessage(MSG_DISALLOWED, "java.logging"),
496         };
497         verifyWithInlineConfigParser(
498                 getNonCompilablePath("InputImportControlModuleImportNoRegex.java"), expected);
499     }
500 
501     @Test
502     public void testModuleImportWithRegex() throws Exception {
503         final String[] expected = {
504             "12:1: " + getCheckMessage(MSG_DISALLOWED, "java.sql"),
505         };
506         verifyWithInlineConfigParser(
507                 getNonCompilablePath("InputImportControlModuleImportWithRegex.java"), expected);
508     }
509 
510     /**
511      * Returns String message of original exception that was thrown in
512      * ImportControlCheck.setUrl or ImportControlCheck.setFile
513      * and caught in test (it was caught and re-thrown twice after that)
514      * Note: this is helper method with hard-coded structure of exception causes. It works
515      * fine for methods mentioned, you may need to adjust it if you try to use it for other needs
516      *
517      * @param exception Exception
518      * @return String message of original exception
519      */
520     private static String getCheckstyleExceptionMessage(CheckstyleException exception) {
521         return exception.getCause().getCause().getCause().getCause().getMessage();
522     }
523 
524 }