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 org.junit.jupiter.api.Assumptions.assumeFalse;
24  
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.net.URI;
28  import java.net.URL;
29  import java.net.URLConnection;
30  import java.net.URLStreamHandler;
31  import java.util.Locale;
32  import java.util.ResourceBundle;
33  import java.util.concurrent.atomic.AtomicBoolean;
34  
35  import org.junit.jupiter.api.AfterEach;
36  import org.junit.jupiter.api.Test;
37  import org.junitpioneer.jupiter.DefaultLocale;
38  
39  import com.puppycrawl.tools.checkstyle.LocalizedMessage.Utf8Control;
40  import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;
41  
42  /**
43   * Custom class loader is needed to pass URLs to pretend these are loaded from the classpath
44   * though we can't add/change the files for testing. The class loader is nested in this class,
45   * so the custom class loader we are using is safe.
46   *
47   * @noinspection ClassLoaderInstantiation
48   * @noinspectionreason ClassLoaderInstantiation - Custom class loader is needed to
49   *      pass URLs for testing
50   */
51  public class LocalizedMessageTest {
52  
53      private static final Locale DEFAULT_LOCALE = Locale.ENGLISH;
54  
55      @DefaultLocale("en")
56      @Test
57      public void testNullArgs() {
58          final LocalizedMessage messageClass = new LocalizedMessage(Definitions.CHECKSTYLE_BUNDLE,
59                  DefaultLogger.class, "DefaultLogger.addException", "myfile");
60          assertWithMessage("Violation should contain exception info")
61                  .that(messageClass.getMessage())
62                  .contains("Error auditing myfile");
63  
64          final LocalizedMessage nullClass = new LocalizedMessage(Definitions.CHECKSTYLE_BUNDLE,
65                  DefaultLogger.class, "DefaultLogger.addException");
66          final String outputForNullArgs = nullClass.getMessage();
67          assertWithMessage("Violation should contain exception info")
68                  .that(outputForNullArgs)
69                  .contains("Error auditing {0}");
70      }
71  
72      @Test
73      public void testBundleReloadUrlNull() throws IOException {
74          final Utf8Control control = new Utf8Control();
75          final ResourceBundle bundle = control.newBundle(
76                  "com.puppycrawl.tools.checkstyle.checks.coding.messages",
77                  Locale.ENGLISH, "java.class",
78                  Thread.currentThread().getContextClassLoader(), true);
79          assertWithMessage("Bundle should be null when reload is true and URL is null")
80                  .that(bundle)
81                  .isNull();
82      }
83  
84      /**
85       * Tests reload of resource bundle.
86       *
87       * @noinspection resource, IOResourceOpenedButNotSafelyClosed
88       * @noinspectionreason resource - we have no need to use try with resources in testing
89       * @noinspectionreason IOResourceOpenedButNotSafelyClosed - no need to close resources in
90       *      testing
91       */
92      @Test
93      public void testBundleReloadUrlNotNull() throws IOException {
94          final AtomicBoolean closed = new AtomicBoolean();
95  
96          final InputStream inputStream = new InputStream() {
97              @Override
98              public int read() {
99                  return -1;
100             }
101 
102             @Override
103             public void close() {
104                 closed.set(true);
105             }
106         };
107         final URLConnection urlConnection = new URLConnection(null) {
108             @Override
109             public void connect() {
110                 // no code
111             }
112 
113             @Override
114             public InputStream getInputStream() {
115                 return inputStream;
116             }
117         };
118         final URL url = URL.of(URI.create("test:///"), new URLStreamHandler() {
119             @Override
120             protected URLConnection openConnection(URL u) {
121                 return urlConnection;
122             }
123         });
124 
125         final Utf8Control control = new Utf8Control();
126         final ResourceBundle bundle = control.newBundle(
127                 "com.puppycrawl.tools.checkstyle.checks.coding.messages", Locale.ENGLISH,
128                 "java.class", new TestUrlsClassLoader(url), true);
129 
130         assertWithMessage("Bundle should not be null when stream is not null")
131                 .that(bundle)
132                 .isNotNull();
133         assertWithMessage("connection should not be using caches")
134                 .that(urlConnection.getUseCaches())
135                 .isFalse();
136         assertWithMessage("connection should be closed")
137                 .that(closed.get())
138                 .isTrue();
139     }
140 
141     /**
142      * Tests reload of resource bundle.
143      *
144      * @noinspection resource, IOResourceOpenedButNotSafelyClosed
145      * @noinspectionreason resource - we have no need to use try with resources in testing
146      * @noinspectionreason IOResourceOpenedButNotSafelyClosed - no need to close resources in
147      *      testing
148      */
149     @Test
150     public void testBundleReloadUrlNotNullFalseReload() throws IOException {
151         final AtomicBoolean closed = new AtomicBoolean();
152 
153         final InputStream inputStream = new InputStream() {
154             @Override
155             public int read() {
156                 return -1;
157             }
158 
159             @Override
160             public void close() {
161                 closed.set(true);
162             }
163         };
164         final URLConnection urlConnection = new URLConnection(null) {
165             @Override
166             public void connect() {
167                 // no code
168             }
169 
170             @Override
171             public InputStream getInputStream() {
172                 return inputStream;
173             }
174         };
175         final URL url = URL.of(URI.create("test:///"), new URLStreamHandler() {
176             @Override
177             protected URLConnection openConnection(URL u) {
178                 return urlConnection;
179             }
180         });
181 
182         final Utf8Control control = new Utf8Control();
183         final ResourceBundle bundle = control.newBundle(
184                 "com.puppycrawl.tools.checkstyle.checks.coding.messages", Locale.ENGLISH,
185                 "java.class", new TestUrlsClassLoader(url), false);
186 
187         assertWithMessage("Bundle should not be null when stream is not null")
188                 .that(bundle)
189                 .isNotNull();
190         assertWithMessage("connection should not be using caches")
191                 .that(urlConnection.getUseCaches())
192                 .isTrue();
193         assertWithMessage("connection should be closed")
194                 .that(closed.get())
195                 .isTrue();
196     }
197 
198     @Test
199     public void testBundleReloadUrlNotNullStreamNull() throws IOException {
200         final URL url = URL.of(URI.create("test:///"), new URLStreamHandler() {
201             @Override
202             protected URLConnection openConnection(URL ignore) {
203                 return null;
204             }
205         });
206 
207         final Utf8Control control = new Utf8Control();
208         final ResourceBundle bundle = control.newBundle(
209                 "com.puppycrawl.tools.checkstyle.checks.coding.messages",
210                 Locale.ENGLISH, "java.class",
211                 new TestUrlsClassLoader(url), true);
212         assertWithMessage("Bundle should be null when stream is null")
213                 .that(bundle)
214                 .isNull();
215     }
216 
217     /**
218      * Verifies that the language specified with the system property {@code user.language} exists.
219      */
220     @Test
221     public void testLanguageIsValid() {
222         final String language = DEFAULT_LOCALE.getLanguage();
223         assumeFalse(language.isEmpty(), "Locale not set");
224         assertWithMessage("Invalid language")
225                 .that(Locale.getISOLanguages())
226                 .asList()
227                 .contains(language);
228     }
229 
230     /**
231      * Verifies that the country specified with the system property {@code user.country} exists.
232      */
233     @Test
234     public void testCountryIsValid() {
235         final String country = DEFAULT_LOCALE.getCountry();
236         assumeFalse(country.isEmpty(), "Locale not set");
237         assertWithMessage("Invalid country")
238                 .that(Locale.getISOCountries())
239                 .asList()
240                 .contains(country);
241     }
242 
243     @Test
244     public void testMessageInFrench() {
245         final LocalizedMessage violation = createSampleViolation();
246         LocalizedMessage.setLocale(Locale.FRENCH);
247 
248         assertWithMessage("Invalid violation")
249             .that(violation.getMessage())
250             .isEqualTo("Instruction vide.");
251     }
252 
253     @DefaultLocale("fr")
254     @Test
255     public void testEnforceEnglishLanguageBySettingUnitedStatesLocale() {
256         LocalizedMessage.setLocale(Locale.US);
257         final LocalizedMessage violation = createSampleViolation();
258 
259         assertWithMessage("Invalid violation")
260             .that(violation.getMessage())
261             .isEqualTo("Empty statement.");
262     }
263 
264     @DefaultLocale("fr")
265     @Test
266     public void testEnforceEnglishLanguageBySettingRootLocale() {
267         LocalizedMessage.setLocale(Locale.ROOT);
268         final LocalizedMessage violation = createSampleViolation();
269 
270         assertWithMessage("Invalid violation")
271             .that(violation.getMessage())
272             .isEqualTo("Empty statement.");
273     }
274 
275     private static LocalizedMessage createSampleViolation() {
276         return new LocalizedMessage("com.puppycrawl.tools.checkstyle.checks.coding.messages",
277                 LocalizedMessage.class, "empty.statement");
278     }
279 
280     @Test
281     public void testArgsFieldIsSetToNullWhenArgsIsNull() {
282         final LocalizedMessage message = new LocalizedMessage(Definitions.CHECKSTYLE_BUNDLE,
283                 DefaultLogger.class, "DefaultLogger.addException", (Object[]) null);
284         final Object[] args = TestUtil.getInternalState(message, "args", Object[].class);
285 
286         assertWithMessage("Args field should be null when null args are passed")
287                 .that(args)
288                 .isNull();
289     }
290 
291     @AfterEach
292     public void tearDown() {
293         LocalizedMessage.setLocale(DEFAULT_LOCALE);
294     }
295 
296     /**
297      * Mocked ClassLoader for testing URL loading.
298      *
299      * @noinspection CustomClassloader
300      * @noinspectionreason CustomClassloader - needed to pass URLs to pretend these are loaded
301      *      from the classpath though we can't add/change the files for testing
302      */
303     private static final class TestUrlsClassLoader extends ClassLoader {
304 
305         private final URL url;
306 
307         private TestUrlsClassLoader(URL url) {
308             this.url = url;
309         }
310 
311         @Override
312         public URL getResource(String name) {
313             return url;
314         }
315     }
316 
317 }