/*
 * Copyright 2001-2004 The Apache Software Foundation.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.axis.i18n;

import java.io.IOException;
import java.io.InputStream;
import java.text.MessageFormat;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.Properties;

/**
 * CURRENTLY NOT USED
 * KEEPING FOR REFERENCE  9/19/2002
 * 
 * <p>Wrapper class for resource bundles. Property files are used to store
 * resource strings, which are the only types of resources available.
 * Property files can inherit properties from other files so that
 * a base property file can be used and a small number of properties
 * can be over-ridden by another property file. For example you may
 * create an english version of a resource file named "resource.properties".
 * You then decide that the British English version of all of the properties
 * except one are the same, so there is no need to redefine all of the
 * properties in "resource_en_GB", just the one that is different.</p>
 * <p>The property file lookup searches for classes with various suffixes
 * on the basis if the desired local and the current default local
 * (as returned by Local.getDefault()). As property files are found the
 * property values are merged so that inheritance is preserved.</p>
 * <p>The order of searching is:</p>
 * <dir>
 * basename + "_" + langage + "_" + country + "_" + variant
 * basename + "_" + langage + "_" + country
 * basename + "_" + langage
 * basename + "_" + defaultLanguage + "_" + defaultCountry + "_" + defaultVariant
 * basename + "_" + defaultLanguage + "_" + defaultCountry
 * basename + "_" + defaultLanguage
 * basename
 * </dir>
 * <p>The basename is the name of the property file without the ".properties"
 * extension.</p>
 * <p>Properties will be cached for performance.<p>
 * <p>Property values stored in the property files can also contain dynamic
 * variables. Any dynamic variable defined in PropertiesUtil.getVariableValue()
 * can be used (such as {date}), as well as arguments in the form {0}, {1}, etc.
 * Argument values are specified in the various overloaded getString() methods.</p>
 * 
 * @author Karl Moss (kmoss@macromedia.com)
 * @author Glen Daniels (gdaniels@apache.org)
 */
public class RB {
    // The static cache of properties. The key is the basename + the local +
    // the default local and the element is the Properties object containing
    // the resources
    static Hashtable propertyCache = new Hashtable();

    // The default base name
    public static final String BASE_NAME = "resource";

    // The property file extension
    public static final String PROPERTY_EXT = ".properties";

    // The name of the current base property file (with extension)
    protected String basePropertyFileName;

    // The properties for the current resource bundle
    protected Properties resourceProperties;

    /**
      * Construct a new RB
      * @param name The name of the property file without the ".properties" extension
      */
    public RB(String name) throws MissingResourceException
    {
        this(null, name, null);
    }

    /**
      * Construct a new RB
      * @param caller The calling object. This is used to get the package name
      * to further construct the basename as well as to get the proper ClassLoader
      * @param name The name of the property file without the ".properties" extension
      */
    public RB(Object caller, String name) throws MissingResourceException
    {
        this(caller, name, null);
    }

    /**
      * Construct a new RB
      * @param caller The calling object. This is used to get the package name
      * to further construct the basename as well as to get the proper ClassLoader
      * @param name The name of the property file without the ".properties" extension
      * @param local The local
      */
    public RB(Object caller, String name, Locale locale) throws MissingResourceException
    {
        ClassLoader cl = null;

        if (caller != null) {

            Class c;
            if (caller instanceof Class) {
                c = (Class) caller;
            }
            else {
                c = caller.getClass();
            }

            // Get the appropriate class loader
            cl = c.getClassLoader();

            if (name.indexOf("/") == -1) {

                // Create the full basename only if not given
                String fullName = c.getName();

                int pos = fullName.lastIndexOf(".");
                if (pos > 0) {
                    name = fullName.substring(0, pos + 1).replace('.', '/') + name;
                }
            }
        } else {
            // Try the shared default properties file...
            if (name.indexOf("/") == -1) {
                name = "org/apache/axis/default-resource";
            }
        }

        Locale defaultLocale = Locale.getDefault();

        // If the locale given is the same as the default locale, ignore it
        if (locale != null) {
            if (locale.equals(defaultLocale)) {
                locale = null;
            }
        }

        // Load the properties. If no property files exist then a
        // MissingResourceException will be thrown
        loadProperties(name, cl, locale, defaultLocale);
    }

