/*
* (C) 2004 - Geotechnical Software Services
*
* This code is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, write to the Free
* Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
* MA 02111-1307, USA.
*/
package no.geosoft.cc.locale;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.ref.WeakReference;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
/**
* Class for holding current locale information, and notify client
* application about locale changes. Used for dynamic (or static)
* locale updates and language translations during a program session.
* <p>
* For language dependent user interfaces, put translation files into
* the packages where reguired. The files are named as follows:
* <tt>Messages_xx_XX.properties</tt> where <em>xx</em> is the laguage
* identifier and <em>XX</em> is the country identifier, for instance
*
* <pre>
* Messages_en_US.properties // american
* Messages_fr_FR.properties // french
* Messages_ch_CH.properties // chineese
* </pre>
*
* and so on. These are ordinary properties files containing language
* translations, for instance (french):
*
* <pre>
* Close = Fermer
* New = Nouveau
* Help = Aide
* </pre>
*
* and (american):
*
* <pre>
* Close = Close
* New = New
* Help = Help
* </pre>
*
* <p>
* A locale aware GUI will use the locale manager like this:
*
* <pre>
* JButton helpButton = new JButton();
* :
* helpButton.setText (localeManager.getText ("Help"));
* </pre>
*
* If the latter is done inside a localeChanged callback, the GUI will
* update itself dynamically if locale is changed during a session.
*
* @author <a href="mailto:jacob.dreyer@geosoft.no">Jacob Dreyer</a>
*/
public class LocaleManager
{
private static LocaleManager instance_ = null;
private Locale defaultLocale_;
private Locale currentLocale_;
private HashMap resourceBundles_; // String -> ResourceBundle
private Collection listeners_; // of WeakReference (LocaleListener)
/**
* Create the locale manager instance.
*/
private LocaleManager()
{
instance_ = this;
currentLocale_ = null;
defaultLocale_ = null;
resourceBundles_ = new HashMap();
listeners_ = null;
// Set locale for this JVM. This is independent of the default
// locale of the application.
Locale.setDefault (new Locale ("en", "US"));
setDefaultLocale ("en", "US");
setLocale ("en", "US");
}
/**
* Return the sole instance of this class.
*
* @return The sole locale manager instance.
*/
public static LocaleManager getInstance()
{
if (instance_ == null)
instance_ = new LocaleManager();
return instance_;
}
/**
* Set the default locale. If a translation is not found for a key
* in the current locale, the current locale is used instead.
*
* @param language Language of default locale.
* @param country Country of default locale.
*/
public void setDefaultLocale (String language, String country)
{
defaultLocale_ = new Locale (language, country);
}
/**
* Set current locale.
*
* @param locale New current locale.
*/
public void setLocale (Locale locale)
{
setLocale (locale.getLanguage(), locale.getCountry());
}
/**
* Return current locale.
*
* @return Current locale.
*/
public Locale getLocale()
{
return currentLocale_;
}
/**
* Set the current locale, and notify listeners about the change.
*
* @param language Language of new locale.
* @param country Country of new locale.
*/
public void setLocale (String language, String country)
{
// Leave here if no change
if (currentLocale_ != null) {
if (language.equals (currentLocale_.getLanguage()) &&
country.equals (currentLocale_.getCountry()))
return;
}
// Create the new locale
currentLocale_ = new Locale (language, country);
// Clear resource bundles
resourceBundles_.clear();
// Call listeners
if (listeners_ != null) {
for (Iterator i = listeners_.iterator(); i.hasNext(); ) {
WeakReference reference = (WeakReference) i.next();
LocaleListener listener = (LocaleListener) reference.get();
if (listener == null)
i.remove();
else
listener.localeChanged();
}
}
}
/**
* Find resource bundle of specified name.
*
* @param bundleName Name of bundle to find.
* @return Requested resource bundle (or null if not found).
*/
private ResourceBundle findResourceBundle (String bundleName)
{
ResourceBundle resourceBundle;
if (!resourceBundles_.containsKey (bundleName)) {
try {
resourceBundle = ResourceBundle.getBundle (bundleName,
currentLocale_);
}
catch (MissingResourceException exception1) {
try {
resourceBundle = ResourceBundle.getBundle (bundleName,
defaultLocale_);
}
catch (MissingResourceException exception2) {
resourceBundle = null;
}
}
if (resourceBundle != null)
resourceBundles_.put (bundleName, resourceBundle);
}
resourceBundle = (ResourceBundle) resourceBundles_.get (bundleName);
return resourceBundle;
}
/**
* Returned localized text for specified text. Search resource within
* specified package.
*
* @param packageName Name of package to locate resource.
* @param tag Text tag.
* @return Localized text.
*/
private String getText (String packageName, String tag)
{
try {
ResourceBundle resourceBundle = (ResourceBundle) resourceBundles_.
get (packageName);
if (resourceBundle == null) {
String bundleName = packageName + ".Messages";
resourceBundle = ResourceBundle.getBundle (bundleName,
currentLocale_);
resourceBundles_.put (packageName, resourceBundle);
}
return resourceBundle.getString (tag);
}
catch (Exception exception) {
// The bundle of the value was not found; return tag instead
return tag;
}
}
/**
* Return localized text for a specified tag. The text is looked up
* in the resource bundle of the package of the caller.
*
* @param tag Tag of text to locate.
* @return Localized text of tag.
*/
public String getText (String tag)
{
// Find calling class
StringWriter stringWriter = new StringWriter();
new Throwable().printStackTrace (new PrintWriter(stringWriter));
String callStack = stringWriter.toString();
int pos1 = callStack.indexOf ("at ");
pos1 = callStack.indexOf ("at ", pos1 + 2) + 3;
int pos2 = callStack.indexOf ("(", pos1);
String line = callStack.substring (pos1, pos2);
line = line.substring (0, line.lastIndexOf ("."));
String packageName = line.substring (0, line.lastIndexOf ("."));
return getText (packageName, tag);
}
/**
* Add a locale listener. The listener is notified when the current
* locale of the LocaleManager is changed.
*
* @param localeListener Listener to add.
*/
public void addLocaleListener (LocaleListener localeListener)
{
if (listeners_ == null)
listeners_ = new ArrayList();
// Check to see if it is there already
for (Iterator i = listeners_.iterator(); i.hasNext(); ) {
WeakReference reference = (WeakReference) i.next();
LocaleListener listener = (LocaleListener) reference.get();
if (listener == localeListener)
return;
}
// Add the listener
listeners_.add (new WeakReference (localeListener));
}
/**
* Remove a locale listener.
*
* @param localeListener Locale listener to remove.
*/
public void removeLocaleListener (LocaleListener localeListener)
{
if (listeners_ == null) return;
for (Iterator i = listeners_.iterator(); i.hasNext(); ) {
WeakReference reference = (WeakReference) i.next();
LocaleListener listener = (LocaleListener) reference.get();
if (listener == localeListener) {
i.remove();
break;
}
}
}
/**
* Get default date format for the current locale.
*
* @param style One of DateFormat.SHORT, MEDIUM or LONG.
* @return Date format for current locale.
*/
public DateFormat getDateFormat (int style)
{
return DateFormat.getDateInstance (style, currentLocale_);
}
/**
* Get default time format for the current locale.
*
* @param style One of DateFormat.SHORT, MEDIUM or LONG.
* @return Time format for current locale.
*/
public DateFormat getTimeFormat (int style)
{
DateFormat dateFormat = DateFormat.getTimeInstance (style, currentLocale_);
// TODO: Check this
String pattern = ((SimpleDateFormat) dateFormat).toPattern();
if (style == DateFormat.LONG){
int ind = pattern.lastIndexOf('s');
String newPattern = null;
if (ind > 0){
newPattern = pattern.substring(0,ind+1)+".SSS";
if (ind+1 < pattern.length()){
newPattern += pattern.substring(ind+1,pattern.length());
}
}
pattern = newPattern;
}
if ((pattern.indexOf("h") == pattern.lastIndexOf("h")) &&
(pattern.indexOf("h") != -1)) {
pattern = pattern.replaceAll("h","HH");
}
else {
pattern = pattern.replaceAll("h","H");
}
pattern = pattern.replaceAll("a","");
pattern = pattern.replaceAll("z","");
pattern = pattern.trim();
((SimpleDateFormat)dateFormat).applyPattern (pattern);
return dateFormat;
}
/**
* Get defaut date/time format for current locale.
*
* @param dateStyle One of DateFormat.SHORT, MEDIUM or LONG.
* @param timeStyle One of DateFormat.SHORT, MEDIUM or LONG.
* @return Date/time format for the currnt locale.
*/
public DateFormat getDateTimeFormat (int dateStyle, int timeStyle)
{
DateFormat dateFormat = DateFormat.getDateTimeInstance (dateStyle, timeStyle, currentLocale_);
String datePattern = ((SimpleDateFormat)DateFormat.getDateInstance(dateStyle,currentLocale_)).toPattern();
DateFormat timeFormat = getTimeFormat(timeStyle);
if (timeFormat instanceof SimpleDateFormat){
String timePattern = ((SimpleDateFormat)timeFormat).toPattern();
String pattern = datePattern + " " + timePattern;
((SimpleDateFormat)dateFormat).applyPattern(pattern);
}
return dateFormat;
}
/**
* Return default localized text reprentation of the specified date.
*
* @param date Date to format.
* @return String representation of specified date.
*/
public String getDate (Date date)
{
return getDate (date, DateFormat.SHORT);
}
/**
* Return default localized text reprentation of the specified date.
*
* @param date Date to format.
* @param style DateFormat.SHORT, MEDIUM or LONG
* @return String representation of specified date.
*/
public String getDate (Date date, int style)
{
if (date == null) return "";
DateFormat dateFormat = getDateFormat (style);
return dateFormat.format (date);
}
/**
* Return default localized text representation of the specified time.
*
* @param date Time to format.
* @param style DateFormat.SHORT, MEDIUM or LONG
* @return String representation of specified time.
*/
public String getTime (Date date, int style)
{
if (date == null) return "";
DateFormat dateFormat = getTimeFormat (style);
return dateFormat.format (date);
}
/**
* Return default localized text representation of the specified time.
*
* @param date Time to format.
* @return String representation of specified time.
*/
public String getTime (Date date)
{
return getTime (date, DateFormat.SHORT);
}
/**
* Return default localized text representation of the specified date/time.
*
* @param date Date/time to format.
* @param dateStyle One of DateFormat.SHORT, MEDIUM or LONG.
* @param timeStyle One of DateFormat.SHORT, MEDIUM or LONG.
* @return String representation of specified date/time.
*/
public String getDateTime (Date date, int dateStyle, int timeStyle)
{
if (date == null) return "";
DateFormat dateFormat = getDateTimeFormat (dateStyle, timeStyle);
return dateFormat.format (date);
}
/**
* Return default localized text representation of the specified date/time.
*
* @param date Date/time to format.
* @return String representation of specified date/time.
*/
public String getDateTime (Date date)
{
return getDateTime (date, DateFormat.SHORT, DateFormat.SHORT);
}
}