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;
21  
22  import static com.google.common.truth.Truth.assertWithMessage;
23  import static com.puppycrawl.tools.checkstyle.internal.utils.TestUtil.getExpectedThrowable;
24  
25  import java.io.File;
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.net.URL;
29  import java.net.URLConnection;
30  import java.net.URLStreamHandler;
31  import java.util.Arrays;
32  import java.util.Collections;
33  import java.util.Enumeration;
34  import java.util.HashSet;
35  import java.util.Set;
36  
37  import org.junit.jupiter.api.Test;
38  import org.xml.sax.SAXException;
39  
40  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
41  
42  /**
43   * Tests loading of package names from XML files.
44   *
45   * @noinspection ClassLoaderInstantiation
46   * @noinspectionreason ClassLoaderInstantiation - Custom class loader is needed to pass URLs to
47   *      pretend these are loaded from the classpath though we can't add/change the files for
48   *      testing. The class loader is nested in this class, so the custom class loader we
49   *      are using is safe.
50   */
51  public class PackageNamesLoaderTest extends AbstractPathTestSupport {
52  
53      @Override
54      protected String getPackageLocation() {
55          return "com/puppycrawl/tools/checkstyle/packagenamesloader";
56      }
57  
58      @Test
59      public void testDefault()
60              throws CheckstyleException {
61          final Set<String> packageNames = PackageNamesLoader
62                  .getPackageNames(Thread.currentThread()
63                          .getContextClassLoader());
64          assertWithMessage("pkgNames.length.")
65              .that(packageNames)
66              .isEmpty();
67      }
68  
69      @Test
70      public void testNoPackages() throws Exception {
71          final Set<String> actualPackageNames = PackageNamesLoader
72                  .getPackageNames(new TestUrlsClassLoader(Collections.emptyEnumeration()));
73  
74          assertWithMessage("Invalid package names length.")
75              .that(actualPackageNames)
76              .isEmpty();
77      }
78  
79      @Test
80      public void testPackagesFile() throws Exception {
81          final Enumeration<URL> enumeration = Collections.enumeration(Collections.singleton(
82                  new File(getPath("InputPackageNamesLoaderFile.xml")).toURI().toURL()));
83  
84          final Set<String> actualPackageNames = PackageNamesLoader
85                  .getPackageNames(new TestUrlsClassLoader(enumeration));
86          final String[] expectedPackageNames = {
87              "com.puppycrawl.tools.checkstyle",
88              "com.puppycrawl.tools.checkstyle.checks",
89              "com.puppycrawl.tools.checkstyle.checks.annotation",
90              "com.puppycrawl.tools.checkstyle.checks.blocks",
91              "com.puppycrawl.tools.checkstyle.checks.coding",
92              "com.puppycrawl.tools.checkstyle.checks.design",
93              "com.puppycrawl.tools.checkstyle.checks.header",
94              "com.puppycrawl.tools.checkstyle.checks.imports",
95              "com.puppycrawl.tools.checkstyle.checks.indentation",
96              "com.puppycrawl.tools.checkstyle.checks.javadoc",
97              "com.puppycrawl.tools.checkstyle.checks.metrics",
98              "com.puppycrawl.tools.checkstyle.checks.modifier",
99              "com.puppycrawl.tools.checkstyle.checks.naming",
100             "com.puppycrawl.tools.checkstyle.checks.regexp",
101             "com.puppycrawl.tools.checkstyle.checks.sizes",
102             "com.puppycrawl.tools.checkstyle.checks.whitespace",
103             "com.puppycrawl.tools.checkstyle.filefilters",
104             "com.puppycrawl.tools.checkstyle.filters",
105         };
106 
107         assertWithMessage("Invalid package names length.")
108             .that(actualPackageNames)
109             .hasSize(expectedPackageNames.length);
110         final Set<String> checkstylePackagesSet =
111                 new HashSet<>(Arrays.asList(expectedPackageNames));
112         assertWithMessage("Invalid names set.")
113             .that(actualPackageNames)
114             .isEqualTo(checkstylePackagesSet);
115     }
116 
117     @Test
118     public void testPackagesWithDots() throws Exception {
119         final Enumeration<URL> enumeration = Collections.enumeration(Collections.singleton(
120                 new File(getPath("InputPackageNamesLoaderWithDots.xml")).toURI().toURL()));
121 
122         final Set<String> actualPackageNames = PackageNamesLoader
123                 .getPackageNames(new TestUrlsClassLoader(enumeration));
124         final String[] expectedPackageNames = {
125             "coding.",
126         };
127 
128         assertWithMessage("Invalid package names length.")
129             .that(actualPackageNames)
130             .hasSize(expectedPackageNames.length);
131         final Set<String> checkstylePackagesSet =
132                 new HashSet<>(Arrays.asList(expectedPackageNames));
133         assertWithMessage("Invalid names set.")
134             .that(actualPackageNames)
135             .isEqualTo(checkstylePackagesSet);
136     }
137 
138     @Test
139     public void testPackagesWithDotsEx() throws Exception {
140         final Enumeration<URL> enumeration = Collections.enumeration(Collections.singleton(
141                 new File(getPath("InputPackageNamesLoaderWithDotsEx.xml")).toURI().toURL()));
142 
143         final Set<String> actualPackageNames = PackageNamesLoader
144                 .getPackageNames(new TestUrlsClassLoader(enumeration));
145         final String[] expectedPackageNames = {
146             "coding.specific",
147             "coding.",
148         };
149 
150         assertWithMessage("Invalid package names length.")
151             .that(actualPackageNames)
152             .hasSize(expectedPackageNames.length);
153         final Set<String> checkstylePackagesSet =
154                 new HashSet<>(Arrays.asList(expectedPackageNames));
155         assertWithMessage("Invalid names set.")
156             .that(actualPackageNames)
157             .isEqualTo(checkstylePackagesSet);
158     }
159 
160     @Test
161     public void testPackagesWithSaxException() throws Exception {
162         final Enumeration<URL> enumeration = Collections.enumeration(Collections.singleton(
163                 new File(getPath("InputPackageNamesLoaderNotXml.java")).toURI().toURL()));
164 
165         try {
166             PackageNamesLoader.getPackageNames(new TestUrlsClassLoader(enumeration));
167             assertWithMessage("CheckstyleException is expected").fail();
168         }
169         catch (CheckstyleException exc) {
170             assertWithMessage("Invalid exception cause class")
171                     .that(exc)
172                     .hasCauseThat()
173                     .isInstanceOf(SAXException.class);
174         }
175     }
176 
177     @Test
178     public void testPackagesWithIoException() throws Exception {
179         final URLConnection urlConnection = new URLConnection(null) {
180             @Override
181             public void connect() {
182                 // no code
183             }
184 
185             @Override
186             public InputStream getInputStream() {
187                 return null;
188             }
189         };
190         final URL url = new URL("test", null, 0, "", new URLStreamHandler() {
191             @Override
192             protected URLConnection openConnection(URL u) {
193                 return urlConnection;
194             }
195         });
196 
197         final Enumeration<URL> enumeration = Collections.enumeration(Collections.singleton(url));
198 
199         try {
200             PackageNamesLoader.getPackageNames(new TestUrlsClassLoader(enumeration));
201             assertWithMessage("CheckstyleException is expected").fail();
202         }
203         catch (CheckstyleException exc) {
204             assertWithMessage("Invalid exception cause class")
205                     .that(exc)
206                     .hasCauseThat()
207                     .isInstanceOf(IOException.class);
208             assertWithMessage("Invalid exception message")
209                     .that(exc)
210                     .hasMessageThat()
211                     .isNotEqualTo("unable to get package file resources");
212             assertWithMessage("Exception message must contain URL")
213                     .that(exc.getMessage())
214                     .contains(url.toString());
215         }
216     }
217 
218     @Test
219     public void testPackagesWithIoExceptionGetResources() {
220         try {
221             PackageNamesLoader.getPackageNames(new TestIoExceptionClassLoader());
222             assertWithMessage("CheckstyleException is expected").fail();
223         }
224         catch (CheckstyleException exc) {
225             assertWithMessage("Invalid exception cause class")
226                     .that(exc)
227                     .hasCauseThat()
228                     .isInstanceOf(IOException.class);
229             assertWithMessage("Invalid exception message")
230                 .that(exc.getMessage())
231                 .isEqualTo("unable to get package file resources");
232         }
233     }
234 
235     @Test
236     public void testUnmodifiableCollection() throws Exception {
237         final Set<String> actualPackageNames = PackageNamesLoader
238                 .getPackageNames(new TestUrlsClassLoader(Collections.emptyEnumeration()));
239 
240         final Exception ex = getExpectedThrowable(UnsupportedOperationException.class,
241                 () -> actualPackageNames.add("com.puppycrawl.tools.checkstyle.checks.modifier"));
242 
243         assertWithMessage("Exception class is not expected")
244                 .that(ex.getClass())
245                 .isEqualTo(UnsupportedOperationException.class);
246     }
247 
248     @Test
249     public void testMapping() throws Exception {
250         final Enumeration<URL> enumeration = Collections.enumeration(Collections.singleton(
251                 new File(getPath("InputPackageNamesLoader1.xml")).toURI().toURL()));
252 
253         final Set<String> actualPackageNames = PackageNamesLoader
254                 .getPackageNames(new TestUrlsClassLoader(enumeration));
255 
256         assertWithMessage("Invalid package names length.")
257             .that(actualPackageNames)
258             .hasSize(3);
259     }
260 
261     @Test
262     public void testMapping2() throws Exception {
263         final Enumeration<URL> enumeration = Collections.enumeration(Collections.singleton(
264                 new File(getPath("InputPackageNamesLoader2.xml")).toURI().toURL()));
265 
266         final Set<String> actualPackageNames = PackageNamesLoader
267                 .getPackageNames(new TestUrlsClassLoader(enumeration));
268 
269         assertWithMessage("Invalid package names length.")
270             .that(actualPackageNames)
271             .hasSize(3);
272     }
273 
274     /**
275      * Mocked ClassLoader for testing URL loading.
276      *
277      * @noinspection CustomClassloader
278      * @noinspectionreason CustomClassloader - needed to pass URLs to pretend these are loaded
279      *      from the classpath though we can't add/change the files for testing
280      */
281     private static final class TestUrlsClassLoader extends ClassLoader {
282 
283         private final Enumeration<URL> urls;
284 
285         private TestUrlsClassLoader(Enumeration<URL> urls) {
286             this.urls = urls;
287         }
288 
289         @Override
290         public Enumeration<URL> getResources(String name) {
291             return urls;
292         }
293     }
294 
295     /**
296      * Mocked ClassLoader for testing exceptions.
297      *
298      * @noinspection CustomClassloader
299      * @noinspectionreason CustomClassloader - needed to throw an exception to
300      *      test a catch statement
301      */
302     private static final class TestIoExceptionClassLoader extends ClassLoader {
303         @Override
304         public Enumeration<URL> getResources(String name) throws IOException {
305             throw new IOException("test");
306         }
307     }
308 
309 }