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