FileDocCategorySizeDatePackage
AppResourceManagerFactory.javaAPI DocphoneME MR2 API (J2ME)19268Wed May 02 18:00:46 BST 2007com.sun.j2me.global

AppResourceManagerFactory.java

/*
 *   
 *
 * Copyright  1990-2007 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt).
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions.
 */
package com.sun.j2me.global;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Enumeration;
import java.util.Vector;
import javax.microedition.global.ResourceException;
import javax.microedition.global.UnsupportedLocaleException;
import com.sun.midp.log.Logging;
import com.sun.midp.log.LogChannels;

/**
 * This class represents a resource manager factory for creating application
 * resource managers.
 *
 */
public class AppResourceManagerFactory extends ResourceManagerFactory {

    /**
     * Class name.
     */
    private static final String classname =
                                AppResourceManagerFactory.class.getName();

    /**
     * Implementation of resource cache.
     */
    protected ResourceCache resourceCache;


    /**
     * Creates a new resource manager factory for creating application resource
     * managers.
     *
     * @param  cache  implementation of {@link ResourceCache} to cache
     *      resources.
     */
    public AppResourceManagerFactory(ResourceCache cache) {
        resourceCache = cache;
    }


    /**
     * Get manager for system locale. It's never used. {@link
     * AppResourceManagerFactory#getManager(String)} replaces this call.
     *
     * @param  baseName  the base name
     * @return  <code>null</code>
     * @throws  javax.microedition.global.ResourceException  if
     *          manager cannot be created
     */
    public ResourceManager getManager(String baseName) {
        return null;
    }

