javacommons/utils/javasrc/com/nokia/mj/impl/utils/ResourceLoader.java
changeset 80 d6dafc5d983f
parent 72 1f0034e370aa
--- a/javacommons/utils/javasrc/com/nokia/mj/impl/utils/ResourceLoader.java	Mon Oct 04 11:29:25 2010 +0300
+++ b/javacommons/utils/javasrc/com/nokia/mj/impl/utils/ResourceLoader.java	Fri Oct 15 12:29:39 2010 +0300
@@ -18,135 +18,131 @@
 
 package com.nokia.mj.impl.utils;
 
-import java.io.*;
-import java.util.*;
+import com.nokia.mj.impl.coreui.CoreUi;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
 
 /**
  * Resource loader to get localised strings and Formatter patterns.
  * <br>
  * Usage:
  * <pre>
- *   ResourceLoader res = new ResourceLoader("javainstaller", "qtn_java_installer_");
+ *   ResourceLoader res = ResourceLoader.getInstance("javainstaller", "qtn_java_installer_");
  *   Label subjectLabel = createLabel(
  *       res.format("subject").arg(certificate.getSubject()).toString(),
  *       horizontalSpan, labelStyle);
  * </pre>
+ * <br>
+ * It is possible to pass a comma separated list of text ids to format
+ * method and also to pass a comma separated list of resource filenames
+ * and text id prefixes to getInstance method. In this case the texts
+ * are searched in the order they are listed and the first text
+ * that is found will be returned. This can be used to specify texts
+ * for different platforms in a single parameter, provided that the
+ * text ids, prefixes and resource filenames are unique in each platform.
+ * <br>
+ * If the text ids, prefixes and resource filenames are not unique
+ * in each platform, then a separate ResourceLoader instance must
+ * be obtained for every platform.
+ * <br>
+ * When more than one resource filenames and text id prefixes
+ * are used, the Nth prefix is applied only to the Nth resource file.
+ * This means that the number of comma separated resource filenames and
+ * prefixes must be the same.
+ * <br>
+ * If the localised text for given id is not found, ResourceLoader
+ * returns the id itself.
  */
