ICU-6694 NumberingSystem implementation for ICU4J

X-SVN-Rev: 25319
This commit is contained in:
John Emmons 2009-01-27 20:08:33 +00:00
parent 926677c828
commit 6d3ab9535f
8 changed files with 338 additions and 21 deletions

1
.gitattributes vendored
View File

@ -357,6 +357,7 @@ icu4j/src/com/ibm/icu/dev/tool/docs/icu4j401.api.gz -text
icu4j/src/com/ibm/icu/dev/tool/tzu/icu.gif -text
icu4j/src/com/ibm/icu/impl/data/icudata.jar -text
icu4j/src/com/ibm/icu/text/CurrencyPluralInfo.java -text
icu4j/src/com/ibm/icu/text/NumberingSystem.java -text
icu4j/src/com/ibm/richtext/textapps/resources/unicode.arabic.red -text
icu4j/src/com/ibm/richtext/textapps/resources/unicode.hebrew.red -text
tools/release/java/.classpath -text

View File

@ -1211,6 +1211,28 @@ public class NumberFormatTest extends com.ibm.icu.dev.test.TestFmwk {
}
}
/**
* Test alternate numbering systems
*/
public void TestNumberingSystems() {
ULocale loc1 = new ULocale("en_US@numbers=thai");
ULocale loc2 = new ULocale("en_US@numbers=hebrew");
ULocale loc3 = new ULocale("en_US@numbers=persian");
ULocale loc4 = new ULocale("hi_IN@numbers=foobar");
NumberFormat fmt1 = NumberFormat.getInstance(loc1);
NumberFormat fmt2 = NumberFormat.getInstance(loc2);
NumberFormat fmt3 = NumberFormat.getInstance(loc3);
NumberFormat fmt4 = NumberFormat.getInstance(loc4);
expect2(fmt1,1234.567,"\u0e51,\u0e52\u0e53\u0e54.\u0e55\u0e56\u0e57");
expect3(fmt2,5678.0,"\u05d4\u05f3\u05ea\u05e8\u05e2\u05f4\u05d7");
expect2(fmt3,1234.567,"\u06f1,\u06f2\u06f3\u06f4.\u06f5\u06f6\u06f7");
expect2(fmt4,1234.567,"\u0967,\u0968\u0969\u096a.\u096b\u096c\u096d");
}
public void TestThreadedFormat() {
class FormatTask implements Runnable {
@ -1647,16 +1669,30 @@ public class NumberFormatTest extends com.ibm.icu.dev.test.TestFmwk {
expect(fmt, n, exp, false);
expect(fmt, exp, n);
}
// Format-Parse test
public void expect3(NumberFormat fmt, Number n, String exp) {
// Don't round-trip format test, since we explicitly do it
expect_rbnf(fmt, n, exp, false);
expect_rbnf(fmt, exp, n);
}
// Format-Parse test (convenience)
public void expect2(NumberFormat fmt, double n, String exp) {
expect2(fmt, new Double(n), exp);
}
// Format-Parse test (convenience)
public void expect3(NumberFormat fmt, double n, String exp) {
expect3(fmt, new Double(n), exp);
}
// Format-Parse test (convenience)
public void expect2(NumberFormat fmt, long n, String exp) {
expect2(fmt, new Long(n), exp);
}
// Format-Parse test (convenience)
public void expect3(NumberFormat fmt, long n, String exp) {
expect3(fmt, new Long(n), exp);
}
// Format test
public void expect(NumberFormat fmt, Number n, String exp, boolean rt) {
@ -1691,6 +1727,36 @@ public class NumberFormatTest extends com.ibm.icu.dev.test.TestFmwk {
saw + "\", expected \"" + exp + "\"");
}
}
// Format test
public void expect_rbnf(NumberFormat fmt, Number n, String exp, boolean rt) {
StringBuffer saw = new StringBuffer();
FieldPosition pos = new FieldPosition(0);
fmt.format(n, saw, pos);
if (saw.toString().equals(exp)) {
logln("Ok " + n + " = \"" +
saw + "\"");
// We should be able to round-trip the formatted string =>
// number => string (but not the other way around: number
// => string => number2, might have number2 != number):
if (rt) {
try {
Number n2 = fmt.parse(exp);
StringBuffer saw2 = new StringBuffer();
fmt.format(n2, saw2, pos);
if (!saw2.toString().equals(exp)) {
errln("FAIL \"" + exp + "\" => " + n2 +
" => \"" + saw2 + '"');
}
} catch (ParseException e) {
errln(e.getMessage());
return;
}
}
} else {
errln("FAIL " + n + " = \"" +
saw + "\", expected \"" + exp + "\"");
}
}
// Format test (convenience)
public void expect(NumberFormat fmt, Number n, String exp) {
@ -1730,6 +1796,26 @@ public class NumberFormatTest extends com.ibm.icu.dev.test.TestFmwk {
}
}
// Parse test
public void expect_rbnf(NumberFormat fmt, String str, Number n) {
Number num = null;
try {
num = (Number) fmt.parse(str);
} catch (ParseException e) {
errln(e.getMessage());
return;
}
// A little tricky here -- make sure Double(12345.0) and
// Long(12345) match.
if (num.equals(n) || num.doubleValue() == n.doubleValue()) {
logln("Ok \"" + str + " = " +
num);
} else {
errln("FAIL \"" + str + " = " +
num + ", expected " + n);
}
}
// Parse test (convenience)
public void expect(NumberFormat fmt, String str, double n) {
expect(fmt, str, new Double(n));

View File

@ -1,6 +1,6 @@
/*
*******************************************************************************
* Copyright (C) 1996-2007, International Business Machines Corporation and *
* Copyright (C) 1996-2009, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
@ -157,6 +157,18 @@ public class RbnfRoundTripTest extends TestFmwk {
doTest(formatter, 0, 12345678);
}
/**
* Perform an exhaustive round-trip test on the Greek spellout rules
*/
public void TestHebrewNumberingRT() {
RuleBasedNumberFormat formatter
= new RuleBasedNumberFormat(new Locale("he", "IL",
""), RuleBasedNumberFormat.NUMBERING_SYSTEM);
formatter.setDefaultRuleSet("%hebrew");
doTest(formatter, 0, 12345678);
}
void doTest(RuleBasedNumberFormat formatter, long lowLimit,
long highLimit) {
try {

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:395969705d3ec27761bfabd301b09444aad168747c91b3742f21bde77c21cd36
size 6638663
oid sha256:bbc8472971db3431bbff7018c370518987388f2de3ba89ec4386d9933c7a3b9e
size 6640115

View File

@ -1,6 +1,6 @@
/*
*******************************************************************************
* Copyright (C) 1996-2008, International Business Machines Corporation and *
* Copyright (C) 1996-2009, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
@ -701,6 +701,13 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
padEscape = DecimalFormat.PATTERN_PAD_ESCAPE;
sigDigit = DecimalFormat.PATTERN_SIGNIFICANT_DIGIT;
// Attempt to set the zero digit based on the numbering system for the locale requested
//
NumberingSystem ns = NumberingSystem.getInstance(locale);
if ( ns != null && ns.getRadix() == 10 && !ns.isAlgorithmic()) {
zeroDigit = ns.getDescription().charAt(0);
}
// Obtain currency data from the currency API. This is strictly
// for backward compatibility; we don't use DecimalFormatSymbols
// for currency data anymore.

View File

@ -1320,20 +1320,33 @@ public abstract class NumberFormat extends UFormat {
pattern = Utility.replace(pattern, "\u00A4", doubleCurrencyStr);
}
DecimalFormat format = new DecimalFormat(pattern, symbols, choice);
// System.out.println("loc: " + desiredLocale + " choice: " + choice + " pat: " + pattern + " sym: " + symbols + " result: " + format);
/*Bug 4408066
Add codes for the new method getIntegerInstance() [Richard/GCL]
*/
// TODO: revisit this -- this is almost certainly not the way we want
// to do this. aliu 1/6/2004
if (choice == INTEGERSTYLE) {
format.setMaximumFractionDigits(0);
format.setDecimalSeparatorAlwaysShown(false);
format.setParseIntegerOnly(true);
NumberingSystem ns = NumberingSystem.getInstance(desiredLocale);
if ( ns == null ) {
return null;
}
NumberFormat format;
if ( ns != null && ns.isAlgorithmic()) {
RuleBasedNumberFormat r = new RuleBasedNumberFormat(desiredLocale,RuleBasedNumberFormat.NUMBERING_SYSTEM);
r.setDefaultRuleSet(ns.getDescription());
format = r;
} else {
DecimalFormat f = new DecimalFormat(pattern, symbols, choice);
// System.out.println("loc: " + desiredLocale + " choice: " + choice + " pat: " + pattern + " sym: " + symbols + " result: " + format);
/*Bug 4408066
Add codes for the new method getIntegerInstance() [Richard/GCL]
*/
// TODO: revisit this -- this is almost certainly not the way we want
// to do this. aliu 1/6/2004
if (choice == INTEGERSTYLE) {
f.setMaximumFractionDigits(0);
f.setDecimalSeparatorAlwaysShown(false);
f.setParseIntegerOnly(true);
}
format = f;
}
// TODO: the actual locale of the *pattern* may differ from that
// for the *symbols*. For now, we use the data for the symbols.
// Revisit this.

View File

@ -0,0 +1,189 @@
/*
*******************************************************************************
* Copyright (C) 2009, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.text;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.text.FieldPosition;
import java.text.ParseException;
import java.text.ParsePosition;
import java.util.Collections;
import java.util.Locale;
import java.util.MissingResourceException;
import java.text.Format;
import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.impl.Utility;
import com.ibm.icu.lang.UCharacter;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.UResourceBundle;
import com.ibm.icu.util.UResourceBundleIterator;
import com.ibm.icu.text.UCharacterIterator;
/**
* <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
* @draft ICU 4.2
*/
public class NumberingSystem {
public NumberingSystem() {
radix = 10;
algorithmic = false;
desc = "0123456789";
}
public static NumberingSystem getInstance(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.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;
return ns;
}
public static NumberingSystem getInstance(Locale inLocale) {
return getInstance(ULocale.forLocale(inLocale));
}
public static NumberingSystem getInstance(ULocale locale) {
String numbersKeyword = locale.getKeywordValue("numbers");
if (numbersKeyword != null) {
NumberingSystem ns = getInstanceByName(numbersKeyword);
if ( ns != null ) {
return ns;
}
}
String defaultNumberingSystem;
try {
ICUResourceBundle rb = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME,locale);
defaultNumberingSystem = rb.getString("defaultNumberingSystem");
} catch (MissingResourceException ex) {
return new NumberingSystem();
}
NumberingSystem ns = getInstanceByName(defaultNumberingSystem);
if ( ns != null ) {
return ns;
}
return new NumberingSystem();
}
public static NumberingSystem getInstance() {
return getInstance(ULocale.getDefault());
}
public static NumberingSystem getInstanceByName(String name) {
int radix;
boolean isAlgorithmic;
String description;
try {
UResourceBundle numberingSystemsInfo = UResourceBundle.getBundleInstance(ICUResourceBundle.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(radix,isAlgorithmic,description);
}
public static String [] getAvailableNames() {
UResourceBundle numberingSystemsInfo = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "numberingSystems");
UResourceBundle nsCurrent = numberingSystemsInfo.get("numberingSystems");
UResourceBundle temp;
StringBuffer availableNamesString = new StringBuffer(256);
String nsName;
int count = 0;
UResourceBundleIterator it = nsCurrent.getIterator();
while (it.hasNext()) {
temp = it.next();
nsName = temp.getKey();
if (count > 0) {
availableNamesString.append(":");
}
availableNamesString.append(nsName);
count++;
}
return availableNamesString.toString().split(":");
}
public static boolean isValidDigitString(String str) {
int c;
int prev = 0;
int i = 0;
UCharacterIterator it = UCharacterIterator.getInstance(str);
it.setToStart();
while ( (c = it.nextCodePoint()) != UCharacterIterator.DONE) {
if ( UCharacter.digit(c) != i ) { // Digits outside the Unicode decimal digit class are not currently supported
return false;
}
if ( prev != 0 && c != prev + 1 ) { // Non-contiguous digits are not currently supported
return false;
}
if ( UCharacter.isSupplementary(c)) { // Digits outside the BMP are not currently supported
return false;
}
i++;
prev = c;
}
return true;
}
public int getRadix() {
return radix;
}
public String getDescription() {
return desc;
}
public boolean isAlgorithmic() {
return algorithmic;
}
private String desc;
private int radix;
private boolean algorithmic;
}

View File

@ -509,6 +509,12 @@ public class RuleBasedNumberFormat extends NumberFormat {
*/
public static final int DURATION = 3;
/**
* Selector code that tells the constructor to create a numbering system formatter
* @draft ICU 4.2
*/
public static final int NUMBERING_SYSTEM = 4;
//-----------------------------------------------------------------------
// data members
//-----------------------------------------------------------------------
@ -719,10 +725,12 @@ public class RuleBasedNumberFormat extends NumberFormat {
* and duration.
* @param locale The locale for the formatter.
* @param format A selector code specifying which kind of formatter to create for that
* locale. There are three legal values: SPELLOUT, which creates a formatter that
* locale. There are four legal values: SPELLOUT, which creates a formatter that
* spells out a value in words in the desired language, ORDINAL, which attaches
* an ordinal suffix from the desired language to the end of a number (e.g. "123rd"),
* and DURATION, which formats a duration in seconds as hours, minutes, and seconds.
* DURATION, which formats a duration in seconds as hours, minutes, and seconds, and
* NUMBERING_SYSTEM, which is used to invoke rules for alternate numbering
* systems such as the Hebrew numbering system, or for Roman numerals, etc..
* @stable ICU 3.2
*/
public RuleBasedNumberFormat(ULocale locale, int format) {
@ -771,10 +779,10 @@ public class RuleBasedNumberFormat extends NumberFormat {
}
private static final String[] rulenames = {
"SpelloutRules", "OrdinalRules", "DurationRules",
"SpelloutRules", "OrdinalRules", "DurationRules", "NumberingSystemRules",
};
private static final String[] locnames = {
"SpelloutLocalizations", "OrdinalLocalizations", "DurationLocalizations",
"SpelloutLocalizations", "OrdinalLocalizations", "DurationLocalizations", "NumberingSystemLocalizations",
};
/**
@ -785,6 +793,7 @@ public class RuleBasedNumberFormat extends NumberFormat {
* out a value in words in the default locale's langyage, ORDINAL, which attaches
* an ordinal suffix from the default locale's language to a numeral, and
* DURATION, which formats a duration in seconds as hours, minutes, and seconds.
* or NUMBERING_SYSTEM, which is used for alternate numbering systems such as Hebrew.
* @stable ICU 2.0
*/
public RuleBasedNumberFormat(int format) {