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