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

AppResourceManager.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 javax.microedition.global.ResourceException;
import com.sun.midp.log.Logging;
import com.sun.midp.log.LogChannels;

/**
 * This class represents a resource manager for accessing application
 * resources.
 *
 */
public class AppResourceManager extends ResourceManager {

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

    /**
     * Constant for string type resource.
     */
    public final static byte TYPE_STRING = (byte) 0x01;

    /**
     * Constant for binary type resource.
     */
    public final static byte TYPE_BINARY = (byte) 0x10;

    /**
     * Constant for end type.
     */
    public final static byte TYPE_END = (byte) 0x00;

    /**
     * Resource cache.
     *
     * @see    ResourceCache
     */
    protected ResourceCache resourceCache;

    /**
     * A resource bundle reader for accessing application resource files.
     *
     * @see    ResourceBundleReader
     */
    protected ResourceBundleReader[] bundleReaders;

    /**
     * Array of locales that is used for hierarchical matching of resources.
     */
    private String[] locales;


    /**
     * 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 readers.
     *
     * @param  base     the base name
     * @param  cache    cache implementation to speed up resource retrieval
     * @param  locales  array of locales to try
     * @param  readers  array of readers corresponding to locales
     */
    public AppResourceManager(String base, String[] locales,
                            ResourceBundleReader[] readers,
                            ResourceCache cache) {

        this.locales = locales;
        setBaseName(base);
        setLocale(locales[0]);
        bundleReaders = readers;
        resourceCache = cache;
    }


    /**
     * Constructor for <code>AppResourceManager</code> without caching.
     *
     * @param  base     the base name
     * @param  locales  array of locales to try
     * @param  readers  array of readers corresponding to locales
     */
    public AppResourceManager(String base,
                              String[] locales,
                              ResourceBundleReader[] readers) {
        this(base, locales, readers, null);
    }


    /**
     * Returns type of the given resource.
     *
     * @param  id                  The id of the resource
     * @return                     The type of resource
     * @throws  ResourceException  If resource wasn't found
     */
    protected byte getResourceType(int id) throws ResourceException {
        byte type;
        AppResourceBundleReader appReader;
        for (int i = 0; i < bundleReaders.length; i++) {
            appReader = (AppResourceBundleReader) bundleReaders[i];
            type = appReader.getResourceType(id);

            if (type == (byte) 0xff) {
                continue;
            } else {
                return type;
            }
        }
        // for
        throw new ResourceException(ResourceException.RESOURCE_NOT_FOUND,
                "Resource not found.");
    }


    /**
     * Retrieves binary resource in the form of a byte array.
     *
     * @param  id                     the id of the resource
     * @return                        resource as array of bytes
     * @exception  ResourceException  is thrown if type of resource isn't
     *      binary.
     */
    public byte[] getData(int id) throws ResourceException {
        Object o;
        try {
            o = getResource(id);
        } catch (ResourceException e) {
            int ec = e.getErrorCode();
            String em = e.getMessage();
            if (ec == ResourceException.UNKNOWN_RESOURCE_TYPE) {
                ec = ResourceException.WRONG_RESOURCE_TYPE;
                em = "Resource is not of type BINARY";
            }
            throw new ResourceException(ec, em);
        }
        if (!(o instanceof byte[])) {
            throw new ResourceException(ResourceException.WRONG_RESOURCE_TYPE,
                "Resource is not of type BINARY");
        }
        return (byte[]) o;
    }


