16 */ |
16 */ |
17 |
17 |
18 |
18 |
19 package com.nokia.mj.impl.utils; |
19 package com.nokia.mj.impl.utils; |
20 |
20 |
21 import java.io.*; |
21 import com.nokia.mj.impl.coreui.CoreUi; |
22 import java.util.*; |
22 |
|
23 import java.util.Enumeration; |
|
24 import java.util.Hashtable; |
23 |
25 |
24 /** |
26 /** |
25 * Resource loader to get localised strings and Formatter patterns. |
27 * Resource loader to get localised strings and Formatter patterns. |
26 * <br> |
28 * <br> |
27 * Usage: |
29 * Usage: |
28 * <pre> |
30 * <pre> |
29 * ResourceLoader res = new ResourceLoader("javainstaller", "qtn_java_installer_"); |
31 * ResourceLoader res = ResourceLoader.getInstance("javainstaller", "qtn_java_installer_"); |
30 * Label subjectLabel = createLabel( |
32 * Label subjectLabel = createLabel( |
31 * res.format("subject").arg(certificate.getSubject()).toString(), |
33 * res.format("subject").arg(certificate.getSubject()).toString(), |
32 * horizontalSpan, labelStyle); |
34 * horizontalSpan, labelStyle); |
33 * </pre> |
35 * </pre> |
|
36 * <br> |
|
37 * It is possible to pass a comma separated list of text ids to format |
|
38 * method and also to pass a comma separated list of resource filenames |
|
39 * and text id prefixes to getInstance method. In this case the texts |
|
40 * are searched in the order they are listed and the first text |
|
41 * that is found will be returned. This can be used to specify texts |
|
42 * for different platforms in a single parameter, provided that the |
|
43 * text ids, prefixes and resource filenames are unique in each platform. |
|
44 * <br> |
|
45 * If the text ids, prefixes and resource filenames are not unique |
|
46 * in each platform, then a separate ResourceLoader instance must |
|
47 * be obtained for every platform. |
|
48 * <br> |
|
49 * When more than one resource filenames and text id prefixes |
|
50 * are used, the Nth prefix is applied only to the Nth resource file. |
|
51 * This means that the number of comma separated resource filenames and |
|
52 * prefixes must be the same. |
|
53 * <br> |
|
54 * If the localised text for given id is not found, ResourceLoader |
|
55 * returns the id itself. |
34 */ |
56 */ |
35 public class ResourceLoader |
57 abstract public class ResourceLoader |
36 { |
58 { |
37 /** AVKON UI identifier. */ |
59 /** AVKON UI identifier. */ |
38 public static final int AVKON = 1; |
60 protected static final int AVKON = 1; |
39 /** QT UI identifier. */ |
61 /** QT UI identifier. */ |
40 public static final int QT = 2; |
62 protected static final int QT = 2; |
41 |
63 |
42 /** Localisation resource basepath */ |
64 /** Separator in text id, resource filename, and prefix strings. */ |
43 private static final String LOC_RESOURCE_BASE = "/resources/com/nokia/mj/impl/"; |
65 protected static final String SEPARATOR = ","; |
44 |
66 |
45 /** Map for ResourceLoader instances. */ |
67 /** Map for ResourceLoader instances. */ |
46 private static Hashtable resourceLoaders = new Hashtable(); |
68 protected static Hashtable iResourceLoaders = new Hashtable(); |
47 |
69 |
48 /** Resource string map. Null if resource could not be loaded. */ |
70 /** |
49 private Hashtable resourceMap = new Hashtable(); |
71 * Flag telling if ResourceLoader has already ensured that |
50 |
72 * UI thread exists. |
51 /** Resource name prefix */ |
73 */ |
52 private String prefix = null; |
74 private static boolean iUiThreadExists = false; |
53 |
|
54 /** Platform localisation type. */ |
|
55 private int locType = -1; |
|
56 |
75 |
57 /*** ----------------------------- PUBLIC ------------------------------ */ |
76 /*** ----------------------------- PUBLIC ------------------------------ */ |
58 |
77 |
59 public static ResourceLoader getInstance(String avkonFileName, |
|
60 String avkonPrefix, |
|
61 String qtFileName, |
|
62 String qtPrefix) |
|
63 { |
|
64 // Construct key from filenames and prefixes, this is the same |
|
65 // between platforms. |
|
66 String key = (new StringBuffer()).append(avkonFileName).append(":") |
|
67 .append(avkonPrefix).append(":").append(qtFileName).append(":") |
|
68 .append(qtPrefix).toString(); |
|
69 ResourceLoader result = (ResourceLoader)resourceLoaders.get(key); |
|
70 |
|
71 if (result == null) |
|
72 { |
|
73 result = new ResourceLoader(avkonFileName, avkonPrefix, qtFileName, qtPrefix); |
|
74 resourceLoaders.put(key, result); |
|
75 } |
|
76 return result; |
|
77 } |
|
78 |
|
79 /** |
78 /** |
80 * Returns a resource loader instance. |
79 * Returns a resource loader instance. |
81 * |
80 * |
82 * @param resourceName name of the resource |
81 * @param avkonFileName name of the Avkon resource file |
83 * @param prefix prefix added before each id when retrieving |
82 * @param avkonPrefix prefix added before each id when retrieving |
|
83 * @param qtFileName name of the Qt resource file |
|
84 * @param qtPrefix prefix added before each id when retrieving |
84 * @return resource loader instance |
85 * @return resource loader instance |
85 */ |
86 */ |
86 public static ResourceLoader getInstance(String resourceName, String prefix) |
87 synchronized public static ResourceLoader getInstance( |
87 { |
88 String avkonFileName, String avkonPrefix, |
88 String key = resourceName + ":" + prefix; |
89 String qtFileName, String qtPrefix) |
89 ResourceLoader result = (ResourceLoader)resourceLoaders.get(key); |
90 { |
90 if (result == null) |
91 ResourceLoader rl = null; |
91 { |
92 if (getLocaleIdQt() == null || qtFileName == null) |
92 result = new ResourceLoader(resourceName, prefix); |
93 { |
93 resourceLoaders.put(key, result); |
94 rl = ResourceLoaderAvkon.getInstance(avkonFileName, avkonPrefix); |
94 } |
|
95 return result; |
|
96 } |
|
97 |
|
98 /** |
|
99 * Private constructor. Loads localisation resource file. |
|
100 * On Avkon UI it's resources are loaded. On Qt platfor it's |
|
101 * resource is first read and if that fails Avkon one is read. |
|
102 * |
|
103 * @param avkonFileName Avkon localisation resource file. |
|
104 * @param avkonPrefix Avkon logical string prefix. |
|
105 * @param qtFileName Qt localisation resource file. |
|
106 * @param qtPrefix Qt logical string prefix. |
|
107 */ |
|
108 private ResourceLoader(String avkonFileName, |
|
109 String avkonPrefix, |
|
110 String qtFileName, |
|
111 String qtPrefix) |
|
112 { |
|
113 String localeId = getLocaleIdQt(); |
|
114 |
|
115 if (localeId == null) |
|
116 { |
|
117 locType = AVKON; |
|
118 prefix = avkonPrefix; |
|
119 loadFile(avkonFileName, true); |
|
120 } |
95 } |
121 else |
96 else |
122 { |
97 { |
123 if (!loadFile(qtFileName, false)) |
98 try |
124 { |
99 { |
125 // Fallback to Avkon |
100 rl = ResourceLoaderQt.getInstance(qtFileName, qtPrefix); |
126 locType = AVKON; |
101 } |
127 prefix = avkonPrefix; |
102 catch (Throwable t) |
128 loadFile(avkonFileName, true); |
103 { |
129 } |
104 Logger.WLOG(Logger.EUtils, |
130 else |
105 "ResourceLoader: Creating ResourceLoaderQt for " + |
131 { |
106 qtFileName + " failed: " + t); |
132 locType = QT; |
107 } |
133 prefix = qtPrefix; |
108 if (rl == null) |
134 } |
109 { |
135 } |
110 rl = ResourceLoaderAvkon.getInstance( |
136 } |
111 avkonFileName, avkonPrefix, qtFileName, qtPrefix); |
137 |
112 } |
138 /** |
113 } |
139 * Creates resource loader, using the current locale of the environment. |
114 return rl; |
140 * |
115 } |
141 * @param resourceName name of the resource |
116 |
142 * @param aPrefix prefix added before each id when retrieving |
117 /** |
143 */ |
118 * Returns a resource loader instance. |
144 ResourceLoader(String resourceName, String aPrefix) |
119 * |
145 { |
120 * @param resourceName comma separated list of resource file names |
146 locType = AVKON; |
121 * @param prefix comma separated list of prefixes added before each |
147 prefix = aPrefix; |
122 * id when retrieving |
148 loadFile(resourceName, true); // Avkon |
123 * @return resource loader instance |
149 } |
124 */ |
|
125 synchronized public static ResourceLoader getInstance( |
|
126 String resourceName, String prefix) |
|
127 { |
|
128 if (getLocaleIdQt() == null) |
|
129 { |
|
130 return getInstance(resourceName, prefix, null, null); |
|
131 } |
|
132 else |
|
133 { |
|
134 return getInstance(resourceName, prefix, resourceName, prefix); |
|
135 } |
|
136 } |
|
137 |
|
138 /** |
|
139 * Get a string formatter of a given resource id. |
|
140 * |
|
141 * @param id comma separated list of resource ids |
|
142 * @return formatter instance |
|
143 * @see Formatter |
|
144 */ |
|
145 public abstract Formatter format(String id); |
150 |
146 |
151 /** |
147 /** |
152 * Get a string formatter of a given resource id. |
148 * Get a string formatter of a given resource id. |
153 * |
149 * |
154 * @param id resource id |
150 * @param id resource id |
155 * @return formatter instance |
151 * @return formatter instance |
156 * @see Formatter |
152 * @see Formatter |
157 */ |
153 */ |
158 public Formatter format(String id) |
154 public abstract Formatter format(Id id); |
159 { |
155 |
160 return new Formatter(string(id), locType); |
156 /** |
161 } |
157 * Formats localised text with specified parameters from an array. |
162 |
158 * |
163 /** |
159 * @param id comma separated list of resource ids |
164 * Get a string formatter of a given resource id. |
160 * @param textParameters parameters to be filled into the text |
165 * |
161 * @return localised text formatted with the provided parameters |
166 * @param id resource id |
162 * @see Formatter |
167 * @return formatter instance |
163 */ |
168 * @see Formatter |
164 public abstract String format(String id, Object[] textParameters); |
169 */ |
|
170 public Formatter format(Id id) |
|
171 { |
|
172 return new Formatter(string(id.getString(locType)), locType); |
|
173 } |
|
174 |
165 |
175 /** |
166 /** |
176 * Formats localised text with specified parameters from an array. |
167 * Formats localised text with specified parameters from an array. |
177 * |
168 * |
178 * @param id resource id |
169 * @param id resource id |
179 * @param textParameters parameters to be filled into the text |
170 * @param textParameters parameters to be filled into the text |
180 * @return localised text formatted with the provided parameters |
171 * @return localised text formatted with the provided parameters |
181 * @see Formatter |
172 * @see Formatter |
182 */ |
173 */ |
183 public String format(String id, Object[] textParameters) |
174 public abstract String format(Id id, Object[] textParameters); |
184 { |
175 |
185 return new Formatter(string(id), locType).format(textParameters); |
176 /** |
186 } |
177 * Gets the locale ID currently being used on the phone. |
187 |
178 * This can be used e.g. to load a localized icon file, by |
188 /** |
179 * adding the locale id as suffix. |
189 * Formats localised text with specified parameters from an array. |
|
190 * |
|
191 * @param id resource id |
|
192 * @param textParameters parameters to be filled into the text |
|
193 * @return localised text formatted with the provided parameters |
|
194 * @see Formatter |
|
195 */ |
|
196 public String format(Id id, Object[] textParameters) |
|
197 { |
|
198 return new Formatter(string(id.getString(locType)), locType).format(textParameters); |
|
199 } |
|
200 |
|
201 |
|
202 /** |
|
203 * Gets the locale ID currently being used on the phone. This can be used |
|
204 * e.g. to load a localized icon file, by adding the locale id as suffix. |
|
205 * |
180 * |
206 * @return Locale ID as provided by the platform |
181 * @return Locale ID as provided by the platform |
207 */ |
182 */ |
208 public String getLocaleId() |
183 public static String getLocaleId() |
209 { |
184 { |
210 int localeId = _getLocaleId(); |
185 int localeId = _getLocaleId(); |
211 if (localeId > 0) |
186 if (localeId > 0) |
212 { |
187 { |
213 if (localeId < 10) |
188 if (localeId < 10) |
228 * |
203 * |
229 * @return Qt Locale Id String, null if not in Qt. |
204 * @return Qt Locale Id String, null if not in Qt. |
230 */ |
205 */ |
231 public static String getLocaleIdQt() |
206 public static String getLocaleIdQt() |
232 { |
207 { |
|
208 if (!iUiThreadExists) |
|
209 { |
|
210 // Do something in UI thread to ensure that it exists |
|
211 // before _getLocaleIdQt() is called. If _getLocaleIdQt() |
|
212 // has been called before creating QApplication, |
|
213 // QApplication creation will panic. |
|
214 try |
|
215 { |
|
216 CoreUi.runInSyncUiThread(new Runnable() { |
|
217 public void run() |
|
218 { |
|
219 iUiThreadExists = true; |
|
220 } |
|
221 }); |
|
222 } |
|
223 catch (Throwable t) |
|
224 { |
|
225 // Assume that UI thread already exists. |
|
226 iUiThreadExists = true; |
|
227 } |
|
228 } |
233 return _getLocaleIdQt(); |
229 return _getLocaleIdQt(); |
234 } |
230 } |
235 |
231 |
|
232 /** |
|
233 * Releases resources and destroys all ResourceLoader instances. |
|
234 */ |
|
235 synchronized public static void destroyAll() |
|
236 { |
|
237 Enumeration e = iResourceLoaders.keys(); |
|
238 while (e.hasMoreElements()) |
|
239 { |
|
240 String key = (String)e.nextElement(); |
|
241 ResourceLoader rl = (ResourceLoader)iResourceLoaders.get(key); |
|
242 if (rl instanceof ResourceLoaderQt) |
|
243 { |
|
244 ((ResourceLoaderQt)rl).destroy(); |
|
245 } |
|
246 } |
|
247 iResourceLoaders.clear(); |
|
248 } |
236 |
249 |
237 /*** ----------------------------- PRIVATE ---------------------------- */ |
250 /*** ----------------------------- PRIVATE ---------------------------- */ |
238 |
251 |
239 /** |
252 /** |
240 * Get a plain string resource with a given resource id. |
253 * Default constructor. |
241 * |
254 */ |
242 * @param id resource id, either with prefix or without |
255 protected ResourceLoader() |
243 * @return resource string, or the id if does not exist |
256 { |
244 */ |
257 } |
245 private String string(String id) |
|
246 { |
|
247 String str = (String)resourceMap.get(id); |
|
248 if (str == null) |
|
249 { |
|
250 // Try with prefix |
|
251 str = (String)resourceMap.get(prefix + id); |
|
252 if (str == null) |
|
253 { |
|
254 // Not found even with prefix. Use the id itself |
|
255 if (!id.startsWith(prefix)) |
|
256 { |
|
257 str = prefix + id; |
|
258 } |
|
259 else |
|
260 { |
|
261 str = id; |
|
262 } |
|
263 |
|
264 Logger.WLOG(Logger.EUtils, "Cannot find resource: " + id); |
|
265 } |
|
266 |
|
267 // Put back to hash with original key for quick retrieval |
|
268 resourceMap.put(id, str); |
|
269 } |
|
270 |
|
271 str = decode(str); |
|
272 str = replaceCharacterCodes(str); |
|
273 |
|
274 return str; |
|
275 } |
|
276 |
|
277 /** |
|
278 * Loads the resources from .loc type file. |
|
279 * |
|
280 * @param resourceName name of the resource file. |
|
281 * @param aIs InputStream pointing to resource. It will be closed after use. |
|
282 * @param true if operation succeed. |
|
283 */ |
|
284 private boolean loadFile(String resourceName, boolean avkon) |
|
285 { |
|
286 InputStream is = null; |
|
287 |
|
288 if (!avkon) // Qt resources. |
|
289 { |
|
290 String langName = getLocaleIdQt(); |
|
291 |
|
292 // Emulator returns falsely en_GB as engineering English locale name. |
|
293 if (langName.equals("en_GB")) |
|
294 { |
|
295 langName = "en"; |
|
296 } |
|
297 |
|
298 // Load with real locale id |
|
299 is = this.getClass().getResourceAsStream( |
|
300 LOC_RESOURCE_BASE + resourceName + "_" + langName + ".loc"); |
|
301 |
|
302 if (is == null) |
|
303 { |
|
304 /* |
|
305 * Does not exist. No need to continue as avkon file cannot |
|
306 * found using qt name. |
|
307 */ |
|
308 return false; |
|
309 } |
|
310 } |
|
311 else // Avkon resources. |
|
312 { |
|
313 // Load with real locale id |
|
314 is = this.getClass().getResourceAsStream( |
|
315 LOC_RESOURCE_BASE + resourceName + "_" + getLocaleId() + ".loc"); |
|
316 |
|
317 if (is == null) |
|
318 { |
|
319 // Load the engineering english |
|
320 is = this.getClass().getResourceAsStream( |
|
321 LOC_RESOURCE_BASE + resourceName + "_sc" + ".loc"); |
|
322 } |
|
323 if (is == null) |
|
324 { |
|
325 // Load the reference engineering english |
|
326 is = this.getClass().getResourceAsStream( |
|
327 LOC_RESOURCE_BASE + resourceName + ".loc"); |
|
328 } |
|
329 if (is == null) |
|
330 { |
|
331 Logger.WLOG(Logger.EUtils, |
|
332 "Cannot load resource file: " + resourceName); |
|
333 return false; |
|
334 } |
|
335 } |
|
336 |
|
337 try |
|
338 { |
|
339 // Loc-files area always on UTF8 format |
|
340 LineReader lr = new LineReader( |
|
341 new BufferedReader(new InputStreamReader(is, "UTF-8"))); |
|
342 String line; |
|
343 |
|
344 while ((line = lr.readLine()) != null) |
|
345 { |
|
346 // Ignore lines which are not #define's |
|
347 if (!line.startsWith("#define ")) |
|
348 { |
|
349 continue; |
|
350 } |
|
351 try |
|
352 { |
|
353 // Skip "#define" + any whitespace |
|
354 line = line.substring(line.indexOf(' ')).trim(); |
|
355 |
|
356 int idEnd = line.indexOf(' '); |
|
357 String id = line.substring(0, idEnd); |
|
358 |
|
359 int strStart = line.indexOf('"', idEnd); |
|
360 int strEnd = line.lastIndexOf('"'); |
|
361 String str = line.substring(strStart + 1, strEnd); |
|
362 |
|
363 resourceMap.put(id, str); |
|
364 |
|
365 } |
|
366 catch (IndexOutOfBoundsException ex) |
|
367 { |
|
368 String error = "Incorrect line " + lr.getLineNumber() + "\"" + |
|
369 line + "\""; |
|
370 Logger.WLOG(Logger.EUtils, error); |
|
371 } |
|
372 } |
|
373 is.close(); |
|
374 |
|
375 } |
|
376 catch (IOException ex) |
|
377 { |
|
378 Logger.WLOG(Logger.EUtils, |
|
379 "Resource file " + resourceName + " handling failed: " |
|
380 + ex.getMessage()); |
|
381 } |
|
382 |
|
383 return true; |
|
384 } |
|
385 |
|
386 /** |
|
387 * Decode given string. Decoding means unescaping escaped characters. |
|
388 * Currently \n, \t, \', \\ and \" patterns are decoded to respective |
|
389 * characters. |
|
390 * |
|
391 * @param str to be decoded. |
|
392 * @return decoded String. |
|
393 */ |
|
394 private String decode(String str) |
|
395 { |
|
396 str = replacePattern(str, "\\n", '\n'); |
|
397 str = replacePattern(str, "\\\\", '\\'); |
|
398 str = replacePattern(str, "\\\"", '\"'); |
|
399 str = replacePattern(str, "\\t", '\t'); |
|
400 str = replacePattern(str, "\\'", '\''); |
|
401 |
|
402 return str; |
|
403 } |
|
404 |
|
405 /** |
|
406 * Replace all occurrences of the pattern in the given String. |
|
407 * |
|
408 * @param resource string to be replaced. |
|
409 * @param pattern to replace. |
|
410 * @param replacement replacement character. |
|
411 * @return String where all occurrences of the pattern are replaced. |
|
412 */ |
|
413 private String replacePattern( |
|
414 String resource, String pattern, char replacement) |
|
415 { |
|
416 StringBuffer sb = new StringBuffer(); |
|
417 |
|
418 int startIndex = resource.indexOf(pattern); |
|
419 if (startIndex != -1) |
|
420 { |
|
421 sb.append(resource.substring(0, startIndex)).append(replacement); |
|
422 startIndex = startIndex + pattern.length(); |
|
423 int endIndex = 0; |
|
424 |
|
425 while ((endIndex = resource.indexOf(pattern, startIndex)) != -1) |
|
426 { |
|
427 sb.append(resource.substring(startIndex, endIndex)) |
|
428 .append(replacement); |
|
429 startIndex = endIndex + pattern.length(); |
|
430 } |
|
431 |
|
432 if (startIndex < resource.length()) |
|
433 { |
|
434 sb.append(resource.substring(startIndex, resource.length())); |
|
435 } |
|
436 return sb.toString(); |
|
437 } |
|
438 return resource; |
|
439 } |
|
440 |
|
441 /** |
|
442 * Replace character codes. They are given as <0x0000> format. Where 0x0000 |
|
443 * contains character unicode as hex representation. |
|
444 * |
|
445 * @param str to replace characters. |
|
446 * @return String where characters are replaced. |
|
447 */ |
|
448 private String replaceCharacterCodes(String str) |
|
449 { |
|
450 StringBuffer sb = new StringBuffer(); |
|
451 int startIndex = str.indexOf("<0x"); |
|
452 |
|
453 if (startIndex != -1 && str.charAt(startIndex + 7) == '>') |
|
454 { |
|
455 sb.append(str.substring(0, startIndex)); |
|
456 try |
|
457 { |
|
458 int charint = Integer.parseInt( |
|
459 str.substring(startIndex + 3, startIndex + 7), 16); |
|
460 sb.append((char) charint); |
|
461 |
|
462 int endIndex = 0; |
|
463 startIndex+= 7; |
|
464 |
|
465 while ((endIndex = str.indexOf("<0x", startIndex)) != -1 |
|
466 && str.charAt(endIndex + 7) == '>') |
|
467 { |
|
468 sb.append(str.substring(startIndex + 1, endIndex)); |
|
469 |
|
470 charint = Integer.parseInt( |
|
471 str.substring(endIndex + 3, endIndex + 7), 16); |
|
472 sb.append((char) charint); |
|
473 startIndex = endIndex + 7; |
|
474 } |
|
475 } |
|
476 catch (NumberFormatException nfe) |
|
477 { |
|
478 Logger.ELOG(Logger.EUtils, |
|
479 "Cannot replace character from string: " + str); |
|
480 return str; |
|
481 } |
|
482 |
|
483 if (startIndex < str.length()) |
|
484 { |
|
485 sb.append(str.substring(startIndex + 1, str.length())); |
|
486 } |
|
487 return sb.toString(); |
|
488 } |
|
489 return str; |
|
490 } |
|
491 |
|
492 |
258 |
493 /*** ----------------------------- NATIVE ----------------------------- */ |
259 /*** ----------------------------- NATIVE ----------------------------- */ |
494 |
260 |
495 /** |
261 /** |
496 * Get device language code. |
262 * Get device language code. |
497 * |
263 * |
498 * @return languege code. |
264 * @return languege code. |
499 */ |
265 */ |
500 private native int _getLocaleId(); |
266 private static native int _getLocaleId(); |
501 |
267 |
502 /** |
268 /** |
503 * Get Locale Id on Qt platform. |
269 * Get Locale Id on Qt platform. |
504 * |
270 * |
505 * @return locale Id string. If not in Qt null. |
271 * @return locale Id string. If not in Qt null. |
506 */ |
272 */ |
507 private static native String _getLocaleIdQt(); |
273 private static native String _getLocaleIdQt(); |
508 |
|
509 } |
274 } |