/* Copyright (c) 2001-2024, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * 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.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS 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 HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR 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.
 */


package org.hsqldb.resources;

import java.lang.reflect.Method;

import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

import org.hsqldb.lib.HashMap;
import org.hsqldb.lib.HsqlArrayList;

/**
 * A ResourceBundle helper class. <p>
 *
 * Allows clients to get/set locale and get at localized resource bundle
 * content in a resource path independent manner, without having to worry
 * about handling exception states or deal directly with ResourceBundle
 * object instances. Instead, clients receive numeric handles to the
 * underlying objects.  Rather than causing exception states, missing or
 * inaccessible resources and underlying MissingResource and NullPointer
 * exceptions result in null return values when attempting to retrieve a
 * resource. <p>
 *
 * @author Campbell Burnet (campbell-burnet@users dot sourceforge.net)
 * @version 1.7.2
 * @since 1.7.2
 */
public final class ResourceBundleHandler {

    /** Used to synchronize access */
    private static final Object mutex = new Object();

    /** The Locale used internally to fetch resource bundles. */
    private static Locale locale = Locale.getDefault();

    /** Map:  Integer object handle maps to: {@code ResourceBundle} object. */
    private static HashMap<String, Integer> bundleHandleMap = new HashMap<>();

    /** List whose elements are {@code ResourceBundle} objects */
    private static HsqlArrayList<ResourceBundle> bundleList =
        new HsqlArrayList<>();

    /**
     * The resource path prefix of the {@code ResourceBundle} objects
     * handled by this class.
     */
    private static final String prefix = "org.hsqldb.resources.";

    /** JDK 1.1 compliance */
    private static final Method newGetBundleMethod = getNewGetBundleMethod();

    /** Pure utility class: external construction disabled. */
    private ResourceBundleHandler() {}

    /**
     * Getter for property locale. <p>
     *
     * @return Value of property locale.
     */
    public static Locale getLocale() {
        synchronized (mutex) {
            return locale;
        }
    }

    /**
     * Setter for property locale. <p>
     *
     * @param l the new locale
     * @throws IllegalArgumentException when the new locale is null
     */
    public static void setLocale(Locale l) throws IllegalArgumentException {

        synchronized (mutex) {
            if (l == null) {
                throw new IllegalArgumentException("null locale");
            }

            locale = l;
        }
    }

    /**
     * Retrieves an {@code int} handle to the {@code ResourceBundle}
     * object corresponding to the specified name and current
     * {@code Locale}, using the specified {@code ClassLoader}. <p>
     *
     * @return {@code int} handle to the {@code ResourceBundle}
     *        object corresponding to the specified name and
     *        current {@code Locale}, or -1 if no such bundle
     *        can be found
     * @param cl The ClassLoader to use in the search
     * @param name of the desired bundle
     */
    public static int getBundleHandle(String name, ClassLoader cl) {

        Integer        bundleHandle;
        ResourceBundle bundle;
        String         bundleName;
        String         bundleKey;

        bundleName = prefix + name;

        synchronized (mutex) {
            bundleKey    = locale.toString() + bundleName;
            bundleHandle = bundleHandleMap.get(bundleKey);

            if (bundleHandle == null) {
                bundle = getBundle(bundleName, locale, cl);

                bundleList.add(bundle);

                bundleHandle = Integer.valueOf(bundleList.size() - 1);

                bundleHandleMap.put(bundleKey, bundleHandle);
            }
        }

        return bundleHandle.intValue();
    }

    /**
     * Retrieves, from the {@code ResourceBundle} object corresponding
     * to the specified handle, the {@code String} value corresponding
     * to the specified key.  {@code null} is retrieved if either there
     *  is no {@code ResourceBundle} object for the handle or there is no
     * {@code String} value for the specified key. <p>
     *
     * @param handle an {@code int} handle to a
     *      {@code ResourceBundle} object
     * @param key A {@code String} key to a {@code String} value
     * @return The String value corresponding to the specified handle and key.
     */
    public static String getString(int handle, String key) {

        ResourceBundle bundle;
        String         s;

        synchronized (mutex) {
            if (handle < 0 || handle >= bundleList.size() || key == null) {
                bundle = null;
            } else {
                bundle = bundleList.get(handle);
            }
        }

        if (bundle == null) {
            s = null;
        } else {
            try {
                s = bundle.getString(key);
            } catch (Exception e) {
                s = null;
            }
        }

        return s;
    }

    /**
     * One-shot initialization of JDK 1.2+ ResourceBundle.getBundle() method
     * having ClassLoader in the signature.
     */
    private static Method getNewGetBundleMethod() {

        Class<ResourceBundle> clazz;
        Class[]               args;

        clazz = ResourceBundle.class;
        args  = new Class[]{ String.class, Locale.class, ClassLoader.class };

        try {
            return clazz.getMethod("getBundle", args);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * Retrieves a resource bundle using the specified base name, locale, and
     * class loader. This is a JDK 1.1 compliant substitution for the
     * ResourceBundle method with the same name and signature. If there
     * is a problem using the JDK 1.2 functionality (the class loader is
     * specified non-null and the underlying method is not available or there
     * is a security exception, etc.), then the behaviour reverts to that
     * of JDK 1.1.
     *
     * @param name the base name of the resource bundle, a fully
     *      qualified class name
     * @param locale the locale for which a resource bundle is desired
     * @param cl the class loader from which to load the resource bundle
     */
    public static ResourceBundle getBundle(
            String name,
            Locale locale,
            ClassLoader cl)
            throws NullPointerException,
                   MissingResourceException {

        if (cl == null) {
            return ResourceBundle.getBundle(name, locale);
        } else if (newGetBundleMethod == null) {
            return ResourceBundle.getBundle(name, locale);
        } else {
            try {
                return (ResourceBundle) newGetBundleMethod.invoke(
                    null,
                    new Object[]{ name, locale, cl });
            } catch (Exception e) {
                return ResourceBundle.getBundle(name, locale);
            }
        }
    }
}