    /**
      * Gets a string message from the resource bundle for the given key
      * @param key The resource key
      * @return The message
      */
    public String getString(String key) throws MissingResourceException
    {
        return getString(key, (Object[]) null);
    }

    /**
      * <p>Gets a string message from the resource bundle for the given key. The
      * message may contain variables that will be substituted with the given
      * arguments. Variables have the format:</p>
      * <dir>
      * This message has two variables: {0} and {1}
      * </dir>
      * @param key The resource key
      * @param arg0 The argument to place in variable {0}
      * @return The message
      */
    public String getString(String key, Object arg0) throws MissingResourceException
    {
        Object[] o = new Object[1];
        o[0] = arg0;
        return getString(key, o);
    }

    /**
      * <p>Gets a string message from the resource bundle for the given key. The
      * message may contain variables that will be substituted with the given
      * arguments. Variables have the format:</p>
      * <dir>
      * This message has two variables: {0} and {1}
      * </dir>
      * @param key The resource key
      * @param arg0 The argument to place in variable {0}
      * @param arg1 The argument to place in variable {1}
      * @return The message
      */
    public String getString(String key, Object arg0, Object arg1) throws MissingResourceException
    {
        Object[] o = new Object[2];
        o[0] = arg0;
        o[1] = arg1;
        return getString(key, o);
    }

    /**
      * <p>Gets a string message from the resource bundle for the given key. The
      * message may contain variables that will be substituted with the given
      * arguments. Variables have the format:</p>
      * <dir>
      * This message has two variables: {0} and {1}
      * </dir>
      * @param key The resource key
      * @param arg0 The argument to place in variable {0}
      * @param arg1 The argument to place in variable {1}
      * @param arg2 The argument to place in variable {1}
      * @return The message
      */
    public String getString(String key, Object arg0, Object arg1, Object arg2) throws MissingResourceException
    {
        Object[] o = new Object[3];
        o[0] = arg0;
        o[1] = arg1;
        o[2] = arg2;
        return getString(key, o);
    }

    /**
      * <p>Gets a string message from the resource bundle for the given key. The
      * message may contain variables that will be substituted with the given
      * arguments. Variables have the format:</p>
      * <dir>
      * This message has two variables: {0} and {1}
      * </dir>
      * @param key The resource key
      * @param array An array of objects to place in corresponding variables
      * @return The message
      */
    public String getString(String key, Object[] array) throws MissingResourceException
    {
        String msg = null;
        if (resourceProperties != null) {
            msg = resourceProperties.getProperty(key);
        }

        if (msg == null) {
            throw new MissingResourceException("Cannot find resource key \"" + key +
                                               "\" in base name " + basePropertyFileName,
                                               basePropertyFileName, key);
        }

        msg = MessageFormat.format(msg, array);
        return msg;
    }

    protected void loadProperties(String basename, ClassLoader loader, Locale locale,
                                  Locale defaultLocale)
        throws MissingResourceException
    {
        // Check the cache first
        String loaderName = "";
        if (loader != null) {
            loaderName = ":" + loader.hashCode();
        }
        String cacheKey = basename + ":" + locale + ":" + defaultLocale + loaderName;
        Properties p = (Properties) propertyCache.get(cacheKey);
        basePropertyFileName = basename + PROPERTY_EXT;

        if (p == null) {
            // The properties were not found in the cache. Search the given locale
            // first
            if (locale != null) {
                p = loadProperties(basename, loader, locale, p);
            }

            // Search the default locale
            if (defaultLocale != null) {
                p = loadProperties(basename, loader, defaultLocale, p);
            }

            // Search for the basename
            p = merge(p, loadProperties(basePropertyFileName, loader));

            if (p == null) {
                throw new MissingResourceException("Cannot find resource for base name " +
                                                   basePropertyFileName, basePropertyFileName, "");
            }

            // Cache the properties
            propertyCache.put(cacheKey, p);

        }

        resourceProperties = p;
    }

