ICU-20627 Adding getAvailableLocalesByType, ICU4C and ICU4J.

This commit is contained in:
Shane Carr 2019-08-15 01:56:22 +00:00 committed by Shane F. Carr
parent 9a3f15c2b3
commit 1d2861bb0c
14 changed files with 551 additions and 115 deletions

View file

@ -19,11 +19,13 @@
* that then do not depend on resource bundle code and res_index bundles.
*/
#include "unicode/errorcode.h"
#include "unicode/utypes.h"
#include "unicode/locid.h"
#include "unicode/uloc.h"
#include "unicode/ures.h"
#include "cmemory.h"
#include "cstring.h"
#include "ucln_cmn.h"
#include "uassert.h"
#include "umutex.h"
@ -95,85 +97,174 @@ U_NAMESPACE_USE
/* ### Constants **************************************************/
/* These strings describe the resources we attempt to load from
the locale ResourceBundle data file.*/
static const char _kIndexLocaleName[] = "res_index";
static const char _kIndexTag[] = "InstalledLocales";
namespace {
static char** _installedLocales = NULL;
static int32_t _installedLocalesCount = 0;
static icu::UInitOnce _installedLocalesInitOnce;
// Enough capacity for the two lists in the res_index.res file
const char** gAvailableLocaleNames[2] = {};
int32_t gAvailableLocaleCounts[2] = {};
icu::UInitOnce ginstalledLocalesInitOnce = U_INITONCE_INITIALIZER;
class AvailableLocalesSink : public ResourceSink {
public:
void put(const char *key, ResourceValue &value, UBool /*noFallback*/, UErrorCode &status) U_OVERRIDE {
ResourceTable resIndexTable = value.getTable(status);
if (U_FAILURE(status)) {
return;
}
for (int32_t i = 0; resIndexTable.getKeyAndValue(i, key, value); ++i) {
ULocAvailableType type;
if (uprv_strcmp(key, "InstalledLocales") == 0) {
type = ULOC_AVAILABLE_DEFAULT;
} else if (uprv_strcmp(key, "AliasLocales") == 0) {
type = ULOC_AVAILABLE_ONLY_LEGACY_ALIASES;
} else {
// CLDRVersion, etc.
continue;
}
ResourceTable availableLocalesTable = value.getTable(status);
if (U_FAILURE(status)) {
return;
}
gAvailableLocaleCounts[type] = availableLocalesTable.getSize();
gAvailableLocaleNames[type] = static_cast<const char**>(
uprv_malloc(gAvailableLocaleCounts[type] * sizeof(const char*)));
if (gAvailableLocaleNames[type] == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return;
}
for (int32_t j = 0; availableLocalesTable.getKeyAndValue(j, key, value); ++j) {
gAvailableLocaleNames[type][j] = key;
}
}
}
};
class AvailableLocalesStringEnumeration : public StringEnumeration {
public:
AvailableLocalesStringEnumeration(ULocAvailableType type) : fType(type) {
}
const char* next(int32_t *resultLength, UErrorCode&) override {
ULocAvailableType actualType = fType;
int32_t actualIndex = fIndex++;
// If the "combined" list was requested, resolve that now
if (fType == ULOC_AVAILABLE_WITH_LEGACY_ALIASES) {
int32_t defaultLocalesCount = gAvailableLocaleCounts[ULOC_AVAILABLE_DEFAULT];
if (actualIndex < defaultLocalesCount) {
actualType = ULOC_AVAILABLE_DEFAULT;
} else {
actualIndex -= defaultLocalesCount;
actualType = ULOC_AVAILABLE_ONLY_LEGACY_ALIASES;
}
}
// Return the requested string
int32_t count = gAvailableLocaleCounts[actualType];
const char* result;
if (actualIndex < count) {
result = gAvailableLocaleNames[actualType][actualIndex];
if (resultLength != nullptr) {
*resultLength = static_cast<int32_t>(uprv_strlen(result));
}
} else {
result = nullptr;
if (resultLength != nullptr) {
*resultLength = 0;
}
}
return result;
}
void reset(UErrorCode&) override {
fIndex = 0;
}
int32_t count(UErrorCode&) const override {
if (fType == ULOC_AVAILABLE_WITH_LEGACY_ALIASES) {
return gAvailableLocaleCounts[ULOC_AVAILABLE_DEFAULT]
+ gAvailableLocaleCounts[ULOC_AVAILABLE_ONLY_LEGACY_ALIASES];
} else {
return gAvailableLocaleCounts[fType];
}
}
private:
ULocAvailableType fType;
int32_t fIndex = 0;
};
/* ### Get available **************************************************/
static UBool U_CALLCONV uloc_cleanup(void) {
char ** temp;
if (_installedLocales) {
temp = _installedLocales;
_installedLocales = NULL;
_installedLocalesCount = 0;
_installedLocalesInitOnce.reset();
uprv_free(temp);
for (int32_t i = 0; i < UPRV_LENGTHOF(gAvailableLocaleNames); i++) {
uprv_free(gAvailableLocaleNames[i]);
gAvailableLocaleNames[i] = nullptr;
gAvailableLocaleCounts[i] = 0;
}
ginstalledLocalesInitOnce.reset();
return TRUE;
}
// Load Installed Locales. This function will be called exactly once
// via the initOnce mechanism.
static void U_CALLCONV loadInstalledLocales() {
UErrorCode status = U_ZERO_ERROR;
int32_t i = 0;
int32_t localeCount;
U_ASSERT(_installedLocales == NULL);
U_ASSERT(_installedLocalesCount == 0);
static void U_CALLCONV loadInstalledLocales(UErrorCode& status) {
ucln_common_registerCleanup(UCLN_COMMON_ULOC, uloc_cleanup);
_installedLocalesCount = 0;
icu::LocalUResourceBundlePointer indexLocale(ures_openDirect(NULL, _kIndexLocaleName, &status));
icu::StackUResourceBundle installed;
ures_getByKey(indexLocale.getAlias(), _kIndexTag, installed.getAlias(), &status);
if(U_SUCCESS(status)) {
localeCount = ures_getSize(installed.getAlias());
_installedLocales = (char **) uprv_malloc(sizeof(char*) * (localeCount+1));
if (_installedLocales != NULL) {
ures_resetIterator(installed.getAlias());
while(ures_hasNext(installed.getAlias())) {
ures_getNextString(installed.getAlias(), NULL, (const char **)&_installedLocales[i++], &status);
}
_installedLocales[i] = NULL;
_installedLocalesCount = localeCount;
ucln_common_registerCleanup(UCLN_COMMON_ULOC, uloc_cleanup);
}
}
icu::LocalUResourceBundlePointer rb(ures_openDirect(NULL, "res_index", &status));
AvailableLocalesSink sink;
ures_getAllItemsWithFallback(rb.getAlias(), "", sink, status);
}
static void _load_installedLocales()
{
umtx_initOnce(_installedLocalesInitOnce, &loadInstalledLocales);
void _load_installedLocales(UErrorCode& status) {
umtx_initOnce(ginstalledLocalesInitOnce, &loadInstalledLocales, status);
}
} // namespace
U_CAPI const char* U_EXPORT2
uloc_getAvailable(int32_t offset)
{
_load_installedLocales();
if (offset > _installedLocalesCount)
return NULL;
return _installedLocales[offset];
uloc_getAvailable(int32_t offset) {
icu::ErrorCode status;
_load_installedLocales(status);
if (status.isFailure()) {
return nullptr;
}
if (offset > gAvailableLocaleCounts[0]) {
// *status = U_ILLEGAL_ARGUMENT_ERROR;
return nullptr;
}
return gAvailableLocaleNames[0][offset];
}
U_CAPI int32_t U_EXPORT2
uloc_countAvailable()
{
_load_installedLocales();
return _installedLocalesCount;
uloc_countAvailable() {
icu::ErrorCode status;
_load_installedLocales(status);
if (status.isFailure()) {
return 0;
}
return gAvailableLocaleCounts[0];
}
U_CAPI UEnumeration* U_EXPORT2
uloc_openAvailableByType(ULocAvailableType type, UErrorCode* status) {
if (U_FAILURE(*status)) {
return nullptr;
}
if (type < 0 || type >= ULOC_AVAILABLE_COUNT) {
*status = U_ILLEGAL_ARGUMENT_ERROR;
return nullptr;
}
_load_installedLocales(*status);
if (U_FAILURE(*status)) {
return nullptr;
}
LocalPointer<AvailableLocalesStringEnumeration> result(
new AvailableLocalesStringEnumeration(type), *status);
if (U_FAILURE(*status)) {
return nullptr;
}
return uenum_openFromStringEnumeration(result.orphan(), status);
}

View file

@ -742,12 +742,18 @@ uloc_getDisplayName(const char* localeID,
/**
* Gets the specified locale from a list of all available locales.
* The return value is a pointer to an item of
* a locale name array. Both this array and the pointers
* it contains are owned by ICU and should not be deleted or written through
* by the caller. The locale name is terminated by a null pointer.
* @param n the specific locale name index of the available locale list
* Gets the specified locale from a list of available locales.
*
* This method corresponds to uloc_openAvailableByType called with the
* ULOC_AVAILABLE_DEFAULT type argument.
*
* The return value is a pointer to an item of a locale name array. Both this
* array and the pointers it contains are owned by ICU and should not be
* deleted or written through by the caller. The locale name is terminated by
* a null pointer.
*
* @param n the specific locale name index of the available locale list;
* should not exceed the number returned by uloc_countAvailable.
* @return a specified locale name of all available locales
* @stable ICU 2.0
*/
@ -762,6 +768,72 @@ uloc_getAvailable(int32_t n);
*/
U_STABLE int32_t U_EXPORT2 uloc_countAvailable(void);
#ifndef U_HIDE_DRAFT_API
/**
* Types for uloc_getAvailableByType and uloc_countAvailableByType.
*
* @draft ICU 65
*/
typedef enum ULocAvailableType {
/**
* Locales that return data when passed to ICU APIs,
* but not including legacy or alias locales.
*
* @draft ICU 65
*/
ULOC_AVAILABLE_DEFAULT,
/**
* Legacy or alias locales that return data when passed to ICU APIs.
* Examples of supported legacy or alias locales:
*
* - iw (alias to he)
* - mo (alias to ro)
* - zh_CN (alias to zh_Hans_CN)
* - sr_BA (alias to sr_Cyrl_BA)
* - ars (alias to ar_SA)
*
* The locales in this set are disjoint from the ones in
* ULOC_AVAILABLE_DEFAULT. To get both sets at the same time, use
* ULOC_AVAILABLE_WITH_LEGACY_ALIASES.
*
* @draft ICU 65
*/
ULOC_AVAILABLE_ONLY_LEGACY_ALIASES,
/**
* The union of the locales in ULOC_AVAILABLE_DEFAULT and
* ULOC_AVAILABLE_ONLY_LEGACY_ALIAS.
*
* @draft ICU 65
*/
ULOC_AVAILABLE_WITH_LEGACY_ALIASES,
#ifndef U_HIDE_INTERNAL_API
/**
* @internal
*/
ULOC_AVAILABLE_COUNT
#endif
} ULocAvailableType;
/**
* Gets a list of available locales according to the type argument, allowing
* the user to access different sets of supported locales in ICU.
*
* The returned UEnumeration must be closed by the caller.
*
* @param type Type choice from ULocAvailableType.
* @param status Set if an error occurred.
* @return a UEnumeration owned by the caller, or nullptr on failure.
* @draft ICU 65
*/
U_DRAFT UEnumeration* U_EXPORT2
uloc_openAvailableByType(ULocAvailableType type, UErrorCode* status);
#endif // U_HIDE_DRAFT_API
/**
*
* Gets a list of all available 2-letter language codes defined in ISO 639,

View file

@ -340,10 +340,10 @@ class IndexRequest(AbstractRequest):
return ("// Warning this file is automatically generated\n"
"{INDEX_NAME}:table(nofallback) {{\n"
"{FORMATTED_VERSION}"
" InstalledLocales {{\n"
" InstalledLocales:table {{\n"
"{FORMATTED_INSTALLED_LOCALES}\n"
" }}\n"
" AliasLocales {{\n"
" AliasLocales:table {{\n"
"{FORMATTED_ALIAS_LOCALES}\n"
" }}\n"
"}}").format(

View file

@ -743,4 +743,18 @@ U_CFUNC UBool assertIntEquals(const char* message, int64_t expected, int64_t act
return TRUE;
}
U_CFUNC UBool assertPtrEquals(const char* message, const void* expected, const void* actual) {
if (expected != actual) {
log_err("FAIL: %s; got 0x%llx; expected 0x%llx\n",
message, actual, expected);
return FALSE;
}
#ifdef VERBOSE_ASSERTIONS
else {
log_verbose("Ok: %s; got 0x%llx\n", message, actual);
}
#endif
return TRUE;
}
#endif

View file

@ -147,6 +147,12 @@ U_CFUNC UBool assertUEquals(const char* msg, const UChar* expectedString,
*/
U_CFUNC UBool assertIntEquals(const char* msg, int64_t expected, int64_t actual);
/**
* Assert that the addresses of the two pointers are the same, returning
* TRUE if they are equal.
*/
U_CFUNC UBool assertPtrEquals(const char* msg, const void* expected, const void* actual);
/*
* note - isICUVersionBefore and isICUVersionAtLeast have been removed.
* use log_knownIssue() instead.

View file

@ -218,6 +218,7 @@ void addLocaleTest(TestNode** root)
TESTCASE(TestSimpleResourceInfo);
TESTCASE(TestDisplayNames);
TESTCASE(TestGetAvailableLocales);
TESTCASE(TestGetAvailableLocalesByType);
TESTCASE(TestDataDirectory);
#if !UCONFIG_NO_FILE_IO && !UCONFIG_NO_LEGACY_CONVERSION
TESTCASE(TestISOFunctions);
@ -834,6 +835,77 @@ static void TestGetAvailableLocales()
}
}
static void TestGetAvailableLocalesByType() {
UErrorCode status = U_ZERO_ERROR;
UEnumeration* uenum = uloc_openAvailableByType(ULOC_AVAILABLE_DEFAULT, &status);
assertSuccess("Constructing the UEnumeration", &status);
assertIntEquals("countAvailable() should be same in old and new methods",
uloc_countAvailable(),
uenum_count(uenum, &status));
for (int32_t i = 0; i < uloc_countAvailable(); i++) {
const char* old = uloc_getAvailable(i);
int32_t len = 0;
const char* new = uenum_next(uenum, &len, &status);
assertEquals("Old and new strings should equal", old, new);
assertIntEquals("String length should be correct", uprv_strlen(old), len);
}
assertPtrEquals("Should get nullptr on the last string",
NULL, uenum_next(uenum, NULL, &status));
uenum_close(uenum);
uenum = uloc_openAvailableByType(ULOC_AVAILABLE_ONLY_LEGACY_ALIASES, &status);
UBool found_he = FALSE;
UBool found_iw = FALSE;
const char* loc;
while ((loc = uenum_next(uenum, NULL, &status))) {
if (uprv_strcmp("he", loc) == 0) {
found_he = TRUE;
}
if (uprv_strcmp("iw", loc) == 0) {
found_iw = TRUE;
}
}
assertTrue("Should NOT have found he amongst the legacy/alias locales", !found_he);
assertTrue("Should have found iw amongst the legacy/alias locales", found_iw);
uenum_close(uenum);
uenum = uloc_openAvailableByType(ULOC_AVAILABLE_WITH_LEGACY_ALIASES, &status);
found_he = FALSE;
found_iw = FALSE;
const UChar* uloc; // test the UChar conversion
int32_t count = 0;
while ((uloc = uenum_unext(uenum, NULL, &status))) {
if (u_strcmp(u"iw", uloc) == 0) {
found_iw = TRUE;
}
if (u_strcmp(u"he", uloc) == 0) {
found_he = TRUE;
}
count++;
}
assertTrue("Should have found he amongst all locales", found_he);
assertTrue("Should have found iw amongst all locales", found_iw);
assertIntEquals("Should return as many strings as claimed",
count, uenum_count(uenum, &status));
// Reset the enumeration and it should still work
uenum_reset(uenum, &status);
count = 0;
while ((loc = uenum_next(uenum, NULL, &status))) {
count++;
}
assertIntEquals("After reset, should return as many strings as claimed",
count, uenum_count(uenum, &status));
uenum_close(uenum);
assertSuccess("No errors should have occurred", &status);
}
/* test for u_getDataDirectory, u_setDataDirectory, uloc_getISO3Language */
static void TestDataDirectory()
{

View file

@ -36,7 +36,8 @@ static void TestDisplayNames(void);
/**
* Test getAvailableLocales
**/
static void TestGetAvailableLocales(void);
static void TestGetAvailableLocales(void);
static void TestGetAvailableLocalesByType(void);
/**
* Test functions to set and access a custom data directory
**/

View file

@ -639,6 +639,7 @@ group: resourcebundle
sort stringenumeration uhash uvector
uscript_props propname
bytesinkutil
errorcode
group: localebuilder
localebuilder.o

View file

@ -933,7 +933,7 @@ public abstract class Collator implements Comparator<Object>, Freezable<Collator
public static final ULocale[] getAvailableULocales() {
if (shim == null) {
return ICUResourceBundle.getAvailableULocales(
ICUData.ICU_COLLATION_BASE_NAME, ICUResourceBundle.ICU_DATA_CLASS_LOADER);
ICUData.ICU_COLLATION_BASE_NAME, ICUResourceBundle.ICU_DATA_CLASS_LOADER);
}
return shim.getAvailableULocales();
}

View file

@ -179,7 +179,7 @@ final class CollatorServiceShim extends Collator.ServiceShim {
// Ported from C++ Collator::makeInstance().
private static final Collator makeInstance(ULocale desiredLocale) {
Output<ULocale> validLocale = new Output<ULocale>(ULocale.ROOT);
Output<ULocale> validLocale = new Output<>(ULocale.ROOT);
CollationTailoring t =
CollationLoader.loadTailoring(desiredLocale, validLocale);
return new RuleBasedCollator(t, validLocale.value);

View file

@ -16,6 +16,7 @@ import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
@ -132,7 +133,8 @@ public class ICUResourceBundle extends UResourceBundle {
r = (ICUResourceBundle) UResourceBundle.getBundleInstance(baseName, parent);
if (isAvailable != null) {
isAvailable[0] = false;
ULocale[] availableULocales = getAvailEntry(baseName, loader).getULocaleList();
ULocale[] availableULocales = getAvailEntry(baseName, loader)
.getULocaleList(ULocale.AvailableType.DEFAULT);
for (int i = 0; i < availableULocales.length; i++) {
if (parent.equals(availableULocales[i])) {
isAvailable[0] = true;
@ -249,7 +251,8 @@ public class ICUResourceBundle extends UResourceBundle {
*/
public static final String[] getKeywordValues(String baseName, String keyword) {
Set<String> keywords = new HashSet<>();
ULocale locales[] = getAvailEntry(baseName, ICU_DATA_CLASS_LOADER).getULocaleList();
ULocale locales[] = getAvailEntry(baseName, ICU_DATA_CLASS_LOADER)
.getULocaleList(ULocale.AvailableType.DEFAULT);
int i;
for (i = 0; i < locales.length; i++) {
@ -493,11 +496,12 @@ public class ICUResourceBundle extends UResourceBundle {
}
/**
* Get the set of Locales installed in the specified bundles.
* Get the set of Locales installed in the specified bundles, for the specified type.
* @return the list of available locales
*/
public static final ULocale[] getAvailableULocales(String baseName, ClassLoader loader) {
return getAvailEntry(baseName, loader).getULocaleList();
public static final ULocale[] getAvailableULocales(String baseName, ClassLoader loader,
ULocale.AvailableType type) {
return getAvailEntry(baseName, loader).getULocaleList(type);
}
/**
@ -505,7 +509,48 @@ public class ICUResourceBundle extends UResourceBundle {
* @return the list of available locales
*/
public static final ULocale[] getAvailableULocales() {
return getAvailableULocales(ICUData.ICU_BASE_NAME, ICU_DATA_CLASS_LOADER);
return getAvailableULocales(ICUData.ICU_BASE_NAME, ICU_DATA_CLASS_LOADER, ULocale.AvailableType.DEFAULT);
}
/**
* Get the set of ULocales installed the base bundle, for the specified type.
* @return the list of available locales for the specified type
*/
public static final ULocale[] getAvailableULocales(ULocale.AvailableType type) {
return getAvailableULocales(ICUData.ICU_BASE_NAME, ICU_DATA_CLASS_LOADER, type);
}
/**
* Get the set of Locales installed in the specified bundles.
* @return the list of available locales
*/
public static final ULocale[] getAvailableULocales(String baseName, ClassLoader loader) {
return getAvailableULocales(baseName, loader, ULocale.AvailableType.DEFAULT);
}
/**
* Get the set of Locales installed in the specified bundles, for the specified type.
* @return the list of available locales
*/
public static final Locale[] getAvailableLocales(String baseName, ClassLoader loader,
ULocale.AvailableType type) {
return getAvailEntry(baseName, loader).getLocaleList(type);
}
/**
* Get the set of ULocales installed the base bundle.
* @return the list of available locales
*/
public static final Locale[] getAvailableLocales() {
return getAvailableLocales(ICUData.ICU_BASE_NAME, ICU_DATA_CLASS_LOADER, ULocale.AvailableType.DEFAULT);
}
/**
* Get the set of Locales installed the base bundle, for the specified type.
* @return the list of available locales
*/
public static final Locale[] getAvailableLocales(ULocale.AvailableType type) {
return getAvailableLocales(ICUData.ICU_BASE_NAME, ICU_DATA_CLASS_LOADER, type);
}
/**
@ -513,15 +558,7 @@ public class ICUResourceBundle extends UResourceBundle {
* @return the list of available locales
*/
public static final Locale[] getAvailableLocales(String baseName, ClassLoader loader) {
return getAvailEntry(baseName, loader).getLocaleList();
}
/**
* Get the set of Locales installed the base bundle.
* @return the list of available locales
*/
public static final Locale[] getAvailableLocales() {
return getAvailEntry(ICUData.ICU_BASE_NAME, ICU_DATA_CLASS_LOADER).getLocaleList();
return getAvailableLocales(baseName, loader, ULocale.AvailableType.DEFAULT);
}
/**
@ -569,31 +606,50 @@ public class ICUResourceBundle extends UResourceBundle {
// Flag for enabling/disabling debugging code
private static final boolean DEBUG = ICUDebug.enabled("localedata");
private static final ULocale[] createULocaleList(String baseName,
ClassLoader root) {
private static final class AvailableLocalesSink extends UResource.Sink {
EnumMap<ULocale.AvailableType, ULocale[]> output;
public AvailableLocalesSink(EnumMap<ULocale.AvailableType, ULocale[]> output) {
this.output = output;
}
@Override
public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
UResource.Table resIndexTable = value.getTable();
for (int i = 0; resIndexTable.getKeyAndValue(i, key, value); ++i) {
ULocale.AvailableType type;
if (key.contentEquals("InstalledLocales")) {
type = ULocale.AvailableType.DEFAULT;
} else if (key.contentEquals("AliasLocales")) {
type = ULocale.AvailableType.ONLY_LEGACY_ALIASES;
} else {
// CLDRVersion, etc.
continue;
}
UResource.Table availableLocalesTable = value.getTable();
ULocale[] locales = new ULocale[availableLocalesTable.getSize()];
for (int j = 0; availableLocalesTable.getKeyAndValue(j, key, value); ++j) {
locales[j] = new ULocale(key.toString());
}
output.put(type, locales);
}
}
}
private static final EnumMap<ULocale.AvailableType, ULocale[]> createULocaleList(
String baseName, ClassLoader root) {
// the canned list is a subset of all the available .res files, the idea
// is we don't export them
// all. gotta be a better way to do this, since to add a locale you have
// to update this list,
// and it's embedded in our binary resources.
ICUResourceBundle bundle = (ICUResourceBundle) UResourceBundle.instantiateBundle(baseName, ICU_RESOURCE_INDEX, root, true);
ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle.instantiateBundle(baseName, ICU_RESOURCE_INDEX, root, true);
bundle = (ICUResourceBundle)bundle.get(INSTALLED_LOCALES);
int length = bundle.getSize();
int i = 0;
ULocale[] locales = new ULocale[length];
UResourceBundleIterator iter = bundle.getIterator();
iter.reset();
while (iter.hasNext()) {
String locstr = iter.next().getKey();
if (locstr.equals("root")) {
locales[i++] = ULocale.ROOT;
} else {
locales[i++] = new ULocale(locstr);
}
}
bundle = null;
return locales;
EnumMap<ULocale.AvailableType, ULocale[]> result = new EnumMap<>(ULocale.AvailableType.class);
AvailableLocalesSink sink = new AvailableLocalesSink(result);
rb.getAllItemsWithFallback("", sink);
return result;
}
// Same as createULocaleList() but catches the MissingResourceException
@ -739,7 +795,7 @@ public class ICUResourceBundle extends UResourceBundle {
private static final class AvailEntry {
private String prefix;
private ClassLoader loader;
private volatile ULocale[] ulocales;
private volatile EnumMap<ULocale.AvailableType, ULocale[]> ulocales;
private volatile Locale[] locales;
private volatile Set<String> nameSet;
private volatile Set<String> fullNameSet;
@ -749,7 +805,9 @@ public class ICUResourceBundle extends UResourceBundle {
this.loader = loader;
}
ULocale[] getULocaleList() {
ULocale[] getULocaleList(ULocale.AvailableType type) {
// Direct data is available for DEFAULT and ONLY_LEGACY_ALIASES
assert type != ULocale.AvailableType.WITH_LEGACY_ALIASES;
if (ulocales == null) {
synchronized(this) {
if (ulocales == null) {
@ -757,14 +815,14 @@ public class ICUResourceBundle extends UResourceBundle {
}
}
}
return ulocales;
return ulocales.get(type);
}
Locale[] getLocaleList() {
Locale[] getLocaleList(ULocale.AvailableType type) {
if (locales == null) {
getULocaleList();
getULocaleList(type);
synchronized(this) {
if (locales == null) {
locales = ICUResourceBundle.getLocaleList(ulocales);
locales = ICUResourceBundle.getLocaleList(ulocales.get(type));
}
}
}

View file

@ -13,6 +13,10 @@ import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
@ -121,6 +125,52 @@ public final class ULocale implements Serializable, Comparable<ULocale> {
}
};
/**
* Types for {@link ULocale#getAvailableLocalesByType}
*
* @draft ICU 65
* @provisional This API might change or be removed in a future release.
*/
public static enum AvailableType {
/**
* Locales that return data when passed to ICU APIs,
* but not including legacy or alias locales.
*
* @draft ICU 65
* @provisional This API might change or be removed in a future release.
*/
DEFAULT,
/**
* Legacy or alias locales that return data when passed to ICU APIs.
* Examples of supported legacy or alias locales:
*
* <ul>
* <li>iw (alias to he)
* <li>mo (alias to ro)
* <li>zh_CN (alias to zh_Hans_CN)
* <li>sr_BA (alias to sr_Cyrl_BA)
* <li>ars (alias to ar_SA)
* </ul>
*
* The locales in this set are disjoint from the ones in
* DEFAULT. To get both sets at the same time, use
* WITH_LEGACY_ALIASES.
*
* @draft ICU 65
* @provisional This API might change or be removed in a future release.
*/
ONLY_LEGACY_ALIASES,
/**
* The union of the locales in DEFAULT and ONLY_LEGACY_ALIASES.
*
* @draft ICU 65
* @provisional This API might change or be removed in a future release.
*/
WITH_LEGACY_ALIASES,
}
/**
* Useful constant for language.
* @stable ICU 3.0
@ -786,11 +836,38 @@ public final class ULocale implements Serializable, Comparable<ULocale> {
/**
* {@icunote} Unlike the Locale API, this returns an array of <code>ULocale</code>,
* not <code>Locale</code>. Returns a list of all installed locales.
* not <code>Locale</code>.
*
* <p>Returns a list of all installed locales. This is equivalent to calling
* {@link #getAvailableLocalesByType} with AvialableType.DEFAULT.
*
* @stable ICU 3.0
*/
public static ULocale[] getAvailableLocales() {
return ICUResourceBundle.getAvailableULocales();
return ICUResourceBundle.getAvailableULocales().clone();
}
/**
* Returns a list of all installed locales according to the specified type.
*
* @draft ICU 65
* @provisional This API might change or be removed in a future release.
*/
public static Collection<ULocale> getAvailableLocalesByType(AvailableType type) {
if (type == null) {
throw new IllegalArgumentException();
}
List<ULocale> result;
if (type == ULocale.AvailableType.WITH_LEGACY_ALIASES) {
result = new ArrayList<>();
Collections.addAll(result,
ICUResourceBundle.getAvailableULocales(ULocale.AvailableType.DEFAULT));
Collections.addAll(result,
ICUResourceBundle.getAvailableULocales(ULocale.AvailableType.ONLY_LEGACY_ALIASES));
} else {
result = Arrays.asList(ICUResourceBundle.getAvailableULocales(type));
}
return Collections.unmodifiableList(result);
}
/**

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5c8773434e9708bca02ad11319c35e01f29f62748851a38ae89de1334c279cca
size 12842785
oid sha256:43a8fe03049ca2fc11189c20cdc0baa3248a983d9885fe44f7946aca7082979a
size 12842784

View file

@ -15,6 +15,7 @@ package com.ibm.icu.dev.test.util;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@ -1024,6 +1025,49 @@ public class ULocaleTest extends TestFmwk {
}
}
@Test
public void TestGetAvailableByType() {
assertEquals("countAvailable() should be same in old and new methods",
ULocale.getAvailableLocales().length,
ULocale.getAvailableLocalesByType(ULocale.AvailableType.DEFAULT).size());
assertEquals("getAvailable() should return same in old and new methods",
Arrays.asList(ULocale.getAvailableLocales()),
ULocale.getAvailableLocalesByType(ULocale.AvailableType.DEFAULT));
Collection<ULocale> legacyLocales = ULocale
.getAvailableLocalesByType(ULocale.AvailableType.ONLY_LEGACY_ALIASES);
assertTrue("getAvailable() legacy/alias should return nonempty", legacyLocales.size() > 0);
boolean found_he = false;
boolean found_iw = false;
for (ULocale loc : legacyLocales) {
if (loc.getName().equals("he")) {
found_he = true;
}
if (loc.getName().equals("iw")) {
found_iw = true;
}
}
assertFalse("Should NOT have found he amongst the legacy/alias locales", found_he);
assertTrue("Should have found iw amongst the legacy/alias locales", found_iw);
Collection<ULocale> allLocales = ULocale
.getAvailableLocalesByType(ULocale.AvailableType.WITH_LEGACY_ALIASES);
found_he = false;
found_iw = false;
for (ULocale loc : allLocales) {
if (loc.getName().equals("he")) {
found_he = true;
}
if (loc.getName().equals("iw")) {
found_iw = true;
}
}
assertTrue("Should have found he amongst the legacy/alias locales", found_he);
assertTrue("Should have found iw amongst the legacy/alias locales", found_iw);
}
@Test
public void TestDisplayNames() {
// consistency check, also check that all data is available
@ -4154,7 +4198,7 @@ public class ULocaleTest extends TestFmwk {
val = loc3.getKeywordValue("numbers");
assertEquals("Default, ICU keyword", null, val);
// Note: ICU does not have getUnicodeKeywordValue()
}