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         try {
167             PackageNamesLoader.getPackageNames(new TestUrlsClassLoader(enumeration));
168             assertWithMessage("CheckstyleException is expected").fail();
169         }
170         catch (CheckstyleException exc) {
171             assertWithMessage("Invalid exception cause class")
172                     .that(exc)
173                     .hasCauseThat()
174                     .isInstanceOf(SAXException.class);
175         }
176     }
177 
178     @Test
179     public void testPackagesWithIoException() throws Exception {
180         final URLStreamHandler handler = new URLStreamHandler() {
181             @Override
182             protected URLConnection openConnection(URL u) {
183                 return new NoOperationUrlConnection(u);
184             }
185         };
186 
187         final URL url = URL.of(URI.create("test://dummy"), handler);
188 
189         final Enumeration<URL> enumeration = Collections.enumeration(Collections.singleton(url));
190 
191         try {
192             PackageNamesLoader.getPackageNames(new TestUrlsClassLoader(enumeration));
193             assertWithMessage("CheckstyleException is expected").fail();
194         }
195         catch (CheckstyleException exc) {
196             assertWithMessage("Invalid exception cause class")
197                     .that(exc)
198                     .hasCauseThat()
199                     .isInstanceOf(IOException.class);
200             assertWithMessage("Invalid exception message")
201                     .that(exc)
202                     .hasMessageThat()
203                     .isNotEqualTo("unable to get package file resources");
204             assertWithMessage("Exception message must contain URL")
205                     .that(exc.getMessage())
206                     .contains(url.toString());
207         }
208     }
209 
210     @Test
211     public void testPackagesWithIoExceptionGetResources() {
212         try {
213             PackageNamesLoader.getPackageNames(new TestIoExceptionClassLoader());
214             assertWithMessage("CheckstyleException is expected").fail();
215         }
216         catch (CheckstyleException exc) {
217             assertWithMessage("Invalid exception cause class")
218                     .that(exc)
219                     .hasCauseThat()
220                     .isInstanceOf(IOException.class);
221             assertWithMessage("Invalid exception message")
222                 .that(exc.getMessage())
223                 .isEqualTo("unable to get package file resources");
224         }
225     }
226 
227     @Test
228     public void testUnmodifiableCollection() throws Exception {
229         final Set<String> actualPackageNames = PackageNamesLoader
230                 .getPackageNames(new TestUrlsClassLoader(Collections.emptyEnumeration()));
231 
232         final Exception ex = getExpectedThrowable(UnsupportedOperationException.class,
233                 () -> actualPackageNames.add("com.puppycrawl.tools.checkstyle.checks.modifier"));
234 
235         assertWithMessage("Exception class is not expected")
236                 .that(ex.getClass())
237                 .isEqualTo(UnsupportedOperationException.class);
238     }
239 
240     @Test
241     public void testMapping() throws Exception {
242         final Enumeration<URL> enumeration = Collections.enumeration(Collections.singleton(
243                 new File(getPath("InputPackageNamesLoader1.xml")).toURI().toURL()));
244 
245         final Set<String> actualPackageNames = PackageNamesLoader
246                 .getPackageNames(new TestUrlsClassLoader(enumeration));
247 
248         assertWithMessage("Invalid package names length.")
249             .that(actualPackageNames)
250             .hasSize(3);
251     }
252 
253     @Test
254     public void testMapping2() throws Exception {
255         final Enumeration<URL> enumeration = Collections.enumeration(Collections.singleton(
256                 new File(getPath("InputPackageNamesLoader2.xml")).toURI().toURL()));
257 
258         final Set<String> actualPackageNames = PackageNamesLoader
259                 .getPackageNames(new TestUrlsClassLoader(enumeration));
260 
261         assertWithMessage("Invalid package names length.")
262             .that(actualPackageNames)
263             .hasSize(3);
264     }
265 
266     /**
267      * Mocked ClassLoader for testing URL loading.
268      *
269      * @noinspection CustomClassloader
270      * @noinspectionreason CustomClassloader - needed to pass URLs to pretend these are loaded
271      *      from the classpath though we can't add/change the files for testing
272      */
273     private static final class TestUrlsClassLoader extends ClassLoader {
274 
275         private final Enumeration<URL> urls;
276 
277         private TestUrlsClassLoader(Enumeration<URL> urls) {
278             this.urls = urls;
279         }
280 
281         @Override
282         public Enumeration<URL> getResources(String name) {
283             return urls;
284         }
285     }
286 
287     /**
288      * Mocked ClassLoader for testing exceptions.
289      *
290      * @noinspection CustomClassloader
291      * @noinspectionreason CustomClassloader - needed to throw an exception to
292      *      test a catch statement
293      */
294     private static final class TestIoExceptionClassLoader extends ClassLoader {
295         @Override
296         public Enumeration<URL> getResources(String name) throws IOException {
297             throw new IOException("test");
298         }
299     }
300 
301     /**
302      * A custom URLConnection that simulates an IO failure.
303      */
304     private static class NoOperationUrlConnection extends URLConnection {
305 
306         /* package */ NoOperationUrlConnection(URL url) {
307             super(url);
308         }
309 
310         @Override
311         public void connect() {
312             // no-op
313         }
314 
315         @Override
316         public InputStream getInputStream() throws IOException {
317             throw new IOException("Simulated IO failure");
318         }
319     }
320 }