-public class ResourceLoader
+abstract public class ResourceLoader
 {
     /** AVKON UI identifier. */
-    public static final int AVKON = 1;
+    protected static final int AVKON = 1;
     /** QT UI identifier. */
-    public static final int QT = 2;
+    protected static final int QT = 2;
 
-    /** Localisation resource basepath */
-    private static final String LOC_RESOURCE_BASE = "/resources/com/nokia/mj/impl/";
+    /** Separator in text id, resource filename, and prefix strings. */
+    protected static final String SEPARATOR = ",";
 
     /** Map for ResourceLoader instances. */
-    private static Hashtable resourceLoaders = new Hashtable();
-
-    /** Resource string map. Null if resource could not be loaded. */
-    private Hashtable resourceMap = new Hashtable();
+    protected static Hashtable iResourceLoaders = new Hashtable();
 
-    /** Resource name prefix */
-    private String prefix = null;
-
-    /** Platform localisation type. */
-    private int locType = -1;
+    /**
+     * Flag telling if ResourceLoader has already ensured that
+     * UI thread exists.
+     */
+    private static boolean iUiThreadExists = false;
 
     /*** ----------------------------- PUBLIC ------------------------------ */
 
-    public static ResourceLoader getInstance(String avkonFileName,
-                                             String avkonPrefix,
-                                             String qtFileName,
-                                             String qtPrefix)
+    /**
+     * Returns a resource loader instance.
+     *
+     * @param avkonFileName name of the Avkon resource file
+     * @param avkonPrefix prefix added before each id when retrieving
+     * @param qtFileName name of the Qt resource file
+     * @param qtPrefix prefix added before each id when retrieving
+     * @return resource loader instance
+     */
+    synchronized public static ResourceLoader getInstance(
+        String avkonFileName, String avkonPrefix,
+        String qtFileName, String qtPrefix)
     {
-        // Construct key from filenames and prefixes, this is the same
-        // between platforms.
-        String key = (new StringBuffer()).append(avkonFileName).append(":")
-            .append(avkonPrefix).append(":").append(qtFileName).append(":")
-            .append(qtPrefix).toString();
-        ResourceLoader result = (ResourceLoader)resourceLoaders.get(key);
-
-        if (result == null)
+        ResourceLoader rl = null;
+        if (getLocaleIdQt() == null || qtFileName == null)
+        {
+            rl = ResourceLoaderAvkon.getInstance(avkonFileName, avkonPrefix);
+        }
+        else
         {
-            result = new ResourceLoader(avkonFileName, avkonPrefix, qtFileName, qtPrefix);
-            resourceLoaders.put(key, result);
+            try
+            {
+                rl = ResourceLoaderQt.getInstance(qtFileName, qtPrefix);
+            }
+            catch (Throwable t)
+            {
+                Logger.WLOG(Logger.EUtils,
+                            "ResourceLoader: Creating ResourceLoaderQt for " +
+                            qtFileName + " failed: " + t);
+            }
+            if (rl == null)
+            {
+                rl = ResourceLoaderAvkon.getInstance(
+                    avkonFileName, avkonPrefix, qtFileName, qtPrefix);
+            }
         }
-        return result;
+        return rl;
     }
 
     /**
      * Returns a resource loader instance.
      *
-     * @param resourceName name of the resource
-     * @param prefix prefix added before each id when retrieving
+     * @param resourceName comma separated list of resource file names
+     * @param prefix comma separated list of prefixes added before each
+     * id when retrieving
      * @return resource loader instance
      */
-    public static ResourceLoader getInstance(String resourceName, String prefix)
+    synchronized public static ResourceLoader getInstance(
+        String resourceName, String prefix)
     {
-        String key = resourceName + ":" + prefix;
-        ResourceLoader result = (ResourceLoader)resourceLoaders.get(key);
-        if (result == null)
+        if (getLocaleIdQt() == null)
         {
-            result = new ResourceLoader(resourceName, prefix);
-            resourceLoaders.put(key, result);
-        }
-        return result;
-    }
-
-    /**
-     * Private constructor. Loads localisation resource file.
-     * On Avkon UI it's resources are loaded. On Qt platfor it's
-     * resource is first read and if that fails Avkon one is read.
-     *
-     * @param avkonFileName Avkon localisation resource file.
-     * @param avkonPrefix   Avkon logical string prefix.
-     * @param qtFileName    Qt localisation resource file.
-     * @param qtPrefix      Qt logical string prefix.
-     */
-    private ResourceLoader(String avkonFileName,
-                           String avkonPrefix,
-                           String qtFileName,
-                           String qtPrefix)
-    {
-        String localeId = getLocaleIdQt();
-
-        if (localeId == null)
-        {
-            locType = AVKON;
-            prefix = avkonPrefix;
-            loadFile(avkonFileName, true);
+            return getInstance(resourceName, prefix, null, null);
         }
         else
         {
-            if (!loadFile(qtFileName, false))
-            {
-                // Fallback to Avkon
-                locType = AVKON;
-                prefix = avkonPrefix;
-                loadFile(avkonFileName, true);
-            }
-            else
-            {
-                locType = QT;
-                prefix = qtPrefix;
-            }
+            return getInstance(resourceName, prefix, resourceName, prefix);
         }
     }
 
     /**
-     * Creates resource loader, using the current locale of the environment.
+     * Get a string formatter of a given resource id.
      *
-     * @param resourceName name of the resource
-     * @param aPrefix prefix added before each id when retrieving
+     * @param id comma separated list of resource ids
+     * @return formatter instance
+     * @see Formatter
      */
-    ResourceLoader(String resourceName, String aPrefix)
-    {
-        locType = AVKON;
-        prefix = aPrefix;
-        loadFile(resourceName, true);  // Avkon
-    }
+    public abstract Formatter format(String id);
 
     /**
      * Get a string formatter of a given resource id.
@@ -155,22 +151,17 @@
      * @return formatter instance
      * @see Formatter
      */
-    public Formatter format(String id)
-    {
-        return new Formatter(string(id), locType);
-    }
+    public abstract Formatter format(Id id);
 
     /**
-     * Get a string formatter of a given resource id.
+     * Formats localised text with specified parameters from an array.
      *
-     * @param id resource id
-     * @return formatter instance
+     * @param id comma separated list of resource ids
+     * @param textParameters parameters to be filled into the text
+     * @return localised text formatted with the provided parameters
      * @see Formatter
      */
-    public Formatter format(Id id)
-    {
-        return new Formatter(string(id.getString(locType)), locType);
-    }
+    public abstract String format(String id, Object[] textParameters);
 
     /**
      * Formats localised text with specified parameters from an array.
@@ -180,32 +171,16 @@
      * @return localised text formatted with the provided parameters
      * @see Formatter
      */
-    public String format(String id, Object[] textParameters)
-    {
-        return new Formatter(string(id), locType).format(textParameters);
-    }
+    public abstract String format(Id id, Object[] textParameters);
 
     /**
-     * Formats localised text with specified parameters from an array.
-     *
-     * @param id resource id
-     * @param textParameters parameters to be filled into the text
-     * @return localised text formatted with the provided parameters
-     * @see Formatter
-     */
-    public String format(Id id, Object[] textParameters)
-    {
-        return new Formatter(string(id.getString(locType)), locType).format(textParameters);
-    }
-
-
-    /**
-     * Gets the locale ID currently being used on the phone. This can be used
-     * e.g. to load a localized icon file, by adding the locale id as suffix.
+     * Gets the locale ID currently being used on the phone.
+     * This can be used e.g. to load a localized icon file, by
+     * adding the locale id as suffix.
      *
      * @return Locale ID as provided by the platform
      */
-    public String getLocaleId()
+    public static String getLocaleId()
     {
         int localeId = _getLocaleId();
         if (localeId > 0)
@@ -230,266 +205,57 @@
      */
     public static String getLocaleIdQt()
     {
+        if (!iUiThreadExists)
+        {
+            // Do something in UI thread to ensure that it exists
+            // before _getLocaleIdQt() is called. If _getLocaleIdQt()
+            // has been called before creating QApplication,
+            // QApplication creation will panic.
+            try
+            {
+                CoreUi.runInSyncUiThread(new Runnable() {
+                    public void run()
+                    {
+                        iUiThreadExists = true;
+                    }
+                });
+            }
+            catch (Throwable t)
+            {
+                // Assume that UI thread already exists.
+                iUiThreadExists = true;
+            }
+        }
         return _getLocaleIdQt();
     }
 
+    /**
+     * Releases resources and destroys all ResourceLoader instances.
+     */
+    synchronized public static void destroyAll()
+    {
+        Enumeration e = iResourceLoaders.keys();
+        while (e.hasMoreElements())
+        {
+            String key = (String)e.nextElement();
+            ResourceLoader rl = (ResourceLoader)iResourceLoaders.get(key);
+            if (rl instanceof ResourceLoaderQt)
+            {
+                ((ResourceLoaderQt)rl).destroy();
+            }
+        }
+        iResourceLoaders.clear();
+    }
 
     /*** ----------------------------- PRIVATE ---------------------------- */
 
     /**
-     * Get a plain string resource with a given resource id.
-     *
-     * @param id resource id, either with prefix or without
-     * @return resource string, or the id if does not exist
+     * Default constructor.
      */
-    private String string(String id)
-    {
-        String str = (String)resourceMap.get(id);
-        if (str == null)
-        {
-            // Try with prefix
-            str = (String)resourceMap.get(prefix + id);
-            if (str == null)
-            {
-                // Not found even with prefix. Use the id itself
-                if (!id.startsWith(prefix))
-                {
-                    str = prefix + id;
-                }
-                else
-                {
-                    str = id;
-                }
-
-                Logger.WLOG(Logger.EUtils, "Cannot find resource: " + id);
-            }
-
-            // Put back to hash with original key for quick retrieval
-            resourceMap.put(id, str);
-        }
-
-        str = decode(str);
-        str = replaceCharacterCodes(str);
-
-        return str;
-    }
-
-    /**
-     * Loads the resources from .loc type file.
-     *
-     * @param resourceName name of the resource file.
-     * @param aIs InputStream pointing to resource. It will be closed after use.
-     * @param true if operation succeed.
-     */
-    private boolean loadFile(String resourceName, boolean avkon)
+    protected ResourceLoader()
     {
-        InputStream is = null;
-
-        if (!avkon)  // Qt resources.
-        {
-            String langName = getLocaleIdQt();
-
-            // Emulator returns falsely en_GB as engineering English locale name.
-            if (langName.equals("en_GB"))
-            {
-                langName = "en";
-            }
-
-            // Load with real locale id
-            is = this.getClass().getResourceAsStream(
-                LOC_RESOURCE_BASE + resourceName + "_" + langName + ".loc");
-
-            if (is == null)
-            {
-                /*
-                 * Does not exist. No need to continue as avkon file cannot
-                 * found using qt name.
-                 */
-                return false;
-            }
-        }
-        else  // Avkon resources.
-        {
-            // Load with real locale id
-            is = this.getClass().getResourceAsStream(
-                 LOC_RESOURCE_BASE + resourceName + "_" + getLocaleId() + ".loc");
-
-            if (is == null)
-            {
-                // Load the engineering english
-                is = this.getClass().getResourceAsStream(
-                         LOC_RESOURCE_BASE + resourceName + "_sc" + ".loc");
-            }
-            if (is == null)
-            {
-                // Load the reference engineering english
-                is = this.getClass().getResourceAsStream(
-                         LOC_RESOURCE_BASE + resourceName + ".loc");
-            }
-            if (is == null)
-            {
-                Logger.WLOG(Logger.EUtils,
-                            "Cannot load resource file: " + resourceName);
-                return false;
-            }
-        }
-
-        try
-        {
-            // Loc-files area always on UTF8 format
-            LineReader lr = new LineReader(
-                new BufferedReader(new InputStreamReader(is, "UTF-8")));
-            String line;
-
-            while ((line = lr.readLine()) != null)
-            {
-                // Ignore lines which are not #define's
-                if (!line.startsWith("#define "))
-                {
-                    continue;
-                }
-                try
-                {
-                    // Skip "#define" + any whitespace
-                    line = line.substring(line.indexOf(' ')).trim();
-
-                    int idEnd = line.indexOf(' ');
-                    String id = line.substring(0, idEnd);
-
-                    int strStart = line.indexOf('"', idEnd);
-                    int strEnd = line.lastIndexOf('"');
-                    String str = line.substring(strStart + 1, strEnd);
-
-                    resourceMap.put(id, str);
-
-                }
-                catch (IndexOutOfBoundsException ex)
-                {
-                    String error = "Incorrect line " + lr.getLineNumber() + "\"" +
-                                   line + "\"";
-                    Logger.WLOG(Logger.EUtils, error);
-                }
-            }
-            is.close();
-
-        }
-        catch (IOException ex)
-        {
-            Logger.WLOG(Logger.EUtils,
-                        "Resource file " + resourceName + " handling failed: "
-                        + ex.getMessage());
-        }
-
-        return true;
     }
 
-    /**
-     * Decode given string. Decoding means unescaping escaped characters.
-     * Currently \n, \t, \', \\ and \" patterns are decoded to respective
-     * characters.
-     *
-     * @param str to be decoded.
-     * @return decoded String.
-     */
-    private String decode(String str)
-    {
-        str = replacePattern(str, "\\n", '\n');
-        str = replacePattern(str, "\\\\", '\\');
-        str = replacePattern(str, "\\\"", '\"');
-        str = replacePattern(str, "\\t", '\t');
-        str = replacePattern(str, "\\'", '\'');
-
-        return str;
-    }
-
-    /**
-     * Replace all occurrences of the pattern in the given String.
-     *
-     * @param resource string to be replaced.
-     * @param pattern to replace.
-     * @param replacement replacement character.
-     * @return String where all occurrences of the pattern are replaced.
-     */
-    private String replacePattern(
-        String resource, String pattern, char replacement)
-    {
-        StringBuffer sb = new StringBuffer();
-
-        int startIndex = resource.indexOf(pattern);
-        if (startIndex != -1)
-        {
-            sb.append(resource.substring(0, startIndex)).append(replacement);
-            startIndex = startIndex + pattern.length();
-            int endIndex = 0;
-
-            while ((endIndex = resource.indexOf(pattern, startIndex)) != -1)
-            {
-                sb.append(resource.substring(startIndex, endIndex))
-                .append(replacement);
-                startIndex = endIndex + pattern.length();
-            }
-
-            if (startIndex < resource.length())
-            {
-                sb.append(resource.substring(startIndex, resource.length()));
-            }
-            return sb.toString();
-        }
-        return resource;
-    }
-
-    /**
-     * Replace character codes. They are given as <0x0000> format. Where 0x0000
-     * contains character unicode as hex representation.
-     *
-     * @param str to replace characters.
-     * @return String where characters are replaced.
-     */
-    private String replaceCharacterCodes(String str)
-    {
-        StringBuffer sb = new StringBuffer();
-        int startIndex = str.indexOf("<0x");
-
-        if (startIndex != -1 && str.charAt(startIndex + 7) == '>')
-        {
-            sb.append(str.substring(0, startIndex));
-            try
-            {
-                int charint = Integer.parseInt(
-                                  str.substring(startIndex + 3, startIndex + 7), 16);
-                sb.append((char) charint);
-
-                int endIndex = 0;
-                startIndex+= 7;
-
-                while ((endIndex = str.indexOf("<0x", startIndex)) != -1
-                        && str.charAt(endIndex + 7) == '>')
-                {
-                    sb.append(str.substring(startIndex + 1, endIndex));
-
-                    charint = Integer.parseInt(
-                                  str.substring(endIndex + 3, endIndex + 7), 16);
-                    sb.append((char) charint);
-                    startIndex = endIndex + 7;
-                }
-            }
-            catch (NumberFormatException nfe)
-            {
-                Logger.ELOG(Logger.EUtils,
-                            "Cannot replace character from string: " + str);
-                return str;
-            }
-
-            if (startIndex < str.length())
-            {
-                sb.append(str.substring(startIndex + 1, str.length()));
-            }
-            return sb.toString();
-        }
-        return str;
-    }
-
-
     /*** ----------------------------- NATIVE ----------------------------- */
 
     /**
@@ -497,7 +263,7 @@
      *
      * @return languege code.
      */
-    private native int _getLocaleId();
+    private static native int _getLocaleId();
 
     /**
      * Get Locale Id on Qt platform.
@@ -505,5 +271,4 @@
      * @return locale Id string. If not in Qt null.
      */
     private static native String _getLocaleIdQt();
-
 }