001: /*
002:
003: Derby - Class org.apache.derby.iapi.services.i18n.MessageService
004:
005: Licensed to the Apache Software Foundation (ASF) under one or more
006: contributor license agreements. See the NOTICE file distributed with
007: this work for additional information regarding copyright ownership.
008: The ASF licenses this file to you under the Apache License, Version 2.0
009: (the "License"); you may not use this file except in compliance with
010: the License. You may obtain a copy of the License at
011:
012: http://www.apache.org/licenses/LICENSE-2.0
013:
014: Unless required by applicable law or agreed to in writing, software
015: distributed under the License is distributed on an "AS IS" BASIS,
016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: See the License for the specific language governing permissions and
018: limitations under the License.
019:
020: */
021:
022: package org.apache.derby.iapi.services.i18n;
023:
024: import org.apache.derby.iapi.services.info.JVMInfo;
025: import org.apache.derby.iapi.services.context.ShutdownException;
026:
027: import java.util.Locale;
028: import java.util.MissingResourceException;
029: import java.util.ResourceBundle;
030: import java.text.MessageFormat;
031:
032: /**
033: * Message Service implementation provides a mechanism for locating
034: * messages and substituting arguments for message parameters.
035: * It also provides a service for locating property values.
036: * <p>
037: * It uses the resource bundle mechanism for locating messages based on
038: * keys; the preferred form of resource bundle is a property file mapping
039: * keys to messages.
040: *
041: * @author ames
042: */
043: public final class MessageService {
044:
045: private static final Locale EN = new Locale("en", "US");
046:
047: private static BundleFinder finder;
048:
049: private MessageService() {
050: }
051:
052: public static ResourceBundle getBundleForLocale(Locale locale,
053: String msgId) {
054: try {
055: return MessageService.getBundleWithEnDefault(
056: "org.apache.derby.loc.m" + hashString50(msgId),
057: locale);
058: } catch (MissingResourceException mre) {
059: }
060: return null;
061: }
062:
063: public static Object setFinder(BundleFinder theFinder) {
064: finder = theFinder;
065:
066: // Return an object for a caller to hang onto so
067: // Garbage collection doesn't GC this class.
068: return new MessageService().getClass();
069: }
070:
071: public static String getTextMessage(String messageID) {
072: return getCompleteMessage(messageID, (Object[]) null);
073: }
074:
075: public static String getTextMessage(String messageID, Object a1) {
076:
077: return getCompleteMessage(messageID, new Object[] { a1 });
078: }
079:
080: public static String getTextMessage(String messageID, Object a1,
081: Object a2) {
082: return getCompleteMessage(messageID, new Object[] { a1, a2 });
083: }
084:
085: public static String getTextMessage(String messageID, Object a1,
086: Object a2, Object a3) {
087: return getCompleteMessage(messageID,
088: new Object[] { a1, a2, a3 });
089: }
090:
091: public static String getTextMessage(String messageID, Object a1,
092: Object a2, Object a3, Object a4) {
093: return getCompleteMessage(messageID, new Object[] { a1, a2, a3,
094: a4 });
095: }
096:
097: /**
098: Transform the message from messageID to the actual error, warning, or
099: info message using the correct locale.
100:
101: <P>
102: The arguments to the messages are passed via an object array, the objects
103: in the array WILL be changed by this class. The caller should NOT get the
104: object back from this array.
105:
106: */
107: public static String getCompleteMessage(String messageId,
108: Object[] arguments) {
109:
110: try {
111: return formatMessage(getBundle(messageId), messageId,
112: arguments, true);
113: } catch (MissingResourceException mre) {
114: // message does not exist in the requested locale or the default locale.
115: // most likely it does exist in our fake base class _en, so try that.
116: } catch (ShutdownException se) {
117: }
118: return formatMessage(getBundleForLocale(EN, messageId),
119: messageId, arguments, false);
120: }
121:
122: /**
123: Method used by Cloudscape Network Server to get localized message (original call
124: from jcc.
125:
126: @param sqlcode sqlcode, not used.
127: @param errmcLen sqlerrmc length
128: @param sqlerrmc sql error message tokens, variable part of error message (ie.,
129: arguments) plus messageId, separated by separator.
130: @param sqlerrp not used
131: @param errd0 not used
132: @param warn not used
133: @param sqlState 5-char sql state
134: @param file not used
135: @param localeStr client locale in string
136: @param msg OUTPUT parameter, localized error message
137: @param rc OUTPUT parameter, return code -- 0 for success
138: */
139: public static void getLocalizedMessage(int sqlcode, short errmcLen,
140: String sqlerrmc, String sqlerrp, int errd0, int errd1,
141: int errd2, int errd3, int errd4, int errd5, String warn,
142: String sqlState, String file, String localeStr,
143: String[] msg, int[] rc) {
144: //figure out client locale from input locale string
145:
146: int _pos1 = localeStr.indexOf("_"); // "_" position
147: int _pos2 = localeStr.lastIndexOf("_");
148:
149: Locale locale = EN; //default locale
150: if (_pos1 != -1) {
151: String language = localeStr.substring(0, _pos1);
152: if (_pos2 == _pos1) {
153: String country = localeStr.substring(_pos1 + 1);
154: locale = new Locale(language, country);
155: } else {
156: String country = localeStr.substring(_pos1 + 1, _pos2);
157: String variant = localeStr.substring(_pos2 + 1);
158: locale = new Locale(language, country, variant);
159: }
160: }
161:
162: // get messageId and arguments, messageId is necessary for us to look up
163: // localized message from property file. messageId was sent as the last
164: // token in the sqlerrmc.
165:
166: String messageId = sqlState; //use sqlState if we don't have messageId
167: Object[] arguments = null;
168: if (sqlerrmc != null && sqlerrmc.length() > 0) {
169: char[] sqlerrmc_chars = sqlerrmc.toCharArray();
170: int numArgs = 0, lastSepIdx = -1; // last separator index
171: for (int i = 0; i < sqlerrmc_chars.length; i++) {
172: if (sqlerrmc_chars[i] == 20) // separator
173: {
174: numArgs++;
175: lastSepIdx = i;
176: }
177: }
178: if (numArgs == 0)
179: messageId = new String(sqlerrmc_chars); //no args, only messageId then
180: else {
181: messageId = new String(sqlerrmc_chars, lastSepIdx + 1,
182: sqlerrmc_chars.length - lastSepIdx - 1);
183: arguments = new Object[numArgs];
184: for (int start = 0, arg = 0, i = 0; i < lastSepIdx + 1; i++) {
185: if (i == lastSepIdx || sqlerrmc_chars[i] == 20) // delimiter
186: {
187: arguments[arg++] = new String(sqlerrmc_chars,
188: start, i - start);
189: start = i + 1;
190: }
191: }
192: }
193: }
194:
195: try {
196: msg[0] = formatMessage(
197: getBundleForLocale(locale, messageId), messageId,
198: arguments, true);
199: rc[0] = 0;
200: return;
201: } catch (MissingResourceException mre) {
202: // message does not exist in the requested locale
203: // most likely it does exist in our fake base class _en, so try that.
204: } catch (ShutdownException se) {
205: }
206: msg[0] = formatMessage(getBundleForLocale(EN, messageId),
207: messageId, arguments, false);
208: rc[0] = 0;
209: }
210:
211: /**
212: Method used by Cloudscape Network Server to get localized message
213:
214: @param locale locale
215: @param messageId message id
216: @param args message arguments
217: */
218: public static String getLocalizedMessage(Locale locale,
219: String messageId, Object[] args) {
220: String locMsg = null;
221:
222: try {
223: locMsg = formatMessage(
224: getBundleForLocale(locale, messageId), messageId,
225: args, true);
226: return locMsg;
227: } catch (MissingResourceException mre) {
228: // message does not exist in the requested locale
229: // most likely it does exist in our fake base class _en, so try that.
230: } catch (ShutdownException se) {
231: }
232: locMsg = formatMessage(getBundleForLocale(EN, messageId),
233: messageId, args, false);
234: return locMsg;
235: }
236:
237: /**
238: */
239: public static String getProperty(String messageId,
240: String propertyName) {
241:
242: ResourceBundle bundle = getBundle(messageId);
243:
244: try {
245: if (bundle != null)
246: return bundle.getString(messageId.concat(".").concat(
247: propertyName));
248: } catch (MissingResourceException mre) {
249: }
250: return null;
251: }
252:
253: //
254: // class implementation
255: //
256: public static String formatMessage(ResourceBundle bundle,
257: String messageId, Object[] arguments, boolean lastChance) {
258:
259: if (arguments == null)
260: arguments = new Object[0];
261:
262: if (bundle != null) {
263:
264: try {
265: messageId = bundle.getString(messageId);
266:
267: try {
268: return MessageFormat.format(messageId, arguments);
269: } catch (IllegalArgumentException iae) {
270: } catch (NullPointerException npe) {
271: //
272: //null arguments cause a NullPointerException.
273: //This improves reporting.
274: }
275:
276: } catch (MissingResourceException mre) {
277: // caller will try and handle the last chance
278: if (lastChance)
279: throw mre;
280: }
281: }
282:
283: if (messageId == null)
284: messageId = "UNKNOWN";
285:
286: StringBuffer sb = new StringBuffer(messageId);
287:
288: sb.append(" : ");
289: int len = arguments.length;
290:
291: for (int i = 0; i < len; i++) {
292: // prepend a comma to all but the first
293: if (i > 0)
294: sb.append(", ");
295:
296: sb.append('[');
297: sb.append(i);
298: sb.append("] ");
299: if (arguments[i] == null)
300: sb.append("null");
301: else
302: sb.append(arguments[i].toString());
303: }
304:
305: return sb.toString();
306: }
307:
308: private static ResourceBundle getBundle(String messageId) {
309:
310: ResourceBundle bundle = null;
311:
312: if (finder != null)
313: bundle = finder.getBundle(messageId);
314:
315: if (bundle == null) {
316: bundle = MessageService.getBundleForLocale(Locale
317: .getDefault(), messageId);
318: }
319:
320: return bundle;
321: }
322:
323: /**
324: Method to use instead of ResourceBundle.getBundle().
325: This method acts like ResourceBundle.getBundle() but if
326: the resource is not available in the requested locale,
327: default locale or base class the one for en_US is returned.
328: */
329:
330: public static ResourceBundle getBundleWithEnDefault(
331: String resource, Locale locale) {
332:
333: try {
334: return ResourceBundle.getBundle(resource, locale);
335: } catch (MissingResourceException mre) {
336:
337: // This covers the case where neither the
338: // requested locale or the default locale
339: // have a resource.
340:
341: return ResourceBundle.getBundle(resource, EN);
342: }
343: }
344:
345: /**
346: Hash function to split messages into 50 files based
347: upon the message identifier or SQLState. We don't use
348: String.hashCode() as it varies between releases and
349: doesn't provide an even distribution across the 50 files.
350:
351: */
352: public static int hashString50(String key) {
353: int hash = 0;
354: int len = key.length();
355: if (len > 5)
356: len = 5;
357:
358: for (int i = 0; i < len; i++) {
359: hash += key.charAt(i);
360: }
361: hash = hash % 50;
362: return hash;
363: }
364: }
|