NumberingSystem.java
// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/*
*******************************************************************************
* Copyright (C) 2009-2016, International Business Machines Corporation and
* others. All Rights Reserved.
*******************************************************************************
*/
package com.ibm.icu.text;
import com.ibm.icu.impl.CacheBase;
import com.ibm.icu.impl.ICUData;
import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.impl.SoftCache;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.ULocale.Category;
import com.ibm.icu.util.UResourceBundle;
import com.ibm.icu.util.UResourceBundleIterator;
import java.util.ArrayList;
import java.util.Locale;
import java.util.MissingResourceException;
/**
* <code>NumberingSystem</code> is the base class for all number systems. This class provides the
* interface for setting different numbering system types, whether it be a simple alternate digit
* system such as Thai digits or Devanagari digits, or an algorithmic numbering system such as
* Hebrew numbering or Chinese numbering.
*
* @author John Emmons
* @stable ICU 4.2
*/
public class NumberingSystem {
private static final String[] OTHER_NS_KEYWORDS = {"native", "traditional", "finance"};
/**
* For convenience, an instance representing the <em>latn</em> numbering system, which
* corresponds to digits in the ASCII range '0' through '9'.
*
* @stable ICU 60
*/
public static final NumberingSystem LATIN = lookupInstanceByName("latn");
/**
* Default constructor. Returns a numbering system that uses the Latin-script decimal digits 0
* through 9. This should be equivalent to NumberingSystem.LATIN.
*
* @stable ICU 4.2
*/
public NumberingSystem() {
radix = 10;
algorithmic = false;
desc = "0123456789";
name = "latn";
}
/**
* Factory method for creating a numbering system.
*
* @param radix_in The radix for this numbering system. ICU currently supports only numbering
* systems whose radix is 10.
* @param isAlgorithmic_in Specifies whether the numbering system is algorithmic (true) or
* numeric (false).
* @param desc_in String used to describe the characteristics of the numbering system. For
* numeric systems, this string contains the digits used by the numbering system, in order,
* starting from zero. For algorithmic numbering systems, the string contains the name of
* the RBNF ruleset in the locale's NumberingSystemRules section that will be used to format
* numbers using this numbering system.
* @stable ICU 4.2
*/
public static NumberingSystem getInstance(
int radix_in, boolean isAlgorithmic_in, String desc_in) {
return getInstance(null, radix_in, isAlgorithmic_in, desc_in);
}
/**
* Factory method for creating a numbering system.
*
* @param name_in The string representing the name of the numbering system.
* @param radix_in The radix for this numbering system. ICU currently supports only numbering
* systems whose radix is 10.
* @param isAlgorithmic_in Specifies whether the numbering system is algorithmic (true) or
* numeric (false).
* @param desc_in String used to describe the characteristics of the numbering system. For
* numeric systems, this string contains the digits used by the numbering system, in order,
* starting from zero. For algorithmic numbering systems, the string contains the name of
* the RBNF ruleset in the locale's NumberingSystemRules section that will be used to format
* numbers using this numbering system.
* @stable ICU 4.6
*/
private static NumberingSystem getInstance(
String name_in, int radix_in, boolean isAlgorithmic_in, String desc_in) {
if (radix_in < 2) {
throw new IllegalArgumentException("Invalid radix for numbering system");
}
if (!isAlgorithmic_in) {
if (desc_in.codePointCount(0, desc_in.length()) != radix_in
|| !isValidDigitString(desc_in)) {
throw new IllegalArgumentException("Invalid digit string for numbering system");
}
}
NumberingSystem ns = new NumberingSystem();
ns.radix = radix_in;
ns.algorithmic = isAlgorithmic_in;
ns.desc = desc_in;
ns.name = name_in;
return ns;
}
/**
* Returns the default numbering system for the specified locale.
*
* @stable ICU 4.2
*/
public static NumberingSystem getInstance(Locale inLocale) {
return getInstance(ULocale.forLocale(inLocale));
}
/**
* Returns the default numbering system for the specified ULocale.
*
* @stable ICU 4.2
*/
public static NumberingSystem getInstance(ULocale locale) {
// Check for @numbers
boolean nsResolved = true;
String numbersKeyword = locale.getKeywordValue("numbers");
if (numbersKeyword != null) {
for (String keyword : OTHER_NS_KEYWORDS) {
if (numbersKeyword.equals(keyword)) {
nsResolved = false;
break;
}
}
} else {
numbersKeyword = "default";
nsResolved = false;
}
if (nsResolved) {
NumberingSystem ns = getInstanceByName(numbersKeyword);
if (ns != null) {
return ns;
}
// If the @numbers keyword points to a bogus numbering system name,
// we return the default for the locale.
numbersKeyword = "default";
}
// Attempt to get the numbering system from the cache
String baseName = locale.getBaseName();
// TODO: Caching by locale+numbersKeyword could yield a large cache.
// Try to load for each locale the mappings from OTHER_NS_KEYWORDS and default
// to real numbering system names; can we get those from supplemental data?
// Then look up those mappings for the locale and resolve the keyword.
String key = baseName + "@numbers=" + numbersKeyword;
LocaleLookupData localeLookupData = new LocaleLookupData(locale, numbersKeyword);
return cachedLocaleData.getInstance(key, localeLookupData);
}
private static class LocaleLookupData {
public final ULocale locale;
public final String numbersKeyword;
LocaleLookupData(ULocale locale, String numbersKeyword) {
this.locale = locale;
this.numbersKeyword = numbersKeyword;
}
}
static NumberingSystem lookupInstanceByLocale(LocaleLookupData localeLookupData) {
ULocale locale = localeLookupData.locale;
ICUResourceBundle rb;
try {
rb =
(ICUResourceBundle)
UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, locale);
rb = rb.getWithFallback("NumberElements");
} catch (MissingResourceException ex) {
return new NumberingSystem();
}
String numbersKeyword = localeLookupData.numbersKeyword;
String resolvedNumberingSystem = null;
for (; ; ) {
try {
resolvedNumberingSystem = rb.getStringWithFallback(numbersKeyword);
break;
} catch (MissingResourceException ex) { // Fall back behavior as defined in TR35
if (numbersKeyword.equals("native") || numbersKeyword.equals("finance")) {
numbersKeyword = "default";
} else if (numbersKeyword.equals("traditional")) {
numbersKeyword = "native";
} else {
break;
}
}
}
NumberingSystem ns = null;
if (resolvedNumberingSystem != null) {
ns = getInstanceByName(resolvedNumberingSystem);
}
if (ns == null) {
ns = new NumberingSystem();
}
return ns;
}
/**
* Returns the default numbering system for the default <code>FORMAT</code> locale.
*
* @see Category#FORMAT
* @stable ICU 4.2
*/
public static NumberingSystem getInstance() {
return getInstance(ULocale.getDefault(Category.FORMAT));
}
/**
* Returns a numbering system from one of the predefined numbering systems known to ICU.
* Numbering system names are based on the numbering systems defined in CLDR. To get a list of
* available numbering systems, use the getAvailableNames method.
*
* @param name The name of the desired numbering system. Numbering system names often correspond
* with the name of the script they are associated with. For example, "thai" for Thai
* digits, "hebr" for Hebrew numerals.
* @return The NumberingSystem instance, or null if not available.
* @stable ICU 4.2
*/
public static NumberingSystem getInstanceByName(String name) {
// Get the numbering system from the cache.
return cachedStringData.getInstance(name, null /* unused */);
}
private static NumberingSystem lookupInstanceByName(String name) {
int radix;
boolean isAlgorithmic;
String description;
try {
UResourceBundle numberingSystemsInfo =
UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, "numberingSystems");
UResourceBundle nsCurrent = numberingSystemsInfo.get("numberingSystems");
UResourceBundle nsTop = nsCurrent.get(name);
description = nsTop.getString("desc");
UResourceBundle nsRadixBundle = nsTop.get("radix");
UResourceBundle nsAlgBundle = nsTop.get("algorithmic");
radix = nsRadixBundle.getInt();
int algorithmic = nsAlgBundle.getInt();
isAlgorithmic = (algorithmic == 1);
} catch (MissingResourceException ex) {
return null;
}
return getInstance(name, radix, isAlgorithmic, description);
}
/**
* Returns a string array containing a list of the names of numbering systems currently known to
* ICU.
*
* @return An array of strings in alphabetical (invariant) order.
* @stable ICU 4.2
*/
public static String[] getAvailableNames() {
UResourceBundle numberingSystemsInfo =
UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, "numberingSystems");
UResourceBundle nsCurrent = numberingSystemsInfo.get("numberingSystems");
UResourceBundle temp;
String nsName;
ArrayList<String> output = new ArrayList<>();
UResourceBundleIterator it = nsCurrent.getIterator();
while (it.hasNext()) {
temp = it.next();
nsName = temp.getKey();
output.add(nsName);
}
return output.toArray(new String[output.size()]);
}
/**
* Convenience method to determine if a given digit string is valid for use as a descriptor of a
* numeric ( non-algorithmic ) numbering system. In order for a digit string to be valid, it
* must contain exactly ten Unicode code points.
*
* @stable ICU 4.2
*/
public static boolean isValidDigitString(String str) {
int numCodepoints = str.codePointCount(0, str.length());
return (numCodepoints == 10);
}
/**
* Returns the radix of the current numbering system.
*
* @stable ICU 4.2
*/
public int getRadix() {
return radix;
}
/**
* Returns the description string of the current numbering system. The description string
* describes the characteristics of the numbering system. For numeric systems, this string
* contains the digits used by the numbering system, in order, starting from zero. For
* algorithmic numbering systems, the string contains the name of the RBNF ruleset in the
* locale's NumberingSystemRules section that will be used to format numbers using this
* numbering system.
*
* @stable ICU 4.2
*/
public String getDescription() {
return desc;
}
/**
* Returns the string representing the name of the numbering system.
*
* @stable ICU 4.6
*/
public String getName() {
return name;
}
/**
* Returns the numbering system's algorithmic status. If true, the numbering system is
* algorithmic and uses an RBNF formatter to format numerals. If false, the numbering system is
* numeric and uses a fixed set of digits.
*
* @stable ICU 4.2
*/
public boolean isAlgorithmic() {
return algorithmic;
}
private String desc;
private int radix;
private boolean algorithmic;
private String name;
/** Cache to hold the NumberingSystems by Locale. */
private static CacheBase<String, NumberingSystem, LocaleLookupData> cachedLocaleData =
new SoftCache<String, NumberingSystem, LocaleLookupData>() {
@Override
protected NumberingSystem createInstance(
String key, LocaleLookupData localeLookupData) {
return lookupInstanceByLocale(localeLookupData);
}
};
/** Cache to hold the NumberingSystems by name. */
private static CacheBase<String, NumberingSystem, Void> cachedStringData =
new SoftCache<String, NumberingSystem, Void>() {
@Override
protected NumberingSystem createInstance(String key, Void unused) {
return lookupInstanceByName(key);
}
};
}