    protected Properties loadProperties(String basename, ClassLoader loader, Locale locale,
                                        Properties props)
    {

        String language = locale.getLanguage();
        String country = locale.getCountry();
        String variant = locale.getVariant();
        if (variant != null) {
            if (variant.trim().length() == 0) {
                variant = null;
            }
        }

        if (language != null) {

            if (country != null) {

                if (variant != null) {
                    props = merge(props, loadProperties(basename + "_" + language +"_" + country + "_" + variant +
                                                        PROPERTY_EXT, loader));
                }
                props = merge(props, loadProperties(basename + "_" + language +"_" + country +
                                                    PROPERTY_EXT, loader));
            }
            props = merge(props, loadProperties(basename + "_" + language + PROPERTY_EXT, loader));
        }
        return props;
    }

    protected Properties loadProperties(String resname, ClassLoader loader)
    {
        Properties props = null;

        // Attempt to open and load the properties
        InputStream in = null;
        try {
            if (loader != null) {
                in = loader.getResourceAsStream(resname);
            }

            // Either we're using the system class loader or we didn't find the
            // resource using the given class loader
            if (in == null) {
                in = ClassLoader.getSystemResourceAsStream(resname);
            }
            if (in != null) {
                props = new Properties();
                try {
                    props.load(in);
                }
                catch (IOException ex) {
                    // On error, clear the props
                    props = null;
                }
            }
        }
        finally {
            if (in != null) {
                try {
                    in.close();
                }
                catch (Exception ex) {
                    // Ignore error on close
                }
            }
        }
        return props;
    }

    /**
      * Merge two Properties objects
      */
    protected Properties merge(Properties p1, Properties p2)
    {
        if ((p1 == null) &&
            (p2 == null)) {
            return null;
        }
        else if (p1 == null) {
            return p2;
        }
        else if (p2 == null) {
            return p1;
        }

        // Now merge. p1 takes precedence
        Enumeration enumeration = p2.keys();
        while (enumeration.hasMoreElements()) {
            String key = (String) enumeration.nextElement();
            if (p1.getProperty(key) == null) {
                p1.put(key, p2.getProperty(key));
            }
        }

        return p1;
    }

    /**
      * Get the underlying properties
      */
    public Properties getProperties()
    {
        return resourceProperties;
    }

    // STATIC ACCESSORS

    /**
      * Get a message from resource.properties from the package of the given object.
      * @param caller The calling object, used to get the package name and class loader
      * @param key The resource key
      * @return The formatted message
      */
    public static String getString(Object caller, String key)
        throws MissingResourceException
    {
        return getMessage(caller, BASE_NAME, null, key, null);
    }

    /**
      * Get a message from resource.properties from the package of the given object.
      * @param caller The calling object, used to get the package name and class loader
      * @param key The resource key
      * @param arg0 The argument to place in variable {0}
      * @return The formatted message
      */
    public static String getString(Object caller, String key, Object arg0)
        throws MissingResourceException
    {
        Object[] o = new Object[1];
        o[0] = arg0;
        return getMessage(caller, BASE_NAME, null, key, o);
    }

    /**
      * Get a message from resource.properties from the package of the given object.
      * @param caller The calling object, used to get the package name and class loader
      * @param key The resource key
      * @param arg0 The argument to place in variable {0}
      * @param arg1 The argument to place in variable {1}
      * @return The formatted message
      */
    public static String getString(Object caller, String key, Object arg0, Object arg1)
        throws MissingResourceException
    {
        Object[] o = new Object[2];
        o[0] = arg0;
        o[1] = arg1;
        return getMessage(caller, BASE_NAME, null, key, o);
    }

    /**
      * Get a message from resource.properties from the package of the given object.
      * @param caller The calling object, used to get the package name and class loader
      * @param key The resource key
      * @param arg0 The argument to place in variable {0}
      * @param arg1 The argument to place in variable {1}
      * @param arg2 The argument to place in variable {2}
      * @return The formatted message
      */
    public static String getString(Object caller, String key, Object arg0, Object arg1, Object arg2)
        throws MissingResourceException
    {
        Object[] o = new Object[3];
        o[0] = arg0;
        o[1] = arg1;
        o[2] = arg2;
        return getMessage(caller, BASE_NAME, null, key, o);
    }

