

/**

ResourceBundle.getBundle(...) → internally calls ClassLoader.getResourceAsStream(...).
The ClassLoader call uses a global Hashtable to cache URLStreamHandlers.
That Hashtable is fully synchronized, so only one thread can access it at a time.
With many threads calling ResourceBundle.getBundle(...) concurrently, you get lock contention, i.e., BLOCKED threads.

Solution: cache bundles using a concurrent cache for bundles keyed by resource_name + locale + custom_dir
Note using custom_dir here instead of classloader as there might be different classloaders trying to get the same resource
from the same directory

ConcurrentHashMap + computeIfAbsent → thread-safe, only one initialization per (resource, locale, loader) key.
 */

package org.greenstone.gsdl3.util;


import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;


public class ResourceBundleManager {

    // Unique key per (resource, locale, custom_dir)
    private static class BundleKey {
        private final String resource;
        private final Locale locale;
        private final String custom_dir;

        BundleKey(String resource, Locale locale, String custom_dir) {
            this.resource = resource;
            this.locale = locale;
	    this.custom_dir = (custom_dir != null) ? custom_dir : "";
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof BundleKey)) return false;
            BundleKey other = (BundleKey) o;
            return Objects.equals(resource, other.resource)
                    && Objects.equals(locale, other.locale)
                    && Objects.equals(custom_dir, other.custom_dir);
        }

        @Override
        public int hashCode() {
            return Objects.hash(resource, locale, custom_dir);
        }
    }

    // a placeholder for a not found result so we don't keep looking for the same resource over and over
    public static final ResourceBundle NOT_FOUND = new ResourceBundle() {
	    @Override
	    protected Object handleGetObject(String key) { return null; }
	    @Override
	    public Enumeration<String> getKeys() { return Collections.emptyEnumeration(); }
	};

    
    // Thread-safe cache of preloaded bundles
    private static final ConcurrentMap<BundleKey, ResourceBundle> bundleCache = new ConcurrentHashMap<>();

    /**
     * Preload a set of bundles from default locations
     * This is called by our ServletStartupListener
     * Ensures no first-request blocking occurs.
     */
    public static void preloadBundles(Set<String> resources, Set<String> langs) {
        for (String resource : resources) {
            for (String lang : langs) {
		Locale locale = new Locale(lang);

		System.err.println("preloading "+resource+" for lang "+lang);
                ResourceBundle bundle = ResourceBundle.getBundle(resource, locale, new UTF8Control());

                // Cache it
                BundleKey key = new BundleKey(resource, locale, "");
                bundleCache.putIfAbsent(key, bundle);
            }
        }
    }

    public static void preloadBundlesCustomDir(Set<String> resources, Set<String> langs, String custom_dir) {
	//        ClassLoader effectiveLoader = (loader != null) ? loader : ClassLoader.getSystemClassLoader();

	CustomClassLoader my_loader = new CustomClassLoader(ResourceBundleManager.class.getClassLoader(), custom_dir);

        for (String resource : resources) {
            for (String lang : langs) {
		Locale locale = new Locale(lang);

		System.err.println("preloading "+resource+" for lang "+lang+", custom dir "+custom_dir);
                ResourceBundle bundle = ResourceBundle.getBundle(resource, locale, my_loader, new UTF8Control());

                // Cache it
                BundleKey key = new BundleKey(resource, locale, custom_dir);
                bundleCache.putIfAbsent(key, bundle);
            }
        }
    }

    /**
     * Get a cached bundle. Loads lazily only if not preloaded.
     */
    public static ResourceBundle getBundle(String resource, Locale locale, String custom_dir) {
	String local_dir = (custom_dir == null) ? "": custom_dir;
	BundleKey key = new BundleKey(resource, locale, local_dir);
	//	System.err.println("getbundle for "+resource+" lang "+locale.getLanguage()+",, "+custom_dir);
        return bundleCache.computeIfAbsent(key, k -> {
            try {
		ClassLoader default_loader = ResourceBundleManager.class.getClassLoader();
		ClassLoader my_loader = local_dir.isEmpty() ? default_loader : new CustomClassLoader(default_loader, local_dir);
		//System.err.println("not in cache");
                return ResourceBundle.getBundle(resource, locale, my_loader, new UTF8Control());
            } catch (MissingResourceException e) {
		//System.err.println("returning null");
		return NOT_FOUND;
		// fallback to default locale
                //return ResourceBundle.getBundle(resource, Locale.getDefault(), effectiveLoader, new UTF8Control());
            }
        });
    }
}