    /**
     * Creates initialized instance of ResourceBundleReader for given name.
     * It opens resource file and reads header.
     *
     * @param name name of resource file
     * @return reader for this resource file
     */
    protected ResourceBundleReader getResourceBundleReaderForName(String name) {
        if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
            Logging.report(Logging.INFORMATION, LogChannels.LC_JSR238,
                           classname + ": getResourceBundleReaderForName (" +
                           name + ")");
        }
        return AppResourceBundleReader.getInstance(name);
    }

    /**
     * Creates a new instance of <code>AppResourceManager</code>. The new
     * instance can be used to get application resources of the given base name
     * and locale. The resource files will be accessed through the given
     * resource bundle reader.
     *
     * @param  baseName     the base name
     * @param  locales  array of locales to try
     * @param  readers  array of readers corresponding to locales
     * @return new <code>AppResourceManager</code> object
     */
    protected ResourceManager newResourceManagerInstance(
                              String baseName,
                              String[] locales,
                              ResourceBundleReader[] readers) {
        if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
            Logging.report(Logging.INFORMATION, LogChannels.LC_JSR238,
                           classname + ": " +
                           "Creating new AppResourceManager for \"" +
                           baseName + "\" with cache: " + this.resourceCache +
                           "\nwith readers:");
            for (int i = 0; i < readers.length; i++) {
                Logging.report(Logging.INFORMATION, LogChannels.LC_JSR238,
                               readers[i].toString());
            }
        }

        return new AppResourceManager(baseName,
                                      locales,
                                      readers,
                                      this.resourceCache);
    }


    /**
     * Instantiates <code>ResourceManager</code> for a given locale, going
     * through all parent locales and creating respective
     * {@link ResourceBundleReader}s for them.
     *
     * @param  baseName            the base name
     * @param  locale              the locale
     * @param mf                   the metafile
     * @param rbr                  the bundle reader
     * @return                     created {@link AppResourceManager} object
     */
    private ResourceManager getManager(String baseName, String locale,
                                       MetaFile mf, ResourceBundleReader rbr) {
        // locales
        Vector l = new Vector(5);
        // readers
        Vector r = new Vector(5);

        l.addElement(locale);
        r.addElement(rbr);
        locale = LocaleHelpers.getParentLocale(locale);

        /*
         * Go through all parent locales,
         * e.g. "en-US-var", "en-US", "en", "", null
         */
        while (locale != null) {
            if (mf.containsLocale(locale)) {
                try {
                    ResourceBundleReader reader =
                            getResourceBundleReaderForName(
                                    getResourceUrl(baseName, locale));
                    l.addElement(locale);
                    r.addElement(reader);
                } catch (ResourceException re_ignore) {
                /* intentionally ignored */
                /* For parent locales it does not throw exception in case any */
                /* problems. The exception is thrown for the current locale */
                /* only in the public getManager methods. */
                }
            }
            locale = LocaleHelpers.getParentLocale(locale);
        }

        String[] locales = new String[l.size()];
        ResourceBundleReader[] readers = new ResourceBundleReader[r.size()];
        l.copyInto(locales);
        r.copyInto(readers);
        // instantiate ResourceManager with supported locales

        return newResourceManagerInstance(baseName, locales, readers);
    }


    /**
     * Creates <code>ResourceManager</code> for given base name and locale.
     * Locale inheritance is resolved here. All parent locales are found and
     * <code>ResourceManager</code> is instantiated for the first locale that
     * is supported and contains any resources. Metafile is read for base name
     * and parent locales are matched with locales from the metafile.
     *
     * @param  baseName            the base name
     * @param  locale              the locale code
     * @return                     created {@link AppResourceManager} object
     * @throws  ResourceException  if resources are not found for any of
     *      the matched locales or resource file has invalid format.
     * @throws  UnsupportedLocaleException  if given base name
     *      supports neither requested locale nor any of its parent locales.
     */
    public ResourceManager getManager(String baseName, String locale)
             throws ResourceException, UnsupportedLocaleException {
        if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
            Logging.report(Logging.INFORMATION, LogChannels.LC_JSR238,
                           classname + ": " + "getManager (" +
                           baseName + "," + locale + ")");
        }
        Vector l = new Vector(4);

        while (locale != null) {
            l.addElement(locale);
            locale = LocaleHelpers.getParentLocale(locale);
        }

        String[] locales = new String[l.size()];
        l.copyInto(locales);

        return getManager(baseName, locales);
    }


    /**
     * Create ResourceManager for given base name and list of locales. No
     * locale inheritance used here. Metafile is read for base name and locales
     * in array are matched with locales from metafile. Only those that match
     * are used.
     *
     * @param  baseName            the base name
     * @param  locales             array of locales
     * @return                     created {@link AppResourceManager} object
     * @throws  ResourceException  if no resources for the base name and any of
     *      the locales were found.
     * @throws  UnsupportedLocaleException  if no locales from the array are
     *      listed in metafile.
     */
    public ResourceManager getManager(String baseName, String[] locales)
             throws ResourceException, UnsupportedLocaleException {

        // read metafile
        MetaFile mf = getMetafileForBaseName(baseName);

        // match locales with those read from metafile
        for (int i = 0; i < locales.length; i++) {
            if (mf.containsLocale(locales[i])) {
                ResourceBundleReader reader = getResourceBundleReaderForName(
                        getResourceUrl(baseName, locales[i]));
                return getManager(baseName, locales[i], mf, reader);
            }
        }
        throw new UnsupportedLocaleException();
    }


    /**
     *  Get array of supported locales for given base name. It reads metafile
     *  for the base name.
     *
     * @param  baseName  the base name
     * @return           array of locales
     */
    public String[] getSupportedLocales(String baseName) {
        if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
            Logging.report(Logging.INFORMATION, LogChannels.LC_JSR238,
                           classname + ": getSupportedLocales");
        }
        if (baseName == null) 
            throw new NullPointerException("Basename is null.");
        
        MetaFile mf = getMetafileForBaseName(baseName);
        String[] locales = mf.toArray();
        if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
            for (int i = 0; i < locales.length; i++) {
                Logging.report(Logging.INFORMATION, LogChannels.LC_JSR238,
                               locales[i]);
            }
        }
        return locales;
    }


    /**
     * Gives resource URL that can be used for method {@link
     * Class#getResourceAsStream}.
     *
     * @param  baseName  the base name
     * @param  locale    the locale
     * @return           resource URL
     */
    protected String getResourceUrl(String baseName, String locale) {
        String resourceUrl;
        resourceUrl = (locale != null && locale.length() > 0)
                 ? GLOBAL_PREFIX + locale + "/" + baseName
                 : GLOBAL_PREFIX + baseName;

        String url = resourceUrl + RESOURCE_FILE_EXTENSION;
        if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
            Logging.report(Logging.INFORMATION, LogChannels.LC_JSR238,
                           classname + ": url=" + url);
        }
        return url;

    }

    /**
     * Read metafile {@link MetaFile} for givane base name. Metafiles are
     * stored under global directory in form _<base name>.<br>
     *
     *
     * @param  baseName  the base name
     * @return           metafile for the base name
     */
    protected MetaFile getMetafileForBaseName(String baseName) {
        MetaFile mf;
        int hcode = resourceCache.getHashCodeForResource("_" + baseName, "", 0);
        Resource r = resourceCache.lookup(hcode);
        if (r == null) {
            try {
                mf = new MetaFile(GLOBAL_PREFIX + "_" + baseName);

            } catch (IOException ioe) {
                throw new ResourceException(
                                     ResourceException.METAFILE_NOT_FOUND,
                                     "Not found metafile for base name \""
                                     + baseName + "\"");
            }
            resourceCache.addResource(new Resource(hcode, mf.getSize(), mf));
        } else {
            mf = (MetaFile) r.getValue();
        }
        return (MetaFile)mf.clone();
    }


    /**
     * Class MetaFile represents metafile for base name. Meta file is text file
     * which contains list of locales for base name. Locales are separated by
     * space (U+0020). Locales can be quoted. <br>
     * "" - empty string in locale list means that common resource bundle is
     * available for this base name.
     */
    public class MetaFile {

        /**
         * Approximate size used for storage of the object in cache.
         */
        int size = 0;

        /**
         * List of locales.
         */
        Vector locales = new Vector(5);


        /**
         * Create new metafile.
         *
         */
        private MetaFile() {
        }

        /**
         * Create new metafile.
         *
         * @param  filename         path to metafile
         * @exception  IOException  Description of the Exception
         * @throws  IOException     if filename can't be read or error occurs
         *      during read.
         */
        public MetaFile(String filename) throws IOException {
            InputStream is = getClass().getResourceAsStream(filename);
            if (is == null) {
                throw new IOException();
            }
            read(is);
        }


        /**
         * Create new metafile.
         *
         * @param  in               stream to read metafile from
         * @exception  IOException  Description of the Exception
         * @throws  IOException     if filename can't be read or error occurs
         *      during read.
         */
        public MetaFile(InputStream in) throws IOException {
            read(in);
        }

	/**
	 * Creates a copy of the instance.
	 * @return the instance copy.
	 */
        public Object clone() {
            MetaFile mf = new MetaFile();
            for (Enumeration e = locales.elements(); e.hasMoreElements(); ) {
                String locale = new String((String)e.nextElement());
                mf.locales.addElement(locale);
            }
            return mf;
        }

        /**
         * Check if given locale appears in metafile.
         *
         * @param  locale  the locale
         * @return         <code>true</code> if locale appeared in metafile
         */
        public boolean containsLocale(String locale) {
            String l;
            for (Enumeration e = locales.elements(); e.hasMoreElements(); ) {
                l = (String) e.nextElement();
                if (l.equals(locale)) {
                    return true;
                }
            }
            return false;
        }


        /**
         * Convert locales to string array.
         *
         * @return    array of locales
         */
        public String[] toArray() {
            String[] larr = new String[locales.size()];
            locales.copyInto(larr);
            return larr;
        }


        /**
         * Initialize metafile. Parse input stream.
         *
         * @param  in               Description of the Parameter
         * @exception  IOException  Description of the Exception
         */
        private void read(InputStream in) throws IOException {
            int BUFFER_LENGTH = 16;
            int readChars = 0;
            char[] buffer = new char[BUFFER_LENGTH];
            InputStreamReader iReader = new InputStreamReader(in);
            StringBuffer sb = new StringBuffer();
            do {
                readChars = iReader.read(buffer);
                if (readChars > 0) {
                    sb.append(buffer, 0, readChars);
                }
            } while (readChars >= 0);
            if (sb.length() == 0) {
                return;
            }
            if (sb.charAt(sb.length() - 1) != ' ') {
            	sb.append(' ');
            }

            boolean inside = false;
            StringBuffer sbuf = new StringBuffer();
            char c = ' ';
            char last = ' ';
            char prev = ' ';
            int sb_length = sb.length();
            for (int i = 0; i < sb_length; i++) {
                c = sb.charAt(i);
                if (c == ' ' || c == '"') {
                    if (inside) {
                        if (last == c) {
                            String locale;
                            if (sbuf.length() == 0) {
                                locale = "";
                            } else {
                                locale = sbuf.toString().trim();
                                if (!LocaleHelpers.isValidLocale(locale)) {
                                    throw new ResourceException(
                                            ResourceException.DATA_ERROR,
                                            "Invalid locale in metafile: \"" +
                                            locale + "\"");
                                }
                            }
                            locale = LocaleHelpers.normalizeLocale(locale);
                            if (!locales.contains(locale)) {
                                if (locale.length() == 0) {
                                    locales.insertElementAt(locale, 0);
                                } else {
                                    locales.addElement(locale);
                                }
                                size += sbuf.length();
                            }
                            sbuf.setLength(0);
                            inside = false;
                                
                        } else {
                            sbuf.append(c);
                        }
                    } else {
                        if (c == prev) {
                            throw new ResourceException(
                                      ResourceException.DATA_ERROR,
                                      "Invalid metafile format, double '" +
                                      c + "' detected");
                        }
                        last = c;
                        if (c == '"') {
                            inside = true;
                        }
                    }
                } else {
                    inside = true;
                    sbuf.append(c);
                }
                prev = c;
            }
            if (inside) {
                throw new ResourceException(ResourceException.DATA_ERROR,
                        "Unclosed quotation");
            }
        }

        /**
         * Get approximate size of metafile instance It counts only string
         * lengths. Necessary for use with {@link ResourceCache}
         *
         * @return    approximate size of MetaFile instance
         */
        public int getSize() {
            return size;
        }
    }
    // MetaFile

}