    /**
      * Get a message from resource.properties from the package of the given object.
      * @param caller The calling object, used to get the package name and class loader
      * @param key The resource key
      * @param arg0 The argument to place in variable {0}
      * @param arg1 The argument to place in variable {1}
      * @param arg2 The argument to place in variable {2}
      * @param arg3 The argument to place in variable {3}
      * @return The formatted message
      */
    public static String getString(Object caller, String key, Object arg0, Object arg1, Object arg2, Object arg3)
        throws MissingResourceException
    {
        Object[] o = new Object[4];
        o[0] = arg0;
        o[1] = arg1;
        o[2] = arg2;
        o[3] = arg3;
        return getMessage(caller, BASE_NAME, null, key, o);
    }


    /**
      * Get a message from resource.properties from the package of the given object.
      * @param caller The calling object, used to get the package name and class loader
      * @param key The resource key
      * @param arg0 The argument to place in variable {0}
      * @param arg1 The argument to place in variable {1}
      * @param arg2 The argument to place in variable {2}
      * @param arg3 The argument to place in variable {3}
      * @param arg4 The argument to place in variable {4}
      * @return The formatted message
      */
    public static String getString(Object caller, String key, Object arg0, Object arg1, Object arg2, Object arg3, Object arg4)
        throws MissingResourceException
    {
        Object[] o = new Object[5];
        o[0] = arg0;
        o[1] = arg1;
        o[2] = arg2;
        o[3] = arg3;
        o[4] = arg4;
        return getMessage(caller, BASE_NAME, null, key, o);
    }


    /**
      * Get a message from resource.properties from the package of the given object.
      * @param caller The calling object, used to get the package name and class loader
      * @param key The resource key
      * @param array An array of objects to place in corresponding variables
      * @return The formatted message
      */
    public static String getString(Object caller, String key, Object[] args)
        throws MissingResourceException
    {
        return getMessage(caller, BASE_NAME, null, key, args);
    }


    /**
      * Get a message from resource.properties from the package of the given object.
      * @param caller The calling object, used to get the package name and class loader
      * @param locale The locale
      * @param key The resource key
      * @return The formatted message
      */
    public static String getString(Object caller, Locale locale, String key)
        throws MissingResourceException
    {
        return getMessage(caller, BASE_NAME, locale, key, null);
    }

    /**
      * Get a message from resource.properties from the package of the given object.
      * @param caller The calling object, used to get the package name and class loader
      * @param locale The locale
      * @param key The resource key
      * @param arg0 The argument to place in variable {0}
      * @return The formatted message
      */
    public static String getString(Object caller, Locale locale, String key, Object arg0)
        throws MissingResourceException
    {
        Object[] o = new Object[1];
        o[0] = arg0;
        return getMessage(caller, BASE_NAME, locale, key, o);
    }

    /**
      * Get a message from resource.properties from the package of the given object.
      * @param caller The calling object, used to get the package name and class loader
      * @param locale The locale
      * @param key The resource key
      * @param arg0 The argument to place in variable {0}
      * @param arg1 The argument to place in variable {1}
      * @return The formatted message
      */
    public static String getString(Object caller, Locale locale, String key, Object arg0, Object arg1)
        throws MissingResourceException
    {
        Object[] o = new Object[2];
        o[0] = arg0;
        o[1] = arg1;
        return getMessage(caller, BASE_NAME, locale, key, o);
    }

    /**
      * Get a message from resource.properties from the package of the given object.
      * @param caller The calling object, used to get the package name and class loader
      * @param locale The locale
      * @param key The resource key
      * @param arg0 The argument to place in variable {0}
      * @param arg1 The argument to place in variable {1}
      * @param arg2 The argument to place in variable {2}
      * @return The formatted message
      */
    public static String getString(Object caller, Locale locale, String key, Object arg0, Object arg1, Object arg2)
        throws MissingResourceException
    {
        Object[] o = new Object[3];
        o[0] = arg0;
        o[1] = arg1;
        o[2] = arg2;
        return getMessage(caller, BASE_NAME, locale, key, o);
    }

