ICU-10268 Add the units along the lines of what was discussed. Needs more work, however.

X-SVN-Rev: 34099
This commit is contained in:
Mark Davis 2013-08-28 15:55:54 +00:00
parent ff5564232d
commit 6037d39183
14 changed files with 1468 additions and 128 deletions

View File

@ -1,6 +1,6 @@
/*
**********************************************************************
* Copyright (c) 2004-2012, International Business Machines
* Copyright (c) 2004-2013, International Business Machines
* Corporation and others. All Rights Reserved.
**********************************************************************
* Author: Alan Liu
@ -10,6 +10,7 @@
*/
package com.ibm.icu.text;
import java.io.Serializable;
import java.text.FieldPosition;
import java.text.ParsePosition;
@ -28,7 +29,7 @@ import com.ibm.icu.util.ULocale;
* @see com.ibm.icu.text.DecimalFormat
* @author Alan Liu
*/
class CurrencyFormat extends MeasureFormat {
class CurrencyFormat extends MeasureFormat implements Serializable {
// Generated by serialver from JDK 1.4.1_01
static final long serialVersionUID = -931679363692504634L;

View File

@ -0,0 +1,553 @@
/*
*******************************************************************************
* Copyright (C) 2013, Google Inc, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.text;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.ObjectStreamException;
import java.text.FieldPosition;
import java.text.ParsePosition;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.MissingResourceException;
import java.util.Set;
import java.util.TreeMap;
import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.util.FormatWidth;
import com.ibm.icu.util.Measure;
import com.ibm.icu.util.MeasureUnit;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.UResourceBundle;
import com.ibm.icu.util.ULocale.Category;
/**
* Mutable class for formatting GeneralMeasures, or sequences of them.
* @author markdavis
*/
public class GeneralMeasureFormat extends MeasureFormat {
// Cache the data for units so we don't have to look it up each time.
// For each format, we'll store a pointer into the EnumMap for quick access.
// TODO use the data to allow parsing.
static final transient Map<ULocale,ParseData> localeToParseData = new HashMap<ULocale,ParseData>();
static final transient Map<ULocale,Map<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>>> localeToUnitToStyleToCountToFormat
= new HashMap<ULocale,Map<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>>>();
static final transient Index<MeasureUnit> index = new Index<MeasureUnit>();
static final class PatternData {
final String prefix;
final String suffix;
public PatternData(String pattern) {
int pos = pattern.indexOf("{0}");
if (pos < 0) {
prefix = pattern;
suffix = null;
} else {
prefix = pattern.substring(0,pos);
suffix = pattern.substring(pos+3);
}
}
public String toString() {
return prefix + "; " + suffix;
}
}
private final ULocale locale;
private final FormatWidth length;
private final NumberFormat numberFormat;
private final transient PluralRules rules;
private final transient Map<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>> unitToStyleToCountToFormat; // invariant once built
private transient ParseData parseData; // set as needed
private static final long serialVersionUID = 7922671801770278517L;
/**
* @param styleToCountToFormat2
* @param rules2
* @param rules2
*/
protected GeneralMeasureFormat(ULocale locale, FormatWidth style,
Map<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>> unitToStyleToCountToFormat,
NumberFormat numberFormat) {
this.locale = locale;
this.length = style;
this.unitToStyleToCountToFormat = unitToStyleToCountToFormat;
rules = PluralRules.forLocale(locale);
this.numberFormat = numberFormat;
}
/**
* Create a format from the locale and length
* @param locale locale of this time unit formatter.
* @param length the desired length
* @draft ICU 52
* @provisional This API might change or be removed in a future release.
*/
public static GeneralMeasureFormat getInstance(ULocale locale, FormatWidth length) {
return getInstance(locale, length, NumberFormat.getInstance(locale));
}
/**
* Create a format from the locale and length
* @param locale locale of this time unit formatter.
* @param length the desired length
* @draft ICU 52
* @provisional This API might change or be removed in a future release.
*/
public static GeneralMeasureFormat getInstance(ULocale locale, FormatWidth length,
NumberFormat decimalFormat) {
synchronized (localeToUnitToStyleToCountToFormat) {
Map<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>> unitToStyleToCountToFormat
= localeToUnitToStyleToCountToFormat.get(locale);
if (unitToStyleToCountToFormat == null) {
unitToStyleToCountToFormat = cacheLocaleData(locale);
}
// System.out.println(styleToCountToFormat);
return new GeneralMeasureFormat(locale, length, unitToStyleToCountToFormat, decimalFormat);
}
}
/**
* Return a formatter for CurrencyAmount objects in the given
* locale.
* @param locale desired locale
* @return a formatter object
* @stable ICU 3.0
*/
public static MeasureFormat getCurrencyFormat(ULocale locale) {
return new CurrencyFormat(locale);
}
/**
* Return a formatter for CurrencyAmount objects in the default
* <code>FORMAT</code> locale.
* @return a formatter object
* @see Category#FORMAT
* @stable ICU 3.0
*/
public static MeasureFormat getCurrencyFormat() {
return getCurrencyFormat(ULocale.getDefault(Category.FORMAT));
}
/**
* @return the locale of the format.
*/
public ULocale getLocale() {
return locale;
}
/**
* @return the desired length for the format
*/
public FormatWidth getLength() {
return length;
}
private static Map<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>> cacheLocaleData(ULocale locale) {
PluralRules rules = PluralRules.forLocale(locale);
Set<String> keywords = rules.getKeywords();
Map<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>> unitToStyleToCountToFormat;
localeToUnitToStyleToCountToFormat.put(locale, unitToStyleToCountToFormat
= new HashMap<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>>());
for (MeasureUnit unit : MeasureUnit.getAvailable()) {
EnumMap<FormatWidth, Map<String, PatternData>> styleToCountToFormat = unitToStyleToCountToFormat.get(unit);
if (styleToCountToFormat == null) {
unitToStyleToCountToFormat.put(unit, styleToCountToFormat = new EnumMap<FormatWidth, Map<String, PatternData>>(FormatWidth.class));
}
ICUResourceBundle resource = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, locale);
for (FormatWidth styleItem : FormatWidth.values()) {
try {
ICUResourceBundle unitTypeRes = resource.getWithFallback(styleItem.resourceKey);
ICUResourceBundle unitsRes = unitTypeRes.getWithFallback(unit.getType());
ICUResourceBundle oneUnitRes = unitsRes.getWithFallback(unit.getCode());
Map<String, PatternData> countToFormat = styleToCountToFormat.get(styleItem);
if (countToFormat == null) {
styleToCountToFormat.put(styleItem, countToFormat = new HashMap<String, PatternData>());
}
for (String keyword : keywords) {
UResourceBundle countBundle;
try {
countBundle = oneUnitRes.get(keyword);
} catch (MissingResourceException e) {
continue;
}
String pattern = countBundle.getString();
// System.out.println(styleItem.resourceKey + "/"
// + unit.getType() + "/"
// + unit.getCode() + "/"
// + keyword + "=" + pattern);
PatternData format = new PatternData(pattern);
countToFormat.put(keyword, format);
// System.out.println(styleToCountToFormat);
}
// fill in 'other' for any missing values
PatternData other = countToFormat.get("other");
for (String keyword : keywords) {
if (!countToFormat.containsKey(keyword)) {
countToFormat.put(keyword, other);
}
}
} catch (MissingResourceException e) {
continue;
}
}
// now fill in the holes
fillin:
if (styleToCountToFormat.size() != FormatWidth.values().length) {
Map<String, PatternData> fallback = styleToCountToFormat.get(FormatWidth.SHORT);
if (fallback == null) {
fallback = styleToCountToFormat.get(FormatWidth.WIDE);
}
if (fallback == null) {
break fillin; // TODO use root
}
for (FormatWidth styleItem : FormatWidth.values()) {
Map<String, PatternData> countToFormat = styleToCountToFormat.get(styleItem);
if (countToFormat == null) {
styleToCountToFormat.put(styleItem, countToFormat = new HashMap<String, PatternData>());
for (Entry<String, PatternData> entry : fallback.entrySet()) {
countToFormat.put(entry.getKey(), entry.getValue());
}
}
}
}
}
return unitToStyleToCountToFormat;
}
/* (non-Javadoc)
* @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition)
*/
@SuppressWarnings("unchecked")
@Override
public StringBuffer format(Object arg0, StringBuffer arg1, FieldPosition arg2) {
if (arg0 instanceof Collection) {
Collection<Measure> coll = (Collection<Measure>) arg0;
return format(arg1, arg2, coll.toArray(new Measure[coll.size()]));
} else if (arg0 instanceof Measure[]) {
return format(arg1, arg2, (Measure[]) arg0);
} else {
return format((Measure) arg0, arg1, arg2);
}
}
/**
* Format a general measure (type-safe).
* @param measure the measure to format
* @param stringBuffer as in {@link #format(Object, StringBuffer, FieldPosition)}
* @param fieldPosition as in {@link #format(Object, StringBuffer, FieldPosition)}
* @return passed-in buffer with appended text.
*/
public StringBuffer format(Measure measure, StringBuffer stringBuffer, FieldPosition fieldPosition) {
Number n = measure.getNumber();
MeasureUnit unit = measure.getUnit();
UFieldPosition pos = new UFieldPosition(fieldPosition.getFieldAttribute(), fieldPosition.getField());
StringBuffer formattedNumber = numberFormat.format(n, new StringBuffer(), pos);
String keyword = rules.select(new PluralRules.FixedDecimal(n.doubleValue(), pos.getCountVisibleFractionDigits(), pos.getFractionDigits()));
Map<FormatWidth, Map<String, PatternData>> styleToCountToFormat = unitToStyleToCountToFormat.get(unit);
Map<String, PatternData> countToFormat = styleToCountToFormat.get(length);
PatternData messagePatternData = countToFormat.get(keyword);
stringBuffer.append(messagePatternData.prefix);
if (messagePatternData.suffix != null) { // there is a number (may not happen with, say, Arabic dual)
// Fix field position
fieldPosition.setBeginIndex(pos.getBeginIndex() + messagePatternData.prefix.length());
fieldPosition.setEndIndex(pos.getEndIndex() + messagePatternData.prefix.length());
stringBuffer.append(formattedNumber);
stringBuffer.append(messagePatternData.suffix);
}
return stringBuffer;
}
/**
* Format a sequence of measures.
* @param stringBuffer as in {@link #format(Object, StringBuffer, FieldPosition)}
* @param fieldPosition as in {@link #format(Object, StringBuffer, FieldPosition)}
* @param measures a sequence of one or more measures.
* @return passed-in buffer with appended text.
*/
public StringBuffer format(StringBuffer stringBuffer, FieldPosition fieldPosition, Measure... measures) {
StringBuffer[] results = new StringBuffer[measures.length];
for (int i = 0; i < measures.length; ++i) {
results[i] = format(measures[i], new StringBuffer(), fieldPosition);
}
ListFormatter listFormatter = ListFormatter.getInstance(locale,
length == FormatWidth.WIDE ? ListFormatter.Style.DURATION : ListFormatter.Style.DURATION_SHORT);
return stringBuffer.append(listFormatter.format(results));
}
/**
* Format a sequence of measures.
* @param measures a sequence of one or more measures.
* @return passed-in buffer with appended text.
*/
public String format(Measure... measures) {
StringBuffer result = format(new StringBuffer(), new FieldPosition(0), measures);
return result.toString();
}
static final class ParseData {
transient Map<String,BitSet> prefixMap;
transient Map<String,BitSet> suffixMap;
transient BitSet nullSuffix;
ParseData(ULocale locale, Map<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>> unitToStyleToCountToFormat) {
prefixMap = new TreeMap<String,BitSet>(LONGEST_FIRST);
suffixMap = new TreeMap<String,BitSet>(LONGEST_FIRST);
nullSuffix = new BitSet();
for (Entry<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>> entry3 : unitToStyleToCountToFormat.entrySet()) {
MeasureUnit unit = entry3.getKey();
int unitIndex = index.addItem(unit);
for (Entry<FormatWidth, Map<String, PatternData>> entry : entry3.getValue().entrySet()) {
//Style style = entry.getKey();
for (Entry<String, PatternData> entry2 : entry.getValue().entrySet()) {
//String keyword = entry2.getKey();
PatternData data = entry2.getValue();
setBits(prefixMap, data.prefix, unitIndex);
if (data.suffix == null) {
nullSuffix.set(unitIndex);
} else {
setBits(suffixMap, data.suffix, unitIndex);
}
}
}
}
}
private void setBits(Map<String, BitSet> map, String string, int unitIndex) {
BitSet bs = map.get(string);
if (bs == null) {
map.put(string, bs = new BitSet());
}
bs.set(unitIndex);
}
public static ParseData of(ULocale locale,
Map<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>> unitToStyleToCountToFormat) {
ParseData result = localeToParseData.get(locale);
if (result == null) {
localeToParseData.put(locale, result = new ParseData(locale, unitToStyleToCountToFormat));
// System.out.println("Prefix:\t" + result.prefixMap.size());
// System.out.println("Suffix:\t" + result.suffixMap.size());
}
return result;
}
private Measure parse(NumberFormat numberFormat, String toParse, ParsePosition parsePosition) {
// TODO optimize this as necessary
// In particular, if we've already matched a suffix and number, store that.
// If the same suffix turns up we can jump
int startIndex = parsePosition.getIndex();
Number bestNumber = null;
int bestUnit = -1;
int longestMatch = -1;
int furthestError = -1;
for (Entry<String, BitSet> prefixEntry : prefixMap.entrySet()) {
String prefix = prefixEntry.getKey();
BitSet prefixSet = prefixEntry.getValue();
for (Entry<String, BitSet> suffixEntry : suffixMap.entrySet()) {
String suffix = suffixEntry.getKey();
BitSet suffixSet = suffixEntry.getValue();
parsePosition.setIndex(startIndex);
if (looseMatches(prefix, toParse, parsePosition)) {
// if (nullSuffix.intersects(prefixSet))
//// // can only happen with singular rule
//// if (longestMatch < parsePosition.getIndex()) {
//// longestMatch = parsePosition.getIndex();
//// Collection<Double> samples = rules.getSamples(keyword);
//// bestNumber = samples.iterator().next();
//// bestUnit = unit;
//// }
// }
Number number = numberFormat.parse(toParse, parsePosition);
if (parsePosition.getErrorIndex() >= 0) {
if (furthestError < parsePosition.getErrorIndex()) {
furthestError = parsePosition.getErrorIndex();
}
continue;
}
if (looseMatches(suffix, toParse, parsePosition) && prefixSet.intersects(suffixSet)) {
if (longestMatch < parsePosition.getIndex()) {
longestMatch = parsePosition.getIndex();
bestNumber = number;
bestUnit = getFirst(prefixSet, suffixSet);
}
} else if (furthestError < parsePosition.getErrorIndex()) {
furthestError = parsePosition.getErrorIndex();
}
} else if (furthestError < parsePosition.getErrorIndex()) {
furthestError = parsePosition.getErrorIndex();
}
}
}
if (longestMatch >= 0) {
parsePosition.setIndex(longestMatch);
return new Measure(bestNumber, index.getUnit(bestUnit));
}
parsePosition.setErrorIndex(furthestError);
return null;
}
}
static class Index<T> {
List<T> intToItem = new ArrayList<T>();
Map<T,Integer> itemToInt = new HashMap<T,Integer>();
int getIndex(T item) {
return itemToInt.get(item);
}
T getUnit(int index) {
return intToItem.get(index);
}
int addItem(T item) {
Integer index = itemToInt.get(item);
if (index != null) {
return index;
}
int size = intToItem.size();
itemToInt.put(item, size);
intToItem.add(item);
return size;
}
}
@Override
public Measure parseObject(String toParse, ParsePosition parsePosition) {
if (parseData == null) {
parseData = ParseData.of(locale, unitToStyleToCountToFormat);
}
// int index = parsePosition.getIndex();
// int errorIndex = parsePosition.getIndex();
Measure result = parseData.parse(numberFormat, toParse, parsePosition);
// if (result == null) {
// parsePosition.setIndex(index);
// parsePosition.setErrorIndex(errorIndex);
// result = compatCurrencyFormat.parseCurrency(toParse, parsePosition);
// }
return result;
}
/**
* @param prefixSet
* @param suffixSet
* @return
*/
private static int getFirst(BitSet prefixSet, BitSet suffixSet) {
for (int i = prefixSet.nextSetBit(0); i >= 0; i = prefixSet.nextSetBit(i+1)) {
if (suffixSet.get(i)) {
return i;
}
}
return 0;
}
/**
* @param suffix
* @param arg0
* @param arg1
* @return
*/
// TODO make this lenient
private static boolean looseMatches(String suffix, String arg0, ParsePosition arg1) {
boolean matches = suffix.regionMatches(0, arg0, arg1.getIndex(), suffix.length());
if (matches) {
arg1.setErrorIndex(-1);
arg1.setIndex(arg1.getIndex() + suffix.length());
} else {
arg1.setErrorIndex(arg1.getIndex());
}
return matches;
}
static final Comparator<String> LONGEST_FIRST = new Comparator<String>() {
public int compare(String as, String bs) {
if (as.length() > bs.length()) {
return -1;
}
if (as.length() < bs.length()) {
return 1;
}
return as.compareTo(bs);
}
};
@Override
public boolean equals(Object obj) {
if (obj == null || obj.getClass() != GeneralMeasureFormat.class) {
return false;
}
GeneralMeasureFormat other = (GeneralMeasureFormat) obj;
return locale.equals(other.locale)
&& length == other.length
&& numberFormat.equals(other.numberFormat);
}
@Override
public int hashCode() {
// TODO Auto-generated method stub
return (locale.hashCode() * 37 + length.hashCode()) * 37 + numberFormat.hashCode();
}
private Object writeReplace() throws ObjectStreamException {
return new GeneralMeasureProxy(locale, length, numberFormat);
}
static class GeneralMeasureProxy implements Externalizable {
private ULocale locale;
private FormatWidth length;
private NumberFormat numberFormat;
public GeneralMeasureProxy(ULocale locale, FormatWidth length, NumberFormat numberFormat) {
this.locale = locale;
this.length = length;
this.numberFormat = numberFormat;
}
// Must have public constructor, to enable Externalizable
public GeneralMeasureProxy() {
}
public void writeExternal(ObjectOutput out) throws IOException {
out.writeByte(0); // version
out.writeObject(locale);
out.writeObject(length);
out.writeObject(numberFormat);
out.writeShort(0); // allow for more data.
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
byte version = in.readByte(); // version
locale = (ULocale) in.readObject();
length = (FormatWidth) in.readObject();
numberFormat = (NumberFormat) in.readObject();
// allow for more data from future version
int extra = in.readShort();
if (extra > 0) {
byte[] extraBytes = new byte[extra];
in.read(extraBytes, 0, extra);
}
}
private Object readResolve() throws ObjectStreamException {
return GeneralMeasureFormat.getInstance(locale, length, numberFormat);
}
}
}

View File

@ -6,6 +6,8 @@
*/
package com.ibm.icu.util;
import java.io.IOException;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.lang.ref.SoftReference;
import java.text.ParsePosition;
@ -30,6 +32,7 @@ import com.ibm.icu.text.CurrencyDisplayNames;
import com.ibm.icu.text.CurrencyMetaInfo;
import com.ibm.icu.text.CurrencyMetaInfo.CurrencyDigits;
import com.ibm.icu.text.CurrencyMetaInfo.CurrencyFilter;
import com.ibm.icu.util.MeasureUnit.MeasureUnitProxy;
import com.ibm.icu.util.ULocale.Category;
/**
@ -53,7 +56,6 @@ import com.ibm.icu.util.ULocale.Category;
* @stable ICU 2.2
*/
public class Currency extends MeasureUnit implements Serializable {
// using serialver from jdk1.4.2_05
private static final long serialVersionUID = -5839973855554750484L;
private static final boolean DEBUG = ICUDebug.enabled("currency");
@ -61,11 +63,6 @@ public class Currency extends MeasureUnit implements Serializable {
private static ICUCache<ULocale, List<TextTrieMap<CurrencyStringInfo>>> CURRENCY_NAME_CACHE =
new SimpleCache<ULocale, List<TextTrieMap<CurrencyStringInfo>>>();
/**
* ISO 4217 3-letter code.
*/
private String isoCode;
/**
* Selector for getName() indicating a symbolic name for a
* currency, such as "$" for USD.
@ -192,7 +189,7 @@ public class Currency extends MeasureUnit implements Serializable {
List<String> list = info.currencies(CurrencyFilter.all());
HashSet<Currency> resultSet = new HashSet<Currency>(list.size());
for (String code : list) {
resultSet.add(new Currency(code));
resultSet.add(getInstance(code));
}
return resultSet;
}
@ -207,7 +204,7 @@ public class Currency extends MeasureUnit implements Serializable {
String variant = loc.getVariant();
if ("EURO".equals(variant)) {
return new Currency(EUR_STR);
return getInstance(EUR_STR);
}
String code = currencyCodeCache.get(loc);
@ -230,7 +227,7 @@ public class Currency extends MeasureUnit implements Serializable {
}
currencyCodeCache.put(loc, code);
}
return new Currency(code);
return getInstance(code);
}
/**
@ -250,9 +247,10 @@ public class Currency extends MeasureUnit implements Serializable {
throw new IllegalArgumentException(
"The input currency code is not 3-letter alphabetic code.");
}
return new Currency(theISOCode.toUpperCase(Locale.ENGLISH));
return (Currency) MeasureUnit.addUnit("currency", theISOCode.toUpperCase(Locale.ENGLISH), CURRENCY_FACTORY);
}
private static boolean isAlpha3Code(String code) {
if (code.length() != 3) {
return false;
@ -396,37 +394,12 @@ public class Currency extends MeasureUnit implements Serializable {
private static final ULocale UND = new ULocale("und");
private static final String[] EMPTY_STRING_ARRAY = new String[0];
/**
* Return a hashcode for this currency.
* @stable ICU 2.2
*/
public int hashCode() {
return isoCode.hashCode();
}
/**
* Return true if rhs is a Currency instance,
* is non-null, and has the same currency code.
* @stable ICU 2.2
*/
public boolean equals(Object rhs) {
if (rhs == null) return false;
if (rhs == this) return true;
try {
Currency c = (Currency) rhs;
return isoCode.equals(c.isoCode);
}
catch (ClassCastException e) {
return false;
}
}
/**
* Returns the ISO 4217 3-letter code for this currency object.
* @stable ICU 2.2
*/
public String getCurrencyCode() {
return isoCode;
return code;
}
/**
@ -437,19 +410,19 @@ public class Currency extends MeasureUnit implements Serializable {
* @stable ICU 49
*/
public int getNumericCode() {
int code = 0;
int result = 0;
try {
UResourceBundle bundle = UResourceBundle.getBundleInstance(
ICUResourceBundle.ICU_BASE_NAME,
"currencyNumericCodes",
ICUResourceBundle.ICU_DATA_CLASS_LOADER);
UResourceBundle codeMap = bundle.get("codeMap");
UResourceBundle numCode = codeMap.get(isoCode);
code = numCode.getInt();
UResourceBundle numCode = codeMap.get(code);
result = numCode.getInt();
} catch (MissingResourceException e) {
// fall through
}
return code;
return result;
}
/**
@ -533,7 +506,7 @@ public class Currency extends MeasureUnit implements Serializable {
}
CurrencyDisplayNames names = CurrencyDisplayNames.getInstance(locale);
return nameStyle == SYMBOL_NAME ? names.getSymbol(isoCode) : names.getName(isoCode);
return nameStyle == SYMBOL_NAME ? names.getSymbol(code) : names.getName(code);
}
/**
@ -582,7 +555,7 @@ public class Currency extends MeasureUnit implements Serializable {
}
CurrencyDisplayNames names = CurrencyDisplayNames.getInstance(locale);
return names.getPluralName(isoCode, pluralCount);
return names.getPluralName(code, pluralCount);
}
/**
@ -759,7 +732,7 @@ public class Currency extends MeasureUnit implements Serializable {
*/
public int getDefaultFractionDigits() {
CurrencyMetaInfo info = CurrencyMetaInfo.getInstance();
CurrencyDigits digits = info.currencyDigits(isoCode);
CurrencyDigits digits = info.currencyDigits(code);
return digits.fractionDigits;
}
@ -771,7 +744,7 @@ public class Currency extends MeasureUnit implements Serializable {
*/
public double getRoundingIncrement() {
CurrencyMetaInfo info = CurrencyMetaInfo.getInstance();
CurrencyDigits digits = info.currencyDigits(isoCode);
CurrencyDigits digits = info.currencyDigits(code);
int data1 = digits.roundingIncrement;
@ -798,7 +771,7 @@ public class Currency extends MeasureUnit implements Serializable {
* @stable ICU 2.2
*/
public String toString() {
return isoCode;
return code;
}
/**
@ -809,7 +782,7 @@ public class Currency extends MeasureUnit implements Serializable {
* @stable ICU 3.4
*/
protected Currency(String theISOCode) {
isoCode = theISOCode;
super("currency", theISOCode);
}
// POW10[i] = 10^i
@ -927,5 +900,9 @@ public class Currency extends MeasureUnit implements Serializable {
return Collections.unmodifiableSet(result);
}
}
private Object writeReplace() throws ObjectStreamException {
return new MeasureUnitProxy(type, code);
}
}
//eof

View File

@ -0,0 +1,26 @@
/*
*******************************************************************************
* Copyright (C) 2013, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.util;
/**
* General purpose formatting width enum.
*/
public enum FormatWidth {
WIDE("units"),
SHORT("unitsShort"),
NARROW("unitsNarrow");
/**
* @internal
* @deprecated internal use
*/
public final String resourceKey;
private FormatWidth(String resourceKey) {
this.resourceKey = resourceKey;
}
}

View File

@ -20,7 +20,7 @@ package com.ibm.icu.util;
* <p>Measure objects are parsed and formatted by subclasses of
* MeasureFormat.
*
* <p>Measure objects are immutable.
* <p>Measure objects are immutable. All subclasses must guarantee that.
*
* @see java.lang.Number
* @see com.ibm.icu.util.MeasureUnit
@ -28,7 +28,7 @@ package com.ibm.icu.util;
* @author Alan Liu
* @stable ICU 3.0
*/
public abstract class Measure {
public class Measure {
private final Number number;
@ -40,7 +40,7 @@ public abstract class Measure {
* @param unit the unit
* @stable ICU 3.0
*/
protected Measure(Number number, MeasureUnit unit) {
public Measure(Number number, MeasureUnit unit) {
if (number == null || unit == null) {
throw new NullPointerException();
}

View File

@ -1,15 +1,29 @@
/*
**********************************************************************
* Copyright (c) 2004-2007, International Business Machines
* Corporation and others. All Rights Reserved.
**********************************************************************
* Author: Alan Liu
* Created: April 20, 2004
* Since: ICU 3.0
**********************************************************************
*/
*******************************************************************************
* Copyright (C) 2004-2013, Google Inc, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.util;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Set;
import java.util.TreeSet;
import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.text.UnicodeSet;
/**
* A unit such as length, mass, volume, currency, etc. A unit is
* coupled with a numeric amount to produce a Measure.
@ -18,10 +32,367 @@ package com.ibm.icu.util;
* @author Alan Liu
* @stable ICU 3.0
*/
public abstract class MeasureUnit {
public class MeasureUnit implements Comparable<MeasureUnit>, Serializable {
private static final long serialVersionUID = -1839973855554750484L;
private static final Map<String, Map<String,MeasureUnit>> cache
= new HashMap<String, Map<String,MeasureUnit>>();
protected final String type;
protected final String code;
/**
* @internal
* @deprecated This API is ICU internal only.
* @param code
*/
protected MeasureUnit() {}
protected MeasureUnit(String type, String code) {
this.type = type;
this.code = code;
}
/**
* Create an instance of a measurement unit.
* @param type the type, such as "length"
* @param code the code, such as "meter"
* @return the unit.
* @draft ICU 52
* @provisional This API might change or be removed in a future release.
*/
public static MeasureUnit getInstance(String type, String code) {
Map<String, MeasureUnit> tmp = cache.get(type);
if (tmp != null) {
MeasureUnit result = tmp.get(code);
if (result != null) {
return result;
}
}
if (type == null || !ASCII.containsAll(type) || code == null || ASCII_HYPHEN.containsAll(code)) {
throw new NullPointerException("The type or code are invalid.");
}
synchronized (MeasureUnit.class) {
return (Currency) MeasureUnit.addUnit(type, code, UNIT_FACTORY);
}
}
static final UnicodeSet ASCII = new UnicodeSet('a', 'z').freeze();
static final UnicodeSet ASCII_HYPHEN = new UnicodeSet('-', '-', 'a', 'z').freeze();
protected interface Factory {
MeasureUnit create(String type, String code);
}
private static Factory UNIT_FACTORY = new Factory() {
public MeasureUnit create(String type, String code) {
return new MeasureUnit(type, code);
}
};
static Factory CURRENCY_FACTORY = new Factory() {
public MeasureUnit create(String type, String code) {
return new Currency(code);
}
};
// /**
// * Register a unit for later use
// * @param type the type, such as "length"
// * @param code the code, such as "meter"
// * @return the unit.
// * @draft ICU 52
// * @provisional This API might change or be removed in a future release.
// */
// public static synchronized MeasureUnit registerUnit(String type, String code) {
// MeasureUnit result = getInstance(type, code);
// if (result == null) {
// result = addUnit(type, code, MY_FACTORY);
// }
// return result;
// }
//
static {
// load all of the units for English, since we know that that is a superset.
/**
* units{
* duration{
* day{
* one{"{0} ден"}
* other{"{0} дена"}
* }
*/
ICUResourceBundle resource = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "en");
for (FormatWidth key : FormatWidth.values()) {
try {
ICUResourceBundle unitsTypeRes = resource.getWithFallback(key.resourceKey);
int size = unitsTypeRes.getSize();
for ( int index = 0; index < size; ++index) {
UResourceBundle unitsRes = unitsTypeRes.get(index);
String type = unitsRes.getKey();
int unitsSize = unitsRes.getSize();
for ( int index2 = 0; index2 < unitsSize; ++index2) {
String unitName = unitsRes.get(index2).getKey();
addUnit(type, unitName, UNIT_FACTORY);
}
}
} catch ( MissingResourceException e ) {
continue;
}
}
// preallocate currencies
try {
UResourceBundle bundle = UResourceBundle.getBundleInstance(
ICUResourceBundle.ICU_BASE_NAME,
"currencyNumericCodes",
ICUResourceBundle.ICU_DATA_CLASS_LOADER);
UResourceBundle codeMap = bundle.get("codeMap");
for (Enumeration<String> it = codeMap.getKeys(); it.hasMoreElements();) {
MeasureUnit.addUnit("currency", it.nextElement(), Currency.CURRENCY_FACTORY);
}
} catch (MissingResourceException e) {
// fall through
}
}
// Must only be called at static initialization, or inside synchronized block.
protected static MeasureUnit addUnit(String type, String unitName, Factory factory) {
Map<String, MeasureUnit> tmp = cache.get(type);
if (tmp == null) {
cache.put(type, tmp = new HashMap<String, MeasureUnit>());
} else {
// "intern" the type by setting to first item's type.
type = tmp.entrySet().iterator().next().getValue().type;
}
MeasureUnit unit = tmp.get(unitName);
if (unit == null) {
tmp.put(unitName, unit = factory.create(type, unitName));
}
return unit;
}
/**
* Get all of the available general units' types.
* @return available units
*/
public static Set<String> getAvailableTypes() {
return Collections.unmodifiableSet(cache.keySet());
}
/**
* Get all of the available general units for a given type.
* @return available units
*/
public static Collection<MeasureUnit> getAvailable(String type) {
Map<String, MeasureUnit> units = cache.get(type);
return units == null ? null : Collections.unmodifiableCollection(units.values());
}
/**
* Get all of the available general units.
* @return available units
*/
public static Set<MeasureUnit> getAvailable() {
Set<MeasureUnit> result = new TreeSet<MeasureUnit>();
for (String type : new TreeSet<String>(MeasureUnit.getAvailableTypes())) {
for (MeasureUnit unit : MeasureUnit.getAvailable(type)) {
result.add(unit);
}
}
return result;
}
/**
* Return a hashcode for this currency.
* @stable ICU 2.2
*/
@Override
public int hashCode() {
return code.hashCode() ^ type.hashCode();
}
/**
* Return true if rhs is a Currency instance,
* is non-null, and has the same currency code.
* @stable ICU 2.2
*/
@Override
public boolean equals(Object rhs) {
if (rhs == null) return false;
if (rhs == this) return true;
try {
MeasureUnit c = (MeasureUnit) rhs;
return type.equals(c.type) && code.equals(c.code);
}
catch (ClassCastException e) {
return false;
}
}
/**
*/
public int compareTo(MeasureUnit other) {
int diff;
return this == other ? 0
: (diff = type.compareTo(other.type)) != 0 ? diff
: code.compareTo(other.code);
}
@Override
public String toString() {
return type + "-" + code;
}
/**
* @return the type for this unit
*/
public String getType() {
return type;
}
/**
* @return the code for this unit.
*/
public String getCode() {
return code;
}
/**
* Useful constants. Not necessarily complete: see {@link #getAvailable()}.
* @draft ICU 52
* @provisional This API might change or be removed in a future release.
*/
public static final MeasureUnit
/** Constant for unit of acceleration: g-force */
G_FORCE = MeasureUnit.getInstance("acceleration", "g-force"),
/** Constant for unit of angle: degree */
DEGREE = MeasureUnit.getInstance("angle", "degree"),
/** Constant for unit of angle: minute */
ARC_MINUTE = MeasureUnit.getInstance("angle", "minute"),
/** Constant for unit of angle: second */
ARC_SECOND = MeasureUnit.getInstance("angle", "second"),
/** Constant for unit of area: acre */
ACRE = MeasureUnit.getInstance("area", "acre"),
/** Constant for unit of area: hectare */
HECTARE = MeasureUnit.getInstance("area", "hectare"),
/** Constant for unit of area: square-foot */
SQUARE_FOOT = MeasureUnit.getInstance("area", "square-foot"),
/** Constant for unit of area: square-kilometer */
SQUARE_KILOMETER = MeasureUnit.getInstance("area", "square-kilometer"),
/** Constant for unit of area: square-meter */
SQUARE_METER = MeasureUnit.getInstance("area", "square-meter"),
/** Constant for unit of area: square-mile */
SQUARE_MILE = MeasureUnit.getInstance("area", "square-mile"),
/** Constant for unit of duration: day */
DAY = MeasureUnit.getInstance("duration", "day"),
/** Constant for unit of duration: hour */
HOUR = MeasureUnit.getInstance("duration", "hour"),
/** Constant for unit of duration: millisecond */
MILLISECOND = MeasureUnit.getInstance("duration", "millisecond"),
/** Constant for unit of duration: minute */
MINUTE = MeasureUnit.getInstance("duration", "minute"),
/** Constant for unit of duration: month */
MONTH = MeasureUnit.getInstance("duration", "month"),
/** Constant for unit of duration: second */
SECOND = MeasureUnit.getInstance("duration", "second"),
/** Constant for unit of duration: week */
WEEK = MeasureUnit.getInstance("duration", "week"),
/** Constant for unit of duration: year */
YEAR = MeasureUnit.getInstance("duration", "year"),
/** Constant for unit of length: centimeter */
CENTIMETER = MeasureUnit.getInstance("length", "centimeter"),
/** Constant for unit of length: foot */
FOOT = MeasureUnit.getInstance("length", "foot"),
/** Constant for unit of length: inch */
INCH = MeasureUnit.getInstance("length", "inch"),
/** Constant for unit of length: kilometer */
KILOMETER = MeasureUnit.getInstance("length", "kilometer"),
/** Constant for unit of length: light-year */
LIGHT_YEAR = MeasureUnit.getInstance("length", "light-year"),
/** Constant for unit of length: meter */
METER = MeasureUnit.getInstance("length", "meter"),
/** Constant for unit of length: mile */
MILE = MeasureUnit.getInstance("length", "mile"),
/** Constant for unit of length: millimeter */
MILLIMETER = MeasureUnit.getInstance("length", "millimeter"),
/** Constant for unit of length: picometer */
PICOMETER = MeasureUnit.getInstance("length", "picometer"),
/** Constant for unit of length: yard */
YARD = MeasureUnit.getInstance("length", "yard"),
/** Constant for unit of mass: gram */
GRAM = MeasureUnit.getInstance("mass", "gram"),
/** Constant for unit of mass: kilogram */
KILOGRAM = MeasureUnit.getInstance("mass", "kilogram"),
/** Constant for unit of mass: ounce */
OUNCE = MeasureUnit.getInstance("mass", "ounce"),
/** Constant for unit of mass: pound */
POUND = MeasureUnit.getInstance("mass", "pound"),
/** Constant for unit of power: horsepower */
HORSEPOWER = MeasureUnit.getInstance("power", "horsepower"),
/** Constant for unit of power: kilowatt */
KILOWATT = MeasureUnit.getInstance("power", "kilowatt"),
/** Constant for unit of power: watt */
WATT = MeasureUnit.getInstance("power", "watt"),
/** Constant for unit of pressure: hectopascal */
HECTOPASCAL = MeasureUnit.getInstance("pressure", "hectopascal"),
/** Constant for unit of pressure: inch-hg */
INCH_HG = MeasureUnit.getInstance("pressure", "inch-hg"),
/** Constant for unit of pressure: millibar */
MILLIBAR = MeasureUnit.getInstance("pressure", "millibar"),
/** Constant for unit of speed: kilometer-per-hour */
KILOMETER_PER_HOUR = MeasureUnit.getInstance("speed", "kilometer-per-hour"),
/** Constant for unit of speed: meter-per-second */
METER_PER_SECOND = MeasureUnit.getInstance("speed", "meter-per-second"),
/** Constant for unit of speed: mile-per-hour */
MILE_PER_HOUR = MeasureUnit.getInstance("speed", "mile-per-hour"),
/** Constant for unit of temperature: celsius */
CELSIUS = MeasureUnit.getInstance("temperature", "celsius"),
/** Constant for unit of temperature: fahrenheit */
FAHRENHEIT = MeasureUnit.getInstance("temperature", "fahrenheit"),
/** Constant for unit of volume: cubic-kilometer */
CUBIC_KILOMETER = MeasureUnit.getInstance("volume", "cubic-kilometer"),
/** Constant for unit of volume: cubic-mile */
CUBIC_MILE = MeasureUnit.getInstance("volume", "cubic-mile"),
/** Constant for unit of volume: liter */
LITER = MeasureUnit.getInstance("volume", "liter");
/** Private **/
private Object writeReplace() throws ObjectStreamException {
return new MeasureUnitProxy(type, code);
}
static final class MeasureUnitProxy implements Externalizable {
private String type;
private String code;
public MeasureUnitProxy(String type, String code) {
this.type = type;
this.code = code;
}
// Must have public constructor, to enable Externalizable
public MeasureUnitProxy() {
}
public void writeExternal(ObjectOutput out) throws IOException {
out.writeByte(0); // version
out.writeUTF(type);
out.writeUTF(code);
out.writeShort(0); // allow for more data.
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
byte version = in.readByte(); // version
type = in.readUTF();
code = in.readUTF();
// allow for more data from future version
int extra = in.readShort();
if (extra > 0) {
byte[] extraBytes = new byte[extra];
in.read(extraBytes, 0, extra);
}
}
private Object readResolve() throws ObjectStreamException {
return "currency".equals(type)
? Currency.getInstance(code)
: MeasureUnit.getInstance(type, code);
}
}
}

View File

@ -6,6 +6,7 @@
*/
package com.ibm.icu.util;
/**
* Measurement unit for time units.
* @see TimeUnitAmount
@ -14,10 +15,11 @@ package com.ibm.icu.util;
* @stable ICU 4.0
*/
public class TimeUnit extends MeasureUnit {
private static final long serialVersionUID = -2839973855554750484L;
/**
* Supports selected time duration units
*/
private final String name;
private final int index;
// Total number of time units. Adjust as necessary.
@ -42,7 +44,7 @@ public class TimeUnit extends MeasureUnit {
// idx must be sequential and must order time units from largest to smallest.
// e.g YEAR is 0; MONTH is 1; ...; SECOND is 6.
private TimeUnit(String name, int idx) {
this.name = name;
super("duration", name);
this.index = idx;
values[idx] = this; // store in values array
}
@ -55,16 +57,6 @@ public class TimeUnit extends MeasureUnit {
return values.clone();
}
/**
* A string representation for debugging.
* It is for debugging purpose. The value might change.
* Please do not count on the value.
* @stable ICU 4.0
*/
public String toString() {
return name;
}
// Returns the index for this TimeUnit. Something between 0 inclusive and
// number of time units exclusive. Smaller time units have larger indexes.
int getIndex() {

View File

@ -0,0 +1,306 @@
/*
*******************************************************************************
* Copyright (C) 2013, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.dev.test.format;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.text.FieldPosition;
import java.text.ParsePosition;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.TreeSet;
import com.ibm.icu.dev.test.TestFmwk;
import com.ibm.icu.dev.test.serializable.SerializableTest;
import com.ibm.icu.impl.CurrencyData.CurrencyFormatInfo;
import com.ibm.icu.impl.Utility;
import com.ibm.icu.text.DecimalFormat;
import com.ibm.icu.text.GeneralMeasureFormat;
import com.ibm.icu.text.MeasureFormat;
import com.ibm.icu.text.NumberFormat;
import com.ibm.icu.util.Currency;
import com.ibm.icu.util.FormatWidth;
import com.ibm.icu.util.Measure;
import com.ibm.icu.util.MeasureUnit;
import com.ibm.icu.util.ULocale;
/**
* @author markdavis
*/
public class MeasureUnitTest extends TestFmwk {
/**
* @author markdavis
*
*/
public static void main(String[] args) {
//generateConstants(); if (true) return;
new MeasureUnitTest().run(args);
}
public void testAUnit() {
String lastType = null;
for (MeasureUnit expected : MeasureUnit.getAvailable()) {
String type = expected.getType();
String code = expected.getCode();
if (!type.equals(lastType)) {
logln(type);
lastType = type;
}
MeasureUnit actual = MeasureUnit.getInstance(type, code);
assertSame("Identity check", expected, actual);
}
}
public void testMultiples() {
for (ULocale locale : new ULocale[]{
ULocale.ENGLISH,
new ULocale("ru"),
//ULocale.JAPANESE
}) {
for (FormatWidth style : FormatWidth.values()) {
GeneralMeasureFormat gmlf = GeneralMeasureFormat.getInstance(locale, style);
String formatted = gmlf.format(
new Measure(2, MeasureUnit.MILE),
new Measure(1, MeasureUnit.FOOT),
new Measure(2.3, MeasureUnit.INCH));
logln(locale + ",\t" + style + ": " + formatted);
}
}
}
public void testGram() {
checkRoundtrip(ULocale.ENGLISH, MeasureUnit.GRAM, 1, 0, FormatWidth.SHORT);
checkRoundtrip(ULocale.ENGLISH, MeasureUnit.G_FORCE, 1, 0, FormatWidth.SHORT);
}
public void testRoundtripFormat() {
for (ULocale locale : new ULocale[]{
ULocale.ENGLISH,
new ULocale("ru"),
//ULocale.JAPANESE
}) {
for (MeasureUnit unit : MeasureUnit.getAvailable()) {
for (double d : new double[]{2.1, 1}) {
for (int fractionalDigits : new int[]{0, 1}) {
for (FormatWidth style : FormatWidth.values()) {
checkRoundtrip(locale, unit, d, fractionalDigits, style);
}
}
}
}
}
}
private void checkRoundtrip(ULocale locale, MeasureUnit unit, double d, int fractionalDigits, FormatWidth style) {
if (unit instanceof Currency) {
return; // known limitation
}
Measure amount = new Measure(d, unit);
String header = locale
+ "\t" + unit
+ "\t" + d
+ "\t" + fractionalDigits;
ParsePosition pex = new ParsePosition(0);
NumberFormat nformat = NumberFormat.getInstance(locale);
nformat.setMinimumFractionDigits(fractionalDigits);
GeneralMeasureFormat format = GeneralMeasureFormat.getInstance(locale, style, nformat);
FieldPosition pos = new FieldPosition(DecimalFormat.FRACTION_FIELD);
StringBuffer b = format.format(amount, new StringBuffer(), pos);
String message = header + "\t" + style
+ "\" + b.substring(0, pos.getBeginIndex())
+ "" + b.substring(pos.getBeginIndex(), pos.getEndIndex())
+ "" + b.substring(pos.getEndIndex()) + "»";
pex.setIndex(0);
Measure unitAmount = format.parseObject(b.toString(), pex);
if (!assertNotNull(message, unitAmount)) {
logln("Parse: «"
+ b.substring(0,pex.getErrorIndex())
+ "||" + b.substring(pex.getErrorIndex()) + "»");
} else if (style != FormatWidth.NARROW) { // narrow items may collide
if (unit.equals(MeasureUnit.GRAM)) {
logKnownIssue("cldrupdate", "waiting on collision fix for gram");
return;
}
if (unit.equals(MeasureUnit.ARC_MINUTE) || unit.equals(MeasureUnit.ARC_SECOND) || unit.equals(MeasureUnit.METER)) {
logKnownIssue("8474", "Waiting for CLDR data");
} else {
assertEquals(message + "\tParse Roundtrip of unit", unit, unitAmount.getUnit());
}
double actualNumber = unitAmount.getNumber().doubleValue();
assertEquals(message + "\tParse Roundtrip of number", d, actualNumber);
}
}
public void testExamples() {
GeneralMeasureFormat fmtFr = GeneralMeasureFormat.getInstance(ULocale.FRENCH, FormatWidth.SHORT);
Measure measure = new Measure(23, MeasureUnit.CELSIUS);
assertEquals("", "23°C", fmtFr.format(measure));
Measure measureF = new Measure(70, MeasureUnit.FAHRENHEIT);
assertEquals("", "70°F", fmtFr.format(measureF));
GeneralMeasureFormat fmtFrFull = GeneralMeasureFormat.getInstance(ULocale.FRENCH, FormatWidth.WIDE);
if (!logKnownIssue("8474", "needs latest CLDR data")) {
assertEquals("", "70 pieds, 5,3 pouces", fmtFrFull.format(new Measure(70, MeasureUnit.FOOT),
new Measure(5.3, MeasureUnit.INCH)));
assertEquals("", "1 pied, 1 pouce", fmtFrFull.format(new Measure(1, MeasureUnit.FOOT),
new Measure(1, MeasureUnit.INCH)));
}
// Degenerate case
GeneralMeasureFormat fmtEn = GeneralMeasureFormat.getInstance(ULocale.ENGLISH, FormatWidth.WIDE);
assertEquals("", "1 inch, 2 feet", fmtEn.format(new Measure(1, MeasureUnit.INCH),
new Measure(2, MeasureUnit.FOOT)));
logln("Show all currently available units");
String lastType = null;
for (MeasureUnit unit : MeasureUnit.getAvailable()) {
String type = unit.getType();
if (!type.equals(lastType)) {
logln(type);
lastType = type;
}
logln("\t" + unit);
}
// TODO
// Add these examples (and others) to the class definition.
// Clarify that these classes *do not* do conversion; they simply do the formatting of whatever units they
// are provided.
}
static void generateConstants() {
System.out.println("static final MeasureUnit");
Map<String, MeasureUnit> seen = new HashMap<String, MeasureUnit>();
boolean first = true;
for (String type : new TreeSet<String>(MeasureUnit.getAvailableTypes())) {
for (MeasureUnit unit : MeasureUnit.getAvailable(type)) {
String code = unit.getCode();
String name = code.toUpperCase(Locale.ENGLISH).replace("-", "_");
if (type.equals("angle")) {
if (code.equals("minute") || code.equals("second")) {
name = "ARC_" + name;
}
}
if (first) {
first = false;
} else {
System.out.print(",");
}
if (seen.containsKey(name)) {
System.out.println("\nCollision!!" + unit + ", " + seen.get(name));
} else {
seen.put(name, unit);
}
System.out.println("\n\t/** Constant for unit of " + type +
": " +
code +
" */");
System.out.print("\t" + name + " = MeasureUnit.getInstance(\"" +
type +
"\", \"" +
code +
"\")");
}
System.out.println(";");
}
}
public void TestSerial() {
checkStreamingEquality(MeasureUnit.CELSIUS);
checkStreamingEquality(GeneralMeasureFormat.getInstance(ULocale.FRANCE, FormatWidth.NARROW));
checkStreamingEquality(Currency.getInstance("EUR"));
}
public <T extends Serializable> void checkStreamingEquality(T item) {
try {
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteOut);
objectOutputStream.writeObject(item);
objectOutputStream.close();
byte[] contents = byteOut.toByteArray();
logln("bytes: " + contents.length + "; " + item.getClass() + ": " + showBytes(contents));
ByteArrayInputStream byteIn = new ByteArrayInputStream(contents);
ObjectInputStream objectInputStream = new ObjectInputStream(byteIn);
Object obj = objectInputStream.readObject();
assertEquals("Streamed Object equals ", item, obj);
} catch (IOException e) {
assertNull("Test Serialization " + item.getClass(), e);
} catch (ClassNotFoundException e) {
assertNull("Test Serialization " + item.getClass(), e);
}
}
/**
* @param contents
* @return
*/
private String showBytes(byte[] contents) {
StringBuilder b = new StringBuilder('[');
for (int i = 0; i < contents.length; ++i) {
int item = contents[i] & 0xFF;
if (item >= 0x20 && item <= 0x7F) {
b.append((char) item);
} else {
b.append('(').append(Utility.hex(item, 2)).append(')');
}
}
return b.append(']').toString();
}
public static class MeasureUnitHandler implements SerializableTest.Handler
{
public Object[] getTestObjects()
{
MeasureUnit items[] = {
MeasureUnit.CELSIUS,
Currency.getInstance("EUR")
};
return items;
}
public boolean hasSameBehavior(Object a, Object b)
{
MeasureUnit a1 = (MeasureUnit) a;
MeasureUnit b1 = (MeasureUnit) b;
return a1.getType().equals(b1.getType())
&& a1.getCode().equals(b1.getCode());
}
}
public static class GeneralMeasureFormatHandler implements SerializableTest.Handler
{
public Object[] getTestObjects()
{
GeneralMeasureFormat items[] = {
GeneralMeasureFormat.getInstance(ULocale.FRANCE, FormatWidth.SHORT),
GeneralMeasureFormat.getInstance(ULocale.FRANCE, FormatWidth.WIDE, NumberFormat.getIntegerInstance(ULocale.CANADA_FRENCH
)),
};
return items;
}
public boolean hasSameBehavior(Object a, Object b)
{
GeneralMeasureFormat a1 = (GeneralMeasureFormat) a;
GeneralMeasureFormat b1 = (GeneralMeasureFormat) b;
return a1.getLocale().equals(b1.getLocale())
&& a1.getLength().equals(b1.getLength())
// && a1.getNumberFormat().equals(b1.getNumberFormat())
;
}
}
}

View File

@ -7,6 +7,10 @@
package com.ibm.icu.dev.test.format;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.text.ParsePosition;
import java.util.Collection;
import java.util.LinkedHashMap;
@ -15,6 +19,7 @@ import java.util.Map;
import java.util.Set;
import com.ibm.icu.dev.test.TestFmwk;
import com.ibm.icu.impl.Utility;
import com.ibm.icu.text.DecimalFormat;
import com.ibm.icu.text.DecimalFormatSymbols;
import com.ibm.icu.text.MessageFormat;
@ -23,6 +28,7 @@ import com.ibm.icu.text.PluralFormat;
import com.ibm.icu.text.PluralRules;
import com.ibm.icu.text.PluralRules.PluralType;
import com.ibm.icu.text.PluralRules.SampleType;
import com.ibm.icu.text.UFieldPosition;
import com.ibm.icu.util.ULocale;
/**
@ -85,7 +91,7 @@ public class PluralFormatUnitTest extends TestFmwk {
plfOddOrEven.applyPattern("other{# is odd or even.}");
NumberFormat numberFormat =
NumberFormat.getInstance(ULocale.getDefault());
NumberFormat.getInstance(ULocale.getDefault());
for (int i = 0; i < 22; ++i) {
assertEquals("Fallback to other gave wrong results",
numberFormat.format(i) + " is odd or even.",
@ -98,7 +104,7 @@ public class PluralFormatUnitTest extends TestFmwk {
// ICU 4.8 does not check for duplicate keywords any more.
PluralFormat pf = new PluralFormat(ULocale.ENGLISH, oddAndEven,
"odd{foo} odd{bar} other{foobar}");
"odd{foo} odd{bar} other{foobar}");
assertEquals("should use first occurrence of the 'odd' keyword", "foo", pf.format(1));
pf.applyPattern("odd{foo} other{bar} other{foobar}");
assertEquals("should use first occurrence of the 'other' keyword", "bar", pf.format(2));
@ -111,7 +117,7 @@ public class PluralFormatUnitTest extends TestFmwk {
PluralFormat plFmt = new PluralFormat(oddAndEven);
plFmt.applyPattern("odd{foo}");
errln("Not defining plural case other should result in an " +
"exception but did not.");
"exception but did not.");
}catch (IllegalArgumentException e){}
// ICU 4.8 does not check for unknown keywords any more.
@ -125,7 +131,7 @@ public class PluralFormatUnitTest extends TestFmwk {
PluralFormat plFmt = new PluralFormat(oddAndEven);
plFmt.applyPattern("*odd{foo} other{bar}");
errln("Defining a message for an invalid keyword should result in " +
"an exception but did not.");
"an exception but did not.");
}catch (IllegalArgumentException e){}
// Test invalid syntax
@ -136,26 +142,26 @@ public class PluralFormatUnitTest extends TestFmwk {
PluralFormat plFmt = new PluralFormat(oddAndEven);
plFmt.applyPattern("odd{foo},other{bar}");
errln("Separating keyword{message} items with other characters " +
"than space should provoke an exception but did not.");
"than space should provoke an exception but did not.");
}catch (IllegalArgumentException e){}
try {
PluralFormat plFmt = new PluralFormat(oddAndEven);
plFmt.applyPattern("od d{foo} other{bar}");
errln("Spaces inside keywords should provoke an exception but " +
"did not.");
"did not.");
}catch (IllegalArgumentException e){}
try {
PluralFormat plFmt = new PluralFormat(oddAndEven);
plFmt.applyPattern("odd{foo}{foobar}other{foo}");
errln("Defining multiple messages after a keyword should provoke " +
"an exception but did not.");
"an exception but did not.");
}catch (IllegalArgumentException e){}
// Check that nested format is preserved.
{
PluralFormat plFmt = new PluralFormat(oddAndEven);
plFmt.applyPattern("odd{The number {0, number, #.#0} is odd.}" +
"other{The number {0, number, #.#0} is even.}");
"other{The number {0, number, #.#0} is even.}");
for (int i = 1; i < 3; ++i) {
assertEquals("format did not preserve a nested format string.",
((i % 2 == 1) ?
@ -169,7 +175,7 @@ public class PluralFormatUnitTest extends TestFmwk {
{
PluralFormat plFmt = new PluralFormat(oddAndEven);
plFmt.applyPattern("odd{The number {1,number,#} is odd.}" +
"other{The number {2,number,#} is even.}");
"other{The number {2,number,#} is even.}");
for (int i = 1; i < 3; ++i) {
assertEquals("format did not preserve # inside curly braces.",
((i % 2 == 1) ? "The number {1,number,#} is odd."
@ -180,6 +186,45 @@ public class PluralFormatUnitTest extends TestFmwk {
}
}
public void TestSerial() {
PluralRules s = PluralRules.forLocale(ULocale.ENGLISH);
checkStreamingEquality(s);
}
public void checkStreamingEquality(PluralRules s) {
try {
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteOut);
objectOutputStream.writeObject(s);
objectOutputStream.close();
byte[] contents = byteOut.toByteArray();
logln(s.getClass() + ": " + showBytes(contents));
ByteArrayInputStream byteIn = new ByteArrayInputStream(contents);
ObjectInputStream objectInputStream = new ObjectInputStream(byteIn);
Object obj = objectInputStream.readObject();
assertEquals("Streamed Object equals ", s, obj);
} catch (Exception e) {
assertNull("TestSerial", e);
}
}
/**
* @param contents
* @return
*/
private String showBytes(byte[] contents) {
StringBuilder b = new StringBuilder('[');
for (int i = 0; i < contents.length; ++i) {
int item = contents[i] & 0xFF;
if (item >= 0x20 && item <= 0x7F) {
b.append((char) item);
} else {
b.append('(').append(Utility.hex(item, 2)).append(')');
}
}
return b.append(']').toString();
}
public void TestSamples() {
Map<ULocale,Set<ULocale>> same = new LinkedHashMap();
for (ULocale locale : PluralRules.getAvailableULocales()) {
@ -242,7 +287,7 @@ public class PluralFormatUnitTest extends TestFmwk {
try {
plFmt.parse("test", new ParsePosition(0));
errln("parse() should throw an UnsupportedOperationException but " +
"did not");
"did not");
} catch (UnsupportedOperationException e) {
}
@ -250,7 +295,7 @@ public class PluralFormatUnitTest extends TestFmwk {
try {
plFmt.parseObject("test", new ParsePosition(0));
errln("parse() should throw an UnsupportedOperationException but " +
"did not");
"did not");
} catch (UnsupportedOperationException e) {
}
}
@ -276,7 +321,7 @@ public class PluralFormatUnitTest extends TestFmwk {
* (because it does not create an object for any "complex" argument).
PluralFormat pf = (PluralFormat)pfmt.getFormatsByArgumentIndex()[1];
logln(pf.toPattern());
*/
*/
logln(pfmt.toPattern());
MessageFormat pfmt2 = new MessageFormat(pfmt.toPattern());
assertEquals("message formats are equal", pfmt, pfmt2);
@ -284,22 +329,22 @@ public class PluralFormatUnitTest extends TestFmwk {
public void TestExtendedPluralFormat() {
String[] targets = {
"There are no widgets.",
"There is one widget.",
"There is a bling widget and one other widget.",
"There is a bling widget and 2 other widgets.",
"There is a bling widget and 3 other widgets.",
"Widgets, five (5-1=4) there be.",
"There is a bling widget and 5 other widgets.",
"There is a bling widget and 6 other widgets.",
"There are no widgets.",
"There is one widget.",
"There is a bling widget and one other widget.",
"There is a bling widget and 2 other widgets.",
"There is a bling widget and 3 other widgets.",
"Widgets, five (5-1=4) there be.",
"There is a bling widget and 5 other widgets.",
"There is a bling widget and 6 other widgets.",
};
String pluralStyle =
"offset:1.0 "
+ "=0 {There are no widgets.} "
+ "=1.0 {There is one widget.} "
+ "=5 {Widgets, five (5-1=#) there be.} "
+ "one {There is a bling widget and one other widget.} "
+ "other {There is a bling widget and # other widgets.}";
"offset:1.0 "
+ "=0 {There are no widgets.} "
+ "=1.0 {There is one widget.} "
+ "=5 {Widgets, five (5-1=#) there be.} "
+ "one {There is a bling widget and one other widget.} "
+ "other {There is a bling widget and # other widgets.}";
PluralFormat pf = new PluralFormat(ULocale.ENGLISH, pluralStyle);
MessageFormat mf = new MessageFormat("{0,plural," + pluralStyle + "}", ULocale.ENGLISH);
Integer args[] = new Integer[1];
@ -318,11 +363,11 @@ public class PluralFormatUnitTest extends TestFmwk {
public void TestExtendedPluralFormatParsing() {
String[] failures = {
"offset:1..0 =0 {Foo}",
"offset:1.0 {Foo}",
"=0= {Foo}",
"=0 {Foo} =0.0 {Bar}",
" = {Foo}",
"offset:1..0 =0 {Foo}",
"offset:1.0 {Foo}",
"=0= {Foo}",
"=0 {Foo} =0.0 {Bar}",
" = {Foo}",
};
for (String fmt : failures) {
try {
@ -348,12 +393,47 @@ public class PluralFormatUnitTest extends TestFmwk {
assertEquals("PluralFormat.format(111)", "111th file", pf.format(111));
}
public void TestBasicFraction() {
String[][] tests = {
{"en", "one: j is 1"},
{"1", "0", "1", "one"},
{"1", "2", "1.00", "other"},
};
ULocale locale = null;
NumberFormat nf = null;
PluralRules pr = null;
for (String[] row : tests) {
switch(row.length) {
case 2:
locale = ULocale.forLanguageTag(row[0]);
nf = NumberFormat.getInstance(locale);
pr = PluralRules.createRules(row[1]);
break;
case 4:
double n = Double.parseDouble(row[0]);
int minFracDigits = Integer.parseInt(row[1]);
nf.setMinimumFractionDigits(minFracDigits);
String expectedFormat = row[2];
String expectedKeyword = row[3];
UFieldPosition pos = new UFieldPosition();
String formatted = nf.format(1.0, new StringBuffer(), pos).toString();
int countVisibleFractionDigits = pos.getCountVisibleFractionDigits();
long fractionDigits = pos.getFractionDigits();
String keyword = pr.select(n, countVisibleFractionDigits, fractionDigits);
assertEquals("Formatted " + n + "\t" + minFracDigits, expectedFormat, formatted);
assertEquals("Keyword " + n + "\t" + minFracDigits, expectedKeyword, keyword);
break;
default:
throw new RuntimeException();
}
}
}
public void TestDecimals() {
// Simple number replacement.
PluralFormat pf = new PluralFormat(ULocale.ENGLISH, "one{one meter}other{# meters}");
assertEquals("simple format(1)", "one meter", pf.format(1));
assertEquals("simple format(1.5)", "1.5 meters", pf.format(1.5));
PluralFormat pf2 = new PluralFormat(ULocale.ENGLISH,
"offset:1 one{another meter}other{another # meters}");
pf2.setNumberFormat(new DecimalFormat("0.0", new DecimalFormatSymbols(ULocale.ENGLISH)));

View File

@ -30,6 +30,7 @@ import java.util.TreeMap;
import java.util.TreeSet;
import com.ibm.icu.dev.test.TestFmwk;
import com.ibm.icu.dev.test.serializable.SerializableTest;
import com.ibm.icu.dev.util.CollectionUtilities;
import com.ibm.icu.dev.util.Relation;
import com.ibm.icu.impl.Utility;
@ -42,6 +43,8 @@ import com.ibm.icu.text.PluralRules.KeywordStatus;
import com.ibm.icu.text.PluralRules.FixedDecimal;
import com.ibm.icu.text.PluralRules.PluralType;
import com.ibm.icu.text.PluralRules.SampleType;
import com.ibm.icu.util.Currency;
import com.ibm.icu.util.MeasureUnit;
import com.ibm.icu.util.Output;
import com.ibm.icu.util.ULocale;
@ -1020,4 +1023,26 @@ public class PluralRulesTest extends TestFmwk {
}
logln("max \tsize:\t" + max);
}
public static class FixedDecimalHandler implements SerializableTest.Handler
{
public Object[] getTestObjects()
{
FixedDecimal items[] = {
new FixedDecimal(3d),
new FixedDecimal(3d, 2),
new FixedDecimal(3.1d, 1),
new FixedDecimal(3.1d, 2),
};
return items;
}
public boolean hasSameBehavior(Object a, Object b)
{
FixedDecimal a1 = (FixedDecimal) a;
FixedDecimal b1 = (FixedDecimal) b;
return a1.equals(b1);
}
}
}

View File

@ -13,6 +13,9 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.Enumeration;
@ -114,9 +117,12 @@ public class CompatibilityTest extends TestFmwk
}catch (MissingResourceException e){
warnln("Could not load the data. "+e.getMessage());
} catch (Exception e) {
e.printStackTrace();
errln("Exception: " + e.toString());
if (e.getMessage() == null
|| !e.getMessage().contains("com.ibm.icu.util.Currency")) {
StringWriter b = new StringWriter();
e.printStackTrace(new PrintWriter(b));
assertNull("", b.toString());
}
}
}
}
@ -151,6 +157,12 @@ public class CompatibilityTest extends TestFmwk
{"ICU_51.1", "com.ibm.icu.text.PluralFormat.dat"},
{"ICU_51.1", "com.ibm.icu.text.PluralRules.dat"},
// Currency format changed in 52, which means NumberFormat did also
{"ICU_3.6", "com.ibm.icu.util.Currency.dat"},
{"ICU_3.6", "com.ibm.icu.text.NumberFormat.dat"},
{"ICU_51.1", "com.ibm.icu.util.Currency.dat"},
{"ICU_51.1", "com.ibm.icu.text.NumberFormat.dat"},
{"ICU_3.6", "com.ibm.icu.text.RuleBasedNumberFormat.dat"},
{"ICU_3.8.1", "com.ibm.icu.text.RuleBasedNumberFormat.dat"},
{"ICU_4.0", "com.ibm.icu.text.RuleBasedNumberFormat.dat"},

View File

@ -1,6 +1,6 @@
/*
*******************************************************************************
* Copyright (C) 2005-2012, International Business Machines Corporation and *
* Copyright (C) 2005-2013, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*
@ -110,7 +110,7 @@ public class CoverageTest extends CompatibilityTest implements URLHandler.URLVis
try {
/*Field uid = */c.getDeclaredField("serialVersionUID");
} catch (Exception e) {
errln("No serialVersionUID");
errln("No serialVersionUID for " + name);
}
if (inputStream == null) {

View File

@ -150,18 +150,6 @@ public class ExceptionTests
return exceptions;
}
}
static class FixedDecimalExceptionHandler extends ExceptionHandler
{
public Object[] getTestObjects()
{
IllegalIcuArgumentException[] exceptions = {
new IllegalIcuArgumentException("Bad argument FOO")
};
return exceptions;
}
}
public static void main(String[] args)
{

View File

@ -13,6 +13,8 @@ import java.util.HashMap;
import java.util.Locale;
import com.ibm.icu.dev.test.TestFmwk;
import com.ibm.icu.dev.test.format.MeasureUnitTest;
import com.ibm.icu.dev.test.format.PluralRulesTest;
import com.ibm.icu.impl.JavaTimeZone;
import com.ibm.icu.impl.OlsonTimeZone;
import com.ibm.icu.impl.TimeZoneAdapter;
@ -417,10 +419,14 @@ public class SerializableTest extends TestFmwk.TestGroup
public boolean hasSameBehavior(Object a, Object b)
{
Currency curr_a = (Currency) a;
Currency curr_b = (Currency) b;
return curr_a.getCurrencyCode().equals(curr_b.getCurrencyCode());
return a == b
|| a != null && b != null
&& curr_a.getCurrencyCode() != null
&& curr_a.getCurrencyCode().equals(curr_b.getCurrencyCode());
}
}
@ -709,7 +715,10 @@ public class SerializableTest extends TestFmwk.TestGroup
map.put("com.ibm.icu.impl.locale.LocaleSyntaxException", new ExceptionTests.LocaleSyntaxExceptionHandler());
map.put("com.ibm.icu.impl.IllegalIcuArgumentException", new ExceptionTests.IllegalIcuArgumentExceptionHandler());
map.put("com.ibm.icu.text.PluralRules$FixedDecimal", new ExceptionTests.FixedDecimalExceptionHandler());
map.put("com.ibm.icu.text.PluralRules$FixedDecimal", new PluralRulesTest.FixedDecimalHandler());
map.put("com.ibm.icu.util.MeasureUnit", new MeasureUnitTest.MeasureUnitHandler());
map.put("com.ibm.icu.util.TimeUnit", new MeasureUnitTest.MeasureUnitHandler());
map.put("com.ibm.icu.text.GeneralMeasureFormat", new MeasureUnitTest.GeneralMeasureFormatHandler());
}
public SerializableTest()