001: /*
002: * Copyright (c) 2007, Sun Microsystems, Inc.
003: *
004: * All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions
008: * are met:
009: *
010: * * Redistributions of source code must retain the above copyright
011: * notice, this list of conditions and the following disclaimer.
012: * * Redistributions in binary form must reproduce the above copyright
013: * notice, this list of conditions and the following disclaimer in
014: * the documentation and/or other materials provided with the
015: * distribution.
016: * * Neither the name of Sun Microsystems, Inc. nor the names of its
017: * contributors may be used to endorse or promote products derived
018: * from this software without specific prior written permission.
019: *
020: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
021: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
022: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
023: * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
024: * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
025: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
026: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
027: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
028: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
029: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
030: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
031: */
032:
033: /*
034: * @(#)LocalizationSupport.java 1.0 04/05/18 @(#)
035: */
036: package l10ndemo;
037:
038: import java.io.IOException;
039: import java.io.InputStream;
040: import java.io.InputStreamReader;
041: import java.util.Hashtable;
042:
043: /**
044: * This class contains a localization support for MIDP applications
045: * Feel free to modify.
046: *
047: * @author breh
048: * @version 1.0
049: */
050: public class LocalizationSupport {
051:
052: /**************************************************************************
053: ****
054: **** Localization Support Begin
055: ****
056: **************************************************************************/
057:
058: /**
059: * Full path to the messages resource bundle. Feel free to change it if you don't
060: * use the message bundle file generated by IDE.
061: */
062: private static final String _MESSAGES_BUNDLE = "/l10ndemo/messages.properties";
063:
064: /**
065: * Error message used in the case there is any problem with initialization of
066: * localization support. Please note, the error message should contain one
067: * parameter sign - '{0}', which is used to fill in a reason of the failure.
068: */
069: private static final String _INIT_LOCALIZATION_ERROR_MSG = "Error when initializing localization support, reason: {0}";
070:
071: /**
072: * Default String is returned from getMessage() methods when there is any problem
073: * with finding the appropriate localized message or any part of it.
074: */
075: private static final String _DEFAULT_STRING = "???";
076:
077: /**
078: * Initializes localization support based on currently set locale (obtained
079: * from "microedition.locale" system property). The initialization method is called
080: * automatically when a call to {@link #getMessage(java.lang.String)} method is attempted for the first time.
081: *
082: *
083: * You can call this method explicitly to see whether there was any problem
084: * with initialization of the localization support. Method returns a status
085: * of the successfulness. If there was any problem with initialization, you can
086: * get reason by using {@link #getErrorMessage()} method.
087: * @return true if the intialization was succesfull, false if there was any problem.
088: */
089: public static boolean initLocalizationSupport() {
090: return initLocalizationSupport(System
091: .getProperty("microedition.locale")); // NOI18N
092: }
093:
094: /**
095: * Explicit initialization of the localization support. This method is usually
096: * called when a particular locale used in the application. E.g. the application
097: * contains only french messages (no default messages, only <CODE>messages_fr.properties</CODE>
098: * files is available), you should initialize the localization support (by calling
099: * <CODE>initLocalizationSupport("fr");</CODE>) before using {@link #getMessage(java.lang.String)} method for the first
100: * time.
101: *
102: * Method returns a status of the successfulness. If there was any problem with
103: * the initialization, you can get explanation by using {@link #getErrorMessage()}
104: * method.
105: * @param locale locale which will be used to determine which message file from bundle will be used
106: * @return true if the intialization was succesfull, false if there was any problem.
107: */
108: public static boolean initLocalizationSupport(String locale) {
109: InputStream in = null;
110: // need access to a class object - cannot use Object.class, because of MIDP1 bug
111: Class clazz = Runtime.getRuntime().getClass();
112: try {
113: // try to find localized resource first (in format ${name}_locale.${suffix})
114: if ((locale != null) && (locale.length() > 1)) {
115: int lastIndex = _MESSAGES_BUNDLE.lastIndexOf('.');
116: String prefix = _MESSAGES_BUNDLE
117: .substring(0, lastIndex);
118: String suffix = _MESSAGES_BUNDLE.substring(lastIndex);
119: // replace '-' with '_', some phones returns locales with
120: // '-' instead of '_'. For example Nokia or Motorola
121: locale = locale.replace('-', '_');
122: in = clazz.getResourceAsStream(prefix + "_" + locale
123: + suffix);
124: if (in == null) {
125: // if no localized resource is found or localization is available
126: // try broader???? locale (i.e. instead og en_US, try just en)
127: in = clazz.getResourceAsStream(prefix + "_"
128: + locale.substring(0, 2) + suffix);
129: }
130: }
131: if (in == null) {
132: // if not found or locale is not set, try default locale
133: in = clazz.getResourceAsStream(_MESSAGES_BUNDLE);
134: }
135: if (in == null) {
136: // no messages bundle was found - initialization failed
137: _localizationErrorMessage = _processPattern(
138: _INIT_LOCALIZATION_ERROR_MSG,
139: new Object[] { "No messages found" }); // NOI18N
140: } else {
141: // load messages to _messageTable hashtable
142: _messageTable = new Hashtable();
143: _loadMessages(in);
144: // we are ok - return true as success ...
145: return true;
146: }
147: } catch (Exception e) {
148: // houston we have a problem
149: _localizationErrorMessage = _processPattern(
150: _INIT_LOCALIZATION_ERROR_MSG, new Object[] { e
151: .getMessage() });
152: }
153: return false;
154: }
155:
156: /**
157: * Returns an error message if there was any problem with accessing the localized
158: * text. The message also possibly explainins a reason of the failure. The message
159: * is taken from <CODE>_INIT_LOCALIZATION_ERROR_MSG</CODE>.
160: * @return error message if there was any failure or null when everything is OK.
161: */
162: public static String getErrorMessage() {
163: return _localizationErrorMessage;
164: }
165:
166: /**
167: * Finds a localized string in a message bundle.
168: * @param key key of the localized string to look for
169: * @return the localized string. If key is not found, then <CODE>_DEFAULT_STRING</CODE> string
170: * is returned
171: */
172: public static final String getMessage(String key) {
173: return getMessage(key, null);
174: }
175:
176: /**
177: * Finds a localized string in a message bundle and formats the message by passing
178: * requested parameters.
179: * @param key key of the localized string to look for
180: * @param args array of arguments to use for formatting the message
181: * @return the localized string. If key is not found, then <CODE>_DEFAULT_STRING</CODE> string
182: * is returned
183: */
184: public static final String getMessage(String key, Object[] args) {
185: if (_messageTable == null) {
186: if (!initLocalizationSupport()) {
187: return _DEFAULT_STRING;
188: }
189: }
190: StringBuffer toAppendTo = new StringBuffer();
191: String s = (String) _messageTable.get(key);
192: if (s == null)
193: return _DEFAULT_STRING;
194: int l = s.length();
195: int n = 0, lidx = -1, lastidx = 0;
196: for (int i = 0; i < l; i++) {
197: if (s.charAt(i) == '{') {
198: n++;
199: if (n == 1) {
200: lidx = i;
201: toAppendTo.append(s.substring(lastidx, i));
202: lastidx = i;
203: }
204: }
205: if (s.charAt(i) == '}') {
206: if (n == 1) {
207: toAppendTo.append(_processPattern(s.substring(
208: lidx + 1, i), args));
209: lidx = -1;
210: lastidx = i + 1;
211: }
212: n--;
213: }
214: }
215: if (n > 0) {
216: toAppendTo.append(_processPattern(s.substring(lidx + 1),
217: args));
218: } else {
219: toAppendTo.append(s.substring(lastidx));
220: }
221:
222: return toAppendTo.toString();
223: }
224:
225: /* The rest is private to localization support. You shouldn't change anything
226: * below this comment unless you really know what you are doing
227: * Ideally, everyhthing below this should be collapsed.
228: */
229:
230: /**
231: * Characters separating keys and values
232: */
233: private static final String _KEY_VALUE_SEPARATORS = "=: \t\r\n\f";
234: /**
235: * Characters strictly separating keys and values
236: */
237: private static final String _STRICT_KEY_VALUE_SEPARTORS = "=:";
238: /**
239: * white space characters understood by the support (these can be in the message file)
240: */
241: private static final String _WHITESPACE_CHARS = " \t\r\n\f";
242:
243: /**
244: * Contains the parsed message bundle.
245: */
246: private static Hashtable _messageTable;
247: /**
248: * Contains an error message if there was any problem with localization support.
249: * If everything is OK, this field is null.
250: */
251: private static String _localizationErrorMessage = null;
252:
253: /**
254: * Loads messages from input stream to hash table.
255: * @param inStream stream from which the messages are read
256: * @throws IOException if there is any problem with reading the messages
257: */
258: private static synchronized void _loadMessages(InputStream inStream)
259: throws IOException {
260:
261: InputStreamReader in = new InputStreamReader(inStream);
262: while (true) {
263: // Get next line
264: String line = _readLine(in);
265: if (line == null)
266: return;
267:
268: if (line.length() > 0) {
269:
270: // Find start of key
271: int len = line.length();
272: int keyStart;
273: for (keyStart = 0; keyStart < len; keyStart++)
274: if (_WHITESPACE_CHARS
275: .indexOf(line.charAt(keyStart)) == -1)
276: break;
277:
278: // Blank lines are ignored
279: if (keyStart == len)
280: continue;
281:
282: // Continue lines that end in slashes if they are not comments
283: char firstChar = line.charAt(keyStart);
284: if ((firstChar != '#') && (firstChar != '!')) {
285: while (_continueLine(line)) {
286: String nextLine = _readLine(in);
287: if (nextLine == null)
288: nextLine = "";
289: String loppedLine = line.substring(0, len - 1);
290: // Advance beyond whitespace on new line
291: int startIndex;
292: for (startIndex = 0; startIndex < nextLine
293: .length(); startIndex++)
294: if (_WHITESPACE_CHARS.indexOf(nextLine
295: .charAt(startIndex)) == -1)
296: break;
297: nextLine = nextLine.substring(startIndex,
298: nextLine.length());
299: line = new String(loppedLine + nextLine);
300: len = line.length();
301: }
302:
303: // Find separation between key and value
304: int separatorIndex;
305: for (separatorIndex = keyStart; separatorIndex < len; separatorIndex++) {
306: char currentChar = line.charAt(separatorIndex);
307: if (currentChar == '\\')
308: separatorIndex++;
309: else if (_KEY_VALUE_SEPARATORS
310: .indexOf(currentChar) != -1)
311: break;
312: }
313:
314: // Skip over whitespace after key if any
315: int valueIndex;
316: for (valueIndex = separatorIndex; valueIndex < len; valueIndex++)
317: if (_WHITESPACE_CHARS.indexOf(line
318: .charAt(valueIndex)) == -1)
319: break;
320:
321: // Skip over one non whitespace key value separators if any
322: if (valueIndex < len)
323: if (_STRICT_KEY_VALUE_SEPARTORS.indexOf(line
324: .charAt(valueIndex)) != -1)
325: valueIndex++;
326:
327: // Skip over white space after other separators if any
328: while (valueIndex < len) {
329: if (_WHITESPACE_CHARS.indexOf(line
330: .charAt(valueIndex)) == -1)
331: break;
332: valueIndex++;
333: }
334: String key = line.substring(keyStart,
335: separatorIndex);
336: String value = (separatorIndex < len) ? line
337: .substring(valueIndex, len) : "";
338:
339: // Convert then store key and value
340: key = _convertString(key);
341: value = _convertString(value);
342: _messageTable.put(key, value);
343: }
344: }
345: }
346:
347: }
348:
349: /**
350: * reads a single line from InputStreamReader
351: * @param in InputStreamReader used to read the line
352: * @throws IOException if there is any problem with reading
353: * @return the read line
354: */
355: private static String _readLine(InputStreamReader in)
356: throws IOException {
357: StringBuffer strBuf = new StringBuffer("");
358: int i;
359: while ((i = in.read()) != -1) {
360: if ((char) i == '\r' || (char) i == '\n')
361: return strBuf.toString();
362: strBuf.append((char) i);
363: }
364: return strBuf.length() > 0 ? strBuf.toString() : null;
365: }
366:
367: /**
368: * determines whether the line of the supplied string continues on the next line
369: * @param line a line of String
370: * @return true if the string contines on the next line, false otherwise
371: */
372: private static boolean _continueLine(String line) {
373: int slashCount = 0;
374: int index = line.length() - 1;
375: while ((index >= 0) && (line.charAt(index--) == '\\'))
376: slashCount++;
377: return (slashCount % 2 == 1);
378: }
379:
380: /**
381: * Decodes a String which uses unicode characters in \\uXXXX format.
382: * @param theString String with \\uXXXX characters
383: * @return resolved string
384: */
385: private static String _convertString(String theString) {
386: char aChar;
387: int len = theString.length();
388: StringBuffer outBuffer = new StringBuffer(len);
389:
390: for (int x = 0; x < len;) {
391: aChar = theString.charAt(x++);
392: if (aChar == '\\') {
393: aChar = theString.charAt(x++);
394: if (aChar == 'u') {
395: // Read the xxxx
396: int value = 0;
397: for (int i = 0; i < 4; i++) {
398: aChar = theString.charAt(x++);
399: switch (aChar) {
400: case '0':
401: case '1':
402: case '2':
403: case '3':
404: case '4':
405: case '5':
406: case '6':
407: case '7':
408: case '8':
409: case '9':
410: value = (value << 4) + aChar - '0';
411: break;
412: case 'a':
413: case 'b':
414: case 'c':
415: case 'd':
416: case 'e':
417: case 'f':
418: value = (value << 4) + 10 + aChar - 'a';
419: break;
420: case 'A':
421: case 'B':
422: case 'C':
423: case 'D':
424: case 'E':
425: case 'F':
426: value = (value << 4) + 10 + aChar - 'A';
427: break;
428: default:
429: // return DEFAULT STRING if there is any problem
430: return _DEFAULT_STRING;
431: }
432: }
433: outBuffer.append((char) value);
434: } else {
435: if (aChar == 't')
436: aChar = '\t';
437: else if (aChar == 'r')
438: aChar = '\r';
439: else if (aChar == 'n')
440: aChar = '\n';
441: else if (aChar == 'f')
442: aChar = '\f';
443: outBuffer.append(aChar);
444: }
445: } else {
446: outBuffer.append(aChar);
447: }
448: }
449: return outBuffer.toString();
450: }
451:
452: /**
453: * Extracts N-th from an array of argumens.
454: * @param indexString a String number
455: * @param args array of arguments
456: * @return the indexString-th parameter from the array
457: */
458: private static String _processPattern(String indexString,
459: Object[] args) {
460: try {
461: int index = Integer.parseInt(indexString);
462: if ((args != null) && (index >= 0) && (index < args.length)) {
463: if (args[index] != null) {
464: return args[index].toString();
465: }
466: }
467: } catch (NumberFormatException nfe) {
468: // NFE - nothing bad basically - the argument is not a number
469: // swallow it for the time being and show default string
470: }
471: return _DEFAULT_STRING;
472: }
473:
474: /**************************************************************************
475: ****
476: **** Localization Support End
477: ****
478: **************************************************************************/
479: }
|