    /**
      * Get a message from resource.properties from the package of the given object.
      * @param caller The calling object, used to get the package name and class loader
      * @param locale The locale
      * @param key The resource key
      * @param arg0 The argument to place in variable {0}
      * @param arg1 The argument to place in variable {1}
      * @param arg2 The argument to place in variable {2}
      * @param arg3 The argument to place in variable {3}
      * @return The formatted message
      */
    public static String getString(Object caller, Locale locale, String key, Object arg0, Object arg1, Object arg2, Object arg3)
        throws MissingResourceException
    {
        Object[] o = new Object[4];
        o[0] = arg0;
        o[1] = arg1;
        o[2] = arg2;
        o[3] = arg3;
        return getMessage(caller, BASE_NAME, locale, key, o);
    }

    /**
      * Get a message from resource.properties from the package of the given object.
      * @param caller The calling object, used to get the package name and class loader
      * @param locale The locale
      * @param key The resource key
      * @param arg0 The argument to place in variable {0}
      * @param arg1 The argument to place in variable {1}
      * @param arg2 The argument to place in variable {2}
      * @param arg3 The argument to place in variable {3}
      * @return The formatted message
      */
    public static String getString(Object caller, Locale locale, String key, Object arg0, Object arg1, Object arg2, Object arg3, Object arg4)
        throws MissingResourceException
    {
        Object[] o = new Object[5];
        o[0] = arg0;
        o[1] = arg1;
        o[2] = arg2;
        o[3] = arg3;
        o[4] = arg4;
        return getMessage(caller, BASE_NAME, locale, key, o);
    }

    /**
      * Get a message from resource.properties from the package of the given object.
      * @param caller The calling object, used to get the package name and class loader
      * @param locale The locale
      * @param key The resource key
      * @param array An array of objects to place in corresponding variables
      * @return The formatted message
      */
    public static String getString(Object caller, Locale locale, String key, Object[] args)
        throws MissingResourceException
    {
        return getMessage(caller, BASE_NAME, locale, key, args);
    }

    // Workhorse that does the resource loading and key lookup
    public static String getMessage(Object caller, String basename, Locale locale, String key,
                                       Object[] args)
        throws MissingResourceException
    {
        String msg = null;
        MissingResourceException firstEx = null;
        String fullName = null;
        Class curClass = null;
        boolean didNull = false;
        
        if (caller != null) {
            if(caller instanceof Class)
                curClass = (Class) caller;
            else
                curClass = caller.getClass();
        }

        while (msg == null) {

            // Get the full name of the resource
            if (curClass != null) {

                // Create the full basename
                String pkgName = curClass.getName();

                int pos = pkgName.lastIndexOf(".");
                if (pos > 0) {
                    fullName = pkgName.substring(0, pos + 1).replace('.', '/') + basename;
                }
                else {
                    fullName = basename;
                }
            }
            else {
                fullName = basename;
            }

            try {
                RB rb = new RB(caller, fullName, locale);
                msg = rb.getString(key, args);
            }
            catch (MissingResourceException ex) {
                if (curClass == null) {
                    throw ex;
                }

                // Save the first exception
                if (firstEx == null) {
                    firstEx = ex;
                }

                // Get the superclass
                curClass = curClass.getSuperclass();
                if (curClass == null) {
                    if (didNull)
                        throw firstEx;
                    didNull = true;
                    caller = null;
                } else {
                    String cname = curClass.getName();
                    if (cname.startsWith("java.") ||
                        cname.startsWith("javax.")) {
                        if (didNull)
                            throw firstEx;
                        didNull = true;
                        caller = null;
                        curClass = null;
                    }
                }
            }

        }
        return msg;
    }

    /**
      * Clears the internal cache
      */
    public static void clearCache()
    {
        propertyCache.clear();
    }
}
