1 | /* |
2 | |
3 | Derby - Class org.apache.derby.iapi.services.i18n.MessageService |
4 | |
5 | Copyright 2000, 2004 The Apache Software Foundation or its licensors, as applicable. |
6 | |
7 | Licensed under the Apache License, Version 2.0 (the "License"); |
8 | you may not use this file except in compliance with the License. |
9 | You may obtain a copy of the License at |
10 | |
11 | http://www.apache.org/licenses/LICENSE-2.0 |
12 | |
13 | Unless required by applicable law or agreed to in writing, software |
14 | distributed under the License is distributed on an "AS IS" BASIS, |
15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
16 | See the License for the specific language governing permissions and |
17 | limitations under the License. |
18 | |
19 | */ |
20 | |
21 | package org.apache.derby.iapi.services.i18n; |
22 | |
23 | import org.apache.derby.iapi.services.info.JVMInfo; |
24 | import org.apache.derby.iapi.services.context.ShutdownException; |
25 | |
26 | import java.util.Locale; |
27 | import java.util.MissingResourceException; |
28 | import java.util.ResourceBundle; |
29 | import java.text.MessageFormat; |
30 | |
31 | /** |
32 | * Message Service implementation provides a mechanism for locating |
33 | * messages and substituting arguments for message parameters. |
34 | * It also provides a service for locating property values. |
35 | * <p> |
36 | * It uses the resource bundle mechanism for locating messages based on |
37 | * keys; the preferred form of resource bundle is a property file mapping |
38 | * keys to messages. |
39 | * |
40 | * @author ames |
41 | */ |
42 | public final class MessageService { |
43 | |
44 | private static final Locale EN = new Locale("en", "US"); |
45 | |
46 | private static BundleFinder finder; |
47 | |
48 | private MessageService() {} |
49 | |
50 | |
51 | public static ResourceBundle getBundleForLocale(Locale locale, String msgId) { |
52 | try { |
53 | return MessageService.getBundleWithEnDefault("org.apache.derby.loc.m"+hashString50(msgId), locale); |
54 | } catch (MissingResourceException mre) { |
55 | } |
56 | return null; |
57 | } |
58 | |
59 | |
60 | public static Object setFinder(BundleFinder theFinder) { |
61 | finder = theFinder; |
62 | |
63 | // Return an object for a caller to hang onto so |
64 | // Garbage collection doesn't GC this class. |
65 | return new MessageService().getClass(); |
66 | } |
67 | |
68 | public static String getTextMessage(String messageID) { |
69 | return getCompleteMessage(messageID, (Object[]) null); |
70 | } |
71 | public static String getTextMessage(String messageID, Object a1) { |
72 | |
73 | return getCompleteMessage(messageID, new Object[]{a1}); |
74 | } |
75 | public static String getTextMessage(String messageID, Object a1, Object a2) { |
76 | return getCompleteMessage(messageID, new Object[]{a1, a2}); |
77 | } |
78 | public static String getTextMessage(String messageID, Object a1, Object a2, Object a3) { |
79 | return getCompleteMessage(messageID, new Object[]{a1, a2, a3}); |
80 | } |
81 | public static String getTextMessage(String messageID, Object a1, Object a2, Object a3, Object a4) { |
82 | return getCompleteMessage(messageID, new Object[]{a1, a2, a3, a4}); |
83 | } |
84 | |
85 | /** |
86 | Transform the message from messageID to the actual error, warning, or |
87 | info message using the correct locale. |
88 | |
89 | <P> |
90 | The arguments to the messages are passed via an object array, the objects |
91 | in the array WILL be changed by this class. The caller should NOT get the |
92 | object back from this array. |
93 | |
94 | */ |
95 | public static String getCompleteMessage(String messageId, Object[] arguments) { |
96 | |
97 | try { |
98 | return formatMessage(getBundle(messageId), messageId, arguments, true); |
99 | } catch (MissingResourceException mre) { |
100 | // message does not exist in the requested locale or the default locale. |
101 | // most likely it does exist in our fake base class _en, so try that. |
102 | } catch (ShutdownException se) { |
103 | } |
104 | return formatMessage(getBundleForLocale(EN, messageId), messageId, arguments, false); |
105 | } |
106 | |
107 | /** |
108 | Method used by Cloudscape Network Server to get localized message (original call |
109 | from jcc. |
110 | |
111 | @param sqlcode sqlcode, not used. |
112 | @param errmcLen sqlerrmc length |
113 | @param sqlerrmc sql error message tokens, variable part of error message (ie., |
114 | arguments) plus messageId, separated by separator. |
115 | @param sqlerrp not used |
116 | @param errd0 not used |
117 | @param warn not used |
118 | @param sqlState 5-char sql state |
119 | @param file not used |
120 | @param localeStr client locale in string |
121 | @param msg OUTPUT parameter, localized error message |
122 | @param rc OUTPUT parameter, return code -- 0 for success |
123 | */ |
124 | public static void getLocalizedMessage(int sqlcode, short errmcLen, String sqlerrmc, |
125 | String sqlerrp, int errd0, int errd1, int errd2, |
126 | int errd3, int errd4, int errd5, String warn, |
127 | String sqlState, String file, String localeStr, |
128 | String[] msg, int[] rc) |
129 | { |
130 | //figure out client locale from input locale string |
131 | |
132 | int _pos1 = localeStr.indexOf("_"); // "_" position |
133 | int _pos2 = localeStr.lastIndexOf("_"); |
134 | |
135 | Locale locale = EN; //default locale |
136 | if (_pos1 != -1) |
137 | { |
138 | String language = localeStr.substring(0, _pos1); |
139 | if (_pos2 == _pos1) |
140 | { |
141 | String country = localeStr.substring(_pos1 + 1); |
142 | locale = new Locale(language, country); |
143 | } |
144 | else |
145 | { |
146 | String country = localeStr.substring(_pos1 + 1, _pos2); |
147 | String variant = localeStr.substring(_pos2 + 1); |
148 | locale = new Locale(language, country, variant); |
149 | } |
150 | } |
151 | |
152 | // get messageId and arguments, messageId is necessary for us to look up |
153 | // localized message from property file. messageId was sent as the last |
154 | // token in the sqlerrmc. |
155 | |
156 | String messageId = sqlState; //use sqlState if we don't have messageId |
157 | Object[] arguments = null; |
158 | if (sqlerrmc != null && sqlerrmc.length() > 0) |
159 | { |
160 | char [] sqlerrmc_chars = sqlerrmc.toCharArray(); |
161 | int numArgs = 0, lastSepIdx = -1; // last separator index |
162 | for (int i = 0; i < sqlerrmc_chars.length; i++) |
163 | { |
164 | if (sqlerrmc_chars[i] == 20) // separator |
165 | { |
166 | numArgs++; |
167 | lastSepIdx = i; |
168 | } |
169 | } |
170 | if (numArgs == 0) |
171 | messageId = new String(sqlerrmc_chars); //no args, only messageId then |
172 | else |
173 | { |
174 | messageId = new String(sqlerrmc_chars, lastSepIdx+1, sqlerrmc_chars.length-lastSepIdx-1); |
175 | arguments = new Object[numArgs]; |
176 | for (int start = 0, arg = 0, i = 0; i < lastSepIdx + 1; i++) |
177 | { |
178 | if (i == lastSepIdx || sqlerrmc_chars[i] == 20) // delimiter |
179 | { |
180 | arguments[arg++] = new String(sqlerrmc_chars, start, i - start); |
181 | start = i + 1; |
182 | } |
183 | } |
184 | } |
185 | } |
186 | |
187 | try { |
188 | msg[0] = formatMessage(getBundleForLocale(locale, messageId), messageId, arguments, true); |
189 | rc[0] = 0; |
190 | return; |
191 | } catch (MissingResourceException mre) { |
192 | // message does not exist in the requested locale |
193 | // most likely it does exist in our fake base class _en, so try that. |
194 | } catch (ShutdownException se) { |
195 | } |
196 | msg[0] = formatMessage(getBundleForLocale(EN, messageId), messageId, arguments, false); |
197 | rc[0] = 0; |
198 | } |
199 | |
200 | /** |
201 | Method used by Cloudscape Network Server to get localized message |
202 | |
203 | @param locale locale |
204 | @param messageId message id |
205 | @param args message arguments |
206 | */ |
207 | public static String getLocalizedMessage(Locale locale, String messageId, Object [] args) |
208 | { |
209 | String locMsg = null; |
210 | |
211 | try { |
212 | locMsg = formatMessage(getBundleForLocale(locale, messageId), messageId, args, true); |
213 | return locMsg; |
214 | } catch (MissingResourceException mre) { |
215 | // message does not exist in the requested locale |
216 | // most likely it does exist in our fake base class _en, so try that. |
217 | } catch (ShutdownException se) { |
218 | } |
219 | locMsg = formatMessage(getBundleForLocale(EN, messageId), messageId, args, false); |
220 | return locMsg; |
221 | } |
222 | |
223 | /** |
224 | */ |
225 | public static String getProperty(String messageId, String propertyName) { |
226 | |
227 | ResourceBundle bundle = getBundle(messageId); |
228 | |
229 | try { |
230 | if (bundle != null) |
231 | return bundle.getString(messageId.concat(".").concat(propertyName)); |
232 | } catch (MissingResourceException mre) { |
233 | } |
234 | return null; |
235 | } |
236 | |
237 | // |
238 | // class implementation |
239 | // |
240 | public static String formatMessage(ResourceBundle bundle, String messageId, Object[] arguments, boolean lastChance) { |
241 | |
242 | if (arguments == null) |
243 | arguments = new Object[0]; |
244 | |
245 | if (bundle != null) { |
246 | |
247 | try { |
248 | messageId = bundle.getString(messageId); |
249 | |
250 | try { |
251 | return MessageFormat.format(messageId, arguments); |
252 | } |
253 | catch (IllegalArgumentException iae) { |
254 | } |
255 | catch (NullPointerException npe) { |
256 | // |
257 | //null arguments cause a NullPointerException. |
258 | //This improves reporting. |
259 | } |
260 | |
261 | } catch (MissingResourceException mre) { |
262 | // caller will try and handle the last chance |
263 | if (lastChance) |
264 | throw mre; |
265 | } |
266 | } |
267 | |
268 | if (messageId == null) |
269 | messageId = "UNKNOWN"; |
270 | |
271 | |
272 | StringBuffer sb = new StringBuffer(messageId); |
273 | |
274 | sb.append(" : "); |
275 | int len = arguments.length; |
276 | |
277 | for (int i=0; i < len; i++) { |
278 | // prepend a comma to all but the first |
279 | if (i > 0) |
280 | sb.append(", "); |
281 | |
282 | sb.append('['); |
283 | sb.append(i); |
284 | sb.append("] "); |
285 | if (arguments[i] == null) |
286 | sb.append("null"); |
287 | else |
288 | sb.append(arguments[i].toString()); |
289 | } |
290 | |
291 | |
292 | return sb.toString(); |
293 | } |
294 | |
295 | private static ResourceBundle getBundle(String messageId) { |
296 | |
297 | ResourceBundle bundle = null; |
298 | |
299 | if (finder != null) |
300 | bundle = finder.getBundle(messageId); |
301 | |
302 | if (bundle == null) { |
303 | bundle = MessageService.getBundleForLocale(Locale.getDefault(), messageId); |
304 | } |
305 | |
306 | return bundle; |
307 | } |
308 | |
309 | /** |
310 | Method to use instead of ResourceBundle.getBundle(). |
311 | This method acts like ResourceBundle.getBundle() but if |
312 | the resource is not available in the requested locale, |
313 | default locale or base class the one for en_US is returned. |
314 | */ |
315 | |
316 | public static ResourceBundle getBundleWithEnDefault(String resource, Locale locale) { |
317 | |
318 | try { |
319 | return ResourceBundle.getBundle(resource, locale); |
320 | } catch (MissingResourceException mre) { |
321 | |
322 | // This covers the case where neither the |
323 | // requested locale or the default locale |
324 | // have a resource. |
325 | |
326 | return ResourceBundle.getBundle(resource, EN); |
327 | } |
328 | } |
329 | |
330 | /** |
331 | Hash function to split messages into 50 files based |
332 | upon the message identifier or SQLState. We don't use |
333 | String.hashCode() as it varies between releases and |
334 | doesn't provide an even distribution across the 50 files. |
335 | |
336 | */ |
337 | public static int hashString50(String key) { |
338 | int hash = 0; |
339 | int len = key.length(); |
340 | if (len > 5) |
341 | len = 5; |
342 | |
343 | for (int i = 0; i < len; i++) { |
344 | hash += key.charAt(i); |
345 | } |
346 | hash = hash % 50; |
347 | return hash; |
348 | } |
349 | } |