/*
 * The Apache Software License, Version 1.1
 *
 *
 * Copyright (c) 2001-2003 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Axis" and "Apache Software Foundation" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    nor may "Apache" appear in their name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */

package org.jboss.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/>
 * <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@macromedia.com)
 */
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/jboss/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 en = p2.keys();
      while (en.hasMoreElements())
      {
         String key = (String)en.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();
   }
}