// ============================================================================
// File:               I18n4Java2.java
//
// Project:            I18n support for the Java 1.2+ platform.
//
// Purpose:            Application specific support for internationalization.
//
// Author:             Rammi
//-----------------------------------------------------------------------------
// Copyright Notice:   (c) 2002  Rammi (rammi@caff.de)
//                     This code is in the public domain.
//                     Use at own risk.
//                     No guarantees given.
//
// Latest change:      $Date$
//
// History:	       $Log$
//=============================================================================

package de.caff.i18n.java2;

import de.caff.i18n.I18n;
import de.caff.i18n.Localizable;
import de.caff.i18n.ResourceBundleCollection;

import java.lang.ref.WeakReference;
import java.util.*;

/**
 *  Application specific support for internationalization,
 *  used for Java 1.2+. 
 *  This uses the weak reference feature to allow for easier garbage collection.
 *
 *  @author <a href="mailto:rammi@caff.de">Rammi</a>
 *  @version $Revision$
 */
public class I18n4Java2 extends I18n
{
  /** The resource bundles. */
  protected Map       resourceBundles  = new HashMap();
  /** The resource bases. */
  protected Set       appResourceBases = new HashSet();
  /** The default locale. */
  protected Locale    defaultLocale    = Locale.getDefault();

  /**
   *  Collection of known localizables, which have to be informed
   *  of localization changes.
   *  <p>
   *  The localizables are only weakly referenced here.
   */
  protected List localizables     = new ArrayList();


  /**
   *  Add an application specific resource class base name.
   *  This should be called by any application/applet before
   *  any other i18n specific code is used.
   *  @param  base  base class name for resources
   *  @see java.util.ResourceBundle
   */
  protected void _addAppResourceBase(String base)
  {
    if (!appResourceBases.contains(base)) {
      appResourceBases.add(base);
      resourceBundles.clear();
    }
  }

  /**
   *  This method is here because some JVM fail to load fallback resources.
   *  @param name full qualified resource class name
   *  @return fallback resources
   */
  private static ResourceBundle getFallbackResourceBundle(String name)
  {
    ResourceBundle bundle;
    Class resClass;
    try {
      resClass = Class.forName(name);
    } catch (ClassNotFoundException e1) {
      throw new MissingResourceException("Can't load fallback resources "+name, name, "");
    }
    try {
      bundle = (ResourceBundle)resClass.newInstance();
    } catch (Exception e1) {
      throw new RuntimeException("Cannot create ResourceBundle "+name);
    }
    return bundle;
  }


  /**
   *  Set the locale to be used as a default for the application.
   *  @param  l   locale to be used as default
   */
  protected void _setDefaultLocale(Locale l)
  {
    // System.out.println("Default locale: "+l);
    if (l == null) {
      l = Locale.getDefault();
    }
    defaultLocale = l;

    _fireLocaleChanged(l);
  }

  /**
   *  Get the locale to be used as a default for the application.
   *  @return   locale to be used as default
   */
  protected Locale _getDefaultLocale() 
  {
    return defaultLocale;
  }

  /**
   *  Get a ResourceBundle for a locale.
   *  @param  l  locale
   *  @return ResourceBundle for that Locale
   *  @exception MissingResourceException  when no appResourceBase is set
   */
  protected ResourceBundle _getBundle(Locale l) 
  {
    if (l == null) {
      l = defaultLocale;
    }
    ResourceBundle res = (ResourceBundle)resourceBundles.get(l);
    if (res == null) {
      // this resource is unknown yet
      if (appResourceBases.size() == 0) {
	throw new MissingResourceException("No application specific resource base defined", 
					   "<unknown>",
					   "");
      }
     
      ResourceBundleCollection collect = new ResourceBundleCollection();

      for (Iterator e = appResourceBases.iterator();   e.hasNext();   ) {
        String baseName = (String)e.next();
        try {
          collect.addResourceBundle(ResourceBundle.getBundle(baseName, l));
        } catch (MissingResourceException e1) {
          collect.addResourceBundle(getFallbackResourceBundle(baseName));
        }
      }
	
      resourceBundles.put(l, collect);
	   
      return collect;
    }
    else {
      return res;
    }
  }


  /**
   *  Add a listener for localization changes.
   *  @param  localizable  listener for changes
   */
  protected void _addLocalizationChangeListener(Localizable localizable)
  {
    localizables.add(new WeakReference(localizable));
    localizable.setLocale(_getDefaultLocale());
  }

  /**
   *  Remove a listener for localization changes.
   *  @param  localizable  listener to be removed
   */
  protected void _removeLocalizationChangeListener(Localizable localizable)
  {
    localizables.remove(localizable);
  }

  /**
   *  Tell all registered localizables of localization changes.
   *  @param  locale  new locale
   */
  protected void _fireLocaleChanged(Locale locale)
  {
    for (ListIterator it = localizables.listIterator(); it.hasNext(); ) {
      WeakReference ref = (WeakReference)it.next();
      Localizable loc = (Localizable)ref.get();
      if (loc != null) {
	loc.setLocale(locale);
      }
      else {
	// no longer referenced
	it.remove();
      }
    }
  }
}




