javacommons/utils/javasrc/com/nokia/mj/impl/utils/ResourceLoader.java
changeset 80 d6dafc5d983f
parent 72 1f0034e370aa
equal deleted inserted replaced
78:71ad690e91f5 80:d6dafc5d983f
    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 }