blob: fada12fc9a6805a9fe1fdb126843ad07c8fdbd06 [file] [log] [blame]
/**
*******************************************************************************
* Copyright (C) 2001-2002, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*
* $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/impl/ICULocaleService.java,v $
* $Date: 2002/08/13 23:40:52 $
* $Revision: 1.5 $
*
*******************************************************************************
*/
package com.ibm.icu.impl;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
public class ICULocaleService extends ICUService {
Locale fallbackLocale;
String fallbackLocaleName;
/**
* Construct an ICULocaleService with a fallback locale string based on the current
* default locale at the time of construction.
*/
public ICULocaleService() {
fallbackLocale = Locale.getDefault();
fallbackLocaleName = LocaleUtility.canonicalLocaleString(fallbackLocale.toString());
}
/**
* A subclass of Key that implements a locale fallback mechanism.
* The first locale to search for is the locale provided by the
* client, and the fallback locale to search for is the current default
* locale. This is instantiated by ICULocaleService.</p>
*
* <p>Canonicalization adjusts the locale string so that the
* section before the first understore is in lower case, and the rest
* is in upper case, with no trailing underscores.</p>
*/
public static class LocaleKey extends ICUService.Key {
private String primaryID;
private String fallbackID;
private String currentID;
/**
* Convenience method for createWithCanonical that canonicalizes both the
* primary and fallback IDs first.
*/
public static LocaleKey create(String primaryID, String fallbackID) {
String canonicalPrimaryID = LocaleUtility.canonicalLocaleString(primaryID);
String canonicalFallbackID = LocaleUtility.canonicalLocaleString(fallbackID);
return new LocaleKey(primaryID, canonicalPrimaryID, canonicalFallbackID);
}
/**
* Convenience method for createWithCanonical that canonicalizes the
* primary ID first, the fallback is assumed to already be canonical.
*/
public static LocaleKey createWithCanonicalFallback(String primaryID, String canonicalFallbackID) {
String canonicalPrimaryID = LocaleUtility.canonicalLocaleString(primaryID);
return new LocaleKey(primaryID, canonicalPrimaryID, canonicalFallbackID);
}
/**
* Create a LocaleKey with canonical primary and fallback IDs.
*/
public static LocaleKey createWithCanonical(String canonicalPrimaryID, String canonicalFallbackID) {
return new LocaleKey(canonicalPrimaryID, canonicalPrimaryID, canonicalFallbackID);
}
/**
* PrimaryID is the user's requested locale string,
* canonicalPrimaryID is this string in canonical form,
* fallbackID is the current default locale's string in
* canonical form.
*/
protected LocaleKey(String primaryID, String canonicalPrimaryID, String canonicalFallbackID) {
super(primaryID);
if (canonicalPrimaryID == null) {
this.primaryID = "";
} else {
this.primaryID = canonicalPrimaryID;
}
if (this.primaryID == "") {
this.fallbackID = null;
} else {
if (canonicalFallbackID == null || this.primaryID.equals(canonicalFallbackID)) {
this.fallbackID = "";
} else {
this.fallbackID = canonicalFallbackID;
}
}
this.currentID = this.primaryID;
}
/**
* Return the (canonical) original ID.
*/
public String canonicalID() {
return primaryID;
}
/**
* Return the (canonical) current ID.
*/
public String currentID() {
return currentID;
}
/**
* If the key has a fallback, modify the key and return true,
* otherwise return false.</p>
*
* <p>First falls back through the primary ID, then through
* the fallbackID. The final fallback is the empty string,
* unless the primary id was the empty string, in which case
* there is no fallback.
*/
public boolean fallback() {
String current = currentID();
int x = current.lastIndexOf('_');
if (x != -1) {
currentID = current.substring(0, x);
return true;
}
if (fallbackID != null) {
currentID = fallbackID;
fallbackID = fallbackID.length() == 0 ? null : "";
return true;
}
currentID = null;
return false;
}
}
/**
* This is a factory that handles multiple keys, and records
* information about the keys it handles or doesn't handle. This
* allows it to quickly filter subsequent queries on keys it has
* seen before. Subclasses implement handleCreate instead of
* create. Before updateVisibleIDs is called, it keeps track of
* keys that it doesn't handle. If its ids are visible, once
* updateVisibleIDs is called, it builds a set of all the keys it
* does handle and keeps track of the keys it does handle.
*/
public static abstract class MultipleKeyFactory implements ICUService.Factory {
protected final boolean visible;
private SoftReference cacheref;
private boolean included;
/**
* Convenience overload of MultipleKeyFactory(boolean) that defaults
* visible to true.
*/
public MultipleKeyFactory() {
this(true);
}
/**
* Constructs a MultipleKeyFactory whose ids are visible iff visible is true.
*/
public MultipleKeyFactory(boolean visible) {
this.visible = visible;
}
/**
* Get the cache of IDs. These are either the ids that we know we
* don't understand, if included is false, or the entire set of ids
* we do know we understand, if included is true. Note that if
* the cache has been freed by gc, we reset the included flag, so
* it must not be tested before this method is called.
*/
private HashSet getCache() {
HashSet cache = null;
if (cacheref != null) {
cache = (HashSet)cacheref.get();
}
if (cache == null) {
cache = new HashSet();
cacheref = new SoftReference(cache);
included = false;
}
return cache;
}
/**
* Get the cache of IDs we understand.
*/
private HashSet getIncludedCache() {
HashSet cache = getCache();
if (!included) {
cache.clear();
handleUpdateVisibleIDs(cache);
included = true;
}
return cache;
}
public final Object create(Key key) {
Object result = null;
String id = key.currentID();
HashSet cache = getCache();
if (cache.contains(id) == included) {
result = handleCreate(key);
if (!included && result == null) {
cache.add(id);
}
}
return result;
}
public final void updateVisibleIDs(Map result) {
if (visible) {
Set cache = getIncludedCache();
Iterator iter = cache.iterator();
while (iter.hasNext()) {
result.put((String)iter.next(), this);
}
}
}
public final String getDisplayName(String id, Locale locale) {
if (visible) {
Set cache = getIncludedCache();
if (cache.contains(id)) {
return handleGetDisplayName(id, locale);
}
}
return null;
}
/**
* Subclasses implement this instead of create.
*/
protected abstract Object handleCreate(Key key);
/**
* Subclasses implement this instead of updateVisibleIDs. Any
* id known to and handled by this class should be added to
* result.
*/
protected abstract void handleUpdateVisibleIDs(Set result);
/**
* Subclasses implement this instead of getDisplayName.
* Return the display name for the (visible) id in the
* provided locale. The default implementation just returns
* the id.
*/
protected String handleGetDisplayName(String id, Locale locale) {
return id;
}
}
/**
* A factory that creates a service based on the ICU locale data.
* Subclasses specify a prefix (default is LocaleElements), a
* semicolon-separated list of required resources, and a visible flag.
* This factory will search the ICU locale data for a bundle with
* the exact prefix. Then it will test whether the required resources
* are all in this exact bundle. If so, it instantiates the full
* resource bundle, and hands it to createServiceFromResource, which
* subclasses must implement. Otherwise it returns null.
*/
public static class ICUResourceBundleFactory extends MultipleKeyFactory {
protected final String name;
protected final String[] requiredContents;
/**
* A service factory based on ICU resource data in the LocaleElements resources.
*/
public ICUResourceBundleFactory(String requiredContents, boolean visible) {
this(ICULocaleData.LOCALE_ELEMENTS, requiredContents, visible);
}
/**
* A service factory based on ICU resource data in resources
* with the given name. If requiredContents is not null, all
* listed resources must come directly from the same bundle.
*/
public ICUResourceBundleFactory(String name, String requiredContents, boolean visible) {
super(visible);
this.name = name;
if (requiredContents != null) {
ArrayList list = new ArrayList();
for (int i = 0, len = requiredContents.length();;) {
while (i < len && requiredContents.charAt(i) == ';') {
++i;
}
if (i == len) {
break;
}
int j = requiredContents.indexOf(';', i);
if (j == -1) {
j = len;
}
list.add(requiredContents.substring(i, j));
i = j;
}
this.requiredContents = (String[])list.toArray(new String[list.size()]);
} else {
this.requiredContents = null;
}
}
/**
* Overrides parent handleCreate call. Parent will filter out keys that it
* knows are not accepted by this factory before calling this method.
*/
protected Object handleCreate(Key key) {
Locale loc = LocaleUtility.getLocaleFromName(key.currentID());
if (acceptsLocale(loc)) {
ResourceBundle bundle = ICULocaleData.getResourceBundle(name, loc); // full resource bundle tree lookup
return createFromBundle(bundle, key);
}
return null;
}
/**
* Queries all the available locales in ICU and adds the names
* of those which it accepts to result. This is quite
* time-consuming so we don't want to do it more than once if
* we have to. This is only called if we are visible.
*/
protected void handleUpdateVisibleIDs(Set result) {
Locale[] locales = ICULocaleData.getAvailableLocales(name);
for (int i = 0; i < locales.length; ++i) {
Locale locale = locales[i];
if (acceptsLocale(locale)) {
result.add(LocaleUtility.canonicalLocaleString(locale.toString()));
}
}
}
/**
* Return a localized name for the locale represented by id.
*/
protected String handleGetDisplayName(String id, Locale locale) {
// use java's display name formatting for now
return LocaleUtility.getLocaleFromName(id).getDisplayName(locale);
}
/**
* We only accept the locale if there is a bundle for this exact locale and if
* all the required resources are directly in this bundle (none is from an
* inherited bundle);
*/
protected boolean acceptsLocale(Locale loc) {
try {
ResourceBundle bundle = ICULocaleData.loadResourceBundle(name, loc); // single resource bundle lookup
if (requiredContents != null) {
for (int i = 0; i < requiredContents.length; ++i) {
if (bundle.getObject(requiredContents[i]) == null) {
return false;
}
}
}
return true;
}
catch (Exception e) {
}
return false;
}
/**
* Subclassers implement this to create their service object based on the bundle and key.
* The default implementation just returns the bundle.
*/
protected Object createFromBundle(ResourceBundle bundle, Key key) {
return bundle;
}
}
protected Key createKey(String id) {
Locale loc = Locale.getDefault();
if (loc != fallbackLocale) {
synchronized (this) {
if (loc != fallbackLocale) {
fallbackLocale = loc;
fallbackLocaleName = LocaleUtility.canonicalLocaleString(loc.toString());
clearServiceCache();
}
}
}
return LocaleKey.createWithCanonicalFallback(id, fallbackLocaleName);
}
}