ICU-10600 Add currencies (need some ugly hacks for that...)

X-SVN-Rev: 36181
This commit is contained in:
Mark Davis 2014-08-17 15:26:18 +00:00
parent a1213c49b1
commit b741781903
5 changed files with 259 additions and 41 deletions

View file

@ -278,9 +278,8 @@ public class MeasureFormat extends UFormat {
rules,
unitToStyleToCountToFormat,
formatters,
new ImmutableNumberFormat(
NumberFormat.getInstance(locale, formatWidth.getCurrencyStyle())),
new ImmutableNumberFormat(intFormat));
new ImmutableNumberFormat(NumberFormat.getInstance(locale, formatWidth.getCurrencyStyle())),
new ImmutableNumberFormat(intFormat));
}
/**
@ -401,16 +400,38 @@ public class MeasureFormat extends UFormat {
}
Number lowNumber = lowValue.getNumber();
Number highNumber = highValue.getNumber();
final boolean isCurrency = unit instanceof Currency;
UFieldPosition lowFpos = new UFieldPosition();
UFieldPosition highFpos = new UFieldPosition();
StringBuffer lowFormatted = null;
StringBuffer highFormatted = null;
if (isCurrency) {
Currency currency = (Currency) unit;
int fracDigits = currency.getDefaultFractionDigits();
int maxFrac = numberFormat.nf.getMaximumFractionDigits();
int minFrac = numberFormat.nf.getMinimumFractionDigits();
if (fracDigits != maxFrac || fracDigits != minFrac) {
DecimalFormat currentNumberFormat = (DecimalFormat) numberFormat.get();
currentNumberFormat.setMaximumFractionDigits(fracDigits);
currentNumberFormat.setMinimumFractionDigits(fracDigits);
lowFormatted = currentNumberFormat.format(lowNumber, new StringBuffer(), lowFpos);
highFormatted = currentNumberFormat.format(highNumber, new StringBuffer(), highFpos);
}
}
if (lowFormatted == null) {
lowFormatted = numberFormat.format(lowNumber, new StringBuffer(), lowFpos);
highFormatted = numberFormat.format(highNumber, new StringBuffer(), highFpos);
}
UFieldPosition fpos = new UFieldPosition();
StringBuffer lowFormatted = numberFormat.format(lowNumber, new StringBuffer(), fpos);
final double lowDouble = lowNumber.doubleValue();
String keywordLow = rules.select(new PluralRules.FixedDecimal(lowDouble,
fpos.getCountVisibleFractionDigits(), fpos.getFractionDigits()));
lowFpos.getCountVisibleFractionDigits(), lowFpos.getFractionDigits()));
StringBuffer highFormatted = numberFormat.format(highNumber, new StringBuffer(), fpos);
String keywordHigh = rules.select(new PluralRules.FixedDecimal(highNumber.doubleValue(),
fpos.getCountVisibleFractionDigits(), fpos.getFractionDigits()));
final double highDouble = highNumber.doubleValue();
String keywordHigh = rules.select(new PluralRules.FixedDecimal(highDouble,
highFpos.getCountVisibleFractionDigits(), highFpos.getFractionDigits()));
final PluralRanges pluralRanges = Factory.getDefaultFactory().getPluralRanges(getLocale());
StandardPluralCategories resolvedCategory = pluralRanges.get(
@ -419,8 +440,25 @@ public class MeasureFormat extends UFormat {
SimplePatternFormatter rangeFormatter = getRangeFormat(getLocale(), formatWidth);
String formattedNumber = rangeFormatter.format(lowFormatted, highFormatted);
if (unit instanceof Currency) {
throw new IllegalArgumentException("Currency units not supported in ranges");
if (isCurrency) {
// Nasty hack
currencyFormat.format(1d); // have to call this for the side effect
Currency currencyUnit = (Currency) unit;
StringBuilder result = new StringBuilder();
appendReplacingCurrency(currencyFormat.getPrefix(lowDouble >= 0), currencyUnit, resolvedCategory, result);
result.append(formattedNumber);
appendReplacingCurrency(currencyFormat.getSuffix(highDouble >= 0), currencyUnit, resolvedCategory, result);
return result.toString();
// StringBuffer buffer = new StringBuffer();
// CurrencyAmount currencyLow = (CurrencyAmount) lowValue;
// CurrencyAmount currencyHigh = (CurrencyAmount) highValue;
// FieldPosition pos = new FieldPosition(NumberFormat.INTEGER_FIELD);
// currencyFormat.format(currencyLow, buffer, pos);
// int startOfInteger = pos.getBeginIndex();
// StringBuffer buffer2 = new StringBuffer();
// FieldPosition pos2 = new FieldPosition(0);
// currencyFormat.format(currencyHigh, buffer2, pos2);
} else {
Map<FormatWidth, QuantityFormatter> styleToCountToFormat = unitToStyleToCountToFormat.get(lowValue.getUnit());
QuantityFormatter countToFormat = styleToCountToFormat.get(formatWidth);
@ -429,6 +467,38 @@ public class MeasureFormat extends UFormat {
}
}
/**
* @param affix
* @param unit
* @param resolvedCategory
* @param result
* @return
*/
private void appendReplacingCurrency(String affix, Currency unit, StandardPluralCategories resolvedCategory, StringBuilder result) {
String replacement = "¤";
int pos = affix.indexOf(replacement);
if (pos < 0) {
replacement = "XXX";
pos = affix.indexOf(replacement);
}
if (pos < 0) {
result.append(affix);
} else {
// for now, just assume single
result.append(affix.substring(0,pos));
// we have a mismatch between the number style and the currency style, so remap
int currentStyle = formatWidth.getCurrencyStyle();
if (currentStyle == NumberFormat.ISOCURRENCYSTYLE) {
result.append(unit.getCurrencyCode());
} else {
result.append(unit.getName(currencyFormat.nf.getLocale(ULocale.ACTUAL_LOCALE),
currentStyle == NumberFormat.CURRENCYSTYLE ? Currency.SYMBOL_NAME : Currency.PLURAL_LONG_NAME,
resolvedCategory.toString(), null));
}
result.append(affix.substring(pos+replacement.length()));
}
}
/**
* Formats a sequence of measures.
*
@ -775,7 +845,7 @@ public class MeasureFormat extends UFormat {
public synchronized String format(Number number) {
return nf.format(number);
}
public String getPrefix(boolean positive) {
return positive ? ((DecimalFormat)nf).getPositivePrefix() : ((DecimalFormat)nf).getNegativePrefix();
}
@ -1109,7 +1179,7 @@ public class MeasureFormat extends UFormat {
public SimplePatternFormatter getRangeFormat(ULocale forLocale, FormatWidth width) {
// TODO fix Hack for French
if (width != FormatWidth.WIDE && forLocale.getLanguage().equals("fr")) {
if (forLocale.getLanguage().equals("fr")) {
return getRangeFormat(ULocale.ROOT, width);
}
SimplePatternFormatter result = localeIdToRangeFormat.get(forLocale);

View file

@ -52,17 +52,29 @@ public class PluralRangesTest extends TestFmwk {
public void TestFormatting() {
Object[][] tests = {
{0.0, 1.0, ULocale.FRANCE, FormatWidth.WIDE, MeasureUnit.FAHRENHEIT, "de 0 à 1 degré Fahrenheit"},
{1.0, 2.0, ULocale.FRANCE, FormatWidth.WIDE, MeasureUnit.FAHRENHEIT, "de 1 à 2 degrés Fahrenheit"},
{0.0, 1.0, ULocale.FRANCE, FormatWidth.WIDE, MeasureUnit.FAHRENHEIT, "01 degré Fahrenheit"},
{1.0, 2.0, ULocale.FRANCE, FormatWidth.WIDE, MeasureUnit.FAHRENHEIT, "12 degrés Fahrenheit"},
{3.1, 4.25, ULocale.FRANCE, FormatWidth.SHORT, MeasureUnit.FAHRENHEIT, "3,14,25 °F"},
{3.1, 4.25, ULocale.ENGLISH, FormatWidth.SHORT, MeasureUnit.FAHRENHEIT, "3.14.25°F"},
{3.1, 4.25, ULocale.CHINESE, FormatWidth.WIDE, MeasureUnit.INCH, "3.1-4.25英寸"},
{0.0, 1.0, ULocale.ENGLISH, FormatWidth.WIDE, MeasureUnit.INCH, "01 inches"},
{0.0, 1.0, ULocale.ENGLISH, FormatWidth.WIDE, Currency.getInstance("EUR"),
IllegalArgumentException.class},
{0.0, 1.0, ULocale.ENGLISH, FormatWidth.NARROW, Currency.getInstance("EUR"), "€0.001.00"},
{0.0, 1.0, ULocale.FRENCH, FormatWidth.NARROW, Currency.getInstance("EUR"), "0,001,00 "},
{0.0, 100.0, ULocale.FRENCH, FormatWidth.NARROW, Currency.getInstance("JPY"), "0100\u00a0¥JP"},
{0.0, 1.0, ULocale.ENGLISH, FormatWidth.SHORT, Currency.getInstance("EUR"), "EUR0.001.00"},
{0.0, 1.0, ULocale.FRENCH, FormatWidth.SHORT, Currency.getInstance("EUR"), "0,001,00\u00a0EUR"},
{0.0, 100.0, ULocale.FRENCH, FormatWidth.SHORT, Currency.getInstance("JPY"), "0100\u00a0JPY"},
{0.0, 1.0, ULocale.ENGLISH, FormatWidth.WIDE, Currency.getInstance("EUR"), "0.001.00 euros"},
{0.0, 1.0, ULocale.FRENCH, FormatWidth.WIDE, Currency.getInstance("EUR"), "0,001,00 euro"},
{0.0, 2.0, ULocale.FRENCH, FormatWidth.WIDE, Currency.getInstance("EUR"), "0,002,00 euros"},
{0.0, 100.0, ULocale.FRENCH, FormatWidth.WIDE, Currency.getInstance("JPY"), "0100 yens japonais"},
};
int i = 0;
for (Object[] test : tests) {
++i;
double low = (Double) test[0];
double high = (Double) test[1];
final ULocale locale = (ULocale) test[2];
@ -77,7 +89,7 @@ public class PluralRangesTest extends TestFmwk {
} catch (Exception e) {
actual = e.getClass();
}
assertEquals("Formatting unit", expected, actual);
assertEquals(i + " Formatting unit", expected, actual);
}
}

View file

@ -1,6 +1,6 @@
/*
*******************************************************************************
* Copyright (C) 1996-2013, International Business Machines Corporation and *
* Copyright (C) 1996-2014, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
@ -51,7 +51,7 @@ public final class CollectionUtilities {
* @param separator
* @return string
*/
public static <T, U extends Collection<T>>String join(U collection, String separator) {
public static <T, U extends Iterable<T>>String join(U collection, String separator) {
StringBuffer result = new StringBuffer();
boolean first = true;
for (Iterator it = collection.iterator(); it.hasNext();) {

View file

@ -1,6 +1,6 @@
/*
*******************************************************************************
* Copyright (C) 1996-2012, International Business Machines Corporation and *
* Copyright (C) 1996-2014, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
@ -387,6 +387,9 @@ public final class UnicodeMap<T> implements Cloneable, Freezable, StringTransfor
public UnicodeMap<T> put(String string, T value) {
int v = UnicodeSet.getSingleCodePoint(string);
if (v == Integer.MAX_VALUE) {
if (locked) {
throw new UnsupportedOperationException("Attempt to modify locked object");
}
if (value != null) {
if (stringMap == null) {
stringMap = new TreeMap<String,T>();
@ -412,10 +415,11 @@ public final class UnicodeMap<T> implements Cloneable, Freezable, StringTransfor
public UnicodeMap<T> putAll(UnicodeSet codepoints, T value) {
UnicodeSetIterator it = new UnicodeSetIterator(codepoints);
while (it.nextRange()) {
_putAll(it.codepoint, it.codepointEnd, value);
}
for (String key : codepoints.strings()) {
put(key, value);
if (it.string == null) {
_putAll(it.codepoint, it.codepointEnd, value);
} else {
put(it.string, value);
}
}
return this;
}
@ -918,33 +922,56 @@ public final class UnicodeMap<T> implements Cloneable, Freezable, StringTransfor
}
/**
* Struct-like class used to iterate over a UnicodeMap in a for loop.
* Caution: The contents may change during the iteration!
*/
public static class EntryRange<T> {
public int codepoint;
public int codepointEnd;
public String string;
public T value;
@Override
public String toString() {
return (string != null ? Utility.hex(string)
: Utility.hex(codepoint) + (codepoint == codepointEnd ? "" : ".." + Utility.hex(codepointEnd)))
+ "=" + value;
}
}
public Iterable<EntryRange> entryRanges() {
/**
* Returns an Iterable over EntryRange, designed for efficient for loops over UnicodeMaps.
* Caution: For efficiency, the EntryRange may be reused, so the EntryRange may change on each iteration!
* The value is guaranteed never to be null.
* @return entry range, for for loops
*/
public Iterable<EntryRange<T>> entryRanges() {
return new EntryRanges();
}
private class EntryRanges implements Iterable<EntryRange>, Iterator<EntryRange> {
private class EntryRanges implements Iterable<EntryRange<T>>, Iterator<EntryRange<T>> {
int pos;
EntryRange result = new EntryRange();
int lastRealRange = values[length-2] == null ? length - 2 : length - 1;
Iterator<Entry<String, T>> stringIterator = stringMap == null ? null : stringMap.entrySet().iterator();
public Iterator<EntryRange> iterator() {
public Iterator<EntryRange<T>> iterator() {
return this;
}
public boolean hasNext() {
return pos < length-1 || (stringIterator != null && stringIterator.hasNext());
return pos < lastRealRange || (stringIterator != null && stringIterator.hasNext());
}
public EntryRange next() {
if (pos < length-1) {
// a range may be null, but then the next one must not be (except the final range)
if (pos < lastRealRange) {
T temp = values[pos];
if (temp == null) {
temp = values[++pos];
}
result.codepoint = transitions[pos];
result.codepointEnd = transitions[pos+1]-1;
result.string = null;
result.value = values[pos];
result.value = temp;
++pos;
} else {
Entry<String, T> entry = stringIterator.next();
@ -1096,4 +1123,54 @@ public final class UnicodeMap<T> implements Cloneable, Freezable, StringTransfor
// if (DEBUG_WRITE) System.out.println("Trans: " + transitions[i] + ",\t" + currentValue);
// }
// }
public final UnicodeMap<T> removeAll(UnicodeSet set) {
return putAll(set, null);
}
public final UnicodeMap<T> removeAll(UnicodeMap<T> reference) {
return removeRetainAll(reference, true);
}
public final UnicodeMap<T> retainAll(UnicodeSet set) {
UnicodeSet toNuke = new UnicodeSet();
// TODO Optimize
for (EntryRange<T> ae : entryRanges()) {
if (ae.string != null) {
if (!set.contains(ae.string)) {
toNuke.add(ae.string);
}
} else {
for (int i = ae.codepoint; i <= ae.codepointEnd; ++i) {
if (!set.contains(i)) {
toNuke.add(i);
}
}
}
}
return putAll(toNuke, null);
}
public final UnicodeMap<T> retainAll(UnicodeMap<T> reference) {
return removeRetainAll(reference, false);
}
private final UnicodeMap<T> removeRetainAll(UnicodeMap<T> reference, boolean remove) {
UnicodeSet toNuke = new UnicodeSet();
// TODO Optimize
for (EntryRange<T> ae : entryRanges()) {
if (ae.string != null) {
if (ae.value.equals(reference.get(ae.string)) == remove) {
toNuke.add(ae.string);
}
} else {
for (int i = ae.codepoint; i <= ae.codepointEnd; ++i) {
if (ae.value.equals(reference.get(i)) == remove) {
toNuke.add(i);
}
}
}
}
return putAll(toNuke, null);
}
}

View file

@ -1,6 +1,6 @@
/*
*******************************************************************************
* Copyright (C) 1996-2012, International Business Machines Corporation and *
* Copyright (C) 1996-2014, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
@ -17,7 +17,9 @@ import java.util.TreeMap;
import java.util.TreeSet;
import com.ibm.icu.dev.test.TestFmwk;
import com.ibm.icu.dev.util.CollectionUtilities;
import com.ibm.icu.dev.util.UnicodeMap;
import com.ibm.icu.dev.util.UnicodeMap.EntryRange;
import com.ibm.icu.impl.Utility;
import com.ibm.icu.text.UTF16;
import com.ibm.icu.text.UnicodeSet;
@ -35,6 +37,63 @@ public class UnicodeMapTest extends TestFmwk {
new UnicodeMapTest().run(args);
}
public void TestIterations() {
UnicodeMap<Double> foo = new UnicodeMap();
checkToString(foo, "");
foo.put(3, 6d).put(5, 10d);
checkToString(foo, "0003=6.0\n0005=10.0\n");
foo.put(0x10FFFF, 666d);
checkToString(foo, "0003=6.0\n0005=10.0\n10FFFF=666.0\n");
foo.put("neg", -555d);
checkToString(foo, "0003=6.0\n0005=10.0\n10FFFF=666.0\n006E,0065,0067=-555.0\n");
double i = 0;
for (EntryRange<Double> entryRange : foo.entryRanges()) {
i += entryRange.value;
}
assertEquals("EntryRange<T>", 127d, i);
}
public void checkToString(UnicodeMap<Double> foo, String expected) {
assertEquals("EntryRange<T>", expected, CollectionUtilities.join(foo.entryRanges(), "\n") + (foo.size() == 0 ? "" : "\n"));
assertEquals("EntryRange<T>", expected, foo.toString());
}
public void TestRemove() {
UnicodeMap<Double> foo = new UnicodeMap()
.putAll(0x20, 0x29, -2d)
.put("abc", 3d)
.put("xy", 2d)
.put("mark", 4d)
.freeze();
UnicodeMap<Double> fii = new UnicodeMap()
.putAll(0x21, 0x25, -2d)
.putAll(0x26, 0x28, -3d)
.put("abc", 3d)
.put("mark", 999d)
.freeze();
UnicodeMap<Double> afterFiiRemoval = new UnicodeMap()
.put(0x20, -2d)
.putAll(0x26, 0x29, -2d)
.put("xy", 2d)
.put("mark", 4d)
.freeze();
UnicodeMap<Double> afterFiiRetained = new UnicodeMap()
.putAll(0x21, 0x25, -2d)
.put("abc", 3d)
.freeze();
UnicodeMap<Double> test = new UnicodeMap<Double>().putAll(foo)
.removeAll(fii);
assertEquals("removeAll", afterFiiRemoval, test);
test = new UnicodeMap<Double>().putAll(foo)
.retainAll(fii);
assertEquals("retainAll", afterFiiRetained, test);
}
public void TestAMonkey() {
SortedMap<String,Integer> stayWithMe = new TreeMap<String,Integer>(OneFirstComparator);
@ -42,7 +101,7 @@ public class UnicodeMapTest extends TestFmwk {
// check one special case, removal near end
me.putAll(0x10FFFE, 0x10FFFF, 666);
me.remove(0x10FFFF);
int iterations = 100000;
SortedMap<String,Integer> test = new TreeMap();
@ -65,9 +124,9 @@ public class UnicodeMapTest extends TestFmwk {
break;
case 2: case 3: case 4: case 5: case 6: case 7: case 8:
other = getRandomKey(rand);
// if (other.equals("\uDBFF\uDFFF") && me.containsKey(0x10FFFF) && me.get(0x10FFFF).equals(me.get(0x10FFFE))) {
// System.out.println("Remove\t" + other + "\n" + me);
// }
// if (other.equals("\uDBFF\uDFFF") && me.containsKey(0x10FFFF) && me.get(0x10FFFF).equals(me.get(0x10FFFE))) {
// System.out.println("Remove\t" + other + "\n" + me);
// }
logln("remove\t" + other);
stayWithMe.remove(other);
try {
@ -138,7 +197,7 @@ public class UnicodeMapTest extends TestFmwk {
Set<String> nonCodePointStrings = stayWithMe.tailMap("").keySet();
if (nonCodePointStrings.size() == 0) nonCodePointStrings = null; // for parallel api
assertEquals("getNonRangeStrings", nonCodePointStrings, me.getNonRangeStrings());
TreeSet<Integer> values = new TreeSet<Integer>(stayWithMe.values());
TreeSet<Integer> myValues = new TreeSet<Integer>(me.values());
assertEquals("values", myValues, values);
@ -147,7 +206,7 @@ public class UnicodeMapTest extends TestFmwk {
assertEquals("containsKey", stayWithMe.containsKey(key), me.containsKey(key));
}
}
static Comparator<String> OneFirstComparator = new Comparator<String>() {
public int compare(String o1, String o2) {
int cp1 = UnicodeSet.getSingleCodePoint(o1);
@ -161,7 +220,7 @@ public class UnicodeMapTest extends TestFmwk {
}
return 0;
}
};
/**
@ -177,8 +236,8 @@ public class UnicodeMapTest extends TestFmwk {
return UTF16.valueOf('A'-1+r);
} else if (r < 20) {
return UTF16.valueOf(0x10FFFF - (r-10));
// } else if (r == 20) {
// return "";
// } else if (r == 20) {
// return "";
}
return "a" + UTF16.valueOf(r + 'a'-1);
}