Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
LocalizedMessage |
|
| 2.588235294117647;2.588 |
1 | //////////////////////////////////////////////////////////////////////////////// | |
2 | // checkstyle: Checks Java source code for adherence to a set of rules. | |
3 | // Copyright (C) 2001-2014 Oliver Burn | |
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 | package com.puppycrawl.tools.checkstyle.api; | |
20 | ||
21 | import java.io.Serializable; | |
22 | import java.text.MessageFormat; | |
23 | import java.util.Arrays; | |
24 | import java.util.Collections; | |
25 | import java.util.HashMap; | |
26 | import java.util.Locale; | |
27 | import java.util.Map; | |
28 | import java.util.MissingResourceException; | |
29 | import java.util.ResourceBundle; | |
30 | ||
31 | ||
32 | /** | |
33 | * Represents a message that can be localised. The translations come from | |
34 | * message.properties files. The underlying implementation uses | |
35 | * java.text.MessageFormat. | |
36 | * | |
37 | * @author Oliver Burn | |
38 | * @author lkuehne | |
39 | * @version 1.0 | |
40 | */ | |
41 | 25340 | public final class LocalizedMessage |
42 | implements Comparable<LocalizedMessage>, Serializable | |
43 | { | |
44 | /** Required for serialization. */ | |
45 | private static final long serialVersionUID = 5675176836184862150L; | |
46 | ||
47 | /** hash function multiplicand */ | |
48 | private static final int HASH_MULT = 29; | |
49 | ||
50 | /** the locale to localise messages to **/ | |
51 | 1 | private static Locale sLocale = Locale.getDefault(); |
52 | ||
53 | /** | |
54 | * A cache that maps bundle names to RessourceBundles. | |
55 | * Avoids repetitive calls to ResourceBundle.getBundle(). | |
56 | * TODO: The cache should be cleared at some point. | |
57 | */ | |
58 | 1 | private static final Map<String, ResourceBundle> BUNDLE_CACHE = |
59 | Collections.synchronizedMap(new HashMap<String, ResourceBundle>()); | |
60 | ||
61 | /** the line number **/ | |
62 | private final int mLineNo; | |
63 | /** the column number **/ | |
64 | private final int mColNo; | |
65 | ||
66 | /** the severity level **/ | |
67 | private final SeverityLevel mSeverityLevel; | |
68 | ||
69 | /** the id of the module generating the message. */ | |
70 | private final String mModuleId; | |
71 | ||
72 | /** the default severity level if one is not specified */ | |
73 | 1 | private static final SeverityLevel DEFAULT_SEVERITY = SeverityLevel.ERROR; |
74 | ||
75 | /** key for the message format **/ | |
76 | private final String mKey; | |
77 | ||
78 | /** arguments for MessageFormat **/ | |
79 | private final Object[] mArgs; | |
80 | ||
81 | /** name of the resource bundle to get messages from **/ | |
82 | private final String mBundle; | |
83 | ||
84 | /** class of the source for this LocalizedMessage */ | |
85 | private final Class<?> mSourceClass; | |
86 | ||
87 | /** a custom message overriding the default message from the bundle. */ | |
88 | private final String mCustomMessage; | |
89 | ||
90 | @Override | |
91 | public boolean equals(Object aObject) | |
92 | { | |
93 | 0 | if (this == aObject) { |
94 | 0 | return true; |
95 | } | |
96 | 0 | if (!(aObject instanceof LocalizedMessage)) { |
97 | 0 | return false; |
98 | } | |
99 | ||
100 | 0 | final LocalizedMessage localizedMessage = (LocalizedMessage) aObject; |
101 | ||
102 | 0 | if (mColNo != localizedMessage.mColNo) { |
103 | 0 | return false; |
104 | } | |
105 | 0 | if (mLineNo != localizedMessage.mLineNo) { |
106 | 0 | return false; |
107 | } | |
108 | 0 | if (!mKey.equals(localizedMessage.mKey)) { |
109 | 0 | return false; |
110 | } | |
111 | ||
112 | 0 | if (!Arrays.equals(mArgs, localizedMessage.mArgs)) { |
113 | 0 | return false; |
114 | } | |
115 | // ignoring mBundle for perf reasons. | |
116 | ||
117 | // we currently never load the same error from different bundles. | |
118 | ||
119 | 0 | return true; |
120 | } | |
121 | ||
122 | @Override | |
123 | public int hashCode() | |
124 | { | |
125 | int result; | |
126 | 0 | result = mLineNo; |
127 | 0 | result = HASH_MULT * result + mColNo; |
128 | 0 | result = HASH_MULT * result + mKey.hashCode(); |
129 | 0 | for (final Object element : mArgs) { |
130 | 0 | result = HASH_MULT * result + element.hashCode(); |
131 | } | |
132 | 0 | return result; |
133 | } | |
134 | ||
135 | /** | |
136 | * Creates a new <code>LocalizedMessage</code> instance. | |
137 | * | |
138 | * @param aLineNo line number associated with the message | |
139 | * @param aColNo column number associated with the message | |
140 | * @param aBundle resource bundle name | |
141 | * @param aKey the key to locate the translation | |
142 | * @param aArgs arguments for the translation | |
143 | * @param aSeverityLevel severity level for the message | |
144 | * @param aModuleId the id of the module the message is associated with | |
145 | * @param aSourceClass the Class that is the source of the message | |
146 | * @param aCustomMessage optional custom message overriding the default | |
147 | */ | |
148 | public LocalizedMessage(int aLineNo, | |
149 | int aColNo, | |
150 | String aBundle, | |
151 | String aKey, | |
152 | Object[] aArgs, | |
153 | SeverityLevel aSeverityLevel, | |
154 | String aModuleId, | |
155 | Class<?> aSourceClass, | |
156 | String aCustomMessage) | |
157 | 3446 | { |
158 | 3446 | mLineNo = aLineNo; |
159 | 3446 | mColNo = aColNo; |
160 | 3446 | mKey = aKey; |
161 | 3446 | mArgs = (null == aArgs) ? null : aArgs.clone(); |
162 | 3446 | mBundle = aBundle; |
163 | 3446 | mSeverityLevel = aSeverityLevel; |
164 | 3446 | mModuleId = aModuleId; |
165 | 3446 | mSourceClass = aSourceClass; |
166 | 3446 | mCustomMessage = aCustomMessage; |
167 | 3446 | } |
168 | ||
169 | /** | |
170 | * Creates a new <code>LocalizedMessage</code> instance. | |
171 | * | |
172 | * @param aLineNo line number associated with the message | |
173 | * @param aColNo column number associated with the message | |
174 | * @param aBundle resource bundle name | |
175 | * @param aKey the key to locate the translation | |
176 | * @param aArgs arguments for the translation | |
177 | * @param aModuleId the id of the module the message is associated with | |
178 | * @param aSourceClass the Class that is the source of the message | |
179 | * @param aCustomMessage optional custom message overriding the default | |
180 | */ | |
181 | public LocalizedMessage(int aLineNo, | |
182 | int aColNo, | |
183 | String aBundle, | |
184 | String aKey, | |
185 | Object[] aArgs, | |
186 | String aModuleId, | |
187 | Class<?> aSourceClass, | |
188 | String aCustomMessage) | |
189 | { | |
190 | 10 | this(aLineNo, |
191 | aColNo, | |
192 | aBundle, | |
193 | aKey, | |
194 | aArgs, | |
195 | DEFAULT_SEVERITY, | |
196 | aModuleId, | |
197 | aSourceClass, | |
198 | aCustomMessage); | |
199 | 10 | } |
200 | ||
201 | /** | |
202 | * Creates a new <code>LocalizedMessage</code> instance. | |
203 | * | |
204 | * @param aLineNo line number associated with the message | |
205 | * @param aBundle resource bundle name | |
206 | * @param aKey the key to locate the translation | |
207 | * @param aArgs arguments for the translation | |
208 | * @param aSeverityLevel severity level for the message | |
209 | * @param aModuleId the id of the module the message is associated with | |
210 | * @param aSourceClass the source class for the message | |
211 | * @param aCustomMessage optional custom message overriding the default | |
212 | */ | |
213 | public LocalizedMessage(int aLineNo, | |
214 | String aBundle, | |
215 | String aKey, | |
216 | Object[] aArgs, | |
217 | SeverityLevel aSeverityLevel, | |
218 | String aModuleId, | |
219 | Class<?> aSourceClass, | |
220 | String aCustomMessage) | |
221 | { | |
222 | 734 | this(aLineNo, 0, aBundle, aKey, aArgs, aSeverityLevel, aModuleId, |
223 | aSourceClass, aCustomMessage); | |
224 | 734 | } |
225 | ||
226 | /** | |
227 | * Creates a new <code>LocalizedMessage</code> instance. The column number | |
228 | * defaults to 0. | |
229 | * | |
230 | * @param aLineNo line number associated with the message | |
231 | * @param aBundle name of a resource bundle that contains error messages | |
232 | * @param aKey the key to locate the translation | |
233 | * @param aArgs arguments for the translation | |
234 | * @param aModuleId the id of the module the message is associated with | |
235 | * @param aSourceClass the name of the source for the message | |
236 | * @param aCustomMessage optional custom message overriding the default | |
237 | */ | |
238 | public LocalizedMessage( | |
239 | int aLineNo, | |
240 | String aBundle, | |
241 | String aKey, | |
242 | Object[] aArgs, | |
243 | String aModuleId, | |
244 | Class<?> aSourceClass, | |
245 | String aCustomMessage) | |
246 | { | |
247 | 1 | this(aLineNo, 0, aBundle, aKey, aArgs, DEFAULT_SEVERITY, aModuleId, |
248 | aSourceClass, aCustomMessage); | |
249 | 1 | } |
250 | ||
251 | /** @return the translated message **/ | |
252 | public String getMessage() | |
253 | { | |
254 | ||
255 | 3667 | final String customMessage = getCustomMessage(); |
256 | 3666 | if (customMessage != null) { |
257 | 5 | return customMessage; |
258 | } | |
259 | ||
260 | try { | |
261 | // Important to use the default class loader, and not the one in | |
262 | // the GlobalProperties object. This is because the class loader in | |
263 | // the GlobalProperties is specified by the user for resolving | |
264 | // custom classes. | |
265 | 3661 | final ResourceBundle bundle = getBundle(mBundle); |
266 | 3660 | final String pattern = bundle.getString(mKey); |
267 | 3624 | return MessageFormat.format(pattern, mArgs); |
268 | } | |
269 | 37 | catch (final MissingResourceException ex) { |
270 | // If the Check author didn't provide i18n resource bundles | |
271 | // and logs error messages directly, this will return | |
272 | // the author's original message | |
273 | 37 | return MessageFormat.format(mKey, mArgs); |
274 | } | |
275 | } | |
276 | ||
277 | /** | |
278 | * Returns the formatted custom message if one is configured. | |
279 | * @return the formatted custom message or <code>null</code> | |
280 | * if there is no custom message | |
281 | */ | |
282 | private String getCustomMessage() | |
283 | { | |
284 | ||
285 | 3667 | if (mCustomMessage == null) { |
286 | 3661 | return null; |
287 | } | |
288 | ||
289 | 6 | return MessageFormat.format(mCustomMessage, mArgs); |
290 | } | |
291 | ||
292 | /** | |
293 | * Find a ResourceBundle for a given bundle name. Uses the classloader | |
294 | * of the class emitting this message, to be sure to get the correct | |
295 | * bundle. | |
296 | * @param aBundleName the bundle name | |
297 | * @return a ResourceBundle | |
298 | */ | |
299 | private ResourceBundle getBundle(String aBundleName) | |
300 | { | |
301 | 3661 | synchronized (BUNDLE_CACHE) { |
302 | 3661 | ResourceBundle bundle = BUNDLE_CACHE |
303 | .get(aBundleName); | |
304 | 3661 | if (bundle == null) { |
305 | 18 | bundle = ResourceBundle.getBundle(aBundleName, sLocale, |
306 | mSourceClass.getClassLoader()); | |
307 | 17 | BUNDLE_CACHE.put(aBundleName, bundle); |
308 | } | |
309 | 3660 | return bundle; |
310 | 1 | } |
311 | } | |
312 | ||
313 | /** @return the line number **/ | |
314 | public int getLineNo() | |
315 | { | |
316 | 103265 | return mLineNo; |
317 | } | |
318 | ||
319 | /** @return the column number **/ | |
320 | public int getColumnNo() | |
321 | { | |
322 | 10548 | return mColNo; |
323 | } | |
324 | ||
325 | /** @return the severity level **/ | |
326 | public SeverityLevel getSeverityLevel() | |
327 | { | |
328 | 6780 | return mSeverityLevel; |
329 | } | |
330 | ||
331 | /** @return the module identifier. */ | |
332 | public String getModuleId() | |
333 | { | |
334 | 0 | return mModuleId; |
335 | } | |
336 | ||
337 | /** | |
338 | * Returns the message key to locate the translation, can also be used | |
339 | * in IDE plugins to map error messages to corrective actions. | |
340 | * | |
341 | * @return the message key | |
342 | */ | |
343 | public String getKey() | |
344 | { | |
345 | 1 | return mKey; |
346 | } | |
347 | ||
348 | /** @return the name of the source for this LocalizedMessage */ | |
349 | public String getSourceName() | |
350 | { | |
351 | 240 | return mSourceClass.getName(); |
352 | } | |
353 | ||
354 | /** @param aLocale the locale to use for localization **/ | |
355 | public static void setLocale(Locale aLocale) | |
356 | { | |
357 | 593 | sLocale = aLocale; |
358 | 593 | } |
359 | ||
360 | //////////////////////////////////////////////////////////////////////////// | |
361 | // Interface Comparable methods | |
362 | //////////////////////////////////////////////////////////////////////////// | |
363 | ||
364 | /** {@inheritDoc} */ | |
365 | public int compareTo(LocalizedMessage aOther) | |
366 | { | |
367 | 25340 | if (getLineNo() == aOther.getLineNo()) { |
368 | 1203 | if (getColumnNo() == aOther.getColumnNo()) { |
369 | 139 | return getMessage().compareTo(aOther.getMessage()); |
370 | } | |
371 | 1064 | return (getColumnNo() < aOther.getColumnNo()) ? -1 : 1; |
372 | } | |
373 | ||
374 | 24137 | return (getLineNo() < aOther.getLineNo()) ? -1 : 1; |
375 | } | |
376 | } |