    /**
     * Method returns resources of type string and binary as objects. Methods
     * {@link #getData} and {@link #getString} are routed via this method. <p>
     *
     * Resource cache is used to speed up resource retrieval if ResourceManager
     * was caching.
     *
     * @param  id                     resource id
     * @return                        The resource value
     * @exception  ResourceException  is thrown if type of resource isn't binary
     *                                nor string
     */
    public Object getResource(int id) throws ResourceException {
        if (id < 0) {
            throw new IllegalArgumentException("Illegal resource ID.");
        }

        int length = 0;
        Object resource = null;
        byte type = (byte) 255;
        ResourceBundleReader reader;
        int hashCode;
        Resource resInCache = null;

        for (int i = 0; i < bundleReaders.length; i++) {
            reader = (ResourceBundleReader) bundleReaders[i];
            try {
                if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
                    Logging.report(Logging.INFORMATION, LogChannels.LC_JSR238,
                                   classname + ": getResource reader=" +
                                   reader.getResourceName());
                    Logging.report(Logging.INFORMATION, LogChannels.LC_JSR238,
                                   classname + ": " +
                                   " getResource for id \"" + id + "\"");
                }
                if (isCaching()) {
                    // check the cache for resource
                    if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
                        Logging.report(Logging.INFORMATION,
                                       LogChannels.LC_JSR238,
                                       classname + ": Using caching instance.");
                    }
                    hashCode =
                          resourceCache.getHashCodeForResource(getBaseName(),
                                                               locales[i],
                                                               id);
                    if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
                        Logging.report(Logging.INFORMATION,
                                       LogChannels.LC_JSR238,
                                       classname + ": hashCode=" + hashCode);
                    }
                    resInCache = resourceCache.lookup(hashCode);
                    if (resInCache != null) {
                        resource = resInCache.getValue();
                        if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
                            Logging.report(Logging.INFORMATION,
                                           LogChannels.LC_JSR238,
                                           classname +
                                           ": Resource was in cache.");
                        }
                    } else {
                        // caching instance
                        if (reader.isValidResourceID(id)) {
                            type = reader.getResourceType(id);
                            length = reader.getResourceLength(id);
                            resource = convertToResourceType(id, type,
                                                             length, reader);
                            if (resource == null) {
                                if (Logging.REPORT_LEVEL <=
                                    Logging.INFORMATION) {
                                    Logging.report(Logging.INFORMATION,
                                            LogChannels.LC_JSR238,
                                            classname + ": Cannot " +
                                            "convert resource to object");
                                }
                                throw new ResourceException(ResourceException.
                                        UNKNOWN_RESOURCE_TYPE,
                                        "Resource type is unknown.");
                            }
                            resourceCache.addResource(new Resource(hashCode,
                                                                   length,
                                                                   resource));
                        } else {
                            if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
                                Logging.report(Logging.INFORMATION,
                                               LogChannels.LC_JSR238,
                                               classname + ": " +
                                               "Resource id = " + id +
                                               " is NOT valid.");
                            }
                            continue;
                        }
                    }
                } else {
                    if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
                        Logging.report(Logging.INFORMATION,
                                       LogChannels.LC_JSR238,
                                       classname + ": " +
                                       "Using NOTcaching instance.");
                    }
                    if (reader.isValidResourceID(id)) {
                        type = reader.getResourceType(id);
                        length = reader.getResourceLength(id);
                        resource = convertToResourceType(id, type,
                                                               length, reader);
                        if (resource == null) {
                            if (Logging.REPORT_LEVEL <= Logging.WARNING) {
                                Logging.report(Logging.WARNING,
                                               LogChannels.LC_JSR238,
                                               classname + ": Cannot " +
                                               "convert resource to object");
                            }
                            throw new ResourceException(ResourceException.
                                    UNKNOWN_RESOURCE_TYPE,
                                    "Resource type is unknown.");
                        }
                    } else {
                        if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
                            Logging.report(Logging.INFORMATION,
                                           LogChannels.LC_JSR238,
                                           classname + ": " +
                                           "Resource id = " + id +
                                           " is NOT valid.");
                        }
                        continue;
                    }
                }

            } catch (IOException ioe) {
                if (Logging.REPORT_LEVEL <= Logging.WARNING) {
                    Logging.report(Logging.WARNING, LogChannels.LC_JSR238,
                                   classname + ": " +
                                   "IOException: " + ioe.getMessage());
                }
                throw new ResourceException(ResourceException.DATA_ERROR,
                                            "Error reading resource");
            }

            if (resource == null) {
                throw new ResourceException(
                                ResourceException.UNKNOWN_RESOURCE_TYPE,
                                "Unknown resource type \"" + type + "\"");
            } else {
                if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
                    Logging.report(Logging.INFORMATION, LogChannels.LC_JSR238,
                                   classname + ": " +
                                   "Returning resource object " + resource);
                }
                return cloneResource(resource);
            }
        }// for

        throw new ResourceException(ResourceException.RESOURCE_NOT_FOUND,
                "Invalid resource id " + id + ".");
    }


    /**
     * Check if it is known resource type by this manager. Convert it to the
     * expected object. It recognizes {@link #TYPE_STRING} and {@link
     * #TYPE_BINARY} resource types.
     *
     * @param  resourceID       the resource identifier
     * @param  type             the resource type
     * @param  length           the resource length
     * @param  reader           resource bundle reader
     * @return                  resource as object
     *
     * @throws      IOException if read failed
     * @exception   ResourceException  exception is thrown when
     *              reading of resource has failed.
     */
    protected Object convertToResourceType(int resourceID,
                    byte type, int length,
                    ResourceBundleReader reader) throws IOException {
        if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
            Logging.report(Logging.INFORMATION, LogChannels.LC_JSR238,
                           classname + ": " +
                           "converting resource id = \"" +
                           resourceID +
                           "\" to object" +
                           " type :\"" + type +
                           "\" with reader : " + reader);
        }
        byte[] data = null;
        if (length != 0) {
            data = reader.getRawResourceData(resourceID);
        } else {
            data = new byte[0];
        }
        if (type == TYPE_STRING) {
            try {
                Object o = getUTF8(data);
                if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
                    Logging.report(Logging.INFORMATION, LogChannels.LC_JSR238,
                                   classname + ": " +
                                   "data converted to utf8 string \"" +
                                   (String)o + "\"");
                }
                return o;
            } catch (IllegalArgumentException iae) {
                throw new IOException("Cannot convert string to UTF8\n" +
                                      iae.getMessage());
            }

        } else if (type == TYPE_BINARY) {
            if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
                Logging.report(Logging.INFORMATION, LogChannels.LC_JSR238,
                               classname + ": " +
                               "data not converted using pure binary");
            }
            return data;
        }
        return null;
    }

    /**
     * The method clones resource.
     *
     * @param resource the resource to clone
     * @return copy of resource
     */
    protected Object cloneResource(Object resource) {
        if (resource instanceof String) {
            if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
                Logging.report(Logging.INFORMATION, LogChannels.LC_JSR238,
                               classname + ": " +
                               "returning cloned string resource");
            }
            return new String((String)resource);
        } else if (resource instanceof byte[]) {
            byte[] arry = (byte[])resource;
            byte[] clone = new byte[arry.length];
            System.arraycopy(arry, 0, clone, 0, arry.length);
            if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
                Logging.report(Logging.INFORMATION, LogChannels.LC_JSR238,
                               classname + ": " +
                               "returning cloned binary resource");
            }
            return clone;
        }
        return resource;
    }


    /**
     * Retrieves string resource.
     *
     * @param  id                     the id of the resource
     * @return                        resource as string
     * @exception  ResourceException  is thrown if type of resource isn't
     *      string.
     */
    public String getString(int id) throws ResourceException {
        Object o;
        try {
            o = getResource(id);
        } catch (ResourceException e) {
            int ec = e.getErrorCode();
            String em = e.getMessage();
            if (ec == ResourceException.UNKNOWN_RESOURCE_TYPE) {
                ec = ResourceException.WRONG_RESOURCE_TYPE;
                em = "Resource is not of type STRING";
            }
            throw new ResourceException(ec, em);
        }
        if (!(o instanceof String)) {
            throw new ResourceException(ResourceException.WRONG_RESOURCE_TYPE,
                    "Resource is not of type STRING");
        }
        return (String) o;
    }


    /**
     * Check if <code>ResourceManager</code> implements caching.
     *
     * @return    <code>true</code> if cache is enabled.
     */
    public boolean isCaching() {
        return resourceCache != null;
    }


    /**
     * Check if resource with given id exists.
     *
     * @param  id  resource id
     * @return     <code>true</code> if resource exists
     */
    public boolean isValidResourceID(int id) {
        if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
            Logging.report(Logging.INFORMATION, LogChannels.LC_JSR238,
                           classname + ": " +
                           "validating resourceId=" + id);
        }

        if (id < 0) {
            throw new IllegalArgumentException("Illegal resource ID.");
        }

        try {
            return (null != getResource(id));
            
        } catch (ResourceException re) {
            return false;
        }
    }


    /**
     * Conversion of byte array to UTF-8 encoded string.
     *
     * @param  bytearr  bytes that make UTF-8 string
     * @return          UTF-8 encoded string
     */
    protected final String getUTF8(byte[] bytearr) throws IllegalArgumentException {
    		StringBuffer sb = new StringBuffer();
    		int count=0;
    		while (count<bytearr.length) {
    		
    		int utf32 = bytearr[count++];
    		
			int numOfBytes;
			if ((utf32 & 0x80) == 0) { sb.append((char) (utf32 & 0x7F));continue;} else
			if ((utf32 & 0xE0) == 0xC0) { numOfBytes = 2; utf32 &= ~0xFFFFFFE0; } else
			if ((utf32 & 0xF0) == 0xE0) { numOfBytes = 3; utf32 &= ~0xFFFFFFF0; } else
			if ((utf32 & 0xF8) == 0xF0) { numOfBytes = 4; utf32 &= ~0xFFFFFFF8; } else 
				throw new IllegalArgumentException("malformed input: around byte " + count);
			
			while (--numOfBytes != 0) {
				if (count==bytearr.length) throw new IllegalArgumentException("malformed input: partial character at end");
				int nextByte = bytearr[count++];
				if ( (nextByte & 0xC0) != 0x80) throw new IllegalArgumentException("malformed input: around byte " + count);
				utf32 <<= 6;
				utf32 += (nextByte & 0x3F);
			}
			
			if (utf32>=0xDC00 && utf32<=0xD800){
				throw new IllegalArgumentException("malformed input: invalid unicode character at pos " + (count-3));
			}
				
			if (utf32>0xFFFF) {
				//handle surrogates:
				int nextChar = 0xDC00 +  (utf32 & 0x3FF);
				utf32 = 0xD800 + ((utf32 - 0x10000) >> 10);
				sb.append((char)utf32);
				sb.append((char)nextChar);
			} else {
				sb.append((char)utf32);
			}
    	}
    		
    	return sb.toString();
    }
    	
}